@pellux/goodvibes-daemon 1.28.20 → 1.28.21
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 +34 -0
- package/README.md +11 -6
- package/package.json +4 -4
- package/src/daemon/handlers/contracts.ts +15 -0
- package/src/daemon/handlers/index.ts +1 -1
- package/src/daemon/handlers/payments/address-store.ts +54 -0
- package/src/daemon/handlers/payments/budget-store.ts +356 -0
- package/src/daemon/handlers/payments/checkout-handlers.ts +526 -0
- package/src/daemon/handlers/payments/index.ts +7 -1
- package/src/daemon/handlers/payments/merchant-judge.ts +57 -0
- package/src/daemon/handlers/payments/notifier.ts +112 -0
- package/src/daemon/handlers/payments/register.ts +261 -134
- package/src/runtime/browser-checkout-seam-holder.ts +55 -0
- package/src/runtime/daemon-handler-composition.ts +28 -11
- package/src/runtime/payments-composition.ts +70 -26
- package/src/runtime/services.ts +13 -6
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* browser-checkout-seam-holder.ts, holding a seam that arrives after its
|
|
3
|
+
* first reader is constructed.
|
|
4
|
+
*
|
|
5
|
+
* ── Why a holder, and not a constructor argument ──────────────────────────
|
|
6
|
+
*
|
|
7
|
+
* The SDK hands out a `BrowserCheckoutSeam` through `onBrowserCheckout`, a
|
|
8
|
+
* callback `composeDaemonBrowser` invokes once, synchronously, at the moment
|
|
9
|
+
* IT composes the browser. In this daemon that composition runs inside
|
|
10
|
+
* `attachWsOnlyGatewayVerbHandlers` (services.ts), which is called AFTER
|
|
11
|
+
* `createDaemonHandlerComposition` builds the payments handlers, because the
|
|
12
|
+
* ws-only verb groups need managers (disposal scope, workspace checkpoint
|
|
13
|
+
* manager, ...) that do not exist yet at the point payments is composed.
|
|
14
|
+
* Reordering the two would ripple across every other verb group that call
|
|
15
|
+
* builds, for one capability's benefit.
|
|
16
|
+
*
|
|
17
|
+
* So the payments composition cannot receive the seam as a constructor
|
|
18
|
+
* argument; it receives a GETTER, closed over this holder, and reads it at
|
|
19
|
+
* CALL time rather than at registration time. By the time any real invocation
|
|
20
|
+
* reaches `payments.checkout.begin`, the daemon has finished booting and
|
|
21
|
+
* `onBrowserCheckout` has already fired (or the composition is one where the
|
|
22
|
+
* browser was never buildable at all, `composeDaemonBrowser` returned null,
|
|
23
|
+
* homeDirectory absent, and the getter honestly keeps returning undefined
|
|
24
|
+
* forever, which the checkout handler already refuses on cleanly).
|
|
25
|
+
*/
|
|
26
|
+
import type { BrowserCheckoutSeam } from '@pellux/goodvibes-sdk/platform/control-plane';
|
|
27
|
+
|
|
28
|
+
export interface BrowserCheckoutSeamHolder {
|
|
29
|
+
readonly get: () => BrowserCheckoutSeam | undefined;
|
|
30
|
+
readonly set: (seam: BrowserCheckoutSeam) => void;
|
|
31
|
+
/**
|
|
32
|
+
* Forgets the held seam. Call this from the browser's own disposal path
|
|
33
|
+
* (services.ts, registered on the SAME disposal scope the browser sessions
|
|
34
|
+
* teardown runs on), so a daemon that has started shutting its browser down
|
|
35
|
+
* cannot hand a checkout call a seam whose `driverFor`/`cardFieldGuard` point
|
|
36
|
+
* at an engine that is being (or has been) torn down. After this, `get()`
|
|
37
|
+
* returns `undefined` again, and `payments.checkout.begin`/`.fillCard` fall
|
|
38
|
+
* back to their ordinary "checkout is not available right now" 409 refusal,
|
|
39
|
+
* see checkout-handlers.ts, rather than reaching into a disposed engine.
|
|
40
|
+
*/
|
|
41
|
+
readonly clear: () => void;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function createBrowserCheckoutSeamHolder(): BrowserCheckoutSeamHolder {
|
|
45
|
+
let seam: BrowserCheckoutSeam | undefined;
|
|
46
|
+
return {
|
|
47
|
+
get: () => seam,
|
|
48
|
+
set: (received) => {
|
|
49
|
+
seam = received;
|
|
50
|
+
},
|
|
51
|
+
clear: () => {
|
|
52
|
+
seam = undefined;
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
@@ -20,6 +20,8 @@ import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
|
20
20
|
import type { ClusterCoordinator } from '@pellux/goodvibes-sdk/platform/cluster';
|
|
21
21
|
import type { GatewayMethodCatalog } from '@pellux/goodvibes-sdk/platform/control-plane';
|
|
22
22
|
import type { SecretsManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
23
|
+
import type { ChannelDeliveryRouter } from '@pellux/goodvibes-sdk/platform/channels';
|
|
24
|
+
import type { ProviderRegistry } from '@pellux/goodvibes-sdk/platform/providers';
|
|
23
25
|
import { registerDaemonHandlers, type DaemonHandlerSurfaces } from '../daemon/handlers/index.ts';
|
|
24
26
|
import type { HandlerContext, HandlerLogger } from '../daemon/handlers/context.ts';
|
|
25
27
|
import { createDaemonCredentialStore } from '../daemon/handlers/credentials.ts';
|
|
@@ -31,6 +33,7 @@ import { registerRemoteSurface } from '../daemon/handlers/remote/index.ts';
|
|
|
31
33
|
import { createPaymentsServices } from './payments-composition.ts';
|
|
32
34
|
import { inboxPollerGate } from './cluster-composition.ts';
|
|
33
35
|
import type { ShellPathService } from '@/runtime/index.ts';
|
|
36
|
+
import type { BrowserCheckoutSeamHolder } from './browser-checkout-seam-holder.ts';
|
|
34
37
|
|
|
35
38
|
export interface DaemonHandlerCompositionOptions {
|
|
36
39
|
readonly gatewayMethods: GatewayMethodCatalog;
|
|
@@ -46,6 +49,15 @@ export interface DaemonHandlerCompositionOptions {
|
|
|
46
49
|
* composition root; the poller is never started outside it.
|
|
47
50
|
*/
|
|
48
51
|
readonly clusterCoordinator: ClusterCoordinator;
|
|
52
|
+
/**
|
|
53
|
+
* Where `payments.checkout.*` reads the browser-checkout seam, filled later
|
|
54
|
+
* by services.ts's own `onBrowserCheckout`. See
|
|
55
|
+
* runtime/browser-checkout-seam-holder.ts and payments-composition.ts's
|
|
56
|
+
* header for why this has to be a getter rather than the seam itself.
|
|
57
|
+
*/
|
|
58
|
+
readonly checkoutSeam: BrowserCheckoutSeamHolder['get'];
|
|
59
|
+
readonly channelDeliveryRouter: Pick<ChannelDeliveryRouter, 'deliver'>;
|
|
60
|
+
readonly providerRegistry: Pick<ProviderRegistry, 'getCurrentModel' | 'getForModel'>;
|
|
49
61
|
}
|
|
50
62
|
|
|
51
63
|
export function createDaemonHandlerComposition(
|
|
@@ -77,8 +89,9 @@ export function createDaemonHandlerComposition(
|
|
|
77
89
|
registerDrafts: (ctx) => registerDraftMethods(ctx),
|
|
78
90
|
// The payment stores need a path resolver and a daemon-scoped secret writer,
|
|
79
91
|
// neither of which is on HandlerContext, so they are built here and the
|
|
80
|
-
// provider only carries the teardown. See payments-composition.ts for
|
|
81
|
-
//
|
|
92
|
+
// provider only carries the teardown. See payments-composition.ts for the
|
|
93
|
+
// full checkout composition (address store, notifier, merchant judge,
|
|
94
|
+
// browser-checkout seam).
|
|
82
95
|
registerPayments: () => createPaymentsServices({
|
|
83
96
|
gatewayMethods: options.gatewayMethods,
|
|
84
97
|
configManager: options.configManager,
|
|
@@ -99,17 +112,21 @@ export function createDaemonHandlerComposition(
|
|
|
99
112
|
// (inboxPollerGate). It is the right answer once there is something to
|
|
100
113
|
// elect over, and today there is not: ClusterConsumerGate.start() is
|
|
101
114
|
// specified to not resolve until consumption has actually begun, and this
|
|
102
|
-
// composition has no payments consumer to start
|
|
103
|
-
//
|
|
104
|
-
// in the election and
|
|
115
|
+
// composition has no payments consumer to start it. Checkout is now
|
|
116
|
+
// wired (payments.checkout.begin/.fillCard), but registering a gate whose
|
|
117
|
+
// start() does nothing would STILL put a fake consumer in the election and
|
|
118
|
+
// in `cluster status`; a real election is a separate piece of work, left
|
|
119
|
+
// for a later pass, not something wiring the checkout pair itself needed.
|
|
105
120
|
//
|
|
106
|
-
// So the honest reading of the topology
|
|
107
|
-
// only node, and it is trivially the one that would
|
|
108
|
-
// means no payments election has been held and this
|
|
109
|
-
// have won it. False on every node is also the safe
|
|
110
|
-
// refuses on false.
|
|
111
|
-
// with a real gate and holdsSurface().
|
|
121
|
+
// So the honest reading of the topology stays what it was: clustering off
|
|
122
|
+
// means this is the only node, and it is trivially the one that would
|
|
123
|
+
// spend; clustering on means no payments election has been held and this
|
|
124
|
+
// node cannot claim to have won it. False on every node is also the safe
|
|
125
|
+
// direction, checkPaymentGates refuses on false.
|
|
112
126
|
isPaymentsLeader: () => !options.clusterCoordinator.enabled,
|
|
127
|
+
checkoutSeam: options.checkoutSeam,
|
|
128
|
+
channelDeliveryRouter: options.channelDeliveryRouter,
|
|
129
|
+
providerRegistry: options.providerRegistry,
|
|
113
130
|
}).unregister,
|
|
114
131
|
registerRemote: (ctx) => registerRemoteSurface(ctx, { manager: options.distributedRuntime }),
|
|
115
132
|
});
|
|
@@ -31,44 +31,67 @@
|
|
|
31
31
|
* time" for a card;
|
|
32
32
|
* - the purchase audit ledger beside the card file.
|
|
33
33
|
*
|
|
34
|
-
* ── The budget ledger is
|
|
34
|
+
* ── The budget ledger is durable ──────────────────────────────────────────
|
|
35
35
|
*
|
|
36
|
-
* `BudgetLedger`
|
|
37
|
-
*
|
|
38
|
-
* checkout flow, so there
|
|
39
|
-
*
|
|
40
|
-
* is
|
|
41
|
-
*
|
|
42
|
-
*
|
|
36
|
+
* `BudgetLedger` used to be constructed empty here and never persisted, correct
|
|
37
|
+
* only while checkout stayed unattached (the sole writer of a spend record was
|
|
38
|
+
* the checkout flow, so there was nothing to persist). Now that checkout is
|
|
39
|
+
* wired below, `DurableBudgetLedger`
|
|
40
|
+
* (daemon/handlers/payments/budget-store.ts) is used instead: it loads its
|
|
41
|
+
* state from `payments-budget.json` beside the card and purchase files at
|
|
42
|
+
* construction and writes back after every reservation, commit and release, so
|
|
43
|
+
* a daemon restarted mid-day does not hand back a budget it already spent.
|
|
43
44
|
*
|
|
44
|
-
* ──
|
|
45
|
+
* ── The checkout pair, over the sdk 2.0.19 browser-checkout seam ──────────
|
|
45
46
|
*
|
|
46
|
-
* `payments.checkout.begin`
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* browser
|
|
50
|
-
* (
|
|
51
|
-
*
|
|
52
|
-
* `
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
47
|
+
* `payments.checkout.begin`/`.fillCard` need a `CardMaterialRedactor` bound to
|
|
48
|
+
* the SAME browser engine the `browser.*` verbs drive, and this daemon does not
|
|
49
|
+
* build that engine, `composeDaemonBrowser` does (control-plane/routes/
|
|
50
|
+
* browser-composition.ts), inside `registerGatewayVerbGroups`, which THIS
|
|
51
|
+
* composition runs before (see runtime/services.ts). `checkoutSeam` is
|
|
52
|
+
* therefore a GETTER, not a value: services.ts passes `onBrowserCheckout` to
|
|
53
|
+
* `attachWsOnlyGatewayVerbHandlers` wired to fill the SAME holder this getter
|
|
54
|
+
* reads (runtime/browser-checkout-seam-holder.ts), and `register.ts`'s checkout
|
|
55
|
+
* handlers read it fresh on every call rather than once at composition time.
|
|
56
|
+
*
|
|
57
|
+
* The rest of `PaymentsGatewayServiceImpl`'s dependencies this composition owns
|
|
58
|
+
* outright: `configBackedAddressStore` reads the shipping/billing addresses the
|
|
59
|
+
* owner profile already writes into `payments.*Address.*` config keys (see
|
|
60
|
+
* that module's header for the exact defect this closes), and
|
|
61
|
+
* `channelBackedPaymentNotifier`/`createProviderBackedMerchantJudgeModel` adapt
|
|
62
|
+
* this daemon's channel router and provider registry to the ports the SDK
|
|
63
|
+
* declares. The untrusted-content ledger is the SAME process-wide singleton the
|
|
64
|
+
* browser composition binds its engine to (`getProcessUntrustedContentLedger`),
|
|
65
|
+
* never a private one, for the reason browser-composition.ts's header gives:
|
|
66
|
+
* a private ledger would make cross-capability derivation invisible.
|
|
67
|
+
*
|
|
68
|
+
* `channelBackedPaymentNotifier`'s own header names the one piece deliberately
|
|
69
|
+
* left for a later pass: no live inbound-reply correlation, so every purchase
|
|
70
|
+
* settles on the windows' own silence rules rather than an early answer. That
|
|
71
|
+
* is a scoped, disclosed gap, not a silent one.
|
|
58
72
|
*/
|
|
59
73
|
import { controlPlaneStorePath } from '@pellux/goodvibes-sdk/platform/control-plane';
|
|
60
74
|
import type { GatewayMethodCatalog } from '@pellux/goodvibes-sdk/platform/control-plane';
|
|
61
|
-
import {
|
|
62
|
-
import type { PaymentsConfigReader } from '@pellux/goodvibes-sdk/platform/payments';
|
|
75
|
+
import { createModelMerchantJudge, readCvvHandling } from '@pellux/goodvibes-sdk/platform/payments';
|
|
76
|
+
import type { BudgetLedger, PaymentsConfigReader } from '@pellux/goodvibes-sdk/platform/payments';
|
|
77
|
+
import { getProcessUntrustedContentLedger } from '@pellux/goodvibes-sdk/platform/security';
|
|
63
78
|
import type { ConfigManager, SecretsManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
79
|
+
import type { ChannelDeliveryRouter } from '@pellux/goodvibes-sdk/platform/channels';
|
|
80
|
+
import type { ProviderRegistry } from '@pellux/goodvibes-sdk/platform/providers';
|
|
64
81
|
import type { ShellPathService } from '@/runtime/index.ts';
|
|
65
82
|
import {
|
|
66
83
|
DaemonCardStore,
|
|
67
84
|
DaemonPurchaseLedger,
|
|
85
|
+
DurableBudgetLedger,
|
|
86
|
+
channelBackedPaymentNotifier,
|
|
87
|
+
configBackedAddressStore,
|
|
88
|
+
createProviderBackedMerchantJudgeModel,
|
|
68
89
|
registerPaymentsMethods,
|
|
90
|
+
type CheckoutComposition,
|
|
69
91
|
type PaymentsSecretStore,
|
|
70
92
|
} from '../daemon/handlers/payments/index.ts';
|
|
71
93
|
import { GOODVIBES_DAEMON_SURFACE_ROOT } from '../config/surface.ts';
|
|
94
|
+
import type { BrowserCheckoutSeamHolder } from './browser-checkout-seam-holder.ts';
|
|
72
95
|
|
|
73
96
|
export interface PaymentsCompositionOptions {
|
|
74
97
|
readonly configManager: ConfigManager;
|
|
@@ -83,6 +106,12 @@ export interface PaymentsCompositionOptions {
|
|
|
83
106
|
* gates.ts: on a clustered install the wrong answer is a double-spend.
|
|
84
107
|
*/
|
|
85
108
|
readonly isPaymentsLeader: () => boolean;
|
|
109
|
+
/** Where the checkout pair reads the browser-checkout seam; see this file's header. */
|
|
110
|
+
readonly checkoutSeam: BrowserCheckoutSeamHolder['get'];
|
|
111
|
+
/** Delivers a purchase notice; the SAME router every other channel send in this daemon uses. */
|
|
112
|
+
readonly channelDeliveryRouter: Pick<ChannelDeliveryRouter, 'deliver'>;
|
|
113
|
+
/** Judges an unfamiliar merchant's recourse through the currently configured model. */
|
|
114
|
+
readonly providerRegistry: Pick<ProviderRegistry, 'getCurrentModel' | 'getForModel'>;
|
|
86
115
|
}
|
|
87
116
|
|
|
88
117
|
export interface PaymentsServices {
|
|
@@ -118,8 +147,13 @@ function livePaymentsConfig(configManager: ConfigManager): PaymentsConfigReader
|
|
|
118
147
|
/**
|
|
119
148
|
* Build the payment stores and bind the answerable verbs to them.
|
|
120
149
|
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
150
|
+
* The card and purchase stores read lazily and write only when a verb asks
|
|
151
|
+
* them to, so composing either by itself creates no file activity. The budget
|
|
152
|
+
* ledger is different: `DurableBudgetLedger`'s constructor reads
|
|
153
|
+
* `payments-budget.json` synchronously to load today's pools
|
|
154
|
+
* (daemon/handlers/payments/budget-store.ts), so constructing the result of
|
|
155
|
+
* THIS function does touch disk, once, for that one file, before any verb is
|
|
156
|
+
* ever called.
|
|
123
157
|
*/
|
|
124
158
|
export function createPaymentsServices(options: PaymentsCompositionOptions): PaymentsServices {
|
|
125
159
|
const config = livePaymentsConfig(options.configManager);
|
|
@@ -131,13 +165,23 @@ export function createPaymentsServices(options: PaymentsCompositionOptions): Pay
|
|
|
131
165
|
const purchases = new DaemonPurchaseLedger({
|
|
132
166
|
filePath: controlPlaneStorePath(options.shellPaths, GOODVIBES_DAEMON_SURFACE_ROOT, 'payments-purchases.json'),
|
|
133
167
|
});
|
|
134
|
-
const budget = new
|
|
168
|
+
const budget = new DurableBudgetLedger(
|
|
169
|
+
controlPlaneStorePath(options.shellPaths, GOODVIBES_DAEMON_SURFACE_ROOT, 'payments-budget.json'),
|
|
170
|
+
);
|
|
171
|
+
const checkout: CheckoutComposition = {
|
|
172
|
+
seam: options.checkoutSeam,
|
|
173
|
+
addresses: configBackedAddressStore(config),
|
|
174
|
+
notifier: channelBackedPaymentNotifier(config, options.channelDeliveryRouter),
|
|
175
|
+
merchantJudge: createModelMerchantJudge(createProviderBackedMerchantJudgeModel(options.providerRegistry)),
|
|
176
|
+
untrusted: getProcessUntrustedContentLedger(),
|
|
177
|
+
};
|
|
135
178
|
const unregister = registerPaymentsMethods(options.gatewayMethods, {
|
|
136
179
|
cards,
|
|
137
180
|
purchases,
|
|
138
181
|
budget,
|
|
139
182
|
config,
|
|
140
183
|
isPaymentsLeader: options.isPaymentsLeader,
|
|
184
|
+
checkout,
|
|
141
185
|
});
|
|
142
186
|
return { cards, purchases, budget, unregister };
|
|
143
187
|
}
|
package/src/runtime/services.ts
CHANGED
|
@@ -56,6 +56,7 @@ import { createTriggerServices } from './trigger-services.ts';
|
|
|
56
56
|
import { createWorkstreamServices } from '@pellux/goodvibes-sdk/platform/orchestration';
|
|
57
57
|
import { wireFleetNeedsInputPush } from './fleet-needs-input-push.ts';
|
|
58
58
|
import { createDaemonHandlerComposition } from './daemon-handler-composition.ts';
|
|
59
|
+
import { createBrowserCheckoutSeamHolder } from './browser-checkout-seam-holder.ts';
|
|
59
60
|
import { createDevicePostureServices } from './device-posture-composition.ts';
|
|
60
61
|
// Re-exported so the daemon entrypoint reaches the housekeeping sweep through
|
|
61
62
|
// the same module it already imports the runtime graph from. `installDevicePosture`
|
|
@@ -387,9 +388,17 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
387
388
|
const { clusterGroup, clusterCoordinator } = createClusterServices({
|
|
388
389
|
configManager, shellPaths, secretsManager,
|
|
389
390
|
});
|
|
391
|
+
// ONE router, not two (a second built from the same four arguments would
|
|
392
|
+
// differ from the one replies leave through). Moved up from its original
|
|
393
|
+
// spot near the ws-only verb options (still consumed there): the payments
|
|
394
|
+
// composition below needs it too.
|
|
395
|
+
const channelDeliveryRouter = deliveryManager.getDeliveryRouter();
|
|
390
396
|
// Daemon handler surfaces (see daemon-handler-composition.ts); the inbox
|
|
391
397
|
// poller registers itself with the coordinator rather than starting eagerly,
|
|
392
398
|
// and the payments family stops being a cataloged 501 facade there.
|
|
399
|
+
// browserCheckoutSeam fills once `onBrowserCheckout` fires below, after this
|
|
400
|
+
// composition; read lazily, per call. See browser-checkout-seam-holder.ts.
|
|
401
|
+
const browserCheckoutSeam = createBrowserCheckoutSeamHolder();
|
|
393
402
|
const daemonHandlers = createDaemonHandlerComposition({
|
|
394
403
|
gatewayMethods,
|
|
395
404
|
secretsManager,
|
|
@@ -399,6 +408,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
399
408
|
shellPaths,
|
|
400
409
|
distributedRuntime,
|
|
401
410
|
clusterCoordinator,
|
|
411
|
+
checkoutSeam: browserCheckoutSeam.get, channelDeliveryRouter, providerRegistry,
|
|
402
412
|
});
|
|
403
413
|
|
|
404
414
|
// Remote runners and the sandboxes tool calls are confined to; see
|
|
@@ -426,12 +436,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
426
436
|
const policyRuntimeState = new PolicyRuntimeState();
|
|
427
437
|
const fileCache = new FileStateCache();
|
|
428
438
|
const projectIndex = new ProjectIndex(workingDirectory);
|
|
429
|
-
//
|
|
430
|
-
// same four arguments AutomationDeliveryManager builds its own from, so the
|
|
431
|
-
// router the gateway verbs held and the router replies actually leave through
|
|
432
|
-
// were different objects, and a delivery strategy registered on one was
|
|
433
|
-
// invisible to the other. The manager's is the one that replies; it is the one.
|
|
434
|
-
const channelDeliveryRouter = deliveryManager.getDeliveryRouter();
|
|
439
|
+
// channelDeliveryRouter now built earlier, near clusterCoordinator above.
|
|
435
440
|
const processManager = new ProcessManager();
|
|
436
441
|
// The phase/work-item orchestration engine, constructed before the process
|
|
437
442
|
// registry so its fleet nodes (workstream/phase/work-item) can be folded in
|
|
@@ -533,6 +538,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
533
538
|
// is what let these stores write to the unscoped orphan directory.
|
|
534
539
|
surfaceRoot: GOODVIBES_DAEMON_SURFACE_ROOT,
|
|
535
540
|
homeDirectory, emailServiceDeps, describeEmailConfigProblem, processRegistry,
|
|
541
|
+
onBrowserCheckout: browserCheckoutSeam.set,
|
|
536
542
|
// The registration-gated surface, not the raw manager: an explicit create in
|
|
537
543
|
// an unregistered workspace refuses with something actionable.
|
|
538
544
|
workspaceCheckpointManager: checkpointing.gatewayManager,
|
|
@@ -564,6 +570,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
564
570
|
disposal: disposalScope.registry,
|
|
565
571
|
...wireFleetNeedsInputPush({ registry: processRegistry, runtimeBus: options.runtimeBus, sessionBroker }),
|
|
566
572
|
});
|
|
573
|
+
disposalScope.registry.add('browser checkout seam holder', () => browserCheckoutSeam.clear()); // newer than 'browser sessions' above, so runs first
|
|
567
574
|
// A loopback fetch that isn't allow-listed asks once through the approval
|
|
568
575
|
// broker; "allow for this project" persists and later fetches never ask. Built
|
|
569
576
|
// once and shared with the tool registry so both ask alike.
|