@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,389 @@
1
+ /**
2
+ * THE SHARED EXCLUSION REGISTER (kernel plan M4, M4-P21).
3
+ *
4
+ * `src/lock.ts` states its own exclusion domain honestly at src/lock.ts:63:
5
+ * the lease excludes within ONE filesystem and ONE clock. Two environments
6
+ * that clone one fleet remote each get their own `state/orchestrator.lock`
7
+ * and BOTH acquire it, because `state/` is gitignored (src/fleet.ts:28) so
8
+ * the lease artifact never travels. M4-P20 measured that dangerous state and
9
+ * committed the captures; this module is the second exclusion layer that
10
+ * closes it.
11
+ *
12
+ * WHAT THE REGISTER IS. A compare-and-swap register on a dedicated git ref
13
+ * of the fleet's shared remote. The ref's value is a commit whose only file
14
+ * is `lease.json`; a write is `git push --force-with-lease=<ref>:<exact sha>`
15
+ * and the remote decides the race. M4-D-11 was PROTOTYPE-BLOCKED and M4-P20's
16
+ * probe closed it (delivery/verification/cross-environment-exclusion-probe.md:1).
17
+ * Three of that probe's findings are load-bearing here and are implemented
18
+ * rather than remembered:
19
+ *
20
+ * 1. ONLY `refs/heads/*` IS PUSHABLE. Tags, notes and custom namespaces are
21
+ * refused with HTTP 403 (CLAUDE.md standing warning 14, re-measured for
22
+ * this phase). So the "dedicated ref" is a dedicated BRANCH and the
23
+ * default is `refs/heads/tiphys/lease`, which is visible in branch
24
+ * listings and subject to any `refs/heads/**` ruleset.
25
+ * 2. THE EXPECTATION MUST BE AN EXPLICIT SHA. The bare `--force-with-lease`
26
+ * form takes its expectation from the remote-tracking ref, so a routine
27
+ * fetch re-arms it; the probe measured it CLOBBERING a live holder with
28
+ * exit 0. `casWrite` below never emits the bare form.
29
+ * 3. NONZERO DOES NOT MEAN "I LOST". A transport failure exits 1 too, so
30
+ * the result has THREE states and the indeterminate one is resolved by
31
+ * re-reading the register rather than assumed either way.
32
+ *
33
+ * WHY THE STALENESS SIGNAL IS A COUNTER AND NOT A CLOCK (M4-P21 criterion 6).
34
+ * Two environments bring two clocks and `isExpired` compares a lease
35
+ * timestamp against the local one (src/lock.ts:171). Comparing one
36
+ * environment's wall clock against another's is exactly the measurement this
37
+ * layer must not make. So the register carries a MONOTONIC FENCING COUNTER
38
+ * that every write increments, and a holder is judged stale only when that
39
+ * counter has not moved across a duration measured entirely on the OBSERVING
40
+ * environment's own clock (two readings of one clock, never one reading of
41
+ * two). Where the register is reachable the counter decides and the command
42
+ * says `signal=counter`. Where it is not reachable there is no counter to
43
+ * read, only the local clock, and a clock is not a cross-environment signal:
44
+ * the command says `signal=clock` and REFUSES rather than falling back to
45
+ * local-only exclusion, which is the vacuous green this layer exists to
46
+ * prevent (criterion 7).
47
+ *
48
+ * C-2 (binding): nothing here reads a run identifier of a running program,
49
+ * probes liveness, sends a signal, or reads the kernel's virtual filesystem.
50
+ * Environment identity is a random id written once to a TRACKED fleet file
51
+ * (criterion 3), so it survives a reclaim and travels with a clone of the
52
+ * fleet; exclusion is decided by the register's counter and by git's own
53
+ * compare-and-swap verdict, never by anything about a machine.
54
+ *
55
+ * DECLARATION, NOT INFERENCE (criterion 1). The layer is entered only when
56
+ * the fleet home's own `package.json` declares it. With the field absent
57
+ * every function here returns "absent" before any subprocess is spawned, so
58
+ * a fleet that cannot reach a remote is not forced to switch the layer off
59
+ * globally and today's behaviour is unchanged for everyone else.
60
+ */
61
+ /** The dotted path of the opt-in field inside the fleet home's package.json. */
62
+ export declare const SHARED_EXCLUSION_FIELD = "tiphys.sharedExclusion";
63
+ /** The remote a declaration defaults to. */
64
+ export declare const DEFAULT_SHARED_REMOTE = "origin";
65
+ /**
66
+ * The register ref a declaration defaults to. A BRANCH, deliberately: see
67
+ * finding 1 in this file's header. The plan's prose names `refs/tiphys/lease`
68
+ * and that namespace is refused by the shared remote this kernel is built
69
+ * against, so the default is the pushable form and the ref stays
70
+ * configurable for a remote with different rules.
71
+ */
72
+ export declare const DEFAULT_SHARED_REF = "refs/heads/tiphys/lease";
73
+ /** The file inside the register commit that carries the lease document. */
74
+ export declare const REGISTER_DOCUMENT_NAME = "lease.json";
75
+ /**
76
+ * The TRACKED fleet file carrying this environment's identity (criterion 3).
77
+ * It sits at the fleet root, outside the gitignored set at src/fleet.ts:28,
78
+ * so an environment that commits it keeps its identity across a reclaim and
79
+ * a clone of that commit reads the same id.
80
+ */
81
+ export declare const ENVIRONMENT_ID_FILE = "tiphys-environment.json";
82
+ /**
83
+ * Where this environment records what it last saw in the register. It lives
84
+ * under the gitignored `state/` prefix ON PURPOSE: it is a measurement taken
85
+ * on THIS environment's clock and it must never travel, or the duration it
86
+ * carries would be compared against a clock that did not produce it.
87
+ */
88
+ export declare const OBSERVATION_FILE: string;
89
+ /** How long the counter must stand still before a holder is judged stale. */
90
+ export declare const DEFAULT_STALE_WINDOW_SECONDS = 900;
91
+ export interface SharedExclusionConfig {
92
+ remote: string;
93
+ ref: string;
94
+ staleWindowSeconds: number;
95
+ }
96
+ export type SharedExclusionDeclaration = {
97
+ kind: "absent";
98
+ } | {
99
+ kind: "declared";
100
+ config: SharedExclusionConfig;
101
+ } | {
102
+ kind: "invalid";
103
+ reason: string;
104
+ };
105
+ /** The register's value: one lease document per register commit. */
106
+ export interface SharedLeaseDocument {
107
+ state: "held" | "free";
108
+ envId: string;
109
+ counter: number;
110
+ acquiredAt: string;
111
+ expiresAt: string;
112
+ durationSeconds: number;
113
+ ref: string;
114
+ }
115
+ export type RegisterRead = {
116
+ kind: "absent";
117
+ } | {
118
+ kind: "present";
119
+ sha: string;
120
+ document: SharedLeaseDocument;
121
+ } | {
122
+ kind: "corrupt";
123
+ sha: string;
124
+ reason: string;
125
+ } | {
126
+ kind: "unreachable";
127
+ reason: string;
128
+ };
129
+ export type CasOutcome = {
130
+ kind: "won";
131
+ sha: string;
132
+ } | {
133
+ kind: "lost";
134
+ reason: string;
135
+ } | {
136
+ kind: "indeterminate";
137
+ reason: string;
138
+ };
139
+ /** What this environment last saw, timed on this environment's own clock. */
140
+ export interface RegisterObservation {
141
+ sha: string;
142
+ counter: number;
143
+ firstSeenMs: number;
144
+ }
145
+ /** Which of the two signals decided a shared-exclusion verdict. */
146
+ export type ExclusionSignal = "counter" | "clock";
147
+ export type SharedPreflight = {
148
+ kind: "proceed";
149
+ signal: ExclusionSignal;
150
+ expectedSha: string;
151
+ nextCounter: number;
152
+ takingOver: boolean;
153
+ line: string;
154
+ /** The document the register currently holds, when it holds one. */
155
+ current?: SharedLeaseDocument;
156
+ } | {
157
+ kind: "refused";
158
+ signal: ExclusionSignal;
159
+ line: string;
160
+ };
161
+ /**
162
+ * Read the fleet home's declaration. Absent means the layer is off and no
163
+ * subprocess is spawned anywhere below; `false` is the same answer written
164
+ * explicitly. A field that is present and unreadable is INVALID rather than
165
+ * absent, because silently treating a typo as "off" is the shape that makes
166
+ * a guard green everywhere and protective nowhere.
167
+ */
168
+ export declare function readSharedExclusion(fleetRoot: string): SharedExclusionDeclaration;
169
+ /** The fleet root a lock path belongs to: <root>/state/orchestrator.lock. */
170
+ export declare function fleetRootForLockPath(lockPath: string): string;
171
+ export interface EnvironmentIdentity {
172
+ envId: string;
173
+ /** True when this call generated the id rather than reading one. */
174
+ generated: boolean;
175
+ path: string;
176
+ }
177
+ /**
178
+ * Read the environment id, or generate one and write it. The id is random
179
+ * and is generated EXACTLY ONCE per fleet home: every later call reads the
180
+ * file. Nothing about the machine enters it.
181
+ */
182
+ export declare function ensureEnvironmentId(fleetRoot: string): EnvironmentIdentity;
183
+ /**
184
+ * THE COMMAND-SCOPED IDENTITY THE REGISTER COMMIT IS WRITTEN UNDER.
185
+ *
186
+ * `git commit-tree` REFUSES without an author, and CI runners carry no git
187
+ * identity (CLAUDE.md standing warning 5), so a register write that relied
188
+ * on ambient configuration would work on a developer's machine and fail on
189
+ * every runner. These are the same two strings `tiphys init` already uses
190
+ * for the fleet bootstrap commit (EXT-F-02 option B): set as command-scoped
191
+ * environment variables on the invocation only, never written to user or
192
+ * global git configuration. They are repeated here rather than imported
193
+ * because `src/commands/init.ts` imports THIS module for the opt-in field,
194
+ * and a test pins the two copies equal so a drift reddens instead of
195
+ * surfacing as a runner-only failure.
196
+ */
197
+ export declare const REGISTER_IDENTITY_NAME = "Tiphys Fleet";
198
+ export declare const REGISTER_IDENTITY_EMAIL = "fleet@tiphys.invalid";
199
+ /**
200
+ * The line of a git stderr block carrying git's OWN rejection marker.
201
+ *
202
+ * NOT "the first line", and M4-P20 paid for the difference: with
203
+ * `push.negotiate` true, git 2.43.0 emits a negotiation warning as the first
204
+ * stderr line of EVERY file-transport push, accepted and refused alike, so a
205
+ * signature taken from the first line cannot tell accept from refuse. The
206
+ * marker below is git's own text, never this module's, so the refusal
207
+ * signature stays captured rather than hand-written (T-003).
208
+ */
209
+ export declare function rejectionLine(stderr: string): string | undefined;
210
+ export declare function renderRegisterDocument(document: SharedLeaseDocument): string;
211
+ /**
212
+ * Read the register. `ls-remote` establishes the value the compare-and-swap
213
+ * will be armed against, then the object is fetched so the document can be
214
+ * read. A failure at either step is UNREACHABLE, never "absent": the two
215
+ * must not be conflated, because "absent" means "nobody holds the lease" and
216
+ * would license an acquire.
217
+ */
218
+ export declare function readRegister(fleetRoot: string, config: SharedExclusionConfig): RegisterRead;
219
+ /**
220
+ * One compare-and-swap write. `expectedSha` is the value the caller read;
221
+ * the empty string means "I expect the register to be absent". The EXACT-SHA
222
+ * form is the only form emitted (finding 2 in this file's header).
223
+ */
224
+ export declare function casWrite(fleetRoot: string, config: SharedExclusionConfig, expectedSha: string, document: SharedLeaseDocument): CasOutcome;
225
+ export declare function readObservation(fleetRoot: string): RegisterObservation | undefined;
226
+ export declare function writeObservation(fleetRoot: string, observation: RegisterObservation): void;
227
+ export interface CounterJudgement {
228
+ stale: boolean;
229
+ /** How long the counter has stood still, on THIS environment's clock. */
230
+ unchangedForMs: number;
231
+ observation: RegisterObservation;
232
+ }
233
+ /**
234
+ * Judge a holder by the FENCING COUNTER, using two readings of ONE clock.
235
+ *
236
+ * A holder is stale only when the register's sha and counter are the same
237
+ * ones this environment first saw at `firstSeenMs` and that much of ITS OWN
238
+ * time has passed. Any advance of the counter resets the measurement, which
239
+ * is what makes a renewing holder safe no matter how far its clock is from
240
+ * this one: the renewal is visible as an increment, and an increment is not
241
+ * a timestamp.
242
+ */
243
+ export declare function judgeByCounter(previous: RegisterObservation | undefined, sha: string, counter: number, nowMs: number, staleWindowMs: number): CounterJudgement;
244
+ export type SharedIntent = "acquire" | "renew" | "release";
245
+ export interface SharedPreflightInput {
246
+ fleetRoot: string;
247
+ config: SharedExclusionConfig;
248
+ envId: string;
249
+ intent: SharedIntent;
250
+ takeover: boolean;
251
+ nowMs: number;
252
+ }
253
+ /**
254
+ * Decide whether the caller may proceed, WITHOUT touching anything. Every
255
+ * refusal here happens before the local lease file is created, which is what
256
+ * makes criterion 7's fail-closed assertion observable: an unreachable
257
+ * register leaves no local lock behind.
258
+ */
259
+ export declare function preflightShared(input: SharedPreflightInput): SharedPreflight;
260
+ /**
261
+ * The one line a register write that was NOT won reports. It lives here
262
+ * rather than at the call site in `src/lock.ts` because it names the
263
+ * staleness basis, and the C-2 structural inspection over that file
264
+ * (test/lock.test.ts:534) forbids that vocabulary there; keeping the
265
+ * sentence in one place also means a reader sees the same wording whichever
266
+ * mutation lost.
267
+ */
268
+ export declare function casFailureLine(ref: string, outcome: CasOutcome): string;
269
+ /** Build the document a won preflight should publish. */
270
+ export declare function buildRegisterDocument(input: {
271
+ state: "held" | "free";
272
+ envId: string;
273
+ counter: number;
274
+ nowMs: number;
275
+ durationSeconds: number;
276
+ ref: string;
277
+ acquiredAt?: string;
278
+ }): SharedLeaseDocument;
279
+ /**
280
+ * THE FOUR STATUSES, AS A CLOSED SET (M4-P22 criterion 1).
281
+ *
282
+ * `doctor` prints exactly one of them and a test compares the printed
283
+ * leading token against this array, so a fifth status cannot be added by
284
+ * writing a new sentence somewhere: it has to be added here, where the
285
+ * comparison sees it.
286
+ *
287
+ * THE FOURTH IS NEVER PASS. That is the DR-0038 shape reused rather than
288
+ * reinvented: a check whose question could not be asked reports a third
289
+ * state instead of being forced into a binary, because an unreachable
290
+ * register reported as `free` would license exactly the second live
291
+ * orchestrator this layer exists to refuse
292
+ * (delivery/decisions/DR-0038-the-declared-single-family-review-exception.md:1).
293
+ */
294
+ export declare const SHARED_LOCK_STATUS_TOKENS: readonly ["not-declared", "free", "held", "unreachable"];
295
+ export type SharedLockStatusToken = (typeof SHARED_LOCK_STATUS_TOKENS)[number];
296
+ export type SharedLockStatus = {
297
+ token: "not-declared";
298
+ text: string;
299
+ } | {
300
+ token: "free";
301
+ text: string;
302
+ } | {
303
+ token: "held";
304
+ envId: string;
305
+ expiresAt: string;
306
+ text: string;
307
+ } | {
308
+ token: "unreachable";
309
+ reason: string;
310
+ text: string;
311
+ };
312
+ /**
313
+ * A PURE READ of this environment's identity: it never generates one.
314
+ *
315
+ * `ensureEnvironmentId` above writes a file when none is there, which is
316
+ * right for `lock acquire`, the command that legitimately enters the layer
317
+ * and is about to publish a register document. It is wrong for a GUARD: a
318
+ * refusal must create nothing, and `doctor` must diagnose a fleet without
319
+ * changing it. An absent identity file therefore reads as "this environment
320
+ * is not the one the register names", which is the fail-closed answer.
321
+ */
322
+ export declare function readEnvironmentId(fleetRoot: string): string | undefined;
323
+ /**
324
+ * The register's state as one of the four statuses, WITHOUT writing an
325
+ * observation and WITHOUT generating an identity.
326
+ *
327
+ * `preflightShared` is deliberately not used here even though it answers a
328
+ * similar question, because it WRITES `state/shared-lease.observed.json` as
329
+ * part of judging staleness. A diagnosis that moves the thing it diagnoses
330
+ * is not a diagnosis, and the staleness judgement is not wanted here anyway:
331
+ * see `guardSharedRegister` below for why a stale holder still refuses.
332
+ *
333
+ * It DOES fetch, because `readRegister` fetches: the register object has to
334
+ * be local before its document can be read. That touches `.git/FETCH_HEAD`
335
+ * in the fleet home and nothing else, and it is stated here rather than left
336
+ * for a reader to discover.
337
+ *
338
+ * TWO CONDITIONS COLLAPSE INTO `unreachable` AND BOTH ARE NAMED IN THE
339
+ * REASON. A declaration that is present and unusable, and a register whose
340
+ * document does not parse, are not `free` and cannot be rendered as
341
+ * `held <envId> until <t>` because neither yields an envId or an expiry. The
342
+ * criterion's set is closed at four, so they take the one status that means
343
+ * "this question could not be answered", and the reason says which of them
344
+ * it was.
345
+ */
346
+ export declare function sharedLockStatus(fleetRoot: string): SharedLockStatus;
347
+ /**
348
+ * What a task-mutating command is allowed to do, given the register.
349
+ *
350
+ * `off` is the fleet that never opted in, and it is returned before any
351
+ * subprocess is spawned, so nothing about today's behaviour changes for a
352
+ * fleet home with no declaration.
353
+ */
354
+ export type SharedMutationVerdict = {
355
+ kind: "off";
356
+ } | {
357
+ kind: "allowed";
358
+ status: SharedLockStatus;
359
+ } | {
360
+ kind: "refused";
361
+ reason: string;
362
+ };
363
+ /**
364
+ * THE CROSS-ENVIRONMENT HALF OF THE HOLDERSHIP GUARD (M4-P22 criteria 2
365
+ * and 3).
366
+ *
367
+ * `checkHoldership` (src/task.ts:439) answers "does THIS process hold THIS
368
+ * filesystem's lease". That question is answered entirely inside one fleet
369
+ * home, and src/lock.ts:63 says so: the local lease excludes within one
370
+ * filesystem and one clock. So in the state this function exists for, the
371
+ * local lease held by THIS environment and the shared register naming
372
+ * ANOTHER one, the old guard is GREEN and the fleet has two orchestrators
373
+ * mutating one set of tasks. That is the dangerous state, and a test that
374
+ * holds neither lease is green without this function and proves nothing.
375
+ *
376
+ * WHY A STALE HOLDER STILL REFUSES. `judgeByCounter` exists so that a lease
377
+ * whose fencing counter has stood still can be taken over, and that takeover
378
+ * is a LEASE operation: `tiphys lock acquire --take-over` advances the
379
+ * counter under the taking-over environment's id, and only then does the
380
+ * register name this environment. Reading staleness here instead would give
381
+ * `spawn` and `teardown` their own opinion about who holds the fleet, which
382
+ * is a second verdict about one lease from a second place, and it would have
383
+ * to write the observation file to reach it. The refusal names the command
384
+ * that resolves it, so the remedy is reachable rather than merely correct.
385
+ *
386
+ * THE REASON IS ONE LINE, and the callers hand it straight to the same
387
+ * single-reason path every other refusal uses.
388
+ */
389
+ export declare function guardSharedRegister(fleetRoot: string, command: string): SharedMutationVerdict;