deepline 0.3.90 → 0.3.91
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/dist/bundling-sources/sdk/src/client.ts +101 -6
- package/dist/bundling-sources/sdk/src/monitor-fleet-contract.ts +2 -556
- package/dist/bundling-sources/sdk/src/monitor-fleets.ts +2 -2
- package/dist/bundling-sources/sdk/src/release.ts +7 -12
- package/dist/bundling-sources/sdk/src/types.ts +6 -1
- package/dist/bundling-sources/shared_libs/integration-contracts/monitor-fleet-contract.ts +549 -0
- package/dist/bundling-sources/shared_libs/monitors/fleets/errors.ts +96 -0
- package/dist/bundling-sources/shared_libs/monitors/fleets/limits.ts +8 -0
- package/dist/bundling-sources/shared_libs/monitors/fleets/plan.ts +320 -0
- package/dist/bundling-sources/shared_libs/monitors/fleets/verdict.ts +106 -0
- package/dist/bundling-sources/shared_libs/monitors/http-error-response.ts +5 -1
- package/dist/bundling-sources/shared_libs/monitors/monitor-fleet-limits.ts +2 -12
- package/dist/bundling-sources/shared_libs/monitors/package.json +107 -5
- package/dist/bundling-sources/shared_libs/monitors/provider-monitor-batch-operation.ts +12 -7
- package/dist/bundling-sources/shared_libs/observability/scheduled-jobs.json +36 -0
- package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/db-session-lease-store.ts +578 -0
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/commands/roll-execution-relay.ts +521 -0
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/commands/roll-receipt-gateway.ts +1378 -0
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/commands/runtime-topology.ts +1024 -0
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-gateway-route-probe.ts +193 -0
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-machine-lifecycle.ts +104 -0
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-machines.ts +1844 -0
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-registry.ts +21 -0
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/process-tree.ts +40 -0
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-gateway-drain.ts +262 -0
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-surface.ts +98 -0
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-topology.ts +303 -0
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-tuple-publication-guardian.ts +70 -0
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-tuple-recovery.ts +519 -0
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/vercel-fly-rollback-alignment.ts +280 -0
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/vercel-production-gate.ts +159 -0
- package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/memory-store.ts +114 -0
- package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +44 -5
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/child-play-resolver.ts +26 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +3662 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/progress-sidecar.ts +47 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-deadline.ts +84 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-gateway-transport.ts +91 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-heartbeat.ts +424 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-progress-push.ts +321 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-sheet-persistence-retry.ts +110 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-terminal-push.ts +625 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +46 -3
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/receipt-gateway.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/webhook-receipt-retry.ts +50 -0
- package/dist/bundling-sources/shared_libs/plays/bundling/repository-adapter.ts +24 -0
- package/dist/bundling-sources/shared_libs/plays/child-run-projection.ts +174 -0
- package/dist/bundling-sources/shared_libs/plays/deploy/api-client.ts +267 -0
- package/dist/bundling-sources/shared_libs/plays/deploy/control-plane-types.ts +73 -0
- package/dist/bundling-sources/shared_libs/plays/deploy/manifest.ts +162 -0
- package/dist/bundling-sources/shared_libs/plays/deploy/project-serializer.ts +87 -0
- package/dist/bundling-sources/shared_libs/plays/deploy/source-file-path.ts +26 -0
- package/dist/bundling-sources/shared_libs/plays/deploy/target-paths.ts +36 -0
- package/dist/bundling-sources/shared_libs/plays/launch/input-contract.ts +400 -0
- package/dist/bundling-sources/shared_libs/plays/launch/prepare.ts +96 -0
- package/dist/bundling-sources/shared_libs/plays/local-file-discovery.ts +385 -0
- package/dist/bundling-sources/shared_libs/plays/play-grep-search.ts +79 -6
- package/dist/bundling-sources/shared_libs/plays/play-reference.ts +117 -0
- package/dist/bundling-sources/shared_libs/plays/run-input-summary.ts +260 -0
- package/dist/bundling-sources/shared_libs/plays/run-summary-types.ts +49 -0
- package/dist/bundling-sources/shared_libs/plays/secrets/errors.ts +19 -0
- package/dist/bundling-sources/shared_libs/plays/sheet-filter.ts +347 -0
- package/dist/bundling-sources/shared_libs/plays/standard-webhooks.ts +117 -0
- package/dist/bundling-sources/shared_libs/plays/status-result.ts +388 -0
- package/dist/bundling-sources/shared_libs/plays/transform-factor-structure.ts +377 -0
- package/dist/bundling-sources/shared_libs/plays/webhook-payload.ts +113 -0
- package/dist/cli/index.js +369 -162
- package/dist/cli/index.mjs +369 -162
- package/dist/{compiler-manifest-BaJfE-5H.d.mts → compiler-manifest-x14KL83E.d.mts} +1 -1
- package/dist/{compiler-manifest-BaJfE-5H.d.ts → compiler-manifest-x14KL83E.d.ts} +1 -1
- package/dist/index.d.mts +45 -8
- package/dist/index.d.ts +45 -8
- package/dist/index.js +61 -14
- package/dist/index.mjs +61 -14
- package/dist/install-integrity.json +54 -2
- package/dist/monitor-fleet-contract.d.mts +3 -10
- package/dist/monitor-fleet-contract.d.ts +3 -10
- package/dist/monitor-fleet-contract.js +2 -0
- package/dist/monitor-fleet-contract.mjs +1 -1
- package/dist/plays/bundle-play-file.d.mts +2 -2
- package/dist/plays/bundle-play-file.d.ts +2 -2
- package/dist/release.d.mts +3 -3
- package/dist/release.d.ts +3 -3
- package/dist/release.js +7 -12
- package/dist/release.mjs +7 -12
- package/package.json +2 -2
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The whole fleet planning decision, as pure functions.
|
|
3
|
+
*
|
|
4
|
+
* Everything here is a function of values the caller already read: the source
|
|
5
|
+
* frontier, the monitors the fleet owns, and the organization's limits. No
|
|
6
|
+
* Convex, no Customer DB, no provider. That is deliberate - every membership
|
|
7
|
+
* bug this feature has had lived in these rules, and each one used to cost a
|
|
8
|
+
* preview deploy to see. Here they cost a millisecond.
|
|
9
|
+
*
|
|
10
|
+
* The output is the `members` array `providerMonitorFleets:commitPlan` takes,
|
|
11
|
+
* verbatim. Planning decides; the commit publishes.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { MONITOR_FLEET_REMOVAL_GRACE_MS } from '../../integration-contracts/monitor-fleet-contract';
|
|
15
|
+
import { FLEET_BLOCKED_BY } from './limits';
|
|
16
|
+
export { FLEET_BLOCKED_BY } from './limits';
|
|
17
|
+
|
|
18
|
+
// Rollout access is checked by convergence immediately before provider work,
|
|
19
|
+
// not by this pure planner: an admitted plan can outlive rollout access.
|
|
20
|
+
|
|
21
|
+
export type FleetMemberAction =
|
|
22
|
+
| 'create'
|
|
23
|
+
| 'update'
|
|
24
|
+
| 'remove'
|
|
25
|
+
| 'blocked'
|
|
26
|
+
| 'grace';
|
|
27
|
+
|
|
28
|
+
/** One monitor the fleet wants, fully resolved from its source row. */
|
|
29
|
+
export type DesiredFleetMember = {
|
|
30
|
+
sourceKey: string;
|
|
31
|
+
monitorKey: string;
|
|
32
|
+
/** The account this monitor watches. Null when the tool declares none. */
|
|
33
|
+
entityKey: string | null;
|
|
34
|
+
/** The rendered monitor definition: `{ key, tool, payload }`. */
|
|
35
|
+
definition: Record<string, unknown>;
|
|
36
|
+
desiredHash: string;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/** A monitor the fleet already owns, as Convex reports it. */
|
|
40
|
+
export type OwnedFleetMonitor = {
|
|
41
|
+
monitorKey: string;
|
|
42
|
+
sourceKey: string | null;
|
|
43
|
+
entityKey: string | null;
|
|
44
|
+
convergence:
|
|
45
|
+
| 'pending'
|
|
46
|
+
| 'submitting'
|
|
47
|
+
| 'rate_limited'
|
|
48
|
+
| 'settled'
|
|
49
|
+
| 'blocked'
|
|
50
|
+
| 'failed';
|
|
51
|
+
desiredHash: string | null;
|
|
52
|
+
appliedHash: string | null;
|
|
53
|
+
firstMissingAt: number | null;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export type PlannedFleetMember = {
|
|
57
|
+
monitorKey: string;
|
|
58
|
+
sourceKey: string;
|
|
59
|
+
entityKey?: string;
|
|
60
|
+
action: FleetMemberAction;
|
|
61
|
+
desiredHash?: string;
|
|
62
|
+
desiredDefinition?: Record<string, unknown>;
|
|
63
|
+
blockedBy?: string;
|
|
64
|
+
/** True when the commit must be given a monitor seed for this key. */
|
|
65
|
+
needsSeed: boolean;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export type FleetPlan = {
|
|
69
|
+
members: PlannedFleetMember[];
|
|
70
|
+
creates: number;
|
|
71
|
+
updates: number;
|
|
72
|
+
removes: number;
|
|
73
|
+
/** Refusals by the limit that caused them, e.g. `{'monitor.watched_entities': 3}`. */
|
|
74
|
+
blocked: Record<string, number>;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export type FleetMembershipMember = {
|
|
78
|
+
sourceKey: string;
|
|
79
|
+
firstMissingAt: number | null;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export type FleetMembershipDecision = {
|
|
83
|
+
/** Source keys the fleet holds after this plan, in selection order. */
|
|
84
|
+
selected: string[];
|
|
85
|
+
/** Slots held by members whose grace has not expired. */
|
|
86
|
+
reservedGraceSlots: number;
|
|
87
|
+
/** Members whose row vanished on this plan. Grace starts now. */
|
|
88
|
+
markMissing: string[];
|
|
89
|
+
/** Members whose grace has expired and are now removed. */
|
|
90
|
+
remove: string[];
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Slots held by members whose source row has vanished but whose removal grace
|
|
95
|
+
* has not expired.
|
|
96
|
+
*
|
|
97
|
+
* Such a member is still a member: it keeps its monitor, and grace exists so a
|
|
98
|
+
* row that comes back is restored rather than recreated. Handing its vacancy
|
|
99
|
+
* to the next ranked candidate immediately would push the fleet past its own
|
|
100
|
+
* limit, and then the reappearance grace is designed to absorb would force an
|
|
101
|
+
* eviction to get back under it - exactly the churn stickiness promises not to
|
|
102
|
+
* cause. Reserve the slot until grace actually expires.
|
|
103
|
+
*/
|
|
104
|
+
export function countReservedRemovalGraceSlots(input: {
|
|
105
|
+
members: readonly FleetMembershipMember[];
|
|
106
|
+
selectedSourceKeys: ReadonlySet<string>;
|
|
107
|
+
now: number;
|
|
108
|
+
}): number {
|
|
109
|
+
return input.members.filter((member) => {
|
|
110
|
+
if (input.selectedSourceKeys.has(member.sourceKey)) return false;
|
|
111
|
+
// Not yet marked missing means this plan is the one that starts grace.
|
|
112
|
+
return (
|
|
113
|
+
member.firstMissingAt === null ||
|
|
114
|
+
input.now - member.firstMissingAt < MONITOR_FLEET_REMOVAL_GRACE_MS
|
|
115
|
+
);
|
|
116
|
+
}).length;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Who the fleet holds after a plan, given what the source still returns.
|
|
121
|
+
*
|
|
122
|
+
* The whole sticky/grace/limit rule in one place: an active member keeps its
|
|
123
|
+
* slot while its row exists, a vanished member keeps its slot until grace
|
|
124
|
+
* expires, and ranking only ever fills what is genuinely free.
|
|
125
|
+
*/
|
|
126
|
+
export function decideFleetMembership(input: {
|
|
127
|
+
limit: number;
|
|
128
|
+
members: readonly FleetMembershipMember[];
|
|
129
|
+
/** Member source keys whose row the source still returns. */
|
|
130
|
+
presentSourceKeys: ReadonlySet<string>;
|
|
131
|
+
/** Ranked non-member candidates, best first. */
|
|
132
|
+
candidates: readonly string[];
|
|
133
|
+
now: number;
|
|
134
|
+
}): FleetMembershipDecision {
|
|
135
|
+
const selected: string[] = [];
|
|
136
|
+
const selectedKeys = new Set<string>();
|
|
137
|
+
// Sticky: ranking only fills vacancies, so score movement cannot churn an
|
|
138
|
+
// active member out of the fleet.
|
|
139
|
+
for (const member of input.members) {
|
|
140
|
+
if (!input.presentSourceKeys.has(member.sourceKey)) continue;
|
|
141
|
+
if (selectedKeys.has(member.sourceKey)) continue;
|
|
142
|
+
selected.push(member.sourceKey);
|
|
143
|
+
selectedKeys.add(member.sourceKey);
|
|
144
|
+
}
|
|
145
|
+
const reservedGraceSlots = countReservedRemovalGraceSlots({
|
|
146
|
+
members: input.members,
|
|
147
|
+
selectedSourceKeys: selectedKeys,
|
|
148
|
+
now: input.now,
|
|
149
|
+
});
|
|
150
|
+
for (const candidate of input.candidates) {
|
|
151
|
+
if (selected.length + reservedGraceSlots >= input.limit) break;
|
|
152
|
+
if (selectedKeys.has(candidate)) continue;
|
|
153
|
+
selected.push(candidate);
|
|
154
|
+
selectedKeys.add(candidate);
|
|
155
|
+
}
|
|
156
|
+
const markMissing: string[] = [];
|
|
157
|
+
const remove: string[] = [];
|
|
158
|
+
for (const member of input.members) {
|
|
159
|
+
if (selectedKeys.has(member.sourceKey)) continue;
|
|
160
|
+
if (member.firstMissingAt === null) {
|
|
161
|
+
markMissing.push(member.sourceKey);
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (input.now - member.firstMissingAt < MONITOR_FLEET_REMOVAL_GRACE_MS) {
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
remove.push(member.sourceKey);
|
|
168
|
+
}
|
|
169
|
+
return { selected, reservedGraceSlots, markMissing, remove };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** The account a monitor is counted against. Never null, so nothing is uncounted. */
|
|
173
|
+
export function fleetEntityKeyFor(input: {
|
|
174
|
+
monitorKey: string;
|
|
175
|
+
entityKey: string | null;
|
|
176
|
+
}): string {
|
|
177
|
+
// A monitor with no derivable account is its own account, so it still
|
|
178
|
+
// occupies exactly one unit of allowance rather than slipping through.
|
|
179
|
+
return input.entityKey ?? `monitor:${input.monitorKey}`;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export type FleetEntitlements = {
|
|
183
|
+
/** New accounts this organization may still open. */
|
|
184
|
+
watchedEntitiesRemaining: number;
|
|
185
|
+
/** Monitors admitted against any one account. */
|
|
186
|
+
monitorsPerEntity: number;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Turn "what the fleet wants" plus "what it already owns" into commit members.
|
|
191
|
+
*
|
|
192
|
+
* Ordering matters and is deliberate: creates are admitted in selection order,
|
|
193
|
+
* so a fleet at its account limit keeps the members the source ranked highest
|
|
194
|
+
* rather than whichever ones happened to sort first by key.
|
|
195
|
+
*/
|
|
196
|
+
export function planFleetMembers(input: {
|
|
197
|
+
desired: readonly DesiredFleetMember[];
|
|
198
|
+
owned: readonly OwnedFleetMonitor[];
|
|
199
|
+
/** Source keys whose grace expired, from `decideFleetMembership`. */
|
|
200
|
+
remove: readonly string[];
|
|
201
|
+
/** Source keys whose row vanished on this plan. */
|
|
202
|
+
markMissing: readonly string[];
|
|
203
|
+
entitlements: FleetEntitlements;
|
|
204
|
+
/**
|
|
205
|
+
* Monitors already counted against an account, org-wide. An account absent
|
|
206
|
+
* from this map is one the organization does not watch yet, and opening it
|
|
207
|
+
* is what costs allowance.
|
|
208
|
+
*/
|
|
209
|
+
entityMonitorCounts: ReadonlyMap<string, number>;
|
|
210
|
+
}): FleetPlan {
|
|
211
|
+
const ownedByKey = new Map(
|
|
212
|
+
input.owned.map((monitor) => [monitor.monitorKey, monitor]),
|
|
213
|
+
);
|
|
214
|
+
const ownedBySource = new Map<string, OwnedFleetMonitor>();
|
|
215
|
+
for (const monitor of input.owned) {
|
|
216
|
+
if (monitor.sourceKey) ownedBySource.set(monitor.sourceKey, monitor);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const entityCounts = new Map(input.entityMonitorCounts);
|
|
220
|
+
const heldEntities = new Set(
|
|
221
|
+
[...entityCounts.entries()]
|
|
222
|
+
.filter(([, count]) => count > 0)
|
|
223
|
+
.map(([entityKey]) => entityKey),
|
|
224
|
+
);
|
|
225
|
+
let newEntitiesUsed = 0;
|
|
226
|
+
|
|
227
|
+
const members: PlannedFleetMember[] = [];
|
|
228
|
+
const blocked: Record<string, number> = {};
|
|
229
|
+
let creates = 0;
|
|
230
|
+
let updates = 0;
|
|
231
|
+
let removes = 0;
|
|
232
|
+
|
|
233
|
+
const refuse = (member: PlannedFleetMember, blockedBy: string) => {
|
|
234
|
+
blocked[blockedBy] = (blocked[blockedBy] ?? 0) + 1;
|
|
235
|
+
members.push({ ...member, action: 'blocked', blockedBy });
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
for (const desired of input.desired) {
|
|
239
|
+
const owned =
|
|
240
|
+
ownedByKey.get(desired.monitorKey) ??
|
|
241
|
+
ownedBySource.get(desired.sourceKey);
|
|
242
|
+
const entityKey = fleetEntityKeyFor(desired);
|
|
243
|
+
const base: PlannedFleetMember = {
|
|
244
|
+
monitorKey: desired.monitorKey,
|
|
245
|
+
sourceKey: desired.sourceKey,
|
|
246
|
+
entityKey,
|
|
247
|
+
action: 'create',
|
|
248
|
+
desiredHash: desired.desiredHash,
|
|
249
|
+
desiredDefinition: desired.definition,
|
|
250
|
+
needsSeed: true,
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const applied = owned?.appliedHash ?? null;
|
|
254
|
+
if (applied !== null) {
|
|
255
|
+
// The provider already holds this monitor. Nothing to admit against a
|
|
256
|
+
// limit: it is already counted, and an update never opens an account.
|
|
257
|
+
const settledOnDesired =
|
|
258
|
+
applied === desired.desiredHash &&
|
|
259
|
+
owned?.convergence === 'settled' &&
|
|
260
|
+
owned.firstMissingAt === null;
|
|
261
|
+
if (settledOnDesired) continue;
|
|
262
|
+
// A member whose row came back inside its grace window is restored by
|
|
263
|
+
// re-publishing the state it already holds. That clears the removal
|
|
264
|
+
// clock through the same guarded transition every other change uses,
|
|
265
|
+
// rather than needing a second write path that could disagree with it.
|
|
266
|
+
members.push({ ...base, action: 'update', needsSeed: false });
|
|
267
|
+
updates += 1;
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const onEntity = entityCounts.get(entityKey) ?? 0;
|
|
272
|
+
if (onEntity >= input.entitlements.monitorsPerEntity) {
|
|
273
|
+
refuse(base, FLEET_BLOCKED_BY.monitorsPerEntity);
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
if (
|
|
277
|
+
!heldEntities.has(entityKey) &&
|
|
278
|
+
newEntitiesUsed >= input.entitlements.watchedEntitiesRemaining
|
|
279
|
+
) {
|
|
280
|
+
refuse(base, FLEET_BLOCKED_BY.watchedEntities);
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
if (!heldEntities.has(entityKey)) {
|
|
284
|
+
heldEntities.add(entityKey);
|
|
285
|
+
newEntitiesUsed += 1;
|
|
286
|
+
}
|
|
287
|
+
entityCounts.set(entityKey, onEntity + 1);
|
|
288
|
+
members.push(base);
|
|
289
|
+
creates += 1;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const plannedKeys = new Set(members.map((member) => member.monitorKey));
|
|
293
|
+
for (const sourceKey of input.markMissing) {
|
|
294
|
+
const owned = ownedBySource.get(sourceKey);
|
|
295
|
+
if (!owned || plannedKeys.has(owned.monitorKey)) continue;
|
|
296
|
+
members.push({
|
|
297
|
+
monitorKey: owned.monitorKey,
|
|
298
|
+
sourceKey,
|
|
299
|
+
...(owned.entityKey ? { entityKey: owned.entityKey } : {}),
|
|
300
|
+
action: 'grace',
|
|
301
|
+
needsSeed: false,
|
|
302
|
+
});
|
|
303
|
+
plannedKeys.add(owned.monitorKey);
|
|
304
|
+
}
|
|
305
|
+
for (const sourceKey of input.remove) {
|
|
306
|
+
const owned = ownedBySource.get(sourceKey);
|
|
307
|
+
if (!owned || plannedKeys.has(owned.monitorKey)) continue;
|
|
308
|
+
members.push({
|
|
309
|
+
monitorKey: owned.monitorKey,
|
|
310
|
+
sourceKey,
|
|
311
|
+
...(owned.entityKey ? { entityKey: owned.entityKey } : {}),
|
|
312
|
+
action: 'remove',
|
|
313
|
+
needsSeed: false,
|
|
314
|
+
});
|
|
315
|
+
plannedKeys.add(owned.monitorKey);
|
|
316
|
+
removes += 1;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return { members, creates, updates, removes, blocked };
|
|
320
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One place that decides what a fleet's convergence counts mean.
|
|
3
|
+
*
|
|
4
|
+
* The verdict is server-owned on purpose. Every surface that has ever shown a
|
|
5
|
+
* fleet - the CLI, the API, a dashboard - has to answer the same question
|
|
6
|
+
* ("is this fleet done, and if not, is anything wrong?"), and each one that
|
|
7
|
+
* derives it from raw counts derives it slightly differently. The counts are
|
|
8
|
+
* the fact; this is the single reading of them.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export type FleetConvergenceCounts = {
|
|
12
|
+
pending: number;
|
|
13
|
+
submitting: number;
|
|
14
|
+
rate_limited: number;
|
|
15
|
+
settled: number;
|
|
16
|
+
blocked: number;
|
|
17
|
+
failed: number;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/** The lifecycle a fleet row records. */
|
|
21
|
+
export type FleetLifecycleStatus =
|
|
22
|
+
| 'active'
|
|
23
|
+
| 'deactivating'
|
|
24
|
+
| 'deactivated'
|
|
25
|
+
| 'billing_suspending'
|
|
26
|
+
| 'billing_suspended'
|
|
27
|
+
| 'billing_reactivating';
|
|
28
|
+
|
|
29
|
+
/** What a caller is told the fleet is doing. */
|
|
30
|
+
export type FleetStatusVerdict =
|
|
31
|
+
| 'converging'
|
|
32
|
+
| 'converged'
|
|
33
|
+
| 'degraded'
|
|
34
|
+
| 'deactivating'
|
|
35
|
+
| 'deactivated'
|
|
36
|
+
| 'billing_suspending'
|
|
37
|
+
| 'billing_suspended'
|
|
38
|
+
| 'billing_reactivating';
|
|
39
|
+
|
|
40
|
+
const ZERO_FLEET_COUNTS: FleetConvergenceCounts = {
|
|
41
|
+
pending: 0,
|
|
42
|
+
submitting: 0,
|
|
43
|
+
rate_limited: 0,
|
|
44
|
+
settled: 0,
|
|
45
|
+
blocked: 0,
|
|
46
|
+
failed: 0,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export function fleetCounts(
|
|
50
|
+
raw: Partial<FleetConvergenceCounts> | null | undefined,
|
|
51
|
+
): FleetConvergenceCounts {
|
|
52
|
+
return { ...ZERO_FLEET_COUNTS, ...(raw ?? {}) };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Monitors a provider still owes an answer for. */
|
|
56
|
+
export function fleetOutstanding(counts: FleetConvergenceCounts): number {
|
|
57
|
+
return counts.pending + counts.submitting + counts.rate_limited;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Monitors this fleet expects to exist, including the ones it cannot create. */
|
|
61
|
+
export function fleetExpected(counts: FleetConvergenceCounts): number {
|
|
62
|
+
return (
|
|
63
|
+
counts.settled +
|
|
64
|
+
counts.pending +
|
|
65
|
+
counts.submitting +
|
|
66
|
+
counts.rate_limited +
|
|
67
|
+
counts.blocked +
|
|
68
|
+
counts.failed
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Monitors the provider is confirmed to hold. */
|
|
73
|
+
export function fleetLive(counts: FleetConvergenceCounts): number {
|
|
74
|
+
return counts.settled;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The verdict.
|
|
79
|
+
*
|
|
80
|
+
* `converged` deliberately requires the published generation to be the one
|
|
81
|
+
* that converged. Outstanding-is-zero alone is equally true of a generation
|
|
82
|
+
* whose monitors have not been committed yet, and answering "converged" then
|
|
83
|
+
* tells a caller its change is live before any provider has seen it.
|
|
84
|
+
*
|
|
85
|
+
* `degraded` is the honest middle: nothing is in flight, so waiting longer
|
|
86
|
+
* changes nothing, but some monitors are blocked or failed and the fleet does
|
|
87
|
+
* not hold what it was asked to hold.
|
|
88
|
+
*/
|
|
89
|
+
export function fleetStatusVerdict(input: {
|
|
90
|
+
status: FleetLifecycleStatus;
|
|
91
|
+
counts: FleetConvergenceCounts;
|
|
92
|
+
generation: number;
|
|
93
|
+
convergedGeneration: number | null;
|
|
94
|
+
}): FleetStatusVerdict {
|
|
95
|
+
if (input.status === 'deactivated') return 'deactivated';
|
|
96
|
+
if (input.status === 'deactivating') return 'deactivating';
|
|
97
|
+
if (input.status === 'billing_suspended') return 'billing_suspended';
|
|
98
|
+
if (input.status === 'billing_suspending') return 'billing_suspending';
|
|
99
|
+
if (input.status === 'billing_reactivating') return 'billing_reactivating';
|
|
100
|
+
const outstanding = fleetOutstanding(input.counts);
|
|
101
|
+
if (outstanding > 0) return 'converging';
|
|
102
|
+
if (input.counts.blocked + input.counts.failed > 0) return 'degraded';
|
|
103
|
+
return input.convergedGeneration === input.generation
|
|
104
|
+
? 'converged'
|
|
105
|
+
: 'converging';
|
|
106
|
+
}
|
|
@@ -189,7 +189,11 @@ export function monitorErrorNextAction(payload: MonitorErrorPayload): string {
|
|
|
189
189
|
return `This workspace already has ${MAX_PRIORITY_MONITORS_PER_ORG} priority radars. Delete or update an existing priority radar only after confirming it is no longer needed, then retry the urgent preview.`;
|
|
190
190
|
}
|
|
191
191
|
if (code === 'monitor_already_exists') {
|
|
192
|
-
const
|
|
192
|
+
const existingMonitor = asRecord(nestedError(payload)?.existing_monitor);
|
|
193
|
+
const existing = existingMonitor?.key;
|
|
194
|
+
if (existingMonitor?.status === 'provisioning') {
|
|
195
|
+
return 'An in-progress provider operation owns this identity. Do not deploy another key or delete and redeploy. Wait for it to finish, then rerun this check; if the conflict remains, contact Deepline support with the returned tool and identity.';
|
|
196
|
+
}
|
|
193
197
|
return typeof existing === 'string'
|
|
194
198
|
? `Run \`deepline monitors get ${existing} --json\` to inspect the monitor that owns this identity. Reuse it or contact Deepline support before replacing it; do not delete and redeploy blindly.`
|
|
195
199
|
: 'Inspect the returned existing_monitor.key with `deepline monitors get <key> --json`. Reuse that monitor or contact Deepline support before replacing it; do not delete and redeploy blindly.';
|
|
@@ -1,19 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* How large one Monitor Fleet may be.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* because Convex needs it too: the commit mutation sizes its own transaction
|
|
6
|
-
* against the same number the authoring contract validates a fleet's selection
|
|
7
|
-
* limit against. Two copies of that number would drift, and the drift would
|
|
8
|
-
* only be visible as a mutation that refuses a plan the SDK admitted.
|
|
9
|
-
*
|
|
10
|
-
* `packages/sdk/src/monitor-fleet-contract.ts` cannot import this file - the
|
|
11
|
-
* workspace-architecture ratchet forbids new `shared_libs` edges from packages
|
|
12
|
-
* - so it mirrors the literal, and `tests/lib/monitors/monitor-fleet-limits-
|
|
13
|
-
* mirror.test.ts` holds the two equal by name. Everything outside Convex reads
|
|
14
|
-
* the contract module's copy and never learns this file exists.
|
|
4
|
+
* Convex transaction sizing and SDK admission use the same canonical limit.
|
|
15
5
|
*/
|
|
16
|
-
export
|
|
6
|
+
export { MONITOR_FLEET_MAX_MEMBERS } from '../integration-contracts/monitor-fleet-contract';
|
|
17
7
|
|
|
18
8
|
/**
|
|
19
9
|
* A claim may carry at most this many independent (organization, fleet)
|
|
@@ -4,10 +4,111 @@
|
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
7
|
-
"./
|
|
8
|
-
"./
|
|
9
|
-
"
|
|
10
|
-
"
|
|
7
|
+
"./fleets/limits": "./fleets/limits.ts",
|
|
8
|
+
"./analytics-install-guides": "./analytics-install-guides.ts",
|
|
9
|
+
"./analytics-install-guides.js": "./analytics-install-guides.ts",
|
|
10
|
+
"./analytics-public": "./analytics-public.ts",
|
|
11
|
+
"./analytics-public.js": "./analytics-public.ts",
|
|
12
|
+
"./billing/charges": "./billing/charges.ts",
|
|
13
|
+
"./billing/charges.js": "./billing/charges.ts",
|
|
14
|
+
"./billing/policy-builders": "./billing/policy-builders.ts",
|
|
15
|
+
"./billing/policy-builders.js": "./billing/policy-builders.ts",
|
|
16
|
+
"./billing/pricing": "./billing/pricing.ts",
|
|
17
|
+
"./billing/pricing.js": "./billing/pricing.ts",
|
|
18
|
+
"./billing/public-pricing": "./billing/public-pricing.ts",
|
|
19
|
+
"./billing/public-pricing.js": "./billing/public-pricing.ts",
|
|
20
|
+
"./billing/session": "./billing/session.ts",
|
|
21
|
+
"./billing/session.js": "./billing/session.ts",
|
|
22
|
+
"./billing/state": "./billing/state.ts",
|
|
23
|
+
"./billing/state.js": "./billing/state.ts",
|
|
24
|
+
"./billing/tamradar": "./billing/tamradar.ts",
|
|
25
|
+
"./billing/tamradar.js": "./billing/tamradar.ts",
|
|
26
|
+
"./billing/types": "./billing/types.ts",
|
|
27
|
+
"./billing/types.js": "./billing/types.ts",
|
|
28
|
+
"./callback-security": "./callback-security.ts",
|
|
29
|
+
"./callback-security.js": "./callback-security.ts",
|
|
30
|
+
"./cursor-page": "./cursor-page.ts",
|
|
31
|
+
"./cursor-page.js": "./cursor-page.ts",
|
|
32
|
+
"./deactivation-reasons": "./deactivation-reasons.ts",
|
|
33
|
+
"./deactivation-reasons.js": "./deactivation-reasons.ts",
|
|
34
|
+
"./fleets/errors": "./fleets/errors.ts",
|
|
35
|
+
"./fleets/errors.js": "./fleets/errors.ts",
|
|
36
|
+
"./fleets/plan": "./fleets/plan.ts",
|
|
37
|
+
"./fleets/plan.js": "./fleets/plan.ts",
|
|
38
|
+
"./fleets/verdict": "./fleets/verdict.ts",
|
|
39
|
+
"./fleets/verdict.js": "./fleets/verdict.ts",
|
|
40
|
+
"./framework-managed-payload-fields": "./framework-managed-payload-fields.ts",
|
|
41
|
+
"./framework-managed-payload-fields.js": "./framework-managed-payload-fields.ts",
|
|
42
|
+
"./generated/provider-monitor-callback-admission-targets": "./generated/provider-monitor-callback-admission-targets.ts",
|
|
43
|
+
"./generated/provider-monitor-callback-admission-targets.js": "./generated/provider-monitor-callback-admission-targets.ts",
|
|
44
|
+
"./http-error-response": "./http-error-response.ts",
|
|
45
|
+
"./http-error-response.js": "./http-error-response.ts",
|
|
46
|
+
"./inbound-publish-key": "./inbound-publish-key.ts",
|
|
47
|
+
"./inbound-publish-key.js": "./inbound-publish-key.ts",
|
|
48
|
+
"./inbound-queue": "./inbound-queue.ts",
|
|
49
|
+
"./inbound-queue.js": "./inbound-queue.ts",
|
|
50
|
+
"./inventory-reconciliation": "./inventory-reconciliation.ts",
|
|
51
|
+
"./inventory-reconciliation.js": "./inventory-reconciliation.ts",
|
|
52
|
+
"./lifecycle-notifications": "./lifecycle-notifications.ts",
|
|
53
|
+
"./lifecycle-notifications.js": "./lifecycle-notifications.ts",
|
|
54
|
+
"./monitor-fleet-limits": "./monitor-fleet-limits.ts",
|
|
55
|
+
"./monitor-fleet-limits.js": "./monitor-fleet-limits.ts",
|
|
56
|
+
"./org-monitor-limits": "./org-monitor-limits.ts",
|
|
57
|
+
"./org-monitor-limits.js": "./org-monitor-limits.ts",
|
|
58
|
+
"./provider-callback-verification": "./provider-callback-verification.ts",
|
|
59
|
+
"./provider-callback-verification.js": "./provider-callback-verification.ts",
|
|
60
|
+
"./provider-monitor-batch-operation": "./provider-monitor-batch-operation.ts",
|
|
61
|
+
"./provider-monitor-batch-operation.js": "./provider-monitor-batch-operation.ts",
|
|
62
|
+
"./provider-monitor-capability": "./provider-monitor-capability.ts",
|
|
63
|
+
"./provider-monitor-capability.js": "./provider-monitor-capability.ts",
|
|
64
|
+
"./provider-monitor-consumer-health": "./provider-monitor-consumer-health.ts",
|
|
65
|
+
"./provider-monitor-consumer-health.js": "./provider-monitor-consumer-health.ts",
|
|
66
|
+
"./provider-monitor-errors": "./provider-monitor-errors.ts",
|
|
67
|
+
"./provider-monitor-errors.js": "./provider-monitor-errors.ts",
|
|
68
|
+
"./provider-monitor-identity-claims": "./provider-monitor-identity-claims.ts",
|
|
69
|
+
"./provider-monitor-identity-claims.js": "./provider-monitor-identity-claims.ts",
|
|
70
|
+
"./provider-monitor-legacy-aliases": "./provider-monitor-legacy-aliases.ts",
|
|
71
|
+
"./provider-monitor-legacy-aliases.js": "./provider-monitor-legacy-aliases.ts",
|
|
72
|
+
"./provider-monitor-priority-execution": "./provider-monitor-priority-execution.ts",
|
|
73
|
+
"./provider-monitor-priority-execution.js": "./provider-monitor-priority-execution.ts",
|
|
74
|
+
"./provider-monitor-public-aliases": "./provider-monitor-public-aliases.ts",
|
|
75
|
+
"./provider-monitor-public-aliases.js": "./provider-monitor-public-aliases.ts",
|
|
76
|
+
"./provider-monitor-runtime-read-eligibility": "./provider-monitor-runtime-read-eligibility.ts",
|
|
77
|
+
"./provider-monitor-runtime-read-eligibility.js": "./provider-monitor-runtime-read-eligibility.ts",
|
|
78
|
+
"./provider-monitor-state": "./provider-monitor-state.ts",
|
|
79
|
+
"./provider-monitor-state.js": "./provider-monitor-state.ts",
|
|
80
|
+
"./provider-monitor-status": "./provider-monitor-status.ts",
|
|
81
|
+
"./provider-monitor-status.js": "./provider-monitor-status.ts",
|
|
82
|
+
"./provider-monitor-upstream-identity": "./provider-monitor-upstream-identity.ts",
|
|
83
|
+
"./provider-monitor-upstream-identity.js": "./provider-monitor-upstream-identity.ts",
|
|
84
|
+
"./provider-webhook-monitor-runtime": "./provider-webhook-monitor-runtime.ts",
|
|
85
|
+
"./provider-webhook-monitor-runtime.js": "./provider-webhook-monitor-runtime.ts",
|
|
86
|
+
"./quickstart-prompts": "./quickstart-prompts.ts",
|
|
87
|
+
"./quickstart-prompts.js": "./quickstart-prompts.ts",
|
|
88
|
+
"./signal-radars/inbound-dead-letter": "./signal-radars/inbound-dead-letter.ts",
|
|
89
|
+
"./signal-radars/inbound-dead-letter.js": "./signal-radars/inbound-dead-letter.ts",
|
|
90
|
+
"./signal-radars/inbound-message": "./signal-radars/inbound-message.ts",
|
|
91
|
+
"./signal-radars/inbound-message.js": "./signal-radars/inbound-message.ts",
|
|
92
|
+
"./signal-radars/inbound-queue": "./signal-radars/inbound-queue.ts",
|
|
93
|
+
"./signal-radars/inbound-queue-ack-probe": "./signal-radars/inbound-queue-ack-probe.ts",
|
|
94
|
+
"./signal-radars/inbound-queue-ack-probe.js": "./signal-radars/inbound-queue-ack-probe.ts",
|
|
95
|
+
"./signal-radars/inbound-queue-delivery": "./signal-radars/inbound-queue-delivery.ts",
|
|
96
|
+
"./signal-radars/inbound-queue-delivery.js": "./signal-radars/inbound-queue-delivery.ts",
|
|
97
|
+
"./signal-radars/inbound-queue.js": "./signal-radars/inbound-queue.ts",
|
|
98
|
+
"./snitcher-install-validation": "./snitcher-install-validation.ts",
|
|
99
|
+
"./snitcher-install-validation.js": "./snitcher-install-validation.ts",
|
|
100
|
+
"./snitcher-ip2company": "./snitcher-ip2company.ts",
|
|
101
|
+
"./snitcher-ip2company.js": "./snitcher-ip2company.ts",
|
|
102
|
+
"./snitcher-radar": "./snitcher-radar.ts",
|
|
103
|
+
"./snitcher-radar.js": "./snitcher-radar.ts",
|
|
104
|
+
"./starter-templates": "./starter-templates.ts",
|
|
105
|
+
"./starter-templates.js": "./starter-templates.ts",
|
|
106
|
+
"./tracking-provider-guidance": "./tracking-provider-guidance.ts",
|
|
107
|
+
"./tracking-provider-guidance.js": "./tracking-provider-guidance.ts",
|
|
108
|
+
"./tracking-provider-integrations": "./tracking-provider-integrations.ts",
|
|
109
|
+
"./tracking-provider-integrations.js": "./tracking-provider-integrations.ts",
|
|
110
|
+
"./validation": "./validation.ts",
|
|
111
|
+
"./validation.js": "./validation.ts"
|
|
11
112
|
},
|
|
12
113
|
"scripts": {
|
|
13
114
|
"build": "tsc -p tsconfig.json --noEmit",
|
|
@@ -25,7 +126,8 @@
|
|
|
25
126
|
"@vercel/queue": "^0.4.0",
|
|
26
127
|
"next": "16.3.0-canary.60",
|
|
27
128
|
"pg": "^8.19.0",
|
|
28
|
-
"react": "19.2.5"
|
|
129
|
+
"react": "19.2.5",
|
|
130
|
+
"@deepline/integration-contracts": "workspace:*"
|
|
29
131
|
},
|
|
30
132
|
"devDependencies": {
|
|
31
133
|
"@types/node": "^20.0.0",
|
|
@@ -17,6 +17,10 @@ export const PROVIDER_MONITOR_BATCH_OPERATION_KINDS = [
|
|
|
17
17
|
'reactivate',
|
|
18
18
|
'audit',
|
|
19
19
|
'repair',
|
|
20
|
+
// Fleet plans already write canonical desired state. This internal
|
|
21
|
+
// operation is the durable, observable hand-off to the fenced Fleet
|
|
22
|
+
// executor; it is not a second lifecycle verb or a raw provider create.
|
|
23
|
+
'converge',
|
|
20
24
|
] as const;
|
|
21
25
|
|
|
22
26
|
export type ProviderMonitorBatchOperationKind =
|
|
@@ -62,7 +66,7 @@ export type ProviderMonitorBatchOperationRequest = {
|
|
|
62
66
|
* this origin instead of recomputing it from a later deployment.
|
|
63
67
|
*/
|
|
64
68
|
callbackBaseUrl?: string;
|
|
65
|
-
/** Required for
|
|
69
|
+
/** Required for direct/target deactivation; Fleet teardown may be customer-owned. */
|
|
66
70
|
deactivationReason?: string;
|
|
67
71
|
};
|
|
68
72
|
|
|
@@ -184,6 +188,9 @@ export function validateProviderMonitorBatchOperationRequest(
|
|
|
184
188
|
'Batch deploy requires a durable manifest of complete definitions.',
|
|
185
189
|
);
|
|
186
190
|
}
|
|
191
|
+
if (request.kind === 'converge') {
|
|
192
|
+
throw new Error('Batch converge requires exactly one Fleet scope.');
|
|
193
|
+
}
|
|
187
194
|
if (request.kind === 'deactivate' && !request.deactivationReason) {
|
|
188
195
|
throw new Error(
|
|
189
196
|
'Batch deactivate requires a declared deactivation reason.',
|
|
@@ -204,6 +211,9 @@ export function validateProviderMonitorBatchOperationRequest(
|
|
|
204
211
|
'Batch deploy requires a durable manifest of complete definitions.',
|
|
205
212
|
);
|
|
206
213
|
}
|
|
214
|
+
if (request.kind === 'converge') {
|
|
215
|
+
throw new Error('Batch converge requires exactly one Fleet scope.');
|
|
216
|
+
}
|
|
207
217
|
if (!isProviderMonitorBatchTarget(scope.target)) {
|
|
208
218
|
throw new Error(`Unknown monitor batch target '${scope.target}'.`);
|
|
209
219
|
}
|
|
@@ -228,12 +238,7 @@ export function validateProviderMonitorBatchOperationRequest(
|
|
|
228
238
|
}
|
|
229
239
|
if (!scope.fleetId.trim())
|
|
230
240
|
throw new Error('A Fleet scope requires fleetId.');
|
|
231
|
-
if (request.kind === 'deactivate' &&
|
|
232
|
-
throw new Error(
|
|
233
|
-
'Batch deactivate requires a declared deactivation reason.',
|
|
234
|
-
);
|
|
235
|
-
}
|
|
236
|
-
if (request.kind === 'deactivate') {
|
|
241
|
+
if (request.kind === 'deactivate' && request.deactivationReason) {
|
|
237
242
|
requireMonitorDeactivationReason(request.deactivationReason!);
|
|
238
243
|
}
|
|
239
244
|
return {
|