deepline 0.3.315 → 0.3.317
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/release.ts +1 -1
- package/dist/bundling-sources/sdk/src/runs/overview.ts +17 -10
- package/dist/bundling-sources/sdk/src/types.ts +6 -5
- package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +64 -0
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +217 -488
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +15 -0
- package/dist/bundling-sources/shared_libs/play-runtime/execution-capabilities.ts +4 -1
- package/dist/bundling-sources/shared_libs/play-runtime/internal-bootstrap-token.ts +13 -4
- package/dist/bundling-sources/shared_libs/play-runtime/launch-identity.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/live-events.ts +31 -0
- package/dist/bundling-sources/shared_libs/play-runtime/map-execution-frame.ts +444 -51
- package/dist/bundling-sources/shared_libs/play-runtime/run-snapshot-stream.ts +22 -43
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +4 -3
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +136 -87
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-authority.ts +24 -6
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-operation-contract.ts +56 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-sheet-errors.ts +94 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backend.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-shared.ts +5 -2
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd.ts +20 -11
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-progress.ts +18 -12
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +396 -35
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/run-ledger-dispatch-authority.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/run-ledger-outbox-recovery.ts +123 -19
- package/dist/bundling-sources/shared_libs/play-runtime/work-receipt-storage-contract.ts +13 -0
- package/dist/bundling-sources/shared_libs/play-runtime/workflow-executor-auth.ts +2 -0
- package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +3 -2
- package/dist/cli/auth-XPLGQ2KO.js +31 -0
- package/dist/cli/{auth-OVWOO5NP.mjs → auth-YAHBE5WU.mjs} +4 -4
- package/dist/cli/{billing-W3OXKWNE.mjs → billing-AY4OS7MR.mjs} +4 -4
- package/dist/cli/{billing-SRZL7UOH.js → billing-ORZDEMH2.js} +70 -70
- package/dist/cli/{chunk-OJP4FCCX.js → chunk-2FKXFTOF.js} +48 -48
- package/dist/cli/{chunk-OJ243ZA5.js → chunk-2HKPAZVJ.js} +1 -1
- package/dist/cli/{chunk-LOWURQMF.js → chunk-342BTES3.js} +50 -50
- package/dist/cli/{chunk-FGAK6FET.js → chunk-3NRNDPAE.js} +26 -26
- package/dist/cli/{chunk-GK67FYMS.mjs → chunk-5BLF3HDM.mjs} +5 -5
- package/dist/cli/{chunk-TZ5KUDPP.mjs → chunk-5OFRM64Y.mjs} +1 -1
- package/dist/cli/{chunk-US4S3E4Y.mjs → chunk-7PZFGB7L.mjs} +1 -1
- package/dist/cli/{chunk-YL42TF53.js → chunk-AMJYFPFK.js} +6 -6
- package/dist/cli/{chunk-PPONZ6IE.js → chunk-AW355CLN.js} +15 -15
- package/dist/cli/{chunk-TQK662WG.js → chunk-BGBYK2T3.js} +60 -60
- package/dist/cli/{chunk-TQ3F77AC.mjs → chunk-CGGM4CE6.mjs} +3 -3
- package/dist/cli/{chunk-FTJQO757.mjs → chunk-D5MHSO5T.mjs} +1 -1
- package/dist/cli/{chunk-Y42UERUY.js → chunk-DSTUP75H.js} +6 -6
- package/dist/cli/{chunk-HNKHMHJ7.mjs → chunk-FRK2AGSG.mjs} +1 -1
- package/dist/cli/{chunk-OUIIYFG3.mjs → chunk-GX3WO4M4.mjs} +2 -2
- package/dist/cli/{chunk-OYJCNCUW.js → chunk-GYOLWON6.js} +2 -2
- package/dist/cli/{chunk-N5V2SBB2.mjs → chunk-KP7W5NO6.mjs} +1 -1
- package/dist/cli/{chunk-L5TSFCJ6.mjs → chunk-KXOXXVYJ.mjs} +2 -2
- package/dist/cli/{chunk-B473QUQU.mjs → chunk-L6ERGROE.mjs} +4 -4
- package/dist/cli/{chunk-R3IFZDJL.mjs → chunk-LI6YPPBR.mjs} +2 -2
- package/dist/cli/{chunk-IGE7EJJY.mjs → chunk-NDFBAMVI.mjs} +19 -15
- package/dist/cli/{chunk-JF4O5OMH.mjs → chunk-OTI562P3.mjs} +1 -1
- package/dist/cli/{chunk-AO4IWPP7.mjs → chunk-PSHIZP3K.mjs} +17 -15
- package/dist/cli/{chunk-VYK23G2C.js → chunk-QFKVAUIQ.js} +239 -235
- package/dist/cli/{chunk-AZWQJAQF.mjs → chunk-QPKHOZI3.mjs} +1 -1
- package/dist/cli/{chunk-URVZGYGG.js → chunk-SUPQQYGY.js} +4 -4
- package/dist/cli/{chunk-C5WSTXUB.js → chunk-TJ2V2JA3.js} +537 -535
- package/dist/cli/{chunk-6BJACIVK.js → chunk-UGQHYOT4.js} +1 -1
- package/dist/cli/{chunk-IALPYID6.js → chunk-XSEMQ4II.js} +3 -3
- package/dist/cli/{chunk-SNFSO2AN.js → chunk-Z5NRL7A7.js} +6 -6
- package/dist/cli/{client-WQ3J5BSH.mjs → client-QNLVLW54.mjs} +3 -3
- package/dist/cli/client-ZW5VAHWD.js +20 -0
- package/dist/cli/{csv-36ARCBNQ.mjs → csv-AKZASZP6.mjs} +3 -3
- package/dist/cli/{csv-CENMEWDH.js → csv-PW6S3SMJ.js} +12 -12
- package/dist/cli/{db-JU7XLKQM.mjs → db-DNF53LDS.mjs} +4 -4
- package/dist/cli/{db-6DQZG6NB.js → db-FLDYET2D.js} +20 -20
- package/dist/cli/{describe-VUD4AFBY.js → describe-JDAEOCYQ.js} +17 -17
- package/dist/cli/{describe-34ALEEFO.mjs → describe-U6RGIFMA.mjs} +10 -10
- package/dist/cli/{executions-H2UXS6FP.js → executions-DASCB6LS.js} +6 -6
- package/dist/cli/{executions-H66BMN6N.mjs → executions-W6BBU2HN.mjs} +4 -4
- package/dist/cli/{feedback-Q7LXXLSH.mjs → feedback-BVWPDWZG.mjs} +3 -3
- package/dist/cli/{feedback-YSYFQSAB.js → feedback-N3MBCT4A.js} +6 -6
- package/dist/cli/{git-XV43NSWN.mjs → git-4GJOKOLU.mjs} +4 -4
- package/dist/cli/git-WVHH3QXQ.js +23 -0
- package/dist/cli/index.js +171 -98
- package/dist/cli/index.mjs +114 -41
- package/dist/cli/{legacy-noops-Z2DTZQN3.mjs → legacy-noops-5XMSV7WL.mjs} +4 -4
- package/dist/cli/{legacy-noops-RGDW6SCA.js → legacy-noops-D7C7ZVKK.js} +7 -7
- package/dist/cli/{monitors-I75KZ2BP.js → monitors-NYP4LG3K.js} +85 -85
- package/dist/cli/{monitors-OFQ56O4S.mjs → monitors-Y4RKWAS5.mjs} +4 -4
- package/dist/cli/{org-75AH6RIB.js → org-PYKQMP6F.js} +60 -60
- package/dist/cli/{org-ZYMCU6XO.mjs → org-XZ26CUCV.mjs} +4 -4
- package/dist/cli/play-BYZCR4PP.js +152 -0
- package/dist/cli/{play-BKXM76DV.mjs → play-FYHIYZ3W.mjs} +9 -9
- package/dist/cli/{providers-GWG2WFIY.mjs → providers-3UG7NK55.mjs} +4 -4
- package/dist/cli/{providers-Z77MM7ID.js → providers-QS5X236G.js} +7 -7
- package/dist/cli/{quickstart-I4RECIQC.js → quickstart-GMASUT7T.js} +5 -5
- package/dist/cli/{quickstart-BGVIV374.mjs → quickstart-PHHHHHWO.mjs} +3 -3
- package/dist/cli/search-52CC3OVI.js +26 -0
- package/dist/cli/{search-TIKBP3RE.mjs → search-AVGITJP4.mjs} +5 -5
- package/dist/cli/{secrets-SNGRI4KS.js → secrets-HY5E6F6G.js} +13 -13
- package/dist/cli/{secrets-YCHKLWKC.mjs → secrets-RBEZRQ6P.mjs} +4 -4
- package/dist/cli/{sessions-WXLADGDX.js → sessions-7CDLWLVV.js} +5 -5
- package/dist/cli/{sessions-IRDG6ZRX.mjs → sessions-ME5R5FTG.mjs} +4 -4
- package/dist/cli/{settings-KZALJDXW.mjs → settings-X4JQLBB6.mjs} +7 -7
- package/dist/cli/{settings-72J4BVF6.js → settings-ZCVJPOFP.js} +74 -74
- package/dist/cli/{setup-FFB3KLFD.js → setup-DROGU6MC.js} +41 -41
- package/dist/cli/{setup-4KCA7ESV.mjs → setup-NEILS2UF.mjs} +6 -6
- package/dist/cli/skills-KUV3WWW5.js +28 -0
- package/dist/cli/{skills-MQZPYUQ3.mjs → skills-PC57GDXZ.mjs} +4 -4
- package/dist/cli/{tools-M3WKRZ5T.mjs → tools-IFJUWQC5.mjs} +6 -6
- package/dist/cli/tools-NHDAOQPB.js +38 -0
- package/dist/cli/update-DRWQCVXJ.js +28 -0
- package/dist/cli/{update-QJMNYGNF.mjs → update-N4HSH2TJ.mjs} +6 -6
- package/dist/cli/{workflow-6T2EUFCN.mjs → workflow-2OZBTUU2.mjs} +5 -5
- package/dist/cli/{workflow-35UUOZ5E.js → workflow-LP4Z6RF5.js} +15 -15
- package/dist/{client-Dh_4Ph1R.d.ts → client-BV68GMPo.d.ts} +7 -6
- package/dist/{client-CvpZsBqt.d.mts → client-D9ACjiRC.d.mts} +7 -6
- package/dist/{compiler-manifest-wOKNiXpa.d.mts → compiler-manifest-BXcZ4P4W.d.mts} +2 -2
- package/dist/{compiler-manifest-wOKNiXpa.d.ts → compiler-manifest-BXcZ4P4W.d.ts} +2 -2
- package/dist/index.d.mts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +7 -7
- package/dist/index.mjs +4 -4
- package/dist/install-integrity.json +82 -82
- package/dist/plays/bundle-play-file.d.mts +2 -2
- package/dist/plays/bundle-play-file.d.ts +2 -2
- package/dist/plays/bundle-play-file.mjs +2 -2
- package/dist/plays/run-request.d.mts +2 -2
- package/dist/plays/run-request.d.ts +2 -2
- package/dist/release.d.mts +1 -1
- package/dist/release.d.ts +1 -1
- package/dist/release.js +2 -2
- package/dist/release.mjs +1 -1
- package/package.json +1 -1
- package/dist/cli/auth-P2TTZU36.js +0 -31
- package/dist/cli/client-CXFPFCIG.js +0 -20
- package/dist/cli/git-SYFCYIFP.js +0 -23
- package/dist/cli/play-OKXUWPMO.js +0 -152
- package/dist/cli/search-3KISUDDT.js +0 -26
- package/dist/cli/skills-XGOK4GRW.js +0 -28
- package/dist/cli/tools-LEAJ7EPW.js +0 -38
- package/dist/cli/update-ZN6LDUHS.js +0 -28
|
@@ -4,6 +4,9 @@ import type {
|
|
|
4
4
|
PlayCheckpoint,
|
|
5
5
|
PlayExecutionEvent,
|
|
6
6
|
} from './ctx-types';
|
|
7
|
+
import type { MapRowOutcome, MapRowOutcomeStatus } from './durability-store';
|
|
8
|
+
import { cloneCsvAliasedRow } from './csv-rename';
|
|
9
|
+
import { RuntimeSheetAttemptSupersededError } from './runtime-sheet-errors';
|
|
7
10
|
|
|
8
11
|
export function cloneMapFrame(frame: MapExecutionFrame): MapExecutionFrame {
|
|
9
12
|
return {
|
|
@@ -14,7 +17,7 @@ export function cloneMapFrame(frame: MapExecutionFrame): MapExecutionFrame {
|
|
|
14
17
|
? {
|
|
15
18
|
suspendedInputRows: frame.suspendedInputRows.map((row) => ({
|
|
16
19
|
inputIndex: row.inputIndex,
|
|
17
|
-
data:
|
|
20
|
+
data: cloneCsvAliasedRow(row.data),
|
|
18
21
|
...(row.authoredInputDigest
|
|
19
22
|
? { authoredInputDigest: row.authoredInputDigest }
|
|
20
23
|
: {}),
|
|
@@ -71,63 +74,453 @@ export class MapExecutionFrameStore {
|
|
|
71
74
|
});
|
|
72
75
|
}
|
|
73
76
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
this.
|
|
86
|
-
|
|
87
|
-
|
|
77
|
+
/**
|
|
78
|
+
* Open one resolver pool's progress. Restored keys are seeded once; offsets
|
|
79
|
+
* never add that same restored baseline again. Sources stay live because a
|
|
80
|
+
* streaming feed admits pages and changes latest-view visibility during execution.
|
|
81
|
+
*/
|
|
82
|
+
progress(
|
|
83
|
+
scope: MapExecutionScope,
|
|
84
|
+
source: MapProgressSource,
|
|
85
|
+
): MapExecutionProgress {
|
|
86
|
+
return new MapExecutionProgress(
|
|
87
|
+
this.checkpoint,
|
|
88
|
+
(frame) => this.set(frame),
|
|
89
|
+
this.emitExecutionEvent,
|
|
90
|
+
scope,
|
|
91
|
+
source,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Restore visible success identities and their logical input count together.
|
|
97
|
+
* Superseded frames can contain historical keys even when their scalar count
|
|
98
|
+
* appears consistent, so the run-filtered sheet reader checks every such key.
|
|
99
|
+
* An explicit success count stays authoritative; its excess over visible keys
|
|
100
|
+
* represents reused inputs. Older materialized suspension plans prove a
|
|
101
|
+
* missing count by conservation of successful, failed, superseded and pending
|
|
102
|
+
* inputs. Frames without that plan keep the historical key-count default.
|
|
103
|
+
*/
|
|
104
|
+
async restoreCompletedRows(
|
|
105
|
+
frame: MapExecutionFrame,
|
|
106
|
+
readVisibleKeys:
|
|
107
|
+
| ((keys: string[]) => Promise<readonly string[]>)
|
|
108
|
+
| undefined,
|
|
109
|
+
maxReadKeys: number,
|
|
110
|
+
): Promise<{
|
|
111
|
+
completedRowKeys: string[];
|
|
112
|
+
completedRowsCount: number;
|
|
113
|
+
reusedRowsCount: number;
|
|
114
|
+
}> {
|
|
115
|
+
const completedRowKeys =
|
|
116
|
+
(frame.supersededRows ?? 0) > 0 &&
|
|
117
|
+
frame.completedRowKeys.length > 0 &&
|
|
118
|
+
readVisibleKeys
|
|
119
|
+
? await this.visibleRestoredKeys(
|
|
120
|
+
frame.completedRowKeys,
|
|
121
|
+
readVisibleKeys,
|
|
122
|
+
maxReadKeys,
|
|
123
|
+
)
|
|
124
|
+
: [...frame.completedRowKeys];
|
|
125
|
+
const completedRowsCount =
|
|
126
|
+
frame.completedRowsCount ??
|
|
127
|
+
(Array.isArray(frame.suspendedInputRows)
|
|
128
|
+
? frame.totalRows -
|
|
129
|
+
(frame.failedRowsCount ?? 0) -
|
|
130
|
+
(frame.supersededRows ?? 0) -
|
|
131
|
+
new Set(frame.pendingRowKeys).size
|
|
132
|
+
: completedRowKeys.length);
|
|
133
|
+
if (
|
|
134
|
+
!Number.isSafeInteger(completedRowsCount) ||
|
|
135
|
+
completedRowsCount < completedRowKeys.length
|
|
136
|
+
) {
|
|
137
|
+
throw new Error(
|
|
138
|
+
`Cannot restore map ${frame.mapInvocationId}: successful input count ${completedRowsCount} contradicts ${completedRowKeys.length} visible success keys.`,
|
|
139
|
+
);
|
|
88
140
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
141
|
+
return {
|
|
142
|
+
completedRowKeys,
|
|
143
|
+
completedRowsCount,
|
|
144
|
+
reusedRowsCount: completedRowsCount - completedRowKeys.length,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private async visibleRestoredKeys(
|
|
149
|
+
keys: readonly string[],
|
|
150
|
+
readVisibleKeys: (keys: string[]) => Promise<readonly string[]>,
|
|
151
|
+
maxReadKeys: number,
|
|
152
|
+
): Promise<string[]> {
|
|
153
|
+
if (!Number.isSafeInteger(maxReadKeys) || maxReadKeys <= 0)
|
|
154
|
+
throw new Error('Restored map key reads require a positive batch size.');
|
|
155
|
+
const visible: string[] = [];
|
|
156
|
+
for (let offset = 0; offset < keys.length; offset += maxReadKeys) {
|
|
157
|
+
const page = keys.slice(offset, offset + maxReadKeys);
|
|
158
|
+
const matched = new Set(await readVisibleKeys(page));
|
|
159
|
+
visible.push(...page.filter((key) => matched.has(key)));
|
|
96
160
|
}
|
|
97
|
-
|
|
98
|
-
|
|
161
|
+
return visible;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
type LiveCount = number | (() => number);
|
|
166
|
+
const readCount = (value: LiveCount | undefined, fallback = 0): number =>
|
|
167
|
+
typeof value === 'function' ? value() : (value ?? fallback);
|
|
168
|
+
const FRAME_FLUSH_ROWS = 100;
|
|
169
|
+
const FRAME_FLUSH_MS = 250;
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Accepted Runtime Sheet writes decide which terminal rows remain visible.
|
|
173
|
+
* The account is shared across a map's pools; restored superseded rows need
|
|
174
|
+
* only their existing scalar because they are absent from restored success keys.
|
|
175
|
+
* Classification changes and recovery update counts in O(1) per reported key.
|
|
176
|
+
*/
|
|
177
|
+
export class MapExecutionVisibility {
|
|
178
|
+
private readonly superseded = new Map<string, MapRowOutcomeStatus>();
|
|
179
|
+
private supersededCompleted = 0;
|
|
180
|
+
private supersededFailed = 0;
|
|
181
|
+
|
|
182
|
+
constructor(private readonly restoredSupersededRows = 0) {}
|
|
183
|
+
|
|
184
|
+
/** Consume visibility facts only after the writer accepts the terminal batch. */
|
|
185
|
+
recordAcceptedWrite(
|
|
186
|
+
result: unknown,
|
|
187
|
+
rows: readonly MapRowOutcome[],
|
|
188
|
+
scope: { tableNamespace: string; runId: string },
|
|
189
|
+
): void {
|
|
190
|
+
if (!result || typeof result !== 'object') return;
|
|
191
|
+
const facts = result as {
|
|
192
|
+
supersededKeys?: string[];
|
|
193
|
+
staleDroppedKeys?: unknown;
|
|
194
|
+
recoveredStaleKeys?: unknown;
|
|
195
|
+
};
|
|
196
|
+
if (facts.supersededKeys?.length) {
|
|
197
|
+
// An attempt-wide rejection never admits visibility facts or outcomes.
|
|
198
|
+
throw new RuntimeSheetAttemptSupersededError({
|
|
199
|
+
tableNamespace: scope.tableNamespace,
|
|
200
|
+
runId: scope.runId,
|
|
201
|
+
writeVersionValue: null,
|
|
202
|
+
submittedRows: rows.length,
|
|
203
|
+
supersededRows: facts.supersededKeys.map((key) => ({
|
|
204
|
+
key,
|
|
205
|
+
reason: 'unreported' as const,
|
|
206
|
+
rowVersionStamp: null,
|
|
207
|
+
})),
|
|
208
|
+
});
|
|
99
209
|
}
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
(existing.failedRowsCount ?? 0) + Math.max(0, input.failedDelta ?? 0),
|
|
210
|
+
const statuses = new Map(
|
|
211
|
+
rows.map((row) => [row.key, row.status ?? 'completed']),
|
|
103
212
|
);
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
213
|
+
if (Array.isArray(facts.staleDroppedKeys)) {
|
|
214
|
+
for (const key of facts.staleDroppedKeys) {
|
|
215
|
+
if (typeof key !== 'string' || !key) continue;
|
|
216
|
+
this.removeSuperseded(key);
|
|
217
|
+
const status = statuses.get(key) ?? 'completed';
|
|
218
|
+
this.superseded.set(key, status);
|
|
219
|
+
if (status === 'failed') this.supersededFailed += 1;
|
|
220
|
+
else this.supersededCompleted += 1;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (Array.isArray(facts.recoveredStaleKeys)) {
|
|
224
|
+
for (const key of facts.recoveredStaleKeys) {
|
|
225
|
+
if (typeof key === 'string' && key) this.removeSuperseded(key);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
private removeSuperseded(key: string): void {
|
|
231
|
+
const previous = this.superseded.get(key);
|
|
232
|
+
if (previous === undefined) return;
|
|
233
|
+
this.superseded.delete(key);
|
|
234
|
+
if (previous === 'failed') this.supersededFailed -= 1;
|
|
235
|
+
else this.supersededCompleted -= 1;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
isVisible(key: string): boolean {
|
|
239
|
+
return !this.superseded.has(key);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
get supersededRows(): number {
|
|
243
|
+
return this.restoredSupersededRows + this.superseded.size;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** One successes/failures/superseded split for live and terminal projections. */
|
|
247
|
+
settlement(completedRows: number, failedRows: number) {
|
|
248
|
+
return {
|
|
249
|
+
completedRows: completedRows - this.supersededCompleted,
|
|
250
|
+
failedRows: failedRows - this.supersededFailed,
|
|
251
|
+
supersededRows: this.supersededRows,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Live offsets are outcomes from earlier pools, before visibility corrections.
|
|
258
|
+
* The shared visibility account owns corrections for earlier and current-pool
|
|
259
|
+
* writes. A committed batch is accounted atomically before projecting either.
|
|
260
|
+
*/
|
|
261
|
+
export interface MapProgressSource {
|
|
262
|
+
totalRows: LiveCount;
|
|
263
|
+
completedOffset?: LiveCount;
|
|
264
|
+
failedOffset?: LiveCount;
|
|
265
|
+
visibility: MapExecutionVisibility;
|
|
266
|
+
skippedInputs?: LiveCount;
|
|
267
|
+
/** Fed maps keep only scalars in checkpoints; the Runtime Sheet owns row records. */
|
|
268
|
+
checkpointRows: 'keys' | 'counts';
|
|
269
|
+
eventFields?: () => Partial<PlayExecutionEvent>;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
type ProgressChange = {
|
|
273
|
+
status?: MapExecutionFrame['status'];
|
|
274
|
+
completedRowKey?: string | null;
|
|
275
|
+
completedRowKeys?: readonly string[];
|
|
276
|
+
pendingRowKey?: string | null;
|
|
277
|
+
pendingRowKeys?: readonly string[];
|
|
278
|
+
failedRowKey?: string | null;
|
|
279
|
+
failedRowKeys?: readonly string[];
|
|
280
|
+
failedDelta?: number;
|
|
281
|
+
activeBoundaryId?: string | null;
|
|
282
|
+
emitEventType?: PlayExecutionEvent['type'];
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Owns the pool's committed outcomes and its scalar progress projection.
|
|
287
|
+
* Resolver admission is activity, not settlement. Sheet-backed row outcomes
|
|
288
|
+
* enter through recordCommittedRows after their terminal write commits; pending
|
|
289
|
+
* keys and lifecycle transitions may update earlier. Failure counts are
|
|
290
|
+
* explicit, never inferred from sparse pending keys. Terminal sheet
|
|
291
|
+
* reconciliation remains authoritative.
|
|
292
|
+
* Keys mutate in place; checkpoint arrays are materialized on a row/time
|
|
293
|
+
* throttle and on every transition, preserving the existing replay format.
|
|
294
|
+
* Accepted terminal input indexes also drain the retained suspension plan in
|
|
295
|
+
* O(1); an empty retained plan remains distinct from an absent plan on replay.
|
|
296
|
+
*/
|
|
297
|
+
export class MapExecutionProgress {
|
|
298
|
+
private readonly completed: Set<string>;
|
|
299
|
+
private readonly pending: Set<string>;
|
|
300
|
+
private failed = 0;
|
|
301
|
+
private rowsSinceFlush = 0;
|
|
302
|
+
private lastFlushAt = 0;
|
|
303
|
+
private started = 0;
|
|
304
|
+
private active = 0;
|
|
305
|
+
private tracksActivity = false;
|
|
306
|
+
private lastActivityKey: string | null = null;
|
|
307
|
+
private readonly initiallyStarted: number;
|
|
308
|
+
private readonly initiallySkipped: number;
|
|
309
|
+
private status: MapExecutionFrame['status'];
|
|
310
|
+
private activeBoundaryId: string | null | undefined;
|
|
311
|
+
private suspendedInputs:
|
|
312
|
+
| Map<number, NonNullable<MapExecutionFrame['suspendedInputRows']>[number]>
|
|
313
|
+
| undefined;
|
|
314
|
+
|
|
315
|
+
constructor(
|
|
316
|
+
private readonly checkpoint: PlayCheckpoint,
|
|
317
|
+
private readonly setFrame: (frame: MapExecutionFrame) => void,
|
|
318
|
+
private readonly emitExecutionEvent: (event: PlayExecutionEvent) => void,
|
|
319
|
+
private readonly scope: MapExecutionScope,
|
|
320
|
+
private readonly source: MapProgressSource,
|
|
321
|
+
) {
|
|
322
|
+
const frame = this.frame();
|
|
323
|
+
this.completed = new Set(frame?.completedRowKeys ?? []);
|
|
324
|
+
this.pending = new Set(frame?.pendingRowKeys ?? []);
|
|
325
|
+
this.status = frame?.status ?? 'running';
|
|
326
|
+
this.activeBoundaryId = frame?.activeBoundaryId;
|
|
327
|
+
this.suspendedInputs = frame?.suspendedInputRows
|
|
328
|
+
? new Map(frame.suspendedInputRows.map((row) => [row.inputIndex, row]))
|
|
329
|
+
: undefined;
|
|
330
|
+
// Freeze settled admission once: live superseded facts can include a
|
|
331
|
+
// resolver already counted in started, whereas skipped inputs are new.
|
|
332
|
+
const baseline = this.settlement();
|
|
333
|
+
this.initiallyStarted =
|
|
334
|
+
baseline.completedRows + baseline.failedRows + baseline.supersededRows;
|
|
335
|
+
this.initiallySkipped = readCount(source.skippedInputs);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
private frame(): MapExecutionFrame | undefined {
|
|
339
|
+
return this.checkpoint.mapFrames?.[this.scope.mapInvocationId];
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
private settlement() {
|
|
343
|
+
return this.source.visibility.settlement(
|
|
344
|
+
readCount(this.source.completedOffset) + this.completed.size,
|
|
345
|
+
readCount(this.source.failedOffset) + this.failed,
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/** One fixed-shape view for checkpoints, settlement events and activity samples. */
|
|
350
|
+
projection() {
|
|
351
|
+
const startedRows =
|
|
352
|
+
this.initiallyStarted +
|
|
353
|
+
readCount(this.source.skippedInputs, this.initiallySkipped) -
|
|
354
|
+
this.initiallySkipped +
|
|
355
|
+
this.started;
|
|
356
|
+
return {
|
|
357
|
+
...this.settlement(),
|
|
358
|
+
totalRows: readCount(this.source.totalRows),
|
|
359
|
+
...(this.tracksActivity
|
|
360
|
+
? {
|
|
361
|
+
startedRows,
|
|
362
|
+
activeRows: this.active,
|
|
363
|
+
waitingRows: readCount(this.source.totalRows) - startedRows,
|
|
364
|
+
}
|
|
112
365
|
: {}),
|
|
113
|
-
updatedAt: Date.now(),
|
|
114
366
|
};
|
|
115
|
-
|
|
116
|
-
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
private emit(type: PlayExecutionEvent['type']): void {
|
|
370
|
+
const { startedRows, activeRows, waitingRows, ...settled } =
|
|
371
|
+
this.projection();
|
|
372
|
+
this.emitExecutionEvent({
|
|
373
|
+
...this.source.eventFields?.(),
|
|
374
|
+
type,
|
|
375
|
+
mapInvocationId: this.scope.mapInvocationId,
|
|
376
|
+
mapNodeId: this.scope.mapNodeId ?? null,
|
|
377
|
+
logicalNamespace: this.scope.logicalNamespace,
|
|
378
|
+
artifactTableNamespace: this.scope.artifactTableNamespace,
|
|
379
|
+
...settled,
|
|
380
|
+
...(type === 'map.progress' && this.tracksActivity
|
|
381
|
+
? { startedRows, activeRows, waitingRows }
|
|
382
|
+
: {}),
|
|
383
|
+
at: Date.now(),
|
|
384
|
+
} as PlayExecutionEvent);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
update(input: ProgressChange): void {
|
|
388
|
+
if (!this.frame()) return;
|
|
389
|
+
const before = this.completed.size;
|
|
390
|
+
for (const raw of [
|
|
391
|
+
input.completedRowKey,
|
|
392
|
+
...(input.completedRowKeys ?? []),
|
|
393
|
+
]) {
|
|
394
|
+
const key = raw?.trim();
|
|
395
|
+
if (!key) continue;
|
|
396
|
+
this.completed.add(key);
|
|
397
|
+
this.pending.delete(key);
|
|
398
|
+
}
|
|
399
|
+
for (const raw of [input.pendingRowKey, ...(input.pendingRowKeys ?? [])]) {
|
|
400
|
+
const key = raw?.trim();
|
|
401
|
+
if (key && !this.completed.has(key)) this.pending.add(key);
|
|
402
|
+
}
|
|
403
|
+
for (const raw of [input.failedRowKey, ...(input.failedRowKeys ?? [])]) {
|
|
404
|
+
const key = raw?.trim();
|
|
405
|
+
if (key) this.pending.delete(key);
|
|
406
|
+
}
|
|
407
|
+
this.failed += Math.max(0, input.failedDelta ?? 0);
|
|
408
|
+
this.rowsSinceFlush += this.completed.size - before;
|
|
409
|
+
this.status = input.status ?? this.status;
|
|
410
|
+
if (input.activeBoundaryId !== undefined)
|
|
411
|
+
this.activeBoundaryId = input.activeBoundaryId;
|
|
412
|
+
const transition =
|
|
413
|
+
input.status !== undefined ||
|
|
414
|
+
input.activeBoundaryId !== undefined ||
|
|
415
|
+
input.emitEventType !== undefined;
|
|
416
|
+
const now = Date.now();
|
|
417
|
+
const flush =
|
|
418
|
+
transition ||
|
|
419
|
+
readCount(this.source.totalRows) <= FRAME_FLUSH_ROWS ||
|
|
420
|
+
this.rowsSinceFlush >= FRAME_FLUSH_ROWS ||
|
|
421
|
+
now - this.lastFlushAt >= FRAME_FLUSH_MS;
|
|
422
|
+
if (flush) this.flush(now);
|
|
423
|
+
const event =
|
|
117
424
|
input.emitEventType ??
|
|
118
|
-
(
|
|
119
|
-
if (
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
425
|
+
(flush && this.completed.size > before ? 'map.progress' : null);
|
|
426
|
+
if (event) this.emit(event);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/** Consume one durably committed batch, then project all its outcomes together. */
|
|
430
|
+
recordCommittedRows(rows: readonly MapRowOutcome[]): void {
|
|
431
|
+
const completed: string[] = [];
|
|
432
|
+
const failed: string[] = [];
|
|
433
|
+
for (const row of rows) {
|
|
434
|
+
if (typeof row.inputIndex === 'number')
|
|
435
|
+
this.suspendedInputs?.delete(row.inputIndex);
|
|
436
|
+
(row.status === 'failed' ? failed : completed).push(row.key);
|
|
437
|
+
}
|
|
438
|
+
this.update({
|
|
439
|
+
completedRowKeys: completed,
|
|
440
|
+
failedRowKeys: failed,
|
|
441
|
+
failedDelta: failed.length,
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
private flush(now: number): void {
|
|
446
|
+
const frame = this.frame();
|
|
447
|
+
if (!frame) return;
|
|
448
|
+
const progress = this.projection();
|
|
449
|
+
this.setFrame({
|
|
450
|
+
...frame,
|
|
451
|
+
status: this.status,
|
|
452
|
+
totalRows: progress.totalRows,
|
|
453
|
+
completedRowKeys:
|
|
454
|
+
this.source.checkpointRows === 'counts'
|
|
455
|
+
? []
|
|
456
|
+
: [...this.completed].filter((key) =>
|
|
457
|
+
this.source.visibility.isVisible(key),
|
|
458
|
+
),
|
|
459
|
+
pendingRowKeys:
|
|
460
|
+
this.source.checkpointRows === 'counts' ? [] : [...this.pending],
|
|
461
|
+
...(this.source.checkpointRows === 'counts'
|
|
462
|
+
? {
|
|
463
|
+
pendingRowsCount: this.pending.size,
|
|
464
|
+
}
|
|
465
|
+
: {}),
|
|
466
|
+
completedRowsCount: progress.completedRows,
|
|
467
|
+
failedRowsCount: progress.failedRows,
|
|
468
|
+
supersededRows: progress.supersededRows,
|
|
469
|
+
...(this.suspendedInputs !== undefined
|
|
470
|
+
? { suspendedInputRows: [...this.suspendedInputs.values()] }
|
|
471
|
+
: {}),
|
|
472
|
+
...(this.activeBoundaryId !== undefined
|
|
473
|
+
? { activeBoundaryId: this.activeBoundaryId }
|
|
474
|
+
: {}),
|
|
475
|
+
updatedAt: now,
|
|
476
|
+
});
|
|
477
|
+
this.rowsSinceFlush = 0;
|
|
478
|
+
this.lastFlushAt = now;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/** Record the resume plan before publishing the suspension or handing it off. */
|
|
482
|
+
suspend(input: {
|
|
483
|
+
pendingRowKeys: readonly string[];
|
|
484
|
+
suspendedInputRows: MapExecutionFrame['suspendedInputRows'];
|
|
485
|
+
activeBoundaryId: string | null;
|
|
486
|
+
}): void {
|
|
487
|
+
const frame = this.frame();
|
|
488
|
+
if (!frame)
|
|
489
|
+
throw new Error('Suspended map lost its durable execution frame.');
|
|
490
|
+
this.pending.clear();
|
|
491
|
+
for (const key of input.pendingRowKeys) {
|
|
492
|
+
this.completed.delete(key);
|
|
493
|
+
this.pending.add(key);
|
|
131
494
|
}
|
|
495
|
+
this.suspendedInputs = input.suspendedInputRows
|
|
496
|
+
? new Map(input.suspendedInputRows.map((row) => [row.inputIndex, row]))
|
|
497
|
+
: undefined;
|
|
498
|
+
this.setFrame({ ...frame, suspendedInputRows: input.suspendedInputRows });
|
|
499
|
+
this.update({
|
|
500
|
+
status: 'suspended',
|
|
501
|
+
activeBoundaryId: input.activeBoundaryId,
|
|
502
|
+
emitEventType: 'map.suspended',
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
resolverStarted(): void {
|
|
507
|
+
this.tracksActivity = true;
|
|
508
|
+
this.started += 1;
|
|
509
|
+
this.active += 1;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
resolverFinished(): void {
|
|
513
|
+
this.active -= 1;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/** Activity never copies key arrays or publishes after a terminal transition. */
|
|
517
|
+
publishActivity(): void {
|
|
518
|
+
if (this.status !== 'running') return;
|
|
519
|
+
this.tracksActivity = true;
|
|
520
|
+
const counts = this.projection();
|
|
521
|
+
const key = `${counts.startedRows}:${counts.activeRows}:${counts.waitingRows}`;
|
|
522
|
+
if (key === this.lastActivityKey) return;
|
|
523
|
+
this.lastActivityKey = key;
|
|
524
|
+
this.emit('map.progress');
|
|
132
525
|
}
|
|
133
526
|
}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
projectPlayStepProgressCounters,
|
|
3
|
+
resolveTimingWindow,
|
|
4
|
+
type PlayStepProgressCounters,
|
|
5
|
+
} from './live-events';
|
|
2
6
|
import {
|
|
3
7
|
LOG_TAIL_LIMIT,
|
|
4
8
|
hasConfirmedPlayRunLedgerWorkCounters,
|
|
@@ -36,18 +40,13 @@ import { stripProvenanceTag } from './log-provenance';
|
|
|
36
40
|
* Vercel app (dashboard and legacy SSE compatibility) and the SDK REST polling
|
|
37
41
|
* watcher (`packages/sdk/src/runs/watch-polling.ts`), so both use the same
|
|
38
42
|
* event projection from the persisted Run Snapshot. See ADR-0008.
|
|
43
|
+
* Snapshots, progress signatures and events share one scalar counter projection;
|
|
44
|
+
* counter changes are significant even when their timestamps are identical.
|
|
39
45
|
*/
|
|
40
46
|
|
|
41
47
|
export type PlayRunLiveStatus = PlayRunLifecycleStatus;
|
|
42
48
|
|
|
43
|
-
export type PlayRunStreamNodeProgress = {
|
|
44
|
-
completed?: number;
|
|
45
|
-
total?: number;
|
|
46
|
-
failed?: number;
|
|
47
|
-
startedRows?: number;
|
|
48
|
-
activeRows?: number;
|
|
49
|
-
waitingRows?: number;
|
|
50
|
-
completedRows?: number;
|
|
49
|
+
export type PlayRunStreamNodeProgress = PlayStepProgressCounters & {
|
|
51
50
|
message?: string;
|
|
52
51
|
updatedAt?: number | null;
|
|
53
52
|
startedAt?: number | null;
|
|
@@ -241,14 +240,7 @@ function buildSnapshotFromLedger(
|
|
|
241
240
|
artifactTableNamespace: step.artifactTableNamespace ?? null,
|
|
242
241
|
progress: step.progress
|
|
243
242
|
? {
|
|
244
|
-
|
|
245
|
-
total: step.progress.total,
|
|
246
|
-
failed: step.progress.failed,
|
|
247
|
-
startedRows: step.progress.startedRows,
|
|
248
|
-
activeRows: step.progress.activeRows,
|
|
249
|
-
waitingRows: step.progress.waitingRows,
|
|
250
|
-
completedRows: step.progress.completedRows,
|
|
251
|
-
supersededRows: step.progress.supersededRows,
|
|
243
|
+
...projectPlayStepProgressCounters(step.progress),
|
|
252
244
|
message: step.progress.message,
|
|
253
245
|
artifactTableNamespace:
|
|
254
246
|
step.progress.artifactTableNamespace ??
|
|
@@ -418,12 +410,9 @@ export type PlayRunStreamEvent =
|
|
|
418
410
|
'play.step.status'
|
|
419
411
|
>
|
|
420
412
|
| RunStreamEventEnvelope<
|
|
421
|
-
{
|
|
413
|
+
PlayStepProgressCounters & {
|
|
422
414
|
runId: string;
|
|
423
415
|
stepId: string;
|
|
424
|
-
completed?: number;
|
|
425
|
-
total?: number;
|
|
426
|
-
failed?: number;
|
|
427
416
|
message?: string;
|
|
428
417
|
artifactTableNamespace: string | null;
|
|
429
418
|
startedAt?: number | null;
|
|
@@ -512,21 +501,17 @@ function getStepStatusSignature(snapshot: PlayRunLiveSnapshot): string {
|
|
|
512
501
|
}
|
|
513
502
|
|
|
514
503
|
function getStepProgressSignature(snapshot: PlayRunLiveSnapshot): string {
|
|
515
|
-
return
|
|
516
|
-
.map((state) =>
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
state.progress?.message ?? '',
|
|
527
|
-
].join(':'),
|
|
528
|
-
)
|
|
529
|
-
.join('|');
|
|
504
|
+
return JSON.stringify(
|
|
505
|
+
snapshot.nodeStates.map((state) => [
|
|
506
|
+
state.nodeId,
|
|
507
|
+
projectPlayStepProgressCounters(state.progress ?? {}),
|
|
508
|
+
state.progress?.artifactTableNamespace ?? '',
|
|
509
|
+
state.progress?.startedAt ?? '',
|
|
510
|
+
state.progress?.completedAt ?? '',
|
|
511
|
+
state.progress?.updatedAt ?? '',
|
|
512
|
+
state.progress?.message ?? '',
|
|
513
|
+
]),
|
|
514
|
+
);
|
|
530
515
|
}
|
|
531
516
|
|
|
532
517
|
function getSnapshotSignature(snapshot: PlayRunLiveSnapshot): string {
|
|
@@ -682,13 +667,7 @@ export function diffPlayRunStreamEvents(input: {
|
|
|
682
667
|
payload: {
|
|
683
668
|
runId: snapshot.runId,
|
|
684
669
|
stepId: state.nodeId,
|
|
685
|
-
|
|
686
|
-
total: state.progress.total,
|
|
687
|
-
failed: state.progress.failed,
|
|
688
|
-
startedRows: state.progress.startedRows,
|
|
689
|
-
activeRows: state.progress.activeRows,
|
|
690
|
-
waitingRows: state.progress.waitingRows,
|
|
691
|
-
completedRows: state.progress.completedRows,
|
|
670
|
+
...projectPlayStepProgressCounters(state.progress),
|
|
692
671
|
message: state.progress.message,
|
|
693
672
|
artifactTableNamespace:
|
|
694
673
|
state.progress.artifactTableNamespace ??
|
|
@@ -2574,9 +2574,9 @@ async function run(
|
|
|
2574
2574
|
runId,
|
|
2575
2575
|
tableNamespace,
|
|
2576
2576
|
});
|
|
2577
|
-
//
|
|
2578
|
-
//
|
|
2579
|
-
//
|
|
2577
|
+
// The admission owner durably pins one write intent per run/table, also
|
|
2578
|
+
// after a scheduler wake recreates this Map. Retain the local version as
|
|
2579
|
+
// an additional consistency check across pages of this runner.
|
|
2580
2580
|
const existingAttempt = runtimeSheetAttempts.get(attemptKey);
|
|
2581
2581
|
const mapStartResult = await retryRuntimeSheetPersistence({
|
|
2582
2582
|
description: `runtime map start table=${tableNamespace}`,
|
|
@@ -2612,6 +2612,7 @@ async function run(
|
|
|
2612
2612
|
columnTypes: mapContext.columnTypes,
|
|
2613
2613
|
}),
|
|
2614
2614
|
rows: items,
|
|
2615
|
+
stableWriteIntent: true,
|
|
2615
2616
|
pendingRerunAdmission: true,
|
|
2616
2617
|
inputOffset: mapContext.inputOffset,
|
|
2617
2618
|
mode: mapContext.mode,
|