@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,440 @@
1
+ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
2
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
3
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
4
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
5
+ });
6
+ }
7
+ return path;
8
+ };
9
+ import { realpathSync } from "node:fs";
10
+ import { createRequire } from "node:module";
11
+ import { isAbsolute, join, relative, sep } from "node:path";
12
+ import { pathToFileURL } from "node:url";
13
+ import { readRegularFileIfPresent, singleLine } from "../task.js";
14
+ /**
15
+ * ADAPTER SELECTION AND LOADING (kernel plan M4, M4-P4; M4-D-03 at
16
+ * delivery/plan/m4-intake.md:729).
17
+ *
18
+ * THE RESOLUTION ROOT IS THE WHOLE SECURITY PROPERTY, and it is the reason
19
+ * this module exists rather than a one-line `await import(specifier)` at the
20
+ * spawn call site. Loading an adapter executes third-party code INSIDE the
21
+ * orchestrator process, the same process that holds delegated merge
22
+ * authority under DR-0012. DR-0029 Part 2c's untrusted-project-content
23
+ * boundary does not exist and M4-D-23 recommends it is not built in M4, so
24
+ * the only mitigation available is WHERE the code is resolved from:
25
+ *
26
+ * - the FLEET HOME is owner-controlled and already pins `@tiphys/kernel`
27
+ * exactly, which is what makes it a boundary at all;
28
+ * - the PROJECT CLONE is the thing under review and may contain anything
29
+ * a contributor pushed, including a `node_modules/` directory.
30
+ *
31
+ * So resolution is rooted at `<fleet home>/package.json` rather than at this
32
+ * module or at `process.cwd()`. A plain `await import(specifier)` resolves
33
+ * relative to THIS MODULE's URL, which is the kernel checkout, and
34
+ * `import.meta.resolve(specifier, parent)` silently ignores its second
35
+ * argument unless Node is started with `--experimental-import-meta-resolve`
36
+ * (measured on v26.6.0, 2026-09-17: the parent was ignored and the specifier
37
+ * resolved from the CALLER's file). Both of those are the shape this module
38
+ * refuses to be.
39
+ *
40
+ * THE ROOTING IS NOT BY ITSELF THE PROPERTY, AND THE SENTENCE THAT STOOD HERE
41
+ * SAID IT WAS (CR-B-004, re-raised as CR-F-CRED-002 and CH-002).
42
+ *
43
+ * Until this round this paragraph asserted, without qualification, that
44
+ * "NOTHING here ever consults the project clone". That is true of BARE
45
+ * specifiers, which is the only shape M4-P4 criterion 2's witness exercises:
46
+ * `createRequire` walks the fleet home and its PARENTS looking for
47
+ * `node_modules`, and the project clone sits BELOW the fleet home at
48
+ * `<fleet>/projects/<name>`, so no such walk reaches it. It was FALSE of the
49
+ * module. Measured at `ad2428b`, with a module in the project clone that drops
50
+ * a sentinel on import: a bare specifier did not evaluate it, an ABSOLUTE
51
+ * specifier did, and the fleet-relative `./projects/demo/evil.mjs` did,
52
+ * because a relative path does not walk up looking for `node_modules`, it
53
+ * walks DOWN the tree the rooting chose.
54
+ *
55
+ * The mechanism is a constraint that holds for one INPUT SHAPE, documented as
56
+ * holding for the module. Both halves are now checked against the RESOLVED
57
+ * REAL PATH, which is the only thing that describes where the code actually
58
+ * comes from, rather than against the specifier's spelling:
59
+ *
60
+ * - a resolved path inside `<fleet>/projects/` is refused WHATEVER the
61
+ * specifier's shape, because the project tree is the thing under review;
62
+ * - a PATH-SHAPED specifier (absolute, or beginning `./` or `../`) whose
63
+ * resolved path is outside the fleet home is refused, because an absolute
64
+ * specifier can point anywhere and a relative one can climb out with
65
+ * `../`. A BARE specifier is exempt from the second check only, because
66
+ * `createRequire`'s parent walk legitimately finds a hoisted
67
+ * `node_modules` above the fleet home, and refusing that would break an
68
+ * ordinary install rather than a hazard.
69
+ *
70
+ * `realpathSync` is used for both, so a symlink planted inside the fleet home
71
+ * pointing into the project clone is refused by the same check rather than by
72
+ * a second one.
73
+ *
74
+ * WHAT IS STILL NOT DEFENDED, said here so the next reader does not re-derive
75
+ * it: an operator who types an absolute path INTO the fleet home gets what
76
+ * they typed, and the fleet home is owner-controlled by assumption. This moves
77
+ * the boundary to the fleet home for every specifier shape; it does not defend
78
+ * inside it.
79
+ *
80
+ * WHAT THIS MODULE DOES NOT DEFEND. Once a specifier resolves inside the
81
+ * fleet home, its code runs. A legitimate fleet-home adapter that is later
82
+ * compromised is not covered by anything here, and no criterion of this
83
+ * phase claims otherwise: this phase MOVES the boundary to the fleet home,
84
+ * it does not defend inside it. That residue is M4-D-23's, stated rather
85
+ * than implied.
86
+ */
87
+ /**
88
+ * The built-in adapter's name, and the one name a loaded adapter may not
89
+ * claim (M4-P4 criterion 5).
90
+ *
91
+ * IT LIVES HERE RATHER THAN IN `src/spawn.ts` so that the runtime import
92
+ * edge runs one way only: `src/spawn.ts` imports this value, and this module
93
+ * imports nothing but a TYPE back. A value-level cycle between the launch
94
+ * module and the loader would evaluate one of them inside the other's
95
+ * temporal dead zone the first time an import order changed, which is a
96
+ * failure nothing in the suite would predict.
97
+ *
98
+ * The launch record's `adapter` field (src/spawn.ts:113) is the only thing
99
+ * that ever says what ran. A loaded adapter naming itself after the built-in
100
+ * one makes every later record ambiguous, and the ambiguity is unresolvable
101
+ * after the fact because the record is the only witness. This is the
102
+ * misattribution guard src/gates/schemas/release-record.schema.json:26
103
+ * already instantiates for release records, applied one layer down.
104
+ */
105
+ export const BUILT_IN_ADAPTER_NAME = "subprocess";
106
+ /**
107
+ * The fleet-home `package.json` field that names a default adapter, as a
108
+ * JSON pointer written out in the one place that reads it.
109
+ *
110
+ * `tiphys.adapter` rather than a top-level key: the kernel's own
111
+ * `package.json` already namespaces its configuration under `tiphys`
112
+ * (package.json:52), so a fleet home carrying kernel configuration uses the
113
+ * same namespace rather than inventing a second convention.
114
+ */
115
+ export const FLEET_ADAPTER_FIELD = "tiphys.adapter";
116
+ /**
117
+ * The specifier the fleet home declares as its default, if any.
118
+ *
119
+ * READ THROUGH `readRegularFileIfPresent`, never `readFileSync`: the fleet
120
+ * home is a directory this process does not own the contents of, and a FIFO
121
+ * at `package.json` would hang the spawn rather than refuse it. That is the
122
+ * hazard CR-520 records for `meta.json`, one path along.
123
+ *
124
+ * A fleet home with no `package.json`, no `tiphys` object, or no `adapter`
125
+ * key declares NOTHING, which is a different fact from declaring something
126
+ * unusable: the first yields `undefined` and the built-in default, the
127
+ * second is a refusal naming the field.
128
+ */
129
+ export function fleetAdapterSpecifier(fleet) {
130
+ const read = readRegularFileIfPresent(fleet.packageJsonPath);
131
+ if (read.kind === "absent") {
132
+ return { ok: true, specifier: undefined };
133
+ }
134
+ if (read.kind === "refused") {
135
+ return {
136
+ ok: false,
137
+ reason: `the fleet home package.json could not be read to find a default ` +
138
+ `adapter (${read.reason})`,
139
+ };
140
+ }
141
+ let parsed;
142
+ try {
143
+ parsed = JSON.parse(read.body);
144
+ }
145
+ catch (error) {
146
+ return {
147
+ ok: false,
148
+ reason: `the fleet home package.json ${fleet.packageJsonPath} does not parse as ` +
149
+ `JSON (${singleLine(error.message)}), so the ` +
150
+ `${FLEET_ADAPTER_FIELD} field cannot be read`,
151
+ };
152
+ }
153
+ if (typeof parsed !== "object" || parsed === null) {
154
+ return {
155
+ ok: false,
156
+ reason: `the fleet home package.json ${fleet.packageJsonPath} does not parse as ` +
157
+ `an object, so the ${FLEET_ADAPTER_FIELD} field cannot be read`,
158
+ };
159
+ }
160
+ const tiphys = parsed.tiphys;
161
+ if (tiphys === undefined) {
162
+ return { ok: true, specifier: undefined };
163
+ }
164
+ if (typeof tiphys !== "object" || tiphys === null || Array.isArray(tiphys)) {
165
+ return {
166
+ ok: false,
167
+ reason: `the fleet home package.json ${fleet.packageJsonPath} declares tiphys as ` +
168
+ `${Array.isArray(tiphys) ? "an array" : typeof tiphys}, and ` +
169
+ `${FLEET_ADAPTER_FIELD} can only be read from an object`,
170
+ };
171
+ }
172
+ const adapter = tiphys.adapter;
173
+ if (adapter === undefined) {
174
+ return { ok: true, specifier: undefined };
175
+ }
176
+ if (typeof adapter !== "string" || adapter.trim() === "") {
177
+ return {
178
+ ok: false,
179
+ reason: `the fleet home package.json ${fleet.packageJsonPath} declares ` +
180
+ `${FLEET_ADAPTER_FIELD} as ${typeof adapter === "string" ? "an empty string" : typeof adapter}, ` +
181
+ `and a default adapter must be a non-empty module specifier`,
182
+ };
183
+ }
184
+ return { ok: true, specifier: adapter };
185
+ }
186
+ /**
187
+ * Resolve `specifier` with Node module resolution ROOTED AT THE FLEET HOME,
188
+ * then evaluate it and check that what came back is an adapter.
189
+ *
190
+ * `createRequire(<fleet home>/package.json)` is the rooting. Every lookup it
191
+ * performs starts at the fleet home and walks its PARENTS, which is ordinary
192
+ * Node resolution with a base this process chose; the project clone lives
193
+ * BELOW the fleet home (`<fleet>/projects/<name>`), so no walk from the
194
+ * fleet home can ever reach its `node_modules`. A relative specifier
195
+ * (`./adapters/mine.js`) is likewise resolved against the fleet home rather
196
+ * than against the current working directory.
197
+ *
198
+ * THE RESOLVED PATH IS THEN CHECKED FOR CONTAINMENT (CR-F-CRED-002, CH-002).
199
+ * `requireFromFleet.resolve` is where the rooting happens and it is NOT where
200
+ * the boundary is enforced, because a path-shaped specifier is not subject to
201
+ * the walk the rooting performs. `refuseResolvedAdapterPath` below is the
202
+ * enforcement and it runs BEFORE the `import`, so a refused module is never
203
+ * evaluated; see the module comment for the two rules and for what they do
204
+ * not cover.
205
+ *
206
+ * THE CONDITION SET IS `require`, AND THAT IS A REAL LIMITATION RATHER THAN
207
+ * AN OVERSIGHT. `require.resolve` applies an `exports` map under the
208
+ * `require` condition, so a package whose map offers ONLY an `import`
209
+ * condition fails to resolve here with ERR_PACKAGE_PATH_NOT_EXPORTED
210
+ * (measured, node v26.6.0, 2026-09-17). Resolution succeeding says nothing
211
+ * about the module SYSTEM: the resolved path is then imported as an ES
212
+ * module through a file URL, so an ESM adapter loads correctly as long as its
213
+ * `exports` map offers a `require` or `default` condition, or a bare string.
214
+ * The refusal below says so, because an adapter author reading
215
+ * "cannot be resolved" with no further detail would go looking in the wrong
216
+ * place. The alternative, a parameterised ESM resolver, does not exist in
217
+ * stable Node; see the module docs above for the measurement.
218
+ */
219
+ /**
220
+ * Is `specifier` a PATH rather than a package name? Node's own rule, and it is
221
+ * spelled out rather than approximated: a specifier is relative when it begins
222
+ * `./` or `../` (or their platform separator forms), and absolute when
223
+ * `isAbsolute` says so. Everything else is bare and reaches the `node_modules`
224
+ * walk.
225
+ */
226
+ function isPathShapedSpecifier(specifier) {
227
+ if (isAbsolute(specifier)) {
228
+ return true;
229
+ }
230
+ for (const prefix of ["./", "../", `.${sep}`, `..${sep}`]) {
231
+ if (specifier.startsWith(prefix)) {
232
+ return true;
233
+ }
234
+ }
235
+ return false;
236
+ }
237
+ /**
238
+ * Resolve a path to its real location, or return it unchanged when it cannot
239
+ * be resolved. An unresolvable path is NOT treated as safe: it is returned as
240
+ * it stands and the containment rules below judge it, so the failure mode of
241
+ * this helper is a refusal rather than a pass.
242
+ */
243
+ function realPathOrItself(path) {
244
+ try {
245
+ return realpathSync(path);
246
+ }
247
+ catch {
248
+ return path;
249
+ }
250
+ }
251
+ /** Whether `path` is `root` itself or sits underneath it. */
252
+ function isInside(root, path) {
253
+ if (path === root) {
254
+ return true;
255
+ }
256
+ const rel = relative(root, path);
257
+ return rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
258
+ }
259
+ /**
260
+ * THE CONTAINMENT RULES, applied to the RESOLVED REAL PATH (CR-F-CRED-002,
261
+ * CH-002). Exported so a test can drive both rules directly without staging a
262
+ * module on disk for every member; `loadAdapter` is the only caller in `src/`.
263
+ *
264
+ * Returns a refusal reason, or undefined to allow. Both rules name the
265
+ * resolved path, because an operator reading the refusal has to be able to see
266
+ * WHERE the specifier landed, which is precisely the thing the specifier's own
267
+ * spelling hides.
268
+ */
269
+ export function refuseResolvedAdapterPath(fleet, specifier, origin, resolved) {
270
+ const realResolved = realPathOrItself(resolved);
271
+ const realFleetRoot = realPathOrItself(fleet.root);
272
+ const realProjectsDir = realPathOrItself(fleet.projectsDir === "" ? join(realFleetRoot, "projects") : fleet.projectsDir);
273
+ if (isInside(realProjectsDir, realResolved)) {
274
+ return (`the adapter ${specifier} (${origin}) resolves to ${realResolved}, which is ` +
275
+ `inside the project tree ${realProjectsDir}; a project clone is the code ` +
276
+ `under review and loading an adapter runs it inside the orchestrator ` +
277
+ `process, so no specifier of any shape may resolve there`);
278
+ }
279
+ if (isPathShapedSpecifier(specifier) && !isInside(realFleetRoot, realResolved)) {
280
+ return (`the adapter ${specifier} (${origin}) is a path-shaped specifier resolving ` +
281
+ `to ${realResolved}, which is outside the fleet home ${realFleetRoot}; the ` +
282
+ `fleet home is the only resolution root the kernel trusts for adapters, and ` +
283
+ `a path specifier is not constrained by the rooting the way a package name is`);
284
+ }
285
+ return undefined;
286
+ }
287
+ export async function loadAdapter(fleet, specifier, origin) {
288
+ const requireFromFleet = createRequire(fleet.packageJsonPath);
289
+ let resolved;
290
+ try {
291
+ resolved = requireFromFleet.resolve(specifier);
292
+ }
293
+ catch (error) {
294
+ const code = error.code ?? "";
295
+ return {
296
+ ok: false,
297
+ reason: `the adapter ${specifier} (${origin}) could not be resolved from the ` +
298
+ `fleet home ${fleet.root}, which is the ONLY resolution root the kernel ` +
299
+ `uses for adapters: install it in the fleet home, and note that an ` +
300
+ `exports map offering only an import condition does not resolve here ` +
301
+ `(${code === "" ? singleLine(String(error)) : code})`,
302
+ };
303
+ }
304
+ const containmentRefusal = refuseResolvedAdapterPath(fleet, specifier, origin, resolved);
305
+ if (containmentRefusal !== undefined) {
306
+ return { ok: false, reason: containmentRefusal };
307
+ }
308
+ let module;
309
+ try {
310
+ module = await import(__rewriteRelativeImportExtension(pathToFileURL(resolved).href));
311
+ }
312
+ catch (error) {
313
+ return {
314
+ ok: false,
315
+ reason: `the adapter ${specifier} (${origin}) resolved to ${resolved} but could ` +
316
+ `not be evaluated: ${singleLine(String(error))}`,
317
+ };
318
+ }
319
+ return checkAdapterShape(module, specifier, origin, resolved);
320
+ }
321
+ /**
322
+ * IS THIS AN ADAPTER (M4-P4 criterion 4)?
323
+ *
324
+ * The three shapes the criterion names are three ways a module can be
325
+ * IMPORTABLE and not be an adapter, and each of them is silent at the import
326
+ * itself: a module with no default export, a default export with no `launch`,
327
+ * and a `launch` that is not callable. Left unchecked, the first two surface
328
+ * as `TypeError: adapter.launch is not a function` thrown out of `spawnTask`
329
+ * AFTER pool create has made a worktree, a branch and a pool record, which is
330
+ * the same "refusal that arrives as a crash, after creation" shape M4-P3's
331
+ * requirement checks exist to prevent (src/spawn.ts's checkAdapterRequirements).
332
+ *
333
+ * A FOURTH CHECK IS HERE THAT THE CRITERION DOES NOT NAME, and it is declared
334
+ * rather than smuggled: the adapter's `name` must be a non-empty string.
335
+ * Criterion 5 refuses one particular name, and `undefined` written into the
336
+ * launch record's `adapter` field would defeat the whole point of that
337
+ * criterion by making the record say nothing at all. A guard on the value of
338
+ * a field that may be absent is a guard that cannot go red for the absent
339
+ * case.
340
+ *
341
+ * Every refusal names the SPECIFIER and the member, because an operator
342
+ * reading it has a module to go and fix and needs to know which one.
343
+ */
344
+ export function checkAdapterShape(module, specifier, origin, resolved) {
345
+ const where = `the adapter ${specifier} (${origin}), resolved to ${resolved},`;
346
+ if (typeof module !== "object" || module === null) {
347
+ return { ok: false, reason: `${where} did not evaluate to a module object` };
348
+ }
349
+ if (!Object.hasOwn(module, "default")) {
350
+ return {
351
+ ok: false,
352
+ reason: `${where} has no default export; an adapter module exports its ` +
353
+ `ExecutorAdapter as the default export`,
354
+ };
355
+ }
356
+ const candidate = module.default;
357
+ if (typeof candidate !== "object" || candidate === null) {
358
+ return {
359
+ ok: false,
360
+ reason: `${where} exports a default that is ${candidate === null ? "null" : typeof candidate}, ` +
361
+ `not an object with a launch member`,
362
+ };
363
+ }
364
+ const launch = candidate.launch;
365
+ if (launch === undefined) {
366
+ return {
367
+ ok: false,
368
+ reason: `${where} exports a default with no launch member; ExecutorAdapter ` +
369
+ `requires launch(request)`,
370
+ };
371
+ }
372
+ if (typeof launch !== "function") {
373
+ return {
374
+ ok: false,
375
+ reason: `${where} exports a default whose launch member is ${typeof launch}, ` +
376
+ `not a function; ExecutorAdapter requires launch(request)`,
377
+ };
378
+ }
379
+ const name = candidate.name;
380
+ if (typeof name !== "string" || name.trim() === "") {
381
+ return {
382
+ ok: false,
383
+ reason: `${where} exports a default with no usable name member; the launch ` +
384
+ `record's adapter field is the only thing that says what ran, so an ` +
385
+ `adapter must name itself with a non-empty string`,
386
+ };
387
+ }
388
+ if (name === BUILT_IN_ADAPTER_NAME) {
389
+ return {
390
+ ok: false,
391
+ reason: `${where} names itself ${BUILT_IN_ADAPTER_NAME}, which is the built-in ` +
392
+ `adapter's name; a loaded adapter may not claim it, because the launch ` +
393
+ `record's adapter field would then no longer say which one ran`,
394
+ };
395
+ }
396
+ return { ok: true, adapter: candidate };
397
+ }
398
+ /**
399
+ * WHICH ADAPTER THIS SPAWN USES, AND WHY (M4-P4 criteria 1 and 6).
400
+ *
401
+ * WHICH SOURCE WINS, and it is explicit in the record rather than silent:
402
+ * the `--adapter` FLAG outranks the fleet-home default, and the fleet-home
403
+ * default outranks the built-in one.
404
+ *
405
+ * THE WORDING IS DELIBERATE AND IS NOT A STYLE CHOICE. The word this sentence
406
+ * would naturally use is claimed by four rows of
407
+ * delivery/plan/cutover/retirement-inventory.json, which assert that no kernel
408
+ * artifact states an authority order among charter, plan and decision records
409
+ * and enumerate BY NAME the `src/` files allowed to carry the token. A new
410
+ * file carrying it reddens `scripts/check-retirement-inventory.mjs`, correctly,
411
+ * because the checker cannot tell a benign use from a refuting one without a
412
+ * human reading it. This use is benign and incidental, so the token is not
413
+ * used here rather than the rows being widened from a phase that does not own
414
+ * that document. A flag is a per-spawn instruction and
415
+ * a fleet-home field is a standing one, so the narrower statement wins; the
416
+ * opposite order would make the flag unusable on any fleet that declared a
417
+ * default.
418
+ *
419
+ * `undefined` for the adapter means USE THE BUILT-IN ONE, and it is returned
420
+ * as `undefined` rather than as `subprocessAdapter` so that this module
421
+ * imports nothing but a type from `src/spawn.ts`. See BUILT_IN_ADAPTER_NAME
422
+ * above for why the import edge runs one way.
423
+ */
424
+ export async function selectAdapter(fleet, flagSpecifier) {
425
+ if (flagSpecifier !== undefined) {
426
+ const origin = "--adapter";
427
+ const loaded = await loadAdapter(fleet, flagSpecifier, origin);
428
+ return loaded.ok ? { ok: true, adapter: loaded.adapter, origin } : loaded;
429
+ }
430
+ const declared = fleetAdapterSpecifier(fleet);
431
+ if (!declared.ok) {
432
+ return declared;
433
+ }
434
+ if (declared.specifier === undefined) {
435
+ return { ok: true, adapter: undefined, origin: "the built-in default" };
436
+ }
437
+ const origin = `the fleet home ${FLEET_ADAPTER_FIELD} field`;
438
+ const loaded = await loadAdapter(fleet, declared.specifier, origin);
439
+ return loaded.ok ? { ok: true, adapter: loaded.adapter, origin } : loaded;
440
+ }
@@ -0,0 +1,32 @@
1
+ import type { Fleet } from "./fleet.ts";
2
+ /**
3
+ * Brief assembly (kernel plan v1, M1-P4 step 2; R-033b, R-083b).
4
+ *
5
+ * The brief for a task is written once, at spawn, to tasks/<id>/brief.md:
6
+ * the operator's brief file verbatim, followed by the fleet's
7
+ * environment-warnings file verbatim when the fleet has one. The
8
+ * warnings file is <fleet>/warnings.md (fleet root, tracked content, not
9
+ * the ignored state/ area). When no warnings file exists, brief.md is
10
+ * byte-identical to the brief file.
11
+ *
12
+ * Verbatim means verbatim: no heading, no banner, no rewriting. A brief
13
+ * is the durable instruction an implementer is judged against, and a
14
+ * kernel that edits it makes the audit trail lie. The only byte this
15
+ * module may add is a single newline between the two documents when the
16
+ * brief does not already end in one.
17
+ */
18
+ /** The fleet's environment-warnings file, appended to every brief. */
19
+ export declare const WARNINGS_FILE = "warnings.md";
20
+ export declare function warningsPath(fleet: Fleet): string;
21
+ export type BriefResult = {
22
+ ok: true;
23
+ value: string;
24
+ } | {
25
+ ok: false;
26
+ reason: string;
27
+ };
28
+ /**
29
+ * Assemble tasks/<id>/brief.md from briefFile. Returns the written path.
30
+ * The task directory must already exist.
31
+ */
32
+ export declare function assembleBrief(fleet: Fleet, taskId: string, briefFile: string): BriefResult;
@@ -0,0 +1,66 @@
1
+ import { writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { readRegularPathIfPresent, refuseOpenPathForWrite, } from "./fleet.js";
4
+ import { briefPath } from "./task.js";
5
+ /**
6
+ * Brief assembly (kernel plan v1, M1-P4 step 2; R-033b, R-083b).
7
+ *
8
+ * The brief for a task is written once, at spawn, to tasks/<id>/brief.md:
9
+ * the operator's brief file verbatim, followed by the fleet's
10
+ * environment-warnings file verbatim when the fleet has one. The
11
+ * warnings file is <fleet>/warnings.md (fleet root, tracked content, not
12
+ * the ignored state/ area). When no warnings file exists, brief.md is
13
+ * byte-identical to the brief file.
14
+ *
15
+ * Verbatim means verbatim: no heading, no banner, no rewriting. A brief
16
+ * is the durable instruction an implementer is judged against, and a
17
+ * kernel that edits it makes the audit trail lie. The only byte this
18
+ * module may add is a single newline between the two documents when the
19
+ * brief does not already end in one.
20
+ */
21
+ /** The fleet's environment-warnings file, appended to every brief. */
22
+ export const WARNINGS_FILE = "warnings.md";
23
+ export function warningsPath(fleet) {
24
+ return join(fleet.root, WARNINGS_FILE);
25
+ }
26
+ /**
27
+ * Assemble tasks/<id>/brief.md from briefFile. Returns the written path.
28
+ * The task directory must already exist.
29
+ */
30
+ export function assembleBrief(fleet, taskId, briefFile) {
31
+ /* THREE PATHS, NONE OF THEM THIS MODULE'S OWN, AND THE TYPE OF EACH IS
32
+ ESTABLISHED BEFORE IT IS OPENED (T-008's shape in shipped code). Until
33
+ this round all three were bare: `--brief` is named by the CALLER,
34
+ `warnings.md` is fleet content the kernel did not create, and the target
35
+ is a path this module CREATES, which is the write direction and blocks
36
+ on a FIFO exactly as a read does. A named pipe at any of them took
37
+ `tiphys spawn` down forever with zero output; `existsSync` did not help,
38
+ because a FIFO exists. */
39
+ const briefRead = readRegularPathIfPresent(briefFile);
40
+ if (briefRead.kind === "absent") {
41
+ return { ok: false, reason: `cannot read brief file ${briefFile}: it is absent` };
42
+ }
43
+ if (briefRead.kind === "refused") {
44
+ return { ok: false, reason: `cannot read brief file ${briefFile}: ${briefRead.reason}` };
45
+ }
46
+ let content = briefRead.body;
47
+ const warnings = warningsPath(fleet);
48
+ const warningsRead = readRegularPathIfPresent(warnings);
49
+ if (warningsRead.kind === "refused") {
50
+ return {
51
+ ok: false,
52
+ reason: `cannot read fleet warnings file ${warnings}: ${warningsRead.reason}`,
53
+ };
54
+ }
55
+ if (warningsRead.kind === "read") {
56
+ const separator = content === "" || content.endsWith("\n") ? "" : "\n";
57
+ content = `${content}${separator}${warningsRead.body}`;
58
+ }
59
+ const target = briefPath(fleet, taskId);
60
+ const refusal = refuseOpenPathForWrite(target);
61
+ if (refusal !== undefined) {
62
+ return { ok: false, reason: `cannot write brief ${target}: ${refusal}` };
63
+ }
64
+ writeFileSync(target, content);
65
+ return { ok: true, value: target };
66
+ }