@tiphys/kernel 0.0.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/AGENTS.md +663 -0
  2. package/LICENSE +202 -0
  3. package/assurance-modes.yaml +299 -0
  4. package/checklists/clean-room.yaml +325 -0
  5. package/checklists/env-failure-diagnosis.yaml +68 -0
  6. package/checklists/flake-playbook.yaml +68 -0
  7. package/checklists/hazard-review.yaml +144 -0
  8. package/checklists/plan-review.yaml +103 -0
  9. package/dist/bin/tiphys.d.ts +2 -0
  10. package/dist/bin/tiphys.js +92 -0
  11. package/dist/src/adapters/load.d.ts +202 -0
  12. package/dist/src/adapters/load.js +440 -0
  13. package/dist/src/brief.d.ts +32 -0
  14. package/dist/src/brief.js +66 -0
  15. package/dist/src/checklists.d.ts +169 -0
  16. package/dist/src/checklists.js +310 -0
  17. package/dist/src/checks.d.ts +1539 -0
  18. package/dist/src/checks.js +5025 -0
  19. package/dist/src/cli.d.ts +9 -0
  20. package/dist/src/cli.js +68 -0
  21. package/dist/src/commands/brief.d.ts +92 -0
  22. package/dist/src/commands/brief.js +343 -0
  23. package/dist/src/commands/checklist.d.ts +42 -0
  24. package/dist/src/commands/checklist.js +168 -0
  25. package/dist/src/commands/cutover.d.ts +35 -0
  26. package/dist/src/commands/cutover.js +448 -0
  27. package/dist/src/commands/doctor.d.ts +264 -0
  28. package/dist/src/commands/doctor.js +1546 -0
  29. package/dist/src/commands/gates.d.ts +9 -0
  30. package/dist/src/commands/gates.js +360 -0
  31. package/dist/src/commands/init.d.ts +30 -0
  32. package/dist/src/commands/init.js +177 -0
  33. package/dist/src/commands/lock.d.ts +34 -0
  34. package/dist/src/commands/lock.js +340 -0
  35. package/dist/src/commands/mode.d.ts +40 -0
  36. package/dist/src/commands/mode.js +134 -0
  37. package/dist/src/commands/next.d.ts +130 -0
  38. package/dist/src/commands/next.js +597 -0
  39. package/dist/src/commands/plan.d.ts +20 -0
  40. package/dist/src/commands/plan.js +105 -0
  41. package/dist/src/commands/pool.d.ts +1 -0
  42. package/dist/src/commands/pool.js +139 -0
  43. package/dist/src/commands/resume.d.ts +1 -0
  44. package/dist/src/commands/resume.js +88 -0
  45. package/dist/src/commands/spawn.d.ts +1 -0
  46. package/dist/src/commands/spawn.js +195 -0
  47. package/dist/src/commands/status.d.ts +21 -0
  48. package/dist/src/commands/status.js +122 -0
  49. package/dist/src/commands/sync.d.ts +47 -0
  50. package/dist/src/commands/sync.js +341 -0
  51. package/dist/src/commands/teardown.d.ts +1 -0
  52. package/dist/src/commands/teardown.js +87 -0
  53. package/dist/src/commands/tuition.d.ts +21 -0
  54. package/dist/src/commands/tuition.js +218 -0
  55. package/dist/src/commands/validate.d.ts +78 -0
  56. package/dist/src/commands/validate.js +430 -0
  57. package/dist/src/commands/watch.d.ts +1 -0
  58. package/dist/src/commands/watch.js +172 -0
  59. package/dist/src/cutover.d.ts +584 -0
  60. package/dist/src/cutover.js +1444 -0
  61. package/dist/src/exclusion.d.ts +389 -0
  62. package/dist/src/exclusion.js +843 -0
  63. package/dist/src/exec/env.d.ts +278 -0
  64. package/dist/src/exec/env.js +334 -0
  65. package/dist/src/fleet.d.ts +223 -0
  66. package/dist/src/fleet.js +298 -0
  67. package/dist/src/gates/adapters/http-json.d.ts +5 -0
  68. package/dist/src/gates/adapters/http-json.js +283 -0
  69. package/dist/src/gates/adapters/migrations-command.d.ts +1 -0
  70. package/dist/src/gates/adapters/migrations-command.js +373 -0
  71. package/dist/src/gates/citations.d.ts +408 -0
  72. package/dist/src/gates/citations.js +1169 -0
  73. package/dist/src/gates/coverage.d.ts +375 -0
  74. package/dist/src/gates/coverage.js +836 -0
  75. package/dist/src/gates/credentials.d.ts +233 -0
  76. package/dist/src/gates/credentials.js +752 -0
  77. package/dist/src/gates/deploy.d.ts +1 -0
  78. package/dist/src/gates/deploy.js +33 -0
  79. package/dist/src/gates/gate-classes.d.ts +56 -0
  80. package/dist/src/gates/gate-classes.js +633 -0
  81. package/dist/src/gates/manifest.d.ts +99 -0
  82. package/dist/src/gates/manifest.js +208 -0
  83. package/dist/src/gates/merge-preconditions.d.ts +319 -0
  84. package/dist/src/gates/merge-preconditions.js +932 -0
  85. package/dist/src/gates/migrations.d.ts +1 -0
  86. package/dist/src/gates/migrations.js +36 -0
  87. package/dist/src/gates/pin.d.ts +114 -0
  88. package/dist/src/gates/pin.js +154 -0
  89. package/dist/src/gates/red-witness.d.ts +22 -0
  90. package/dist/src/gates/red-witness.js +482 -0
  91. package/dist/src/gates/release.d.ts +283 -0
  92. package/dist/src/gates/release.js +820 -0
  93. package/dist/src/gates/result.d.ts +116 -0
  94. package/dist/src/gates/result.js +91 -0
  95. package/dist/src/gates/run.d.ts +614 -0
  96. package/dist/src/gates/run.js +1614 -0
  97. package/dist/src/gates/schemas/citation-config.schema.json +59 -0
  98. package/dist/src/gates/schemas/coverage-config.schema.json +77 -0
  99. package/dist/src/gates/schemas/gate-manifest.schema.json +125 -0
  100. package/dist/src/gates/schemas/gate-result.schema.json +160 -0
  101. package/dist/src/gates/schemas/phase-declaration.schema.json +87 -0
  102. package/dist/src/gates/schemas/release-record.schema.json +119 -0
  103. package/dist/src/gates/schemas/verifier-config.schema.json +101 -0
  104. package/dist/src/gates/schemas/witness-spec.schema.json +110 -0
  105. package/dist/src/gates/scope.d.ts +131 -0
  106. package/dist/src/gates/scope.js +1018 -0
  107. package/dist/src/gates/suite.d.ts +217 -0
  108. package/dist/src/gates/suite.js +968 -0
  109. package/dist/src/gates/validate.d.ts +121 -0
  110. package/dist/src/gates/validate.js +414 -0
  111. package/dist/src/hooks.d.ts +84 -0
  112. package/dist/src/hooks.js +125 -0
  113. package/dist/src/index.d.ts +31 -0
  114. package/dist/src/index.js +30 -0
  115. package/dist/src/liveness.d.ts +321 -0
  116. package/dist/src/liveness.js +396 -0
  117. package/dist/src/lock.d.ts +256 -0
  118. package/dist/src/lock.js +792 -0
  119. package/dist/src/model-resolution.d.ts +159 -0
  120. package/dist/src/model-resolution.js +307 -0
  121. package/dist/src/modes.d.ts +149 -0
  122. package/dist/src/modes.js +258 -0
  123. package/dist/src/path-identity.d.ts +34 -0
  124. package/dist/src/path-identity.js +48 -0
  125. package/dist/src/plan.d.ts +73 -0
  126. package/dist/src/plan.js +153 -0
  127. package/dist/src/pool.d.ts +326 -0
  128. package/dist/src/pool.js +988 -0
  129. package/dist/src/roles.d.ts +461 -0
  130. package/dist/src/roles.js +776 -0
  131. package/dist/src/spawn.d.ts +482 -0
  132. package/dist/src/spawn.js +1003 -0
  133. package/dist/src/status.d.ts +116 -0
  134. package/dist/src/status.js +148 -0
  135. package/dist/src/task.d.ts +504 -0
  136. package/dist/src/task.js +307 -0
  137. package/dist/src/teardown.d.ts +39 -0
  138. package/dist/src/teardown.js +422 -0
  139. package/dist/src/tuition.d.ts +159 -0
  140. package/dist/src/tuition.js +311 -0
  141. package/dist/src/validate.d.ts +263 -0
  142. package/dist/src/validate.js +742 -0
  143. package/dist/src/version.d.ts +3 -0
  144. package/dist/src/version.js +38 -0
  145. package/dist/src/watcher.d.ts +275 -0
  146. package/dist/src/watcher.js +849 -0
  147. package/dist/src/witness/run.d.ts +299 -0
  148. package/dist/src/witness/run.js +1373 -0
  149. package/dist/src/witness/spec.d.ts +270 -0
  150. package/dist/src/witness/spec.js +475 -0
  151. package/dist/tsconfig.src.tsbuildinfo +1 -0
  152. package/gate-registry.yaml +526 -0
  153. package/gates.manifest.json +257 -0
  154. package/package.json +72 -3
  155. package/role-model-config.yaml +88 -0
  156. package/roles/README.md +128 -0
  157. package/roles/_shared-dispatch-contract.md +87 -0
  158. package/roles/adversarial-plan-reviewer.md +80 -0
  159. package/roles/clean-room-reviewer.md +140 -0
  160. package/roles/implementer.md +463 -0
  161. package/roles/investigator.md +138 -0
  162. package/roles/plan-writer.md +95 -0
  163. package/schemas/README.md +82 -0
  164. package/schemas/assurance-modes.schema.json +264 -0
  165. package/schemas/charter.schema.json +185 -0
  166. package/schemas/checklist.schema.json +114 -0
  167. package/schemas/cutover-state.schema.json +64 -0
  168. package/schemas/decision-record.schema.json +88 -0
  169. package/schemas/executor-record.schema.json +36 -0
  170. package/schemas/final-report.schema.json +90 -0
  171. package/schemas/finding.schema.json +106 -0
  172. package/schemas/gate-registry.schema.json +260 -0
  173. package/schemas/mechanism-index.schema.json +94 -0
  174. package/schemas/model-resolution.schema.json +362 -0
  175. package/schemas/plan.schema.json +300 -0
  176. package/schemas/report.schema.json +579 -0
  177. package/schemas/role-brief.schema.json +105 -0
  178. package/schemas/role-model-config.schema.json +90 -0
  179. package/schemas/status-line.schema.json +40 -0
  180. package/schemas/tuition.schema.json +191 -0
  181. package/schemas/verdict.schema.json +295 -0
  182. package/schemas/work-history.schema.json +183 -0
  183. package/schemas/write-bypass.schema.json +69 -0
  184. package/templates/charter.example.yaml +54 -0
  185. package/templates/decision-record.example.yaml +27 -0
  186. package/templates/final-report.example.yaml +80 -0
  187. package/templates/plan.example.yaml +87 -0
  188. package/templates/report.example.yaml +236 -0
  189. package/templates/warnings.md +74 -0
  190. package/templates/work-history.example.yaml +185 -0
  191. package/tuition/README.md +76 -0
  192. package/tuition/T-001.yaml +48 -0
  193. package/tuition/T-002.yaml +51 -0
  194. package/tuition/T-003.yaml +100 -0
  195. package/tuition/T-004.yaml +52 -0
  196. package/tuition/T-005.yaml +72 -0
  197. package/tuition/T-006.yaml +81 -0
  198. package/tuition/T-007.yaml +56 -0
  199. package/tuition/T-008.yaml +111 -0
  200. package/tuition/T-009.yaml +50 -0
  201. package/tuition/T-015.yaml +36 -0
  202. package/tuition/T-016.yaml +36 -0
  203. package/tuition/T-017.yaml +46 -0
  204. package/tuition/T-018.yaml +84 -0
  205. package/tuition/T-021.yaml +40 -0
  206. package/tuition/T-022.yaml +36 -0
  207. package/tuition/mechanism-index.yaml +256 -0
@@ -0,0 +1,742 @@
1
+ /**
2
+ * THE KERNEL'S SCHEMA VALIDATION ENGINE (kernel plan M3, M3-P1 step 8;
3
+ * DR-0013).
4
+ *
5
+ * DR-0013 decided this module's substance and it is not a set of defaults to
6
+ * be revisited casually. Ajv 8.20.0 exact, JSON Schema Draft 2020-12, strict
7
+ * mode, all errors, schema and meta-schema validation, and NO coercion, NO
8
+ * inserted defaults, NO removal of additional properties, NO mutation of the
9
+ * validated input and NO automatic loading of remote schemas. Unknown or
10
+ * invalidly combined keywords fail schema COMPILATION, before any instance is
11
+ * examined.
12
+ *
13
+ * AJV IS AN INTERNAL IMPLEMENTATION DETAIL (DR-0013 clause 5). Its wording is
14
+ * never a public contract. Every Ajv error is mapped, by keyword, into the
15
+ * Tiphys diagnostic contract
16
+ *
17
+ * INVALID <json-pointer> <message>
18
+ *
19
+ * with a deterministic order. The order is a FINAL SORT by (pointer, message)
20
+ * applied to the collected list, not a property of the traversal, so a future
21
+ * engine that walks differently still emits the same lines in the same order.
22
+ * That is the same rule src/gates/validate.ts stated for M2 and it is
23
+ * deliberately identical: the two modules now share this one engine.
24
+ *
25
+ * THE MESSAGE TABLE IS THE CONTRACT, not Ajv's `message` field. A keyword
26
+ * whose Ajv error reaches `renderAjvError` and matches no `case` of that
27
+ * function's switch falls to its `default:` arm, which is a Tiphys DEFECT and
28
+ * is reported as one, naming the keyword, rather than being papered over with
29
+ * Ajv's own sentence. That refusal is what stops Ajv wording leaking into a
30
+ * public contract by omission (DR-0013 criterion 8).
31
+ *
32
+ * THIS PARAGRAPH NAMED THE WRONG MECHANISM UNTIL THE DR-0047 SWEEP (CR-VS-004).
33
+ * It said the refusal came from a `MESSAGE_BY_KEYWORD` set. That set existed,
34
+ * nothing ever read it (three references: its own declaration and two comments
35
+ * saying it was the contract), and its contents disagreed with
36
+ * `AUTHORING_VOCABULARY` in BOTH directions: `$ref`, `items`, `properties` and
37
+ * `then` were in the vocabulary and not in the set, `minimum`, `maximum` and
38
+ * `maxItems` were in the set and not in the vocabulary. A dead constant that a
39
+ * file's own documentation calls the contract is how the next reader edits the
40
+ * thing that does nothing, so it is deleted and this sentence now names the
41
+ * switch, which is what actually refuses.
42
+ *
43
+ * YAML IS INPUT DECODING AND IS A SEPARATE STAGE (DR-0013 YAML clause 3).
44
+ * `decodeDocument` decodes; `validateInstance` validates an already-decoded
45
+ * value. A decode failure and a validation failure are distinguishable in the
46
+ * diagnostic and neither produces a stack trace on any stream.
47
+ *
48
+ * PATHS THIS MODULE READS ARE NOT ITS OWN (D-M3-27, and the mechanism index's
49
+ * row `reading-a-path-whose-type-is-not-established`). Every read goes through
50
+ * `classifyEntry`, so a named pipe handed to `tiphys validate` is refused
51
+ * with the observed entry type instead of blocking the command forever.
52
+ */
53
+ import { readFileSync, statSync } from "node:fs";
54
+ import { createRequire } from "node:module";
55
+ import { classifyEntry } from "./task.js";
56
+ /**
57
+ * THE TWO PRODUCTION DEPENDENCIES ARE LOADED LAZILY, AND THIS IS NOT A
58
+ * MICRO-OPTIMISATION. Measured 2026-08-08 while retiring the M2 engine.
59
+ *
60
+ * Three delivered M2 tests COPY the kernel's own tree to a scratch location
61
+ * OUTSIDE the repository and run it there: `stagedDist` in
62
+ * `test/gates.test.ts` copies `dist/`, and `copyInstallation` in
63
+ * `test/scope-gate.test.ts` copies `src/`. Node resolves a bare specifier by
64
+ * walking `node_modules` UPWARD from the importing file, and a copy under
65
+ * `/tmp` has no `node_modules` above it. A top-level `import ... from "ajv"`
66
+ * in this module therefore made three previously-passing M2 tests fail with
67
+ * `ERR_MODULE_NOT_FOUND` at module load, exit code 1, before any of the
68
+ * conditions those tests exist to exercise could happen. Captured in the
69
+ * work history.
70
+ *
71
+ * Those tests are re-run UNCHANGED under DR-0013 clause 6 and may not be
72
+ * edited, so the module must not need `ajv` resolvable merely to be
73
+ * IMPORTED. `createRequire` defers the resolution to the first schema
74
+ * compilation, which a copied-out gate that fails earlier never reaches, and
75
+ * an unresolvable dependency then becomes a compile diagnostic rather than an
76
+ * uncaught crash read as a verdict (the CR-1047 property, one level up).
77
+ */
78
+ const requireDependency = createRequire(import.meta.url);
79
+ function ajvModule() {
80
+ return requireDependency("ajv/dist/2020.js");
81
+ }
82
+ function yamlModule() {
83
+ return requireDependency("yaml");
84
+ }
85
+ /** The dialect every Tiphys schema declares (DR-0013 clause 3). */
86
+ export const TIPHYS_DIALECT = "https://json-schema.org/draft/2020-12/schema";
87
+ /** The document root, in RFC 6901 URI-fragment form. */
88
+ export const ROOT_POINTER = "#";
89
+ /**
90
+ * THE DECLARED AUTHORING VOCABULARY (DR-0013 clause 7). THIS LIST IS THE SOURCE
91
+ * OF TRUTH. Ajv supplies Draft 2020-12 entire; this is what Tiphys schemas are
92
+ * ALLOWED to use, so a keyword outside it is a deliberate expansion rather than
93
+ * an accident. Every entry has both a positive and a negative test (validator
94
+ * criterion 2), asserted by `test/schemas.test.ts`, which DERIVES its cases
95
+ * from this array rather than from a hand-written list.
96
+ *
97
+ * `schemas/README.md` RENDERS THIS LIST FOR A HUMAN READER AND IS NOT ITSELF
98
+ * THE CONTRACT, and the distinction was paid for (CR-VS-004, FIND-02 of the
99
+ * DR-0047 sweep). The sentence here used to say the vocabulary IS "documented
100
+ * in `schemas/README.md`", which reads as a guarantee that the two agree.
101
+ * Measured at the swept head they did not: this array holds sixteen keywords
102
+ * and that document's table declared fifteen, `uniqueItems` being the missing
103
+ * row, since the day M3-P1 wrote both. Nothing asserted the relation, so no gate
104
+ * could see it.
105
+ *
106
+ * THE DRIFT IS NOW CLOSED, AND BY THE TEST RATHER THAN BY THE ROW (DR-0047
107
+ * sweep round 2). The row was added to `schemas/README.md`, which closes the one
108
+ * instance; what closes the MECHANISM is that test/schemas.test.ts:340 parses
109
+ * that document's keyword column and compares it to this array, so the two
110
+ * halves can no longer disagree silently. The document still does not DECIDE
111
+ * anything: this array is the contract and that one renders it, which is why
112
+ * the comparison is written as an assertion about the document rather than as a
113
+ * generator.
114
+ *
115
+ * AND THE SECOND HALF OF THAT SENTENCE IS GONE. It said every entry "therefore
116
+ * also has an entry in `MESSAGE_BY_KEYWORD`", which was false in both
117
+ * directions and about a constant nothing read; see the module header.
118
+ */
119
+ export const AUTHORING_VOCABULARY = [
120
+ "$ref",
121
+ "additionalProperties",
122
+ "const",
123
+ "contains",
124
+ "enum",
125
+ "if",
126
+ "items",
127
+ "minItems",
128
+ "minLength",
129
+ "oneOf",
130
+ "pattern",
131
+ "properties",
132
+ "required",
133
+ "then",
134
+ "type",
135
+ "uniqueItems",
136
+ ];
137
+ /** Annotations that carry no constraint and are permitted everywhere. */
138
+ export const ANNOTATION_KEYS = [
139
+ "$comment",
140
+ "$defs",
141
+ "$id",
142
+ "$schema",
143
+ "description",
144
+ "title",
145
+ ];
146
+ /** Render a value the way every diagnostic in this contract renders one. */
147
+ export function render(value) {
148
+ return JSON.stringify(value) ?? String(value);
149
+ }
150
+ /** The JSON type name of a value, as the `type` keyword uses it. */
151
+ export function jsonTypeOf(value) {
152
+ if (value === null) {
153
+ return "null";
154
+ }
155
+ if (Array.isArray(value)) {
156
+ return "array";
157
+ }
158
+ const primitive = typeof value;
159
+ if (primitive === "number") {
160
+ return Number.isInteger(value) ? "integer" : "number";
161
+ }
162
+ if (primitive === "boolean") {
163
+ return "boolean";
164
+ }
165
+ if (primitive === "string") {
166
+ return "string";
167
+ }
168
+ return "object";
169
+ }
170
+ /**
171
+ * THE MESSAGE CONTRACT. One entry per way a value can fail. The first eight
172
+ * entries are M2's, copied verbatim from `src/gates/validate.ts`'s
173
+ * `DIAGNOSTIC_MESSAGES` because DR-0013 clause 6 retires that ENGINE while
174
+ * preserving its diagnostic contract: an M2 test asserting
175
+ * `required property id is missing` must still read exactly that after the
176
+ * swap, and that is checked by re-running M2's tests unchanged rather than by
177
+ * this comment.
178
+ */
179
+ export const DIAGNOSTIC_MESSAGES = {
180
+ type: (expected, observed) => `expected type ${expected} but found ${observed}`,
181
+ required: (name) => `required property ${name} is missing`,
182
+ additionalProperties: (name) => `property ${name} is not permitted here`,
183
+ enum: (value, permitted) => `value ${value} is not one of the permitted values ${permitted}`,
184
+ const: (value, required) => `value ${value} does not equal the required constant ${required}`,
185
+ minimum: (value, minimum) => `value ${value} is below the minimum ${minimum}`,
186
+ minItems: (count, minimum) => `array has ${count} items, fewer than the required minimum ${minimum}`,
187
+ pattern: (value, pattern) => `value ${value} does not match the required pattern ${pattern}`,
188
+ /* M3's additions to the vocabulary, Tiphys-owned wording throughout. */
189
+ minLength: (value, minimum) => `value ${value} is shorter than the required minimum length ${minimum}`,
190
+ uniqueItems: (first, second) => `array items ${first} and ${second} are duplicates and must be unique`,
191
+ contains: (minimum) => `array contains no item matching the required shape, and ${minimum} is required`,
192
+ /* CR-004 (M3-P3 fix round 1). The generic `contains` line above names the
193
+ FIELD and not the required value, so an author reading it is told an array
194
+ is missing something and not WHAT. When the `contains` subschema is a bare
195
+ `const`, the value is knowable and is named. The generic form stays for
196
+ every other shape, because a subschema with `properties` and `required`
197
+ has no single value to quote and inventing a rendering of it would be
198
+ worse than saying "the required shape". */
199
+ containsValue: (value, minimum) => `array contains no item equal to ${value}, and ${minimum} is required`,
200
+ oneOf: () => "value matches no permitted alternative here",
201
+ ifThen: () => "value does not satisfy the requirements its own shape triggers here",
202
+ maximum: (value, maximum) => `value ${value} is above the maximum ${maximum}`,
203
+ maxItems: (count, maximum) => `array has ${count} items, more than the permitted maximum ${maximum}`,
204
+ cyclicRef: (reference) => `schema reference ${reference} is cyclic`,
205
+ unresolvedRef: (reference) => `schema reference ${reference} does not resolve`,
206
+ /* ------------------------------------------------------------------ */
207
+ /* COMPILATION FAILURES ARE PART OF THE CONTRACT TOO (fix round 1, */
208
+ /* B-002). Criteria 4, 5 and 7 all fail at COMPILATION rather than at */
209
+ /* validation, and the first version of this module printed the raw */
210
+ /* exception text on that path, so Ajv's wording reached a public */
211
+ /* stream on the arm nobody had asserted about. That is T-009's shape */
212
+ /* one layer down: one witnessed arm, one unwitnessed arm, and the */
213
+ /* unwitnessed one is the one that broke. Every entry below carries */
214
+ /* only an IDENTIFIER lifted out of the Ajv error (a keyword name, a */
215
+ /* reference, a location), never an Ajv sentence. */
216
+ /* ------------------------------------------------------------------ */
217
+ unknownKeyword: (keyword) => `schema keyword ${keyword} is not in this validator's vocabulary`,
218
+ strictPolicyUntyped: (keyword, expected) => `schema uses keyword ${keyword} without declaring type ${expected}, which this validator's strict policy requires`,
219
+ strictPolicy: () => "schema is refused by this validator's strict policy",
220
+ remoteRef: (reference) => `schema reference ${reference} is remote, and this validator never loads remote schemas`,
221
+ invalidSchemaDocument: () => "schema is not a valid JSON Schema document",
222
+ patternUncompilable: () => "schema contains a pattern that is not a valid regular expression",
223
+ cyclicCompilation: () => "schema references form a cycle this validator cannot compile",
224
+ /** Nothing above matched. Deliberately carries no Ajv text at all. */
225
+ uncompilable: () => "schema could not be compiled",
226
+ /** A keyword Ajv reported that this table does not translate. */
227
+ untranslated: (keyword) => `internal defect: no Tiphys diagnostic is defined for schema keyword ${keyword}`,
228
+ };
229
+ /**
230
+ * `INVALID <json-pointer> <message>`. The public contract, shared with
231
+ * `src/gates/validate.ts`.
232
+ *
233
+ * Pointers are RFC 6901 JSON Pointers in URI-fragment form, so the document
234
+ * root is `#` and a nested location is `#/gates/0/id`. The fragment form is
235
+ * chosen because the bare pointer to the root is the EMPTY STRING, which
236
+ * would render this line with two consecutive spaces.
237
+ */
238
+ export function formatDiagnostic(diagnostic) {
239
+ return `INVALID ${diagnostic.pointer} ${diagnostic.message}`;
240
+ }
241
+ export function formatDiagnostics(diagnostics) {
242
+ return diagnostics.map(formatDiagnostic);
243
+ }
244
+ /** The contract's deterministic order: by pointer, then by message, ASCII. */
245
+ export function sortDiagnostics(diagnostics) {
246
+ const sorted = [...diagnostics];
247
+ sorted.sort((a, b) => {
248
+ if (a.pointer !== b.pointer) {
249
+ return a.pointer < b.pointer ? -1 : 1;
250
+ }
251
+ if (a.message === b.message) {
252
+ return 0;
253
+ }
254
+ return a.message < b.message ? -1 : 1;
255
+ });
256
+ return sorted;
257
+ }
258
+ function escapeSegment(segment) {
259
+ return segment.replace(/~/g, "~0").replace(/\//g, "~1");
260
+ }
261
+ /** Ajv's `instancePath` is a bare pointer; the contract's is fragment form. */
262
+ function toFragmentPointer(instancePath, extra) {
263
+ const base = instancePath === "" ? ROOT_POINTER : `${ROOT_POINTER}${instancePath}`;
264
+ return extra === undefined ? base : `${base}/${escapeSegment(extra)}`;
265
+ }
266
+ /** Resolve an Ajv `instancePath` against the instance, for reporting values. */
267
+ function valueAt(instance, instancePath) {
268
+ if (instancePath === "") {
269
+ return instance;
270
+ }
271
+ let node = instance;
272
+ for (const raw of instancePath.slice(1).split("/")) {
273
+ const segment = raw.replace(/~1/g, "/").replace(/~0/g, "~");
274
+ if (Array.isArray(node)) {
275
+ node = node[Number(segment)];
276
+ continue;
277
+ }
278
+ if (typeof node === "object" && node !== null) {
279
+ node = Object.prototype.hasOwnProperty.call(node, segment)
280
+ ? node[segment]
281
+ : undefined;
282
+ continue;
283
+ }
284
+ return undefined;
285
+ }
286
+ return node;
287
+ }
288
+ /**
289
+ * Translate ONE Ajv error into the Tiphys contract. `undefined` means the
290
+ * error carries no independent information for a reader (Ajv reports a
291
+ * failing branch of `oneOf`/`if` as well as the composite), and the caller
292
+ * drops it.
293
+ */
294
+ function renderAjvError(error, instance,
295
+ /**
296
+ * `contains` subschema path -> the `const` that subschema requires, for the
297
+ * errors where it is a bare `const`. Built by the caller from the SUBSIDIARY
298
+ * errors Ajv reports beside the composite, which `isSubsidiary` then drops:
299
+ * the value is already in the error stream and was being thrown away.
300
+ */
301
+ containsConst = new Map()) {
302
+ const params = error.params;
303
+ const at = error.instancePath;
304
+ switch (error.keyword) {
305
+ case "type":
306
+ return {
307
+ pointer: toFragmentPointer(at),
308
+ message: DIAGNOSTIC_MESSAGES.type(String(params["type"]), jsonTypeOf(valueAt(instance, at))),
309
+ };
310
+ case "required":
311
+ return {
312
+ pointer: toFragmentPointer(at, String(params["missingProperty"])),
313
+ message: DIAGNOSTIC_MESSAGES.required(String(params["missingProperty"])),
314
+ };
315
+ case "additionalProperties":
316
+ return {
317
+ pointer: toFragmentPointer(at, String(params["additionalProperty"])),
318
+ message: DIAGNOSTIC_MESSAGES.additionalProperties(String(params["additionalProperty"])),
319
+ };
320
+ case "enum":
321
+ return {
322
+ pointer: toFragmentPointer(at),
323
+ message: DIAGNOSTIC_MESSAGES.enum(render(valueAt(instance, at)), params["allowedValues"].map((v) => render(v)).join(", ")),
324
+ };
325
+ case "const":
326
+ return {
327
+ pointer: toFragmentPointer(at),
328
+ message: DIAGNOSTIC_MESSAGES.const(render(valueAt(instance, at)), render(params["allowedValue"])),
329
+ };
330
+ case "minimum":
331
+ return {
332
+ pointer: toFragmentPointer(at),
333
+ message: DIAGNOSTIC_MESSAGES.minimum(render(valueAt(instance, at)), render(params["limit"])),
334
+ };
335
+ case "maximum":
336
+ return {
337
+ pointer: toFragmentPointer(at),
338
+ message: DIAGNOSTIC_MESSAGES.maximum(render(valueAt(instance, at)), render(params["limit"])),
339
+ };
340
+ case "minItems": {
341
+ const value = valueAt(instance, at);
342
+ return {
343
+ pointer: toFragmentPointer(at),
344
+ message: DIAGNOSTIC_MESSAGES.minItems(String(Array.isArray(value) ? value.length : 0), String(params["limit"])),
345
+ };
346
+ }
347
+ case "maxItems": {
348
+ const value = valueAt(instance, at);
349
+ return {
350
+ pointer: toFragmentPointer(at),
351
+ message: DIAGNOSTIC_MESSAGES.maxItems(String(Array.isArray(value) ? value.length : 0), String(params["limit"])),
352
+ };
353
+ }
354
+ case "minLength":
355
+ return {
356
+ pointer: toFragmentPointer(at),
357
+ message: DIAGNOSTIC_MESSAGES.minLength(render(valueAt(instance, at)), String(params["limit"])),
358
+ };
359
+ case "pattern":
360
+ return {
361
+ pointer: toFragmentPointer(at),
362
+ message: DIAGNOSTIC_MESSAGES.pattern(render(valueAt(instance, at)), String(params["pattern"])),
363
+ };
364
+ case "uniqueItems":
365
+ return {
366
+ pointer: toFragmentPointer(at),
367
+ /* Ajv reports the LATER index first. The contract sorts them, so
368
+ the line reads in document order and does not depend on which
369
+ direction the engine happened to compare in. */
370
+ message: DIAGNOSTIC_MESSAGES.uniqueItems(String(Math.min(Number(params["i"]), Number(params["j"]))), String(Math.max(Number(params["i"]), Number(params["j"])))),
371
+ };
372
+ case "contains": {
373
+ const minimum = String(params["minContains"] ?? 1);
374
+ const required = containsConst.get(error.schemaPath);
375
+ return {
376
+ pointer: toFragmentPointer(at),
377
+ message: required === undefined
378
+ ? DIAGNOSTIC_MESSAGES.contains(minimum)
379
+ : DIAGNOSTIC_MESSAGES.containsValue(render(required), minimum),
380
+ };
381
+ }
382
+ case "oneOf":
383
+ /* `anyOf`, `allOf` and `not` are not in the AUTHORING vocabulary, so no
384
+ Tiphys schema uses them. They still reach this renderer, because
385
+ META-SCHEMA validation runs the 2020-12 meta-schema (which uses all
386
+ three) over a Tiphys schema document. Mapping them here is what stopped
387
+ `internal defect: no Tiphys diagnostic is defined for schema keyword
388
+ anyOf` from being the diagnostic a bad `type` produced, which is how
389
+ the untranslated-keyword guard earned its keep. */
390
+ case "anyOf":
391
+ case "allOf":
392
+ case "not":
393
+ return {
394
+ pointer: toFragmentPointer(at),
395
+ message: DIAGNOSTIC_MESSAGES.oneOf(),
396
+ };
397
+ case "if":
398
+ return {
399
+ pointer: toFragmentPointer(at),
400
+ message: DIAGNOSTIC_MESSAGES.ifThen(),
401
+ };
402
+ default:
403
+ return {
404
+ pointer: toFragmentPointer(at),
405
+ message: DIAGNOSTIC_MESSAGES.untranslated(error.keyword),
406
+ };
407
+ }
408
+ }
409
+ /**
410
+ * Is this Ajv error a SUBSIDIARY of a composite the caller already reports?
411
+ *
412
+ * Ajv with allErrors reports the failing branches inside
413
+ * `oneOf`/`anyOf`/`not`/`contains` as well as the composite keyword itself.
414
+ * Which branch is reported, and how many, depends on Ajv's branch ordering and
415
+ * on the instance's length, which is exactly the nondeterminism the contract
416
+ * forbids, so only the COMPOSITE line survives: its pointer is the instance
417
+ * location the author must look at. `contains` is in this list for the second
418
+ * reason rather than the first: a ten-item array that contains nothing
419
+ * matching would otherwise emit eleven lines for one fault.
420
+ *
421
+ * `if`/`then`/`else` is DELIBERATELY NOT in that list. Exactly one branch
422
+ * applies and which one is decided by the instance, not by the engine, so the
423
+ * branch's own diagnostics are deterministic AND are the informative ones: a
424
+ * charter with `mode: none` and no `reason` must say `reason`, which only the
425
+ * `then` branch's `required` error carries (criterion 5b).
426
+ */
427
+ function isSubsidiary(error) {
428
+ return /\/(oneOf|anyOf|not|contains)\//.test(error.schemaPath);
429
+ }
430
+ /**
431
+ * A fresh Ajv, configured EXACTLY as DR-0013 clause 4 lists. Each policy is
432
+ * named so a diff that removes one is visible as the removal of a decided
433
+ * policy rather than as a formatting change.
434
+ */
435
+ export function makeAjv() {
436
+ const { Ajv2020: Constructor } = ajvModule();
437
+ return new Constructor({
438
+ strict: true,
439
+ allErrors: true,
440
+ validateSchema: true,
441
+ coerceTypes: false,
442
+ useDefaults: false,
443
+ removeAdditional: false,
444
+ /* No `loadSchema`: an unresolved remote reference fails compilation
445
+ rather than being fetched (DR-0013 clause 4, criterion 7). */
446
+ });
447
+ }
448
+ /**
449
+ * COMPANION SCHEMAS (M3-P4). A shipped schema may `$ref` a definition in a
450
+ * SIBLING shipped schema, so that a contract shared by two artifact types has
451
+ * ONE definition rather than two that can drift. `schemas/work-history.schema.json`
452
+ * is the first instance: its `claims[]`, `fix-round[]` and `gate-evidence[]`
453
+ * items resolve into `schemas/report.schema.json`.
454
+ *
455
+ * THIS IS NOT THE REMOTE LOADING DR-0013 CLAUSE 4 WITHHOLDS, and the
456
+ * difference is the whole reason the parameter exists rather than a
457
+ * `loadSchema` callback. A companion is supplied BY THE CALLER, from the
458
+ * shipped `schemas/` directory, named in `src/commands/validate.ts`'s
459
+ * companion table beside the type table. Nothing is fetched, nothing is
460
+ * resolved from the reference itself, and a `$ref` naming a document the
461
+ * caller did not supply still fails COMPILATION with the existing
462
+ * `unresolvedRef` or `remoteRef` diagnostic. The set of documents a schema
463
+ * may reach is therefore declared in one auditable place, which is the
464
+ * property clause 4 is protecting.
465
+ */
466
+ const compiled = new WeakMap();
467
+ function sameCompanions(left, right) {
468
+ return (left.length === right.length && left.every((one, index) => one === right[index]));
469
+ }
470
+ /**
471
+ * Compile a schema. Cached by schema OBJECT IDENTITY, because a fresh Ajv per
472
+ * call is both slow and wrong: two schemas carrying the same `$id` cannot
473
+ * share one Ajv instance, so each schema gets its own.
474
+ *
475
+ * IDENTITY, NOT CONTENT, and it has one observable consequence worth stating
476
+ * rather than discovering. A caller that MUTATES a schema object in place
477
+ * after compiling it keeps the old validator, because the WeakMap key is
478
+ * unchanged. That is correct for how schemas are used here (documents read
479
+ * from disk and never edited), and it was measured: the first attempt at
480
+ * criterion 4's red witness defanged a keyword in place and the diagnostics
481
+ * did not move, which read exactly like a schema whose keyword was doing
482
+ * nothing. The witness was redone with a schema re-read from disk per arm.
483
+ * Any future caller that wants to compile a modified schema must hand over a
484
+ * NEW object.
485
+ *
486
+ * The cache is keyed by the schema object AND by the COMPANION LIST, also by
487
+ * identity: the same document compiled with and without a companion is two
488
+ * different compilations, and returning the first for the second would be the
489
+ * quiet wrong answer. Companion lists here are one or zero long, so the scan
490
+ * is a scan of a one-element array.
491
+ */
492
+ export function compileSchema(schema, companions = []) {
493
+ const entries = compiled.get(schema) ?? [];
494
+ const cached = entries.find((entry) => sameCompanions(entry.companions, companions));
495
+ if (cached !== undefined) {
496
+ return cached.result;
497
+ }
498
+ let result;
499
+ const ajv = makeAjv();
500
+ try {
501
+ for (const companion of companions) {
502
+ ajv.addSchema(companion);
503
+ }
504
+ result = { ok: true, validator: ajv.compile(schema) };
505
+ }
506
+ catch (error) {
507
+ const diagnostics = compilationDiagnostics(error, ajv, schema);
508
+ result = {
509
+ ok: false,
510
+ diagnostics,
511
+ reason: formatDiagnostics(diagnostics).join("; "),
512
+ };
513
+ }
514
+ entries.push({ companions: [...companions], result });
515
+ compiled.set(schema, entries);
516
+ return result;
517
+ }
518
+ function ajvText(error) {
519
+ const text = error instanceof Error ? error.message : String(error);
520
+ return text.replace(/\s+/g, " ").trim();
521
+ }
522
+ /**
523
+ * Translate a COMPILATION failure into the Tiphys diagnostic contract.
524
+ *
525
+ * The classification reads Ajv's exception only to EXTRACT IDENTIFIERS from
526
+ * it; nothing Ajv wrote is passed through. An unrecognised shape becomes the
527
+ * bare `schema could not be compiled`, which loses detail deliberately:
528
+ * losing detail is recoverable, and leaking a third party's wording into a
529
+ * contract nine phases are written against is not.
530
+ */
531
+ export function compilationDiagnostics(error, ajv, schema) {
532
+ const text = ajvText(error);
533
+ const unknownKeyword = /unknown keyword: "([^"]+)"/.exec(text);
534
+ if (unknownKeyword !== null) {
535
+ return [
536
+ {
537
+ pointer: ROOT_POINTER,
538
+ message: DIAGNOSTIC_MESSAGES.unknownKeyword(unknownKeyword[1]),
539
+ },
540
+ ];
541
+ }
542
+ const untyped = /missing type "([^"]+)" for keyword "([^"]+)" at "([^"]+)"/.exec(text);
543
+ if (untyped !== null) {
544
+ return [
545
+ {
546
+ pointer: untyped[3].startsWith("#")
547
+ ? untyped[3]
548
+ : ROOT_POINTER,
549
+ message: DIAGNOSTIC_MESSAGES.strictPolicyUntyped(untyped[2], untyped[1]),
550
+ },
551
+ ];
552
+ }
553
+ const missingRef = /can't resolve reference (\S+) from id/.exec(text);
554
+ if (missingRef !== null) {
555
+ const reference = missingRef[1];
556
+ /* A scheme-qualified reference is REMOTE, and DR-0013 clause 4 withholds
557
+ the loader that would fetch it, so it fails closed rather than being
558
+ skipped or retrieved. A bare `#/...` that does not resolve is the local
559
+ case and gets the local message. */
560
+ const remote = /^[a-z][a-z0-9+.-]*:/i.test(reference);
561
+ return [
562
+ {
563
+ pointer: ROOT_POINTER,
564
+ message: remote
565
+ ? DIAGNOSTIC_MESSAGES.remoteRef(reference)
566
+ : DIAGNOSTIC_MESSAGES.unresolvedRef(reference),
567
+ },
568
+ ];
569
+ }
570
+ if (text.startsWith("schema is invalid")) {
571
+ /* META-SCHEMA VALIDATION. Ajv leaves the failures on the instance, and
572
+ they are ordinary errors over the SCHEMA as the instance, so they route
573
+ through the same renderer and come out with pointers INTO the schema.
574
+ That is strictly more useful than Ajv's sentence and owes it nothing. */
575
+ const metaErrors = ajv.errors ?? [];
576
+ const mapped = metaErrors
577
+ .filter((one) => !isSubsidiary(one))
578
+ .map((one) => renderAjvError(one, schema))
579
+ .filter((one) => one !== undefined);
580
+ return sortDiagnostics([
581
+ { pointer: ROOT_POINTER, message: DIAGNOSTIC_MESSAGES.invalidSchemaDocument() },
582
+ ...mapped,
583
+ ]);
584
+ }
585
+ if (error instanceof SyntaxError && /regular expression/i.test(text)) {
586
+ return [
587
+ { pointer: ROOT_POINTER, message: DIAGNOSTIC_MESSAGES.patternUncompilable() },
588
+ ];
589
+ }
590
+ if (error instanceof RangeError) {
591
+ return [
592
+ { pointer: ROOT_POINTER, message: DIAGNOSTIC_MESSAGES.cyclicCompilation() },
593
+ ];
594
+ }
595
+ if (text.startsWith("strict mode")) {
596
+ return [{ pointer: ROOT_POINTER, message: DIAGNOSTIC_MESSAGES.strictPolicy() }];
597
+ }
598
+ return [{ pointer: ROOT_POINTER, message: DIAGNOSTIC_MESSAGES.uncompilable() }];
599
+ }
600
+ /**
601
+ * Validate an already-decoded instance against a schema document.
602
+ *
603
+ * Returns diagnostics in the contract's deterministic order. A schema that
604
+ * cannot be compiled yields ONE root diagnostic rather than a throw: this
605
+ * function is called from a CLI whose ordinary input is hand-authored, and a
606
+ * thrown compile error there is the stack trace step 8b exists to prevent.
607
+ */
608
+ export function validateInstance(schema, instance, companions = []) {
609
+ const compilation = compileSchema(schema, companions);
610
+ if (!compilation.ok) {
611
+ return compilation.diagnostics;
612
+ }
613
+ const ok = compilation.validator(instance);
614
+ if (ok) {
615
+ return [];
616
+ }
617
+ const errors = compilation.validator.errors ?? [];
618
+ /* CR-004. Ajv reports, beside every failing `contains`, one subsidiary
619
+ `const` error per array item carrying the required value in
620
+ `params.allowedValue`. `isSubsidiary` drops those (they are one fault
621
+ reported N times), so the value was being discarded a few lines before the
622
+ message that needed it. Harvest it first, keyed by the contains
623
+ subschema's own schemaPath, which is the subsidiary's path minus the
624
+ trailing `/const`. */
625
+ const containsConst = new Map();
626
+ for (const error of errors) {
627
+ if (error.keyword === "const" && error.schemaPath.endsWith("/contains/const")) {
628
+ containsConst.set(error.schemaPath.slice(0, -"/const".length), error.params["allowedValue"]);
629
+ }
630
+ }
631
+ const diagnostics = [];
632
+ for (const error of errors) {
633
+ if (isSubsidiary(error)) {
634
+ continue;
635
+ }
636
+ const diagnostic = renderAjvError(error, instance, containsConst);
637
+ if (diagnostic !== undefined) {
638
+ diagnostics.push(diagnostic);
639
+ }
640
+ }
641
+ /* Ajv can report the same pointer and message twice through two branches;
642
+ the contract emits one line per distinct fault. */
643
+ const unique = new Map();
644
+ for (const diagnostic of diagnostics) {
645
+ unique.set(formatDiagnostic(diagnostic), diagnostic);
646
+ }
647
+ return sortDiagnostics([...unique.values()]);
648
+ }
649
+ /** Validate and format in one step. */
650
+ export function validateToLines(schema, instance, companions = []) {
651
+ return formatDiagnostics(validateInstance(schema, instance, companions));
652
+ }
653
+ /**
654
+ * Decode YAML (or JSON, which YAML 1.2 is a superset of) into a plain value.
655
+ *
656
+ * A parse failure returns a reason, never a throw and never a stack trace.
657
+ * `yaml` reports position information; it is collapsed to one line because
658
+ * the contract is one diagnostic line per failure.
659
+ */
660
+ export function decodeDocument(text, label) {
661
+ const { parse: parseYaml, YAMLParseError } = yamlModule();
662
+ let value;
663
+ try {
664
+ value = parseYaml(text, { prettyErrors: false });
665
+ }
666
+ catch (error) {
667
+ if (error instanceof YAMLParseError) {
668
+ const where = error.linePos?.[0];
669
+ const at = where === undefined ? "" : ` at line ${String(where.line)} column ${String(where.col)}`;
670
+ return {
671
+ ok: false,
672
+ reason: `${label} is not valid YAML${at}: ${ajvText(error.message)}`,
673
+ };
674
+ }
675
+ return { ok: false, reason: `${label} could not be decoded: ${ajvText(error)}` };
676
+ }
677
+ return { ok: true, value };
678
+ }
679
+ /**
680
+ * READ A PATH THIS PROGRAM DID NOT CREATE (D-M3-27).
681
+ *
682
+ * `tiphys validate <file>` takes an operator-supplied path, so the path may
683
+ * be a FIFO, a directory, a socket or a dangling symlink. Opening a FIFO with
684
+ * no writer blocks forever and the command never returns, which is the M1-P5
685
+ * class (CR-520). The type is established with `classifyEntry` BEFORE the
686
+ * open, and an irregular entry is refused naming the path and the observed
687
+ * type.
688
+ */
689
+ export function readOperatorPath(path) {
690
+ const entry = classifyEntry(path);
691
+ if (entry.kind === "absent" || entry.kind === "dangling") {
692
+ return { ok: false, reason: `${path} does not exist` };
693
+ }
694
+ if (entry.kind === "irregular" || entry.kind === "unexaminable") {
695
+ return { ok: false, reason: entry.reason };
696
+ }
697
+ try {
698
+ return { ok: true, body: readFileSync(path, "utf8") };
699
+ }
700
+ catch (error) {
701
+ return { ok: false, reason: `${path} could not be read: ${ajvText(error)}` };
702
+ }
703
+ }
704
+ /**
705
+ * Establish that a path is a DIRECTORY before walking it. Same hazard, same
706
+ * rule: `--context` is operator-supplied and a FIFO there would block the
707
+ * walk exactly as it blocks a read.
708
+ */
709
+ export function classifyContextDirectory(path) {
710
+ const entry = classifyEntry(path);
711
+ if (entry.kind === "absent" || entry.kind === "dangling") {
712
+ return `${path} does not exist`;
713
+ }
714
+ if (entry.kind === "unexaminable") {
715
+ return entry.reason;
716
+ }
717
+ if (entry.kind === "regular") {
718
+ return `${path} is a regular file, not a directory, so it was not walked`;
719
+ }
720
+ /* `classifyEntry` calls everything that is not a regular file "irregular",
721
+ including a directory, which is what a context path is SUPPOSED to be.
722
+ Distinguish here rather than widening classifyEntry, whose callers all
723
+ want a regular file (M2's fleet-state reads). */
724
+ const stats = safeStat(path);
725
+ if (stats === undefined) {
726
+ return `${path} could not be examined`;
727
+ }
728
+ if (!stats.isDirectory()) {
729
+ return entry.kind === "irregular"
730
+ ? entry.reason.replace("not a regular file", "not a directory")
731
+ : `${path} is not a directory`;
732
+ }
733
+ return undefined;
734
+ }
735
+ function safeStat(path) {
736
+ try {
737
+ return statSync(path);
738
+ }
739
+ catch {
740
+ return undefined;
741
+ }
742
+ }