human-to-code 0.0.1 → 0.1.2

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 (134) hide show
  1. package/Readme.md +368 -38
  2. package/SECURITY.md +175 -11
  3. package/dist/adapters/general.d.ts +30 -0
  4. package/dist/adapters/general.d.ts.map +1 -0
  5. package/dist/adapters/general.js +77 -0
  6. package/dist/adapters/general.js.map +1 -0
  7. package/dist/adapters/node.d.ts +8 -0
  8. package/dist/adapters/node.d.ts.map +1 -0
  9. package/dist/adapters/node.js +1071 -0
  10. package/dist/adapters/node.js.map +1 -0
  11. package/dist/adapters/python.d.ts +7 -0
  12. package/dist/adapters/python.d.ts.map +1 -0
  13. package/dist/adapters/python.js +540 -0
  14. package/dist/adapters/python.js.map +1 -0
  15. package/dist/adapters/rust.d.ts +7 -0
  16. package/dist/adapters/rust.d.ts.map +1 -0
  17. package/dist/adapters/rust.js +517 -0
  18. package/dist/adapters/rust.js.map +1 -0
  19. package/dist/analyzer-types.d.ts +161 -0
  20. package/dist/analyzer-types.d.ts.map +1 -0
  21. package/dist/analyzer-types.js +8 -0
  22. package/dist/analyzer-types.js.map +1 -0
  23. package/dist/analyzer-utils.d.ts +40 -0
  24. package/dist/analyzer-utils.d.ts.map +1 -0
  25. package/dist/analyzer-utils.js +591 -0
  26. package/dist/analyzer-utils.js.map +1 -0
  27. package/dist/analyzer.d.ts +21 -0
  28. package/dist/analyzer.d.ts.map +1 -0
  29. package/dist/analyzer.js +111 -0
  30. package/dist/analyzer.js.map +1 -0
  31. package/dist/certification.d.ts +102 -0
  32. package/dist/certification.d.ts.map +1 -0
  33. package/dist/certification.js +193 -0
  34. package/dist/certification.js.map +1 -0
  35. package/dist/cli.d.ts +4 -0
  36. package/dist/cli.d.ts.map +1 -0
  37. package/dist/cli.js +566 -0
  38. package/dist/cli.js.map +1 -0
  39. package/dist/compiler-skills.d.ts +25 -0
  40. package/dist/compiler-skills.d.ts.map +1 -0
  41. package/dist/compiler-skills.js +158 -0
  42. package/dist/compiler-skills.js.map +1 -0
  43. package/dist/compiler-tools.d.ts +29 -0
  44. package/dist/compiler-tools.d.ts.map +1 -0
  45. package/dist/compiler-tools.js +337 -0
  46. package/dist/compiler-tools.js.map +1 -0
  47. package/dist/config.d.ts +122 -0
  48. package/dist/config.d.ts.map +1 -0
  49. package/dist/config.js +704 -0
  50. package/dist/config.js.map +1 -0
  51. package/dist/context.d.ts +153 -0
  52. package/dist/context.d.ts.map +1 -0
  53. package/dist/context.js +772 -0
  54. package/dist/context.js.map +1 -0
  55. package/dist/contracts.d.ts +196 -0
  56. package/dist/contracts.d.ts.map +1 -0
  57. package/dist/contracts.js +721 -0
  58. package/dist/contracts.js.map +1 -0
  59. package/dist/discovery.d.ts +34 -0
  60. package/dist/discovery.d.ts.map +1 -0
  61. package/dist/discovery.js +312 -0
  62. package/dist/discovery.js.map +1 -0
  63. package/dist/documentation.d.ts +44 -0
  64. package/dist/documentation.d.ts.map +1 -0
  65. package/dist/documentation.js +418 -0
  66. package/dist/documentation.js.map +1 -0
  67. package/dist/index.d.ts +23 -0
  68. package/dist/index.d.ts.map +1 -0
  69. package/dist/index.js +21 -0
  70. package/dist/index.js.map +1 -0
  71. package/dist/patch.d.ts +71 -0
  72. package/dist/patch.d.ts.map +1 -0
  73. package/dist/patch.js +394 -0
  74. package/dist/patch.js.map +1 -0
  75. package/dist/pinned-http.d.ts +15 -0
  76. package/dist/pinned-http.d.ts.map +1 -0
  77. package/dist/pinned-http.js +170 -0
  78. package/dist/pinned-http.js.map +1 -0
  79. package/dist/planner.d.ts +30 -0
  80. package/dist/planner.d.ts.map +1 -0
  81. package/dist/planner.js +228 -0
  82. package/dist/planner.js.map +1 -0
  83. package/dist/provider.d.ts +168 -0
  84. package/dist/provider.d.ts.map +1 -0
  85. package/dist/provider.js +512 -0
  86. package/dist/provider.js.map +1 -0
  87. package/dist/providers.d.ts +41 -0
  88. package/dist/providers.d.ts.map +1 -0
  89. package/dist/providers.js +954 -0
  90. package/dist/providers.js.map +1 -0
  91. package/dist/run-store.d.ts +24 -0
  92. package/dist/run-store.d.ts.map +1 -0
  93. package/dist/run-store.js +285 -0
  94. package/dist/run-store.js.map +1 -0
  95. package/dist/schemas.d.ts +18 -0
  96. package/dist/schemas.d.ts.map +1 -0
  97. package/dist/schemas.js +228 -0
  98. package/dist/schemas.js.map +1 -0
  99. package/dist/secret-scan.d.ts +30 -0
  100. package/dist/secret-scan.d.ts.map +1 -0
  101. package/dist/secret-scan.js +122 -0
  102. package/dist/secret-scan.js.map +1 -0
  103. package/dist/snapshot.d.ts +33 -0
  104. package/dist/snapshot.d.ts.map +1 -0
  105. package/dist/snapshot.js +136 -0
  106. package/dist/snapshot.js.map +1 -0
  107. package/dist/support-matrix.d.ts +23 -0
  108. package/dist/support-matrix.d.ts.map +1 -0
  109. package/dist/support-matrix.js +199 -0
  110. package/dist/support-matrix.js.map +1 -0
  111. package/dist/types.d.ts +63 -0
  112. package/dist/types.d.ts.map +1 -0
  113. package/dist/types.js +8 -0
  114. package/dist/types.js.map +1 -0
  115. package/dist/validation.d.ts +25 -0
  116. package/dist/validation.d.ts.map +1 -0
  117. package/dist/validation.js +388 -0
  118. package/dist/validation.js.map +1 -0
  119. package/dist/workflow.d.ts +65 -0
  120. package/dist/workflow.d.ts.map +1 -0
  121. package/dist/workflow.js +1636 -0
  122. package/dist/workflow.js.map +1 -0
  123. package/package.json +36 -9
  124. package/.claude/settings.local.json +0 -12
  125. package/.github/workflows/ci.yml +0 -18
  126. package/CONTRIBUTING.md +0 -40
  127. package/src/cli.ts +0 -218
  128. package/src/config.ts +0 -186
  129. package/src/discovery.ts +0 -186
  130. package/src/index.ts +0 -22
  131. package/src/types.ts +0 -74
  132. package/test/config.test.ts +0 -97
  133. package/test/discovery.test.ts +0 -91
  134. package/tsconfig.json +0 -17
@@ -0,0 +1,721 @@
1
+ /**
2
+ * Versioned, provider-neutral artifacts for the Human-to-Code pipeline.
3
+ *
4
+ * This module deliberately has no dependency on an LLM SDK. Artifacts are
5
+ * plain JSON values, are validated with an exact-key policy, and are hashed
6
+ * using a canonical representation so they can be persisted and audited.
7
+ */
8
+ import { createHash } from "node:crypto";
9
+ import { posix } from "node:path";
10
+ export const ARTIFACT_SCHEMA_VERSION = 1;
11
+ /** Canonical JSON (RFC 8785-style key ordering, with stricter JSON inputs). */
12
+ export function canonicalJson(value) {
13
+ const active = new Set();
14
+ const visit = (input, path) => {
15
+ if (input === null)
16
+ return "null";
17
+ if (typeof input === "string" || typeof input === "boolean") {
18
+ return JSON.stringify(input);
19
+ }
20
+ if (typeof input === "number") {
21
+ if (!Number.isFinite(input)) {
22
+ throw new TypeError(`${path} contains a non-finite number.`);
23
+ }
24
+ return Object.is(input, -0) ? "0" : JSON.stringify(input);
25
+ }
26
+ if (typeof input !== "object") {
27
+ throw new TypeError(`${path} is not a JSON value.`);
28
+ }
29
+ if (active.has(input)) {
30
+ throw new TypeError(`${path} contains a circular reference.`);
31
+ }
32
+ active.add(input);
33
+ try {
34
+ if (Array.isArray(input)) {
35
+ if (Reflect.ownKeys(input).some((key) => typeof key === "symbol")) {
36
+ throw new TypeError(`${path} has symbol-keyed array properties.`);
37
+ }
38
+ const values = [];
39
+ for (let index = 0; index < input.length; index += 1) {
40
+ if (!Object.hasOwn(input, index)) {
41
+ throw new TypeError(`${path}[${index}] is a sparse array entry, not a JSON value.`);
42
+ }
43
+ values.push(visit(input[index], `${path}[${index}]`));
44
+ }
45
+ const extraKeys = Object.keys(input).filter((key) => !/^(?:0|[1-9][0-9]*)$/u.test(key));
46
+ if (extraKeys.length > 0) {
47
+ throw new TypeError(`${path} has non-index array properties.`);
48
+ }
49
+ return `[${values.join(",")}]`;
50
+ }
51
+ const prototype = Object.getPrototypeOf(input);
52
+ if (prototype !== Object.prototype && prototype !== null) {
53
+ throw new TypeError(`${path} must contain only plain JSON objects.`);
54
+ }
55
+ const record = input;
56
+ const ownKeys = Reflect.ownKeys(record);
57
+ if (ownKeys.some((key) => typeof key === "symbol")
58
+ || ownKeys.some((key) => typeof key === "string" && !Object.prototype.propertyIsEnumerable.call(record, key))) {
59
+ throw new TypeError(`${path} must not contain symbol or non-enumerable properties.`);
60
+ }
61
+ return `{${Object.keys(record)
62
+ .sort()
63
+ .map((key) => `${JSON.stringify(key)}:${visit(record[key], `${path}.${key}`)}`)
64
+ .join(",")}}`;
65
+ }
66
+ finally {
67
+ active.delete(input);
68
+ }
69
+ };
70
+ return visit(value, "$ ".trim());
71
+ }
72
+ export function sha256Text(text) {
73
+ return createHash("sha256").update(text, "utf8").digest("hex");
74
+ }
75
+ export function sha256Bytes(bytes) {
76
+ return createHash("sha256").update(bytes).digest("hex");
77
+ }
78
+ export function hashCanonical(value) {
79
+ return sha256Text(canonicalJson(value));
80
+ }
81
+ export class ArtifactValidationError extends Error {
82
+ issues;
83
+ constructor(issues) {
84
+ super(issues.map((issue) => `${issue.path}: ${issue.message}`).join("\n"));
85
+ this.name = "ArtifactValidationError";
86
+ this.issues = issues;
87
+ }
88
+ }
89
+ class Inspector {
90
+ issues = [];
91
+ issue(path, code, message) {
92
+ this.issues.push({ path, code, message });
93
+ }
94
+ record(value, path) {
95
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
96
+ this.issue(path, "TYPE", "must be an object.");
97
+ return undefined;
98
+ }
99
+ return value;
100
+ }
101
+ exact(record, path, required, optional = []) {
102
+ const allowed = new Set([...required, ...optional]);
103
+ for (const key of Object.keys(record)) {
104
+ if (!allowed.has(key))
105
+ this.issue(`${path}.${key}`, "UNKNOWN_KEY", "is not allowed.");
106
+ }
107
+ for (const key of required) {
108
+ if (!Object.hasOwn(record, key))
109
+ this.issue(`${path}.${key}`, "MISSING", "is required.");
110
+ }
111
+ }
112
+ string(value, path, options = {}) {
113
+ if (typeof value !== "string") {
114
+ this.issue(path, "TYPE", "must be a string.");
115
+ return false;
116
+ }
117
+ if (options.nonEmpty && value.trim().length === 0)
118
+ this.issue(path, "VALUE", "must not be empty.");
119
+ if (options.max !== undefined && value.length > options.max)
120
+ this.issue(path, "VALUE", `must be at most ${options.max} characters.`);
121
+ return true;
122
+ }
123
+ boolean(value, path) {
124
+ if (typeof value !== "boolean") {
125
+ this.issue(path, "TYPE", "must be a boolean.");
126
+ return false;
127
+ }
128
+ return true;
129
+ }
130
+ integer(value, path, min = 0, max = Number.MAX_SAFE_INTEGER) {
131
+ if (!Number.isSafeInteger(value) || value < min || value > max) {
132
+ this.issue(path, "VALUE", `must be an integer from ${min} to ${max}.`);
133
+ return false;
134
+ }
135
+ return true;
136
+ }
137
+ array(value, path) {
138
+ if (!Array.isArray(value)) {
139
+ this.issue(path, "TYPE", "must be an array.");
140
+ return undefined;
141
+ }
142
+ return value;
143
+ }
144
+ enum(value, path, values) {
145
+ if (typeof value !== "string" || !values.includes(value)) {
146
+ this.issue(path, "VALUE", `must be one of: ${values.join(", ")}.`);
147
+ return false;
148
+ }
149
+ return true;
150
+ }
151
+ finish() {
152
+ if (this.issues.length > 0)
153
+ throw new ArtifactValidationError(this.issues);
154
+ }
155
+ }
156
+ const SHA256 = /^[a-f0-9]{64}$/u;
157
+ const ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
158
+ const WORKSPACE_ID = /^[A-Za-z0-9][A-Za-z0-9._:/@-]{0,511}$/u;
159
+ const PATCH_KINDS = ["create", "edit", "delete", "rename"];
160
+ const RISK_CATEGORIES = [
161
+ "dependency_change",
162
+ "lockfile_change",
163
+ "database_migration",
164
+ "public_api_break",
165
+ "authentication_change",
166
+ "unsafe_rust",
167
+ "ffi",
168
+ "validation_config_change",
169
+ ];
170
+ function inspectSchemaVersion(i, record, path) {
171
+ if (record.schemaVersion !== ARTIFACT_SCHEMA_VERSION) {
172
+ i.issue(`${path}.schemaVersion`, "VALUE", "must equal 1.");
173
+ }
174
+ }
175
+ function inspectSha(i, value, path) {
176
+ if (!i.string(value, path) || !SHA256.test(value)) {
177
+ if (typeof value === "string")
178
+ i.issue(path, "VALUE", "must be a lowercase SHA-256 hex digest.");
179
+ }
180
+ }
181
+ /** Paths in contracts and patches are always root-relative POSIX paths. */
182
+ export function isSafeRelativePath(path, allowPattern = false) {
183
+ if (path.length === 0 || /[\u0000-\u001f\u007f\u202a-\u202e\u2066-\u2069]/u.test(path)
184
+ || path.includes("\\") || path.startsWith("/") || /^[A-Za-z]:/u.test(path))
185
+ return false;
186
+ const plain = allowPattern ? path.replaceAll("*", "x") : path;
187
+ const normalized = posix.normalize(plain);
188
+ return normalized !== ".." && !normalized.startsWith("../") && normalized !== "." && !plain.split("/").includes("..");
189
+ }
190
+ function inspectPath(i, value, path, allowPattern = false, allowRoot = false) {
191
+ if (i.string(value, path, { nonEmpty: true, max: 4096 }) && !(allowRoot && value === ".") && !isSafeRelativePath(value, allowPattern)) {
192
+ i.issue(path, "VALUE", "must be a confined, root-relative POSIX path.");
193
+ }
194
+ }
195
+ function inspectStringArray(i, value, path, options = {}) {
196
+ const result = [];
197
+ const array = i.array(value, path);
198
+ if (!array)
199
+ return result;
200
+ array.forEach((item, index) => {
201
+ const itemPath = `${path}[${index}]`;
202
+ if (options.pathItems)
203
+ inspectPath(i, item, itemPath, options.patternPaths);
204
+ else
205
+ i.string(item, itemPath, { nonEmpty: options.nonEmptyItems, max: 16_384 });
206
+ if (typeof item === "string")
207
+ result.push(item);
208
+ });
209
+ if (options.unique) {
210
+ const seen = new Set();
211
+ result.forEach((item, index) => {
212
+ if (seen.has(item))
213
+ i.issue(`${path}[${index}]`, "DUPLICATE", "must be unique.");
214
+ seen.add(item);
215
+ });
216
+ }
217
+ return result;
218
+ }
219
+ function inspectId(i, value, path) {
220
+ if (i.string(value, path, { nonEmpty: true, max: 128 }) && !ID.test(value)) {
221
+ i.issue(path, "VALUE", "contains unsupported characters.");
222
+ }
223
+ }
224
+ function inspectWorkspaceId(i, value, path) {
225
+ if (i.string(value, path, { nonEmpty: true, max: 512 })
226
+ && (!WORKSPACE_ID.test(value) || value.includes("\0") || value.split("/").includes(".."))) {
227
+ i.issue(path, "VALUE", "must be a bounded analyzer workspace identifier.");
228
+ }
229
+ }
230
+ function inspectTimestamp(i, value, path) {
231
+ if (i.string(value, path, { nonEmpty: true })
232
+ && (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?(?:Z|[+-]\d{2}:\d{2})$/u.test(value)
233
+ || !Number.isFinite(Date.parse(value)))) {
234
+ i.issue(path, "VALUE", "must be an ISO-8601 timestamp.");
235
+ }
236
+ }
237
+ function inspectFileDigest(i, value, path) {
238
+ const record = i.record(value, path);
239
+ if (!record)
240
+ return;
241
+ i.exact(record, path, ["path", "sha256"]);
242
+ inspectPath(i, record.path, `${path}.path`);
243
+ inspectSha(i, record.sha256, `${path}.sha256`);
244
+ }
245
+ export function validateChangeContractV1(value) {
246
+ const i = new Inspector();
247
+ const record = i.record(value, "$contract");
248
+ if (!record) {
249
+ i.finish();
250
+ throw new Error("unreachable");
251
+ }
252
+ i.exact(record, "$contract", [
253
+ "schemaVersion", "source", "projectFingerprint", "targetWorkspaces", "targetSymbols",
254
+ "requirements", "acceptanceCriteria", "scope", "prohibitedChanges", "risks",
255
+ "authorizedRisks", "unresolvedQuestions",
256
+ ]);
257
+ inspectSchemaVersion(i, record, "$contract");
258
+ inspectFileDigest(i, record.source, "$contract.source");
259
+ inspectSha(i, record.projectFingerprint, "$contract.projectFingerprint");
260
+ const targetWorkspaces = inspectStringArray(i, record.targetWorkspaces, "$contract.targetWorkspaces", { nonEmptyItems: true, unique: true });
261
+ targetWorkspaces.forEach((workspace, index) => inspectWorkspaceId(i, workspace, `$contract.targetWorkspaces[${index}]`));
262
+ if (targetWorkspaces.length === 0)
263
+ i.issue("$contract.targetWorkspaces", "VALUE", "must identify at least one workspace.");
264
+ inspectStringArray(i, record.targetSymbols, "$contract.targetSymbols", { nonEmptyItems: true, unique: true });
265
+ const requirements = i.array(record.requirements, "$contract.requirements");
266
+ const requirementIds = new Set();
267
+ requirements?.forEach((raw, index) => {
268
+ const path = `$contract.requirements[${index}]`;
269
+ const requirement = i.record(raw, path);
270
+ if (!requirement)
271
+ return;
272
+ i.exact(requirement, path, ["id", "description"]);
273
+ inspectId(i, requirement.id, `${path}.id`);
274
+ i.string(requirement.description, `${path}.description`, { nonEmpty: true, max: 16_384 });
275
+ if (typeof requirement.id === "string") {
276
+ if (requirementIds.has(requirement.id))
277
+ i.issue(`${path}.id`, "DUPLICATE", "must be unique.");
278
+ requirementIds.add(requirement.id);
279
+ }
280
+ });
281
+ if (requirements?.length === 0)
282
+ i.issue("$contract.requirements", "VALUE", "must contain at least one requirement.");
283
+ const acceptance = i.record(record.acceptanceCriteria, "$contract.acceptanceCriteria");
284
+ if (acceptance) {
285
+ i.exact(acceptance, "$contract.acceptanceCriteria", ["automated", "manual"]);
286
+ const automated = inspectStringArray(i, acceptance.automated, "$contract.acceptanceCriteria.automated", { nonEmptyItems: true, unique: true });
287
+ const manual = inspectStringArray(i, acceptance.manual, "$contract.acceptanceCriteria.manual", { nonEmptyItems: true, unique: true });
288
+ if (automated.length + manual.length === 0)
289
+ i.issue("$contract.acceptanceCriteria", "VALUE", "must contain at least one acceptance criterion.");
290
+ }
291
+ const scope = i.record(record.scope, "$contract.scope");
292
+ if (scope) {
293
+ i.exact(scope, "$contract.scope", ["allowedPaths", "allowedOperations", "prohibitedPaths"]);
294
+ const allowed = inspectStringArray(i, scope.allowedPaths, "$contract.scope.allowedPaths", { nonEmptyItems: true, unique: true, pathItems: true, patternPaths: true });
295
+ if (allowed.length === 0)
296
+ i.issue("$contract.scope.allowedPaths", "VALUE", "must contain at least one path.");
297
+ const operations = i.array(scope.allowedOperations, "$contract.scope.allowedOperations");
298
+ const seenOperations = new Set();
299
+ operations?.forEach((operation, index) => {
300
+ i.enum(operation, `$contract.scope.allowedOperations[${index}]`, PATCH_KINDS);
301
+ if (typeof operation === "string") {
302
+ if (seenOperations.has(operation))
303
+ i.issue(`$contract.scope.allowedOperations[${index}]`, "DUPLICATE", "must be unique.");
304
+ seenOperations.add(operation);
305
+ }
306
+ });
307
+ if (operations?.length === 0)
308
+ i.issue("$contract.scope.allowedOperations", "VALUE", "must contain at least one operation.");
309
+ inspectStringArray(i, scope.prohibitedPaths, "$contract.scope.prohibitedPaths", { nonEmptyItems: true, unique: true, pathItems: true, patternPaths: true });
310
+ }
311
+ inspectStringArray(i, record.prohibitedChanges, "$contract.prohibitedChanges", { nonEmptyItems: true, unique: true });
312
+ const authorized = i.array(record.authorizedRisks, "$contract.authorizedRisks");
313
+ const authorizedSet = new Set();
314
+ authorized?.forEach((risk, index) => {
315
+ i.enum(risk, `$contract.authorizedRisks[${index}]`, RISK_CATEGORIES);
316
+ if (typeof risk === "string") {
317
+ if (authorizedSet.has(risk))
318
+ i.issue(`$contract.authorizedRisks[${index}]`, "DUPLICATE", "must be unique.");
319
+ authorizedSet.add(risk);
320
+ }
321
+ });
322
+ const risks = i.array(record.risks, "$contract.risks");
323
+ const assessed = new Set();
324
+ risks?.forEach((raw, index) => {
325
+ const path = `$contract.risks[${index}]`;
326
+ const risk = i.record(raw, path);
327
+ if (!risk)
328
+ return;
329
+ i.exact(risk, path, ["category", "reason"]);
330
+ i.enum(risk.category, `${path}.category`, RISK_CATEGORIES);
331
+ i.string(risk.reason, `${path}.reason`, { nonEmpty: true, max: 4096 });
332
+ if (typeof risk.category === "string")
333
+ assessed.add(risk.category);
334
+ });
335
+ authorizedSet.forEach((risk) => {
336
+ if (!assessed.has(risk))
337
+ i.issue("$contract.authorizedRisks", "UNAUTHORIZED", `risk '${risk}' must have a matching assessment.`);
338
+ });
339
+ const questions = i.array(record.unresolvedQuestions, "$contract.unresolvedQuestions");
340
+ const questionIds = new Set();
341
+ questions?.forEach((raw, index) => {
342
+ const path = `$contract.unresolvedQuestions[${index}]`;
343
+ const question = i.record(raw, path);
344
+ if (!question)
345
+ return;
346
+ i.exact(question, path, ["id", "question", "material"]);
347
+ inspectId(i, question.id, `${path}.id`);
348
+ i.string(question.question, `${path}.question`, { nonEmpty: true, max: 8192 });
349
+ i.boolean(question.material, `${path}.material`);
350
+ if (question.material === true)
351
+ i.issue(path, "UNRESOLVED", "material questions must be resolved before generation.");
352
+ if (typeof question.id === "string") {
353
+ if (questionIds.has(question.id))
354
+ i.issue(`${path}.id`, "DUPLICATE", "must be unique.");
355
+ questionIds.add(question.id);
356
+ }
357
+ });
358
+ i.finish();
359
+ return value;
360
+ }
361
+ function globMatches(pattern, path) {
362
+ const escaped = pattern.replace(/[.+^${}()|[\]\\]/gu, "\\$&");
363
+ const expression = escaped.replaceAll("**", "\0").replaceAll("*", "[^/]*").replaceAll("\0", ".*");
364
+ return new RegExp(`^${expression}(?:/.*)?$`, "u").test(path);
365
+ }
366
+ function contractAllowsPath(contract, path) {
367
+ return contract.scope.allowedPaths.some((pattern) => globMatches(pattern, path))
368
+ && !contract.scope.prohibitedPaths.some((pattern) => globMatches(pattern, path));
369
+ }
370
+ const LOCKFILES = /(?:^|\/)(?:package-lock\.json|pnpm-lock\.yaml|yarn\.lock|bun\.lockb?|Cargo\.lock|poetry\.lock|Pipfile\.lock|uv\.lock)$/u;
371
+ const DEPENDENCY_MANIFESTS = /(?:^|\/)(?:package\.json|Cargo\.toml|pyproject\.toml|Pipfile|requirements(?:-[^/]*)?\.txt)$/u;
372
+ const MIGRATIONS = /(?:^|\/)(?:migrations?|alembic|prisma\/migrations)(?:\/|$)|\.sql$/u;
373
+ const VALIDATION_CONFIG = /(?:^|\/)(?:(?:jest|vitest|eslint|biome|playwright|tsconfig|mypy|pytest|ruff|clippy)(?:\.[^/]*)?(?:\.config)?\.[^/]+|package\.json|pyproject\.toml|tox\.ini|noxfile\.py|Makefile|Justfile|Jenkinsfile|azure-pipelines\.ya?ml|\.gitlab-ci\.ya?ml|\.github\/workflows\/[^/]+\.ya?ml)$/iu;
374
+ const AUTH_PATH = /(?:^|\/)(?:auth|authentication|authorization|security|guards?)(?:[._/-]|$)/iu;
375
+ const UNSAFE_RUST = /\bunsafe\b/u;
376
+ const FFI = /(?:extern\s+"C"|#\s*\[\s*(?:unsafe\s*\(\s*)?no_mangle|\b(?:std|core)::ffi\b|\blibc::|\bbindgen\b)/u;
377
+ /** Paths that no reviewed contract is allowed to expose or mutate. */
378
+ export function isHardProtectedPatchPath(path) {
379
+ const parts = path.replaceAll("\\", "/").split("/").filter(Boolean);
380
+ return parts.some((part) => [".git", ".hg", ".svn", ".human-to-code", "secrets.human", "human-to-code.config.json", ".npmrc", ".yarnrc", ".yarnrc.yml", ".pypirc", ".netrc", ".git-credentials", ".envrc"].includes(part.toLowerCase()))
381
+ || parts.some((part) => /^\.env(?:\..*)?$/iu.test(part)
382
+ || /\.(?:pem|key|p12|pfx|jks|keystore)$/iu.test(part)
383
+ || /^(?:id_rsa|id_dsa|id_ecdsa|id_ed25519)(?:\..*)?$/iu.test(part)
384
+ || /^(?:credentials?|secrets?)(?:\..*)?$/iu.test(part))
385
+ || parts.some((part, index) => part.toLowerCase() === ".cargo" && /^credentials(?:\.toml)?$/iu.test(parts[index + 1] ?? ""))
386
+ || parts.some((part, index) => part.toLowerCase() === ".docker" && (parts[index + 1] ?? "").toLowerCase() === "config.json");
387
+ }
388
+ /** Conservative, deterministic risk inference for patch authorization. */
389
+ export function inferPatchRisks(operation) {
390
+ const paths = operation.kind === "rename" ? [operation.from, operation.path] : [operation.path];
391
+ const added = operation.kind === "create" ? operation.content : operation.kind === "edit" ? operation.newText : "";
392
+ const risks = new Set();
393
+ if (paths.some((path) => LOCKFILES.test(path)))
394
+ risks.add("lockfile_change");
395
+ if (paths.some((path) => DEPENDENCY_MANIFESTS.test(path)))
396
+ risks.add("dependency_change");
397
+ if (paths.some((path) => MIGRATIONS.test(path)))
398
+ risks.add("database_migration");
399
+ if (paths.some((path) => VALIDATION_CONFIG.test(path)))
400
+ risks.add("validation_config_change");
401
+ if (paths.some((path) => AUTH_PATH.test(path)))
402
+ risks.add("authentication_change");
403
+ if (UNSAFE_RUST.test(added))
404
+ risks.add("unsafe_rust");
405
+ if (FFI.test(added))
406
+ risks.add("ffi");
407
+ if (operation.kind === "delete" || operation.kind === "rename")
408
+ risks.add("public_api_break");
409
+ if (operation.kind === "edit" && /\bexport\b/u.test(operation.oldText) && !/\bexport\b/u.test(operation.newText))
410
+ risks.add("public_api_break");
411
+ return [...risks].sort();
412
+ }
413
+ export function validatePatchSetV1(value, contract) {
414
+ const i = new Inspector();
415
+ const record = i.record(value, "$patch");
416
+ if (!record) {
417
+ i.finish();
418
+ throw new Error("unreachable");
419
+ }
420
+ i.exact(record, "$patch", ["schemaVersion", "contractHash", "snapshotHash", "operations", "requirementIds", "proposedTests"]);
421
+ inspectSchemaVersion(i, record, "$patch");
422
+ inspectSha(i, record.contractHash, "$patch.contractHash");
423
+ inspectSha(i, record.snapshotHash, "$patch.snapshotHash");
424
+ if (contract && record.contractHash !== hashCanonical(contract)) {
425
+ i.issue("$patch.contractHash", "VALUE", "does not match the canonical contract hash.");
426
+ }
427
+ const requirementIds = inspectStringArray(i, record.requirementIds, "$patch.requirementIds", { nonEmptyItems: true, unique: true });
428
+ if (requirementIds.length === 0)
429
+ i.issue("$patch.requirementIds", "VALUE", "must contain at least one requirement id.");
430
+ inspectStringArray(i, record.proposedTests, "$patch.proposedTests", { nonEmptyItems: true, unique: true });
431
+ if (contract) {
432
+ const known = new Set(contract.requirements.map((requirement) => requirement.id));
433
+ requirementIds.forEach((id, index) => {
434
+ if (!known.has(id))
435
+ i.issue(`$patch.requirementIds[${index}]`, "VALUE", "does not identify a contract requirement.");
436
+ });
437
+ known.forEach((id) => {
438
+ if (!requirementIds.includes(id))
439
+ i.issue("$patch.requirementIds", "MISSING", `does not cover requirement '${id}'.`);
440
+ });
441
+ }
442
+ const operations = i.array(record.operations, "$patch.operations");
443
+ if (operations && operations.length > 200)
444
+ i.issue("$patch.operations", "VALUE", "must contain at most 200 operations.");
445
+ const destinations = new Set();
446
+ operations?.forEach((raw, index) => {
447
+ const path = `$patch.operations[${index}]`;
448
+ const operation = i.record(raw, path);
449
+ if (!operation)
450
+ return;
451
+ if (!i.enum(operation.kind, `${path}.kind`, PATCH_KINDS))
452
+ return;
453
+ const required = operation.kind === "create"
454
+ ? ["kind", "path", "content"]
455
+ : operation.kind === "edit"
456
+ ? ["kind", "path", "baseHash", "oldText", "newText"]
457
+ : operation.kind === "delete"
458
+ ? ["kind", "path", "baseHash"]
459
+ : ["kind", "from", "path", "baseHash"];
460
+ i.exact(operation, path, required);
461
+ inspectPath(i, operation.path, `${path}.path`);
462
+ if (operation.kind === "rename")
463
+ inspectPath(i, operation.from, `${path}.from`);
464
+ if (operation.kind !== "create")
465
+ inspectSha(i, operation.baseHash, `${path}.baseHash`);
466
+ if (operation.kind === "create") {
467
+ i.string(operation.content, `${path}.content`, { max: 10_000_000 });
468
+ }
469
+ if (operation.kind === "edit") {
470
+ i.string(operation.oldText, `${path}.oldText`, { nonEmpty: true, max: 10_000_000 });
471
+ i.string(operation.newText, `${path}.newText`, { max: 10_000_000 });
472
+ if (operation.oldText === operation.newText)
473
+ i.issue(path, "VALUE", "edit must change text.");
474
+ }
475
+ if (typeof operation.path === "string") {
476
+ if (destinations.has(operation.path))
477
+ i.issue(`${path}.path`, "DUPLICATE", "overlaps another operation destination.");
478
+ destinations.add(operation.path);
479
+ }
480
+ if (contract && typeof operation.path === "string") {
481
+ if (!contract.scope.allowedOperations.includes(operation.kind)) {
482
+ i.issue(`${path}.kind`, "OUT_OF_SCOPE", "operation kind is not allowed by the contract.");
483
+ }
484
+ const affected = operation.kind === "rename" && typeof operation.from === "string"
485
+ ? [operation.from, operation.path]
486
+ : [operation.path];
487
+ affected.forEach((affectedPath) => {
488
+ if (isHardProtectedPatchPath(affectedPath))
489
+ i.issue(`${path}.path`, "UNAUTHORIZED", `'${affectedPath}' is a hard-protected credential or tool-state path.`);
490
+ if (!contractAllowsPath(contract, affectedPath))
491
+ i.issue(`${path}.path`, "OUT_OF_SCOPE", `'${affectedPath}' is outside the contract scope.`);
492
+ });
493
+ const inferred = inferPatchRisks(operation);
494
+ inferred.forEach((risk) => {
495
+ if (!contract.authorizedRisks.includes(risk)) {
496
+ i.issue(path, "UNAUTHORIZED", `operation requires explicit '${risk}' authorization.`);
497
+ }
498
+ });
499
+ }
500
+ if (!contract && typeof operation.path === "string") {
501
+ const affected = operation.kind === "rename" && typeof operation.from === "string" ? [operation.from, operation.path] : [operation.path];
502
+ affected.forEach((affectedPath) => {
503
+ if (isHardProtectedPatchPath(affectedPath))
504
+ i.issue(`${path}.path`, "UNAUTHORIZED", `'${affectedPath}' is a hard-protected credential or tool-state path.`);
505
+ });
506
+ }
507
+ });
508
+ if (operations?.length === 0)
509
+ i.issue("$patch.operations", "VALUE", "must contain at least one operation.");
510
+ i.finish();
511
+ return value;
512
+ }
513
+ export function validateValidationPlanV1(value) {
514
+ const i = new Inspector();
515
+ const record = i.record(value, "$validationPlan");
516
+ if (!record) {
517
+ i.finish();
518
+ throw new Error("unreachable");
519
+ }
520
+ i.exact(record, "$validationPlan", ["schemaVersion", "profileFingerprint", "commands", "manualChecks"]);
521
+ inspectSchemaVersion(i, record, "$validationPlan");
522
+ inspectSha(i, record.profileFingerprint, "$validationPlan.profileFingerprint");
523
+ inspectStringArray(i, record.manualChecks, "$validationPlan.manualChecks", { nonEmptyItems: true, unique: true });
524
+ const commands = i.array(record.commands, "$validationPlan.commands");
525
+ const ids = new Set();
526
+ commands?.forEach((raw, index) => {
527
+ const path = `$validationPlan.commands[${index}]`;
528
+ const command = i.record(raw, path);
529
+ if (!command)
530
+ return;
531
+ i.exact(command, path, ["id", "argv", "cwd", "timeoutMs", "required", "category"]);
532
+ inspectId(i, command.id, `${path}.id`);
533
+ const argv = inspectStringArray(i, command.argv, `${path}.argv`, { nonEmptyItems: true });
534
+ if (argv.length === 0)
535
+ i.issue(`${path}.argv`, "VALUE", "must contain an executable and arguments; shell strings are not accepted.");
536
+ inspectPath(i, command.cwd, `${path}.cwd`, false, true);
537
+ i.integer(command.timeoutMs, `${path}.timeoutMs`, 1, 3_600_000);
538
+ i.boolean(command.required, `${path}.required`);
539
+ i.enum(command.category, `${path}.category`, ["format", "lint", "typecheck", "test", "build", "integration", "security"]);
540
+ if (typeof command.id === "string") {
541
+ if (ids.has(command.id))
542
+ i.issue(`${path}.id`, "DUPLICATE", "must be unique.");
543
+ ids.add(command.id);
544
+ }
545
+ });
546
+ if (commands?.length === 0)
547
+ i.issue("$validationPlan.commands", "VALUE", "must contain at least one command.");
548
+ i.finish();
549
+ return value;
550
+ }
551
+ function inspectCommandResult(i, value, path) {
552
+ const result = i.record(value, path);
553
+ if (!result)
554
+ return;
555
+ i.exact(result, path, ["id", "status", "exitCode", "signal", "durationMs", "stdout", "stderr", "timedOut", "flaky", "outputTruncated", "startedAt", "finishedAt"]);
556
+ inspectId(i, result.id, `${path}.id`);
557
+ i.enum(result.status, `${path}.status`, ["passed", "failed", "skipped", "error"]);
558
+ if (result.exitCode !== null)
559
+ i.integer(result.exitCode, `${path}.exitCode`, 0, 255);
560
+ if (result.signal !== null)
561
+ i.string(result.signal, `${path}.signal`, { nonEmpty: true, max: 64 });
562
+ i.integer(result.durationMs, `${path}.durationMs`, 0, Number.MAX_SAFE_INTEGER);
563
+ i.string(result.stdout, `${path}.stdout`, { max: 10_000_000 });
564
+ i.string(result.stderr, `${path}.stderr`, { max: 10_000_000 });
565
+ i.boolean(result.timedOut, `${path}.timedOut`);
566
+ i.boolean(result.flaky, `${path}.flaky`);
567
+ i.boolean(result.outputTruncated, `${path}.outputTruncated`);
568
+ inspectTimestamp(i, result.startedAt, `${path}.startedAt`);
569
+ inspectTimestamp(i, result.finishedAt, `${path}.finishedAt`);
570
+ if (typeof result.startedAt === "string" && typeof result.finishedAt === "string"
571
+ && Date.parse(result.finishedAt) < Date.parse(result.startedAt)) {
572
+ i.issue(`${path}.finishedAt`, "VALUE", "must not precede startedAt.");
573
+ }
574
+ if (result.status === "passed" && (result.exitCode !== 0 || result.timedOut === true)) {
575
+ i.issue(`${path}.status`, "VALUE", "passed results require exitCode 0 and no timeout.");
576
+ }
577
+ }
578
+ export function validateValidationReportV1(value) {
579
+ const i = new Inspector();
580
+ const record = i.record(value, "$validationReport");
581
+ if (!record) {
582
+ i.finish();
583
+ throw new Error("unreachable");
584
+ }
585
+ i.exact(record, "$validationReport", ["schemaVersion", "status", "sandbox", "baseline", "candidate", "repairs", "manualChecks", "diagnostics", "startedAt", "finishedAt"]);
586
+ inspectSchemaVersion(i, record, "$validationReport");
587
+ i.enum(record.status, "$validationReport.status", ["validated", "non_regression_only", "unvalidated", "failed"]);
588
+ i.enum(record.sandbox, "$validationReport.sandbox", ["strong", "degraded", "none"]);
589
+ for (const field of ["baseline", "candidate"]) {
590
+ i.array(record[field], `$validationReport.${field}`)?.forEach((entry, index) => inspectCommandResult(i, entry, `$validationReport.${field}[${index}]`));
591
+ }
592
+ const repairAttempts = new Set();
593
+ const repairs = i.array(record.repairs, "$validationReport.repairs");
594
+ if (repairs && repairs.length > 2)
595
+ i.issue("$validationReport.repairs", "VALUE", "must contain at most two repair attempts.");
596
+ repairs?.forEach((raw, index) => {
597
+ const path = `$validationReport.repairs[${index}]`;
598
+ const repair = i.record(raw, path);
599
+ if (!repair)
600
+ return;
601
+ i.exact(repair, path, ["attempt", "diagnostics"], ["patchHash"]);
602
+ i.integer(repair.attempt, `${path}.attempt`, 1, 2);
603
+ if (typeof repair.attempt === "number") {
604
+ if (repairAttempts.has(repair.attempt))
605
+ i.issue(`${path}.attempt`, "DUPLICATE", "must be unique.");
606
+ repairAttempts.add(repair.attempt);
607
+ }
608
+ inspectStringArray(i, repair.diagnostics, `${path}.diagnostics`, { nonEmptyItems: true });
609
+ if (repair.patchHash !== undefined)
610
+ inspectSha(i, repair.patchHash, `${path}.patchHash`);
611
+ });
612
+ const manualChecks = i.array(record.manualChecks, "$validationReport.manualChecks");
613
+ manualChecks?.forEach((raw, index) => {
614
+ const path = `$validationReport.manualChecks[${index}]`;
615
+ const check = i.record(raw, path);
616
+ if (!check)
617
+ return;
618
+ i.exact(check, path, ["description", "status"], ["evidence"]);
619
+ i.string(check.description, `${path}.description`, { nonEmpty: true, max: 4096 });
620
+ i.enum(check.status, `${path}.status`, ["passed", "failed", "pending", "not_applicable"]);
621
+ if (check.evidence !== undefined)
622
+ i.string(check.evidence, `${path}.evidence`, { nonEmpty: true, max: 16_384 });
623
+ });
624
+ inspectStringArray(i, record.diagnostics, "$validationReport.diagnostics", { nonEmptyItems: true });
625
+ inspectTimestamp(i, record.startedAt, "$validationReport.startedAt");
626
+ inspectTimestamp(i, record.finishedAt, "$validationReport.finishedAt");
627
+ if (typeof record.startedAt === "string" && typeof record.finishedAt === "string" && Date.parse(record.finishedAt) < Date.parse(record.startedAt)) {
628
+ i.issue("$validationReport.finishedAt", "VALUE", "must not precede startedAt.");
629
+ }
630
+ if (record.status === "validated" && record.sandbox !== "strong") {
631
+ i.issue("$validationReport.status", "VALUE", "validated status requires a strong sandbox.");
632
+ }
633
+ if (record.sandbox === "none"
634
+ && ((Array.isArray(record.baseline) && record.baseline.length > 0)
635
+ || (Array.isArray(record.candidate) && record.candidate.length > 0))) {
636
+ i.issue("$validationReport.sandbox", "VALUE", "a report with no sandbox cannot contain executed command results.");
637
+ }
638
+ if (record.status === "validated") {
639
+ const results = [
640
+ ...(Array.isArray(record.baseline) ? record.baseline : []),
641
+ ...(Array.isArray(record.candidate) ? record.candidate : []),
642
+ ];
643
+ if (results.length === 0 || results.some((result) => result.status !== "passed" || result.flaky === true || result.outputTruncated === true || result.timedOut === true)) {
644
+ i.issue("$validationReport.status", "VALUE", "validated status requires non-flaky, non-truncated passing command results.");
645
+ }
646
+ if (Array.isArray(record.manualChecks) && record.manualChecks.some((check) => {
647
+ if (typeof check !== "object" || check === null || Array.isArray(check))
648
+ return true;
649
+ return !["passed", "not_applicable"].includes(String(check.status));
650
+ })) {
651
+ i.issue("$validationReport.status", "VALUE", "validated status requires every manual check to be resolved successfully.");
652
+ }
653
+ }
654
+ i.finish();
655
+ return value;
656
+ }
657
+ export function validateRunRecordV1(value) {
658
+ const i = new Inspector();
659
+ const record = i.record(value, "$run");
660
+ if (!record) {
661
+ i.finish();
662
+ throw new Error("unreachable");
663
+ }
664
+ i.exact(record, "$run", ["runId", "schemaVersion", "createdAt", "updatedAt", "root", "status", "diagnostics"], [
665
+ "contractHash", "contextManifestHash", "patchHash", "validationReportHash", "provider", "usage",
666
+ ]);
667
+ inspectId(i, record.runId, "$run.runId");
668
+ inspectSchemaVersion(i, record, "$run");
669
+ inspectTimestamp(i, record.createdAt, "$run.createdAt");
670
+ inspectTimestamp(i, record.updatedAt, "$run.updatedAt");
671
+ i.string(record.root, "$run.root", { nonEmpty: true, max: 4096 });
672
+ i.enum(record.status, "$run.status", ["VERIFIED", "NEEDS_INPUT", "UNSUPPORTED", "INCONCLUSIVE", "FAILED", "SECURITY_BLOCKED"]);
673
+ inspectStringArray(i, record.diagnostics, "$run.diagnostics", { nonEmptyItems: true });
674
+ for (const key of ["contractHash", "contextManifestHash", "patchHash", "validationReportHash"]) {
675
+ if (record[key] !== undefined)
676
+ inspectSha(i, record[key], `$run.${key}`);
677
+ }
678
+ if (record.provider !== undefined) {
679
+ const provider = i.record(record.provider, "$run.provider");
680
+ if (provider) {
681
+ i.exact(provider, "$run.provider", ["name", "requestedModel", "resolvedModel", "requestIds"]);
682
+ i.string(provider.name, "$run.provider.name", { nonEmpty: true, max: 128 });
683
+ i.string(provider.requestedModel, "$run.provider.requestedModel", { nonEmpty: true, max: 256 });
684
+ i.string(provider.resolvedModel, "$run.provider.resolvedModel", { nonEmpty: true, max: 256 });
685
+ inspectStringArray(i, provider.requestIds, "$run.provider.requestIds", { nonEmptyItems: true, unique: true });
686
+ }
687
+ }
688
+ if (record.usage !== undefined) {
689
+ const usage = i.record(record.usage, "$run.usage");
690
+ if (usage) {
691
+ i.exact(usage, "$run.usage", ["inputTokens", "outputTokens", "totalTokens", "requests"], ["repairs", "costUsd"]);
692
+ i.integer(usage.inputTokens, "$run.usage.inputTokens");
693
+ i.integer(usage.outputTokens, "$run.usage.outputTokens");
694
+ i.integer(usage.totalTokens, "$run.usage.totalTokens");
695
+ i.integer(usage.requests, "$run.usage.requests");
696
+ if (usage.repairs !== undefined)
697
+ i.integer(usage.repairs, "$run.usage.repairs", 0, 2);
698
+ if (typeof usage.inputTokens === "number" && typeof usage.outputTokens === "number" && usage.totalTokens !== usage.inputTokens + usage.outputTokens) {
699
+ i.issue("$run.usage.totalTokens", "VALUE", "must equal inputTokens + outputTokens.");
700
+ }
701
+ if (usage.costUsd !== undefined && (typeof usage.costUsd !== "number" || !Number.isFinite(usage.costUsd) || usage.costUsd < 0)) {
702
+ i.issue("$run.usage.costUsd", "VALUE", "must be a non-negative finite number.");
703
+ }
704
+ }
705
+ }
706
+ if (typeof record.createdAt === "string" && typeof record.updatedAt === "string" && Date.parse(record.updatedAt) < Date.parse(record.createdAt)) {
707
+ i.issue("$run.updatedAt", "VALUE", "must not precede createdAt.");
708
+ }
709
+ if (record.status === "VERIFIED") {
710
+ for (const key of ["contractHash", "contextManifestHash", "patchHash", "validationReportHash"]) {
711
+ if (record[key] === undefined)
712
+ i.issue(`$run.${key}`, "MISSING", "is required for VERIFIED status.");
713
+ }
714
+ }
715
+ else if (Array.isArray(record.diagnostics) && record.diagnostics.length === 0) {
716
+ i.issue("$run.diagnostics", "MISSING", "must explain every non-verified terminal status.");
717
+ }
718
+ i.finish();
719
+ return value;
720
+ }
721
+ //# sourceMappingURL=contracts.js.map