mcp-pacemaker 2.1.0 → 2.1.2

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 (41) hide show
  1. package/CHANGELOG.md +142 -2
  2. package/CONTRIBUTING.md +11 -4
  3. package/README.md +116 -764
  4. package/SECURITY.md +7 -0
  5. package/THIRD_PARTY_NOTICES.txt +1 -1
  6. package/autostart/windows/legacy-access.ps1 +359 -0
  7. package/bin/account-upgrade-controller.mjs +1 -1
  8. package/bin/account-upgrade-worker.mjs +7 -0
  9. package/bin/cli-main.mjs +61 -5
  10. package/bin/dependency-lock.json +642 -0
  11. package/bin/legacy-access.mjs +73 -0
  12. package/bin/legacy-installation.mjs +75 -19
  13. package/bin/legacy-task.mjs +24 -0
  14. package/bin/legacy-upgrade.mjs +66 -10
  15. package/bin/managed-upgrade.mjs +102 -12
  16. package/bin/mcp-bridge.mjs +115 -20
  17. package/bin/prewarm.mjs +28 -6
  18. package/bin/service-control.mjs +10 -5
  19. package/bin/upgrade-capability.json +2 -1
  20. package/bin/upgrade-dependencies.mjs +67 -6
  21. package/bin/upgrade-package.mjs +2 -0
  22. package/bin/windows-legacy/LegacyProcessBroker.build.json +3 -3
  23. package/bin/windows-legacy/LegacyProcessBroker.exe +0 -0
  24. package/bin/windows-legacy/src/LegacyNative.cs +60 -11
  25. package/bin/windows-legacy/src/LegacyProcessBroker.cs +317 -197
  26. package/bin/windows-legacy-process.mjs +12 -5
  27. package/docs/account-upgrades.md +17 -4
  28. package/docs/comparison.md +167 -0
  29. package/docs/compatibility.md +39 -0
  30. package/docs/configuration.md +146 -0
  31. package/docs/dashboards.md +125 -0
  32. package/docs/execution-context.md +11 -5
  33. package/docs/http-api.md +47 -0
  34. package/docs/install.md +52 -0
  35. package/docs/operations.md +99 -0
  36. package/docs/upgrades.md +324 -0
  37. package/package.json +11 -3
  38. package/tools/windows-legacy/README.md +118 -92
  39. package/tools/windows-legacy/inventory.mjs +8 -6
  40. package/ui/dist/THIRD_PARTY_NOTICES.txt +1 -1
  41. package/ui/dist/third-party-manifest.json +6 -6
@@ -1,4 +1,4 @@
1
- # Legacy observed-process broker
1
+ # Legacy process broker
2
2
 
3
3
  This separate own-source .NET Framework 4.6.2 executable supports an explicitly
4
4
  partial, restart-only legacy handoff. It does not change the modern lifetime
@@ -38,28 +38,26 @@ never resolves target arguments against its own cwd or guesses the target cwd.
38
38
  Existing case-insensitive full-path comparison and native executable identity
39
39
  queries are unchanged.
40
40
 
41
- Child discovery uses one documented `TH32CS_SNAPPROCESS` snapshot per capture
42
- or revalidation phase. At most 16,384 PID/parent pairs are retained in memory.
43
- The native entry buffer also contains other OS fields; the broker reads only
44
- the PID/parent offsets, never marshals names, and clears that buffer after each
45
- entry. It closes the snapshot before opening candidates. No global pair list
46
- is returned or logged, and no unrelated entries receive process/token/image
47
- queries. Snapshot overflow, layout/duplicate/enumeration failure and
48
- `ERROR_BAD_LENGTH` refuse the whole operation without retry.
41
+ Process discovery uses documented `TH32CS_SNAPPROCESS` snapshots: one per capture
42
+ or revalidation phase to bind the two roots, and one per stop sweep round. At most
43
+ 16,384 PID/parent pairs are retained in memory. The native entry buffer also
44
+ contains other OS fields; the broker reads only the PID/parent offsets, never
45
+ marshals names, and clears that buffer after each entry. It closes the snapshot
46
+ before opening candidates. No global pair list is returned or logged, and no
47
+ unrelated entries receive process/token/image queries. Snapshot overflow,
48
+ layout/duplicate/enumeration failure and `ERROR_BAD_LENGTH` refuse a capture or
49
+ revalidation phase without retry; during a stop they are reported, and the sweep
50
+ still stops every process it already holds and tries again in its next round.
49
51
 
50
52
  Snapshot pairs are discovery hints, not process authority or an atomic tree.
51
- Every candidate must still bind to an already-held live parent through native
52
- parent/creation/owner checks. Unknown historical branches remain unproven.
53
- Fresh revalidation refuses added or changed generations; a reused numeric PID
54
- cannot replace an exited captured handle.
55
-
56
- Argv queries cover only held live IDs, at most 32 per query and four concurrent
57
- workers. Each worker owns its connected WMI scope/searcher/cursor. Missing,
58
- extra, duplicate or stale rows refuse the entire phase. Failed waves stop new
59
- dispatch and join before a response; the native watchdog remains the hard
60
- whole-helper bound for blocked calls. Descendant argv is discarded after
61
- hashing. Already-exited captured descendants remain recorded as gone rather
62
- than being adopted by PID.
53
+ A sweep candidate must still bind to a held process through its own native
54
+ parent, creation-time and owner checks. Unknown historical branches stay
55
+ untouched.
56
+
57
+ The argv query covers only the two held roots, in one query per phase run by a
58
+ worker that owns its connected WMI scope/searcher/cursor. Missing, extra,
59
+ duplicate or stale rows refuse the entire phase; the native watchdog remains the
60
+ hard whole-helper bound for a blocked call.
63
61
 
64
62
  Each phase separately hashes at most 64 executable files, 256 MiB per file and
65
63
  512 MiB total. Read handles deny writes/deletion until the phase finishes.
@@ -71,59 +69,75 @@ launcher/task ownership, source hashes, and config-directory nonce/revision.
71
69
  Matching process arguments and captured script hashes do **not** make an arbitrary
72
70
  script an approved legacy package. Cwd is not a supported config binding.
73
71
 
74
- Prepare is read-only. It captures the roots and a bounded set of currently
75
- provable descendants **before any stop**. Each ancestry edge is checked against
76
- a held, still-live parent and child creation time. Script read handles deny
77
- script modification/deletion during this session. The plan contains:
72
+ Prepare is read-only. It captures **only the two roots** and binds them to the
73
+ listener and to each other. It does not list the bridge's session processes: a
74
+ busy 1.3 bridge starts and ends them every few seconds, so a list would be stale
75
+ before the stop. It also walks its own parent chain, checking creation order at
76
+ each step, and refuses with `Run the upgrade from a terminal the 1.3 bridge did
77
+ not start` if that chain reaches the supervisor or the bridge: such an upgrade
78
+ would stop itself. Script read handles deny script modification/deletion during
79
+ this session. The plan contains:
78
80
 
79
81
  ```text
80
82
  protocol: 1
81
83
  kind: "legacy-observed-process-set"
82
84
  roots: { supervisor, bridge }
83
- observed: [captured identities]
84
- excluded: [proven older nonmembers; never termination targets]
85
+ observed: [] (2.1.0 plans listed descendants here; recovery still verifies them)
86
+ excluded: []
85
87
  observedSetSha256
86
88
  treeCompleteness: "unproven"
87
89
  scope
88
90
  planSha256
89
91
  ```
90
92
 
91
- Identities include PID, creation time, parent PID/creation time, owner SID, image
92
- path/hash, argv hash and root script hash. No opaque commit token is in `plan`.
93
- Passing a previous full `plan` as `expected` requires exact equality, including
94
- all root generations, hashes and observed members. Changes require a new plan
95
- and confirmation; no new set is silently substituted.
96
-
97
- If a query returns a candidate whose actual creator PID equals a still-live held
98
- parent, but whose creation time predates that parent's held generation, it is
99
- excluded with PID/birth/parent-birth evidence and reason `predates-held-parent`.
100
- Only query/synchronize access is opened for this check; an excluded candidate
101
- never receives terminate access and is not traversed. This handles older
102
- survivors whose creator's numeric PID was reused. The plan digest binds the
103
- excluded evidence too. A dead parent, changed live edge, inaccessible query or
104
- other ambiguous ownership still refuses. Stop rechecks any excluded candidate
105
- still returned by the same rooted query; changed generations require replanning.
106
-
107
- Cached identities repeat the native generation, parent, owner and liveness
108
- checks before reuse. If a selected root predates a newer held generation of its
109
- numeric creator PID, only that false descendant edge is ignored. The root
110
- remains selected, its parent-birth binding is not rewritten, and it is not added
111
- to `excluded`. Non-root or unproven cached edges still refuse. Revalidation uses
112
- the same classification; depth eight and the combined identity limit do not
113
- change.
114
-
115
- Only after explicit per-plan interruption/uncertain-HTTP confirmation and holding
116
- and rechecking the **exact selected** autostart registration may the caller invoke
117
- `stop()`. This broker does not attest that the user has paused callers or held the
118
- task. It rechecks roots, listener, argv/image identity and currently reachable
119
- members. A newly observed child before commit causes refusal, not silent expansion.
120
- Then it terminates the selected supervisor, bridge, and admitted descendants
121
- through their retained native handles, never by a fresh PID lookup.
122
-
123
- All root/capture ambiguities refuse. After mutation starts, failures are explicit
124
- and may be partial; never automatically relaunch the old supervisor, replay
125
- tools, or broaden cleanup. The caller must preserve its task hold and journal
126
- when stop is uncertain.
93
+ Root identities include PID, creation time, parent PID/creation time, owner SID,
94
+ image path/hash, argv hash and root script hash. No opaque commit token is in
95
+ `plan`. Passing a previous `plan` as `expected` binds its roots: protocol, kind,
96
+ port, root, owner and both root identities must match exactly. Descendants are
97
+ not compared, so sessions that started or ended since the plan do not refuse it.
98
+ A changed root generation still requires a new plan and confirmation.
99
+
100
+ Only after the caller's per-plan confirmation and holding and rechecking the
101
+ **exact selected** autostart registration may the caller invoke `stop()`. This
102
+ broker does not attest that the task is held. It rechecks the roots, listener and
103
+ argv/image identity, then stops through retained native handles, never by a
104
+ fresh PID lookup:
105
+
106
+ 1. Terminate the supervisor and wait for it, so it cannot start another bridge.
107
+ 2. Pin: take a snapshot and hold a handle to each process whose live parent PID
108
+ (read from its own handle, not the snapshot) is a held process and which
109
+ started no earlier than that process, walking the whole tree in one pass.
110
+ Repeat until a snapshot shows nothing new, at most eight times. Nothing is
111
+ stopped here.
112
+ 3. Terminate the bridge and wait for it.
113
+ 4. Sweep: pin again, then terminate every held process not yet stopped and wait
114
+ for them, even if that pin found the tree still growing or could not read a
115
+ snapshot. Repeat until a round's pin settles with nothing new and nothing is
116
+ left to stop.
117
+
118
+ Pinning comes first because Node ends a process's direct children together with
119
+ it through a job object, and 1.3 runs each bare server command under a `cmd.exe`
120
+ wrapper. Without the pin, ending the bridge would end the wrappers at once and
121
+ leave the real servers under them with a parent nobody holds. A process that has
122
+ already exited is held too while it can still be opened, so its children stay
123
+ traceable. A wrapper the bridge starts in the few milliseconds between the last
124
+ pin snapshot and the bridge's exit is not held; if it has started its server by
125
+ then, that server is left running like any other untraceable process.
126
+
127
+ Held handles keep their PIDs from being reused, so a process that names a held
128
+ PID as its parent and started no earlier than it was started by it. A process
129
+ older than that parent got the PID from an earlier owner and is left alone; a
130
+ held root that appears under a pinned process (its creator's PID was reused) is
131
+ already held and is skipped. The broker and its own parent chain, matched by PID
132
+ and creation time, are never adopted. A live process owned by another user is
133
+ reported, not stopped.
134
+ Processes whose parent had already exited before the stop cannot be traced and
135
+ stay untouched.
136
+
137
+ Root capture ambiguities refuse. After mutation starts, failures are explicit and
138
+ may be partial; never automatically relaunch the old supervisor, replay tools, or
139
+ broaden cleanup. The caller must preserve its task hold and journal when the root
140
+ stop is uncertain.
127
141
 
128
142
  ### Failure diagnostics
129
143
 
@@ -131,9 +145,9 @@ The existing refusal envelope keeps its exception type, native error code,
131
145
  stage, reason and `mutationStarted` flag. When available, `captureFailure`
132
146
  contains fixed API/substage labels and nullable stored PID, creation, parent,
133
147
  liveness and owner-match facts. An initial query-handle denial reports the
134
- requested PID but `generationKnown: false` and `creationTime: null`. Existing
135
- ancestry-refusal details are retained. Other call sites can report an API with
136
- unknown substage/target; unknown fields are not inferred from a PID or exception.
148
+ requested PID but `generationKnown: false` and `creationTime: null`. Other call
149
+ sites can report an API with unknown substage/target; unknown fields are not
150
+ inferred from a PID or exception.
137
151
 
138
152
  `observation: "stored-observations-not-fresh-proof"` identifies these as prior
139
153
  observations, not a new identity check or stop authority. No diagnostic recovery
@@ -146,7 +160,9 @@ Success frames, plans, digests, approvals and stop behavior do not change.
146
160
 
147
161
  ```text
148
162
  legacyRootStopVerified: true|false
149
- observedDescendantsStopped: true|false
163
+ observedDescendantsStopped: true|false (every process the sweep found was stopped)
164
+ descendantsFound
165
+ descendantsStopped
150
166
  treeCompleteness: "unproven"
151
167
  observedCount
152
168
  excludedCount
@@ -156,43 +172,53 @@ unattributed: "Not discovered or not provable; left untouched."
156
172
  ```
157
173
 
158
174
  There is no `stopped:true` or `activeProcesses:0` field. A wrapper that exits
159
- before capture can leave an unprovable descendant. Matching snapshots and empty
160
- captured sets do not prove no such processes exist. This broker neither counts
161
- nor kills historical/unattributed orphans.
175
+ before the stop leaves its children with no traceable parent. Matching snapshots
176
+ do not prove no such processes exist. This broker neither counts nor kills
177
+ historical/unattributed orphans. The caller requires `legacyRootStopVerified`;
178
+ a process the sweep could not stop is reported in `errors` and left running.
162
179
 
163
180
  `verifyLegacyProcessesGone(plan)` validates the protected plan's shape/digest,
164
- then opens **only its captured IDs** with query/synchronize access. A different
165
- creation time is an unrelated reused PID and is untouched; a matching live
166
- identity keeps the receipt false; inaccessible identities keep it unverified.
167
- It never requests terminate access or performs discovery. The journal's
168
- authentication/ownership and phase authorization belong to the caller. A hash
169
- inside the plan is an integrity check, not an authentication credential.
170
-
171
- Excluded candidates are never opened by recovery verification and are never
172
- required to exit. Counts describe this bounded observation, not all historical
173
- orphans or complete tree coverage.
181
+ then opens **only its captured IDs** (the roots, plus any descendants a 2.1.0 plan
182
+ listed) with query/synchronize access. A different creation time is an unrelated
183
+ reused PID and is untouched; a matching live identity keeps the receipt false;
184
+ inaccessible identities keep it unverified. It never requests terminate access or
185
+ performs discovery, so its `descendantsFound` and `descendantsStopped` are null,
186
+ and for a plan that lists no descendants `observedDescendantsStopped` is false:
187
+ the check cannot vouch for them. The journal's authentication/ownership and phase
188
+ authorization belong to the caller. A hash inside the plan is an integrity check,
189
+ not an authentication credential.
174
190
 
175
191
  ## Bounds and lifecycle
176
192
 
177
- - At most 512 **combined held and excluded identities**, including both roots,
178
- and eight ancestry levels. Any count/depth/byte/query failure refuses the
179
- whole plan; no subset is silently accepted.
180
- - At most 48 argv queries during capture and 32 during revalidation.
181
- - Initial capture: 15-second native watchdog, 20-second Node response bound.
193
+ - Plans hold at most 512 identities, including both roots, so a 2.1.0 plan is
194
+ still accepted for recovery checks. A plan written now holds only the roots.
195
+ - The stop holds at most 2048 processes and reads at most 2048 children of one
196
+ process. Each pin takes at most eight snapshots; a tree still growing after
197
+ that is pinned further by the next sweep round. The sweep runs at most 16
198
+ rounds. One busy 1.3 host had 533 processes in its tree. Reaching the process,
199
+ child or round bound is reported in `errors`; the root stop stands.
200
+ - One argv query during capture and one during revalidation.
201
+ - The native watchdog allows 15 seconds to receive the request. Capture and
202
+ revalidation then each have 120 seconds, timed from the request rather than
203
+ from broker start; verification is bounded by the watchdog alone. Each phase
204
+ adds a 10-second watchdog margin, longer than one 8-second WMI query, so a
205
+ phase refusal is reported rather than cut off. Node waits 150 seconds for a
206
+ prepare or verification response.
182
207
  - Uncommitted session: 120 seconds; expiration requires fresh prepare.
183
- - Stop revalidation: 15 seconds; termination/waits share **one eight-second**
184
- budget. Node's whole stop exchange is bounded to 30 seconds.
208
+ - Stop: the supervisor, the pin, the bridge and every sweep round share **one
209
+ 20-second** budget. Once it is spent, remaining exits are polled, not awaited,
210
+ and the sweep ends. Node's whole stop exchange, including revalidation, is
211
+ bounded to 170 seconds.
185
212
  - Close/exit verification: three seconds.
186
- - Plans: at most 240 KiB UTF-8 before emit/accept. Complete encoded requests:
187
- at most 256 KiB; no full command lines or config contents emitted.
213
+ - Plans: at most 240 KiB UTF-8 before emit/accept. Requests and responses: at most
214
+ 256 KiB UTF-8. No full command lines or config contents are emitted.
188
215
  - Native parent watch: caller loss before commit releases handles without target
189
216
  termination. After commit the already-authorized bounded stop finishes.
190
217
 
191
218
  Prepare/stop responses include bounded phase timing/resource diagnostics and
192
219
  the broker's own PID/FILETIME for owned exit evidence. These values confer no
193
- target authority and are not part of the plan digest. Termination order and its
194
- single shared eight-second budget are unchanged. A test-only outer cleanup job
195
- is not proof that the production stop succeeded.
220
+ target authority and are not part of the plan digest. A test-only outer
221
+ cleanup job is not proof that the production stop succeeded.
196
222
 
197
223
  Detached native launch avoids inherited console/Node child termination, but is
198
224
  not an unbounded daemon. Parent loss and deadlines end the broker. Native target
@@ -18,7 +18,7 @@ export const LEGACY_BROKER_TEST = 'test/windows-legacy-process.test.mjs';
18
18
  export const LEGACY_BROKER_REQUIRED_TESTS = Object.freeze([
19
19
  'legacy API denials preserve refusal and bounded stored-only failure diagnostics',
20
20
  'legacy failure metadata cannot mask errors or cross concurrent operation identities',
21
- 'legacy cached selected-root creator-PID reuse skips only the older edge and retains strict refusals',
21
+ 'legacy stop sweep stops descendants of held parents and skips reused-PID edges',
22
22
  'legacy protected-plan model enforces 512 combined identities and UTF8 byte bounds without process queries',
23
23
  'legacy expected request refuses oversized UTF8 framing before native discovery',
24
24
  ...['relative', 'drive-relative', 'root-relative'].flatMap((spelling) =>
@@ -28,19 +28,21 @@ export const LEGACY_BROKER_REQUIRED_TESTS = Object.freeze([
28
28
  'legacy script qualification recognizes drive and UNC forms without cwd resolution',
29
29
  'legacy script identity preserves fully qualified short-path handling',
30
30
  'legacy script identity does not equate a junction spelling with a different canonical root',
31
- 'legacy broker captures held roots and stops only its observed set with partial receipt',
31
+ 'legacy broker plans only its roots and stops the processes they started',
32
+ 'legacy broker stops a server the bridge ran through cmd.exe, which Node ends with the bridge',
33
+ 'legacy broker refuses an upgrade started from inside the 1.3 tree without stopping anything',
32
34
  'legacy broker close is read-only and an identical expected plan can be reopened',
33
- 'legacy broker preserves an already-exited captured descendant without adopting a replacement PID',
34
- 'legacy broker rejects stale generation or expanded observed plan without stopping roots',
35
+ 'legacy broker reopens its plan while sessions start and end, and stops the later ones',
36
+ 'legacy broker rejects a stale root generation without stopping roots',
35
37
  'legacy broker rejects wrong root and malformed protected verification plan',
36
38
  'legacy broker refuses extra supervisor argv and a foreign listener without process mutation',
37
39
  'legacy read-only recovery does not stop an unrelated process reusing the endpoint',
38
40
  'legacy native inventory rejects changed executable, source and build metadata',
39
41
  'legacy prepare and recovery verify refuse wrong-hash packaged helper before any discovery',
40
42
  'legacy broker parent loss releases read-only capture without stopping legacy roots',
41
- 'legacy broker reports captured descendant termination failure without certifying stop',
43
+ 'legacy broker reports a session process it could not stop and still verifies the root stop',
42
44
  'legacy broker preserves Unicode plan identity across fragmented UTF8 pipe messages',
43
- 'legacy broker excludes a proven older nonmember without terminating or verifying its exit',
45
+ 'legacy broker sweep leaves an older process alive when it names a stopped root as parent',
44
46
  ]);
45
47
 
46
48
  export function verifyLegacyBrokerAssets(root) {
@@ -219,7 +219,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
219
219
  SOFTWARE.
220
220
 
221
221
  ==============================================================================
222
- vite@7.3.6
222
+ vite@7.3.7
223
223
  Declared license: MIT
224
224
 
225
225
  --- LICENSE.md: # Vite core license ---
@@ -32,12 +32,12 @@
32
32
  },
33
33
  {
34
34
  "name": "vite",
35
- "version": "7.3.6"
35
+ "version": "7.3.7"
36
36
  }
37
37
  ],
38
- "producerLockSha256": "5ac61562ea1bd680a2326aeee0967814839cb3c1a632907572ff1cf7c6bb1703",
38
+ "producerLockSha256": "f9176a69ff877b2596b7b00190ba5a9f88ae99f579aa06f947a6ba78f17ac6fa",
39
39
  "projectLicenseSha256": "590ad9c29ee5580f83b9c05e403ca259d89e8366d1ed4c7207aa5e0a07afbf79",
40
- "noticesSha256": "2843c26d27582cd029efa8d82408817ac82af0d5b9c4e7cd7962103b70244dfe",
40
+ "noticesSha256": "f114a23088813c16f46ac1b7d2749938b1c846afaa0a2a03349fddb9d51cdf90",
41
41
  "chunks": [
42
42
  {
43
43
  "file": "assets/index-CcwMo2sI.js",
@@ -116,7 +116,7 @@
116
116
  },
117
117
  {
118
118
  "id": "commonjsHelpers.js",
119
- "package": "vite@7.3.6",
119
+ "package": "vite@7.3.7",
120
120
  "generated": true,
121
121
  "renderedLength": 140
122
122
  },
@@ -282,7 +282,7 @@
282
282
  },
283
283
  {
284
284
  "id": "vite/modulepreload-polyfill.js",
285
- "package": "vite@7.3.6",
285
+ "package": "vite@7.3.7",
286
286
  "generated": true,
287
287
  "renderedLength": 1168
288
288
  }
@@ -533,7 +533,7 @@
533
533
  },
534
534
  {
535
535
  "name": "vite",
536
- "version": "7.3.6",
536
+ "version": "7.3.7",
537
537
  "license": "MIT",
538
538
  "lockPath": "node_modules/vite",
539
539
  "licenses": [