@quan-huayan/dsh-research-engine 0.1.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.
@@ -0,0 +1,41 @@
1
+ {
2
+ "$id": "https://research-engine.local/contract/conventions.schema.json",
3
+ "title": "convention —— 用户权威(必须带 decision 引用)",
4
+ "type": "object",
5
+ "additionalProperties": false,
6
+ "required": ["schemaVersion"],
7
+ "properties": { "schemaVersion": { "type": "integer", "const": 3 } },
8
+ "$defs": {
9
+ "decision": {
10
+ "type": "object",
11
+ "additionalProperties": false,
12
+ "required": ["askCallId", "answer", "sessionId", "ts"],
13
+ "properties": {
14
+ "askCallId": { "type": "string", "minLength": 1, "description": "用户答复所对应的提问调用标识。" },
15
+ "question": { "type": "string", "description": "提问原文。" },
16
+ "answer": { "type": "string", "minLength": 1, "description": "用户答复原文(工具拷贝,不是模型转述)。" },
17
+ "selected": { "type": "array", "items": { "type": "string" } },
18
+ "custom": { "type": "string", "description": "用户补充说明原文(若有)。" },
19
+ "sessionId": { "type": "string", "minLength": 1 },
20
+ "ts": { "type": "string", "minLength": 1 },
21
+ "attested": { "type": "boolean", "description": "true = 原文在案但无法回源(会话日志缺失)。" }
22
+ }
23
+ },
24
+ "convention": {
25
+ "type": "object",
26
+ "additionalProperties": false,
27
+ "required": ["id", "statement", "authority", "status", "source", "decision", "createdAt"],
28
+ "properties": {
29
+ "id": { "type": "string", "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" },
30
+ "statement": { "type": "string", "minLength": 1 },
31
+ "scope": { "type": "string", "description": "生效范围;缺省 = 全工程。" },
32
+ "authority": { "type": "string", "const": "user" },
33
+ "status": { "type": "string", "enum": ["active", "deprecated"] },
34
+ "source": { "type": "string", "minLength": 1, "description": "依据来源(文件:行 / runId / 实测命令)。" },
35
+ "decision": { "$ref": "#/$defs/decision" },
36
+ "createdAt": { "type": "string", "minLength": 1 },
37
+ "deprecatedAt": { "type": "string" }
38
+ }
39
+ }
40
+ }
41
+ }
@@ -0,0 +1,150 @@
1
+ # research-engine 八要素字段字典(契约 v3)
2
+
3
+ > 与 `contract/*.schema.json` 配套:schema 管结构与类型,本表管**语义、来源、校验**。
4
+ > 每一行 = 一个字段在物理上的唯一落点、谁能写它、写入时校验什么、读它时怎么判合法。
5
+
6
+ ## 0. 落点速查
7
+
8
+ | 要素 | 物理落点 | 属主 |
9
+ |---|---|---|
10
+ | 对话 | `registry.jsonl` 的 `decision` 事件 | exp-ledger |
11
+ | 环境 | `project.yaml` / `pipeline.yaml` / `templates.yaml` / `scripts/*.meta.json` | exp-ledger / stage-ctrl / task-dispatch |
12
+ | 状态 | `registry.jsonl`(权威)+ `.research/state.json`、`_report/*`(派生) | exp-ledger / stage-ctrl / kb-core |
13
+ | 证据 | `experiments/<runId>/{config,manifest,observations}.json` + `raw/` | task-dispatch(raw 由进程写) |
14
+ | 用户权威 | `project.yaml.conventions[]` + `decision` 事件 | exp-ledger |
15
+ | 证据权威 | `experiments/<runId>/observations.json` | task-dispatch |
16
+ | agent 提议 | `.kb/notes/*.md`(`status: proposed`) | kb-core |
17
+ | 结论 | `.kb/notes/*.md`(`kind: claim`) | kb-core |
18
+
19
+ ## 1. `registry.jsonl` 事件(append-only)
20
+
21
+ 每行一个 JSON 对象,公共字段:
22
+
23
+ | 字段 | 类型 | 必填 | 来源 | 校验 |
24
+ |---|---|---|---|---|
25
+ | `record` | string | 是 | 事件类型 | 枚举:`project/entity/decision/pipeline/stage/run/observe/verify/render/reconcile/note` |
26
+ | `ts` | string | 是 | 工具时钟 | ISO 8601 |
27
+ | `actor` | string | 是 | 工具 | `engine`(工具自身)或 `user`(带裁决) |
28
+ | `seq` | integer | 是 | 追加时自增 | 必须 = 行号;旧台账缺此字段按行号解释 |
29
+ | `prev` | string | 是 | 上一行内容的 sha256 | 空台账为 `"genesis"`;断链即台账被外部改写 |
30
+ | `commit` | string | 否 | 版本库 | **内部字段**,绝不进入模型可见文本 |
31
+
32
+ 各 `record` 的专有字段:
33
+
34
+ | record | 字段 |
35
+ |---|---|
36
+ | `project` | `action`(`init`/`reconcile`), `root`, `id` |
37
+ | `entity` | `id`, `kind`, `name?`, `props?`, `source` |
38
+ | `decision` | `kind`(`convention`/`note`/`run-archive`), `id`, `askCallId`, `question?`, `answer`, `sessionId`, `attested?` |
39
+ | `pipeline` | `action`(`stage`/`edge`), `stage?`, `edgeFrom?`, `edgeTo?`, `entry`, `graphVersion` |
40
+ | `stage` | `from`, `to`, `graphVersion`(**只有真正的阶段转移**;声明走 `pipeline`) |
41
+ | `run` | `runId`, `template`, `templateVersion`, `stage`, `kind`, `status`, `params`, `paramsHash`, `graphVersion`, `jobId?`, `detail?`, `outputs?`, `complete?` |
42
+ | `observe` | `runId`, `fields[]`, `compareTo?` |
43
+ | `verify` | `status`(`clean`/`dirty`/`unknown`), `dirty[]`, `unknown[]`, `attested[]` |
44
+ | `render` | `kind`(`notes-skill`/`report`/`index`/`state`), `paths[]`, `count` |
45
+ | `reconcile` | `mode`, `paths[]`, `reason`, `decision?` |
46
+ | `note` | `name`, `kind`, `status`, `authority`, `decidedBy?` |
47
+
48
+ **重放语义**:状态 = 按 `seq` 折叠事件。`run` 取该 runId 最后一条(字段逐条合并);`stage` 取最后一条的 `to`(`pipeline` 事件不参与当前阶段的折叠)。
49
+
50
+ ## 2. `project.yaml`
51
+
52
+ | 字段 | 类型 | 必填 | 来源 | 校验 |
53
+ |---|---|---|---|---|
54
+ | `schemaVersion` | integer | 是 | 常量 3 | 不等则拒绝写入 |
55
+ | `project.id` | string | 是 | `project_init` | kebab-case,与目录名建议一致 |
56
+ | `project.name` | string | 是 | `project_init` / 用户 | 非空 |
57
+ | `project.domain` | string | 否 | `project_init` | — |
58
+ | `managed` | string[] | 是 | `contract/managed.json` | 必须与受管白名单一致 |
59
+ | `conventions[]` | convention[] | 是 | `convention_declare` | 每条必须带 `decision`;`authority` 恒为 `user` |
60
+ | `vocabulary` | object | 否 | `project_init` / 迁移 | 开放词汇 |
61
+ | `legacy.keep[]` | {pattern,count} | 否 | `project_init` | 只登记不哈希 |
62
+
63
+ ### convention
64
+
65
+ | 字段 | 类型 | 必填 | 来源 | 校验 |
66
+ |---|---|---|---|---|
67
+ | `id` | string | 是 | 工具生成(kebab) | 工程内唯一 |
68
+ | `statement` | string | 是 | 模型起草 | 与用户答复不矛盾(答复里的硬约束必须在 statement 中出现) |
69
+ | `scope` | string | 否 | 模型 | 缺省 = 全工程 |
70
+ | `authority` | string | 是 | 常量 `user` | 不可为 `agent` |
71
+ | `status` | string | 是 | 工具 | `active` / `deprecated`(**永不删除**) |
72
+ | `source` | string | 是 | 模型 | `文件:行` / `runId` / 实测命令 |
73
+ | `decision` | decision | 是 | 会话回查 | `askCallId` 必须对应本会话中一次真实提问且用户确有答复 |
74
+
75
+ ## 3. `pipeline.yaml`
76
+
77
+ | 字段 | 类型 | 必填 | 校验 |
78
+ |---|---|---|---|
79
+ | `graphVersion` | integer | 是 | 每次 `stage_declare` 成功 +1 |
80
+ | `entry` | string\|null | 否 | 必须是已知阶段;且不得有入边(fatal) |
81
+ | `stages[].id` | string | 是 | 唯一;改名走「新 id + 旧 id `deprecated`」 |
82
+ | `stages[].status` | enum | 否 | `active`(缺省)/ `deprecated` |
83
+ | `edges[].from/to` | string | 是 | 端点必须已声明;不重复;不自环(fatal) |
84
+
85
+ ## 4. `templates.yaml`
86
+
87
+ | 字段 | 类型 | 必填 | 校验 |
88
+ |---|---|---|---|
89
+ | `version` | integer | 是 | 内容变更 +1 |
90
+ | `status` | enum | 是 | `active` / `deprecated` |
91
+ | `allow` | string[] | 是 | 非空;`run_launch` 命令前缀必须命中 |
92
+ | `scriptRef` | object | 是 | 必须已由 `script_declare` 声明;哈希与磁盘一致 |
93
+ | `paramsSchema` | object | 是 | JSON Schema(object 根);`run_draft` 用它校验 params |
94
+ | `observables[]` | observable[] | 是 | **可为空数组**,但字段必须显式声明;`run_observe` 只认这里 |
95
+ | `args` | string | 否 | `{{key}}` 必须在 params 中出现;保留占位见 `managed.json` |
96
+
97
+ ## 5. `experiments/<runId>/config.json`(实例冻结)
98
+
99
+ | 字段 | 类型 | 来源 | 说明 |
100
+ |---|---|---|---|
101
+ | `runId` | string | `run_draft` | `<projectId>-<template>-<ts>-<rand>` |
102
+ | `template` / `templateVersion` | string / integer | 环境 | 冻结时的模板身份 |
103
+ | `params` / `paramsHash` | object / string | 入参 | 参数与规范化哈希 |
104
+ | `stage` / `graphVersion` | string / integer | 状态 | 冻结时的阶段机 |
105
+ | `command` | string | 工具渲染 | 完整命令行(**权威**,`run_launch` 以它为准) |
106
+ | `scriptRef` | object | 环境 | `{name, mode, path, sha256}` |
107
+ | `interpreter` | string | 工具推导 | 由脚本扩展名决定 |
108
+ | `workdir` | string | 工具 | 工程根 |
109
+ | `envExpectation` | object | 工具 | `{seed, device?, python?, torch?, cuda?, gpu?}`,`probed` 标记是否实测 |
110
+
111
+ ## 6. `manifest.json` / `observations.json`
112
+
113
+ manifest:每个产出 `{path, role, sha256, bytes, producer, ts, stored}`;`raw/` 下未登记的文件 → 该 run 证据不完整(`complete:false`)。
114
+
115
+ observations:`{field, value, unit, file, line, sha256, extractedAt, compareTo?}`。
116
+ - `field` 必须来自模板 `observables[].field`;
117
+ - `file` 必须在 manifest 中登记且当前哈希与 `sha256` 一致;
118
+ - `compareTo` 记录 `{runId, verdict, otherValue, delta, deltaPct, tolerancePct}`。
119
+
120
+ ## 7. `.kb/notes/<name>.md`
121
+
122
+ frontmatter(YAML):
123
+
124
+ | 字段 | 类型 | 必填 | 校验 |
125
+ |---|---|---|---|
126
+ | `kind` | enum | 是 | `proposal` / `claim` / `lesson` / `question` |
127
+ | `status` | enum | 是 | `proposed`(缺省)/ `accepted` / `superseded` / `retracted` |
128
+ | `authority` | enum | 是 | 写入时恒为 `agent`;裁决后可带 `decidedBy`(user 权威) |
129
+ | `evidence[]` | pointer[] | claim 必填 | `{type:'run'\|'file'\|'observation', ref}`,逐条解析,失败即拒绝 |
130
+ | `scope` | string | claim 必填 | 成立条件 |
131
+ | `decidedBy` | decision | 裁决后 | 用户裁决原文 |
132
+
133
+ ## 8. `.research/state.json`(派生)
134
+
135
+ `{derived:true, generatedAt, source:'registry.jsonl', graphVersion, stage, history[]}` —— 可随时重建。
136
+
137
+ **谁写它**:只有 `stage_goto`。
138
+
139
+ | 调用 | 语义 | 是否要用户裁决 |
140
+ |---|---|---|
141
+ | `stage_goto to=<阶段>` | 记录一次真实转移(必须走已声明的边) | 否 |
142
+ | `stage_goto replay=true` | 纯重放:按台账重算,不产生转移 | 见下 |
143
+
144
+ **冲突规则(P3)**:重放只在**会改变现有派生状态断言的值**时才需要用户裁决——
145
+ 文件已存在且 `stage` 与台账重放值不同(或形状不合 v3)时,说明**两个来源互相矛盾**,
146
+ 裁决权不在工具也不在 agent,必须带 `decision{askCallId, answer}`;裁决会记为 `decision` 事件(`kind: state-replay`)。
147
+ 文件缺失、或已与台账一致时,重放只是缓存重建,不需要裁决。
148
+
149
+ **体检**:`project_verify` 会比对派生状态与台账;形状不合或值矛盾即点名并给出修法,
150
+ 派生视图永远不会因为「没被检查」而继续撒谎。
@@ -0,0 +1,36 @@
1
+ {
2
+ "version": 3,
3
+ "note": "受管白名单的唯一定义。project.yaml.managed 必须与此一致;工具写入前用它判定守卫范围。",
4
+ "managed": [
5
+ "project.yaml",
6
+ "pipeline.yaml",
7
+ "templates.yaml",
8
+ "registry.jsonl",
9
+ "scripts/**",
10
+ ".kb/**",
11
+ "skills/*/SKILL.md",
12
+ "_report/**",
13
+ ".research/state.json",
14
+ ".research/managed.json",
15
+ ".research/ignored.json",
16
+ "experiments/*/config.json",
17
+ "experiments/*/manifest.json",
18
+ "experiments/*/observations.json"
19
+ ],
20
+ "processWritable": [
21
+ "experiments/*/raw/**"
22
+ ],
23
+ "versionedRaw": [
24
+ "experiments/*/raw/**"
25
+ ],
26
+ "never": {
27
+ "note": "遗留区与用户数据:只登记,不改、不删、不入版本库。",
28
+ "extensions": [".pth", ".png", ".txt", ".txt~", ".ipynb", ".zip", ".bin", ".npz", ".pt", ".csv"],
29
+ "areas": ["data", "history", "x", "x1", "x2"]
30
+ },
31
+ "reservedPlaceholders": {
32
+ "__raw__": "experiments/<runId>/raw(本次 run 的原始产出目录,工程内相对路径)",
33
+ "__runId__": "本次 runId",
34
+ "__project__": "工程根(绝对路径)"
35
+ }
36
+ }
@@ -0,0 +1,33 @@
1
+ {
2
+ "$id": "https://research-engine.local/contract/manifest.schema.json",
3
+ "title": "experiments/<runId>/manifest.json —— 证据清单",
4
+ "type": "object",
5
+ "additionalProperties": false,
6
+ "required": ["schemaVersion", "runId", "entries"],
7
+ "properties": {
8
+ "schemaVersion": { "type": "integer", "const": 3 },
9
+ "runId": { "type": "string", "minLength": 1 },
10
+ "entries": {
11
+ "type": "array",
12
+ "items": { "$ref": "#/$defs/entry" }
13
+ },
14
+ "complete": { "type": "boolean", "description": "false = 证据不完整(存在未登记产出),不得据此下结论。" },
15
+ "updatedAt": { "type": "string" }
16
+ },
17
+ "$defs": {
18
+ "entry": {
19
+ "type": "object",
20
+ "additionalProperties": false,
21
+ "required": ["path", "role", "sha256", "bytes", "producer", "ts"],
22
+ "properties": {
23
+ "path": { "type": "string", "description": "工程内相对路径。" },
24
+ "role": { "type": "string", "description": "证据角色(config / log / stdout / result / checkpoint / figure / other)。" },
25
+ "sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" },
26
+ "bytes": { "type": "integer", "minimum": 0 },
27
+ "producer": { "type": "string", "description": "产生者:工具名或进程。" },
28
+ "ts": { "type": "string", "minLength": 1 },
29
+ "stored": { "type": "string", "enum": ["versioned", "hash-only"], "description": "versioned = 已随版本库存档;hash-only = 仅记录哈希(版本库不可用或未启用)。" }
30
+ }
31
+ }
32
+ }
33
+ }
@@ -0,0 +1,56 @@
1
+ {
2
+ "$id": "https://research-engine.local/contract/note.schema.json",
3
+ "title": ".kb/notes/<name>.md —— frontmatter:提议 / 结论 / 教训 / 问题",
4
+ "type": "object",
5
+ "additionalProperties": false,
6
+ "required": ["schemaVersion", "name", "kind", "status", "authority", "description", "evidence", "createdAt", "updatedAt"],
7
+ "properties": {
8
+ "schemaVersion": { "type": "integer", "const": 3 },
9
+ "name": { "type": "string", "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" },
10
+ "kind": { "type": "string", "enum": ["proposal", "claim", "lesson", "question"] },
11
+ "status": { "type": "string", "enum": ["proposed", "accepted", "superseded", "retracted"] },
12
+ "authority": { "type": "string", "enum": ["agent", "user", "evidence"] },
13
+ "description": { "type": "string", "minLength": 1 },
14
+ "evidence": {
15
+ "type": "array",
16
+ "items": { "$ref": "#/$defs/pointer" },
17
+ "description": "证据指针;claim 必填且必须可解析。"
18
+ },
19
+ "scope": { "type": "string", "description": "成立条件 / 适用范围。" },
20
+ "decidedBy": { "$ref": "#/$defs/decision" },
21
+ "supersedes": { "type": "array", "items": { "type": "string" } },
22
+ "supersededBy": { "type": "string" },
23
+ "tags": { "type": "array", "items": { "type": "string" } },
24
+ "stage": { "type": "string" },
25
+ "createdAt": { "type": "string", "minLength": 1 },
26
+ "updatedAt": { "type": "string", "minLength": 1 },
27
+ "legacy": { "type": "object", "description": "迁移前的原始 frontmatter 字段(保留不丢)。" }
28
+ },
29
+ "$defs": {
30
+ "pointer": {
31
+ "type": "object",
32
+ "additionalProperties": false,
33
+ "required": ["type", "ref"],
34
+ "properties": {
35
+ "type": { "type": "string", "enum": ["run", "file", "observation"] },
36
+ "ref": { "type": "string", "minLength": 1, "description": "runId / 工程内相对路径(可带 :行) / runId#field。" },
37
+ "note": { "type": "string" }
38
+ }
39
+ },
40
+ "decision": {
41
+ "type": "object",
42
+ "additionalProperties": false,
43
+ "required": ["askCallId", "answer", "sessionId", "ts"],
44
+ "properties": {
45
+ "askCallId": { "type": "string" },
46
+ "question": { "type": "string" },
47
+ "answer": { "type": "string" },
48
+ "selected": { "type": "array", "items": { "type": "string" } },
49
+ "custom": { "type": "string" },
50
+ "sessionId": { "type": "string" },
51
+ "ts": { "type": "string" },
52
+ "attested": { "type": "boolean" }
53
+ }
54
+ }
55
+ }
56
+ }
@@ -0,0 +1,46 @@
1
+ {
2
+ "$id": "https://research-engine.local/contract/observations.schema.json",
3
+ "title": "experiments/<runId>/observations.json —— 证据权威(工具提取值)",
4
+ "type": "object",
5
+ "additionalProperties": false,
6
+ "required": ["schemaVersion", "runId", "observations"],
7
+ "properties": {
8
+ "schemaVersion": { "type": "integer", "const": 3 },
9
+ "runId": { "type": "string", "minLength": 1 },
10
+ "observations": {
11
+ "type": "array",
12
+ "items": { "$ref": "#/$defs/observation" }
13
+ },
14
+ "updatedAt": { "type": "string" }
15
+ },
16
+ "$defs": {
17
+ "observation": {
18
+ "type": "object",
19
+ "additionalProperties": false,
20
+ "required": ["field", "value", "file", "line", "sha256", "extractedAt"],
21
+ "properties": {
22
+ "field": { "type": "string" },
23
+ "value": { "type": ["number", "string"] },
24
+ "unit": { "type": "string" },
25
+ "file": { "type": "string", "description": "来源文件(工程内相对路径)。" },
26
+ "line": { "type": "integer", "minimum": 1 },
27
+ "sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$", "description": "提取时来源文件的哈希。" },
28
+ "extractedAt": { "type": "string", "minLength": 1 },
29
+ "compareTo": { "$ref": "#/$defs/comparison" }
30
+ }
31
+ },
32
+ "comparison": {
33
+ "type": "object",
34
+ "additionalProperties": false,
35
+ "required": ["runId", "verdict"],
36
+ "properties": {
37
+ "runId": { "type": "string" },
38
+ "verdict": { "type": "string", "enum": ["match", "mismatch", "missing"] },
39
+ "otherValue": { "type": ["number", "string", "null"] },
40
+ "delta": { "type": ["number", "null"] },
41
+ "deltaPct": { "type": ["number", "null"] },
42
+ "tolerancePct": { "type": ["number", "null"] }
43
+ }
44
+ }
45
+ }
46
+ }
@@ -0,0 +1,43 @@
1
+ {
2
+ "$id": "https://research-engine.local/contract/pipeline.schema.json",
3
+ "title": "pipeline.yaml —— 环境:阶段机",
4
+ "type": "object",
5
+ "additionalProperties": false,
6
+ "required": ["schemaVersion", "graphVersion", "stages", "edges"],
7
+ "properties": {
8
+ "schemaVersion": { "type": "integer", "const": 3 },
9
+ "graphVersion": { "type": "integer", "minimum": 1, "description": "阶段机版本;每次变更递增,run 冻结时抄录。" },
10
+ "entry": { "type": ["string", "null"], "description": "入口阶段 id;可为 null(图未设入口)。" },
11
+ "stages": {
12
+ "type": "array",
13
+ "items": { "$ref": "#/$defs/stage" }
14
+ },
15
+ "edges": {
16
+ "type": "array",
17
+ "items": { "$ref": "#/$defs/edge" }
18
+ }
19
+ },
20
+ "$defs": {
21
+ "stage": {
22
+ "type": "object",
23
+ "additionalProperties": false,
24
+ "required": ["id", "name"],
25
+ "properties": {
26
+ "id": { "type": "string", "pattern": "^[a-z0-9]+(?:[a-z0-9_-]*[a-z0-9])?$" },
27
+ "name": { "type": "string" },
28
+ "status": { "type": "string", "enum": ["active", "deprecated"] },
29
+ "replacedBy": { "type": "string", "description": "改名后的新 id(旧 id 只 deprecated,永不删除)。" }
30
+ }
31
+ },
32
+ "edge": {
33
+ "type": "object",
34
+ "additionalProperties": false,
35
+ "required": ["from", "to"],
36
+ "properties": {
37
+ "from": { "type": "string" },
38
+ "to": { "type": "string" },
39
+ "note": { "type": "string" }
40
+ }
41
+ }
42
+ }
43
+ }
@@ -0,0 +1,66 @@
1
+ {
2
+ "$id": "https://research-engine.local/contract/project.schema.json",
3
+ "title": "project.yaml —— 环境:身份 / 受管区 / legacy 声明 / 约定",
4
+ "type": "object",
5
+ "additionalProperties": false,
6
+ "required": ["schemaVersion", "project", "managed", "conventions"],
7
+ "properties": {
8
+ "schemaVersion": { "type": "integer", "const": 3 },
9
+ "project": { "$ref": "#/$defs/identity" },
10
+ "managed": {
11
+ "type": "array",
12
+ "minItems": 1,
13
+ "items": { "type": "string" },
14
+ "description": "受管区白名单(相对工程根的 pathspec);只有这些路径进版本库,也只有这些路径的越权改动会被判 dirty。"
15
+ },
16
+ "conventions": {
17
+ "type": "array",
18
+ "items": { "$ref": "https://research-engine.local/contract/conventions.schema.json#/$defs/convention" },
19
+ "description": "用户权威声明;每条必须带 decision 引用。"
20
+ },
21
+ "vocabulary": {
22
+ "type": "object",
23
+ "additionalProperties": false,
24
+ "properties": {
25
+ "entityKinds": { "type": "array", "items": { "type": "string" } },
26
+ "noteKinds": { "type": "array", "items": { "type": "string" } },
27
+ "observableRoles": { "type": "array", "items": { "type": "string" } }
28
+ },
29
+ "description": "开放词汇表(迁移自旧 kb.types / 实体类别)。"
30
+ },
31
+ "legacy": {
32
+ "type": "object",
33
+ "additionalProperties": false,
34
+ "properties": {
35
+ "note": { "type": "string" },
36
+ "keep": {
37
+ "type": "array",
38
+ "items": {
39
+ "type": "object",
40
+ "additionalProperties": false,
41
+ "required": ["pattern", "count"],
42
+ "properties": {
43
+ "pattern": { "type": "string" },
44
+ "count": { "type": "integer" }
45
+ }
46
+ }
47
+ },
48
+ "protectedAreas": { "type": "array", "items": { "type": "string" } }
49
+ },
50
+ "description": "遗留区:只登记 size/mtime,不改不删不哈希。"
51
+ }
52
+ },
53
+ "$defs": {
54
+ "identity": {
55
+ "type": "object",
56
+ "additionalProperties": false,
57
+ "required": ["id", "name"],
58
+ "properties": {
59
+ "id": { "type": "string", "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" },
60
+ "name": { "type": "string", "minLength": 1 },
61
+ "domain": { "type": "string" },
62
+ "description": { "type": "string" }
63
+ }
64
+ }
65
+ }
66
+ }
@@ -0,0 +1,78 @@
1
+ {
2
+ "$id": "https://research-engine.local/contract/templates.schema.json",
3
+ "title": "templates.yaml —— 环境:可执行声明(含 observables)",
4
+ "type": "object",
5
+ "additionalProperties": false,
6
+ "required": ["schemaVersion", "templates"],
7
+ "properties": {
8
+ "schemaVersion": { "type": "integer", "const": 3 },
9
+ "templates": {
10
+ "type": "object",
11
+ "additionalProperties": { "$ref": "#/$defs/template" }
12
+ }
13
+ },
14
+ "$defs": {
15
+ "template": {
16
+ "type": "object",
17
+ "additionalProperties": false,
18
+ "required": ["version", "status", "description", "allow", "scriptRef", "paramsSchema", "observables"],
19
+ "properties": {
20
+ "version": { "type": "integer", "minimum": 1 },
21
+ "status": { "type": "string", "enum": ["active", "deprecated"] },
22
+ "description": { "type": "string" },
23
+ "allow": {
24
+ "type": "array",
25
+ "minItems": 1,
26
+ "items": { "type": "string" },
27
+ "description": "命令前缀白名单;run_launch 的安全闸依据。"
28
+ },
29
+ "scriptRef": { "$ref": "#/$defs/scriptRef" },
30
+ "args": { "type": "string", "description": "追加参数模板,支持 {{key}}(来自 params)与 {{__raw__}}/{{__runId__}}/{{__project__}} 保留占位。" },
31
+ "paramsSchema": { "$ref": "#/$defs/paramsSchema" },
32
+ "observables": {
33
+ "type": "array",
34
+ "items": { "$ref": "#/$defs/observable" },
35
+ "description": "可提取字段声明;run_observe 只能提取这里声明过的字段(防挑数字)。"
36
+ },
37
+ "updatedAt": { "type": "string" },
38
+ "deprecatedAt": { "type": "string" }
39
+ }
40
+ },
41
+ "scriptRef": {
42
+ "type": "object",
43
+ "additionalProperties": false,
44
+ "required": ["name", "mode", "path"],
45
+ "properties": {
46
+ "name": { "type": "string" },
47
+ "mode": { "type": "string", "enum": ["create", "adopt"] },
48
+ "path": { "type": "string", "description": "工程内相对路径。" },
49
+ "sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" },
50
+ "version": { "type": "integer", "minimum": 1 }
51
+ }
52
+ },
53
+ "paramsSchema": {
54
+ "type": "object",
55
+ "additionalProperties": true,
56
+ "required": ["type"],
57
+ "properties": {
58
+ "type": { "type": "string", "const": "object" },
59
+ "properties": { "type": "object" },
60
+ "required": { "type": "array", "items": { "type": "string" } },
61
+ "additionalProperties": { "type": "boolean" }
62
+ }
63
+ },
64
+ "observable": {
65
+ "type": "object",
66
+ "additionalProperties": false,
67
+ "required": ["field", "role", "pattern"],
68
+ "properties": {
69
+ "field": { "type": "string", "pattern": "^[A-Za-z_][A-Za-z0-9_]*$" },
70
+ "role": { "type": "string", "description": "从 manifest 中哪个角色的产出文件提取。" },
71
+ "pattern": { "type": "string", "description": "正则;第 1 个捕获组 = 数值。" },
72
+ "unit": { "type": "string" },
73
+ "description": { "type": "string" },
74
+ "kind": { "type": "string", "enum": ["number", "string"] }
75
+ }
76
+ }
77
+ }
78
+ }