dsh-keys-manager 0.2.1 → 0.2.3
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 +97 -0
- package/README.md +108 -15
- package/README.zh-CN.md +80 -2
- package/bundle/cordis.patch.yml +5 -2
- package/lib/client/styles.d.ts +1 -1
- package/lib/client/types.d.ts +27 -2
- package/lib/client/ui.d.ts +14 -0
- package/lib/client.js +445 -18
- package/lib/domain.d.ts +63 -0
- package/lib/host-types.d.ts +29 -0
- package/lib/index.d.ts +973 -45
- package/lib/index.js +4257 -332
- package/lib/index.js.map +1 -1
- package/lib/providers.js +426 -0
- package/lib/providers.js.map +1 -1
- package/lib/store.js +4 -0
- package/lib/store.js.map +1 -1
- package/locale/en.json +27 -1
- package/locale/zh.json +27 -1
- package/package.json +14 -12
- package/scripts/prepare-release.mjs +431 -154
- package/scripts/verify-install.mjs +156 -0
package/lib/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AccountAttemptView, StateView } from './domain.js';
|
|
1
|
+
import type { AccountAttemptView, KeyTestResult, StateView } from './domain.js';
|
|
2
2
|
import type { CredentialsLike, GenerateOptionsLike, HostContext, KeySelection, RequestErrorAction, AgentRequestPayload, RequestErrorPayload, SettingsLike, StreamChunkLike } from './host-types.js';
|
|
3
3
|
import { KeysManagerStore } from './store.js';
|
|
4
4
|
export declare const name = "keys-manager";
|
|
@@ -6,6 +6,15 @@ export declare const inject: string[];
|
|
|
6
6
|
export declare const provide: string[];
|
|
7
7
|
/** The credential-record scope pi-ai stores its per-provider grants under (llm-pi-ai/src/auth.ts:29). */
|
|
8
8
|
export declare const OAUTH_GRANT_RECORD_SCOPE = "llm-pi-ai";
|
|
9
|
+
/**
|
|
10
|
+
* The credential-record scope the llm-omp transports package owns: one
|
|
11
|
+
* `llm-omp-transports/<route>` grant per transport route (its
|
|
12
|
+
* `CURSOR_CREDENTIAL_KEY` is `llm-omp-transports/cursor`). Keys Manager reads
|
|
13
|
+
* those records to list/verify an account and deletes one on removal; it
|
|
14
|
+
* never writes one and never projects one into `llm-pi-ai/<provider>`, so a
|
|
15
|
+
* transport route keeps serving from its own adapter-owned credential.
|
|
16
|
+
*/
|
|
17
|
+
export declare const TRANSPORT_CREDENTIAL_SCOPE = "llm-omp-transports";
|
|
9
18
|
/**
|
|
10
19
|
* Stable failure code for a harvest whose state flush rejected AND whose
|
|
11
20
|
* credential-side rollback then failed to write.
|
|
@@ -46,17 +55,147 @@ export declare class KeysManagerService {
|
|
|
46
55
|
private readonly credentialWrappers;
|
|
47
56
|
private brokerOwners;
|
|
48
57
|
private readonly bindingCache;
|
|
58
|
+
/**
|
|
59
|
+
* Providers whose managed credentials are being removed right now. While a
|
|
60
|
+
* provider is unmanaged only the unbind direction of
|
|
61
|
+
* {@link syncBrokerBinding} may run: a demotion inside the deletion loop
|
|
62
|
+
* (an `oauth` row iterated before a surviving `api-key` row) must never
|
|
63
|
+
* re-point the released profile at the broker.
|
|
64
|
+
*/
|
|
65
|
+
private readonly releasingProviders;
|
|
49
66
|
private lastBindingPruneAt;
|
|
50
67
|
private readonly selectionQueues;
|
|
51
68
|
private queue;
|
|
52
69
|
private closing;
|
|
70
|
+
/** The one shutdown this manager runs; a later close() joins it (round 38). */
|
|
71
|
+
private closePromise;
|
|
72
|
+
/**
|
|
73
|
+
* The pre-close restoration is running (PR review round 37). Only inside it
|
|
74
|
+
* — set when {@link restoreBorrowedWindows} starts and cleared in its
|
|
75
|
+
* `finally`, the cleanup deadline included — does the module-private
|
|
76
|
+
* {@link PRE_CLOSE_RESTORE} capability let a write past shutdown. A
|
|
77
|
+
* restoration that outlives its deadline resumes after this went down and
|
|
78
|
+
* is refused like any other continuation.
|
|
79
|
+
*/
|
|
80
|
+
private preCloseRestorePhase;
|
|
81
|
+
/**
|
|
82
|
+
* Shutdown has begun (PR review round 34). Set on `close()`'s first line,
|
|
83
|
+
* before the pre-close restoration runs, so a test arriving while that
|
|
84
|
+
* restoration is putting the outstanding borrows back is refused instead of
|
|
85
|
+
* starting a second window the restoration would not cover. Never cleared:
|
|
86
|
+
* the instance is closing.
|
|
87
|
+
*/
|
|
88
|
+
private shutdownRequested;
|
|
89
|
+
/**
|
|
90
|
+
* Providers whose in-flight row test borrows the route's credential
|
|
91
|
+
* surface (QC B4: an OAuth row test's projection, and the broker reference
|
|
92
|
+
* it may temporarily release). While a provider is borrowed the binding
|
|
93
|
+
* authority must not derive a real binding from the borrowed world: a
|
|
94
|
+
* mutation landing inside the window (`addKey`, `deleteKey`, an account
|
|
95
|
+
* removal) would otherwise persist an unbind decided about a state that is
|
|
96
|
+
* not the route's. {@link syncBrokerBinding} consults this exactly like
|
|
97
|
+
* {@link releasingProviders}; the test's own window-end sync passes
|
|
98
|
+
* `borrow: true` so the restored state is written back in one step.
|
|
99
|
+
*/
|
|
100
|
+
private readonly testBindingBorrows;
|
|
101
|
+
/**
|
|
102
|
+
* Providers with a borrowing row test in flight, mapped to that test's own
|
|
103
|
+
* promise. The promise is the isolation seam: an ordinary request for the
|
|
104
|
+
* provider waits on it (bounded, see {@link settleProviderTest}) before
|
|
105
|
+
* resolving a credential, and reports its own outcome if the test fails.
|
|
106
|
+
* Only OAuth-row tests borrow (QC N7), so only they register here.
|
|
107
|
+
*/
|
|
108
|
+
private readonly testsInFlight;
|
|
109
|
+
/**
|
|
110
|
+
* Every in-flight row test, by provider — the single-concurrent-test lock
|
|
111
|
+
* for both row kinds. Separate from {@link testsInFlight} because an
|
|
112
|
+
* api-key test borrows nothing and must not isolate ordinary requests.
|
|
113
|
+
*/
|
|
114
|
+
private readonly testsActive;
|
|
115
|
+
/**
|
|
116
|
+
* Every row test window that has not been released yet, by provider (PR
|
|
117
|
+
* review round 33). Shutdown restores each window's borrowed projection
|
|
118
|
+
* before `closing` refuses the in-window cleanup, so the map outlives
|
|
119
|
+
* {@link testsActive} for a window whose request never settles.
|
|
120
|
+
*/
|
|
121
|
+
private readonly testWindows;
|
|
122
|
+
/**
|
|
123
|
+
* Upper bound on one ordinary request's wait for an in-flight row test
|
|
124
|
+
* (QC B3). Injectable so tests can exercise the bound quickly.
|
|
125
|
+
*/
|
|
126
|
+
private isolationWaitMs;
|
|
127
|
+
/**
|
|
128
|
+
* Providers whose abandoned test window is still being cleaned up (QC fix
|
|
129
|
+
* wave 2). The fence stays until a cleanup completed: a new test would
|
|
130
|
+
* borrow a projection that is not back yet, and an ordinary request must not
|
|
131
|
+
* be served by a borrowed grant or an unbound route.
|
|
132
|
+
*/
|
|
133
|
+
private readonly cleaningTests;
|
|
134
|
+
/**
|
|
135
|
+
* Nesting depth of an in-flight removal per provider (PR review round 12).
|
|
136
|
+
* {@link settleTestBeforeRemoval} can only wait for a test that already
|
|
137
|
+
* exists, so a test starting after that check would read the grant and the
|
|
138
|
+
* projection and then re-project an account the removal deleted. {@link
|
|
139
|
+
* testKey} refuses while this marker is up. A depth, because `deleteKey`
|
|
140
|
+
* releases through `releaseLocked` inside its own removal.
|
|
141
|
+
*/
|
|
142
|
+
private readonly removalsInProgress;
|
|
143
|
+
/**
|
|
144
|
+
* Generation of the newest cleanup attempt per provider (PR review round 2).
|
|
145
|
+
* An attempt that outlives its deadline keeps writing while a later attempt
|
|
146
|
+
* for the same window starts. Only the newest generation may release
|
|
147
|
+
* provider-wide state — the fence, the borrow marker, the in-flight maps —
|
|
148
|
+
* so a superseded attempt can never clear isolation a newer window owns.
|
|
149
|
+
*/
|
|
150
|
+
private readonly cleanupAttempts;
|
|
151
|
+
/**
|
|
152
|
+
* The abandoned window currently fencing each provider (QC fix wave 3,
|
|
153
|
+
* W-002), so a later test attempt can retry its cleanup. Without this the
|
|
154
|
+
* fence could only ever clear from the abandoned promise's settlement, and a
|
|
155
|
+
* promise that settled while the seam was still wedged left the provider
|
|
156
|
+
* isolated forever.
|
|
157
|
+
*/
|
|
158
|
+
private readonly abandonedWindows;
|
|
159
|
+
/** Hard bound on one abandoned window's cleanup. Injectable for tests. */
|
|
160
|
+
private testCleanupDeadlineMs;
|
|
161
|
+
/** Hard bound on the shutdown drain of the grant chains. Injectable for tests. */
|
|
162
|
+
private grantDrainDeadlineMs;
|
|
163
|
+
/** Providers whose isolation-deadline warning already fired (warn once). */
|
|
164
|
+
private readonly isolationWaitWarned;
|
|
53
165
|
/** Interactive sign-in attempts, by attempt id. */
|
|
54
166
|
private readonly loginAttempts;
|
|
55
167
|
/** The provider → attempt id index for the single-attempt rule. */
|
|
56
168
|
private readonly loginsInFlight;
|
|
57
169
|
private loginSequence;
|
|
170
|
+
/** The route → attempt id index for the single transport sign-in rule. */
|
|
171
|
+
private readonly transportInFlight;
|
|
172
|
+
/**
|
|
173
|
+
* Per-route removal epoch. A removal bumps it before withdrawing the live
|
|
174
|
+
* attempt, so that attempt's settle path can tell the account is gone and
|
|
175
|
+
* drop a grant its commit had already started writing.
|
|
176
|
+
*/
|
|
177
|
+
private readonly transportRemovals;
|
|
178
|
+
/**
|
|
179
|
+
* Transport-account rows, hydrated from the `llm-omp-transports/<route>`
|
|
180
|
+
* records. Kept out of the pi-ai pool document on purpose: the rows are a
|
|
181
|
+
* read-only projection of another package's records, rebuilt by
|
|
182
|
+
* {@link refreshTransportAccounts} and rendered by `buildState`.
|
|
183
|
+
*/
|
|
184
|
+
private transportRows;
|
|
185
|
+
/** Tail of the serialized transport read chain. */
|
|
186
|
+
private transportRefresh;
|
|
58
187
|
/** The account row whose grant payload currently IS the projection, per provider. */
|
|
59
188
|
private readonly activeAccounts;
|
|
189
|
+
/**
|
|
190
|
+
* Providers whose live projection EXISTS but no managed row owns it (PR
|
|
191
|
+
* review round 3). `activeAccounts` alone cannot tell "no projection" from
|
|
192
|
+
* "a projection this process does not manage": an orphan or foreign grant
|
|
193
|
+
* (hydration found no matching row) still serves ahead of any broker
|
|
194
|
+
* reference, so the route must not be treated as the key pool's. Set by
|
|
195
|
+
* hydration and by the reconciler when a record is present but unattributed,
|
|
196
|
+
* cleared when the record is deleted or becomes owned.
|
|
197
|
+
*/
|
|
198
|
+
private readonly foreignProjections;
|
|
60
199
|
/**
|
|
61
200
|
* Account rows committed to removal (C6-1/I1 removal-aware oracle), marked
|
|
62
201
|
* by {@link removeOAuthGrant} before its first awaited delete. A multi-row
|
|
@@ -101,12 +240,15 @@ export declare class KeysManagerService {
|
|
|
101
240
|
* order is preserved so the older observation can never commit after the
|
|
102
241
|
* newer one and leave the row on a stale generation.
|
|
103
242
|
*
|
|
104
|
-
* Round-8 finding I-000106
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
243
|
+
* Round-8 finding I-000106 assumed activation would join this chain. It does
|
|
244
|
+
* not: activation runs its compare-and-write inside the mutation queue turn,
|
|
245
|
+
* and the only writer of `row.oauth.grantRef` is {@link reconcileGrantRecord},
|
|
246
|
+
* which runs outside that queue. The fence is therefore a generation re-check
|
|
247
|
+
* on both sides of the write — before it (the row must still name the
|
|
248
|
+
* generation that was read) and after it (a rotation commit can land inside
|
|
249
|
+
* the seam's compare-and-write await, which only reads the projection key).
|
|
250
|
+
* Chain members are the echo reconciler, the key-test borrow and adoption
|
|
251
|
+
* classification.
|
|
110
252
|
*/
|
|
111
253
|
private readonly grantReconciliations;
|
|
112
254
|
/**
|
|
@@ -157,6 +299,16 @@ export declare class KeysManagerService {
|
|
|
157
299
|
* to the requested provider's own unresolved read.
|
|
158
300
|
*/
|
|
159
301
|
private readonly hydratedProviders;
|
|
302
|
+
/**
|
|
303
|
+
* Providers whose hydration ownership read FAILED (PR review round 8). A
|
|
304
|
+
* failed read knows nothing about the route, so it is not the "no projection"
|
|
305
|
+
* answer a successful empty read is: treating it as one let the startup
|
|
306
|
+
* repair read an empty owner/foreign map as proof that the pool serves and
|
|
307
|
+
* bind the broker over a persisted OAuth projection it could not read.
|
|
308
|
+
* Readiness therefore stays false for these providers and the repair skips
|
|
309
|
+
* them; only a restart can retry the read.
|
|
310
|
+
*/
|
|
311
|
+
private readonly ownershipReadFailures;
|
|
160
312
|
/**
|
|
161
313
|
* One resolver set per provider still being waited on (round-10 should-fix
|
|
162
314
|
* SF2). A wait registers exactly one entry here and removes it when the wait
|
|
@@ -168,13 +320,43 @@ export declare class KeysManagerService {
|
|
|
168
320
|
private readonly hydrationWaiters;
|
|
169
321
|
/**
|
|
170
322
|
* True once the load-time hydration read is over (round-10 SF1). From then
|
|
171
|
-
* on no
|
|
172
|
-
*
|
|
173
|
-
*
|
|
323
|
+
* on no hydration read can land. It is NOT evidence that a given provider's
|
|
324
|
+
* projection was read (PR review round 17): a provider adopted after the
|
|
325
|
+
* snapshot was never in it, so readiness is decided per provider
|
|
326
|
+
* ({@link projectionOwnershipKnown}) and adoption reads its own projection.
|
|
174
327
|
*/
|
|
175
328
|
private hydrationSettled;
|
|
329
|
+
/**
|
|
330
|
+
* Credential-store generation of the ownership hydration (PR review round
|
|
331
|
+
* 27). A remounted credentials service starts a new pass while the previous
|
|
332
|
+
* store's reads can still be in flight; their answers describe records the
|
|
333
|
+
* new backend does not hold, so the epoch gates every write and settle of
|
|
334
|
+
* the pass that started it.
|
|
335
|
+
*/
|
|
336
|
+
private hydrationEpoch;
|
|
337
|
+
/**
|
|
338
|
+
* Whether a credential store was ever attached (PR review round 27). The
|
|
339
|
+
* first attach is the startup mount and keeps its behaviour; every later one
|
|
340
|
+
* replaces the credential backend, so the ownership classification loaded
|
|
341
|
+
* from the previous one is stale and the read restarts from unknown.
|
|
342
|
+
*/
|
|
343
|
+
private credentialsMounted;
|
|
176
344
|
/** Providers whose bounded-wait warning already fired (warn once). */
|
|
177
345
|
private readonly hydrationWaitWarned;
|
|
346
|
+
/**
|
|
347
|
+
* Stream attempts each provider served without an attributed account
|
|
348
|
+
* (`I-000120`, D8): the ownership read had not landed, or it had failed.
|
|
349
|
+
* Counted at the one managed serving site (`wrapStream`), never in the
|
|
350
|
+
* hydration pass — a failed read serves no request, it only makes later
|
|
351
|
+
* attempts unattributed. Two earlier branches are outside this definition and
|
|
352
|
+
* stay uncounted: a provider this manager does not manage (no ownership is
|
|
353
|
+
* ever asked for) and the `mismatch` profile shape, which hands the request
|
|
354
|
+
* to pi-ai untouched precisely because this manager's own binding cannot
|
|
355
|
+
* describe what serves it. Cleared only when a later read decides the route's
|
|
356
|
+
* attribution; process-local, never persisted and never on the warnings
|
|
357
|
+
* channel.
|
|
358
|
+
*/
|
|
359
|
+
private readonly unattributedRequests;
|
|
178
360
|
/** Channels whose lost-subscription warning already fired (warn once). */
|
|
179
361
|
private readonly subscriptionLostWarnings;
|
|
180
362
|
/** Disposer for the `authorization/settled` subscription. */
|
|
@@ -193,7 +375,17 @@ export declare class KeysManagerService {
|
|
|
193
375
|
* after a restart. An orphan projection — no matching row, e.g. a foreign
|
|
194
376
|
* or stale grant — is left untouched: the manager never adopts a grant it
|
|
195
377
|
* does not own, and out-of-band updates keep surfacing the native-
|
|
196
|
-
* management warning.
|
|
378
|
+
* management warning. A live record that is not a managed grant at all (a
|
|
379
|
+
* native api-key, an unrecognized payload) is foreign for the same reason
|
|
380
|
+
* (PR review round 4): it serves the route, so the startup repair must not
|
|
381
|
+
* bind the broker over it.
|
|
382
|
+
*
|
|
383
|
+
* EVERY managed provider's projection is read (PR review round 13), not
|
|
384
|
+
* only those with OAuth rows: a key-only pool can be overridden by a
|
|
385
|
+
* native grant persisted for the same route, and a skipped read left the
|
|
386
|
+
* startup repair free to bind the broker over it. The reads run
|
|
387
|
+
* concurrently so one provider's stalled read cannot delay another
|
|
388
|
+
* provider's own readiness (round 9).
|
|
197
389
|
*
|
|
198
390
|
* Returns its own completion (round-8 finding I-000108), and the returned
|
|
199
391
|
* promise is already caught, so a failed hydration degrades to "no owner"
|
|
@@ -202,11 +394,31 @@ export declare class KeysManagerService {
|
|
|
202
394
|
* each request waits on its own provider's landing instead.
|
|
203
395
|
*/
|
|
204
396
|
private hydrateProjectionOwnership;
|
|
397
|
+
/**
|
|
398
|
+
* Drop every ownership fact loaded from a previous credential store (PR
|
|
399
|
+
* review round 27). The classification is per backend: a record that existed
|
|
400
|
+
* in the old store says nothing about the remounted one, and a route whose
|
|
401
|
+
* old store held a managed projection would otherwise stay attributed to
|
|
402
|
+
* that account and off its surviving key pool. The epoch bump discards a read
|
|
403
|
+
* still in flight from the replaced store instead of letting its answer land
|
|
404
|
+
* over the new one; ownership stays unknown until the new pass reads.
|
|
405
|
+
*/
|
|
406
|
+
private invalidateProjectionOwnership;
|
|
205
407
|
/**
|
|
206
408
|
* Record that one provider's hydration read is over — owner or not (SF1) —
|
|
207
409
|
* and wake the requests waiting on that provider alone (SF2).
|
|
208
410
|
*/
|
|
209
411
|
private noteProviderHydrated;
|
|
412
|
+
/**
|
|
413
|
+
* An authoritative projection read answered for this provider, so a failed
|
|
414
|
+
* startup hydration no longer leaves ownership unknown (PR review round 13:
|
|
415
|
+
* the marker used to survive until a restart — no attribution, no key
|
|
416
|
+
* selection, and row tests refused with "ownership is still loading"). The
|
|
417
|
+
* binding re-derive is queued behind the reconciliation that proved the
|
|
418
|
+
* read: its attribution decides the route's serving shape, and the startup
|
|
419
|
+
* repair was skipped while that shape was unknown.
|
|
420
|
+
*/
|
|
421
|
+
private noteProjectionReadSucceeded;
|
|
210
422
|
/** Release every provider's readiness wait when hydration cannot proceed. */
|
|
211
423
|
private settleHydrationWaits;
|
|
212
424
|
/**
|
|
@@ -232,10 +444,10 @@ export declare class KeysManagerService {
|
|
|
232
444
|
* Wait (bounded) until projection-ownership hydration can attribute this
|
|
233
445
|
* request, and report whether it did.
|
|
234
446
|
*
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
*
|
|
238
|
-
*
|
|
447
|
+
* Readiness is decided PER PROVIDER (round-10 should-fix SF1): every managed
|
|
448
|
+
* provider's own projection read lands independently (PR review round 13), so
|
|
449
|
+
* a provider whose read has landed needs no wait and a stalled read for
|
|
450
|
+
* another provider cannot delay this one.
|
|
239
451
|
*
|
|
240
452
|
* Returns false when the bound elapsed first. The caller then treats
|
|
241
453
|
* attribution as unknown (no owner) and keeps key state untouched: a
|
|
@@ -243,15 +455,24 @@ export declare class KeysManagerService {
|
|
|
243
455
|
* repeat the I-000108 corruption. The hydration promise is not cancelled,
|
|
244
456
|
* so a late result still lands under the generation guard and later
|
|
245
457
|
* requests are attributed normally.
|
|
246
|
-
*
|
|
247
|
-
* Readiness is decided PER PROVIDER (round-10 should-fix SF1). Hydration
|
|
248
|
-
* writes owners progressively, so racing the one global promise would
|
|
249
|
-
* discard an owner this provider already confirmed just because another
|
|
250
|
-
* provider's read stalled. A provider whose own read has landed — with an
|
|
251
|
-
* owner or with none — is ready without waiting, and the "unknown" fallback
|
|
252
|
-
* is confined to the requested provider's own unresolved read.
|
|
253
458
|
*/
|
|
254
459
|
private awaitProjectionOwnership;
|
|
460
|
+
/**
|
|
461
|
+
* Whether this provider's projection ownership can already be decided
|
|
462
|
+
* without waiting (round-10 SF1, made the single readiness rule by PR review
|
|
463
|
+
* round 3). Every managed provider's projection is read at hydration (a
|
|
464
|
+
* key-only pool can be overridden by a persisted native grant, PR review
|
|
465
|
+
* round 13), so the rule is the same for all of them: this provider's own
|
|
466
|
+
* read landed, or a claim already owns it.
|
|
467
|
+
*
|
|
468
|
+
* The process-wide settled flag is deliberately NOT part of this rule (PR
|
|
469
|
+
* review round 17): a provider adopted after the snapshot was never read by
|
|
470
|
+
* it, so "hydration is over" says nothing about THAT route's projection.
|
|
471
|
+
* Adoption reads it itself ({@link readAdoptionProjection}) and an extra
|
|
472
|
+
* provider's profile is created by its own call, so every managed provider
|
|
473
|
+
* still answers through its own marker.
|
|
474
|
+
*/
|
|
475
|
+
private projectionOwnershipKnown;
|
|
255
476
|
/**
|
|
256
477
|
* Record that a newly committed grant became the provider's projection and
|
|
257
478
|
* return that projection's generation (round-6 finding SF1).
|
|
@@ -266,6 +487,14 @@ export declare class KeysManagerService {
|
|
|
266
487
|
* previous grant's.
|
|
267
488
|
*/
|
|
268
489
|
private noteProjectionCommitted;
|
|
490
|
+
/**
|
|
491
|
+
* Whether a claim landed after `generation` was captured. A reconciliation
|
|
492
|
+
* reads its snapshot before its awaited record read and decides after it, so
|
|
493
|
+
* an activation (or login) that claimed the route in between owns the answer
|
|
494
|
+
* now: the stale body must not clear the claim, mark the route foreign, or
|
|
495
|
+
* re-point the binding. The hydration write uses the same guard.
|
|
496
|
+
*/
|
|
497
|
+
private ownershipSuperseded;
|
|
269
498
|
/**
|
|
270
499
|
* Claim the projection owner for one account row, bumping the provider's
|
|
271
500
|
* ownership generation so an in-flight hydration read of an older state
|
|
@@ -284,13 +513,69 @@ export declare class KeysManagerService {
|
|
|
284
513
|
* the remaining attempt/step records after `ATTEMPT_TTL_MS`.
|
|
285
514
|
*/
|
|
286
515
|
pruneTransient(now?: number): void;
|
|
516
|
+
/**
|
|
517
|
+
* Whether a continuation that resumes from an await must stop writing
|
|
518
|
+
* (PR review round 35). `shutdownRequested` is up for `close()`'s own
|
|
519
|
+
* pre-close restoration, which writes by design: it alone carries the
|
|
520
|
+
* module-private {@link PRE_CLOSE_RESTORE} capability (round 36), which is
|
|
521
|
+
* honoured only while that phase is running (round 37). Both facts are read
|
|
522
|
+
* here, at the caller's write decision, so a capability the phase left
|
|
523
|
+
* behind cannot pass a check made earlier.
|
|
524
|
+
*/
|
|
525
|
+
private persistenceRefused;
|
|
287
526
|
/** Stop accepting mutations, drain the queue, then persist the final snapshot. */
|
|
288
527
|
close(): Promise<void>;
|
|
528
|
+
/** The body of the one shutdown this manager runs. */
|
|
529
|
+
private closeOnce;
|
|
530
|
+
/**
|
|
531
|
+
* Put every outstanding window's borrowed projection back before shutdown
|
|
532
|
+
* closes the store (PR review round 33).
|
|
533
|
+
*
|
|
534
|
+
* `close()` sets `closing`, which refuses {@link cleanupTestWindow} — the one
|
|
535
|
+
* identity-checked restoration — so a test whose request ignores the abort
|
|
536
|
+
* used to leave the tested account projected and the binding it released
|
|
537
|
+
* released, past unload and into the next startup. Each window's captured
|
|
538
|
+
* facts are restored through the same compare-by-payload path as the late
|
|
539
|
+
* reconcile, under one cleanup bound and the attempt fence (a superseded
|
|
540
|
+
* attempt writes nothing), and a window that released the binding re-syncs it
|
|
541
|
+
* against the restored serving source.
|
|
542
|
+
*
|
|
543
|
+
* The request itself is never waited for — an adapter can still rewrite the
|
|
544
|
+
* projection after its restore — so a restoration that cannot finish in the
|
|
545
|
+
* bound warns once and leaves the state to the next startup's hydration.
|
|
546
|
+
*/
|
|
547
|
+
private restoreBorrowedWindows;
|
|
548
|
+
/**
|
|
549
|
+
* Drain the grant chains under a hard bound (PR review round 29).
|
|
550
|
+
*
|
|
551
|
+
* The work is not cancelled — the credential seam offers no cancellation and
|
|
552
|
+
* a wedged provider request may never settle — it is only stopped from
|
|
553
|
+
* holding shutdown: the chain past the deadline is abandoned, and its late
|
|
554
|
+
* write can only land in a store that is closing anyway.
|
|
555
|
+
*/
|
|
556
|
+
private drainGrantReconciliations;
|
|
289
557
|
/** Push a user-facing warning that rides the next state response. */
|
|
290
558
|
private pushWarning;
|
|
291
559
|
private takeWarnings;
|
|
560
|
+
/**
|
|
561
|
+
* Report declared headers whose stored value no longer matches what the
|
|
562
|
+
* environment supplies. The stored value is never replaced — the user may
|
|
563
|
+
* have set it deliberately — but the divergence must be visible, because
|
|
564
|
+
* otherwise the route keeps sending the stale value forever.
|
|
565
|
+
*/
|
|
566
|
+
private warnHeaderDrift;
|
|
292
567
|
/** Current settings service, if the composition mounted one. */
|
|
293
568
|
private settingsService;
|
|
569
|
+
/**
|
|
570
|
+
* Mount the settings service — the host's `ctx.inject(['settings'])` seam.
|
|
571
|
+
*
|
|
572
|
+
* A mount that lands after construction retries the binding repair: the
|
|
573
|
+
* startup pass could not read a profile yet and would otherwise leave the
|
|
574
|
+
* route broken for the whole session.
|
|
575
|
+
*/
|
|
576
|
+
attachSettings(settings: SettingsLike): void;
|
|
577
|
+
/** Unmount the settings service that a re-mount replaced. */
|
|
578
|
+
detachSettings(settings: SettingsLike): void;
|
|
294
579
|
/** Current raw credential provider. */
|
|
295
580
|
private credentialsRaw;
|
|
296
581
|
/**
|
|
@@ -326,6 +611,23 @@ export declare class KeysManagerService {
|
|
|
326
611
|
* Add account that would fail. The check is lazy — see {@link oauthFlowFor}.
|
|
327
612
|
*/
|
|
328
613
|
private oauthFlowAvailable;
|
|
614
|
+
/**
|
|
615
|
+
* Resolve one transport route's sign-in flow.
|
|
616
|
+
*
|
|
617
|
+
* The transports package registers `llm-omp-transports/<route>` itself, so
|
|
618
|
+
* the live flow list is also this manager's inventory of transport routes:
|
|
619
|
+
* a route with no registered flow is not a transport account surface, and a
|
|
620
|
+
* route whose record exists but whose flow is gone is not listed at all
|
|
621
|
+
* (there would be nothing to sign in with).
|
|
622
|
+
*
|
|
623
|
+
* The method id is the flow's own (`methods[0].id`), never a literal
|
|
624
|
+
* `'oauth'`: the scope prefix is the whole cross-package contract, and the
|
|
625
|
+
* transports package may name a method that is not OAuth. A flow with no
|
|
626
|
+
* method cannot be begun and is not a sign-in surface.
|
|
627
|
+
*/
|
|
628
|
+
private transportFlowFor;
|
|
629
|
+
/** Every registered transport flow, in seam order. */
|
|
630
|
+
private transportFlows;
|
|
329
631
|
/**
|
|
330
632
|
* Warn once per channel: a lost subscription silently disables a whole
|
|
331
633
|
* feature (harvest never runs; rotations never sync), so the degradation
|
|
@@ -342,6 +644,14 @@ export declare class KeysManagerService {
|
|
|
342
644
|
private watchAuthorizationSettled;
|
|
343
645
|
/** One settled broadcast: finalize the matching attempt, harvest on success. */
|
|
344
646
|
private onAuthorizationSettled;
|
|
647
|
+
/**
|
|
648
|
+
* The seam interaction one attempt hands to its flow.
|
|
649
|
+
*
|
|
650
|
+
* Shared by the pi-ai and transport sign-ins: both surfaces park notices and
|
|
651
|
+
* prompts on the same {@link LoginAttempt} and are polled through
|
|
652
|
+
* `accountAttempt`; only what happens after `authorized` differs.
|
|
653
|
+
*/
|
|
654
|
+
private attemptInteraction;
|
|
345
655
|
/**
|
|
346
656
|
* Start one interactive OAuth sign-in. Returns immediately with the
|
|
347
657
|
* attempt view; the attempt walks `pending → (prompt ⇄ pending) →
|
|
@@ -357,6 +667,65 @@ export declare class KeysManagerService {
|
|
|
357
667
|
accountAttempt(attemptId: string): AccountAttemptView | undefined;
|
|
358
668
|
/** Every attempt, newest first, for T3 surfaces. */
|
|
359
669
|
listAccountAttempts(): AccountAttemptView[];
|
|
670
|
+
/**
|
|
671
|
+
* Re-read every `llm-omp-transports/<route>` grant into
|
|
672
|
+
* {@link transportRows}.
|
|
673
|
+
*
|
|
674
|
+
* Every caller gets a pass appended after the one already in flight, so an
|
|
675
|
+
* awaiting caller observes a read that started after its own write (a
|
|
676
|
+
* removal, a sign-in commit). Poll-only callers use
|
|
677
|
+
* {@link refreshTransportAccountsIfIdle} instead, so a slow store cannot
|
|
678
|
+
* accumulate one pass per page tick. A failed read leaves that route's
|
|
679
|
+
* previous row alone instead of claiming the account disappeared; the next
|
|
680
|
+
* pass retries.
|
|
681
|
+
*/
|
|
682
|
+
refreshTransportAccounts(): Promise<void>;
|
|
683
|
+
/** Poll hint: start a pass only when none is in flight. */
|
|
684
|
+
private refreshTransportAccountsIfIdle;
|
|
685
|
+
/** One read pass over every registered transport route. */
|
|
686
|
+
private readTransportAccounts;
|
|
687
|
+
/**
|
|
688
|
+
* Remote: start one transport sign-in.
|
|
689
|
+
*
|
|
690
|
+
* The flow lives on the transports package's own key
|
|
691
|
+
* (`llm-omp-transports/<route>`), so the grant it commits stays that route's
|
|
692
|
+
* credential: nothing here harvests it into a pool row or a `llm-pi-ai`
|
|
693
|
+
* projection. The attempt is otherwise the same record the pi-ai dialog
|
|
694
|
+
* polls through `accountAttempt`, so the client reuses that dialog; an
|
|
695
|
+
* authorized outcome is terminal here (the flow has already committed).
|
|
696
|
+
*/
|
|
697
|
+
beginTransportLogin(route: string): Promise<AccountAttemptView>;
|
|
698
|
+
/**
|
|
699
|
+
* Remote: confirm one transport route's stored grant.
|
|
700
|
+
*
|
|
701
|
+
* A transport route has exactly one grant record and its adapter resolves
|
|
702
|
+
* that record per request, so the stored grant already IS the route's live
|
|
703
|
+
* credential. Activation is therefore a state-level re-read: it writes
|
|
704
|
+
* nothing (in particular no `llm-pi-ai/<route>` projection, which the
|
|
705
|
+
* transport route never consults) and reports the current status so the UI
|
|
706
|
+
* can tell "active" from "expired" or "sign in again".
|
|
707
|
+
*/
|
|
708
|
+
activateTransportAccount(route: string): Promise<StateView>;
|
|
709
|
+
/**
|
|
710
|
+
* Remote: drop one transport route's grant record.
|
|
711
|
+
*
|
|
712
|
+
* The record is the whole account — the transports package re-creates none —
|
|
713
|
+
* so deleting it leaves the route signed out until the next sign-in. A live
|
|
714
|
+
* sign-in is withdrawn first: left running, its commit would recreate the
|
|
715
|
+
* grant this removal just deleted. No manager state is written, hence no
|
|
716
|
+
* `run` queue and no state flush.
|
|
717
|
+
*/
|
|
718
|
+
removeTransportAccount(route: string): Promise<StateView>;
|
|
719
|
+
/** The route's removal epoch; absent means never removed. */
|
|
720
|
+
private transportGenerationOf;
|
|
721
|
+
/**
|
|
722
|
+
* Drop a grant a withdrawn sign-in wrote after its account was removed.
|
|
723
|
+
*
|
|
724
|
+
* The seam refuses a commit once the attempt's signal aborts, but a commit
|
|
725
|
+
* that had already started can still land. The account was deleted, so that
|
|
726
|
+
* write is deleted too instead of being reported as a signed-in route.
|
|
727
|
+
*/
|
|
728
|
+
private dropRemovedTransportGrant;
|
|
360
729
|
/**
|
|
361
730
|
* Detach the parked prompt. With a reason the flow's awaited promise is
|
|
362
731
|
* rejected too — a parked prompt dropped un-rejected would leave the
|
|
@@ -386,6 +755,11 @@ export declare class KeysManagerService {
|
|
|
386
755
|
* recorded at that same broadcast (R6-SF1): it names the projection whose
|
|
387
756
|
* failures own a cooldown, so the stale-window reset below clears only a
|
|
388
757
|
* window attributed to an OLDER projection.
|
|
758
|
+
*
|
|
759
|
+
* A harvest that finds the attempt already settled writes neither the
|
|
760
|
+
* plugin-scope copy nor a row, but the projection the seam committed for
|
|
761
|
+
* this attempt is still demoted (ownership-checked, I-000122): a
|
|
762
|
+
* failed/cancelled login must not keep serving through the route.
|
|
389
763
|
*/
|
|
390
764
|
private harvestLogin;
|
|
391
765
|
/**
|
|
@@ -460,6 +834,30 @@ export declare class KeysManagerService {
|
|
|
460
834
|
* Resolves `ok: false` only when the compare/delete writes themselves
|
|
461
835
|
* rejected; an ownership mismatch is the guard doing its job (the live
|
|
462
836
|
* record belongs to someone else) and counts as a clean skip.
|
|
837
|
+
*
|
|
838
|
+
* Every manager-initiated projection deletion registers the one-shot
|
|
839
|
+
* {@link intentionalProjectionRemovals} marker before the delete, exactly
|
|
840
|
+
* as {@link decideAndDeleteProjection} does (I-000125). Unmarked, the
|
|
841
|
+
* removal's own `credentials/record-updated` echo reconciles as an
|
|
842
|
+
* external deletion: the route's previously active managed account is
|
|
843
|
+
* flagged `valueMissing`, its owner claim is dropped and a misleading
|
|
844
|
+
* "removed outside Keys Manager" warning is pushed — even though that
|
|
845
|
+
* row and its plugin-scope grant are untouched.
|
|
846
|
+
*
|
|
847
|
+
* The marker only silences that echo; the route's state is corrected
|
|
848
|
+
* explicitly. The removal leaves the route with no stored projection, so the
|
|
849
|
+
* provider's owner claim — whatever it named before this attempt's grant
|
|
850
|
+
* replaced the record — is untruthful: {@link oauthOwnerFor} keeps naming a
|
|
851
|
+
* row pi-ai no longer resolves, `wrapStream` skips key-pool selection, oauth
|
|
852
|
+
* failures are attributed to that row, and the client keeps "Set active"
|
|
853
|
+
* disabled — the account cannot be re-activated. The claim is therefore
|
|
854
|
+
* invalidated (I-000125), and ONLY the claim: the surviving row keeps its
|
|
855
|
+
* `grantRef` and its `valueMissing: false`, so it stays managed and
|
|
856
|
+
* re-activatable while the route falls back to its API-key pool.
|
|
857
|
+
*
|
|
858
|
+
* The marker is armed only while the provider is still managed: a released
|
|
859
|
+
* provider has no echo consumer, and a marker nothing consumes would stay
|
|
860
|
+
* armed for its whole TTL and mask a genuine out-of-band removal.
|
|
463
861
|
*/
|
|
464
862
|
private revertHarvestProjection;
|
|
465
863
|
/** Expire wedged sign-ins and drop long-settled attempt records. */
|
|
@@ -586,24 +984,315 @@ export declare class KeysManagerService {
|
|
|
586
984
|
* Make one account's grant payload the route's stored credential
|
|
587
985
|
* (D1 activation projection).
|
|
588
986
|
*
|
|
589
|
-
*
|
|
590
|
-
*
|
|
591
|
-
*
|
|
592
|
-
*
|
|
593
|
-
*
|
|
594
|
-
*
|
|
595
|
-
*
|
|
596
|
-
*
|
|
597
|
-
*
|
|
598
|
-
*
|
|
599
|
-
*
|
|
600
|
-
*
|
|
601
|
-
*
|
|
602
|
-
*
|
|
603
|
-
*
|
|
604
|
-
*
|
|
987
|
+
* Plan iter-pool-activate-queue, "Spike outcome" — the contract this method
|
|
988
|
+
* implements. The slow half (read the row's grant, validate it, capture what
|
|
989
|
+
* the route serves now) runs outside the mutation queue, so a slow credential
|
|
990
|
+
* store does not delay unrelated mutations. The mutation queue turn owns every
|
|
991
|
+
* write:
|
|
992
|
+
*
|
|
993
|
+
* 1. admission checks in queue order, which produce this method's error
|
|
994
|
+
* surface (`Provider "…" is not managed`, `Account "…" not found on …`,
|
|
995
|
+
* `Key "…" is not an OAuth account`, `The credential store does not support
|
|
996
|
+
* records`) — plus a re-check that the row still names the generation the
|
|
997
|
+
* read used;
|
|
998
|
+
* 2. the projection write as a compare-and-write under the seam's per-key
|
|
999
|
+
* lock (credentials/src/index.ts:236-250): it lands only while the live
|
|
1000
|
+
* record still equals the captured baseline, "both absent" included. A
|
|
1001
|
+
* rotation, a removal or any foreign writer that replaced the projection
|
|
1002
|
+
* meanwhile makes the compare fail, so this call refuses with the retryable
|
|
1003
|
+
* `could not be projected` message and leaves the newer projection
|
|
1004
|
+
* untouched — there is nothing to delete, because nothing was written;
|
|
1005
|
+
* 3. the owner claim, the row writes and the returned `buildState`.
|
|
1006
|
+
*
|
|
1007
|
+
* There is deliberately no shutdown check after the write: `close()` awaits
|
|
1008
|
+
* this queue before it closes the store, so a turn that started in time always
|
|
1009
|
+
* finishes (measured in the spike that produced this contract). A check there
|
|
1010
|
+
* is what used to leave an orphan projection behind.
|
|
1011
|
+
*
|
|
1012
|
+
* The one write the turn cannot make durable by itself is a rejected state
|
|
1013
|
+
* flush; see {@link compensateActivationFlush} for that compensation.
|
|
605
1014
|
*/
|
|
606
1015
|
activateAccount(provider: string, accountId: string): Promise<StateView>;
|
|
1016
|
+
/**
|
|
1017
|
+
* The slow half of an activation, outside the queue: read the row's grant
|
|
1018
|
+
* generation, validate it, and capture the projection the route serves now.
|
|
1019
|
+
*/
|
|
1020
|
+
private prepareActivation;
|
|
1021
|
+
/** Whether the projection still holds the value this activation read. */
|
|
1022
|
+
private matchesActivationBaseline;
|
|
1023
|
+
/**
|
|
1024
|
+
* Write the projection only while the route still serves the baseline. The
|
|
1025
|
+
* seam's `modifyRecord` mutate body runs under the per-key lock, so this is
|
|
1026
|
+
* the one atomic primitive the activation has (I-000090: `deleteRecord` has
|
|
1027
|
+
* no expected-payload form). Returns the payload written, or `undefined` when
|
|
1028
|
+
* the compare failed and nothing was touched.
|
|
1029
|
+
*/
|
|
1030
|
+
private compareAndWriteProjection;
|
|
1031
|
+
/**
|
|
1032
|
+
* Undo one activation whose state flush was rejected: the row this turn
|
|
1033
|
+
* cleared in memory, the owner claim it took, and the projection it wrote
|
|
1034
|
+
* (back to the captured baseline).
|
|
1035
|
+
*
|
|
1036
|
+
* The baseline decides the mechanism. A present baseline is restored with the
|
|
1037
|
+
* same compare-and-write the projection used; only an absent baseline has to
|
|
1038
|
+
* remove a record, and only while that record is still this activation's own
|
|
1039
|
+
* payload. A foreign writer's record is never deleted.
|
|
1040
|
+
*/
|
|
1041
|
+
private compensateActivationFlush;
|
|
1042
|
+
/** Put the route's owner classification back to a captured snapshot. */
|
|
1043
|
+
private applyActivationClassification;
|
|
1044
|
+
/** Put the projection back to the present baseline, if it is still ours. */
|
|
1045
|
+
private restoreActivationProjection;
|
|
1046
|
+
/**
|
|
1047
|
+
* The delete authority's proof for an activation's own projection, as an
|
|
1048
|
+
* explicit three-valued answer (QC R2): only `matches` may authorize touching
|
|
1049
|
+
* the record. `unknown` is a store failure — a closed seam, an unreadable
|
|
1050
|
+
* record — and proves nothing, so it is never permission.
|
|
1051
|
+
*/
|
|
1052
|
+
private activationProjectionProof;
|
|
1053
|
+
/**
|
|
1054
|
+
* Delete one projection an activation just proved it still owns, through the
|
|
1055
|
+
* same guarded shape the demotion path uses (MF2): the intentional-removal
|
|
1056
|
+
* marker is armed before the delete, so the deletion's own echo is read as
|
|
1057
|
+
* ours instead of as an out-of-band removal.
|
|
1058
|
+
*/
|
|
1059
|
+
private deleteActivationProjection;
|
|
1060
|
+
/**
|
|
1061
|
+
* Remote: send one minimal request with the selected key or account row.
|
|
1062
|
+
*
|
|
1063
|
+
* Observation-only by construction: the request runs under a test-only
|
|
1064
|
+
* request scope, so the broker answers with the pinned credential and the
|
|
1065
|
+
* stream listener skips selection and every health/binding write. An OAuth
|
|
1066
|
+
* row's grant is projected only for the duration of the request; the
|
|
1067
|
+
* projection (and the in-memory owner) that served the route before is
|
|
1068
|
+
* restored even when the request fails, so a test never changes which
|
|
1069
|
+
* account the route is on.
|
|
1070
|
+
*
|
|
1071
|
+
* Every expected refusal is a structured `{ ok: false, error }` result
|
|
1072
|
+
* (M2): the inline row line is the only failure surface, so a refusal must
|
|
1073
|
+
* not also raise the page-level banner a thrown RPC error produces.
|
|
1074
|
+
*/
|
|
1075
|
+
testKey(provider: string, keyId: string): Promise<KeyTestResult>;
|
|
1076
|
+
/**
|
|
1077
|
+
* Take over a test window that did not settle within the isolation deadline
|
|
1078
|
+
* (QC fix wave 2) or whose own cleanup could not restore the route (PR
|
|
1079
|
+
* review MF1).
|
|
1080
|
+
*
|
|
1081
|
+
* The fence is set synchronously, before the first cleanup await: the wedged
|
|
1082
|
+
* test may resume at any await from here, and it must observe the
|
|
1083
|
+
* abandonment before it starts a borrow or restores from its own stale
|
|
1084
|
+
* snapshot. The provider keeps its isolation entry until the cleanup
|
|
1085
|
+
* completed — see {@link settleAbandonedWindow}.
|
|
1086
|
+
*/
|
|
1087
|
+
private abandonTestWindow;
|
|
1088
|
+
/**
|
|
1089
|
+
* Clean up one abandoned window and release its provider only on success.
|
|
1090
|
+
*
|
|
1091
|
+
* A cleanup that cannot finish needs the same untimed seam the wedged test is
|
|
1092
|
+
* parked in, so it fails exactly like the test did. Releasing the provider
|
|
1093
|
+
* then would serve ordinary traffic from the borrowed grant (or, on a
|
|
1094
|
+
* broker-bound profile, from a route whose binding was released and never
|
|
1095
|
+
* restored), so the isolation entry and the fence both stay. Two events can
|
|
1096
|
+
* retry the cleanup: the abandoned promise's settlement, and the next test
|
|
1097
|
+
* attempt ({@link recoverAbandonedWindow}, QC fix wave 3 W-002) — a promise
|
|
1098
|
+
* that settles while the seam is still wedged must not be the only path.
|
|
1099
|
+
*/
|
|
1100
|
+
private settleAbandonedWindow;
|
|
1101
|
+
/** One later cleanup attempt for an abandoned window, once the seam answers again. */
|
|
1102
|
+
private retryAbandonedWindow;
|
|
1103
|
+
/**
|
|
1104
|
+
* One bounded cleanup attempt for a fenced provider (QC fix wave 3, W-002).
|
|
1105
|
+
*
|
|
1106
|
+
* Returns whether the provider is servable again. The caller's attempt is
|
|
1107
|
+
* what makes the fence self-clearing: `testKey` calls this before refusing,
|
|
1108
|
+
* so isolation no longer depends on the abandoned promise settling after the
|
|
1109
|
+
* seam recovered. A failed attempt keeps the fence and warns exactly once.
|
|
1110
|
+
*/
|
|
1111
|
+
private recoverAbandonedWindow;
|
|
1112
|
+
/**
|
|
1113
|
+
* Run the window's cleanup under its hard deadline and drop the provider's
|
|
1114
|
+
* isolation only when it completed. Returns the reason it could not finish.
|
|
1115
|
+
*
|
|
1116
|
+
* PR review round 21: the request that borrowed the route is not part of the
|
|
1117
|
+
* cleanup, so a cleanup can finish while that request is still able to
|
|
1118
|
+
* refresh and rewrite the borrowed projection. Such a window is held
|
|
1119
|
+
* ({@link RowTestWindow.held}) — the fence and the isolation entries stay —
|
|
1120
|
+
* and only its settlement reconciles the late write and releases it.
|
|
1121
|
+
*/
|
|
1122
|
+
private attemptAbandonedCleanup;
|
|
1123
|
+
/** Drop every isolation entry this window still owns, identity-checked. */
|
|
1124
|
+
private releaseAbandonedWindow;
|
|
1125
|
+
/**
|
|
1126
|
+
* Put the route back the way the window found it: the mutations this window
|
|
1127
|
+
* started, then the projection (compare-by-payload and rotation-aware, so
|
|
1128
|
+
* nothing a writer stored meanwhile is overwritten), then the binding — the
|
|
1129
|
+
* original finally's order, because the binding decision reads the restored
|
|
1130
|
+
* owner map — then the borrow marker. Returns the reason the cleanup could
|
|
1131
|
+
* not complete.
|
|
1132
|
+
*
|
|
1133
|
+
* `stillCurrent` is passed by abandoned-window attempts only: a superseded
|
|
1134
|
+
* attempt that outlived its deadline may finish its writes, but the
|
|
1135
|
+
* provider-wide borrow marker belongs to the newest attempt (PR review
|
|
1136
|
+
* round 2) — deleting it would let ordinary traffic reach a newer window's
|
|
1137
|
+
* borrowed projection.
|
|
1138
|
+
*/
|
|
1139
|
+
private cleanupTestWindow;
|
|
1140
|
+
/**
|
|
1141
|
+
* Reconcile the projection once an abandoned window's request finally
|
|
1142
|
+
* settles (PR review round 13).
|
|
1143
|
+
*
|
|
1144
|
+
* The watchdog's cleanup can complete before the wedged request resumes: an
|
|
1145
|
+
* adapter that ignored the abort then refreshes and rewrites the borrowed
|
|
1146
|
+
* grant, and the abandoned window used to skip cleanup on settlement — so
|
|
1147
|
+
* the tested account stayed projected over the restored one and served
|
|
1148
|
+
* ordinary traffic. The shared compare-by-payload restore is idempotent and
|
|
1149
|
+
* only touches a projection this test left (the borrowed grant or a
|
|
1150
|
+
* rotation of the tested row): a foreign payload survives, and a newer
|
|
1151
|
+
* generation is written back to the tested row before the pre-test
|
|
1152
|
+
* projection is restored.
|
|
1153
|
+
*
|
|
1154
|
+
* Returns the failure, so a caller that releases the provider afterwards (PR
|
|
1155
|
+
* review round 21) can keep the fence instead. `ownsFence` is passed by the
|
|
1156
|
+
* settlement of a held window and by the cleanup that releases a window whose
|
|
1157
|
+
* settlement it deferred (PR review round 28): both already own the
|
|
1158
|
+
* provider's fence, which the default guards would read as a competing
|
|
1159
|
+
* cleanup and skip.
|
|
1160
|
+
*/
|
|
1161
|
+
private reconcileLateWindowProjection;
|
|
1162
|
+
/**
|
|
1163
|
+
* Record one in-flight window mutation before it is awaited (QC fix wave 3,
|
|
1164
|
+
* W-001). The normalized promise never rejects: the mutation's own outcome
|
|
1165
|
+
* belongs to the code awaiting it, while the cleanup only needs to know when
|
|
1166
|
+
* the write is over.
|
|
1167
|
+
*/
|
|
1168
|
+
private recordWindowMutation;
|
|
1169
|
+
/**
|
|
1170
|
+
* Await every mutation this window recorded, including one the continuation
|
|
1171
|
+
* of an earlier mutation records (a borrow that settles right before its
|
|
1172
|
+
* binding release starts). The microtask hop after each settling round lets
|
|
1173
|
+
* that continuation run before the cleanup judges the route clean. A
|
|
1174
|
+
* mutation the seam never answers is bounded by the caller's deadline, not
|
|
1175
|
+
* here.
|
|
1176
|
+
*/
|
|
1177
|
+
private awaitWindowMutations;
|
|
1178
|
+
/**
|
|
1179
|
+
* Run one cleanup under its hard deadline. The work is not cancelled — the
|
|
1180
|
+
* credential seam offers no cancellation — it is only stopped from holding
|
|
1181
|
+
* the provider past the bound; a late result is ignored.
|
|
1182
|
+
*/
|
|
1183
|
+
private withinCleanupDeadline;
|
|
1184
|
+
/**
|
|
1185
|
+
* Decide the route's serving shape and run one row test. Separate from
|
|
1186
|
+
* {@link testKey} so the concurrency lock is registered before the first
|
|
1187
|
+
* await.
|
|
1188
|
+
*/
|
|
1189
|
+
private runRowTest;
|
|
1190
|
+
/**
|
|
1191
|
+
* The live provider profile one test must reason about, read once at entry
|
|
1192
|
+
* (QC B1/N6), or the reason it cannot be used. An unreadable settings
|
|
1193
|
+
* snapshot and a missing profile are distinct causes, so the row never
|
|
1194
|
+
* claims a model is missing when the settings service is merely absent.
|
|
1195
|
+
*/
|
|
1196
|
+
private testRouteProfile;
|
|
1197
|
+
/** One request pinned to the row's API-key literal. */
|
|
1198
|
+
private runApiKeyRowTest;
|
|
1199
|
+
/** One request with the row's grant temporarily projected as the route's credential. */
|
|
1200
|
+
private runOAuthRowTest;
|
|
1201
|
+
/** The refusal an abandoned test reports instead of a stale success. */
|
|
1202
|
+
private abandonedTestResult;
|
|
1203
|
+
/** The refusal a chain reports when shutdown refuses its persistence. */
|
|
1204
|
+
private shutdownTestResult;
|
|
1205
|
+
/** The refusal a test reports when it arrives after shutdown began (round 34). */
|
|
1206
|
+
private shutdownEntryResult;
|
|
1207
|
+
/**
|
|
1208
|
+
* Mark one provider as being removed for the whole removal body (PR review
|
|
1209
|
+
* round 12). The marker is up before the body's first await — the removal's
|
|
1210
|
+
* own test wait included — so {@link testKey} can refuse a test that would
|
|
1211
|
+
* otherwise start inside the removal. Nesting is a depth because a
|
|
1212
|
+
* last-key `deleteKey` releases through `releaseLocked`.
|
|
1213
|
+
*/
|
|
1214
|
+
private withRemovalInProgress;
|
|
1215
|
+
/**
|
|
1216
|
+
* Hold a row/account removal until the provider's running row test settles
|
|
1217
|
+
* (PR review round 9, R8-2).
|
|
1218
|
+
*
|
|
1219
|
+
* `testKey` captures its row, its credential and the route's serving shape
|
|
1220
|
+
* before the first await, so a removal landing inside that window can delete
|
|
1221
|
+
* the credential the test is about to send. For a borrowed OAuth account the
|
|
1222
|
+
* removal is worse than a stale read: it compares its decision against the
|
|
1223
|
+
* test's temporary projection, deletes it and demotes every row, and the
|
|
1224
|
+
* test's own restore then writes the removed account's grant back. The
|
|
1225
|
+
* removal therefore waits for the window instead of racing it. The wait is
|
|
1226
|
+
* bounded by {@link isolationWaitMs} — a test wedged in an untimed seam call
|
|
1227
|
+
* must not park the removal forever — and a provider whose abandoned window
|
|
1228
|
+
* still owns the route's records refuses the removal outright. A wait that
|
|
1229
|
+
* hits its own bound refuses too (PR review round 10, I-000179): proceeding
|
|
1230
|
+
* would delete the credential the parked test is about to send. The user can
|
|
1231
|
+
* retry once the abandoned window has been cleaned up.
|
|
1232
|
+
*/
|
|
1233
|
+
private settleTestBeforeRemoval;
|
|
1234
|
+
/**
|
|
1235
|
+
* Put the projection back as it was before the test borrowed it, without
|
|
1236
|
+
* losing or overwriting anything that changed meanwhile.
|
|
1237
|
+
*
|
|
1238
|
+
* `late` marks the abandoned window's settlement, after the watchdog's
|
|
1239
|
+
* cleanup already released the provider: a removal no longer waits for this
|
|
1240
|
+
* test, so the pre-test projection's owning row may be gone by the time this
|
|
1241
|
+
* runs and must not be projected again (PR review round 19).
|
|
1242
|
+
*
|
|
1243
|
+
* The decision is compare-by-payload against the borrowed grant: only a
|
|
1244
|
+
* live record that still carries it — or a refresh-rotated version of it
|
|
1245
|
+
* that {@link matchOwningAccount} attributes to the tested row — is
|
|
1246
|
+
* replaced. A rotation is reconciled into the tested row first (the same
|
|
1247
|
+
* plugin-scope write-back {@link reconcileGrantRecord} performs), because
|
|
1248
|
+
* the provider may already have consumed the superseded refresh token. A
|
|
1249
|
+
* foreign or newer payload (another account, a native writer) is left
|
|
1250
|
+
* untouched, and a record removed during the window stays removed. In every
|
|
1251
|
+
* branch the owner map ends consistent with what the store actually holds.
|
|
1252
|
+
*
|
|
1253
|
+
* Returns a failure message when the restore or the rotation write-back
|
|
1254
|
+
* did not land, so the caller reports `{ ok: false }` instead of a false
|
|
1255
|
+
* pass.
|
|
1256
|
+
*/
|
|
1257
|
+
private restoreProjection;
|
|
1258
|
+
/**
|
|
1259
|
+
* Point the owner map at the OAuth row the stored record identifies.
|
|
1260
|
+
*
|
|
1261
|
+
* The argument is the live record, not its grant payload (PR review round 8):
|
|
1262
|
+
* a payload-only signature cannot tell a removed projection from a PRESENT
|
|
1263
|
+
* record this pool cannot interpret (a native api-key, an unrecognized
|
|
1264
|
+
* payload), so a surviving native record reached this method as `undefined`,
|
|
1265
|
+
* cleared the foreign marker, and `keyPoolServes` then let the window-end
|
|
1266
|
+
* sync bind the broker over it. Only an absent record is a confirmed removal
|
|
1267
|
+
* (I-000167); a present record no row owns keeps the route foreign (round 3).
|
|
1268
|
+
*/
|
|
1269
|
+
private reconcileProjectionOwner;
|
|
1270
|
+
/**
|
|
1271
|
+
* Persist one authoritative rotated grant payload for a row: write the
|
|
1272
|
+
* plugin-scope record for the rotated generation, then hand the commit to
|
|
1273
|
+
* {@link commitGrantGeneration} — the same tail {@link reconcileGrantRecord}
|
|
1274
|
+
* performs. The caller runs inside the provider's grant chain and owns the
|
|
1275
|
+
* decision, including the echo-specific generation guards, so only the
|
|
1276
|
+
* write-back semantics live here.
|
|
1277
|
+
*/
|
|
1278
|
+
private writeBackRotation;
|
|
1279
|
+
/**
|
|
1280
|
+
* The single owner of `row.oauth.grantRef` semantics (QC N1): point the row
|
|
1281
|
+
* at the record holding its rotated generation, merge the refresh-derived
|
|
1282
|
+
* identity fields, and remove the superseded record. Both the echo
|
|
1283
|
+
* reconciler and the connection test's rotation write-back commit through
|
|
1284
|
+
* here, so the ref scheme, the identity merge and the cleanup policy have
|
|
1285
|
+
* exactly one implementation.
|
|
1286
|
+
*
|
|
1287
|
+
* The record write stays with the caller: the reconciler must revalidate the
|
|
1288
|
+
* row between that write and this commit (C6-1), so only the tail is shared.
|
|
1289
|
+
*/
|
|
1290
|
+
private commitGrantGeneration;
|
|
1291
|
+
/**
|
|
1292
|
+
* Send the minimal request under the given scope and map its terminal
|
|
1293
|
+
* chunk. A thrown error or the 20-second abort is reported, never swallowed.
|
|
1294
|
+
*/
|
|
1295
|
+
private runTestRequest;
|
|
607
1296
|
/**
|
|
608
1297
|
* Demote the cascade to the key pool by deleting the projection record:
|
|
609
1298
|
* pi-ai's per-request resolution prefers the stored credential and only
|
|
@@ -724,22 +1413,131 @@ export declare class KeysManagerService {
|
|
|
724
1413
|
private describeSettings;
|
|
725
1414
|
/** Find one live settings descriptor by entry id. */
|
|
726
1415
|
private namespace;
|
|
727
|
-
/**
|
|
1416
|
+
/**
|
|
1417
|
+
* Apply one path-addressed settings write through the live revision.
|
|
1418
|
+
*
|
|
1419
|
+
* `stillWrites` runs after the descriptor re-read and immediately before the
|
|
1420
|
+
* mutation: a decision derived from an earlier snapshot can be stale by then
|
|
1421
|
+
* ({@link syncBrokerBinding}'s bind gate, PR review round 25). A declining
|
|
1422
|
+
* guard skips the write and reports it.
|
|
1423
|
+
*/
|
|
728
1424
|
private mutateSettings;
|
|
729
1425
|
/** Prune expired bindings at most once a minute, without a restart. */
|
|
730
1426
|
private pruneBindings;
|
|
1427
|
+
/**
|
|
1428
|
+
* Project one provider's unattributed-request count into its row. Additive
|
|
1429
|
+
* and optional (`I-000120`): a row with no count spreads nothing, so an
|
|
1430
|
+
* older snapshot and a healthy store render exactly as before.
|
|
1431
|
+
*/
|
|
1432
|
+
private unattributedView;
|
|
731
1433
|
/** Build the complete UI state. */
|
|
732
1434
|
buildState(): StateView;
|
|
733
|
-
/**
|
|
1435
|
+
/**
|
|
1436
|
+
* Remote: complete manager state.
|
|
1437
|
+
*
|
|
1438
|
+
* The transport rows come from another package's records, and reading them
|
|
1439
|
+
* is async while this endpoint is synchronous, so every poll starts a
|
|
1440
|
+
* refresh for the next one (the client ticks every 30 s), unless one is
|
|
1441
|
+
* already in flight. The constructor warms the first read and record
|
|
1442
|
+
* updates refresh immediately, so the one-tick lag only shows on a cold
|
|
1443
|
+
* start.
|
|
1444
|
+
*/
|
|
734
1445
|
getState(): StateView;
|
|
735
1446
|
/** Find one directory entry by route id. */
|
|
736
1447
|
private directoryEntry;
|
|
1448
|
+
/**
|
|
1449
|
+
* Whether the key pool is the credential source that serves this route: no
|
|
1450
|
+
* OAuth projection owns it and at least one API-key row still holds a value.
|
|
1451
|
+
* This is the one arbiter for both the verdict below and the binding write
|
|
1452
|
+
* ({@link syncBrokerBinding}).
|
|
1453
|
+
*/
|
|
1454
|
+
private keyPoolServes;
|
|
737
1455
|
/**
|
|
738
1456
|
* Classify the live binding. An unreadable settings snapshot is `unknown`,
|
|
739
1457
|
* not a confirmed mismatch: transient settings failures must not disable
|
|
740
1458
|
* key-pool recovery for a provider that is still managed.
|
|
1459
|
+
*
|
|
1460
|
+
* Only a value Keys Manager never wrote is a `mismatch`: the warning now
|
|
1461
|
+
* means "the user took the profile back", not "the OAuth route is broken".
|
|
1462
|
+
* An unbound profile is healthy while the key pool is not the serving
|
|
1463
|
+
* source (an OAuth projection serves, or no API-key row holds a value) —
|
|
1464
|
+
* that is the OAuth-only route's native shape.
|
|
741
1465
|
*/
|
|
742
1466
|
private bindingState;
|
|
1467
|
+
/**
|
|
1468
|
+
* The single authority for a managed provider's pi-ai `apiKeyEnv`.
|
|
1469
|
+
*
|
|
1470
|
+
* pi-ai resolves a named credential synchronously and throws
|
|
1471
|
+
* `MISSING_CREDENTIAL` when the credential seam cannot answer it, so the
|
|
1472
|
+
* broker reference may only sit on a profile whose requests the key pool
|
|
1473
|
+
* actually serves ({@link keyPoolServes}). Every other shape must be
|
|
1474
|
+
* unbound, or pi-ai never reaches its own stored OAuth grant.
|
|
1475
|
+
*
|
|
1476
|
+
* Each direction has exactly one provably owned write:
|
|
1477
|
+
* - bind: a profile that names no credential, or — with `takeover`, passed
|
|
1478
|
+
* solely by {@link adoptProvider} — the pre-adoption reference adoption
|
|
1479
|
+
* recorded and deliberately replaces;
|
|
1480
|
+
* - unbind: only the broker reference Keys Manager itself wrote.
|
|
1481
|
+
*
|
|
1482
|
+
* Any other value is a hand-edit (or the pre-adoption reference release
|
|
1483
|
+
* restored): it is never touched, and the `mismatch` verdict stays the
|
|
1484
|
+
* signal that the user took the profile back.
|
|
1485
|
+
*
|
|
1486
|
+
* `write: false` is the startup repair: it only touches a profile that
|
|
1487
|
+
* already exists, so a first run never materializes one from nothing. The
|
|
1488
|
+
* release direction is single-sourced in {@link releasingProviders}: a
|
|
1489
|
+
* provider being unmanaged is never bound again, whatever the row order of
|
|
1490
|
+
* the pool it is deleting.
|
|
1491
|
+
*
|
|
1492
|
+
* A read-side problem or a refused write degrades to a warning: the
|
|
1493
|
+
* callers' committed change must not fail on a settings problem, the user
|
|
1494
|
+
* still sees that the route may resolve the wrong credential source, and
|
|
1495
|
+
* the next repair pass retries.
|
|
1496
|
+
*
|
|
1497
|
+
* Returns whether the profile ends in the shape the serving source justifies
|
|
1498
|
+
* (`true` also when no write was needed). The connection test's borrow is the
|
|
1499
|
+
* one caller that acts on it: a failed release must refuse the test instead
|
|
1500
|
+
* of sending a request the wrong credential source would answer.
|
|
1501
|
+
*
|
|
1502
|
+
* While a row test borrows the route (QC B4) no caller may persist a decision
|
|
1503
|
+
* derived from that borrowed world — an `addKey` landing inside the window
|
|
1504
|
+
* would otherwise unbind the profile for good. The test's own writes pass
|
|
1505
|
+
* `borrow: true`; the window-end sync then writes the restored state.
|
|
1506
|
+
*/
|
|
1507
|
+
private syncBrokerBinding;
|
|
1508
|
+
/**
|
|
1509
|
+
* Repair every managed provider's binding through the one authority. The
|
|
1510
|
+
* startup pass and the settings-mount seam both run it, so a profile that
|
|
1511
|
+
* was unreadable when hydration settled is still healed in this process
|
|
1512
|
+
* instead of waiting for the next start.
|
|
1513
|
+
*/
|
|
1514
|
+
private repairBrokerBindings;
|
|
1515
|
+
/**
|
|
1516
|
+
* One provider's repair pass (PR review round 42). A recorded adoption
|
|
1517
|
+
* takeover debt is settled here only when the authoritative readiness says
|
|
1518
|
+
* no projection serves the route — the case a restart leaves behind when the
|
|
1519
|
+
* foreign record disappeared while the process was down.
|
|
1520
|
+
*/
|
|
1521
|
+
private repairBinding;
|
|
1522
|
+
/**
|
|
1523
|
+
* Re-derive a managed route's binding after its projection changed outside
|
|
1524
|
+
* this manager's account pool: a removal, a committed harvest that handed
|
|
1525
|
+
* the route back to its API-key pool (PR review round 2), or a foreign
|
|
1526
|
+
* projection taking a bound route over (round 4).
|
|
1527
|
+
*
|
|
1528
|
+
* The route's serving source is no longer the managed pool, or the pool is
|
|
1529
|
+
* the only source left: either way `syncBrokerBinding` derives the one
|
|
1530
|
+
* profile shape pi-ai's resolution order requires. A store refusal only
|
|
1531
|
+
* warns — these are compensation paths and must never fail the change that
|
|
1532
|
+
* triggered them; the next repair pass retries.
|
|
1533
|
+
*/
|
|
1534
|
+
private syncBindingAfterProjectionChange;
|
|
1535
|
+
/**
|
|
1536
|
+
* A refused binding write must not fail the change that triggered it, but
|
|
1537
|
+
* it cannot stay in the log alone either: the route may resolve the wrong
|
|
1538
|
+
* credential source until the next repair.
|
|
1539
|
+
*/
|
|
1540
|
+
private bindingWriteRefused;
|
|
743
1541
|
/** Candidate credential refs from which an existing key can be imported. */
|
|
744
1542
|
private importRefs;
|
|
745
1543
|
/** Create and store one key record without adding it to a provider. */
|
|
@@ -748,10 +1546,75 @@ export declare class KeysManagerService {
|
|
|
748
1546
|
private findImport;
|
|
749
1547
|
/** Remote: take over one existing provider route. */
|
|
750
1548
|
adoptProvider(provider: string): Promise<StateView>;
|
|
751
|
-
/**
|
|
1549
|
+
/**
|
|
1550
|
+
* Read and classify one provider's live projection at adoption (PR review
|
|
1551
|
+
* round 17) — the same read hydration does, for the one route hydration
|
|
1552
|
+
* cannot cover: a provider that did not exist when the snapshot was taken.
|
|
1553
|
+
*
|
|
1554
|
+
* An adoption owns no account row yet, so any live record is one no row of
|
|
1555
|
+
* this pool owns: a native api-key, an unrecognized payload, and a grant left
|
|
1556
|
+
* behind by a released account all serve the route ahead of the broker
|
|
1557
|
+
* reference, so the route is marked foreign and the pool is not bound over it
|
|
1558
|
+
* (PR review rounds 3–4). An absent record clears any stale marker: the pool
|
|
1559
|
+
* may serve. A failed read is not an absent projection (round 8): ownership
|
|
1560
|
+
* stays unknown, so nothing may be bound and no repair touches the route
|
|
1561
|
+
* until an authoritative read answers ({@link noteProjectionReadSucceeded}).
|
|
1562
|
+
*
|
|
1563
|
+
* Returns `unknown` for that failure; the adoption itself is kept — there is
|
|
1564
|
+
* no write to roll back, and only the binding is undecided.
|
|
1565
|
+
*
|
|
1566
|
+
* The classification runs inside the route's grant chain (PR review round
|
|
1567
|
+
* 22, Codex P1). A native write or deletion reconciled while the read is
|
|
1568
|
+
* outstanding is therefore either already visible to it or queued behind it:
|
|
1569
|
+
* a read whose snapshot a newer event overtook reports `superseded` and lands
|
|
1570
|
+
* nothing, so a stale absence can never bind the broker over a newer
|
|
1571
|
+
* projection state and a stale presence can never leave the pool unbound.
|
|
1572
|
+
*/
|
|
1573
|
+
private readAdoptionProjection;
|
|
1574
|
+
/** Body of {@link readAdoptionProjection}: read, then land the classification. */
|
|
1575
|
+
private classifyAdoptionProjection;
|
|
1576
|
+
/**
|
|
1577
|
+
* Undo an adoption whose initial binding write was refused (PR review
|
|
1578
|
+
* round 2): drop the managed record and its broker entry, delete the
|
|
1579
|
+
* plugin-scope copy this call created, and put back a pre-adoption
|
|
1580
|
+
* reference that landed despite the refusal. The source credential and the
|
|
1581
|
+
* profile's other fields are never touched — adoption only read them.
|
|
1582
|
+
*/
|
|
1583
|
+
private rollbackAdoption;
|
|
1584
|
+
/**
|
|
1585
|
+
* Discover models for a new extra provider, falling back to the seed catalog.
|
|
1586
|
+
* A route that declares a static header installs the seed catalog directly:
|
|
1587
|
+
* the discovery request has no header slot, so probing such an endpoint can
|
|
1588
|
+
* only fail (and would hold the serialized mutation queue for the probe's
|
|
1589
|
+
* whole timeout).
|
|
1590
|
+
*/
|
|
752
1591
|
private discoverModels;
|
|
753
|
-
/**
|
|
1592
|
+
/**
|
|
1593
|
+
* Remote: install and adopt one extra built-in provider.
|
|
1594
|
+
*
|
|
1595
|
+
* Deliberate exemption from the one-authority rule
|
|
1596
|
+
* ({@link syncBrokerBinding}): this call creates the profile it binds and
|
|
1597
|
+
* the key that serves it in the same mutation, so the write below is the
|
|
1598
|
+
* route's first binding rather than a takeover of a value the user owns.
|
|
1599
|
+
*/
|
|
754
1600
|
addExtraProvider(extraId: string, alias: string, value: string): Promise<StateView>;
|
|
1601
|
+
/**
|
|
1602
|
+
* Restore a managed extra route's declared static headers.
|
|
1603
|
+
*
|
|
1604
|
+
* A managed profile can lose its declared headers through a Models edit;
|
|
1605
|
+
* every managed add-key flow repairs them instead of leaving a route whose
|
|
1606
|
+
* required header is silently absent. Only a live profile with a healthy
|
|
1607
|
+
* binding is ours to repair: a deleted profile would gain a header-only
|
|
1608
|
+
* half-profile, and a replaced one is the state release refuses to touch. A
|
|
1609
|
+
* stored value for the same header always wins. The caller may pass the
|
|
1610
|
+
* headers it already resolved at its own entry (where a missing variable can
|
|
1611
|
+
* still refuse the add before any write); otherwise this method resolves
|
|
1612
|
+
* them. Either way a failure only warns, because the key has already been
|
|
1613
|
+
* added: a route whose headers cannot be resolved, and a settings write that
|
|
1614
|
+
* cannot be performed, both leave the repair pending for the next add so a
|
|
1615
|
+
* half-applied add is never turned into a rejected one.
|
|
1616
|
+
*/
|
|
1617
|
+
private repairDeclaredHeaders;
|
|
755
1618
|
/** Add one key; caller already owns the mutation queue. */
|
|
756
1619
|
private addKeyLocked;
|
|
757
1620
|
/** Remote: add one key to a managed provider. */
|
|
@@ -760,12 +1623,45 @@ export declare class KeysManagerService {
|
|
|
760
1623
|
updateKey(provider: string, keyId: string, patch: unknown): Promise<StateView>;
|
|
761
1624
|
/** Remote: delete one key, releasing the provider when it was the last one. */
|
|
762
1625
|
deleteKey(provider: string, keyId: string): Promise<StateView>;
|
|
763
|
-
/** Read one live profile for release-time cleanup decisions. */
|
|
1626
|
+
/** Read one live profile for repair and release-time cleanup decisions. */
|
|
764
1627
|
private profileAt;
|
|
765
|
-
/**
|
|
1628
|
+
/**
|
|
1629
|
+
* Restore the exact live profile captured before release mutated it.
|
|
1630
|
+
*
|
|
1631
|
+
* Deliberate exemption from the one-authority rule: release is unmanaging,
|
|
1632
|
+
* so this write puts the pre-adoption document back instead of deriving a
|
|
1633
|
+
* binding from a key pool that is being deleted. Every other caller goes
|
|
1634
|
+
* through {@link syncBrokerBinding}.
|
|
1635
|
+
*/
|
|
766
1636
|
private restoreProfileSnapshot;
|
|
767
|
-
/**
|
|
1637
|
+
/**
|
|
1638
|
+
* Remove every managed credential, recording progress before a retry.
|
|
1639
|
+
*
|
|
1640
|
+
* The provider is flagged as releasing for the whole loop: a demotion
|
|
1641
|
+
* inside it (an `oauth` row iterated before a surviving `api-key` row) must
|
|
1642
|
+
* not re-bind the profile, or the reference release just restored would be
|
|
1643
|
+
* overwritten and then stripped by the closing unbind. While unmanaging the
|
|
1644
|
+
* binding only ever goes one way — off.
|
|
1645
|
+
*/
|
|
768
1646
|
private deleteManagedCredentials;
|
|
1647
|
+
/**
|
|
1648
|
+
* Drop any armed intentional-removal marker for a provider being released.
|
|
1649
|
+
*
|
|
1650
|
+
* The marker only exists to silence the record-updated echo of the manager's
|
|
1651
|
+
* own projection removal, and that echo is dropped outright once the provider
|
|
1652
|
+
* is unmanaged: {@link onGrantRecordUpdated} returns at event time and
|
|
1653
|
+
* {@link reconcileGrantRecord} at body time. A release can land between the
|
|
1654
|
+
* arm and that echo — the abort compensation arms inside the mutation queue
|
|
1655
|
+
* while its echo is still chained behind an in-flight body — leaving the
|
|
1656
|
+
* marker armed for its whole TTL with nothing left to consume it. A provider
|
|
1657
|
+
* re-adopted inside that window would then read a genuine out-of-band
|
|
1658
|
+
* removal as the manager's own: no warning, no `valueMissing`, no
|
|
1659
|
+
* stale-owner cleanup.
|
|
1660
|
+
*
|
|
1661
|
+
* Called only on the successful release path: a release that throws leaves
|
|
1662
|
+
* the provider managed, and its echo may still legitimately arrive.
|
|
1663
|
+
*/
|
|
1664
|
+
private dropIntentionalRemoval;
|
|
769
1665
|
/** Restore the provider profile and drop management; caller owns the queue. */
|
|
770
1666
|
private releaseLocked;
|
|
771
1667
|
/** Remote: stop managing a provider and restore its original key reference. */
|
|
@@ -832,6 +1728,38 @@ export declare class KeysManagerService {
|
|
|
832
1728
|
* entry (row removed) resolves to null.
|
|
833
1729
|
*/
|
|
834
1730
|
private oauthOwnerFor;
|
|
1731
|
+
/**
|
|
1732
|
+
* Keep an ordinary request out of the window where a row test borrows (or
|
|
1733
|
+
* is restoring) this provider's projection. The provider stream is built
|
|
1734
|
+
* only after the bounded wait, so neither branch can start a request inside
|
|
1735
|
+
* the borrowed window; a consumer that cancels during the wait constructs
|
|
1736
|
+
* nothing at all. A test-scope request returns from `streamListener` before
|
|
1737
|
+
* this wrapper, so a test never waits on its own guard.
|
|
1738
|
+
*/
|
|
1739
|
+
private isolateFromTest;
|
|
1740
|
+
/**
|
|
1741
|
+
* Wait (bounded) for the in-flight borrowing test on this provider to
|
|
1742
|
+
* settle. The bound is {@link KEY_TEST_ISOLATION_WAIT_MS}, not the test's
|
|
1743
|
+
* own request timeout: the awaited promise also covers untimed
|
|
1744
|
+
* credential-store reads, so without this an ordinary request could park
|
|
1745
|
+
* here forever. A failed or timed-out test never fails the waiting request.
|
|
1746
|
+
*
|
|
1747
|
+
* QC fix wave 2: the one exception is a provider whose abandoned window is
|
|
1748
|
+
* still fenced. The wait is over, but the route's credential surface has not
|
|
1749
|
+
* been reconciled, so serving the request would answer it with the borrowed
|
|
1750
|
+
* grant (or, after a released binding, with no bound route at all). Such a
|
|
1751
|
+
* request is refused instead — the fence is what keeps cross-use impossible
|
|
1752
|
+
* until the cleanup completes.
|
|
1753
|
+
*/
|
|
1754
|
+
private settleProviderTest;
|
|
1755
|
+
/** The refusal an ordinary request gets while its route is still unreconciled. */
|
|
1756
|
+
private testCleanupRefusal;
|
|
1757
|
+
/**
|
|
1758
|
+
* Wrap one connection-test stream: keep the pinned credential scope alive
|
|
1759
|
+
* for every chunk and do nothing else. No selection, no cursor, no health
|
|
1760
|
+
* writes — the request is pure observation.
|
|
1761
|
+
*/
|
|
1762
|
+
private wrapTestStream;
|
|
835
1763
|
/** Wrap one managed stream so credential resolution sees this key. */
|
|
836
1764
|
private wrapStream;
|
|
837
1765
|
/**
|