oh-my-knowledge 0.48.0 → 0.49.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -18
- package/README.zh.md +55 -23
- package/dist/analysis/coverage-analyzer.d.ts +1 -0
- package/dist/analysis/coverage-analyzer.js +125 -62
- package/dist/analysis/failure-clusterer.js +2 -1
- package/dist/analysis/gap-analyzer.d.ts +2 -2
- package/dist/analysis/gap-analyzer.js +13 -3
- package/dist/analysis/hedging-classifier.d.ts +2 -2
- package/dist/analysis/hedging-classifier.js +3 -4
- package/dist/analysis/report-diagnostics.js +9 -7
- package/dist/analysis/sample-diagnostics.js +6 -6
- package/dist/artifact-graph/doctor.js +15 -7
- package/dist/assets/agent-skills/omk/SKILL.md +27 -7
- package/dist/assets/agent-skills/omk/references/commands.md +18 -17
- package/dist/authoring/evolver.d.ts +10 -6
- package/dist/authoring/evolver.js +496 -83
- package/dist/authoring/generator.d.ts +3 -3
- package/dist/authoring/generator.js +5 -10
- package/dist/authoring/sample-fixer.d.ts +8 -6
- package/dist/authoring/sample-fixer.js +76 -5
- package/dist/cli/commands/doctor.js +31 -14
- package/dist/cli/commands/eval/index.d.ts +3 -0
- package/dist/cli/commands/eval/index.js +163 -17
- package/dist/cli/commands/evolve.d.ts +4 -4
- package/dist/cli/commands/evolve.js +27 -13
- package/dist/cli/commands/init.js +16 -3
- package/dist/cli/commands/observe/inbox.js +28 -21
- package/dist/cli/commands/observe/index.js +20 -11
- package/dist/cli/commands/observe/ingest.d.ts +3 -0
- package/dist/cli/commands/observe/ingest.js +30 -2
- package/dist/cli/commands/sample.d.ts +6 -3
- package/dist/cli/commands/sample.js +72 -68
- package/dist/cli/lib/codex-model-hint.d.ts +9 -0
- package/dist/cli/lib/codex-model-hint.js +45 -0
- package/dist/cli/lib/generation-failure-hint.d.ts +2 -0
- package/dist/cli/lib/generation-failure-hint.js +61 -0
- package/dist/cli/lib/i18n-dict/common.d.ts +1 -1
- package/dist/cli/lib/i18n-dict/common.js +4 -0
- package/dist/cli/lib/i18n-dict/gen.d.ts +1 -1
- package/dist/cli/lib/i18n-dict/gen.js +38 -6
- package/dist/cli/lib/i18n-dict/help.js +6 -6
- package/dist/cli/lib/i18n-dict/init.d.ts +1 -1
- package/dist/cli/lib/i18n-dict/init.js +13 -9
- package/dist/cli/lib/i18n-dict/run.d.ts +1 -1
- package/dist/cli/lib/i18n-dict/run.js +34 -2
- package/dist/cli/lib/llm-failure-classifier.d.ts +2 -0
- package/dist/cli/lib/llm-failure-classifier.js +8 -0
- package/dist/cli/lib/parse-run-config.d.ts +6 -5
- package/dist/cli/lib/parse-run-config.js +16 -9
- package/dist/cli/lib/runtime-defaults.d.ts +21 -0
- package/dist/cli/lib/runtime-defaults.js +79 -0
- package/dist/diagnosis/observe-mapper.js +14 -15
- package/dist/diagnosis/observe-producer.js +3 -1
- package/dist/diagnosis/studio-projection.js +14 -7
- package/dist/diagnosis/types.d.ts +2 -0
- package/dist/diagnosis/types.js +12 -0
- package/dist/doctor/endpoint-rule.js +2 -1
- package/dist/eval-core/artifact-file-names.js +18 -1
- package/dist/eval-core/artifact-index.d.ts +7 -11
- package/dist/eval-core/artifact-index.js +139 -80
- package/dist/eval-core/cache.d.ts +12 -3
- package/dist/eval-core/cache.js +89 -29
- package/dist/eval-core/comparability.js +10 -6
- package/dist/eval-core/evaluation-execution.d.ts +2 -1
- package/dist/eval-core/evaluation-execution.js +122 -37
- package/dist/eval-core/evaluation-job.d.ts +4 -1
- package/dist/eval-core/evaluation-job.js +4 -1
- package/dist/eval-core/evaluation-reporting.d.ts +15 -13
- package/dist/eval-core/evaluation-reporting.js +54 -52
- package/dist/eval-core/execution-strategy.d.ts +2 -0
- package/dist/eval-core/execution-strategy.js +11 -9
- package/dist/eval-core/fact-checker.js +15 -7
- package/dist/eval-core/holdout.js +3 -2
- package/dist/eval-core/judge-independence.d.ts +2 -2
- package/dist/eval-core/mock-hook.cjs +23 -6
- package/dist/eval-core/mocks-runtime.js +30 -8
- package/dist/eval-core/report-document.d.ts +12 -0
- package/dist/eval-core/report-document.js +1151 -0
- package/dist/eval-core/report-extensions.d.ts +4 -0
- package/dist/eval-core/report-extensions.js +500 -0
- package/dist/eval-core/report-file-migration.js +7 -2
- package/dist/eval-core/resume-compatibility.d.ts +31 -0
- package/dist/eval-core/resume-compatibility.js +141 -0
- package/dist/eval-core/sample-fingerprint.d.ts +12 -0
- package/dist/eval-core/sample-fingerprint.js +193 -0
- package/dist/eval-core/schema.js +86 -31
- package/dist/eval-core/verdict.d.ts +8 -4
- package/dist/eval-core/verdict.js +24 -10
- package/dist/eval-workflows/batch-evaluation-workflow.d.ts +2 -1
- package/dist/eval-workflows/batch-evaluation-workflow.js +25 -12
- package/dist/eval-workflows/evaluation-pipeline/preflight-warnings.d.ts +10 -5
- package/dist/eval-workflows/evaluation-pipeline/preflight-warnings.js +58 -21
- package/dist/eval-workflows/evaluation-pipeline/report-finalize.js +3 -1
- package/dist/eval-workflows/evaluation-pipeline/run-state.d.ts +4 -1
- package/dist/eval-workflows/evaluation-pipeline/run-state.js +4 -1
- package/dist/eval-workflows/evaluation-pipeline/test-set-hash.d.ts +6 -5
- package/dist/eval-workflows/evaluation-pipeline/test-set-hash.js +17 -10
- package/dist/eval-workflows/evaluation-pipeline.js +12 -7
- package/dist/eval-workflows/run-evaluation.d.ts +9 -7
- package/dist/eval-workflows/run-evaluation.js +79 -51
- package/dist/executors/anthropic-api.js +65 -9
- package/dist/executors/claude-cli.js +16 -79
- package/dist/executors/claude-protocol.d.ts +28 -0
- package/dist/executors/claude-protocol.js +180 -0
- package/dist/executors/claude-sdk-trace.js +56 -28
- package/dist/executors/claude-sdk.d.ts +1 -0
- package/dist/executors/claude-sdk.js +39 -93
- package/dist/executors/codex-cli-trace.js +166 -31
- package/dist/executors/codex-cli.d.ts +6 -8
- package/dist/executors/codex-cli.js +49 -151
- package/dist/executors/codex-protocol.d.ts +24 -0
- package/dist/executors/codex-protocol.js +234 -0
- package/dist/executors/codex-sdk.js +68 -120
- package/dist/executors/gemini.js +88 -13
- package/dist/executors/index.d.ts +2 -3
- package/dist/executors/index.js +5 -3
- package/dist/executors/openai-api.js +70 -9
- package/dist/executors/runtime-fingerprint.js +88 -11
- package/dist/executors/script-command.d.ts +8 -0
- package/dist/executors/script-command.js +87 -0
- package/dist/executors/script.js +202 -29
- package/dist/executors/shared.d.ts +35 -3
- package/dist/executors/shared.js +113 -15
- package/dist/grading/assertions.d.ts +1 -1
- package/dist/grading/assertions.js +19 -9
- package/dist/grading/diagnostic.d.ts +9 -2
- package/dist/grading/diagnostic.js +25 -2
- package/dist/grading/index.js +10 -4
- package/dist/grading/judge.js +19 -6
- package/dist/grading/layered-scores.d.ts +2 -3
- package/dist/grading/layered-scores.js +2 -3
- package/dist/inputs/load-samples.d.ts +1 -2
- package/dist/inputs/load-samples.js +23 -1
- package/dist/inputs/mcp-resolver.js +6 -3
- package/dist/inputs/sample-document.d.ts +11 -0
- package/dist/inputs/sample-document.js +96 -0
- package/dist/managed/evidence.d.ts +1 -0
- package/dist/managed/evidence.js +1 -1
- package/dist/managed/store.js +200 -91
- package/dist/observability/codex-trace-adapter.d.ts +5 -0
- package/dist/observability/codex-trace-adapter.js +850 -0
- package/dist/observability/experience.d.ts +32 -6
- package/dist/observability/experience.js +2695 -459
- package/dist/observability/feedback-matchers.js +16 -1
- package/dist/observability/inbox-view-model.d.ts +1 -1
- package/dist/observability/inbox-view-model.js +19 -14
- package/dist/observability/inbox.d.ts +7 -1
- package/dist/observability/inbox.js +632 -124
- package/dist/observability/problem-patterns.js +2 -0
- package/dist/observability/review-state.d.ts +6 -0
- package/dist/observability/review-state.js +235 -63
- package/dist/observability/skill-chain-advisories.js +1 -1
- package/dist/observability/skill-chain.js +17 -4
- package/dist/observability/skill-health-analyzer.d.ts +32 -7
- package/dist/observability/skill-health-analyzer.js +194 -121
- package/dist/observability/skill-health-report.d.ts +10 -0
- package/dist/observability/skill-health-report.js +620 -0
- package/dist/observability/soft-standards/constants.d.ts +0 -1
- package/dist/observability/soft-standards/constants.js +0 -1
- package/dist/observability/soft-standards/index.d.ts +1 -1
- package/dist/observability/soft-standards/index.js +1 -1
- package/dist/observability/soft-standards/llm-extractor.js +8 -10
- package/dist/observability/soft-standards/skill-standards-store.d.ts +2 -1
- package/dist/observability/soft-standards/skill-standards-store.js +59 -18
- package/dist/observability/soft-standards/types.d.ts +2 -2
- package/dist/observability/trace-adapter.d.ts +12 -7
- package/dist/observability/trace-adapter.js +11 -9
- package/dist/observability/trace-attribution.d.ts +13 -5
- package/dist/observability/trace-attribution.js +315 -21
- package/dist/observability/trace-ingestion.d.ts +9 -0
- package/dist/observability/trace-ingestion.js +80 -0
- package/dist/observability/trace-ir.d.ts +113 -0
- package/dist/observability/trace-ir.js +87 -0
- package/dist/observability/trace-segmenter.d.ts +19 -6
- package/dist/observability/trace-segmenter.js +377 -196
- package/dist/observability/trace-session-index.d.ts +19 -0
- package/dist/observability/trace-session-index.js +68 -0
- package/dist/observability/trace-source.d.ts +12 -4
- package/dist/observability/trace-source.js +939 -215
- package/dist/renderer/html-renderer.js +37 -6
- package/dist/renderer/icons.js +3 -0
- package/dist/renderer/observation-inbox-renderer.js +208 -90
- package/dist/renderer/skill-detail-renderer.js +452 -109
- package/dist/renderer/skill-health-renderer.js +69 -12
- package/dist/renderer/summary.js +28 -7
- package/dist/renderer/table.js +21 -4
- package/dist/renderer/test-view.d.ts +1 -0
- package/dist/renderer/test-view.js +44 -9
- package/dist/server/indexed-report-store.js +14 -18
- package/dist/server/job-store.js +64 -26
- package/dist/server/report-server.js +190 -78
- package/dist/server/report-store.js +57 -80
- package/dist/server/skill-index.js +143 -49
- package/dist/server/skill-insights.js +44 -5
- package/dist/shared/artifact-graph.d.ts +3 -0
- package/dist/shared/artifact-graph.js +224 -0
- package/dist/shared/assertion-types.d.ts +8 -0
- package/dist/shared/assertion-types.js +46 -0
- package/dist/shared/atomic-json.d.ts +8 -0
- package/dist/shared/atomic-json.js +33 -0
- package/dist/shared/diagnosis-schema.d.ts +9 -0
- package/dist/shared/diagnosis-schema.js +181 -0
- package/dist/shared/doctor-report.d.ts +3 -0
- package/dist/shared/doctor-report.js +103 -0
- package/dist/shared/evaluation-job.d.ts +6 -0
- package/dist/shared/evaluation-job.js +217 -0
- package/dist/shared/executor-result.d.ts +17 -0
- package/dist/shared/executor-result.js +221 -0
- package/dist/shared/file-lock.d.ts +12 -0
- package/dist/shared/file-lock.js +129 -0
- package/dist/shared/json-value.d.ts +5 -0
- package/dist/shared/json-value.js +36 -0
- package/dist/shared/keyed-mutex.d.ts +7 -0
- package/dist/shared/keyed-mutex.js +24 -0
- package/dist/shared/record-count.d.ts +8 -0
- package/dist/shared/record-count.js +43 -0
- package/dist/shared/sample-contract.d.ts +3 -0
- package/dist/shared/sample-contract.js +332 -0
- package/dist/shared/timestamp.d.ts +6 -0
- package/dist/shared/timestamp.js +64 -0
- package/dist/shared/token-usage.d.ts +19 -0
- package/dist/shared/token-usage.js +50 -0
- package/dist/shared/tool-call-status.d.ts +8 -0
- package/dist/shared/tool-call-status.js +28 -0
- package/dist/shared/tool-identity.d.ts +21 -0
- package/dist/shared/tool-identity.js +84 -0
- package/dist/shared/tool-search.js +73 -16
- package/dist/shared/trace-projection.d.ts +5 -0
- package/dist/shared/trace-projection.js +20 -0
- package/dist/shared/trace-source-kind.d.ts +3 -0
- package/dist/shared/trace-source-kind.js +12 -0
- package/dist/types/diagnosis.d.ts +2 -0
- package/dist/types/eval.d.ts +4 -0
- package/dist/types/executor.d.ts +32 -5
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/judge.d.ts +2 -0
- package/dist/types/observability.d.ts +116 -9
- package/dist/types/report.d.ts +58 -6
- package/dist/types/skill-index.d.ts +7 -0
- package/dist/types/trace.d.ts +2 -0
- package/dist/types/trace.js +1 -0
- package/package.json +9 -5
package/dist/managed/store.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import { existsSync,
|
|
2
|
-
import { join, normalize } from 'node:path';
|
|
1
|
+
import { existsSync, readFileSync, readdirSync, } from 'node:fs';
|
|
2
|
+
import { isAbsolute, join, normalize } from 'node:path';
|
|
3
3
|
import { hashString } from '../eval-core/evaluation-reporting.js';
|
|
4
4
|
import { OMK_HOME } from '../eval-core/default-dirs.js';
|
|
5
|
+
import { writeJsonFileAtomic } from '../shared/atomic-json.js';
|
|
6
|
+
import { withFileLock } from '../shared/file-lock.js';
|
|
7
|
+
import { isRfc3339Timestamp } from '../shared/timestamp.js';
|
|
5
8
|
/**
|
|
6
9
|
* 受管记录的 per-record 文件存储。一条记录一个 `.omk/managed/<id>.json`,镜像 report-store 的
|
|
7
10
|
* 成熟模式(原子 tmp+rename):每次 install 只碰自己那个文件,independent write、不丢别人、
|
|
@@ -19,6 +22,8 @@ export function globalManagedDir() {
|
|
|
19
22
|
return join(OMK_HOME, 'managed');
|
|
20
23
|
}
|
|
21
24
|
export function recordPath(dir, id) {
|
|
25
|
+
if (!isManagedRecordId(id))
|
|
26
|
+
throw new TypeError('invalid managed record id');
|
|
22
27
|
return join(dir, `${id}.json`);
|
|
23
28
|
}
|
|
24
29
|
/** 稳定身份 = hash(kind, name)。源路径是可变属性、不进 id。kind 取自固定枚举(无 `|`),分隔可注入。 */
|
|
@@ -31,6 +36,18 @@ export { hashArtifactSource, isDistributablePath, distributableCopyFilter } from
|
|
|
31
36
|
function isStringField(v) {
|
|
32
37
|
return typeof v === 'string';
|
|
33
38
|
}
|
|
39
|
+
function isNonEmptyString(v) {
|
|
40
|
+
return typeof v === 'string' && v.length > 0;
|
|
41
|
+
}
|
|
42
|
+
function isManagedRecordId(v) {
|
|
43
|
+
return typeof v === 'string' && /^[0-9a-f]{12}$/.test(v);
|
|
44
|
+
}
|
|
45
|
+
function isNonNegativeSafeInteger(v) {
|
|
46
|
+
return typeof v === 'number' && Number.isSafeInteger(v) && v >= 0;
|
|
47
|
+
}
|
|
48
|
+
function isRate(v) {
|
|
49
|
+
return typeof v === 'number' && Number.isFinite(v) && v >= 0 && v <= 1;
|
|
50
|
+
}
|
|
34
51
|
/** 仅当字段缺省或是 string 才放行(可选 string 字段的脏值守卫,如 source.url / evidence.verdict)。 */
|
|
35
52
|
function isOptionalString(v) {
|
|
36
53
|
return v === undefined || typeof v === 'string';
|
|
@@ -47,27 +64,41 @@ function isOptionalSha(v) {
|
|
|
47
64
|
}
|
|
48
65
|
// 受管记录可安装的 kind(managed 记录绝不是 baseline)。
|
|
49
66
|
const MANAGED_KINDS = new Set(['skill', 'prompt', 'agent', 'workflow']);
|
|
67
|
+
const MANAGED_DECISIONS = new Set(['promote', 'reject', 'rollback']);
|
|
68
|
+
const VERDICTS = new Set([
|
|
69
|
+
'PROGRESS',
|
|
70
|
+
'CAUTIOUS',
|
|
71
|
+
'REGRESS',
|
|
72
|
+
'NOISE',
|
|
73
|
+
'UNDERPOWERED',
|
|
74
|
+
'SOLO',
|
|
75
|
+
]);
|
|
76
|
+
const OVERRIDE_VERDICTS = new Set([...VERDICTS, 'UNKNOWN']);
|
|
77
|
+
const OVERRIDDEN_BLOCKS = new Set(['drifted', 'no_evidence', 'incomparable', 'verdict_blocked']);
|
|
50
78
|
// 校验到**运行时实际收窄**的边界,不止「字段是 string」:记录文件是用户可手改、且可能随仓库分发(被
|
|
51
79
|
// loadAllManagedRecords 无 opt-in 读到)的不可信输入。若只查 string,畸形记录(如 source.url 是对象、
|
|
52
80
|
// sourceKind 是任意串)会绕过 validator,在下游(omk list 的 sourceLabel ?? → dispWidth(对象) 等)
|
|
53
81
|
// 抛 TypeError 让命令崩溃。这里把 source / evidence 收窄到 list 等消费方实际解引用的类型,判脏即丢弃该文件。
|
|
54
|
-
function isManagedArtifactRecord(value) {
|
|
82
|
+
function isManagedArtifactRecord(value, expectedId) {
|
|
55
83
|
if (!value || typeof value !== 'object')
|
|
56
84
|
return false;
|
|
57
85
|
const r = value;
|
|
58
86
|
if (!(r.recordKind === 'managed-artifact'
|
|
59
87
|
&& r.schemaVersion === 2
|
|
60
|
-
&&
|
|
61
|
-
&&
|
|
88
|
+
&& isManagedRecordId(r.id)
|
|
89
|
+
&& (expectedId === undefined || r.id === expectedId)
|
|
90
|
+
&& isNonEmptyString(r.name)
|
|
62
91
|
&& isStringField(r.kind) && MANAGED_KINDS.has(r.kind)
|
|
63
|
-
&&
|
|
92
|
+
&& r.id === managedRecordId(r.kind, r.name)
|
|
93
|
+
&& isNonEmptyString(r.contentHash)
|
|
94
|
+
&& isRfc3339Timestamp(r.installedAt)
|
|
64
95
|
&& r.source && typeof r.source === 'object'
|
|
65
96
|
&& Array.isArray(r.distribution)
|
|
66
97
|
&& Array.isArray(r.evidence)
|
|
67
98
|
&& Array.isArray(r.decisions)))
|
|
68
99
|
return false;
|
|
69
100
|
const src = r.source;
|
|
70
|
-
if (!(
|
|
101
|
+
if (!(isNonEmptyString(src.locator)
|
|
71
102
|
&& (src.sourceKind === 'file' || src.sourceKind === 'git')
|
|
72
103
|
&& typeof src.isDirectorySkill === 'boolean'
|
|
73
104
|
&& isOptionalString(src.url)
|
|
@@ -77,22 +108,51 @@ function isManagedArtifactRecord(value) {
|
|
|
77
108
|
// 读取的 locator(畸形但可通过上面 string 校验)→ 「读了什么」与「显示什么」不一致。判脏丢弃。
|
|
78
109
|
if (src.sourceKind === 'file' && (src.url !== undefined || src.ref !== undefined))
|
|
79
110
|
return false;
|
|
80
|
-
const
|
|
81
|
-
|
|
111
|
+
const distributionPaths = new Set();
|
|
112
|
+
const okDist = r.distribution.every((d) => {
|
|
113
|
+
if (!d || typeof d !== 'object')
|
|
114
|
+
return false;
|
|
115
|
+
const target = d;
|
|
116
|
+
if (!isNonEmptyString(target.label)
|
|
117
|
+
|| !isNonEmptyString(target.path)
|
|
118
|
+
|| !isAbsolute(target.path)
|
|
119
|
+
|| !isNonEmptyString(target.contentHash)
|
|
120
|
+
|| !isRfc3339Timestamp(target.copiedAt))
|
|
121
|
+
return false;
|
|
122
|
+
const pathKey = normalize(target.path).replace(/[\\/]+$/, '') || target.path;
|
|
123
|
+
if (distributionPaths.has(pathKey))
|
|
124
|
+
return false;
|
|
125
|
+
distributionPaths.add(pathKey);
|
|
126
|
+
return true;
|
|
127
|
+
});
|
|
128
|
+
const evidenceKeys = new Set();
|
|
82
129
|
const okEv = r.evidence.every((e) => {
|
|
83
130
|
if (!e || typeof e !== 'object')
|
|
84
131
|
return false;
|
|
85
132
|
const ev = e;
|
|
86
|
-
if (!(
|
|
133
|
+
if (!isNonEmptyString(ev.reportId)
|
|
134
|
+
|| !isNonEmptyString(ev.contentHash)
|
|
135
|
+
|| !isRfc3339Timestamp(ev.recordedAt)
|
|
136
|
+
|| (ev.verdict !== undefined && !VERDICTS.has(String(ev.verdict))))
|
|
87
137
|
return false;
|
|
88
|
-
|
|
138
|
+
const evidenceKey = `${ev.reportId}\0${ev.contentHash}`;
|
|
139
|
+
if (evidenceKeys.has(evidenceKey))
|
|
89
140
|
return false;
|
|
141
|
+
evidenceKeys.add(evidenceKey);
|
|
142
|
+
if (ev.sampleCoverage !== undefined) {
|
|
143
|
+
const coverage = ev.sampleCoverage;
|
|
144
|
+
if (!coverage
|
|
145
|
+
|| typeof coverage !== 'object'
|
|
146
|
+
|| !isNonNegativeSafeInteger(coverage.count)
|
|
147
|
+
|| !isNonEmptyString(coverage.hash))
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
90
150
|
// comparability 若存在:必须是带 string cliVersion 的对象(list / promote 会读它)。可选 marker
|
|
91
151
|
// judgePromptHash / debiasMode 同样收窄到声明类型 —— 否则任意类型脏值会穿过 validator 原样进 `omk list
|
|
92
152
|
// --json`(及未来 promote gate)消费方。
|
|
93
153
|
if (ev.comparability !== undefined) {
|
|
94
154
|
const c = ev.comparability;
|
|
95
|
-
if (!c || typeof c !== 'object' || !
|
|
155
|
+
if (!c || typeof c !== 'object' || !isNonEmptyString(c.cliVersion))
|
|
96
156
|
return false;
|
|
97
157
|
if (!isOptionalString(c.judgePromptHash))
|
|
98
158
|
return false;
|
|
@@ -111,36 +171,53 @@ function isManagedArtifactRecord(value) {
|
|
|
111
171
|
if (!d || typeof d !== 'object')
|
|
112
172
|
return false;
|
|
113
173
|
const dec = d;
|
|
114
|
-
if (!
|
|
174
|
+
if (!MANAGED_DECISIONS.has(String(dec.decisionKind))
|
|
175
|
+
|| !isNonEmptyString(dec.actor)
|
|
176
|
+
|| !isRfc3339Timestamp(dec.decidedAt))
|
|
115
177
|
return false;
|
|
116
178
|
// promote/reject/rollback 的证据指针(promote 写)同样收窄:任意类型脏值不得穿过 validator 进
|
|
117
179
|
// deriveManagedState / promote 消费方。actor / decidedAt / reason 是 install 后才追加的可选展示字段,
|
|
118
180
|
// 旧记录(install 时 decisions 恒空)无,按 optional 读。
|
|
119
|
-
if (!isOptionalString(dec.
|
|
181
|
+
if (!isOptionalString(dec.reason))
|
|
182
|
+
return false;
|
|
183
|
+
if ((dec.contentHash !== undefined && !isNonEmptyString(dec.contentHash))
|
|
184
|
+
|| (dec.reportId !== undefined && !isNonEmptyString(dec.reportId)))
|
|
120
185
|
return false;
|
|
121
|
-
if (
|
|
186
|
+
if ((dec.decisionKind === 'promote' || dec.decisionKind === 'rollback')
|
|
187
|
+
&& !isNonEmptyString(dec.contentHash))
|
|
122
188
|
return false;
|
|
123
189
|
if (dec.override !== undefined) {
|
|
124
190
|
const o = dec.override;
|
|
125
|
-
if (
|
|
191
|
+
if (dec.decisionKind !== 'promote'
|
|
192
|
+
|| !o
|
|
193
|
+
|| typeof o !== 'object'
|
|
194
|
+
|| !OVERRIDE_VERDICTS.has(String(o.verdict)))
|
|
126
195
|
return false;
|
|
127
196
|
if (o.overriddenBlocks !== undefined
|
|
128
|
-
&& !(Array.isArray(o.overriddenBlocks)
|
|
197
|
+
&& !(Array.isArray(o.overriddenBlocks)
|
|
198
|
+
&& o.overriddenBlocks.every((block) => OVERRIDDEN_BLOCKS.has(String(block)))))
|
|
129
199
|
return false;
|
|
130
200
|
}
|
|
131
201
|
return true;
|
|
132
202
|
});
|
|
133
203
|
// observations(#235)缺省合法(旧记录无);present 必须是数组,每条窄到声明类型 —— healthBand / confidence
|
|
134
204
|
// 驱动读时 marker(deriveProductionGap),畸形脏值会让 list / Studio 误判,故判脏丢整条记录(同 okEv / okDec)。
|
|
205
|
+
const observationIds = new Set();
|
|
135
206
|
const okObs = r.observations === undefined || (Array.isArray(r.observations) && r.observations.every((o) => {
|
|
136
207
|
if (!o || typeof o !== 'object')
|
|
137
208
|
return false;
|
|
138
209
|
const obs = o;
|
|
139
210
|
if (obs.observationKind !== 'production-health')
|
|
140
211
|
return false;
|
|
141
|
-
if (!
|
|
212
|
+
if (!isNonEmptyString(obs.reportId)
|
|
213
|
+
|| observationIds.has(obs.reportId)
|
|
214
|
+
|| !isRfc3339Timestamp(obs.observedAt))
|
|
142
215
|
return false;
|
|
143
|
-
|
|
216
|
+
observationIds.add(obs.reportId);
|
|
217
|
+
if (!isRate(obs.gapRate)
|
|
218
|
+
|| !isRate(obs.weightedGapRate)
|
|
219
|
+
|| obs.weightedGapRate > obs.gapRate
|
|
220
|
+
|| !isNonNegativeSafeInteger(obs.segmentCount))
|
|
144
221
|
return false;
|
|
145
222
|
if (obs.confidence !== 'high' && obs.confidence !== 'low' && obs.confidence !== 'underpowered')
|
|
146
223
|
return false;
|
|
@@ -149,17 +226,20 @@ function isManagedArtifactRecord(value) {
|
|
|
149
226
|
const g = obs.gapByType;
|
|
150
227
|
if (!g || typeof g !== 'object')
|
|
151
228
|
return false;
|
|
152
|
-
return ['failed_search', 'explicit_marker', 'hedging', 'repeated_failure']
|
|
229
|
+
return ['failed_search', 'explicit_marker', 'hedging', 'repeated_failure']
|
|
230
|
+
.every((key) => isNonNegativeSafeInteger(g[key]));
|
|
153
231
|
}));
|
|
154
232
|
return okDist && okEv && okDec && okObs;
|
|
155
233
|
}
|
|
156
234
|
export function loadManagedRecord(dir, id) {
|
|
235
|
+
if (!isManagedRecordId(id))
|
|
236
|
+
return null;
|
|
157
237
|
const path = recordPath(dir, id);
|
|
158
238
|
if (!existsSync(path))
|
|
159
239
|
return null;
|
|
160
240
|
try {
|
|
161
241
|
const parsed = JSON.parse(readFileSync(path, 'utf-8'));
|
|
162
|
-
return isManagedArtifactRecord(parsed) ? parsed : null;
|
|
242
|
+
return isManagedArtifactRecord(parsed, id) ? parsed : null;
|
|
163
243
|
}
|
|
164
244
|
catch {
|
|
165
245
|
return null;
|
|
@@ -169,12 +249,13 @@ function readRecordsFromDir(dir) {
|
|
|
169
249
|
if (!existsSync(dir))
|
|
170
250
|
return [];
|
|
171
251
|
const out = [];
|
|
172
|
-
for (const entry of readdirSync(dir)) {
|
|
173
|
-
|
|
252
|
+
for (const entry of readdirSync(dir).sort()) {
|
|
253
|
+
const match = /^([0-9a-f]{12})\.json$/.exec(entry);
|
|
254
|
+
if (!match)
|
|
174
255
|
continue;
|
|
175
256
|
try {
|
|
176
257
|
const parsed = JSON.parse(readFileSync(join(dir, entry), 'utf-8'));
|
|
177
|
-
if (isManagedArtifactRecord(parsed))
|
|
258
|
+
if (isManagedArtifactRecord(parsed, match[1]))
|
|
178
259
|
out.push(parsed);
|
|
179
260
|
}
|
|
180
261
|
catch {
|
|
@@ -183,6 +264,17 @@ function readRecordsFromDir(dir) {
|
|
|
183
264
|
}
|
|
184
265
|
return out;
|
|
185
266
|
}
|
|
267
|
+
function persistManagedRecord(dir, record) {
|
|
268
|
+
if (!isManagedArtifactRecord(record, record.id)) {
|
|
269
|
+
throw new TypeError('invalid managed artifact record');
|
|
270
|
+
}
|
|
271
|
+
writeJsonFileAtomic(recordPath(dir, record.id), record);
|
|
272
|
+
}
|
|
273
|
+
function withManagedRecordLock(dir, recordId, operation) {
|
|
274
|
+
if (!isManagedRecordId(recordId))
|
|
275
|
+
throw new TypeError('invalid managed record id');
|
|
276
|
+
return withFileLock(join(dir, `${recordId}.lock`), operation, { label: `managed record ${recordId}` });
|
|
277
|
+
}
|
|
186
278
|
/** 读全部记录。项目目录空 → 兜底全局(镜像 observe inbox 的 project→global)。 */
|
|
187
279
|
export function loadAllManagedRecords(dir = managedDir()) {
|
|
188
280
|
const local = readRecordsFromDir(dir);
|
|
@@ -244,13 +336,14 @@ export function mergeManagedRecord(prev, next) {
|
|
|
244
336
|
}
|
|
245
337
|
/** 读旧记录 → 合并 → 原子 tmp+rename 只写该 id 的文件。返回合并后记录。 */
|
|
246
338
|
export function upsertManagedRecord(dir, record) {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
339
|
+
if (!isManagedArtifactRecord(record, record.id)) {
|
|
340
|
+
throw new TypeError('invalid managed artifact record');
|
|
341
|
+
}
|
|
342
|
+
return withManagedRecordLock(dir, record.id, () => {
|
|
343
|
+
const merged = mergeManagedRecord(loadManagedRecord(dir, record.id), record);
|
|
344
|
+
persistManagedRecord(dir, merged);
|
|
345
|
+
return merged;
|
|
346
|
+
});
|
|
254
347
|
}
|
|
255
348
|
/**
|
|
256
349
|
* 追加一条评测证据(append-only)。与 install 的 upsert 路径相反——`mergeManagedRecord` 刻意**保留旧
|
|
@@ -260,21 +353,25 @@ export function upsertManagedRecord(dir, record) {
|
|
|
260
353
|
* null —— 这是"管理是 install 显式 opt-in"的体现:eval 绝不为未纳管的 skill 凭空建记录。
|
|
261
354
|
*/
|
|
262
355
|
export function appendManagedEvidence(dir, recordId, evidence) {
|
|
263
|
-
|
|
264
|
-
if (!prev)
|
|
356
|
+
if (!isManagedRecordId(recordId))
|
|
265
357
|
return null;
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
358
|
+
return withManagedRecordLock(dir, recordId, () => {
|
|
359
|
+
const prev = loadManagedRecord(dir, recordId);
|
|
360
|
+
if (!prev)
|
|
361
|
+
return null;
|
|
362
|
+
const duplicateIndex = prev.evidence.findIndex((entry) => entry.reportId === evidence.reportId && entry.contentHash === evidence.contentHash);
|
|
363
|
+
const candidateEvidence = duplicateIndex >= 0
|
|
364
|
+
? prev.evidence.map((entry, index) => index === duplicateIndex ? evidence : entry)
|
|
365
|
+
: [...prev.evidence, evidence];
|
|
366
|
+
if (!isManagedArtifactRecord({ ...prev, evidence: candidateEvidence }, recordId)) {
|
|
367
|
+
throw new TypeError('invalid managed evidence');
|
|
368
|
+
}
|
|
369
|
+
if (duplicateIndex >= 0)
|
|
370
|
+
return prev;
|
|
371
|
+
const merged = { ...prev, evidence: candidateEvidence };
|
|
372
|
+
persistManagedRecord(dir, merged);
|
|
373
|
+
return merged;
|
|
374
|
+
});
|
|
278
375
|
}
|
|
279
376
|
/**
|
|
280
377
|
* 追加一条 observe 生产健康观测(append-only,#235)。同 evidence 不走 upsert:`mergeManagedRecord` 保留旧
|
|
@@ -283,21 +380,26 @@ export function appendManagedEvidence(dir, recordId, evidence) {
|
|
|
283
380
|
* 匹配)返回 null —— 与 evidence 同口径:observe 绝不为未纳管 skill 凭空建记录(管理是 install 显式 opt-in)。
|
|
284
381
|
*/
|
|
285
382
|
export function appendManagedObservation(dir, recordId, observation) {
|
|
286
|
-
|
|
287
|
-
if (!prev)
|
|
383
|
+
if (!isManagedRecordId(recordId))
|
|
288
384
|
return null;
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
385
|
+
return withManagedRecordLock(dir, recordId, () => {
|
|
386
|
+
const prev = loadManagedRecord(dir, recordId);
|
|
387
|
+
if (!prev)
|
|
388
|
+
return null;
|
|
389
|
+
const observations = prev.observations ?? [];
|
|
390
|
+
const duplicateIndex = observations.findIndex((entry) => entry.reportId === observation.reportId);
|
|
391
|
+
const candidateObservations = duplicateIndex >= 0
|
|
392
|
+
? observations.map((entry, index) => index === duplicateIndex ? observation : entry)
|
|
393
|
+
: [...observations, observation];
|
|
394
|
+
if (!isManagedArtifactRecord({ ...prev, observations: candidateObservations }, recordId)) {
|
|
395
|
+
throw new TypeError('invalid managed observation');
|
|
396
|
+
}
|
|
397
|
+
if (duplicateIndex >= 0)
|
|
398
|
+
return prev;
|
|
399
|
+
const merged = { ...prev, observations: candidateObservations };
|
|
400
|
+
persistManagedRecord(dir, merged);
|
|
401
|
+
return merged;
|
|
402
|
+
});
|
|
301
403
|
}
|
|
302
404
|
/**
|
|
303
405
|
* 把受管记录的 drift 基线(contentHash)重锚到新源内容哈,保留 evidence / decisions / source /
|
|
@@ -309,18 +411,20 @@ export function appendManagedObservation(dir, recordId, observation) {
|
|
|
309
411
|
* 返回 null(同 evidence / decision:管理是 install 显式 opt-in)。
|
|
310
412
|
*/
|
|
311
413
|
export function rebaselineManagedContentHash(dir, recordId, newHash) {
|
|
312
|
-
|
|
313
|
-
|
|
414
|
+
if (!isNonEmptyString(newHash))
|
|
415
|
+
throw new TypeError('newHash must be a non-empty string');
|
|
416
|
+
if (!isManagedRecordId(recordId))
|
|
314
417
|
return null;
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
418
|
+
return withManagedRecordLock(dir, recordId, () => {
|
|
419
|
+
const prev = loadManagedRecord(dir, recordId);
|
|
420
|
+
if (!prev)
|
|
421
|
+
return null;
|
|
422
|
+
if (prev.contentHash === newHash)
|
|
423
|
+
return prev;
|
|
424
|
+
const merged = { ...prev, contentHash: newHash };
|
|
425
|
+
persistManagedRecord(dir, merged);
|
|
426
|
+
return merged;
|
|
427
|
+
});
|
|
324
428
|
}
|
|
325
429
|
/**
|
|
326
430
|
* 当前内容(contentHash === record.contentHash)是否处于 promoted 态。决定是 append-only 事件流,
|
|
@@ -379,37 +483,42 @@ export function currentPromoteOverride(record) {
|
|
|
379
483
|
* (未 install)返回 null,与 evidence 同口径(管理是 install 显式 opt-in,绝不为未纳管 skill 凭空建记录)。
|
|
380
484
|
*/
|
|
381
485
|
export function appendManagedDecision(dir, recordId, decision) {
|
|
382
|
-
|
|
383
|
-
if (!prev)
|
|
486
|
+
if (!isManagedRecordId(recordId))
|
|
384
487
|
return null;
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
488
|
+
return withManagedRecordLock(dir, recordId, () => {
|
|
489
|
+
const prev = loadManagedRecord(dir, recordId);
|
|
490
|
+
if (!prev)
|
|
491
|
+
return null;
|
|
492
|
+
if (!isManagedArtifactRecord({ ...prev, decisions: [...prev.decisions, decision] }, recordId)) {
|
|
493
|
+
throw new TypeError('invalid managed decision');
|
|
494
|
+
}
|
|
495
|
+
const isPromotion = decision.decisionKind === 'promote' || decision.decisionKind === 'rollback';
|
|
496
|
+
const onCurrent = decision.contentHash !== undefined && decision.contentHash === prev.contentHash;
|
|
497
|
+
const dup = isPromotion && onCurrent
|
|
498
|
+
// promote/rollback 当前内容:不改变 promoted 态 → no-op(promote 当已 promoted、rollback 当未 promoted)。
|
|
499
|
+
? (decision.decisionKind === 'promote') === isCurrentlyPromoted(prev)
|
|
500
|
+
: decision.contentHash !== undefined
|
|
501
|
+
&& prev.decisions.some((d) => d.decisionKind === decision.decisionKind && d.contentHash === decision.contentHash);
|
|
502
|
+
if (dup)
|
|
503
|
+
return prev;
|
|
504
|
+
const merged = { ...prev, decisions: [...prev.decisions, decision] };
|
|
505
|
+
persistManagedRecord(dir, merged);
|
|
506
|
+
return merged;
|
|
507
|
+
});
|
|
403
508
|
}
|
|
404
509
|
/**
|
|
405
510
|
* 统一的记录构造点——所有消费方(CLI / 未来 server / SDK)经此组装,保证 schema 一致。
|
|
406
511
|
* 只接收事实(身份、源、hash、分发落点);evidence/decisions 恒为空(eval/promote 的地盘)。
|
|
407
512
|
*/
|
|
408
513
|
export function buildManagedArtifactRecord(input) {
|
|
514
|
+
const id = input.id ?? managedRecordId(input.kind, input.name);
|
|
515
|
+
if (id !== managedRecordId(input.kind, input.name)) {
|
|
516
|
+
throw new TypeError('managed record id must match kind and name');
|
|
517
|
+
}
|
|
409
518
|
return {
|
|
410
519
|
recordKind: 'managed-artifact',
|
|
411
520
|
schemaVersion: 2,
|
|
412
|
-
id
|
|
521
|
+
id,
|
|
413
522
|
name: input.name,
|
|
414
523
|
kind: input.kind,
|
|
415
524
|
source: input.source,
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Codex rollout JSONL -> source-neutral Trace IR. */
|
|
2
|
+
import type { TraceSession } from './trace-ir.js';
|
|
3
|
+
export declare function isCodexJsonl(records: unknown[]): boolean;
|
|
4
|
+
export declare function isCodexGuardianRollout(records: unknown[]): boolean;
|
|
5
|
+
export declare function parseCodexSessionFile(filePath: string, rawRecords: unknown[]): TraceSession;
|