pi-amq 0.1.1

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 (264) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/LICENSE +21 -0
  3. package/README.md +92 -0
  4. package/THIRD_PARTY_NOTICES.md +33 -0
  5. package/dist/extension/guidance.d.ts +25 -0
  6. package/dist/extension/guidance.d.ts.map +1 -0
  7. package/dist/extension/guidance.js +121 -0
  8. package/dist/extension/guidance.js.map +1 -0
  9. package/dist/extension/guide.d.ts +30 -0
  10. package/dist/extension/guide.d.ts.map +1 -0
  11. package/dist/extension/guide.js +128 -0
  12. package/dist/extension/guide.js.map +1 -0
  13. package/dist/extension/index.d.ts +4 -0
  14. package/dist/extension/index.d.ts.map +1 -0
  15. package/dist/extension/index.js +6 -0
  16. package/dist/extension/index.js.map +1 -0
  17. package/dist/extension/runtime.d.ts +48 -0
  18. package/dist/extension/runtime.d.ts.map +1 -0
  19. package/dist/extension/runtime.js +1095 -0
  20. package/dist/extension/runtime.js.map +1 -0
  21. package/dist/extension/tools.d.ts +12 -0
  22. package/dist/extension/tools.d.ts.map +1 -0
  23. package/dist/extension/tools.js +198 -0
  24. package/dist/extension/tools.js.map +1 -0
  25. package/dist/extension/worker-runtime.d.ts +34 -0
  26. package/dist/extension/worker-runtime.d.ts.map +1 -0
  27. package/dist/extension/worker-runtime.js +974 -0
  28. package/dist/extension/worker-runtime.js.map +1 -0
  29. package/dist/extension/worker-tools.d.ts +140 -0
  30. package/dist/extension/worker-tools.d.ts.map +1 -0
  31. package/dist/extension/worker-tools.js +217 -0
  32. package/dist/extension/worker-tools.js.map +1 -0
  33. package/dist/integration/confirmed-bind-onboarding.d.ts +44 -0
  34. package/dist/integration/confirmed-bind-onboarding.d.ts.map +1 -0
  35. package/dist/integration/confirmed-bind-onboarding.js +199 -0
  36. package/dist/integration/confirmed-bind-onboarding.js.map +1 -0
  37. package/dist/integration/fleet-view.d.ts +66 -0
  38. package/dist/integration/fleet-view.d.ts.map +1 -0
  39. package/dist/integration/fleet-view.js +238 -0
  40. package/dist/integration/fleet-view.js.map +1 -0
  41. package/dist/integration/fresh-session-dispatcher.d.ts +66 -0
  42. package/dist/integration/fresh-session-dispatcher.d.ts.map +1 -0
  43. package/dist/integration/fresh-session-dispatcher.js +103 -0
  44. package/dist/integration/fresh-session-dispatcher.js.map +1 -0
  45. package/dist/integration/launch-preview.d.ts +31 -0
  46. package/dist/integration/launch-preview.d.ts.map +1 -0
  47. package/dist/integration/launch-preview.js +325 -0
  48. package/dist/integration/launch-preview.js.map +1 -0
  49. package/dist/integration/transport-port.d.ts +39 -0
  50. package/dist/integration/transport-port.d.ts.map +1 -0
  51. package/dist/integration/transport-port.js +2 -0
  52. package/dist/integration/transport-port.js.map +1 -0
  53. package/dist/integration/worker-completion-notifier.d.ts +22 -0
  54. package/dist/integration/worker-completion-notifier.d.ts.map +1 -0
  55. package/dist/integration/worker-completion-notifier.js +81 -0
  56. package/dist/integration/worker-completion-notifier.js.map +1 -0
  57. package/dist/transport/amq-cli-adapter.d.ts +41 -0
  58. package/dist/transport/amq-cli-adapter.d.ts.map +1 -0
  59. package/dist/transport/amq-cli-adapter.js +291 -0
  60. package/dist/transport/amq-cli-adapter.js.map +1 -0
  61. package/dist/transport/amq-message.d.ts +47 -0
  62. package/dist/transport/amq-message.d.ts.map +1 -0
  63. package/dist/transport/amq-message.js +173 -0
  64. package/dist/transport/amq-message.js.map +1 -0
  65. package/dist/transport/amq-operations.d.ts +105 -0
  66. package/dist/transport/amq-operations.d.ts.map +1 -0
  67. package/dist/transport/amq-operations.js +320 -0
  68. package/dist/transport/amq-operations.js.map +1 -0
  69. package/dist/transport/atomic-json.d.ts +18 -0
  70. package/dist/transport/atomic-json.d.ts.map +1 -0
  71. package/dist/transport/atomic-json.js +90 -0
  72. package/dist/transport/atomic-json.js.map +1 -0
  73. package/dist/transport/binding-resolver.d.ts +46 -0
  74. package/dist/transport/binding-resolver.d.ts.map +1 -0
  75. package/dist/transport/binding-resolver.js +389 -0
  76. package/dist/transport/binding-resolver.js.map +1 -0
  77. package/dist/transport/bridge-journal.d.ts +100 -0
  78. package/dist/transport/bridge-journal.d.ts.map +1 -0
  79. package/dist/transport/bridge-journal.js +459 -0
  80. package/dist/transport/bridge-journal.js.map +1 -0
  81. package/dist/transport/dlq-retry-audit.d.ts +28 -0
  82. package/dist/transport/dlq-retry-audit.d.ts.map +1 -0
  83. package/dist/transport/dlq-retry-audit.js +66 -0
  84. package/dist/transport/dlq-retry-audit.js.map +1 -0
  85. package/dist/transport/inbox-pump.d.ts +81 -0
  86. package/dist/transport/inbox-pump.d.ts.map +1 -0
  87. package/dist/transport/inbox-pump.js +558 -0
  88. package/dist/transport/inbox-pump.js.map +1 -0
  89. package/dist/transport/index.d.ts +12 -0
  90. package/dist/transport/index.d.ts.map +1 -0
  91. package/dist/transport/index.js +12 -0
  92. package/dist/transport/index.js.map +1 -0
  93. package/dist/transport/presence-projector.d.ts +29 -0
  94. package/dist/transport/presence-projector.d.ts.map +1 -0
  95. package/dist/transport/presence-projector.js +73 -0
  96. package/dist/transport/presence-projector.js.map +1 -0
  97. package/dist/transport/priority-delivery-scheduler.d.ts +85 -0
  98. package/dist/transport/priority-delivery-scheduler.d.ts.map +1 -0
  99. package/dist/transport/priority-delivery-scheduler.js +210 -0
  100. package/dist/transport/priority-delivery-scheduler.js.map +1 -0
  101. package/dist/transport/receiver-lease.d.ts +102 -0
  102. package/dist/transport/receiver-lease.d.ts.map +1 -0
  103. package/dist/transport/receiver-lease.js +778 -0
  104. package/dist/transport/receiver-lease.js.map +1 -0
  105. package/dist/transport/runtime-status.d.ts +48 -0
  106. package/dist/transport/runtime-status.d.ts.map +1 -0
  107. package/dist/transport/runtime-status.js +71 -0
  108. package/dist/transport/runtime-status.js.map +1 -0
  109. package/dist/workflow/atomic-storage.d.ts +12 -0
  110. package/dist/workflow/atomic-storage.d.ts.map +1 -0
  111. package/dist/workflow/atomic-storage.js +104 -0
  112. package/dist/workflow/atomic-storage.js.map +1 -0
  113. package/dist/workflow/budget.d.ts +18 -0
  114. package/dist/workflow/budget.d.ts.map +1 -0
  115. package/dist/workflow/budget.js +74 -0
  116. package/dist/workflow/budget.js.map +1 -0
  117. package/dist/workflow/index.d.ts +17 -0
  118. package/dist/workflow/index.d.ts.map +1 -0
  119. package/dist/workflow/index.js +17 -0
  120. package/dist/workflow/index.js.map +1 -0
  121. package/dist/workflow/lifecycle-projection.d.ts +18 -0
  122. package/dist/workflow/lifecycle-projection.d.ts.map +1 -0
  123. package/dist/workflow/lifecycle-projection.js +22 -0
  124. package/dist/workflow/lifecycle-projection.js.map +1 -0
  125. package/dist/workflow/model-fallback.d.ts +2 -0
  126. package/dist/workflow/model-fallback.d.ts.map +1 -0
  127. package/dist/workflow/model-fallback.js +17 -0
  128. package/dist/workflow/model-fallback.js.map +1 -0
  129. package/dist/workflow/pi-session-fork.d.ts +18 -0
  130. package/dist/workflow/pi-session-fork.d.ts.map +1 -0
  131. package/dist/workflow/pi-session-fork.js +80 -0
  132. package/dist/workflow/pi-session-fork.js.map +1 -0
  133. package/dist/workflow/run-artifacts.d.ts +5 -0
  134. package/dist/workflow/run-artifacts.d.ts.map +1 -0
  135. package/dist/workflow/run-artifacts.js +25 -0
  136. package/dist/workflow/run-artifacts.js.map +1 -0
  137. package/dist/workflow/run-cleanup.d.ts +70 -0
  138. package/dist/workflow/run-cleanup.d.ts.map +1 -0
  139. package/dist/workflow/run-cleanup.js +222 -0
  140. package/dist/workflow/run-cleanup.js.map +1 -0
  141. package/dist/workflow/run-gate.d.ts +43 -0
  142. package/dist/workflow/run-gate.d.ts.map +1 -0
  143. package/dist/workflow/run-gate.js +70 -0
  144. package/dist/workflow/run-gate.js.map +1 -0
  145. package/dist/workflow/run-store.d.ts +208 -0
  146. package/dist/workflow/run-store.d.ts.map +1 -0
  147. package/dist/workflow/run-store.js +2965 -0
  148. package/dist/workflow/run-store.js.map +1 -0
  149. package/dist/workflow/spawn-confirmation.d.ts +44 -0
  150. package/dist/workflow/spawn-confirmation.d.ts.map +1 -0
  151. package/dist/workflow/spawn-confirmation.js +228 -0
  152. package/dist/workflow/spawn-confirmation.js.map +1 -0
  153. package/dist/workflow/system-process-inspector.d.ts +9 -0
  154. package/dist/workflow/system-process-inspector.d.ts.map +1 -0
  155. package/dist/workflow/system-process-inspector.js +129 -0
  156. package/dist/workflow/system-process-inspector.js.map +1 -0
  157. package/dist/workflow/worker-child-extension.d.ts +3 -0
  158. package/dist/workflow/worker-child-extension.d.ts.map +1 -0
  159. package/dist/workflow/worker-child-extension.js +182 -0
  160. package/dist/workflow/worker-child-extension.js.map +1 -0
  161. package/dist/workflow/worker-gate.d.ts +74 -0
  162. package/dist/workflow/worker-gate.d.ts.map +1 -0
  163. package/dist/workflow/worker-gate.js +165 -0
  164. package/dist/workflow/worker-gate.js.map +1 -0
  165. package/dist/workflow/worker-launch.d.ts +65 -0
  166. package/dist/workflow/worker-launch.d.ts.map +1 -0
  167. package/dist/workflow/worker-launch.js +338 -0
  168. package/dist/workflow/worker-launch.js.map +1 -0
  169. package/dist/workflow/worker-model-catalog.d.ts +36 -0
  170. package/dist/workflow/worker-model-catalog.d.ts.map +1 -0
  171. package/dist/workflow/worker-model-catalog.js +105 -0
  172. package/dist/workflow/worker-model-catalog.js.map +1 -0
  173. package/dist/workflow/worker-process-supervisor.d.ts +110 -0
  174. package/dist/workflow/worker-process-supervisor.d.ts.map +1 -0
  175. package/dist/workflow/worker-process-supervisor.js +1188 -0
  176. package/dist/workflow/worker-process-supervisor.js.map +1 -0
  177. package/dist/workflow/worker-prompt-capsule.d.ts +32 -0
  178. package/dist/workflow/worker-prompt-capsule.d.ts.map +1 -0
  179. package/dist/workflow/worker-prompt-capsule.js +52 -0
  180. package/dist/workflow/worker-prompt-capsule.js.map +1 -0
  181. package/dist/workflow/worker-role-presets.d.ts +38 -0
  182. package/dist/workflow/worker-role-presets.d.ts.map +1 -0
  183. package/dist/workflow/worker-role-presets.js +201 -0
  184. package/dist/workflow/worker-role-presets.js.map +1 -0
  185. package/dist/workflow/workflow-doctor.d.ts +57 -0
  186. package/dist/workflow/workflow-doctor.d.ts.map +1 -0
  187. package/dist/workflow/workflow-doctor.js +289 -0
  188. package/dist/workflow/workflow-doctor.js.map +1 -0
  189. package/dist/workflow/workflow-lease.d.ts +125 -0
  190. package/dist/workflow/workflow-lease.d.ts.map +1 -0
  191. package/dist/workflow/workflow-lease.js +689 -0
  192. package/dist/workflow/workflow-lease.js.map +1 -0
  193. package/dist/workflow/workflow-types.d.ts +335 -0
  194. package/dist/workflow/workflow-types.d.ts.map +1 -0
  195. package/dist/workflow/workflow-types.js +11 -0
  196. package/dist/workflow/workflow-types.js.map +1 -0
  197. package/dist/workflow/workspace-paths.d.ts +4 -0
  198. package/dist/workflow/workspace-paths.d.ts.map +1 -0
  199. package/dist/workflow/workspace-paths.js +22 -0
  200. package/dist/workflow/workspace-paths.js.map +1 -0
  201. package/docs/conformance.md +116 -0
  202. package/docs/demo-transcript.md +34 -0
  203. package/docs/design.md +1481 -0
  204. package/docs/operator-guide.md +100 -0
  205. package/extension/guidance.ts +162 -0
  206. package/extension/guide.ts +155 -0
  207. package/extension/index.ts +8 -0
  208. package/extension/runtime.ts +1224 -0
  209. package/extension/tools.ts +221 -0
  210. package/extension/worker-runtime.ts +1150 -0
  211. package/extension/worker-tools.ts +276 -0
  212. package/integration/confirmed-bind-onboarding.ts +296 -0
  213. package/integration/fleet-view.ts +286 -0
  214. package/integration/fresh-session-dispatcher.ts +166 -0
  215. package/integration/launch-preview.ts +396 -0
  216. package/integration/transport-port.ts +29 -0
  217. package/integration/worker-completion-notifier.ts +98 -0
  218. package/package.json +55 -0
  219. package/roles/oracle.md +9 -0
  220. package/roles/researcher.md +8 -0
  221. package/roles/reviewer.md +9 -0
  222. package/roles/scout.md +8 -0
  223. package/roles/worker.md +10 -0
  224. package/scripts/demo-two-terminal.sh +59 -0
  225. package/skills/coordination-patterns/SKILL.md +41 -0
  226. package/skills/coordination-patterns/references/supervised-worker-fan-out.md +12 -0
  227. package/skills/coordination-patterns/references/today-mail-team.md +36 -0
  228. package/transport/amq-cli-adapter.ts +381 -0
  229. package/transport/amq-message.ts +218 -0
  230. package/transport/amq-operations.ts +482 -0
  231. package/transport/atomic-json.ts +115 -0
  232. package/transport/binding-resolver.ts +565 -0
  233. package/transport/bridge-journal.ts +588 -0
  234. package/transport/dlq-retry-audit.ts +97 -0
  235. package/transport/inbox-pump.ts +729 -0
  236. package/transport/index.ts +11 -0
  237. package/transport/presence-projector.ts +104 -0
  238. package/transport/priority-delivery-scheduler.ts +328 -0
  239. package/transport/receiver-lease.ts +945 -0
  240. package/transport/runtime-status.ts +112 -0
  241. package/workflow/.gitkeep +1 -0
  242. package/workflow/atomic-storage.ts +120 -0
  243. package/workflow/budget.ts +90 -0
  244. package/workflow/index.ts +16 -0
  245. package/workflow/lifecycle-projection.ts +37 -0
  246. package/workflow/model-fallback.ts +17 -0
  247. package/workflow/pi-session-fork.ts +96 -0
  248. package/workflow/run-artifacts.ts +36 -0
  249. package/workflow/run-cleanup.ts +315 -0
  250. package/workflow/run-gate.ts +122 -0
  251. package/workflow/run-store.ts +3890 -0
  252. package/workflow/spawn-confirmation.ts +305 -0
  253. package/workflow/system-process-inspector.ts +142 -0
  254. package/workflow/worker-child-extension.ts +193 -0
  255. package/workflow/worker-gate.ts +238 -0
  256. package/workflow/worker-launch.ts +485 -0
  257. package/workflow/worker-model-catalog.ts +165 -0
  258. package/workflow/worker-process-supervisor.ts +1504 -0
  259. package/workflow/worker-prompt-capsule.ts +81 -0
  260. package/workflow/worker-role-presets.ts +241 -0
  261. package/workflow/workflow-doctor.ts +400 -0
  262. package/workflow/workflow-lease.ts +905 -0
  263. package/workflow/workflow-types.ts +367 -0
  264. package/workflow/workspace-paths.ts +32 -0
@@ -0,0 +1,565 @@
1
+ import { isAbsolute, normalize } from "node:path";
2
+
3
+ import {
4
+ type AmqCliAdapter,
5
+ type AmqExitCategory,
6
+ } from "./amq-cli-adapter.js";
7
+
8
+ export const PI_AMQ_BINDING_ENTRY_TYPE = "pi-amq-binding/v1" as const;
9
+
10
+ const BINDING_KEYS = [
11
+ "handle",
12
+ "root",
13
+ "rootId",
14
+ "baseRoot",
15
+ "baseRootId",
16
+ "session",
17
+ "project",
18
+ "amqVersion",
19
+ ] as const;
20
+
21
+ const FLAG_KEYS = ["handle", "root", "session", "mode"] as const;
22
+ const INHERITED_KEYS = [
23
+ "AM_ROOT",
24
+ "AM_ROOT_ID",
25
+ "AM_BASE_ROOT",
26
+ "AM_BASE_ROOT_ID",
27
+ "AM_SESSION",
28
+ "AM_ME",
29
+ ] as const;
30
+
31
+ export interface BindingProjectionV1 {
32
+ readonly handle: string;
33
+ readonly root: string;
34
+ readonly rootId: string;
35
+ readonly baseRoot: string;
36
+ readonly baseRootId: string;
37
+ readonly session: string;
38
+ readonly project: string;
39
+ readonly amqVersion: string;
40
+ }
41
+
42
+ export interface ExplicitBindingFlags {
43
+ readonly handle?: string;
44
+ readonly root?: string;
45
+ readonly session?: string;
46
+ /** Phase 1 carries this value but does not invent mode semantics absent from the design. */
47
+ readonly mode?: string;
48
+ }
49
+
50
+ export interface ResolveBindingInput {
51
+ readonly flags?: ExplicitBindingFlags;
52
+ readonly environment?: Readonly<NodeJS.ProcessEnv>;
53
+ readonly persistedBinding?: unknown;
54
+ }
55
+
56
+ export type BindingSource = "flags" | "environment" | "persisted";
57
+
58
+ export type DormantReason =
59
+ | "unbound"
60
+ | "invalid-flags"
61
+ | "incomplete-environment"
62
+ | "invalid-environment"
63
+ | "invalid-persisted-binding"
64
+ | "authority-mismatch"
65
+ | "context-mismatch"
66
+ | "verification-failed"
67
+ | "invalid-env-output"
68
+ | "verification-mismatch"
69
+ | "stale-persisted-binding";
70
+
71
+ export type BindingResolution =
72
+ | {
73
+ readonly kind: "bound";
74
+ readonly source: BindingSource;
75
+ readonly binding: BindingProjectionV1;
76
+ readonly requestedMode?: string;
77
+ }
78
+ | {
79
+ readonly kind: "dormant";
80
+ readonly reason: DormantReason;
81
+ readonly diagnostic: string;
82
+ readonly action: "/amq bind";
83
+ readonly exitCategory?: AmqExitCategory;
84
+ };
85
+
86
+ interface CandidateBinding {
87
+ readonly source: BindingSource;
88
+ readonly handle: string;
89
+ readonly root: string;
90
+ readonly session?: string;
91
+ readonly expected?: BindingProjectionV1;
92
+ readonly requestedMode?: string;
93
+ }
94
+
95
+ interface AmqEnvV1 {
96
+ readonly amqVersion: string;
97
+ readonly root: string;
98
+ readonly rootId: string;
99
+ readonly baseRoot: string;
100
+ readonly baseRootId: string;
101
+ readonly session: string;
102
+ readonly inSession: boolean;
103
+ readonly me: string;
104
+ readonly project: string;
105
+ }
106
+
107
+ interface ParsedFlags {
108
+ readonly candidate?: CandidateBinding;
109
+ readonly requestedMode?: string;
110
+ }
111
+
112
+ interface ParsedEnvironment {
113
+ readonly candidate?: CandidateBinding;
114
+ }
115
+
116
+ /** Resolves authority without reading AMQ-owned filesystem paths. */
117
+ export class BindingResolver {
118
+ readonly #adapter: Pick<AmqCliAdapter, "runJson">;
119
+
120
+ constructor(adapter: Pick<AmqCliAdapter, "runJson">) {
121
+ this.#adapter = adapter;
122
+ }
123
+
124
+ async resolve(input: ResolveBindingInput = {}): Promise<BindingResolution> {
125
+ const environment = input.environment ?? process.env;
126
+
127
+ let flags: ParsedFlags;
128
+ try {
129
+ flags = parseFlags(input.flags);
130
+ } catch (error) {
131
+ return dormant("invalid-flags", toError(error).message);
132
+ }
133
+
134
+ let inherited: ParsedEnvironment;
135
+ try {
136
+ inherited = parseInheritedEnvironment(environment);
137
+ } catch (error) {
138
+ const message = toError(error).message;
139
+ const reason = message.startsWith("Incomplete inherited AMQ context")
140
+ ? "incomplete-environment"
141
+ : "invalid-environment";
142
+ return dormant(reason, message);
143
+ }
144
+
145
+ let persisted: BindingProjectionV1 | undefined;
146
+ if (input.persistedBinding !== undefined) {
147
+ try {
148
+ persisted = parseBindingProjectionV1(input.persistedBinding);
149
+ } catch (error) {
150
+ return dormant("invalid-persisted-binding", toError(error).message);
151
+ }
152
+ }
153
+
154
+ if (flags.candidate) {
155
+ if (inherited.candidate && routingConflict(flags.candidate, inherited.candidate.expected)) {
156
+ return dormant(
157
+ "authority-mismatch",
158
+ "Explicit AMQ flags conflict with the inherited AMQ pin; relaunch or confirm the change with /amq bind",
159
+ );
160
+ }
161
+ if (persisted && routingConflict(flags.candidate, persisted)) {
162
+ return dormant(
163
+ "authority-mismatch",
164
+ "Explicit AMQ flags differ from the persisted pi-amq-binding/v1 projection",
165
+ );
166
+ }
167
+ const expected = persisted ?? inherited.candidate?.expected;
168
+ return this.#verify(
169
+ withExpected(flags.candidate, expected),
170
+ environment,
171
+ );
172
+ }
173
+
174
+ if (inherited.candidate) {
175
+ if (persisted && !sameBindingAuthority(inherited.candidate.expected, persisted)) {
176
+ return dormant(
177
+ "authority-mismatch",
178
+ "Inherited AMQ context differs from the persisted pi-amq-binding/v1 projection",
179
+ );
180
+ }
181
+ return this.#verify(
182
+ withMode(inherited.candidate, flags.requestedMode),
183
+ environment,
184
+ );
185
+ }
186
+
187
+ if (persisted) {
188
+ return this.#verify(
189
+ {
190
+ source: "persisted",
191
+ handle: persisted.handle,
192
+ root: persisted.root,
193
+ session: persisted.session,
194
+ expected: persisted,
195
+ ...(flags.requestedMode ? { requestedMode: flags.requestedMode } : {}),
196
+ },
197
+ environment,
198
+ );
199
+ }
200
+
201
+ return dormant("unbound", "No explicit, inherited, or persisted AMQ binding is available");
202
+ }
203
+
204
+ async #verify(
205
+ candidate: CandidateBinding,
206
+ environment: Readonly<NodeJS.ProcessEnv>,
207
+ ): Promise<BindingResolution> {
208
+ const argv = candidate.source === "environment"
209
+ ? ["env"]
210
+ : ["env", "--root", candidate.root, "--me", candidate.handle];
211
+ const result = await this.#adapter.runJson(argv, {
212
+ env: commandEnvironment(environment),
213
+ parse: parseAmqEnvV1,
214
+ });
215
+
216
+ if (!result.ok || !result.json) {
217
+ if (result.category === "context-mismatch") {
218
+ return dormant(
219
+ "context-mismatch",
220
+ literalDiagnostic(result.stderr, "AMQ refused the current root/session pin"),
221
+ result.category,
222
+ );
223
+ }
224
+ if (result.category === "invalid-json") {
225
+ return dormant(
226
+ "invalid-env-output",
227
+ result.jsonError?.message ?? "AMQ env returned invalid JSON",
228
+ result.category,
229
+ );
230
+ }
231
+ return dormant(
232
+ "verification-failed",
233
+ literalDiagnostic(result.stderr, `amq env verification failed (${result.category})`),
234
+ result.category,
235
+ );
236
+ }
237
+
238
+ const live = result.json;
239
+ const mismatches = candidateMismatches(candidate, live);
240
+ if (mismatches.length > 0) {
241
+ const reason = candidate.source === "persisted"
242
+ ? "stale-persisted-binding"
243
+ : "verification-mismatch";
244
+ return dormant(
245
+ reason,
246
+ `Live AMQ context mismatch: ${mismatches.join(", ")}`,
247
+ );
248
+ }
249
+
250
+ const mailbox = await this.#adapter.runJson(
251
+ [
252
+ "list",
253
+ "--root",
254
+ live.root,
255
+ "--me",
256
+ live.me,
257
+ "--new",
258
+ "--limit",
259
+ "1",
260
+ ],
261
+ {
262
+ env: commandEnvironment(environment),
263
+ parse: parseMailboxList,
264
+ },
265
+ );
266
+ if (!mailbox.ok) {
267
+ return dormant(
268
+ mailbox.category === "context-mismatch" ? "context-mismatch" : "verification-failed",
269
+ literalDiagnostic(mailbox.stderr, `AMQ mailbox verification failed (${mailbox.category})`),
270
+ mailbox.category,
271
+ );
272
+ }
273
+
274
+ return {
275
+ kind: "bound",
276
+ source: candidate.source,
277
+ binding: projectionFromEnv(live),
278
+ ...(candidate.requestedMode ? { requestedMode: candidate.requestedMode } : {}),
279
+ };
280
+ }
281
+ }
282
+
283
+ export function parseBindingProjectionV1(value: unknown): BindingProjectionV1 {
284
+ const object = record(value, "pi-amq-binding/v1");
285
+ rejectUnknownKeys(object, BINDING_KEYS, "pi-amq-binding/v1");
286
+ const binding: BindingProjectionV1 = {
287
+ handle: requiredString(object, "handle"),
288
+ root: requiredAbsolutePath(object, "root"),
289
+ rootId: requiredString(object, "rootId"),
290
+ baseRoot: requiredAbsolutePath(object, "baseRoot"),
291
+ baseRootId: requiredString(object, "baseRootId"),
292
+ session: stringField(object, "session"),
293
+ project: stringField(object, "project"),
294
+ amqVersion: requiredString(object, "amqVersion"),
295
+ };
296
+ return binding;
297
+ }
298
+
299
+ function parseAmqEnvV1(value: unknown): AmqEnvV1 {
300
+ const object = record(value, "amq env --json");
301
+ if (numberField(object, "schema_version") !== 1) {
302
+ throw new Error("amq env schema_version must be 1");
303
+ }
304
+ const peers = record(object.peers, "amq env peers");
305
+ for (const [peer, path] of Object.entries(peers)) {
306
+ if (typeof path !== "string") throw new Error(`amq env peers.${peer} must be a string`);
307
+ }
308
+
309
+ const output: AmqEnvV1 = {
310
+ amqVersion: requiredString(object, "amq_version"),
311
+ root: requiredAbsolutePath(object, "root"),
312
+ rootId: requiredString(object, "root_id"),
313
+ baseRoot: requiredAbsolutePath(object, "base_root"),
314
+ baseRootId: requiredString(object, "base_root_id"),
315
+ session: stringField(object, "session_name"),
316
+ inSession: booleanField(object, "in_session"),
317
+ me: requiredString(object, "me"),
318
+ project: stringField(object, "project"),
319
+ };
320
+ requiredString(object, "root_source");
321
+ if (output.inSession !== (output.session.length > 0)) {
322
+ throw new Error("amq env in_session and session_name are inconsistent");
323
+ }
324
+ return output;
325
+ }
326
+
327
+ function parseMailboxList(value: unknown): readonly unknown[] {
328
+ if (!Array.isArray(value)) throw new Error("amq list --json must return an array");
329
+ return value;
330
+ }
331
+
332
+ function parseFlags(value: ExplicitBindingFlags | undefined): ParsedFlags {
333
+ if (!value) return {};
334
+ const object = value as Record<string, unknown>;
335
+ rejectUnknownKeys(object, FLAG_KEYS, "explicit AMQ flags");
336
+
337
+ const requestedMode = optionalNonEmptyString(object.mode, "mode");
338
+ const hasRoutingFlag = object.handle !== undefined || object.root !== undefined || object.session !== undefined;
339
+ if (!hasRoutingFlag) return requestedMode ? { requestedMode } : {};
340
+
341
+ const handle = optionalNonEmptyString(object.handle, "handle");
342
+ const root = optionalNonEmptyString(object.root, "root");
343
+ const session = optionalNonEmptyString(object.session, "session");
344
+ if (!handle || !root) {
345
+ throw new Error("Explicit AMQ routing requires both handle and absolute root");
346
+ }
347
+ if (!isAbsolute(root)) throw new Error("Explicit AMQ root must be absolute");
348
+
349
+ return {
350
+ candidate: {
351
+ source: "flags",
352
+ handle,
353
+ root: normalize(root),
354
+ ...(session ? { session } : {}),
355
+ ...(requestedMode ? { requestedMode } : {}),
356
+ },
357
+ ...(requestedMode ? { requestedMode } : {}),
358
+ };
359
+ }
360
+
361
+ function parseInheritedEnvironment(environment: Readonly<NodeJS.ProcessEnv>): ParsedEnvironment {
362
+ const present = INHERITED_KEYS.filter((key) => Object.hasOwn(environment, key));
363
+ if (present.length === 0) return {};
364
+ const missing = INHERITED_KEYS.filter((key) => !Object.hasOwn(environment, key));
365
+ if (missing.length > 0) {
366
+ throw new Error(`Incomplete inherited AMQ context; missing ${missing.join(", ")}`);
367
+ }
368
+
369
+ const root = requiredEnvironmentValue(environment, "AM_ROOT");
370
+ const rootId = requiredEnvironmentValue(environment, "AM_ROOT_ID");
371
+ const baseRoot = requiredEnvironmentValue(environment, "AM_BASE_ROOT");
372
+ const baseRootId = requiredEnvironmentValue(environment, "AM_BASE_ROOT_ID");
373
+ const handle = requiredEnvironmentValue(environment, "AM_ME");
374
+ const session = environment.AM_SESSION;
375
+ if (typeof session !== "string") throw new Error("AM_SESSION must be present as a string");
376
+ if (!isAbsolute(root) || !isAbsolute(baseRoot)) {
377
+ throw new Error("Inherited AM_ROOT and AM_BASE_ROOT must be absolute");
378
+ }
379
+
380
+ const expected: BindingProjectionV1 = {
381
+ handle,
382
+ root: normalize(root),
383
+ rootId,
384
+ baseRoot: normalize(baseRoot),
385
+ baseRootId,
386
+ session,
387
+ project: "<live-verification-required>",
388
+ amqVersion: "<live-verification-required>",
389
+ };
390
+ return {
391
+ candidate: {
392
+ source: "environment",
393
+ handle,
394
+ root: expected.root,
395
+ session,
396
+ expected,
397
+ },
398
+ };
399
+ }
400
+
401
+ function candidateMismatches(candidate: CandidateBinding, live: AmqEnvV1): string[] {
402
+ const mismatches: string[] = [];
403
+ compare(mismatches, "handle", candidate.handle, live.me);
404
+ comparePath(mismatches, "root", candidate.root, live.root);
405
+ if (candidate.session !== undefined) compare(mismatches, "session", candidate.session, live.session);
406
+
407
+ const expected = candidate.expected;
408
+ if (!expected) return mismatches;
409
+ compare(mismatches, "rootId", expected.rootId, live.rootId);
410
+ comparePath(mismatches, "baseRoot", expected.baseRoot, live.baseRoot);
411
+ compare(mismatches, "baseRootId", expected.baseRootId, live.baseRootId);
412
+ if (expected.project !== "<live-verification-required>") {
413
+ compare(mismatches, "project", expected.project, live.project);
414
+ }
415
+ return mismatches;
416
+ }
417
+
418
+ function routingConflict(candidate: CandidateBinding, other: BindingProjectionV1 | undefined): boolean {
419
+ if (!other) return false;
420
+ if (candidate.handle !== other.handle || normalize(candidate.root) !== normalize(other.root)) return true;
421
+ return candidate.session !== undefined && candidate.session !== other.session;
422
+ }
423
+
424
+ function sameBindingAuthority(
425
+ left: BindingProjectionV1 | undefined,
426
+ right: BindingProjectionV1,
427
+ ): boolean {
428
+ if (!left) return false;
429
+ return left.handle === right.handle
430
+ && normalize(left.root) === normalize(right.root)
431
+ && left.rootId === right.rootId
432
+ && normalize(left.baseRoot) === normalize(right.baseRoot)
433
+ && left.baseRootId === right.baseRootId
434
+ && left.session === right.session;
435
+ }
436
+
437
+ function withExpected(
438
+ candidate: CandidateBinding,
439
+ expected: BindingProjectionV1 | undefined,
440
+ ): CandidateBinding {
441
+ return expected ? { ...candidate, expected } : candidate;
442
+ }
443
+
444
+ function withMode(candidate: CandidateBinding, requestedMode: string | undefined): CandidateBinding {
445
+ return requestedMode ? { ...candidate, requestedMode } : candidate;
446
+ }
447
+
448
+ function projectionFromEnv(env: AmqEnvV1): BindingProjectionV1 {
449
+ return {
450
+ handle: env.me,
451
+ root: env.root,
452
+ rootId: env.rootId,
453
+ baseRoot: env.baseRoot,
454
+ baseRootId: env.baseRootId,
455
+ session: env.session,
456
+ project: env.project,
457
+ amqVersion: env.amqVersion,
458
+ };
459
+ }
460
+
461
+ function dormant(
462
+ reason: DormantReason,
463
+ diagnostic: string,
464
+ exitCategory?: AmqExitCategory,
465
+ ): BindingResolution {
466
+ return {
467
+ kind: "dormant",
468
+ reason,
469
+ diagnostic,
470
+ action: "/amq bind",
471
+ ...(exitCategory ? { exitCategory } : {}),
472
+ };
473
+ }
474
+
475
+ function record(value: unknown, name: string): Record<string, unknown> {
476
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
477
+ throw new Error(`${name} must be an object`);
478
+ }
479
+ return value as Record<string, unknown>;
480
+ }
481
+
482
+ function rejectUnknownKeys(
483
+ object: Record<string, unknown>,
484
+ allowed: readonly string[],
485
+ name: string,
486
+ ): void {
487
+ const unknown = Object.keys(object).filter((key) => !allowed.includes(key));
488
+ if (unknown.length > 0) throw new Error(`${name} contains unknown fields: ${unknown.join(", ")}`);
489
+ }
490
+
491
+ function requiredString(object: Record<string, unknown>, key: string): string {
492
+ return optionalNonEmptyString(object[key], key) ?? fail(`${key} is required`);
493
+ }
494
+
495
+ function stringField(object: Record<string, unknown>, key: string): string {
496
+ const value = object[key];
497
+ if (typeof value !== "string") throw new Error(`${key} must be a string`);
498
+ return value;
499
+ }
500
+
501
+ function numberField(object: Record<string, unknown>, key: string): number {
502
+ const value = object[key];
503
+ if (typeof value !== "number" || !Number.isFinite(value)) throw new Error(`${key} must be a number`);
504
+ return value;
505
+ }
506
+
507
+ function booleanField(object: Record<string, unknown>, key: string): boolean {
508
+ const value = object[key];
509
+ if (typeof value !== "boolean") throw new Error(`${key} must be a boolean`);
510
+ return value;
511
+ }
512
+
513
+ function requiredAbsolutePath(object: Record<string, unknown>, key: string): string {
514
+ const path = requiredString(object, key);
515
+ if (!isAbsolute(path)) throw new Error(`${key} must be absolute`);
516
+ return normalize(path);
517
+ }
518
+
519
+ function optionalNonEmptyString(value: unknown, name: string): string | undefined {
520
+ if (value === undefined) return undefined;
521
+ if (typeof value !== "string" || value.trim().length === 0) {
522
+ throw new Error(`${name} must be a non-empty string`);
523
+ }
524
+ return value;
525
+ }
526
+
527
+ function requiredEnvironmentValue(
528
+ environment: Readonly<NodeJS.ProcessEnv>,
529
+ key: keyof NodeJS.ProcessEnv,
530
+ ): string {
531
+ const value = environment[key];
532
+ if (typeof value !== "string" || value.length === 0) {
533
+ throw new Error(`${String(key)} must be a non-empty string`);
534
+ }
535
+ return value;
536
+ }
537
+
538
+ function commandEnvironment(environment: Readonly<NodeJS.ProcessEnv>): NodeJS.ProcessEnv {
539
+ const projected: NodeJS.ProcessEnv = { ...environment };
540
+ for (const key of INHERITED_KEYS) {
541
+ if (!Object.hasOwn(environment, key)) projected[key] = undefined;
542
+ }
543
+ return projected;
544
+ }
545
+
546
+ function compare(mismatches: string[], field: string, expected: string, actual: string): void {
547
+ if (expected !== actual) mismatches.push(field);
548
+ }
549
+
550
+ function comparePath(mismatches: string[], field: string, expected: string, actual: string): void {
551
+ if (normalize(expected) !== normalize(actual)) mismatches.push(field);
552
+ }
553
+
554
+ function literalDiagnostic(stderr: string, fallback: string): string {
555
+ const literal = stderr.trim();
556
+ return literal.length > 0 ? literal : fallback;
557
+ }
558
+
559
+ function fail(message: string): never {
560
+ throw new Error(message);
561
+ }
562
+
563
+ function toError(error: unknown): Error {
564
+ return error instanceof Error ? error : new Error(String(error));
565
+ }