mcp-pacemaker 2.1.0 → 2.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.
- package/CHANGELOG.md +98 -2
- package/CONTRIBUTING.md +10 -3
- package/README.md +98 -760
- package/autostart/windows/legacy-access.ps1 +359 -0
- package/bin/account-upgrade-controller.mjs +1 -1
- package/bin/account-upgrade-worker.mjs +7 -0
- package/bin/cli-main.mjs +61 -5
- package/bin/dependency-lock.json +642 -0
- package/bin/legacy-access.mjs +73 -0
- package/bin/legacy-installation.mjs +75 -19
- package/bin/legacy-task.mjs +24 -0
- package/bin/legacy-upgrade.mjs +66 -10
- package/bin/managed-upgrade.mjs +102 -12
- package/bin/mcp-bridge.mjs +38 -20
- package/bin/prewarm.mjs +28 -6
- package/bin/service-control.mjs +10 -5
- package/bin/upgrade-capability.json +2 -1
- package/bin/upgrade-dependencies.mjs +67 -6
- package/bin/upgrade-package.mjs +2 -0
- package/bin/windows-legacy/LegacyProcessBroker.build.json +3 -3
- package/bin/windows-legacy/LegacyProcessBroker.exe +0 -0
- package/bin/windows-legacy/src/LegacyNative.cs +60 -11
- package/bin/windows-legacy/src/LegacyProcessBroker.cs +317 -197
- package/bin/windows-legacy-process.mjs +12 -5
- package/docs/account-upgrades.md +17 -4
- package/docs/compatibility.md +39 -0
- package/docs/configuration.md +146 -0
- package/docs/dashboards.md +125 -0
- package/docs/execution-context.md +11 -5
- package/docs/http-api.md +47 -0
- package/docs/install.md +52 -0
- package/docs/operations.md +99 -0
- package/docs/upgrades.md +324 -0
- package/package.json +10 -3
- package/tools/windows-legacy/README.md +118 -92
- package/tools/windows-legacy/inventory.mjs +8 -6
- package/ui/dist/third-party-manifest.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Legacy
|
|
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
|
-
|
|
42
|
-
or revalidation phase
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
is returned or logged, and no
|
|
47
|
-
queries. Snapshot overflow,
|
|
48
|
-
`ERROR_BAD_LENGTH` refuse
|
|
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
|
-
|
|
52
|
-
parent
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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: [
|
|
84
|
-
excluded: [
|
|
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
|
-
|
|
92
|
-
path/hash, argv hash and root script hash. No opaque commit token is in
|
|
93
|
-
Passing a previous
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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`.
|
|
135
|
-
|
|
136
|
-
|
|
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
|
|
160
|
-
|
|
161
|
-
|
|
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**
|
|
165
|
-
|
|
166
|
-
identity keeps the receipt false;
|
|
167
|
-
It never requests terminate access or
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
-
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
|
184
|
-
budget.
|
|
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.
|
|
187
|
-
|
|
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.
|
|
194
|
-
|
|
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
|
|
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
|
|
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
|
|
34
|
-
'legacy broker rejects stale generation
|
|
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
|
|
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
|
|
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) {
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"version": "7.3.6"
|
|
36
36
|
}
|
|
37
37
|
],
|
|
38
|
-
"producerLockSha256": "
|
|
38
|
+
"producerLockSha256": "c2162e5015b4d015058c6ba539d42dfd9afdc8c8c2ecd23a44acbebfbb6f0088",
|
|
39
39
|
"projectLicenseSha256": "590ad9c29ee5580f83b9c05e403ca259d89e8366d1ed4c7207aa5e0a07afbf79",
|
|
40
40
|
"noticesSha256": "2843c26d27582cd029efa8d82408817ac82af0d5b9c4e7cd7962103b70244dfe",
|
|
41
41
|
"chunks": [
|