release-skill 0.9.10 → 0.9.12

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 (71) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codebuddy-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +2 -2
  4. package/.kimi-plugin/plugin.json +1 -1
  5. package/CHANGELOG.md +44 -0
  6. package/INSTALL.md +2 -2
  7. package/INSTALL.zh-CN.md +2 -2
  8. package/README.md +19 -13
  9. package/README.zh-CN.md +18 -13
  10. package/adapters/claude/.claude-plugin/plugin.json +1 -1
  11. package/adapters/claude/bin/consumer-contract-vectors.json +7 -7
  12. package/adapters/claude/bin/error-codes.json +1 -1
  13. package/adapters/claude/bin/foundation-resource-binding.json +1 -1
  14. package/adapters/claude/bin/registry.json +8 -1
  15. package/adapters/claude/bin/release-skill.bundle.mjs +1064 -935
  16. package/adapters/claude/bin/rules.json +1 -1
  17. package/adapters/claude/data/hosts/codebuddy/host-descriptor.json +5 -0
  18. package/adapters/claude/schemas/release-project.schema.json +1 -1
  19. package/adapters/claude/src/schemas/engineering-baseline.schema.json +77 -0
  20. package/adapters/claude/src/schemas/host-descriptor.schema.json +9 -2
  21. package/adapters/codex/.codex-plugin/plugin.json +2 -2
  22. package/adapters/codex/bin/consumer-contract-vectors.json +7 -7
  23. package/adapters/codex/bin/error-codes.json +1 -1
  24. package/adapters/codex/bin/foundation-resource-binding.json +1 -1
  25. package/adapters/codex/bin/registry.json +8 -1
  26. package/adapters/codex/bin/release-skill.bundle.mjs +1064 -935
  27. package/adapters/codex/bin/rules.json +1 -1
  28. package/adapters/codex/data/hosts/codebuddy/host-descriptor.json +5 -0
  29. package/adapters/codex/schemas/release-project.schema.json +1 -1
  30. package/adapters/codex/src/schemas/engineering-baseline.schema.json +77 -0
  31. package/adapters/codex/src/schemas/host-descriptor.schema.json +9 -2
  32. package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
  33. package/adapters/kimi/bin/consumer-contract-vectors.json +7 -7
  34. package/adapters/kimi/bin/error-codes.json +1 -1
  35. package/adapters/kimi/bin/foundation-resource-binding.json +1 -1
  36. package/adapters/kimi/bin/registry.json +8 -1
  37. package/adapters/kimi/bin/release-skill.bundle.mjs +1064 -935
  38. package/adapters/kimi/bin/rules.json +1 -1
  39. package/adapters/kimi/data/hosts/codebuddy/host-descriptor.json +5 -0
  40. package/adapters/kimi/schemas/release-project.schema.json +1 -1
  41. package/adapters/kimi/src/schemas/engineering-baseline.schema.json +77 -0
  42. package/adapters/kimi/src/schemas/host-descriptor.schema.json +9 -2
  43. package/adapters/workbuddy/.codebuddy-plugin/plugin.json +1 -1
  44. package/adapters/workbuddy/bin/consumer-contract-vectors.json +7 -7
  45. package/adapters/workbuddy/bin/error-codes.json +1 -1
  46. package/adapters/workbuddy/bin/foundation-resource-binding.json +1 -1
  47. package/adapters/workbuddy/bin/registry.json +8 -1
  48. package/adapters/workbuddy/bin/release-skill.bundle.mjs +1064 -935
  49. package/adapters/workbuddy/bin/rules.json +1 -1
  50. package/adapters/workbuddy/data/hosts/codebuddy/host-descriptor.json +5 -0
  51. package/adapters/workbuddy/schemas/release-project.schema.json +1 -1
  52. package/adapters/workbuddy/src/schemas/engineering-baseline.schema.json +77 -0
  53. package/adapters/workbuddy/src/schemas/host-descriptor.schema.json +9 -2
  54. package/bin/consumer-contract-vectors.json +7 -7
  55. package/bin/error-codes.json +1 -1
  56. package/bin/foundation-resource-binding.json +1 -1
  57. package/bin/registry.json +8 -1
  58. package/bin/release-skill.bundle.mjs +1064 -935
  59. package/bin/rules.json +1 -1
  60. package/data/hosts/codebuddy/host-descriptor.json +5 -0
  61. package/package.json +4 -4
  62. package/platform-manifest.json +2 -2
  63. package/references/02-project-config.md +7 -1
  64. package/schemas/release-project.schema.json +1 -1
  65. package/src/commands/verify.mjs +47 -9
  66. package/src/core/proposal-document.mjs +52 -0
  67. package/src/core/proposal-inbox.mjs +8 -52
  68. package/src/core/release-metadata.mjs +51 -3
  69. package/src/producers/foundation-resource-projection.mjs +3 -3
  70. package/src/schemas/engineering-baseline.schema.json +77 -0
  71. package/src/schemas/host-descriptor.schema.json +9 -2
package/bin/rules.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "kind": "skill-family.contracts.rules",
4
- "contractsVersion": "1.15.0",
4
+ "contractsVersion": "1.16.0",
5
5
  "budget": {
6
6
  "firstVersionMax": 20,
7
7
  "absoluteMax": 30,
@@ -5,6 +5,11 @@
5
5
  "displayName": "CodeBuddy",
6
6
  "support": "manual",
7
7
  "maturity": "manual",
8
+ "verification": {
9
+ "driverId": "codebuddy-print-v1",
10
+ "authStrategy": "existing-user-state",
11
+ "credentialMutation": "host-managed"
12
+ },
8
13
  "sourceAliases": [],
9
14
  "pathCategories": [],
10
15
  "probeCapabilities": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "release-skill",
3
- "version": "0.9.10",
3
+ "version": "0.9.12",
4
4
  "description": "Safe preparation and frozen GitHub/npm production publishing with full happy end verification",
5
5
  "author": {
6
6
  "name": "广州市风荷科技有限公司"
@@ -78,9 +78,9 @@
78
78
  "ajv-formats": "3.0.1",
79
79
  "libnpmpublish": "11.2.0",
80
80
  "npm-registry-fetch": "18.0.2",
81
- "skill-family-contracts": "0.16.0",
82
- "skill-family-engineering-kit": "0.16.0",
83
- "skill-family-harness-node": "0.16.0",
81
+ "skill-family-contracts": "0.17.0",
82
+ "skill-family-engineering-kit": "0.17.0",
83
+ "skill-family-harness-node": "0.17.0",
84
84
  "yaml": "2.9.0"
85
85
  },
86
86
  "devDependencies": {
@@ -2,11 +2,11 @@
2
2
  "schemaVersion": "1.0.0",
3
3
  "platformId": "release-skill",
4
4
  "platformName": "release-skill Plugin Platform",
5
- "version": "0.9.10",
5
+ "version": "0.9.12",
6
6
  "logicalRoot": "skills-src/",
7
7
  "logicalRootDigest": "aec96745b30289ed48d576fbfc98ecd3aacb834f83394be76d4734d7d0fc0ad4",
8
8
  "projectionDigest": "e5117d89c9f965e15a1464cc84f542cb1e75a685f842183d0cfc7a105ace5ceb",
9
- "projectionId": "release-skill:platform:0.9.10",
9
+ "projectionId": "release-skill:platform:0.9.12",
10
10
  "manifestRequired": true,
11
11
  "projectionSources": {
12
12
  "skills-src-root": {
@@ -42,7 +42,7 @@ releaseUnits: # 发布单元数组,至少 1 个
42
42
  version:
43
43
  source: <string> # 版本来源,如 "package.json"
44
44
  tagTemplate: <string> # tag 模板,如 "artifact-graph-v{version}"
45
- distributions: # 分发渠道数组
45
+ distributions: # 必填;分发渠道数组,也可显式设为 []
46
46
  - type: <string> # "npm" | "claude-plugin" | "codex-plugin"
47
47
  package: <string> # npm 包名(type 为 npm 时必填)
48
48
  # 插件渠道(claude-plugin/codex-plugin/kimi-plugin/codebuddy-plugin)
@@ -112,6 +112,12 @@ policy: # 可选,安全策略
112
112
  forbiddenContentPatterns: [<string>]
113
113
  ```
114
114
 
115
+ `distributions: []` 表示该发布单元不生成 npm 或插件市场动作,不表示跳过公开发布。
116
+ 生产模式的 `prepare` 仍会冻结 GitHub 快照,并生成 `push-snapshot`、`create-tag` 和
117
+ `github-release`。该单元的 `postPublish` 声明保持独立生效,其中 phase 为
118
+ `postVerify` 的 hook 也不受影响。因此纯插件可以先验证公开 GitHub Release,再向中央
119
+ Hub 投递登记提案。
120
+
115
121
  `publicSourceAuthorityReceipt` 只适用于 production prepare。`coordinatorUnitId`
116
122
  指定承载该文件的 GitHub Release;`subjectUnitIds` 必须引用现有 release unit,且每个
117
123
  unit 恰好声明一个 npm distribution。prepare 等所有 subject tarball 冻结后生成
@@ -155,7 +155,7 @@
155
155
  },
156
156
  "distributions": {
157
157
  "type": "array",
158
- "minItems": 1,
158
+ "description": "Distribution-specific publish and consumer checks. An explicit empty array publishes only the release unit's GitHub snapshot, tag, and GitHub Release; postPublish declarations, including postVerify-phase hooks, remain independent.",
159
159
  "items": {
160
160
  "type": "object",
161
161
  "required": [
@@ -64,6 +64,7 @@ import {
64
64
  isWorktreeFileClean,
65
65
  commitBaselineAdvance,
66
66
  } from '../core/baseline-advance.mjs';
67
+ import { updatePreviousPublicBaselines } from '../core/release-metadata.mjs';
67
68
  import { loadProjectConfig } from '../core/config.mjs';
68
69
  import { assertTransition, PUBLISHED, VERIFIED } from '../core/state-machine.mjs';
69
70
  import { clearFrozenMarker } from '../core/frozen-marker.mjs';
@@ -2330,17 +2331,41 @@ export async function verifyRelease(options) {
2330
2331
  // =======================================================================
2331
2332
  let baselineAdvance = null;
2332
2333
  const baselineAdvances = deriveBaselineAdvances(plan);
2333
- if (baselineAdvances.length > 0) {
2334
+ const hasFirstReleaseBaseline = (plan.units ?? [])
2335
+ .some((unit) => unit?.previousPublicBaseline?.mode === 'none');
2336
+ if (hasFirstReleaseBaseline || baselineAdvances.length > 0) {
2334
2337
  try {
2335
2338
  const configAbs = configPathOpt
2336
2339
  ? (isAbsolute(configPathOpt) ? configPathOpt : join(root, configPathOpt))
2337
2340
  : join(root, '.release-skill/project.yaml');
2338
2341
  const cleanBefore = await isWorktreeFileClean({ root, filePath: configAbs, execFn });
2339
- const applyResult = await applyBaselineAdvances({
2340
- configPath: configAbs,
2341
- advances: baselineAdvances,
2342
- validateFn: async () => { await loadProjectConfig({ root, configPath: configAbs }); },
2343
- });
2342
+ // A first-release plan must convert mode=none to a fully bound
2343
+ // baseline. The metadata updater processes the whole plan, so mixed
2344
+ // none+bound plans take this branch exclusively and do not omit or
2345
+ // double-write their already-bound units. Pure bound plans retain
2346
+ // the existing derive/apply path unchanged.
2347
+ let applyResult;
2348
+ if (hasFirstReleaseBaseline) {
2349
+ await loadProjectConfig({ root, configPath: configAbs });
2350
+ const metadataResult = await updatePreviousPublicBaselines({
2351
+ root,
2352
+ planPath,
2353
+ configPath: configAbs,
2354
+ validateFn: async () => { await loadProjectConfig({ root, configPath: configAbs }); },
2355
+ });
2356
+ applyResult = {
2357
+ changed: metadataResult.status === 'UPDATED',
2358
+ updatedUnits: metadataResult.status === 'UPDATED'
2359
+ ? metadataResult.units.map((unit) => unit.id)
2360
+ : [],
2361
+ };
2362
+ } else {
2363
+ applyResult = await applyBaselineAdvances({
2364
+ configPath: configAbs,
2365
+ advances: baselineAdvances,
2366
+ validateFn: async () => { await loadProjectConfig({ root, configPath: configAbs }); },
2367
+ });
2368
+ }
2344
2369
  baselineAdvance = { ...applyResult, committed: false };
2345
2370
  if (applyResult.changed && cleanBefore) {
2346
2371
  const versionLabel = (plan.units ?? [])
@@ -2362,12 +2387,25 @@ export async function verifyRelease(options) {
2362
2387
  committed: baselineAdvance.committed,
2363
2388
  });
2364
2389
  } catch (err) {
2365
- baselineAdvance = { failed: true, error: err.message };
2390
+ const errorDetails = err instanceof ReleaseError
2391
+ ? err.toJSON().details
2392
+ : undefined;
2393
+ baselineAdvance = {
2394
+ failed: true,
2395
+ error: err.message,
2396
+ ...(errorDetails ? { details: errorDetails } : {}),
2397
+ };
2366
2398
  await evidence.append({
2367
2399
  phase: 'verify',
2368
- step: 'baseline-advance',
2369
2400
  status: 'failed',
2370
- error: { code: err.code, message: err.message },
2401
+ error: {
2402
+ code: err.code,
2403
+ message: err.message,
2404
+ },
2405
+ details: {
2406
+ step: 'baseline-advance',
2407
+ ...(errorDetails ? { errorDetails } : {}),
2408
+ },
2371
2409
  });
2372
2410
  }
2373
2411
  }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Pure proposal-document projection shared by runtime transports and frozen
3
+ * postVerify execution hooks. Keep this module free of package dependencies
4
+ * so a detached public-tag worktree can load it without node_modules.
5
+ *
6
+ * @module core/proposal-document
7
+ */
8
+
9
+ import { ReleaseError, GATE_FAILED } from './errors.mjs';
10
+
11
+ /** Proposal document identity. */
12
+ export const PROPOSAL_SCHEMA_VERSION = 1;
13
+ export const PROPOSAL_KIND = 'release-skill/update-proposal';
14
+
15
+ /** Proposal file location inside the downstream repository. */
16
+ export function proposalFileName(unitId, version) {
17
+ if (typeof unitId !== 'string' || unitId.length === 0
18
+ || typeof version !== 'string' || version.length === 0) {
19
+ throw new ReleaseError(GATE_FAILED, 'proposal file name requires unitId and version', { unitId, version });
20
+ }
21
+ return `incoming/${unitId}-${version}.json`;
22
+ }
23
+
24
+ /**
25
+ * Build the deterministic downstream proposal from a post-publish context.
26
+ * Local materialization paths are intentionally excluded.
27
+ */
28
+ export function buildProposalDocument(context) {
29
+ const required = ['unitId', 'version', 'tag', 'commit', 'planDigest', 'runId'];
30
+ for (const field of required) {
31
+ if (typeof context?.[field] !== 'string' || context[field].length === 0) {
32
+ throw new ReleaseError(GATE_FAILED, `proposal document requires context field "${field}"`, { field });
33
+ }
34
+ }
35
+ const { unitId, version, tag, commit, tree, manifestDigest, planDigest, runId, publishedAt, verifyEvidence } = context;
36
+ return {
37
+ schemaVersion: PROPOSAL_SCHEMA_VERSION,
38
+ kind: PROPOSAL_KIND,
39
+ unitId,
40
+ version,
41
+ tag,
42
+ sha: commit,
43
+ commit,
44
+ ...(typeof tree === 'string' && tree.length > 0 ? { tree } : {}),
45
+ ...(typeof manifestDigest === 'string' && manifestDigest.length > 0 ? { manifestDigest } : {}),
46
+ planDigest,
47
+ runId,
48
+ ...(publishedAt !== undefined ? { publishedAt } : {}),
49
+ ...(verifyEvidence !== undefined ? { verifyEvidence } : {}),
50
+ changeSummary: `release ${unitId} ${version} (tag ${tag}, commit ${commit})`,
51
+ };
52
+ }
@@ -53,12 +53,16 @@ import {
53
53
  describeGitRemoteUrlFailure,
54
54
  redactUrlCredentialsIfPresent,
55
55
  } from './git-url-policy.mjs';
56
+ import { buildProposalDocument, proposalFileName } from './proposal-document.mjs';
56
57
 
57
- const execFileAsync = promisify(execFileCb);
58
+ export {
59
+ PROPOSAL_SCHEMA_VERSION,
60
+ PROPOSAL_KIND,
61
+ buildProposalDocument,
62
+ proposalFileName,
63
+ } from './proposal-document.mjs';
58
64
 
59
- /** Proposal document identity. */
60
- export const PROPOSAL_SCHEMA_VERSION = 1;
61
- export const PROPOSAL_KIND = 'release-skill/update-proposal';
65
+ const execFileAsync = promisify(execFileCb);
62
66
 
63
67
  const PROBE_TIMEOUT_MS = 30_000;
64
68
  const GIT_TIMEOUT_MS = 120_000;
@@ -187,54 +191,6 @@ function parseLsRemote(stdout) {
187
191
  // Proposal document (deterministic projection of the §2.3 context)
188
192
  // ---------------------------------------------------------------------------
189
193
 
190
- /** Proposal file location inside the downstream repository. */
191
- export function proposalFileName(unitId, version) {
192
- if (typeof unitId !== 'string' || unitId.length === 0
193
- || typeof version !== 'string' || version.length === 0) {
194
- throw new ReleaseError(GATE_FAILED, 'proposal file name requires unitId and version', { unitId, version });
195
- }
196
- return `incoming/${unitId}-${version}.json`;
197
- }
198
-
199
- /**
200
- * Build the proposal document from the §2.3 context projection. Pure and
201
- * deterministic. Deliberately EXCLUDES payloadDir (a local materialization
202
- * path has no place in a downstream-facing document), and optional frozen
203
- * fields are ABSENT rather than null when the plan did not freeze them.
204
- *
205
- * Field set suffices to backfill a missing downstream hub entry:
206
- * unitId/version/tag/sha/commit/tree/manifestDigest/planDigest/runId/
207
- * publishedAt/verifyEvidence/changeSummary.
208
- *
209
- * @param {object} context - The §2.3 context projection.
210
- * @returns {object} The proposal document.
211
- */
212
- export function buildProposalDocument(context) {
213
- const required = ['unitId', 'version', 'tag', 'commit', 'planDigest', 'runId'];
214
- for (const field of required) {
215
- if (typeof context?.[field] !== 'string' || context[field].length === 0) {
216
- throw new ReleaseError(GATE_FAILED, `proposal document requires context field "${field}"`, { field });
217
- }
218
- }
219
- const { unitId, version, tag, commit, tree, manifestDigest, planDigest, runId, publishedAt, verifyEvidence } = context;
220
- return {
221
- schemaVersion: PROPOSAL_SCHEMA_VERSION,
222
- kind: PROPOSAL_KIND,
223
- unitId,
224
- version,
225
- tag,
226
- sha: commit,
227
- commit,
228
- ...(typeof tree === 'string' && tree.length > 0 ? { tree } : {}),
229
- ...(typeof manifestDigest === 'string' && manifestDigest.length > 0 ? { manifestDigest } : {}),
230
- planDigest,
231
- runId,
232
- ...(publishedAt !== undefined ? { publishedAt } : {}),
233
- ...(verifyEvidence !== undefined ? { verifyEvidence } : {}),
234
- changeSummary: `release ${unitId} ${version} (tag ${tag}, commit ${commit})`,
235
- };
236
- }
237
-
238
194
  /**
239
195
  * Deterministic manual-sync prompt (closure semantics): delivery success plus
240
196
  * this prompt in evidence IS the closed loop — downstream consumption is the
@@ -1,5 +1,5 @@
1
1
  import { lstat, readFile } from 'node:fs/promises';
2
- import { resolve } from 'node:path';
2
+ import { isAbsolute, relative, resolve, sep } from 'node:path';
3
3
  import YAML from 'yaml';
4
4
  import { replaceFileAtomic } from 'skill-family-harness-node';
5
5
 
@@ -20,7 +20,20 @@ function assertOid(value, label) {
20
20
  export async function updatePreviousPublicBaselines(options = {}) {
21
21
  const root = resolve(options.root ?? process.cwd());
22
22
  const planPath = resolve(options.planPath ?? '');
23
- const configPath = resolve(root, '.release-skill', 'project.yaml');
23
+ const configPath = isAbsolute(options.configPath ?? '')
24
+ ? resolve(options.configPath)
25
+ : resolve(root, options.configPath ?? '.release-skill/project.yaml');
26
+ const configRelativePath = relative(root, configPath);
27
+ if (
28
+ configRelativePath === '..'
29
+ || configRelativePath.startsWith(`..${sep}`)
30
+ || isAbsolute(configRelativePath)
31
+ ) {
32
+ throw new ReleaseError(CONFIG_INVALID, 'project config must stay within the project root', {
33
+ root,
34
+ configPath,
35
+ });
36
+ }
24
37
  let plan;
25
38
  let configRaw;
26
39
  let configStat;
@@ -69,6 +82,7 @@ export async function updatePreviousPublicBaselines(options = {}) {
69
82
  const old = configUnit.get('previousPublicBaseline', true)?.toJSON?.() ?? {};
70
83
  const next = {
71
84
  mode: 'bound',
85
+ ...(typeof old.githubHost === 'string' ? { githubHost: old.githubHost } : {}),
72
86
  repo: old.repo ?? unit.publicRepo,
73
87
  ref: old.ref ?? `refs/heads/${frozen.branch}`,
74
88
  commit: frozen.commit,
@@ -78,6 +92,15 @@ export async function updatePreviousPublicBaselines(options = {}) {
78
92
  if (!next.repo || !next.ref) {
79
93
  throw new ReleaseError(GATE_FAILED, `unit "${unit.id}" cannot derive its next public baseline identity`);
80
94
  }
95
+ const changed = [
96
+ 'mode',
97
+ 'repo',
98
+ 'ref',
99
+ 'commit',
100
+ 'tree',
101
+ 'manifestDigest',
102
+ ].some((field) => old[field] !== next[field]);
103
+ if (!changed) continue;
81
104
  configUnit.set('previousPublicBaseline', next);
82
105
  updates.push({
83
106
  id: unit.id,
@@ -86,16 +109,41 @@ export async function updatePreviousPublicBaselines(options = {}) {
86
109
  });
87
110
  }
88
111
 
112
+ if (updates.length === 0) {
113
+ return { status: 'UNCHANGED', configPath, units: [] };
114
+ }
89
115
  const nextRaw = doc.toString();
90
116
  if (nextRaw === configRaw) {
91
117
  return { status: 'UNCHANGED', configPath, units: updates };
92
118
  }
93
119
  try {
94
- await replaceFileAtomic(root, '.release-skill/project.yaml', nextRaw, {
120
+ await replaceFileAtomic(root, configRelativePath, nextRaw, {
95
121
  mode: configStat.mode & 0o777,
96
122
  });
97
123
  } catch (error) {
98
124
  throw new ReleaseError(CONFIG_INVALID, `cannot update project release metadata: ${error.message}`, error.details);
99
125
  }
126
+ if (typeof options.validateFn === 'function') {
127
+ try {
128
+ await options.validateFn(configPath);
129
+ } catch (error) {
130
+ try {
131
+ await replaceFileAtomic(root, configRelativePath, configRaw, {
132
+ mode: configStat.mode & 0o777,
133
+ });
134
+ } catch (restoreError) {
135
+ throw new ReleaseError(
136
+ CONFIG_INVALID,
137
+ `cannot restore project release metadata after validation failed: ${restoreError.message}`,
138
+ { validationError: error.message, restoreError: restoreError.details },
139
+ );
140
+ }
141
+ throw new ReleaseError(
142
+ GATE_FAILED,
143
+ `baseline advance reverted: rewritten config failed validation: ${error.message}`,
144
+ { configPath, cause: error.message },
145
+ );
146
+ }
147
+ }
100
148
  return { status: 'UPDATED', configPath, units: updates };
101
149
  }
@@ -55,9 +55,9 @@ import { digestBytes, publishFileOrReplace, readFileStrict, withTemporaryWorkspa
55
55
  import { canonicalJson } from '../core/digest.mjs';
56
56
 
57
57
  export const FOUNDATION_PACKAGES = Object.freeze({
58
- 'skill-family-contracts': '0.16.0',
59
- 'skill-family-engineering-kit': '0.16.0',
60
- 'skill-family-harness-node': '0.16.0',
58
+ 'skill-family-contracts': '0.17.0',
59
+ 'skill-family-engineering-kit': '0.17.0',
60
+ 'skill-family-harness-node': '0.17.0',
61
61
  });
62
62
 
63
63
  export const BINDING_RECORD_PATH = 'bin/foundation-resource-binding.json';
@@ -0,0 +1,77 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://contracts.skill-family.example/v1/engineering-baseline.json",
4
+ "title": "EngineeringBaseline",
5
+ "description": "Business-neutral identity for an engineering baseline. The contract carries the provider-owned baseline identity, canonical rule lineage references, and the Foundation reference implementation identity; it does not carry rule bodies, effects, equivalence, or exemption semantics.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": [
9
+ "schemaVersion",
10
+ "kind",
11
+ "provider",
12
+ "id",
13
+ "version",
14
+ "digest",
15
+ "canonicalRuleRefs",
16
+ "referenceImplementation"
17
+ ],
18
+ "properties": {
19
+ "schemaVersion": { "const": 1 },
20
+ "kind": { "const": "skill-family.engineering-baseline" },
21
+ "provider": {
22
+ "type": "string",
23
+ "minLength": 1,
24
+ "description": "Provider that owns the baseline identity, for example an independent Audit implementation."
25
+ },
26
+ "id": {
27
+ "type": "string",
28
+ "minLength": 1,
29
+ "description": "Provider-scoped stable baseline identifier."
30
+ },
31
+ "version": {
32
+ "type": "string",
33
+ "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$",
34
+ "description": "Semantic version of the provider-owned baseline identity."
35
+ },
36
+ "digest": {
37
+ "type": "string",
38
+ "pattern": "^[0-9a-f]{64}$",
39
+ "description": "SHA-256 digest of the canonical JSON of this document with digest omitted."
40
+ },
41
+ "canonicalRuleRefs": {
42
+ "type": "array",
43
+ "minItems": 1,
44
+ "items": { "$ref": "#/$defs/canonicalRuleRef" },
45
+ "description": "Sorted, unique references to provider-owned canonical rule lineages. Rule bodies and decisions remain outside this contract."
46
+ },
47
+ "referenceImplementation": {
48
+ "$ref": "#/$defs/referenceImplementation",
49
+ "description": "Identity of the Foundation reference implementation used by the baseline provider."
50
+ }
51
+ },
52
+ "$defs": {
53
+ "canonicalRuleRef": {
54
+ "type": "object",
55
+ "additionalProperties": false,
56
+ "required": ["id", "revision", "revisionDigest"],
57
+ "properties": {
58
+ "id": { "type": "string", "minLength": 1 },
59
+ "revision": { "type": "integer", "minimum": 1 },
60
+ "revisionDigest": { "type": "string", "pattern": "^[0-9a-f]{64}$" }
61
+ }
62
+ },
63
+ "referenceImplementation": {
64
+ "type": "object",
65
+ "additionalProperties": false,
66
+ "required": ["foundationVersion", "profile", "skeletonDigest"],
67
+ "properties": {
68
+ "foundationVersion": {
69
+ "type": "string",
70
+ "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$"
71
+ },
72
+ "profile": { "type": "string", "minLength": 1 },
73
+ "skeletonDigest": { "type": "string", "pattern": "^[0-9a-f]{64}$" }
74
+ }
75
+ }
76
+ }
77
+ }
@@ -25,7 +25,7 @@
25
25
  "additionalProperties": false,
26
26
  "required": ["driverId", "authStrategy", "credentialMutation"],
27
27
  "properties": {
28
- "driverId": { "enum": ["kimi-code-print-v1", "workbuddy-codebuddy-print-v1", "claude-code-print-v1", "codex-exec-v1", "qodercli-print-v1"] },
28
+ "driverId": { "enum": ["kimi-code-print-v1", "workbuddy-codebuddy-print-v1", "codebuddy-print-v1", "claude-code-print-v1", "codex-exec-v1", "qodercli-print-v1"] },
29
29
  "authStrategy": { "const": "existing-user-state" },
30
30
  "credentialMutation": { "const": "host-managed" }
31
31
  }
@@ -80,6 +80,13 @@
80
80
  "properties": { "verification": { "type": "object", "properties": { "driverId": { "const": "workbuddy-codebuddy-print-v1" } } } }
81
81
  }
82
82
  },
83
+ {
84
+ "if": { "properties": { "hostId": { "const": "codebuddy" } }, "required": ["hostId"] },
85
+ "then": {
86
+ "required": ["verification"],
87
+ "properties": { "verification": { "type": "object", "properties": { "driverId": { "const": "codebuddy-print-v1" } } } }
88
+ }
89
+ },
83
90
  {
84
91
  "if": { "properties": { "hostId": { "const": "claude" } }, "required": ["hostId"] },
85
92
  "then": {
@@ -102,7 +109,7 @@
102
109
  }
103
110
  },
104
111
  {
105
- "if": { "properties": { "hostId": { "enum": ["kimi-code", "workbuddy", "claude", "codex", "qoder"] } }, "required": ["hostId"] },
112
+ "if": { "properties": { "hostId": { "enum": ["kimi-code", "workbuddy", "codebuddy", "claude", "codex", "qoder"] } }, "required": ["hostId"] },
106
113
  "else": { "properties": { "verification": false } }
107
114
  },
108
115
  {