skill-family-contracts 0.2.1 → 0.3.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/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # Changelog
2
2
 
3
+ <!-- release-skill:changelog:start version=0.3.0 locale=en baseline=sha256:32a8df58662f9dbb64e142cb8a2b329556b4bd59872e48fcbc317cc971cbdd7c -->
4
+ ## [0.3.0] - 2026-08-12
5
+
6
+ This source candidate replaces the Quickstart Profile candidate with v2 while preserving the stable Contracts registry and kernel protocol.
7
+
8
+ ### Added
9
+
10
+ - Adds the v2 protocol definition with the business-neutral execute-method operation and a real $id-indexed Resource, Task, and Result schema collection.
11
+ - Enforces JSON-safe Task and Result boundaries, one path-backed observation, terminal output shapes, and exact evidence bindings.
12
+
13
+ ### Changed
14
+
15
+ - Replaces the incompatible 0.2.1 candidate surface; consumers that still require v1 must remain pinned to exactly 0.2.1.
16
+ - Keeps method identifiers, parameter schemas, and domain result semantics under consumer ownership.
17
+
18
+ ### Upgrade Notes
19
+
20
+ Version 0.3.0 is a local, unpublished source candidate. Pin all candidate imports to an exact package version and migrate v1 integrations before selecting this version.
21
+ <!-- release-skill:changelog:end version=0.3.0 locale=en -->
22
+
23
+
3
24
  <!-- release-skill:changelog:start version=0.2.1 locale=en baseline=sha256:b759650e4d52969bd907bccea937c64622b00b0ff97851da9c6c4aee8adb4888 -->
4
25
  ## [0.2.1] - 2026-08-10
5
26
 
@@ -1,5 +1,26 @@
1
1
  # 变更日志
2
2
 
3
+ <!-- release-skill:changelog:start version=0.3.0 locale=zh-CN baseline=sha256:5f1c4cf56cc336279cbc6f11bc6cf8ce0a7524f454ff6589e067e828707a7cc7 -->
4
+ ## [0.3.0] - 2026-08-12
5
+
6
+ 本源码候选版以 Quickstart Profile v2 替换原 candidate,同时保持稳定 Contracts 登记表和内核协议不变。
7
+
8
+ ### 新增
9
+
10
+ - 新增 v2 协议定义,以业务中立的 execute-method 作为唯一操作,并按真实 $id 登记 Resource、Task、Result Schema 集合。
11
+ - 收紧 Task 与 Result 的 JSON-safe 边界、单一 path-backed observation、终态输出形状及 evidence 精确回指。
12
+
13
+ ### 变更
14
+
15
+ - 替换与 0.2.1 不兼容的 candidate 面;仍依赖 v1 的消费者必须继续精确锁定 0.2.1。
16
+ - 方法标识、参数 Schema 与领域结果语义继续归消费者所有。
17
+
18
+ ### 升级说明
19
+
20
+ 0.3.0 当前只是本地、未发布的源码候选。candidate 导入必须精确锁定包版本,v1 接入完成迁移后才能选用本版本。
21
+ <!-- release-skill:changelog:end version=0.3.0 locale=zh-CN -->
22
+
23
+
3
24
  <!-- release-skill:changelog:start version=0.2.1 locale=zh-CN baseline=sha256:1f9b53564843c024415fda87d41abf96bc22db2ec5b90d12d7a8cd7c897a24fc -->
4
25
  ## [0.2.1] - 2026-08-10
5
26
 
package/README.md CHANGED
@@ -4,28 +4,28 @@
4
4
 
5
5
  # skill-family-contracts
6
6
 
7
- <!-- release-skill:release-version: 0.2.1 -->
7
+ <!-- release-skill:release-version: 0.3.0 -->
8
8
 
9
9
  The single authoritative package of machine-executable engineering structure and mechanism protocols (Contracts 1.4.0, frozen).
10
10
 
11
11
  <!-- release-skill:managed:start id=latest-release -->
12
- **0.2.1** (2026-08-10)
12
+ **0.3.0** (2026-08-12)
13
13
 
14
- This release adds a candidate Quickstart Profile contract surface and makes the package release documentation available in English and Simplified Chinese.
14
+ This source candidate replaces the Quickstart Profile candidate with v2 while preserving the stable Contracts registry and kernel protocol.
15
15
 
16
16
  **Added**
17
17
 
18
- - Adds candidate Resource, Task, and Result schemas with strict validation helpers. The candidate schemas remain outside the stable Contracts registry.
19
- - Adds complete English and Simplified Chinese package documentation, including an agent quick-reference section.
18
+ - Adds the v2 protocol definition with the business-neutral execute-method operation and a real $id-indexed Resource, Task, and Result schema collection.
19
+ - Enforces JSON-safe Task and Result boundaries, one path-backed observation, terminal output shapes, and exact evidence bindings.
20
20
 
21
21
  **Changed**
22
22
 
23
- - Manages the current README and CHANGELOG release sections from one bilingual, versioned notes source.
24
- - Distributes the project NOTICE separately from the Apache-2.0 LICENSE.
23
+ - Replaces the incompatible 0.2.1 candidate surface; consumers that still require v1 must remain pinned to exactly 0.2.1.
24
+ - Keeps method identifiers, parameter schemas, and domain result semantics under consumer ownership.
25
25
 
26
26
  **Upgrade Notes**
27
27
 
28
- Stable registry consumers do not need to change. Import the Quickstart Profile only through its candidate subpath and do not treat it as a frozen Contracts object.
28
+ Version 0.3.0 is a local, unpublished source candidate. Pin all candidate imports to an exact package version and migrate v1 integrations before selecting this version.
29
29
  <!-- release-skill:managed:end id=latest-release -->
30
30
 
31
31
  ## Problem It Solves
@@ -41,14 +41,14 @@ Schema validation is based entirely on [Ajv](https://ajv.js.org/) (exact version
41
41
  ## Installation and Minimal Example
42
42
 
43
43
  ```sh
44
- npm install skill-family-contracts@0.2.1
44
+ npm install skill-family-contracts@0.3.0
45
45
  npm info skill-family-contracts --help
46
46
  ```
47
47
 
48
48
  The minimal example starts from an empty directory and demonstrates how to validate a registered contract object:
49
49
 
50
50
  ```js
51
- // Run from an empty directory: npm install skill-family-contracts@0.2.1
51
+ // Run from an empty directory: npm install skill-family-contracts@0.3.0
52
52
  import { validateDocument } from "skill-family-contracts";
53
53
 
54
54
  const document = {
@@ -81,7 +81,9 @@ import {
81
81
  } from "skill-family-contracts/candidate/quickstart-profile";
82
82
  ```
83
83
 
84
- The subpath is public but **not stable**. Its schemas are deliberately absent from `src/registry.json`, do not expand the eighteen stable object classes, and may change or be removed in a later minor release. Consumers should pin the exact package version and keep candidate imports outside their stable public API. Use the root package export for production contracts that require frozen registry and compatibility guarantees.
84
+ Version 0.3.0 carries Quickstart Profile v2. Its protocol fixes the business-neutral operation to `execute-method`; Resource, Task, and Result schemas resolve one another through their real v2 `$id` values. Foundation validates the JSON-safe exchange shape, while the consumer owns method identifiers, parameter schemas, and domain results.
85
+
86
+ The subpath is public but **not stable**. Its schemas remain absent from `src/registry.json`, do not expand the eighteen stable object classes, and may change or be removed in a later minor release. Pin exactly `0.3.0` when evaluating v2. Integrations that still require candidate v1 must stay pinned to exactly `0.2.1`.
85
87
 
86
88
  ## Typical Use Cases
87
89
 
package/README.zh-CN.md CHANGED
@@ -5,28 +5,28 @@
5
5
 
6
6
  # skill-family-contracts
7
7
 
8
- <!-- release-skill:release-version: 0.2.1 -->
8
+ <!-- release-skill:release-version: 0.3.0 -->
9
9
 
10
10
  机器可执行工程结构和机制协议的唯一权威包(Contracts 1.4.0,冻结)。
11
11
 
12
12
  <!-- release-skill:managed:start id=latest-release -->
13
- **0.2.1** (2026-08-10)
13
+ **0.3.0** (2026-08-12)
14
14
 
15
- 本版新增 Quickstart Profile 候选契约面,并为包发布文档提供完整英文版与简体中文版。
15
+ 本源码候选版以 Quickstart Profile v2 替换原 candidate,同时保持稳定 Contracts 登记表和内核协议不变。
16
16
 
17
17
  **新增**
18
18
 
19
- - 新增候选 Resource、Task、Result Schema 及其严格校验辅助函数。这组候选 Schema 不进入稳定 Contracts 登记表。
20
- - 新增完整的英文与简体中文包文档,并补充智能体快速参考章节。
19
+ - 新增 v2 协议定义,以业务中立的 execute-method 作为唯一操作,并按真实 $id 登记 Resource、Task、Result Schema 集合。
20
+ - 收紧 Task 与 Result 的 JSON-safe 边界、单一 path-backed observation、终态输出形状及 evidence 精确回指。
21
21
 
22
22
  **变更**
23
23
 
24
- - 使用同一份双语版本化说明源管理当前 README 与 CHANGELOG 的发布区域。
25
- - 项目 NOTICE 与 Apache-2.0 LICENSE 分开分发。
24
+ - 替换与 0.2.1 不兼容的 candidate 面;仍依赖 v1 的消费者必须继续精确锁定 0.2.1。
25
+ - 方法标识、参数 Schema 与领域结果语义继续归消费者所有。
26
26
 
27
27
  **升级说明**
28
28
 
29
- 稳定登记表的消费者无需修改。Quickstart Profile 只能通过 candidate 子路径导入,不得把它当作已冻结的 Contracts 对象。
29
+ 0.3.0 当前只是本地、未发布的源码候选。candidate 导入必须精确锁定包版本,v1 接入完成迁移后才能选用本版本。
30
30
  <!-- release-skill:managed:end id=latest-release -->
31
31
 
32
32
  ## 解决的问题
@@ -42,14 +42,14 @@ Schema 验证完全基于 [Ajv](https://ajv.js.org/)(精确版本见 `package.
42
42
  ## 安装和最小示例
43
43
 
44
44
  ```sh
45
- npm install skill-family-contracts@0.2.1
45
+ npm install skill-family-contracts@0.3.0
46
46
  npm info skill-family-contracts --help
47
47
  ```
48
48
 
49
49
  最小示例从空目录开始,演示如何校验一份已登记契约对象:
50
50
 
51
51
  ```js
52
- // 从空目录运行:npm install skill-family-contracts@0.2.1
52
+ // 从空目录运行:npm install skill-family-contracts@0.3.0
53
53
  import { validateDocument } from "skill-family-contracts";
54
54
 
55
55
  const document = {
@@ -82,7 +82,9 @@ import {
82
82
  } from "skill-family-contracts/candidate/quickstart-profile";
83
83
  ```
84
84
 
85
- 以上子路径公开但**不稳定**。这些 Schema 刻意不进入 `src/registry.json`,不扩张十八类稳定对象,后续小版本可以修改或移除。调用方需要锁定精确包版本,并避免把 candidate 导入转成自身稳定公共 API。生产合同若需要冻结登记与兼容性保证,继续使用包根导出。
85
+ 0.3.0 携带 Quickstart Profile v2。协议把业务中立的操作固定为 `execute-method`,Resource、Task、Result Schema 通过真实的 v2 `$id` 互相解析。Foundation 只校验 JSON-safe 交换结构;方法标识、参数 Schema 与领域结果继续归消费者所有。
86
+
87
+ 以上子路径公开但**不稳定**。这些 Schema 不进入 `src/registry.json`,也不扩张十八类稳定对象,后续小版本可以修改或移除。评估 v2 时应精确锁定 `0.3.0`;仍依赖 candidate v1 的接入必须继续精确锁定 `0.2.1`。
86
88
 
87
89
  ## 典型使用场景
88
90
 
@@ -0,0 +1,13 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "kind": "skill-family.resource",
4
+ "id": "acme-review-observation",
5
+ "location": {
6
+ "path": "/etc/acme-review-observation.json"
7
+ },
8
+ "role": "observation",
9
+ "digest": {
10
+ "algorithm": "sha256",
11
+ "value": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
12
+ }
13
+ }
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "kind": "skill-family.resource",
4
- "id": "observation-main",
4
+ "id": "acme-review-observation",
5
5
  "location": {
6
- "path": "inputs/observation.json"
6
+ "path": "inputs/acme-review-observation.json"
7
7
  },
8
8
  "role": "observation",
9
9
  "digest": {
@@ -0,0 +1,13 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "kind": "skill-family.resource",
4
+ "id": "acme-review-evidence-uri",
5
+ "location": {
6
+ "uri": "urn:example:acme-review:evidence:0001"
7
+ },
8
+ "role": "evidence",
9
+ "digest": {
10
+ "algorithm": "sha256",
11
+ "value": "fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210"
12
+ }
13
+ }
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "kind": "skill-family.resource",
4
- "id": "observation-main",
4
+ "id": "acme-review-observation",
5
5
  "location": {
6
- "path": "inputs/observation.json"
6
+ "path": "inputs/acme-review-observation.json"
7
7
  },
8
8
  "role": "observation",
9
9
  "digest": {
@@ -1,12 +1,31 @@
1
1
  import { readFileSync } from "node:fs";
2
- import { detectDialect, validateDocument } from "../../src/validator.mjs";
2
+ import Ajv2020 from "ajv/dist/2020.js";
3
+ import { findSchemaRegistration } from "../../src/registry.mjs";
4
+
5
+ /**
6
+ * Candidate quickstart profile v2 (unstable).
7
+ *
8
+ * Unlike v1, the collection is registered under its real $ids in one
9
+ * dialect-matching Ajv instance: task and result reference the v2 Resource
10
+ * schema and the stable operation-request/operation-result envelopes by
11
+ * $ref, and no $ref is rewritten into a local $def. The candidate $ids stay
12
+ * absent from the stable registry on purpose.
13
+ */
3
14
 
4
15
  const documents = Object.freeze({
16
+ protocol: load("protocol.json"),
5
17
  resource: load("resource.schema.json"),
6
18
  task: load("task.schema.json"),
7
19
  result: load("result.schema.json"),
8
20
  });
9
21
 
22
+ const VALIDATE_KINDS = Object.freeze(["resource", "task", "result"]);
23
+
24
+ const STABLE_ENVELOPE_IDS = Object.freeze([
25
+ "https://contracts.skill-family.example/v1/operation-request.json",
26
+ "https://contracts.skill-family.example/v1/operation-result.json",
27
+ ]);
28
+
10
29
  function load(name) {
11
30
  return Object.freeze(
12
31
  JSON.parse(readFileSync(new URL(name, import.meta.url), "utf8")),
@@ -14,46 +33,226 @@ function load(name) {
14
33
  }
15
34
 
16
35
  export const QUICKSTART_PROFILE_ID = "quickstart-profile";
17
- export const QUICKSTART_PROFILE_VERSION = 1;
36
+ export const QUICKSTART_PROFILE_VERSION = 2;
18
37
  export const QUICKSTART_PROTOCOL = Object.freeze({
19
- name: "skill-family.quickstart-profile",
20
- version: 1,
38
+ name: documents.protocol.name,
39
+ version: documents.protocol.version,
21
40
  });
41
+ export const QUICKSTART_OPERATION = Object.freeze(
42
+ documents.protocol.operations.map((operation) => operation.name),
43
+ );
44
+
45
+ /** The frozen candidate protocol definition document (not a JSON Schema). */
46
+ export function loadQuickstartProtocol() {
47
+ return structuredClone(documents.protocol);
48
+ }
22
49
 
23
50
  /** Candidate-only schemas. They are deliberately absent from registry.json. */
24
51
  export function quickstartProfileSchemas() {
25
- return structuredClone(documents);
52
+ return structuredClone({
53
+ resource: documents.resource,
54
+ task: documents.task,
55
+ result: documents.result,
56
+ });
57
+ }
58
+
59
+ /** Enumerates the collection as registry-style entries ($id + document). */
60
+ export function listQuickstartProfileSchemas() {
61
+ return VALIDATE_KINDS.map((kind) => ({
62
+ kind,
63
+ $id: documents[kind].$id,
64
+ document: structuredClone(documents[kind]),
65
+ }));
66
+ }
67
+
68
+ // The stable envelopes declare a date-time format; the candidate instance
69
+ // must define it identically so the envelopes compile under validateFormats.
70
+ const DATE_TIME_PATTERN =
71
+ /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])[Tt]([01]\d|2[0-3]):[0-5]\d:[0-5]\d(\.\d+)?([Zz]|[+-]([01]\d|2[0-3]):[0-5]\d)$/;
72
+
73
+ function isValidDateTime(value) {
74
+ if (typeof value !== "string" || !DATE_TIME_PATTERN.test(value)) return false;
75
+ const [datePart] = value.split(/[Tt]/, 1);
76
+ const [year, month, day] = datePart.split("-").map(Number);
77
+ const roundTrip = new Date(Date.UTC(year, month - 1, day));
78
+ return (
79
+ roundTrip.getUTCFullYear() === year &&
80
+ roundTrip.getUTCMonth() === month - 1 &&
81
+ roundTrip.getUTCDate() === day
82
+ );
26
83
  }
27
84
 
28
- function linkResourceSchema(schema) {
29
- const linked = structuredClone(schema);
30
- const resourceId = documents.resource.$id;
31
- const visit = (value) => {
32
- if (Array.isArray(value)) {
33
- for (const item of value) visit(item);
34
- return;
85
+ let collection = null;
86
+
87
+ function getCollection() {
88
+ if (collection) return collection;
89
+ const ajv = new Ajv2020({
90
+ coerceTypes: false,
91
+ useDefaults: false,
92
+ allErrors: true,
93
+ validateFormats: true,
94
+ strict: true,
95
+ });
96
+ ajv.addFormat("date-time", { type: "string", validate: isValidDateTime });
97
+ for (const schemaId of STABLE_ENVELOPE_IDS) {
98
+ const registration = findSchemaRegistration(schemaId);
99
+ if (!registration) {
100
+ throw new Error(`stable envelope schema is not registered: ${schemaId}`);
35
101
  }
36
- if (!value || typeof value !== "object") return;
37
- if (value.$ref === resourceId) value.$ref = "#/$defs/quickstartResource";
38
- for (const child of Object.values(value)) visit(child);
39
- };
40
- visit(linked);
41
- linked.$defs = {
42
- ...(linked.$defs ?? {}),
43
- quickstartResource: structuredClone(documents.resource),
44
- };
45
- return linked;
102
+ ajv.addSchema(
103
+ JSON.parse(readFileSync(new URL(`../../${registration.file}`, import.meta.url), "utf8")),
104
+ );
105
+ }
106
+ for (const kind of VALIDATE_KINDS) {
107
+ ajv.addSchema(documents[kind]);
108
+ }
109
+ collection = Object.freeze(
110
+ Object.fromEntries(
111
+ VALIDATE_KINDS.map((kind) => [kind, ajv.getSchema(documents[kind].$id)]),
112
+ ),
113
+ );
114
+ return collection;
46
115
  }
47
116
 
48
- /** Validate one candidate profile document through the shared Ajv validator. */
117
+ function normalizeErrors(ajvErrors) {
118
+ if (!ajvErrors) return [];
119
+ return ajvErrors.map((error) => ({
120
+ keyword: error.keyword,
121
+ instancePath: error.instancePath,
122
+ schemaPath: error.schemaPath,
123
+ message: error.message,
124
+ params: error.params,
125
+ }));
126
+ }
127
+
128
+ function escapePointerSegment(segment) {
129
+ return String(segment).replaceAll("~", "~0").replaceAll("/", "~1");
130
+ }
131
+
132
+ const ARRAY_INDEX_PATTERN = /^(0|[1-9]\d*)$/;
133
+
134
+ // Inspects own properties through descriptors only, so accessors are refused
135
+ // without ever being invoked. JSON would drop symbol-keyed and non-enumerable
136
+ // properties silently and execute accessors, so all three classes fail closed.
137
+ function probeObjectMembers(value, instancePath, ancestors) {
138
+ const symbolKeys = Object.getOwnPropertySymbols(value);
139
+ if (symbolKeys.length > 0) {
140
+ return {
141
+ instancePath: `${instancePath}/${escapePointerSegment(String(symbolKeys[0]))}`,
142
+ reason: "symbol-keyed property",
143
+ };
144
+ }
145
+ const isArray = Array.isArray(value);
146
+ let arrayLength = 0;
147
+ if (isArray) {
148
+ const lengthDescriptor = Object.getOwnPropertyDescriptor(value, "length");
149
+ if (!lengthDescriptor || lengthDescriptor.get || lengthDescriptor.set) {
150
+ return { instancePath: `${instancePath}/length`, reason: "accessor property" };
151
+ }
152
+ arrayLength = lengthDescriptor.value;
153
+ }
154
+ for (const key of Object.getOwnPropertyNames(value)) {
155
+ if (isArray && key === "length") continue;
156
+ const memberPath = `${instancePath}/${escapePointerSegment(key)}`;
157
+ if (isArray && (!ARRAY_INDEX_PATTERN.test(key) || Number(key) >= arrayLength)) {
158
+ return { instancePath: memberPath, reason: "non-index array property" };
159
+ }
160
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
161
+ if (!descriptor) continue;
162
+ if (descriptor.get || descriptor.set) {
163
+ return { instancePath: memberPath, reason: "accessor property" };
164
+ }
165
+ if (!descriptor.enumerable) {
166
+ return { instancePath: memberPath, reason: "non-enumerable property" };
167
+ }
168
+ const issue = probeJsonValue(descriptor.value, memberPath, ancestors);
169
+ if (issue) return issue;
170
+ }
171
+ return null;
172
+ }
173
+
174
+ function probeJsonValue(value, instancePath, ancestors) {
175
+ if (value === null) return null;
176
+ const type = typeof value;
177
+ if (type === "boolean" || type === "string") return null;
178
+ if (type === "number") {
179
+ return Number.isFinite(value) ? null : { instancePath, reason: "non-finite number" };
180
+ }
181
+ if (type === "bigint") return { instancePath, reason: "bigint" };
182
+ if (type === "undefined") return { instancePath, reason: "undefined" };
183
+ if (type === "function" || type === "symbol") return { instancePath, reason: type };
184
+ if (type !== "object") return { instancePath, reason: type };
185
+ const isArray = Array.isArray(value);
186
+ if (!isArray) {
187
+ const prototype = Object.getPrototypeOf(value);
188
+ if (prototype !== Object.prototype && prototype !== null) {
189
+ return {
190
+ instancePath,
191
+ reason: `non-plain object (${Object.prototype.toString.call(value)})`,
192
+ };
193
+ }
194
+ }
195
+ if (ancestors.has(value)) return { instancePath, reason: "circular reference" };
196
+ ancestors.add(value);
197
+ const issue = probeObjectMembers(value, instancePath, ancestors);
198
+ ancestors.delete(value);
199
+ return issue;
200
+ }
201
+
202
+ /**
203
+ * Deep JSON-safety probe for candidate documents and caller-owned values.
204
+ * Pure JSON data is null, booleans, finite numbers, strings, arrays, and
205
+ * plain objects. Anything else (bigint, undefined, function, symbol,
206
+ * non-finite number, non-plain object, circular reference) is reported as
207
+ * { instancePath, reason } at the first offending location, instead of
208
+ * surviving into structuredClone, digestDocument, or JSON.stringify where it
209
+ * would throw or silently drift. Own properties JSON would ignore or execute
210
+ * are refused the same way: symbol-keyed and non-enumerable properties are
211
+ * dropped by serialization and accessors would be executed, so the probe
212
+ * rejects them through descriptors without invoking any accessor. Repeated
213
+ * references to the same JSON-safe object stay accepted when acyclic; only
214
+ * true cycles fail closed. Returns null when the value is pure JSON.
215
+ */
216
+ export function findNonJsonValue(value) {
217
+ return probeJsonValue(value, "", new Set());
218
+ }
219
+
220
+ /**
221
+ * Validates one candidate profile document against the collection registered
222
+ * under real $ids. Returns { valid, errorCode, errors, data } with the same
223
+ * shape as the stable validateDocument; data is a normalized deep copy and
224
+ * caller input is never mutated. Documents containing non-JSON values are
225
+ * refused deterministically before any clone or serialization.
226
+ */
49
227
  export function validateQuickstartProfileDocument(kind, document) {
50
- if (!Object.hasOwn(documents, kind)) {
228
+ if (!VALIDATE_KINDS.includes(kind)) {
51
229
  throw new TypeError(`unknown quickstart profile document kind: ${String(kind)}`);
52
230
  }
53
- const schema = kind === "resource" ? documents.resource : linkResourceSchema(documents[kind]);
54
- return validateDocument(document, {
55
- schema,
56
- dialect: detectDialect(schema),
57
- policy: "strict",
58
- });
231
+ const target = document === undefined ? null : document;
232
+ const jsonIssue = findNonJsonValue(target);
233
+ if (jsonIssue) {
234
+ return {
235
+ valid: false,
236
+ errorCode: "SFC1001",
237
+ errors: [
238
+ {
239
+ keyword: "json-value",
240
+ instancePath: jsonIssue.instancePath,
241
+ schemaPath: "#",
242
+ message: `value is not representable as JSON: ${jsonIssue.reason}`,
243
+ params: { reason: jsonIssue.reason },
244
+ },
245
+ ],
246
+ data: null,
247
+ };
248
+ }
249
+ const validate = getCollection()[kind];
250
+ const data = structuredClone(target);
251
+ const valid = validate(data) === true;
252
+ return {
253
+ valid,
254
+ errorCode: valid ? null : "SFC1001",
255
+ errors: valid ? [] : normalizeErrors(validate.errors),
256
+ data,
257
+ };
59
258
  }
@@ -0,0 +1,64 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "kind": "skill-family.protocol-definition",
4
+ "name": "skill-family.quickstart-profile",
5
+ "version": 2,
6
+ "status": "candidate",
7
+ "description": "Candidate v2 quickstart profile protocol. One business-neutral operation (execute-method) wraps a caller-owned method around exactly one path-backed observation Resource and binds the terminal Result back to the complete canonical Task. Foundation never registers or interprets the caller method vocabulary; this definition deliberately stays outside the stable protocol registry.",
8
+ "terminalStates": ["succeeded", "failed", "rejected"],
9
+ "operations": [
10
+ {
11
+ "name": "execute-method",
12
+ "status": "candidate",
13
+ "description": "Executes one caller-owned method. params.method identifies the method and params.parameters carries caller-owned parameters; Foundation does not interpret either. params.inputs holds exactly one path-backed observation Resource; params.correlation carries run, stage, and attempt (attempt counts from 1). succeeded means the mechanism execution completed, not that a domain conclusion passed.",
14
+ "params": {
15
+ "type": "object",
16
+ "additionalProperties": false,
17
+ "required": ["method", "parameters", "inputs", "correlation"],
18
+ "properties": {
19
+ "method": {
20
+ "type": "string",
21
+ "maxLength": 128,
22
+ "pattern": "^[a-z][a-z0-9]*(-[a-z0-9]+)*$",
23
+ "description": "Caller-owned method identifier; no Foundation method vocabulary exists."
24
+ },
25
+ "parameters": {
26
+ "type": "object",
27
+ "description": "Caller-owned parameter object; Foundation never reads its fields."
28
+ },
29
+ "inputs": {
30
+ "type": "array",
31
+ "minItems": 1,
32
+ "maxItems": 1,
33
+ "description": "Exactly one path-backed observation Resource."
34
+ },
35
+ "correlation": {
36
+ "type": "object",
37
+ "additionalProperties": false,
38
+ "required": ["run", "stage", "attempt"],
39
+ "properties": {
40
+ "run": {
41
+ "type": "string",
42
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$"
43
+ },
44
+ "stage": {
45
+ "type": "string",
46
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$"
47
+ },
48
+ "attempt": {
49
+ "type": "integer",
50
+ "minimum": 1
51
+ }
52
+ }
53
+ }
54
+ }
55
+ }
56
+ }
57
+ ],
58
+ "resultContract": {
59
+ "outputs": {
60
+ "execute-method": "On succeeded, outputs is mandatory and carries summary, outputs (role=output Resources), evidence (role=evidence Resources), the caller-owned domainResult, and a taskBinding that echoes operationId, the complete canonical Task digest, the observation id/digest, and the exact run/stage/attempt correlation. taskBinding.evidenceBindings carries exactly one mechanism back-reference per evidence Resource, each fixing resourceId, operationId, observationId, and the run/stage/attempt correlation; the array only proves the evidence belongs to this Task exchange and never interprets which domain conclusion the evidence supports. On failed and rejected, outputs is mandatory and null."
61
+ },
62
+ "errors": "Every error entry carries a code registered in the frozen contracts error registry; a format-conforming but unregistered code is refused. failed and rejected results carry at least one."
63
+ }
64
+ }
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://contracts.skill-family.example/candidate/quickstart-profile/v1/resource.json",
3
+ "$id": "https://contracts.skill-family.example/candidate/quickstart-profile/v2/resource.json",
4
4
  "title": "QuickstartResource",
5
- "description": "Candidate Resource profile for one content-addressed observation, output, or evidence item.",
5
+ "description": "Candidate Resource profile (v2) for one content-addressed observation, output, or evidence item. location.path and location.uri are mutually exclusive; URIs are only structurally checked and never fetched.",
6
6
  "type": "object",
7
7
  "additionalProperties": false,
8
8
  "required": ["schemaVersion", "kind", "id", "location", "role", "digest"],
@@ -23,11 +23,13 @@
23
23
  "properties": {
24
24
  "path": {
25
25
  "type": "string",
26
- "pattern": "^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$))[A-Za-z0-9._-]+(?:/[A-Za-z0-9._-]+)*$"
26
+ "maxLength": 1024,
27
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]*(?:/[A-Za-z0-9][A-Za-z0-9._-]*)*$"
27
28
  },
28
29
  "uri": {
29
30
  "type": "string",
30
- "pattern": "^[A-Za-z][A-Za-z0-9+.-]*:.+$"
31
+ "maxLength": 2048,
32
+ "pattern": "^[A-Za-z][A-Za-z0-9+.-]*:[^\\s]+$"
31
33
  }
32
34
  },
33
35
  "oneOf": [
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://contracts.skill-family.example/candidate/quickstart-profile/v1/result.json",
3
+ "$id": "https://contracts.skill-family.example/candidate/quickstart-profile/v2/result.json",
4
4
  "title": "QuickstartResult",
5
- "description": "Candidate narrow profile over the stable terminal operation-result envelope.",
5
+ "description": "Candidate narrow profile (v2) over the stable terminal operation-result envelope. Every terminal state fixes outputs explicitly: succeeded requires the outputs object, failed and rejected require null outputs plus at least one error whose code is registered in the frozen contracts error registry. succeeded.outputs fixes summary, outputs, evidence, domainResult, and a taskBinding whose evidenceBindings carries exactly one mechanism back-reference per evidence Resource. domainResult is an arbitrary caller-owned JSON value that Foundation never interprets.",
6
6
  "allOf": [
7
7
  {
8
8
  "$ref": "https://contracts.skill-family.example/v1/operation-result.json"
@@ -13,12 +13,13 @@
13
13
  "protocol": {
14
14
  "const": {
15
15
  "name": "skill-family.quickstart-profile",
16
- "version": 1
16
+ "version": 2
17
17
  }
18
18
  },
19
19
  "operation": {
20
- "const": "audit"
20
+ "const": "execute-method"
21
21
  },
22
+ "completedAt": false,
22
23
  "outputs": {
23
24
  "oneOf": [
24
25
  {
@@ -28,6 +29,36 @@
28
29
  "$ref": "#/$defs/outputs"
29
30
  }
30
31
  ]
32
+ },
33
+ "errors": {
34
+ "type": "array",
35
+ "items": {
36
+ "type": "object",
37
+ "properties": {
38
+ "code": {
39
+ "enum": [
40
+ "SFC1001",
41
+ "SFC1002",
42
+ "SFC1003",
43
+ "SFC1004",
44
+ "SFC1005",
45
+ "SFC1006",
46
+ "SFC1007",
47
+ "SFC1008",
48
+ "SFC1009",
49
+ "SFC1010",
50
+ "SFC1011",
51
+ "SFC1012",
52
+ "SFC2002",
53
+ "SFC2003",
54
+ "SFC2004",
55
+ "SFC3001",
56
+ "SFC3002",
57
+ "SFC3003"
58
+ ]
59
+ }
60
+ }
61
+ }
31
62
  }
32
63
  },
33
64
  "allOf": [
@@ -41,7 +72,6 @@
41
72
  "required": ["state"]
42
73
  },
43
74
  "then": {
44
- "type": "object",
45
75
  "required": ["outputs"],
46
76
  "properties": {
47
77
  "outputs": {
@@ -49,6 +79,24 @@
49
79
  }
50
80
  }
51
81
  }
82
+ },
83
+ {
84
+ "if": {
85
+ "properties": {
86
+ "state": {
87
+ "enum": ["failed", "rejected"]
88
+ }
89
+ },
90
+ "required": ["state"]
91
+ },
92
+ "then": {
93
+ "required": ["outputs"],
94
+ "properties": {
95
+ "outputs": {
96
+ "const": null
97
+ }
98
+ }
99
+ }
52
100
  }
53
101
  ]
54
102
  }
@@ -73,10 +121,32 @@
73
121
  }
74
122
  }
75
123
  },
124
+ "evidenceBinding": {
125
+ "type": "object",
126
+ "additionalProperties": false,
127
+ "required": ["resourceId", "operationId", "observationId", "correlation"],
128
+ "properties": {
129
+ "resourceId": {
130
+ "type": "string",
131
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$"
132
+ },
133
+ "operationId": {
134
+ "type": "string",
135
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$"
136
+ },
137
+ "observationId": {
138
+ "type": "string",
139
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$"
140
+ },
141
+ "correlation": {
142
+ "$ref": "#/$defs/correlation"
143
+ }
144
+ }
145
+ },
76
146
  "taskBinding": {
77
147
  "type": "object",
78
148
  "additionalProperties": false,
79
- "required": ["operationId", "taskDigest", "observationId", "observationDigest", "correlation"],
149
+ "required": ["operationId", "taskDigest", "observationId", "observationDigest", "correlation", "evidenceBindings"],
80
150
  "properties": {
81
151
  "operationId": {
82
152
  "type": "string",
@@ -96,6 +166,12 @@
96
166
  },
97
167
  "correlation": {
98
168
  "$ref": "#/$defs/correlation"
169
+ },
170
+ "evidenceBindings": {
171
+ "type": "array",
172
+ "items": {
173
+ "$ref": "#/$defs/evidenceBinding"
174
+ }
99
175
  }
100
176
  }
101
177
  },
@@ -113,7 +189,7 @@
113
189
  "items": {
114
190
  "allOf": [
115
191
  {
116
- "$ref": "https://contracts.skill-family.example/candidate/quickstart-profile/v1/resource.json"
192
+ "$ref": "https://contracts.skill-family.example/candidate/quickstart-profile/v2/resource.json"
117
193
  },
118
194
  {
119
195
  "type": "object",
@@ -131,7 +207,7 @@
131
207
  "items": {
132
208
  "allOf": [
133
209
  {
134
- "$ref": "https://contracts.skill-family.example/candidate/quickstart-profile/v1/resource.json"
210
+ "$ref": "https://contracts.skill-family.example/candidate/quickstart-profile/v2/resource.json"
135
211
  },
136
212
  {
137
213
  "type": "object",
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://contracts.skill-family.example/candidate/quickstart-profile/v1/task.json",
3
+ "$id": "https://contracts.skill-family.example/candidate/quickstart-profile/v2/task.json",
4
4
  "title": "QuickstartTask",
5
- "description": "Candidate narrow profile over the stable operation-request envelope.",
5
+ "description": "Candidate narrow profile (v2) over the stable operation-request envelope. The single operation is the business-neutral execute-method; params.method and params.parameters are caller-owned and Foundation never interprets them. The Task digest binds the complete canonical Task, so the optional requestedAt timestamp is excluded from this profile.",
6
6
  "allOf": [
7
7
  {
8
8
  "$ref": "https://contracts.skill-family.example/v1/operation-request.json"
@@ -13,12 +13,13 @@
13
13
  "protocol": {
14
14
  "const": {
15
15
  "name": "skill-family.quickstart-profile",
16
- "version": 1
16
+ "version": 2
17
17
  }
18
18
  },
19
19
  "operation": {
20
- "const": "audit"
20
+ "const": "execute-method"
21
21
  },
22
+ "requestedAt": false,
22
23
  "params": {
23
24
  "type": "object",
24
25
  "additionalProperties": false,
@@ -26,6 +27,7 @@
26
27
  "properties": {
27
28
  "method": {
28
29
  "type": "string",
30
+ "maxLength": 128,
29
31
  "pattern": "^[a-z][a-z0-9]*(-[a-z0-9]+)*$"
30
32
  },
31
33
  "parameters": {
@@ -38,7 +40,7 @@
38
40
  "items": {
39
41
  "allOf": [
40
42
  {
41
- "$ref": "https://contracts.skill-family.example/candidate/quickstart-profile/v1/resource.json"
43
+ "$ref": "https://contracts.skill-family.example/candidate/quickstart-profile/v2/resource.json"
42
44
  },
43
45
  {
44
46
  "type": "object",
package/package.json CHANGED
@@ -9,7 +9,8 @@
9
9
  },
10
10
  "description": "Machine-readable contracts for Skill Family engineering.",
11
11
  "engines": {
12
- "node": ">=22.22.2 <23"
12
+ "node": ">=22.22.2 <23",
13
+ "pnpm": "10.30.0"
13
14
  },
14
15
  "exports": {
15
16
  ".": "./src/index.mjs",
@@ -37,7 +38,7 @@
37
38
  "url": "https://github.com/ifoohoo/skill-family-contracts.git"
38
39
  },
39
40
  "type": "module",
40
- "version": "0.2.1",
41
+ "version": "0.3.0",
41
42
  "scripts": {
42
43
  "check": "node --test",
43
44
  "test": "node --test"
@@ -0,0 +1,23 @@
1
+ version: 0.3.0
2
+ date: 2026-08-12
3
+ locales:
4
+ en:
5
+ summary: This source candidate replaces the Quickstart Profile candidate with v2 while preserving the stable Contracts registry and kernel protocol.
6
+ changes:
7
+ added:
8
+ - Adds the v2 protocol definition with the business-neutral execute-method operation and a real $id-indexed Resource, Task, and Result schema collection.
9
+ - Enforces JSON-safe Task and Result boundaries, one path-backed observation, terminal output shapes, and exact evidence bindings.
10
+ changed:
11
+ - Replaces the incompatible 0.2.1 candidate surface; consumers that still require v1 must remain pinned to exactly 0.2.1.
12
+ - Keeps method identifiers, parameter schemas, and domain result semantics under consumer ownership.
13
+ upgradeNotes: Version 0.3.0 is a local, unpublished source candidate. Pin all candidate imports to an exact package version and migrate v1 integrations before selecting this version.
14
+ zh-CN:
15
+ summary: 本源码候选版以 Quickstart Profile v2 替换原 candidate,同时保持稳定 Contracts 登记表和内核协议不变。
16
+ changes:
17
+ added:
18
+ - 新增 v2 协议定义,以业务中立的 execute-method 作为唯一操作,并按真实 $id 登记 Resource、Task、Result Schema 集合。
19
+ - 收紧 Task 与 Result 的 JSON-safe 边界、单一 path-backed observation、终态输出形状及 evidence 精确回指。
20
+ changed:
21
+ - 替换与 0.2.1 不兼容的 candidate 面;仍依赖 v1 的消费者必须继续精确锁定 0.2.1。
22
+ - 方法标识、参数 Schema 与领域结果语义继续归消费者所有。
23
+ upgradeNotes: 0.3.0 当前只是本地、未发布的源码候选。candidate 导入必须精确锁定包版本,v1 接入完成迁移后才能选用本版本。