@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,504 @@
1
+ import type { Fleet } from "./fleet.ts";
2
+ import { classifyPathEntry as classifyEntry, readRegularPathIfPresent as readRegularFileIfPresent, refuseOpenPathForWrite as refuseOpenForWrite } from "./fleet.ts";
3
+ /**
4
+ * Task state (kernel plan v1, M1-P4 step 1). A task's durable state is
5
+ * <fleet>/tasks/<id>/, holding plain JSON per plan decision D-3:
6
+ *
7
+ * meta.json the task record written by spawn and closed by teardown
8
+ * brief.md the assembled brief (src/brief.ts)
9
+ * turn-end the payload's completion record (src/hooks.ts)
10
+ * executor.json the launch record written by the executor adapter
11
+ * report.md a scout's report, the teardown gate for shape scout
12
+ *
13
+ * PLAN CONSTRAINT C-1 (FM-052, FM-049), binding on every consumer: the
14
+ * ONE current-state authority for a task is meta.json's status plus the
15
+ * turn-end file's recorded exit code. Currency is never derived from the
16
+ * tail of an event or status log, and the turn-end file is a completion
17
+ * NOTIFICATION, not the task's state: a missing turn-end never means
18
+ * success (tuition T-002).
19
+ *
20
+ * The task directory deliberately sits OUTSIDE the worktree (FM-059), so
21
+ * the pool's dirty check never needs an exemption list for the kernel's
22
+ * own injected files. That invariant is absolute: nothing this phase
23
+ * writes may ever land inside <fleet>/worktrees/<id>.
24
+ */
25
+ /**
26
+ * OPEN NOTHING WHOSE TYPE HAS NOT BEEN ESTABLISHED (fix round 4, CR-520).
27
+ *
28
+ * THE MECHANISM AND ITS RULE. Opening a path is not a total operation.
29
+ * open(2) on a named pipe with no peer BLOCKS IN THE KERNEL, for reading
30
+ * and for writing, until a peer appears. A block is not an exception, so
31
+ * no try/catch sees it, no "this function never raises" reasoning touches
32
+ * it, and no error classification reaches it. Every process that reaches
33
+ * such an open stops forever with no output at all.
34
+ *
35
+ * This project has now paid for that mechanism three times in one phase:
36
+ * once at tasks/<id>/meta.json (delta review NEW-2), then on six further
37
+ * paths after the first fix was applied at ONE CALL SITE instead of at the
38
+ * read (CR-520), then on four more this round found by deriving the
39
+ * inventory again rather than inheriting it. The lesson recorded in
40
+ * tuition T-005 is that a rule fixed at a call site does not travel; the
41
+ * rule has to be a property of the operation.
42
+ *
43
+ * So the probe lives HERE, in the readers and in one classifier, and every
44
+ * caller is protected by construction rather than by remembering:
45
+ *
46
+ * - lstat the path (the link itself), then stat (what it resolves to),
47
+ * and open ONLY when that is a regular file;
48
+ * - a directory, FIFO, socket, device node, or a symlink resolving to
49
+ * any of those is classified WITHOUT being opened;
50
+ * - nothing at the path is not an error, because absence is the normal
51
+ * transient shape of most of this kernel's state files.
52
+ *
53
+ * WHY THIS LIVES IN src/task.ts. It is a general filesystem rule and not a
54
+ * task rule, and a dedicated module would be its right home. This module
55
+ * is the lowest one in the import graph that the fix round authorized to
56
+ * touch (src/liveness.ts imports it, src/watcher.ts imports it, and it
57
+ * imports neither), and it already carries one cross-cutting helper for
58
+ * the same reason (runStep, below). Moving both to their own module is
59
+ * recorded as an M2 item rather than done here without authorization.
60
+ *
61
+ * RESIDUAL, stated rather than papered over: the probe and the open are
62
+ * two syscalls, so a path that changes type between them can still be
63
+ * opened as something other than a regular file. Closing that needs
64
+ * open(O_NONBLOCK) followed by fstat, which Node's synchronous fs API
65
+ * does not expose for reads. Nothing in this kernel writes that state, and
66
+ * the window is now the only way to reach the block rather than the
67
+ * default path to it.
68
+ */
69
+ /**
70
+ * ONE IMPLEMENTATION, RE-EXPORTED, AND THAT IS THE DR-0047 SWEEP ROUND 2
71
+ * CONSOLIDATION.
72
+ *
73
+ * The paragraphs above explain WHY the probe exists and they still hold. What
74
+ * changed is where it lives. Until this round there were TWO byte-equivalent
75
+ * copies of it: `classifyEntry`/`readRegularFileIfPresent`/`refuseOpenForWrite`
76
+ * here, and `classifyPathEntry`/`readRegularPathIfPresent`/
77
+ * `refuseOpenPathForWrite` in src/fleet.ts, with identical types, identical
78
+ * bodies and identical sentences. Round 1 created the second deliberately and
79
+ * both implementers flagged it: src/lock.ts and src/exclusion.ts sit BELOW this
80
+ * module in the import graph, so importing from here would have made a cycle.
81
+ *
82
+ * THE DIRECTION THAT REMOVES THE COPY WITHOUT MAKING A CYCLE IS DOWNWARD, AND
83
+ * IT IS THE ONE THE COMMENT ABOVE ALREADY ASKED FOR. src/fleet.ts imports NO
84
+ * local module at all, so it is the leaf; this module already imports
85
+ * `Fleet` from it. The chain is task -> fleet, and lock -> fleet, and nothing
86
+ * points back. The alternative, making lock and exclusion call into here, is
87
+ * the cycle task -> lock -> task and is what round 1 correctly refused.
88
+ *
89
+ * THE NAMES HERE ARE KEPT AS ALIASES rather than the 88 call sites in 31 files
90
+ * being rewritten. The names are the whole reason the rewrite would be
91
+ * expensive and none of the reason the duplication was a defect: what mattered
92
+ * was two bodies that could drift, and there is now one body.
93
+ */
94
+ export type { PathEntryClass as EntryClass, RegularPathRead as RegularRead } from "./fleet.ts";
95
+ export { classifyEntry, readRegularFileIfPresent, refuseOpenForWrite };
96
+ export type TaskShape = "ship" | "scout";
97
+ export type TaskStatus = "open" | "closed";
98
+ /**
99
+ * WHOSE AUTHORITY A PAYLOAD RUNS UNDER (M4-P8 step 2).
100
+ *
101
+ * `orchestrator` is the process that plans, reviews and merges, and is the
102
+ * only payload class the declared credential escape hatch was ever written
103
+ * for (SpawnOptions.allowPrCredentials, M2-P8 criterion 1: "for the
104
+ * orchestrator's own spawns, never for an implementer payload"). `project`
105
+ * is every payload that works inside a project clone.
106
+ *
107
+ * It is DECLARED, never derived. The kernel cannot infer it from a command,
108
+ * a role or a branch without holding a vocabulary for one of those, and a
109
+ * derived answer would be wrong in the one direction that matters.
110
+ */
111
+ export type PayloadClass = "orchestrator" | "project";
112
+ /**
113
+ * One granted allowlist extension, as it is recorded in meta.json.
114
+ *
115
+ * BUILT THROUGH THE ACCESSORS, NEVER BY READING `entry.name` DIRECTLY
116
+ * (CR-B-002, the half of it that is about the record). A bare-string entry
117
+ * has no `name` property, so `{name: entry.name, reason: entry.reason}`
118
+ * produced the literal record `{}`: an operator opening the task directory
119
+ * could see THAT a widening happened and not WHICH name was widened. The
120
+ * record is now built with `extensionName` / `extensionReason`
121
+ * (src/exec/env.ts), which read both forms.
122
+ *
123
+ * `reason` is OPTIONAL HERE AND REQUIRED ON THE AUDITED ROUTE, and the
124
+ * difference is the point rather than an inconsistency. This type describes
125
+ * what was actually recorded; the audited route refuses an entry with no
126
+ * usable reason before this record is built, so an entry reaching meta.json
127
+ * without one came through a seam that does not demand one. Recording the
128
+ * absence AS an absence (the key is simply not present in the JSON) is what
129
+ * keeps it distinguishable from a blank reason. Defaulting it to `""` would
130
+ * substitute a valid-looking value for a missing one, which is the same
131
+ * mechanism as CR-B-002 wearing a record's clothes.
132
+ */
133
+ export interface CredentialExtensionRecord {
134
+ name: string;
135
+ reason?: string;
136
+ }
137
+ /**
138
+ * THE HANDOVER COMPARISON (M4-P8 criterion 6): what the kernel handed the
139
+ * adapter against what actually reached the child.
140
+ *
141
+ * TWO PROPERTIES, AND THE STATUS WORD NAMES WHICH OF THEM WERE CHECKED.
142
+ * That is finding CR-B-001 (clean-room-retro-B-criteria, HIGH) and it is
143
+ * worth stating as a mechanism rather than as one word: `status: "compared"`
144
+ * used to be written after comparing NAME SETS ONLY, while an operator reads
145
+ * "compared" as "the handover was verified". An adapter that keeps the name
146
+ * set byte-identical and merely restores `HOME` and `XDG_CONFIG_HOME` to
147
+ * their real paths defeats the whole M2R-004 defense, because that defense
148
+ * works by REDIRECTING those names rather than by dropping them, and the
149
+ * record then POSITIVELY ASSERTED a clean handover while the child read the
150
+ * real gh credential store. The general shape is a record whose status word
151
+ * is stronger than the check behind it.
152
+ *
153
+ * Both halves of the repair are here. The word is weakened so it can only
154
+ * say what was checked, and the check is strengthened so the pointer VALUES
155
+ * are compared too.
156
+ *
157
+ * compared names AND the five CREDENTIAL_STORE_REDIRECTIONS
158
+ * pointers were both in hand and both compared.
159
+ * names-compared the name sets were compared and NO pointer evidence
160
+ * was available. This is the value the old `compared`
161
+ * should have carried: it is a true statement about one
162
+ * property of two, and it never reads as a clean bill.
163
+ * pointers-compared the pointers were observed in the child and compared,
164
+ * and the adapter reported no name set. Reachable with
165
+ * any adapter written before M4-P8, which reports
166
+ * nothing, running under the current turn-end hook.
167
+ * unreported neither property could be checked: no reported name
168
+ * set and no pointer evidence.
169
+ * not-applicable the spawn ran under the declared escape hatch, so the
170
+ * kernel handed over no environment at all and there is
171
+ * nothing a difference could be measured against.
172
+ *
173
+ * NO VALUE OF ANY VARIABLE IS RECORDED HERE, and the original design's
174
+ * reason for that is untouched and right: a value comparison that WROTE the
175
+ * values would put credential material into a record an operator reads.
176
+ * `changedRedirections` carries NAMES only. The five redirection targets are
177
+ * harness-owned paths inside the task directory and carry no credential
178
+ * material, but recording only the names that differ is strictly less and is
179
+ * enough to act on.
180
+ */
181
+ export interface CredentialHandoverRecord {
182
+ status: "compared" | "names-compared" | "pointers-compared" | "unreported" | "not-applicable";
183
+ /** Names the adapter reported that the kernel did not hand over. */
184
+ added: string[];
185
+ /** Names the kernel handed over that the adapter did not report. */
186
+ removed: string[];
187
+ /**
188
+ * Credential-store pointers whose value where the payload ran is not the
189
+ * harness-owned path the kernel handed over. Names only, never values.
190
+ * Empty when the pointers were compared and agreed, and also empty when
191
+ * there was no pointer evidence, which is why `status` and not this array
192
+ * is what says whether the comparison happened.
193
+ */
194
+ changedRedirections: string[];
195
+ /**
196
+ * WHICH ARTIFACT THE POINTER EVIDENCE WAS READ FROM, absent when there was
197
+ * none. It is a statement about a FILE, not a provenance the kernel
198
+ * verified, and the value used to say otherwise.
199
+ *
200
+ * turn-end-record read from tasks/<id>/turn-end. The kernel generates the
201
+ * hook that normally writes it, and for an adapter that
202
+ * honours the contract those values come from inside the
203
+ * child environment. THE FILE IS ALSO ADAPTER-REACHABLE:
204
+ * the adapter is handed `hookPath` and the record sits
205
+ * beside it, so an adapter that skips the hook and writes
206
+ * the record itself produces a record the kernel cannot
207
+ * tell from the hook's.
208
+ * adapter read from the adapter's launch outcome. The adapter's
209
+ * word about its own behaviour, and always was.
210
+ *
211
+ * THE VALUE WAS `child` UNTIL THE DR-0047 SWEEP FIX ROUND (CR-F-CRED-001,
212
+ * MEDIUM). Measured: an adapter that reverted HOME and XDG_CONFIG_HOME for
213
+ * its payload and wrote the turn-end record itself produced
214
+ * `{"status":"compared","changedRedirections":[],"redirectionSource":"child"}`
215
+ * while the payload could reach a real gh credential store. The word `child`
216
+ * asserted an observation no child had made. Neither value is evidence that
217
+ * the adapter was honest, and the record now says only what it can support.
218
+ * This is CR-B-001's own mechanism, a record whose status word is stronger
219
+ * than the check behind it, recurring one level up inside the same record;
220
+ * see src/hooks.ts for why a nonce does not close it and a rename does.
221
+ */
222
+ redirectionSource?: "turn-end-record" | "adapter";
223
+ }
224
+ /**
225
+ * THE CREDENTIAL DECISION FOR ONE SPAWN (M4-P8 step 6), recorded in the
226
+ * file the kernel already owns rather than in a new one across a new seam.
227
+ *
228
+ * The hazard this phase names is "a credential reaches a project payload
229
+ * and no artifact says so". Every arm of the decision is therefore written
230
+ * down even when it is the boring one: the payload class, whether the
231
+ * scrub ran, every widening with the reason it was granted, the handover
232
+ * comparison, and the refusal if one fired after this record existed.
233
+ */
234
+ export interface TaskCredentialRecord {
235
+ payloadClass: PayloadClass;
236
+ /**
237
+ * `scrubbed` when buildChildEnv constructed the environment, `inherited`
238
+ * when allowPrCredentials handed the parent's environment over unchanged.
239
+ *
240
+ * THIS FIELD RECORDS THE KERNEL'S DECISION AND ITS OWN CONSTRUCTION, NOT
241
+ * WHAT THE CHILD RECEIVED, and it is spelled out because it is the second
242
+ * member of CR-B-001's class found in this record (the derivation is in
243
+ * delivery/work-history/credential-route-fixes.md). "scrubbed" is a strong
244
+ * word: it says buildChildEnv ran, staged the five empty redirect targets
245
+ * and returned an environment, because a failure there is a rollback. It
246
+ * does NOT say the payload ran with that environment, because between this
247
+ * field being written and the payload starting there is an adapter. The
248
+ * field that speaks to what reached the child is `handover`, and it says
249
+ * which of its two properties it checked.
250
+ */
251
+ scrubMode: "scrubbed" | "inherited";
252
+ /** Every granted extension, in the order the caller declared them. */
253
+ extensions: CredentialExtensionRecord[];
254
+ /**
255
+ * ABSENT UNTIL THE LAUNCH HAS REPORTED, and the absence is a fact rather
256
+ * than an omission: this record is written BEFORE the payload starts,
257
+ * which is what makes a failure to write it safe to roll back, and at
258
+ * that moment no adapter has launched anything to compare. It is the same
259
+ * reason `ExecutorRecord` carries no resolved model.
260
+ */
261
+ handover?: CredentialHandoverRecord;
262
+ /**
263
+ * The refusal that fired AFTER this record was written, if one did.
264
+ *
265
+ * The step-5 refusal (the escape hatch asked for on a project payload) is
266
+ * deliberately absent from this field and cannot appear in it: that one
267
+ * refuses before the task directory exists, so there is no meta.json to
268
+ * record it in, and creating one would undo the property that a refused
269
+ * spawn creates nothing.
270
+ */
271
+ refusal?: string;
272
+ }
273
+ export declare const TASK_SHAPES: readonly TaskShape[];
274
+ /**
275
+ * Task meta (the plan's field set, M1-P4 step 1).
276
+ *
277
+ * - id: the task id, a safe path segment (pool's TASK_ID_PATTERN).
278
+ * - project: absolute path of the project clone the worktree came from.
279
+ * - shape: ship or scout; it selects teardown's refusal rules.
280
+ * - branch: the TASK branch (task/<id>), the branch teardown judges for
281
+ * landedness. Note the deliberate naming difference from the pool
282
+ * record, whose "branch" is the project's default branch and whose
283
+ * "branchName" is this one; meta records the branch the task works on.
284
+ * - worktree: absolute path of the task worktree.
285
+ * - baseSha: the fetched base SHA pool create emitted (EXT-F-03).
286
+ * - baseOffline: provenance, COPIED from the pool record's offline field
287
+ * and never recomputed (PR-212). It is true only when the fetch failed
288
+ * and --offline authorized falling back to the last fetched
289
+ * remote-tracking SHA. A spawn that recomputed it from its own flags
290
+ * would report a fetched base as offline whenever a contended fetch
291
+ * succeeded on retry, which is exactly the provenance inversion V-2
292
+ * produced in M1-P3.
293
+ * - status: open at spawn, closed by a successful teardown.
294
+ * - createdAt: ISO-8601 timestamp of the spawn.
295
+ * - credentials: the M4-P8 credential decision for this spawn. OPTIONAL in
296
+ * the type, and the optionality is about READING rather than writing:
297
+ * every spawn from M4-P8 on writes it, and `readTaskMeta` deliberately
298
+ * does not require it, so a record written by an earlier kernel still
299
+ * reads instead of becoming an unreadable task whose worktree nobody can
300
+ * tear down. Consumers that need it check for it.
301
+ */
302
+ export interface TaskMeta {
303
+ id: string;
304
+ project: string;
305
+ shape: TaskShape;
306
+ branch: string;
307
+ worktree: string;
308
+ baseSha: string;
309
+ baseOffline: boolean;
310
+ status: TaskStatus;
311
+ createdAt: string;
312
+ credentials?: TaskCredentialRecord;
313
+ }
314
+ export declare function taskDir(fleet: Fleet, taskId: string): string;
315
+ export declare function metaPath(fleet: Fleet, taskId: string): string;
316
+ export declare function briefPath(fleet: Fleet, taskId: string): string;
317
+ export declare function turnEndPath(fleet: Fleet, taskId: string): string;
318
+ export declare function executorRecordPath(fleet: Fleet, taskId: string): string;
319
+ export declare function reportPath(fleet: Fleet, taskId: string): string;
320
+ /** Serialize meta the way every kernel JSON state file is written. */
321
+ export declare function renderTaskMeta(meta: TaskMeta): string;
322
+ export declare function writeTaskMeta(fleet: Fleet, meta: TaskMeta): void;
323
+ /**
324
+ * THE FOUR WAYS A TASK RECORD FAILS TO READ, KEPT APART (CR-F02, MEDIUM;
325
+ * T-036's mechanism).
326
+ *
327
+ * `absent` is a category that is empty BY OBSERVATION: the kernel looked and
328
+ * there is no record. Every other member is a category that is empty BY
329
+ * CONSTRUCTION: there IS something there and this read could not turn it into
330
+ * a record. Collapsing them makes "nothing here" and "something here I could
331
+ * not read" the same answer, and a task killed mid-write is the ordinary
332
+ * failure this repository keeps meeting, so the two are not the same answer at
333
+ * any caller that decides whether work is in flight.
334
+ */
335
+ export type TaskMetaRead =
336
+ /** A record that read and passed the field check. */
337
+ {
338
+ kind: "read";
339
+ meta: TaskMeta;
340
+ }
341
+ /** Nothing at this path. Empty by observation. */
342
+ | {
343
+ kind: "absent";
344
+ }
345
+ /** Present and not openable as a regular file, with the probe's reason. */
346
+ | {
347
+ kind: "unreadable";
348
+ reason: string;
349
+ }
350
+ /** Present, opened, and not JSON. The truncated-mid-write shape. */
351
+ | {
352
+ kind: "unparsable";
353
+ reason: string;
354
+ }
355
+ /** Present, parsed, and not a task record. Names the first bad field. */
356
+ | {
357
+ kind: "malformed";
358
+ reason: string;
359
+ };
360
+ /**
361
+ * THE ONE READ of a task record, and the one that says WHICH of the four
362
+ * outcomes happened.
363
+ *
364
+ * The type probe is INSIDE this function and not in front of one of its
365
+ * callers (CR-520, CR-521). There is exactly one implementation of "read a
366
+ * task record", every caller of it is protected, and adding a caller
367
+ * cannot reopen the hole: src/teardown.ts reaches this directly, without
368
+ * going through the liveness classifier, and a named pipe here used to
369
+ * hang it forever.
370
+ *
371
+ * WHY THIS IS A SEPARATE FUNCTION FROM `readTaskMeta` RATHER THAN ITS NEW
372
+ * SIGNATURE, declared rather than left to be inferred. Widening the return
373
+ * type of `readTaskMeta` is the shape this repair wants, and it is a
374
+ * compile-time break in four modules that this fix round's file list does not
375
+ * own (src/pool.ts, src/liveness.ts, src/teardown.ts, src/commands/next.ts).
376
+ * The distinction is therefore made AVAILABLE upstream of all of them here,
377
+ * `readTaskMeta` is documented as the deliberate NARROWING of it, and the
378
+ * sites that still collapse are named in the fix round's work history rather
379
+ * than quietly left.
380
+ */
381
+ export declare function classifyTaskMeta(fleet: Fleet, taskId: string): TaskMetaRead;
382
+ /**
383
+ * Read meta.json, or undefined when it is absent, is not a regular file,
384
+ * does not parse, or parses and is not a task record.
385
+ *
386
+ * THIS IS A DELIBERATE NARROWING OF `classifyTaskMeta` AND THE COLLAPSE IS THE
387
+ * WHOLE OF CR-F02. `undefined` answers "is there a readable record" and it
388
+ * cannot answer "is there a task here", because it is returned both when the
389
+ * kernel looked and found nothing and when it found something it could not
390
+ * read. A caller that reports an absence, skips an entry, or decides nothing
391
+ * is in flight must call `classifyTaskMeta` instead; a caller that REFUSES on
392
+ * every one of the four (teardown does) loses nothing by using this.
393
+ */
394
+ export declare function readTaskMeta(fleet: Fleet, taskId: string): TaskMeta | undefined;
395
+ /** Set meta.json status (teardown's last step; C-1's state authority). */
396
+ export declare function setTaskStatus(fleet: Fleet, meta: TaskMeta, status: TaskStatus): void;
397
+ export type GuardResult = {
398
+ ok: true;
399
+ } | {
400
+ ok: false;
401
+ reason: string;
402
+ };
403
+ /**
404
+ * Holdership guard for the task-mutating commands (PR-203), shared by
405
+ * spawn and teardown so there is exactly one implementation of the rule.
406
+ * It lives here rather than in a guard module of its own because this
407
+ * phase's files-to-touch list has no shared-guard file and the rule is a
408
+ * precondition of every task mutation.
409
+ *
410
+ * The transport is M1-P3's, unchanged and not re-invented: lock acquire
411
+ * PRINTS the opaque holderId, the operator carries it, and here it
412
+ * arrives as the TIPHYS_HOLDER_ID environment variable. No second
413
+ * identity mechanism exists, and nothing about the holder is derived
414
+ * from the running program (plan constraint C-2): this reads the lease
415
+ * FILE and nothing else.
416
+ *
417
+ * With no lease file present the command proceeds (the plan's M1 test
418
+ * contexts). A lease that exists but is unreadable, expired, or held by
419
+ * a different holder refuses: fail closed, because every one of those
420
+ * states means this caller cannot prove it is the one orchestrator.
421
+ */
422
+ export declare function checkHoldership(fleet: Fleet): GuardResult;
423
+ /** True when the task directory already exists (spawn's rollback scope). */
424
+ export declare function taskDirExists(fleet: Fleet, taskId: string): boolean;
425
+ /**
426
+ * True when tasks/<id>/ already holds a previous incarnation of the task
427
+ * id, which spawn refuses (CR-301).
428
+ *
429
+ * The task directory is the DURABLE record: teardown removes the worktree
430
+ * and the pool record but deliberately leaves tasks/<id>/ behind, so the
431
+ * id is free from the pool's point of view and occupied from the task
432
+ * state's. Spawning into it would overwrite the closed task's records,
433
+ * hand the launch-failure rollback files it did not create, and leave the
434
+ * previous incarnation's turn-end file readable while the new
435
+ * incarnation's meta says open, which is a completion that did not happen
436
+ * sitting under the C-1 state authority.
437
+ *
438
+ * A path that exists but is not a directory counts as occupied too: it is
439
+ * not a state this kernel may write into, and refusing costs the operator
440
+ * one rename while guessing could cost the record.
441
+ */
442
+ export declare function taskDirOccupied(fleet: Fleet, taskId: string): boolean;
443
+ /**
444
+ * Collapse captured git or error output to ONE line (CR-303). Plan step 5 ends
445
+ * "every refusal is exit nonzero plus a single reason line", and git's
446
+ * own stderr is routinely five lines, so any interpolation of it must be
447
+ * flattened rather than trusted to be short. The M1-P6 harness reads
448
+ * these reason lines as evidence.
449
+ */
450
+ export declare function singleLine(text: string): string;
451
+ export type StepResult<T> = {
452
+ ok: true;
453
+ value: T;
454
+ } | {
455
+ ok: false;
456
+ reason: string;
457
+ };
458
+ /**
459
+ * Run one step that may signal failure by THROWING, and fold a raised
460
+ * error into the same ok/reason shape every other step in spawn and
461
+ * teardown returns (F-1, F-2).
462
+ *
463
+ * This exists because the modules were written as a result type end to
464
+ * end while the Node fs calls underneath them are not: writeFileSync and
465
+ * mkdirSync raise. Every returned failure was handled correctly and every
466
+ * THROWN one walked straight past the handler, out of the command, and
467
+ * onto stderr as a stack trace, taking spawn's rollback and teardown's
468
+ * state update with it. Wrapping is therefore not defensive decoration
469
+ * for a state M1 never reaches: it is the difference between a rollback
470
+ * that runs and an orphaned worktree, and between a task marked closed
471
+ * and a meta.json that lies about a worktree that is already gone.
472
+ *
473
+ * It never swallows: the caller still gets a reason naming the step, and
474
+ * still decides whether to roll back, report a partial failure, or refuse.
475
+ */
476
+ export declare function runStep<T>(what: string, step: () => T): StepResult<T>;
477
+ /**
478
+ * runStep's shape for a step that returns a PROMISE (M4-P2 step 2).
479
+ *
480
+ * This is a PROMOTION, not a new function. It lived module-private in
481
+ * src/watcher.ts, where its own comment said "src/task.ts covers the sync
482
+ * one"; spawn's launch call site needs the same shape now that
483
+ * `ExecutorAdapter.launch` returns a promise, and T-005's one-mechanism
484
+ * rule makes copying it a third time the wrong answer. The promotion was
485
+ * measured before it was made: the two functions were run side by side over
486
+ * six `what`/thrown-error pairs covering an ordinary Error, an Error with an
487
+ * empty message, a thrown string, a thrown object with a custom toString, an
488
+ * Error subclass and a multi-line pair, and their reason strings were
489
+ * compared as buffers. All six were byte-identical, so no watcher message
490
+ * changes on any of those six. The capture is in the M4-P2 work history
491
+ * under `delivery/work-history/m4-p2.md`.
492
+ *
493
+ * WHY A SEPARATE FUNCTION RATHER THAN WIDENING runStep. `runStep` returns
494
+ * `{ok: true, value}` the instant its callback returns, and a callback that
495
+ * returns a promise returns one IMMEDIATELY. So `runStep` over an async
496
+ * step is not merely imprecise: its `ok: true` is a claim that the step
497
+ * SUCCEEDED when nothing has run yet, and a later rejection escapes the
498
+ * result type entirely as an unhandled rejection. That is the exact defect
499
+ * this phase's criterion 4 witness reddens against.
500
+ *
501
+ * It never swallows, on the same terms as the sync one: the caller gets a
502
+ * reason naming the step and still decides what to do about it.
503
+ */
504
+ export declare function runStepAsync<T>(what: string, step: () => Promise<T>): Promise<StepResult<T>>;