skill-family-contracts 0.1.2 → 0.2.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.
Files changed (67) hide show
  1. package/CONTRIBUTING.md +13 -67
  2. package/README.md +25 -9
  3. package/SECURITY.md +4 -9
  4. package/package.json +3 -3
  5. package/src/checker.mjs +18 -14
  6. package/src/error-codes.json +27 -1
  7. package/src/errors.mjs +2 -1
  8. package/src/fixtures/adapter-build-manifest/dialect-01-unsupported.json +7 -0
  9. package/src/fixtures/adapter-build-manifest/negative-01-path-escape.json +7 -0
  10. package/src/fixtures/adapter-build-manifest/positive-01-single-file.json +7 -0
  11. package/src/fixtures/adapter-source/dialect-01-unsupported.json +7 -0
  12. package/src/fixtures/adapter-source/negative-01-path-escape.json +7 -0
  13. package/src/fixtures/adapter-source/positive-01-minimal.json +7 -0
  14. package/src/fixtures/host-capability-fact/dialect-01-unsupported.json +7 -0
  15. package/src/fixtures/host-capability-fact/negative-01-unknown-without-manual.json +7 -0
  16. package/src/fixtures/host-capability-fact/positive-01-unknown.json +7 -0
  17. package/src/fixtures/host-descriptor/dialect-01-unsupported.json +7 -0
  18. package/src/fixtures/host-descriptor/negative-01-command-data.json +7 -0
  19. package/src/fixtures/host-descriptor/positive-01-unsupported.json +7 -0
  20. package/src/fixtures/host-operation-plan/dialect-01-unsupported.json +7 -0
  21. package/src/fixtures/host-operation-plan/negative-01-unsupported-action.json +7 -0
  22. package/src/fixtures/host-operation-plan/positive-01-unsupported.json +7 -0
  23. package/src/fixtures/host-operation-receipt/dialect-01-unsupported.json +7 -0
  24. package/src/fixtures/host-operation-receipt/negative-01-success-without-hash.json +7 -0
  25. package/src/fixtures/host-operation-receipt/positive-01-rejected.json +7 -0
  26. package/src/fixtures/host-probe-result/dialect-01-unsupported.json +7 -0
  27. package/src/fixtures/host-probe-result/negative-01-unsupported-with-fact.json +7 -0
  28. package/src/fixtures/host-probe-result/positive-01-unsupported.json +7 -0
  29. package/src/fixtures/host-registry/dialect-01-unsupported.json +7 -0
  30. package/src/fixtures/host-registry/negative-01-path-escape.json +7 -0
  31. package/src/fixtures/host-registry/positive-01-reference.json +7 -0
  32. package/src/fixtures/report-binding/dialect-01-unsupported-dialect.json +21 -0
  33. package/src/fixtures/report-binding/negative-01-wrong-kind.json +21 -0
  34. package/src/fixtures/report-binding/negative-02-short-report-digest.json +21 -0
  35. package/src/fixtures/report-binding/negative-03-floating-renderer-version.json +21 -0
  36. package/src/fixtures/report-binding/positive-01-valid-binding.json +21 -0
  37. package/src/fixtures/report-model/dialect-01-coerce-count-tolerant.json +49 -0
  38. package/src/fixtures/report-model/dialect-02-coerce-count-strict.json +48 -0
  39. package/src/fixtures/report-model/negative-01-wrong-kind.json +17 -0
  40. package/src/fixtures/report-model/negative-02-unknown-field.json +17 -0
  41. package/src/fixtures/report-model/negative-03-bad-result-digest.json +17 -0
  42. package/src/fixtures/report-model/negative-04-unsupported-locale.json +22 -0
  43. package/src/fixtures/report-model/negative-05-empty-evidence.json +46 -0
  44. package/src/fixtures/report-model/positive-01-full-zh.json +62 -0
  45. package/src/fixtures/report-model/positive-02-empty-sections-en.json +49 -0
  46. package/src/fixtures/state-event-envelope/dialect-01-unsupported.json +27 -0
  47. package/src/fixtures/state-event-envelope/negative-01-zero-fencing.json +27 -0
  48. package/src/fixtures/state-event-envelope/positive-01-valid.json +27 -0
  49. package/src/fixtures/state-snapshot-metadata/dialect-01-unsupported.json +20 -0
  50. package/src/fixtures/state-snapshot-metadata/negative-01-extra-field.json +21 -0
  51. package/src/fixtures/state-snapshot-metadata/positive-01-valid.json +21 -0
  52. package/src/index.mjs +18 -4
  53. package/src/kernel-protocol.json +1 -1
  54. package/src/registry.json +85 -1
  55. package/src/rules.json +2 -26
  56. package/src/schemas/adapter-build-manifest.schema.json +41 -0
  57. package/src/schemas/adapter-source.schema.json +47 -0
  58. package/src/schemas/host-capability-fact.schema.json +31 -0
  59. package/src/schemas/host-descriptor.schema.json +64 -0
  60. package/src/schemas/host-operation-plan.schema.json +48 -0
  61. package/src/schemas/host-operation-receipt.schema.json +81 -0
  62. package/src/schemas/host-probe-result.schema.json +26 -0
  63. package/src/schemas/host-registry.schema.json +18 -0
  64. package/src/schemas/report-binding.schema.json +42 -0
  65. package/src/schemas/report-model.schema.json +308 -0
  66. package/src/schemas/state-event-envelope.schema.json +38 -0
  67. package/src/schemas/state-snapshot-metadata.schema.json +25 -0
package/CONTRIBUTING.md CHANGED
@@ -1,79 +1,25 @@
1
- # 贡献指南(Contributing)
1
+ # 贡献指南(skill-family-contracts)
2
2
 
3
- 感谢关注 `skill-family-foundation-workspace`。本文件说明本地开发环境、
4
- 验证流程与 PR 规则。行为准则见 [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md),
5
- Git 写操作的授权边界见仓库内文档站的生命周期指南(`docs/git-lifecycle.md`)。
3
+ 本仓是 `skill-family-contracts` 的公开发布镜像,只承载版本化发布物。开发在私有工作区进行,不在本仓公开。
6
4
 
7
- 拓扑说明:本工作区仓永久 private,是唯一开发真源;三个 public 仓
8
- (`skill-family-contracts`、`skill-family-harness-node`、
9
- `skill-family-engineering-kit`)是版本化发布镜像,不承载完整开发历史与
10
- 测试。本指南描述的是私有工作区内经授权的开发流程;消费坐标与发布状态
11
- 唯一见 `docs/status/current.md`。
5
+ ## 问题报告
12
6
 
13
- ## Bug 报告与 Issue
7
+ Bug 报告请在本仓 GitHub Issues 提交:https://github.com/ifoohoo/skill-family-contracts/issues
14
8
 
15
- 普通 bug 报告欢迎通过各 public 发布镜像仓的 GitHub Issues 提交(例如
16
- `https://github.com/ifoohoo/skill-family-engineering-kit/issues`)。提交
17
- bug 报告时请尽量提供:受影响包与版本、最小复现步骤、预期与实际行为。
9
+ 提交时请尽量提供:受影响版本、最小复现步骤、预期与实际行为。
18
10
 
19
- **未经事先讨论的 Pull Request 不予接受。** 如果你希望贡献代码,请先
20
- 通过 Issue 讨论变更的动机与范围,获得维护者确认后再提交 PR。这是为了
21
- 避免重复劳动、确保变更与本项目的架构裁决和发布合同一致。
11
+ ## 代码贡献
22
12
 
23
- ## 本地安装
13
+ 未经事先讨论的 Pull Request 不予接受。请先通过 Issue 讨论变更的动机与范围,获得维护者确认后再提交 PR。
24
14
 
25
- 必需工具(精确版本见仓库内 `docs/setup.md` 的安装合同):
15
+ ## 行为准则
26
16
 
27
- - Node.js:`>=22.22.2 <23`(版本取自 `.node-version`)
28
- - pnpm:10.30.0(`packageManager` 精确锁定)
29
- - Python:3.10 或更高(仅文档站构建需要)
17
+ 互动请遵守本仓 CODE_OF_CONDUCT.md。
30
18
 
31
- ```bash
32
- pnpm install
33
- pnpm synth
34
- pnpm check
35
- ```
36
-
37
- - `pnpm synth` 再生成 projen 受管文件。受管文件只能通过修改
38
- `.projenrc.js` 间接变更;手写源码、文档和 fixture 不会被 synth 覆盖。
39
- - `pnpm check` 是统一根门禁:10 个稳定门禁 ID 按固定顺序串联执行,
40
- 任一步骤非 0 即整体失败。
41
-
42
- ## 提交 PR 前必须做到
43
-
44
- 1. 只修改当前任务授权的文件,不顺手改动无关文件;
45
- 2. 修改 `.projenrc.js` 后运行 `pnpm synth`,并保证连续两次 synth 零漂移;
46
- 3. 本地完整通过根门禁:
47
-
48
- ```bash
49
- pnpm check
50
- ```
51
-
52
- 4. 涉及文档时额外运行文档事实与链接检查:
53
-
54
- ```bash
55
- node scripts/docs/fact-check.mjs
56
- node scripts/docs/link-check.mjs
57
- ```
58
-
59
- 5. 涉及三个叶子包时,确认发布字节合同仍然成立:
60
-
61
- ```bash
62
- pnpm run release-artifacts:build -- --output .artifacts/publish-candidates
63
- pnpm run release-artifacts:verify -- --root .artifacts/publish-candidates --manifest .artifacts/publish-candidates/release-artifacts.manifest.json
64
- ```
65
-
66
- ## PR 规则
19
+ ## 安全报告
67
20
 
68
- - PR 描述须包含:动机、变更面、本地门禁结果(退出码)与剩余风险;
69
- - 不得为转绿而弱化任何产品门禁、Action pin、权限、泄漏策略或 tarball 合同;
70
- - 不新增运行时依赖,除非先经评审并显式修订泄漏策略中的公开依赖清单;
71
- - 不提交生成物与本地状态:`node_modules`、`site/`、`.artifacts/`、缓存、
72
- 日志、凭据或本机绝对路径一律不进入提交;
73
- - 受管投影必须与产生它的源变更在同一 PR 内提交,禁止手改受管文件;
74
- - 动态状态(实时发布状态、远端对象状态)不写入版本化文档,唯一产品状态
75
- 口径见 `docs/status/current.md`。
21
+ 漏洞报告渠道见本仓 SECURITY.md。请走 Private vulnerability reporting,不要以公开 Issue 提交漏洞。
76
22
 
77
- ## 安全报告
23
+ ## 许可证
78
24
 
79
- 漏洞报告渠道与支持版本见 [SECURITY.md](SECURITY.md)。
25
+ 贡献内容默认按 Apache-2.0 授权,与本包许可证一致。
package/README.md CHANGED
@@ -3,9 +3,9 @@
3
3
 
4
4
  # skill-family-contracts
5
5
 
6
- 机器可执行工程结构和机制协议的唯一权威包(Contracts v1,冻结)。
6
+ 机器可执行工程结构和机制协议的唯一权威包(Contracts 1.4.0,冻结)。
7
7
 
8
- 本包拥有:六类顶层对象的 JSON Schema、Kernel Protocol(内核协议)、稳定错误码、
8
+ 本包拥有:十八类顶层对象的 JSON Schema、Kernel Protocol(内核协议)、稳定错误码、
9
9
  协议名/`$id` 登记表,以及九种有限机械检查类型与受限强制规则集。
10
10
  本包不执行骨架生成、文件写入、审计或发布;机制实现由 Harness 承担,
11
11
  工程命令由 Kit 承担,二者单向消费本包。
@@ -13,7 +13,7 @@
13
13
  Schema 验证完全基于 [Ajv](https://ajv.js.org/)(精确版本见 `package.json`),
14
14
  按方言路由到对应 Ajv 类;不实现任何手写 Schema 子集解释器。
15
15
 
16
- ## 六类顶层对象
16
+ ## 十八类顶层对象
17
17
 
18
18
  | 对象 | `$id` | Schema 文件 |
19
19
  | --- | --- | --- |
@@ -23,6 +23,18 @@ Schema 验证完全基于 [Ajv](https://ajv.js.org/)(精确版本见 `package.
23
23
  | `operation-request` | `https://contracts.skill-family.example/v1/operation-request.json` | `src/schemas/operation-request.schema.json` |
24
24
  | `operation-result` | `https://contracts.skill-family.example/v1/operation-result.json` | `src/schemas/operation-result.schema.json` |
25
25
  | `migration-manifest` | `https://contracts.skill-family.example/v1/migration-manifest.json` | `src/schemas/migration-manifest.schema.json` |
26
+ | `report-model` | `https://contracts.skill-family.example/v1/report-model.json` | `src/schemas/report-model.schema.json` |
27
+ | `report-binding` | `https://contracts.skill-family.example/v1/report-binding.json` | `src/schemas/report-binding.schema.json` |
28
+ | `host-descriptor` | `https://contracts.skill-family.example/v1/host-descriptor.json` | `src/schemas/host-descriptor.schema.json` |
29
+ | `host-capability-fact` | `https://contracts.skill-family.example/v1/host-capability-fact.json` | `src/schemas/host-capability-fact.schema.json` |
30
+ | `adapter-build-manifest` | `https://contracts.skill-family.example/v1/adapter-build-manifest.json` | `src/schemas/adapter-build-manifest.schema.json` |
31
+ | `host-operation-plan` | `https://contracts.skill-family.example/v1/host-operation-plan.json` | `src/schemas/host-operation-plan.schema.json` |
32
+ | `host-operation-receipt` | `https://contracts.skill-family.example/v1/host-operation-receipt.json` | `src/schemas/host-operation-receipt.schema.json` |
33
+ | `adapter-source` | `https://contracts.skill-family.example/v1/adapter-source.json` | `src/schemas/adapter-source.schema.json` |
34
+ | `host-registry` | `https://contracts.skill-family.example/v1/host-registry.json` | `src/schemas/host-registry.schema.json` |
35
+ | `host-probe-result` | `https://contracts.skill-family.example/v1/host-probe-result.json` | `src/schemas/host-probe-result.schema.json` |
36
+ | `state-event-envelope` | `https://contracts.skill-family.example/v1/state-event-envelope.json` | `src/schemas/state-event-envelope.schema.json` |
37
+ | `state-snapshot-metadata` | `https://contracts.skill-family.example/v1/state-snapshot-metadata.json` | `src/schemas/state-snapshot-metadata.schema.json` |
26
38
 
27
39
  所有 v1 Schema 使用 draft 2020-12 方言;实例信封统一为
28
40
  `schemaVersion: 1` + 唯一 `kind` 常量 + 各层 `additionalProperties: false`。
@@ -48,7 +60,7 @@ Schema 验证完全基于 [Ajv](https://ajv.js.org/)(精确版本见 `package.
48
60
  ## 稳定错误码
49
61
 
50
62
  冻结登记表:`src/error-codes.json`。`SFC1xxx` 为合同权威层错误,
51
- `SFC2xxx` 为内核操作错误。码只增不改、不复用。v1 冻结:
63
+ `SFC2xxx` 为内核操作错误,`SFC3xxx` 为报告绑定错误。码只增不改、不复用。v1 冻结:
52
64
 
53
65
  | 码 | 名称 | 含义摘要 |
54
66
  | --- | --- | --- |
@@ -67,6 +79,9 @@ Schema 验证完全基于 [Ajv](https://ajv.js.org/)(精确版本见 `package.
67
79
  | SFC2002 | UNKNOWN_OPERATION | 操作名不在冻结词汇表 |
68
80
  | SFC2003 | INVALID_PARAMS | 参数不满足操作的冻结 params 合同 |
69
81
  | SFC2004 | EXECUTION_FAILED | 机制运行时执行失败(仅运行时可演示) |
82
+ | SFC3001 | REPORT_DIGEST_MISMATCH | 报告或结果摘要与绑定不一致 |
83
+ | SFC3002 | REPORT_ELEMENT_MISSING | 报告缺少强制元素 |
84
+ | SFC3003 | REPORT_FACT_DRIFT | 报告字节偏离确定性重渲染结果 |
70
85
 
71
86
  ## 方言与验证策略(Ajv)
72
87
 
@@ -85,13 +100,14 @@ Schema 验证完全基于 [Ajv](https://ajv.js.org/)(精确版本见 `package.
85
100
  `schema.ref-resolves`、`schema.dialect-declared`、`fixture.positive-passes`、
86
101
  `fixture.negative-coded`、`error-code.registered`、`rules.budget`。
87
102
 
88
- 首版强制规则 **13 条**(CR-001…CR-013),预算上限 20 条、绝对上限 30 条;
103
+ 当前强制规则 **9 条**(CR-001、CR-006…CR-013)。其中 CR-001 对登记表内全部 Schema 做统一编译,
104
+ 不再为每个对象重复占用一条规则;预算上限 20 条、绝对上限 30 条;
89
105
  `rules.budget` 是机械门禁,超限即 `runChecks` 失败并报 `SFC1008`。
90
106
 
91
107
  ## Fixture
92
108
 
93
109
  `src/fixtures/<contract>/` 为每类合同提供正例(positive)、反例(negative)
94
- 与方言边界(dialect-boundary)样例,共 32 个。每个 fixture 声明目标 Schema、
110
+ 与方言边界(dialect-boundary)样例,共 76 个。每个 fixture 声明目标 Schema、
95
111
  方言、策略与期望;反例期望携带稳定失败码。`verifyAllFixtures()` 机械重放全部期望,
96
112
  行为不符报 `SFC1010`。fixture 是完全虚构数据,不是审计 oracle。
97
113
 
@@ -125,14 +141,14 @@ import {
125
141
  ## 安装
126
142
 
127
143
  ```sh
128
- npm install skill-family-contracts
144
+ npm install skill-family-contracts@0.2.0
129
145
  npm info skill-family-contracts --help
130
146
  ```
131
147
 
132
148
  ## 最小示例
133
149
 
134
150
  ```js
135
- // 从空目录运行:npm install skill-family-contracts
151
+ // 从空目录运行:npm install skill-family-contracts@0.2.0
136
152
  import { validateDocument } from "skill-family-contracts";
137
153
 
138
154
  const document = {
@@ -153,4 +169,4 @@ if (!result.valid) console.error(result.errorCode);
153
169
 
154
170
  ## 故障诊断
155
171
 
156
- 验证失败时 `errorCode` 为 `SCHEMA_VALIDATION_FAILED`(SFC1001);未知 Schema 报 `UNKNOWN_SCHEMA_ID`(SFC1002)。如失败,检查文档是否满足目标 Schema 的必填字段与类型约束。
172
+ 验证失败时 `errorCode` 为 `SFC1001`(SCHEMA_VALIDATION_FAILED,文档未通过目标 Schema 验证);`$id` 未注册时报 `SFC1002`(UNKNOWN_SCHEMA_ID)。如失败,检查文档是否满足目标 Schema 的必填字段与类型约束。
package/SECURITY.md CHANGED
@@ -12,15 +12,10 @@
12
12
  ## 报告漏洞
13
13
 
14
14
  请勿通过公开 issue 直接披露未修复的安全漏洞。private workspace 仓永久
15
- private,不是公开报告渠道;请通过以下渠道私下报告:
16
-
17
- 1. **首选**:通过任一 public 发布镜像仓(例如
18
- `https://github.com/ifoohoo/skill-family-engineering-kit`)在 GitHub 上使用
19
- **Private vulnerability reporting**(仓库 Security 页签的
20
- "Report a vulnerability");
21
- 2. **备选**:如上述通道不可用,请通过邮件联系仓库维护者
22
- (`ifoohoo` GitHub 组织的 `security` 团队,或仓库 `README.md` 中列出的
23
- 维护者邮箱)。
15
+ private,不是公开报告渠道;请通过任一 public 发布镜像仓(例如
16
+ `https://github.com/ifoohoo/skill-family-engineering-kit`)在 GitHub 上使用
17
+ **Private vulnerability reporting**(仓库 Security 页签的
18
+ "Report a vulnerability")私下报告。
24
19
 
25
20
  报告时请尽量提供:
26
21
 
package/package.json CHANGED
@@ -27,9 +27,9 @@
27
27
  "url": "https://github.com/ifoohoo/skill-family-contracts.git"
28
28
  },
29
29
  "type": "module",
30
- "version": "0.1.2",
30
+ "version": "0.2.0",
31
31
  "scripts": {
32
- "check": "node --input-type=module -e \"import('./src/index.mjs').then(() => console.log('smoke ok')).catch(e => { console.error(e); process.exit(1) })\"",
33
- "test": "node --input-type=module -e \"import('./src/index.mjs').then(() => console.log('smoke ok')).catch(e => { console.error(e); process.exit(1) })\""
32
+ "check": "node --test",
33
+ "test": "node --test"
34
34
  }
35
35
  }
package/src/checker.mjs CHANGED
@@ -117,22 +117,26 @@ function schemaEntries(ctx) {
117
117
 
118
118
  const HANDLERS = {
119
119
  "schema.compile": (rule, ctx) => {
120
- const registration = ctx.registry.schemas.find(
121
- (entry) => entry.object === rule.target.object,
122
- );
123
- if (!registration) {
124
- return fail(rule, "SFC1002", `no registered schema for object: ${rule.target.object}`);
120
+ const registrations = rule.target.registry === "schemas"
121
+ ? ctx.registry.schemas
122
+ : ctx.registry.schemas.filter((entry) => entry.object === rule.target.object);
123
+ if (registrations.length === 0) {
124
+ return fail(rule, "SFC1002", `no registered schema for target: ${rule.target.object ?? rule.target.registry}`);
125
125
  }
126
- try {
127
- compileSchema(
128
- { schema: ctx.loadSchema(registration) },
129
- { dialect: registration.dialect, policy: "strict" },
130
- );
131
- return pass(rule);
132
- } catch (cause) {
133
- if (cause instanceof ContractsError) return fail(rule, cause.code, cause.message);
134
- throw cause;
126
+ for (const registration of registrations) {
127
+ try {
128
+ compileSchema(
129
+ { schema: ctx.loadSchema(registration) },
130
+ { dialect: registration.dialect, policy: "strict" },
131
+ );
132
+ } catch (cause) {
133
+ if (cause instanceof ContractsError) {
134
+ return fail(rule, cause.code, `${registration.object}: ${cause.message}`);
135
+ }
136
+ throw cause;
137
+ }
135
138
  }
139
+ return pass(rule);
136
140
  },
137
141
 
138
142
  "schema.unique-id": (rule, ctx) => {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "kind": "skill-family.contracts.error-codes",
4
- "contractsVersion": "1.1.1",
4
+ "contractsVersion": "1.4.0",
5
5
  "policy": {
6
6
  "format": "^SFC[0-9]{4}$",
7
7
  "ranges": [
@@ -14,6 +14,11 @@
14
14
  "prefix": "SFC2",
15
15
  "category": "kernel",
16
16
  "note": "Kernel protocol operation errors."
17
+ },
18
+ {
19
+ "prefix": "SFC3",
20
+ "category": "report",
21
+ "note": "Report contract errors: digest binding, mandatory elements, fact drift. Style findings are never coded here; they are advisory output of the report check, not errors."
17
22
  }
18
23
  ],
19
24
  "stability": "Codes are append-only. A frozen code is never reused for a different meaning; a deprecated code stays reserved. New codes require a contracts change."
@@ -123,6 +128,27 @@
123
128
  "category": "kernel",
124
129
  "stableSince": "1.0.0",
125
130
  "meaning": "The mechanism runtime failed while executing a well-formed operation; details carry the mechanism evidence. Demonstrable only by a mechanism runtime, not by static contracts checks."
131
+ },
132
+ {
133
+ "code": "SFC3001",
134
+ "name": "REPORT_DIGEST_MISMATCH",
135
+ "category": "report",
136
+ "stableSince": "1.2.0",
137
+ "meaning": "A report binding digest (model, result, or report digest) does not match the recomputed sha256 digest, or the report is not the canonical render of its model; the binding is stale or tampered and is never trusted."
138
+ },
139
+ {
140
+ "code": "SFC3002",
141
+ "name": "REPORT_ELEMENT_MISSING",
142
+ "category": "report",
143
+ "stableSince": "1.2.0",
144
+ "meaning": "A mandatory report element is absent, empty where it must not be, or declared in an unsupported form: the conclusion, non-empty evidence, risks/next-action sections, a supported locale, or a coded outcome for failed/blocked runs. No half report is issued for a missing element."
145
+ },
146
+ {
147
+ "code": "SFC3003",
148
+ "name": "REPORT_FACT_DRIFT",
149
+ "category": "report",
150
+ "stableSince": "1.2.0",
151
+ "meaning": "A report model misstates the bound operation result's terminal state/errors, or rendered report bytes diverge from the deterministic re-render of the bound report model: fact fields rewritten, sections deleted, duplicated, or injected."
126
152
  }
127
153
  ]
128
154
  }
package/src/errors.mjs CHANGED
@@ -17,7 +17,8 @@ function deepFreeze(obj) {
17
17
  *
18
18
  * The registry file (error-codes.json) is the single source of truth; codes are
19
19
  * append-only and never reused. SFC1xxx covers contracts-authority failures,
20
- * SFC2xxx covers kernel-operation failures.
20
+ * SFC2xxx covers kernel-operation failures, SFC3xxx covers report-contract
21
+ * failures (digest binding, mandatory elements, fact drift).
21
22
  */
22
23
 
23
24
  const ERROR_REGISTRY = deepFreeze(JSON.parse(
@@ -0,0 +1,7 @@
1
+ {
2
+ "fixtureId": "adapter-build-manifest.dialect-boundary.unsupported", "contract": "adapter-build-manifest", "class": "dialect-boundary",
3
+ "description": "Unsupported schema dialect fails before validation.",
4
+ "validation": { "schemaId": "https://contracts.skill-family.example/v1/adapter-build-manifest.json", "dialect": "draft-04", "policy": "strict" },
5
+ "expect": { "valid": false, "errorCode": "SFC1006" },
6
+ "data": { "schemaVersion": 1, "kind": "skill-family.adapter-build-manifest" }
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "fixtureId": "adapter-build-manifest.negative.path-escape", "contract": "adapter-build-manifest", "class": "negative",
3
+ "description": "An escaping target is rejected.",
4
+ "validation": { "schemaId": "https://contracts.skill-family.example/v1/adapter-build-manifest.json", "dialect": "2020-12", "policy": "strict" },
5
+ "expect": { "valid": false, "errorCode": "SFC1001" },
6
+ "data": { "schemaVersion": 1, "kind": "skill-family.adapter-build-manifest", "hostId": "fixture-host", "pathCategory": { "id": "fixture-project-skills", "scope": "project", "anchor": "project-root", "relPath": ".fixture/skills" }, "sourceClosure": { "digest": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "members": [{ "sourcePath": "demo/SKILL.md", "sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "bytes": 4 }] }, "members": [{ "sourcePath": "demo/SKILL.md", "target": "../outside", "sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "bytes": 4 }], "digest": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" }
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "fixtureId": "adapter-build-manifest.positive.single-file", "contract": "adapter-build-manifest", "class": "positive",
3
+ "description": "A manifest binds one source byte set to one controlled target.",
4
+ "validation": { "schemaId": "https://contracts.skill-family.example/v1/adapter-build-manifest.json", "dialect": "2020-12", "policy": "strict" },
5
+ "expect": { "valid": true },
6
+ "data": { "schemaVersion": 1, "kind": "skill-family.adapter-build-manifest", "hostId": "fixture-host", "pathCategory": { "id": "fixture-project-skills", "scope": "project", "anchor": "project-root", "relPath": ".fixture/skills" }, "sourceClosure": { "digest": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "members": [{ "sourcePath": "demo/SKILL.md", "sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "bytes": 4 }] }, "members": [{ "sourcePath": "demo/SKILL.md", "target": ".fixture/skills/demo/SKILL.md", "sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "bytes": 4 }], "digest": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" }
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "fixtureId": "adapter-source.dialect-boundary.unsupported", "contract": "adapter-source", "class": "dialect-boundary",
3
+ "description": "Unsupported schema dialect fails before source validation.",
4
+ "validation": { "schemaId": "https://contracts.skill-family.example/v1/adapter-source.json", "dialect": "draft-04", "policy": "strict" },
5
+ "expect": { "valid": false, "errorCode": "SFC1006" },
6
+ "data": { "schemaVersion": 1, "kind": "skill-family.adapter-source", "skillFamilyId": "fixture-family", "skills": [{ "id": "demo-skill", "files": [{ "path": "SKILL.md", "content": "# Demo\n" }] }] }
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "fixtureId": "adapter-source.negative.path-escape", "contract": "adapter-source", "class": "negative",
3
+ "description": "An adapter source path cannot escape its declared skill directory.",
4
+ "validation": { "schemaId": "https://contracts.skill-family.example/v1/adapter-source.json", "dialect": "2020-12", "policy": "strict" },
5
+ "expect": { "valid": false, "errorCode": "SFC1001" },
6
+ "data": { "schemaVersion": 1, "kind": "skill-family.adapter-source", "skillFamilyId": "fixture-family", "skills": [{ "id": "demo-skill", "files": [{ "path": "../outside", "content": "bad" }] }] }
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "fixtureId": "adapter-source.positive.minimal", "contract": "adapter-source", "class": "positive",
3
+ "description": "A minimal source closure contains one skill and its required SKILL.md bytes.",
4
+ "validation": { "schemaId": "https://contracts.skill-family.example/v1/adapter-source.json", "dialect": "2020-12", "policy": "strict" },
5
+ "expect": { "valid": true },
6
+ "data": { "schemaVersion": 1, "kind": "skill-family.adapter-source", "skillFamilyId": "fixture-family", "skills": [{ "id": "demo-skill", "files": [{ "path": "SKILL.md", "content": "# Demo\n" }] }] }
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "fixtureId": "host-capability-fact.dialect-boundary.unsupported", "contract": "host-capability-fact", "class": "dialect-boundary",
3
+ "description": "Unsupported schema dialect fails before validation.",
4
+ "validation": { "schemaId": "https://contracts.skill-family.example/v1/host-capability-fact.json", "dialect": "draft-04", "policy": "strict" },
5
+ "expect": { "valid": false, "errorCode": "SFC1006" },
6
+ "data": { "schemaVersion": 1, "kind": "skill-family.host-capability-fact", "hostId": "fixture-host", "capability": "smoke", "state": "unknown", "evidence": ["No frozen probe."], "unknownReason": "driver-limited", "manualSteps": ["Verify manually."] }
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "fixtureId": "host-capability-fact.negative.unknown-without-manual", "contract": "host-capability-fact", "class": "negative",
3
+ "description": "Unknown without a manual verification path is rejected.",
4
+ "validation": { "schemaId": "https://contracts.skill-family.example/v1/host-capability-fact.json", "dialect": "2020-12", "policy": "strict" },
5
+ "expect": { "valid": false, "errorCode": "SFC1001" },
6
+ "data": { "schemaVersion": 1, "kind": "skill-family.host-capability-fact", "hostId": "fixture-host", "capability": "smoke", "state": "unknown", "evidence": ["No frozen probe."], "unknownReason": "driver-limited" }
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "fixtureId": "host-capability-fact.positive.unknown", "contract": "host-capability-fact", "class": "positive",
3
+ "description": "An unknown fact includes a reason and manual steps.",
4
+ "validation": { "schemaId": "https://contracts.skill-family.example/v1/host-capability-fact.json", "dialect": "2020-12", "policy": "strict" },
5
+ "expect": { "valid": true },
6
+ "data": { "schemaVersion": 1, "kind": "skill-family.host-capability-fact", "hostId": "fixture-host", "capability": "smoke", "state": "unknown", "evidence": ["No frozen probe."], "unknownReason": "driver-limited", "manualSteps": ["Verify manually."] }
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "fixtureId": "host-descriptor.dialect-boundary.unsupported", "contract": "host-descriptor", "class": "dialect-boundary",
3
+ "description": "Unsupported schema dialect fails before validation.",
4
+ "validation": { "schemaId": "https://contracts.skill-family.example/v1/host-descriptor.json", "dialect": "draft-04", "policy": "strict" },
5
+ "expect": { "valid": false, "errorCode": "SFC1006" },
6
+ "data": { "schemaVersion": 1, "kind": "skill-family.host-descriptor", "hostId": "fixture-host", "displayName": "Fixture Host", "support": "unsupported", "unsupportedReason": "No frozen driver.", "pathCategories": [], "probeCapabilities": [] }
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "fixtureId": "host-descriptor.negative.command-data", "contract": "host-descriptor", "class": "negative",
3
+ "description": "Descriptor command data is outside the closed contract.",
4
+ "validation": { "schemaId": "https://contracts.skill-family.example/v1/host-descriptor.json", "dialect": "2020-12", "policy": "strict" },
5
+ "expect": { "valid": false, "errorCode": "SFC1001" },
6
+ "data": { "schemaVersion": 1, "kind": "skill-family.host-descriptor", "hostId": "fixture-host", "displayName": "Fixture Host", "support": "unsupported", "unsupportedReason": "No frozen driver.", "pathCategories": [], "probeCapabilities": [], "executable": "dangerous" }
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "fixtureId": "host-descriptor.positive.unsupported", "contract": "host-descriptor", "class": "positive",
3
+ "description": "An unsupported host is explicit and carries neither a driver nor paths.",
4
+ "validation": { "schemaId": "https://contracts.skill-family.example/v1/host-descriptor.json", "dialect": "2020-12", "policy": "strict" },
5
+ "expect": { "valid": true },
6
+ "data": { "schemaVersion": 1, "kind": "skill-family.host-descriptor", "hostId": "fixture-host", "displayName": "Fixture Host", "support": "unsupported", "unsupportedReason": "No frozen driver.", "pathCategories": [], "probeCapabilities": [] }
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "fixtureId": "host-operation-plan.dialect-boundary.unsupported", "contract": "host-operation-plan", "class": "dialect-boundary",
3
+ "description": "Unsupported schema dialect fails before validation.",
4
+ "validation": { "schemaId": "https://contracts.skill-family.example/v1/host-operation-plan.json", "dialect": "draft-04", "policy": "strict" },
5
+ "expect": { "valid": false, "errorCode": "SFC1006" },
6
+ "data": { "schemaVersion": 1, "kind": "skill-family.host-operation-plan" }
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "fixtureId": "host-operation-plan.negative.unsupported-action", "contract": "host-operation-plan", "class": "negative",
3
+ "description": "Unsupported planning cannot hide an action.",
4
+ "validation": { "schemaId": "https://contracts.skill-family.example/v1/host-operation-plan.json", "dialect": "2020-12", "policy": "strict" },
5
+ "expect": { "valid": false, "errorCode": "SFC1001" },
6
+ "data": { "schemaVersion": 1, "kind": "skill-family.host-operation-plan", "hostId": "fixture-host", "status": "unsupported", "unsupportedReason": "No frozen driver.", "probeFacts": [], "actions": [{ "sequence": 1 }], "writeSet": [], "digest": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" }
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "fixtureId": "host-operation-plan.positive.unsupported", "contract": "host-operation-plan", "class": "positive",
3
+ "description": "Unsupported planning is an explicit zero-action result.",
4
+ "validation": { "schemaId": "https://contracts.skill-family.example/v1/host-operation-plan.json", "dialect": "2020-12", "policy": "strict" },
5
+ "expect": { "valid": true },
6
+ "data": { "schemaVersion": 1, "kind": "skill-family.host-operation-plan", "hostId": "fixture-host", "status": "unsupported", "unsupportedReason": "No frozen driver.", "probeFacts": [], "actions": [], "writeSet": [], "digest": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" }
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "fixtureId": "host-operation-receipt.dialect-boundary.unsupported", "contract": "host-operation-receipt", "class": "dialect-boundary",
3
+ "description": "Unsupported schema dialect fails before validation.",
4
+ "validation": { "schemaId": "https://contracts.skill-family.example/v1/host-operation-receipt.json", "dialect": "draft-04", "policy": "strict" },
5
+ "expect": { "valid": false, "errorCode": "SFC1006" },
6
+ "data": { "schemaVersion": 1, "kind": "skill-family.host-operation-receipt" }
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "fixtureId": "host-operation-receipt.negative.success-without-hash", "contract": "host-operation-receipt", "class": "negative",
3
+ "description": "A successful write without sha256 is rejected.",
4
+ "validation": { "schemaId": "https://contracts.skill-family.example/v1/host-operation-receipt.json", "dialect": "2020-12", "policy": "strict" },
5
+ "expect": { "valid": false, "errorCode": "SFC1001" },
6
+ "data": { "schemaVersion": 1, "kind": "skill-family.host-operation-receipt", "hostId": "fixture-host", "planDigest": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "overall": "succeeded", "authorizationRef": "approval-1", "writes": [{ "target": ".fixture/skills/demo/SKILL.md", "outcome": "succeeded" }], "closure": { "digestAlgorithm": "sha256", "digest": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "resources": [{ "path": ".fixture/skills/demo/SKILL.md", "sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" }] } }
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "fixtureId": "host-operation-receipt.positive.rejected", "contract": "host-operation-receipt", "class": "positive",
3
+ "description": "A rejected receipt has a reason and zero writes.",
4
+ "validation": { "schemaId": "https://contracts.skill-family.example/v1/host-operation-receipt.json", "dialect": "2020-12", "policy": "strict" },
5
+ "expect": { "valid": true },
6
+ "data": { "schemaVersion": 1, "kind": "skill-family.host-operation-receipt", "hostId": "fixture-host", "planDigest": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "overall": "rejected", "reason": "Apply is unavailable.", "writes": [] }
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "fixtureId": "host-probe-result.dialect-boundary.unsupported", "contract": "host-probe-result", "class": "dialect-boundary",
3
+ "description": "Unsupported schema dialect fails before probe-result validation.",
4
+ "validation": { "schemaId": "https://contracts.skill-family.example/v1/host-probe-result.json", "dialect": "draft-04", "policy": "strict" },
5
+ "expect": { "valid": false, "errorCode": "SFC1006" },
6
+ "data": { "schemaVersion": 1, "kind": "skill-family.host-probe-result", "hostId": "fixture-host", "support": "unsupported", "reason": "No audited driver.", "facts": [] }
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "fixtureId": "host-probe-result.negative.unsupported-with-fact", "contract": "host-probe-result", "class": "negative",
3
+ "description": "An unsupported host cannot claim a capability fact.",
4
+ "validation": { "schemaId": "https://contracts.skill-family.example/v1/host-probe-result.json", "dialect": "2020-12", "policy": "strict" },
5
+ "expect": { "valid": false, "errorCode": "SFC1001" },
6
+ "data": { "schemaVersion": 1, "kind": "skill-family.host-probe-result", "hostId": "fixture-host", "support": "unsupported", "reason": "No audited driver.", "facts": [{ "schemaVersion": 1, "kind": "skill-family.host-capability-fact", "hostId": "fixture-host", "capability": "cli", "state": "unavailable", "evidence": ["Not present."] }] }
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "fixtureId": "host-probe-result.positive.unsupported", "contract": "host-probe-result", "class": "positive",
3
+ "description": "An unsupported host probe is explicit and contains no capability facts.",
4
+ "validation": { "schemaId": "https://contracts.skill-family.example/v1/host-probe-result.json", "dialect": "2020-12", "policy": "strict" },
5
+ "expect": { "valid": true },
6
+ "data": { "schemaVersion": 1, "kind": "skill-family.host-probe-result", "hostId": "fixture-host", "support": "unsupported", "reason": "No audited driver.", "facts": [] }
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "fixtureId": "host-registry.dialect-boundary.unsupported", "contract": "host-registry", "class": "dialect-boundary",
3
+ "description": "Unsupported schema dialect fails before registry validation.",
4
+ "validation": { "schemaId": "https://contracts.skill-family.example/v1/host-registry.json", "dialect": "draft-04", "policy": "strict" },
5
+ "expect": { "valid": false, "errorCode": "SFC1006" },
6
+ "data": { "schemaVersion": 1, "kind": "skill-family.host-registry", "hosts": ["fixture-host"] }
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "fixtureId": "host-registry.negative.duplicate-host", "contract": "host-registry", "class": "negative",
3
+ "description": "A registry cannot declare the same fictional host twice.",
4
+ "validation": { "schemaId": "https://contracts.skill-family.example/v1/host-registry.json", "dialect": "2020-12", "policy": "strict" },
5
+ "expect": { "valid": false, "errorCode": "SFC1001" },
6
+ "data": { "schemaVersion": 1, "kind": "skill-family.host-registry", "hosts": ["fixture-host", "fixture-host"] }
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "fixtureId": "host-registry.positive.reference", "contract": "host-registry", "class": "positive",
3
+ "description": "A registry declares fictional host identifiers without embedding Profile data.",
4
+ "validation": { "schemaId": "https://contracts.skill-family.example/v1/host-registry.json", "dialect": "2020-12", "policy": "strict" },
5
+ "expect": { "valid": true },
6
+ "data": { "schemaVersion": 1, "kind": "skill-family.host-registry", "hosts": ["fixture-host"] }
7
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "fixtureId": "report-binding.dialect-boundary.unsupported-dialect",
3
+ "contract": "report-binding",
4
+ "class": "dialect-boundary",
5
+ "description": "Dialect boundary: a valid binding document declared under an unsupported dialect (draft-04) is rejected before any schema validation, with the stable dialect code.",
6
+ "validation": {
7
+ "schemaId": "https://contracts.skill-family.example/v1/report-binding.json",
8
+ "dialect": "draft-04",
9
+ "policy": "strict"
10
+ },
11
+ "expect": { "valid": false, "errorCode": "SFC1006" },
12
+ "data": {
13
+ "schemaVersion": 1,
14
+ "kind": "skill-family.report-binding",
15
+ "runId": "run-aurora-0001",
16
+ "modelDigest": "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789",
17
+ "resultDigest": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
18
+ "reportDigest": "fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210",
19
+ "rendererVersion": "0.2.0"
20
+ }
21
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "fixtureId": "report-binding.negative.wrong-kind",
3
+ "contract": "report-binding",
4
+ "class": "negative",
5
+ "description": "A binding with a wrong kind discriminator is rejected: a report model or any other document cannot impersonate the binding envelope.",
6
+ "validation": {
7
+ "schemaId": "https://contracts.skill-family.example/v1/report-binding.json",
8
+ "dialect": "2020-12",
9
+ "policy": "strict"
10
+ },
11
+ "expect": { "valid": false, "errorCode": "SFC1001" },
12
+ "data": {
13
+ "schemaVersion": 1,
14
+ "kind": "skill-family.report-model",
15
+ "runId": "run-aurora-0001",
16
+ "modelDigest": "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789",
17
+ "resultDigest": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
18
+ "reportDigest": "fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210",
19
+ "rendererVersion": "0.2.0"
20
+ }
21
+ }