@melite/estate-drafts 0.0.0-stage → 0.1.5-pre-alpha
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/LICENSE +557 -0
- package/README.md +55 -2
- package/lib/bench.d.ts +376 -0
- package/lib/bench.js +1941 -0
- package/lib/index.d.ts +15 -0
- package/lib/index.js +17 -0
- package/lib/remote.d.ts +325 -0
- package/lib/remote.js +1307 -0
- package/lib/spec.d.ts +88 -0
- package/lib/spec.js +87 -0
- package/lib/store.d.ts +46 -0
- package/lib/store.js +194 -0
- package/lib/trail.d.ts +16 -0
- package/lib/trail.js +91 -0
- package/lib/typert.host.d.ts +3 -0
- package/lib/typert.host.js +1016 -0
- package/lib/typert.remote-client.d.ts +54 -0
- package/lib/typert.remote-client.js +732 -0
- package/lib/types.d.ts +3 -0
- package/lib/types.js +2 -0
- package/package.json +65 -4
package/lib/remote.js
ADDED
|
@@ -0,0 +1,1307 @@
|
|
|
1
|
+
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
|
|
2
|
+
var useValue = arguments.length > 2;
|
|
3
|
+
for (var i = 0; i < initializers.length; i++) {
|
|
4
|
+
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
5
|
+
}
|
|
6
|
+
return useValue ? value : void 0;
|
|
7
|
+
};
|
|
8
|
+
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
9
|
+
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
10
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
11
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
12
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
13
|
+
var _, done = false;
|
|
14
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
15
|
+
var context = {};
|
|
16
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
17
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
18
|
+
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
19
|
+
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
20
|
+
if (kind === "accessor") {
|
|
21
|
+
if (result === void 0) continue;
|
|
22
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
23
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
24
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
25
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
26
|
+
}
|
|
27
|
+
else if (_ = accept(result)) {
|
|
28
|
+
if (kind === "field") initializers.unshift(_);
|
|
29
|
+
else descriptor[key] = _;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
33
|
+
done = true;
|
|
34
|
+
};
|
|
35
|
+
import { Remote, RemoteError, TypertRemoteService, } from "@deepseek-ai/dsh-typert-protocol";
|
|
36
|
+
import { admitWiring, benchBlockOf, strippedForAdmission, toleratedByRow } from "./bench.js";
|
|
37
|
+
import { STAGE_PATCH_KEYS, RESTORED_PROVENANCE_SPELLING } from "@melite/contracts/benchStage";
|
|
38
|
+
export { STAGE_PATCH_KEYS };
|
|
39
|
+
import { unwrapItemArrays } from "@melite/comfyui-card-kit";
|
|
40
|
+
import { openDraftsDomain } from "./store.js";
|
|
41
|
+
import { DRAFT_ID_RE } from "./spec.js";
|
|
42
|
+
import { addStageBench, ensureBench, notifyBenchChange, notifyBenchDocWrite, onBenchChange, clearBenchChangeSeam, setBenchChangeSeam, getBench, groupBench, patchBench, clearBench, removeStageBench, reorderBench, rewireBench, undoBench, ungroupBench, } from "./bench.js";
|
|
43
|
+
import { benchDraftIdOf } from "@melite/contracts/benchStage";
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
export function installBenchChangeSeam(ctx) {
|
|
64
|
+
let detach = null;
|
|
65
|
+
ctx.effect(() => {
|
|
66
|
+
const mine = {
|
|
67
|
+
emit: (session) => { ctx.emit("bench.changed", session); },
|
|
68
|
+
on: (fn) => {
|
|
69
|
+
const stop = ctx.on("bench.changed", fn);
|
|
70
|
+
return () => { stop(); };
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
setBenchChangeSeam(mine);
|
|
74
|
+
// the ownership stamp: an older fiber's unwind clears only its own
|
|
75
|
+
// install — a displaced seam detaches nothing (clearBenchChangeSeam)
|
|
76
|
+
detach = () => { clearBenchChangeSeam(mine); };
|
|
77
|
+
return detach;
|
|
78
|
+
});
|
|
79
|
+
return () => { detach?.(); };
|
|
80
|
+
}
|
|
81
|
+
/** The wrap voice for a thrown store error: Error.message when the
|
|
82
|
+
* value carries one, else the string form. Every store/domain throw
|
|
83
|
+
* site constructs `new Error` (or an Error subclass), so the else
|
|
84
|
+
* arm only guards degenerate non-Error throws. */
|
|
85
|
+
function thrownMessage(error) {
|
|
86
|
+
/* v8 ignore next -- a non-Error throw requires a degenerate throw
|
|
87
|
+
* site; openDraftsDomain and the domain layer construct Errors. */
|
|
88
|
+
return error instanceof Error ? error.message : String(error);
|
|
89
|
+
}
|
|
90
|
+
/** ── THE OUTPUT HISTORY HELPERS (stroke 40) ──────────────────────
|
|
91
|
+
* resultOutputUrls now lives in trail.ts (the cycle-free seam) and
|
|
92
|
+
* is re-exported below with the trail laws. ───────────────────── */
|
|
93
|
+
export function sameResultOutputs(a, b) {
|
|
94
|
+
return sameUrlSet(resultOutputUrls(a), resultOutputUrls(b));
|
|
95
|
+
}
|
|
96
|
+
function sameUrlSet(a, b) {
|
|
97
|
+
if (a.length !== b.length)
|
|
98
|
+
return false;
|
|
99
|
+
const sa = new Set(a);
|
|
100
|
+
return b.every((u) => sa.has(u));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
export { canonicalOutputUrl, resultOutputUrls, isTerminalTrailWord as isTerminalWord, trailDedupe } from "./trail.js";
|
|
107
|
+
import { canonicalOutputUrl, isTerminalTrailWord as isTerminalWord, resultOutputUrls, sameTrailRunIdentity, } from "./trail.js";
|
|
108
|
+
function sameRunIdentity(a, b) {
|
|
109
|
+
return sameTrailRunIdentity(a, b);
|
|
110
|
+
}
|
|
111
|
+
function resolverOf(ctx) {
|
|
112
|
+
let seat;
|
|
113
|
+
try {
|
|
114
|
+
seat = ctx.get("cardRegistry", false);
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
if (seat === null || seat === undefined || typeof seat !== "object")
|
|
120
|
+
return null;
|
|
121
|
+
const fn = seat.resolveStageParams;
|
|
122
|
+
if (typeof fn !== "function")
|
|
123
|
+
return null;
|
|
124
|
+
return seat;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
export function admissionOf(ctx) {
|
|
148
|
+
const reg = resolverOf(ctx);
|
|
149
|
+
if (reg === null || reg.admitStageModel === undefined)
|
|
150
|
+
return undefined;
|
|
151
|
+
return (card, params, provenance) => {
|
|
152
|
+
const admitOnce = () => {
|
|
153
|
+
const verdict = reg.admitStageModel(card, params["model"]);
|
|
154
|
+
if (!verdict.ok) {
|
|
155
|
+
throw new RemoteError("bench/bad-model", verdict.error, { card, offered: verdict.offered });
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
try {
|
|
159
|
+
admitOnce();
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
catch (original) {
|
|
163
|
+
if (provenance !== RESTORED_PROVENANCE
|
|
164
|
+
&& provenance !== RESTORED_WIRE_SPELLING
|
|
165
|
+
|| typeof params["model"] !== "string"
|
|
166
|
+
|| params["model"] === ""
|
|
167
|
+
|| reg.repairStageModel === undefined)
|
|
168
|
+
throw original;
|
|
169
|
+
const fix = reg.repairStageModel(card, params["model"]);
|
|
170
|
+
if (fix === null)
|
|
171
|
+
throw original;
|
|
172
|
+
params["model"] = fix.model;
|
|
173
|
+
if (fix.variant !== undefined
|
|
174
|
+
&& (params["variant"] === undefined || params["variant"] === "")) {
|
|
175
|
+
params["variant"] = fix.variant;
|
|
176
|
+
}
|
|
177
|
+
try {
|
|
178
|
+
admitOnce();
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
// the repair did not clear admission — the ORIGINAL refusal
|
|
182
|
+
// is the honest voice, never the repair's shadow error
|
|
183
|
+
throw original;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
export const RESTORED_PROVENANCE = Symbol("melite.restored-provenance");
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
export const RESTORED_WIRE_SPELLING = RESTORED_PROVENANCE_SPELLING;
|
|
206
|
+
/** The bench documents' id prefix (the bench family, not a flow export). */
|
|
207
|
+
const BENCH_ID_PREFIX = "bench-";
|
|
208
|
+
/** A plain record (the module's own shape guard). */
|
|
209
|
+
const isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
export function laneAdmissionOf(ctx) {
|
|
224
|
+
const reg = resolverOf(ctx);
|
|
225
|
+
if (reg === null || reg.admitStageRelations === undefined)
|
|
226
|
+
return undefined;
|
|
227
|
+
return (card, params, provenance) => {
|
|
228
|
+
|
|
229
|
+
// door audit's HIGH-2): `provenance` arrives in the request body,
|
|
230
|
+
// and melite_construct's load verb forwards it verbatim — so a
|
|
231
|
+
// caller could exempt any state by claiming "restored", and the
|
|
232
|
+
// one receipt for the carve in the suite WAS that bypass. The
|
|
233
|
+
// carve now rides a marker the SERVER sets, never a field a
|
|
234
|
+
// caller writes: a domain-internal restore (bench-load) passes
|
|
235
|
+
// the symbol below, and a client claim is just a string. The run
|
|
236
|
+
|
|
237
|
+
// still teaches at Run — the carve only spares a live composer
|
|
238
|
+
|
|
239
|
+
if (provenance === RESTORED_PROVENANCE || provenance === RESTORED_WIRE_SPELLING) {
|
|
240
|
+
return; // the archive loads; the run teaches
|
|
241
|
+
}
|
|
242
|
+
const verdict = reg.admitStageRelations(card, params);
|
|
243
|
+
if (!verdict.ok) {
|
|
244
|
+
throw new RemoteError("bench/lane-refusal", verdict.error, { card });
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
export function laneMigrationOf(ctx) {
|
|
257
|
+
const reg = resolverOf(ctx);
|
|
258
|
+
if (reg === null || reg.laneMigrate === undefined)
|
|
259
|
+
return undefined;
|
|
260
|
+
return reg.laneMigrate.bind(reg);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
export function benchAdmissionOf(ctx) {
|
|
268
|
+
const modelAdmit = admissionOf(ctx);
|
|
269
|
+
const laneAdmit = laneAdmissionOf(ctx);
|
|
270
|
+
if (modelAdmit === undefined)
|
|
271
|
+
return laneAdmit ?? undefined;
|
|
272
|
+
if (laneAdmit === undefined)
|
|
273
|
+
return modelAdmit;
|
|
274
|
+
return (card, params, provenance) => {
|
|
275
|
+
modelAdmit(card, params, provenance);
|
|
276
|
+
laneAdmit(card, params, provenance);
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
/** Attach the served `resolved` map per stage (prepareCardParams'
|
|
280
|
+
* own stamped + lifted body — the exact input the next run
|
|
281
|
+
* composes). Copies rows, never mutates the domain's cached doc:
|
|
282
|
+
* the stored record keeps params/result/runStatus only, the serve
|
|
283
|
+
* derives `resolved` fresh on every read. A stage whose params name
|
|
284
|
+
* no resolvable unit rides un-resolved (no key at all — the client
|
|
285
|
+
* treats absence as the local-derive fallback). */
|
|
286
|
+
function withResolvedStages(ctx, stages) {
|
|
287
|
+
if (!Array.isArray(stages))
|
|
288
|
+
return stages;
|
|
289
|
+
const reg = resolverOf(ctx);
|
|
290
|
+
if (reg === null)
|
|
291
|
+
return stages;
|
|
292
|
+
return stages.map((s) => {
|
|
293
|
+
if (typeof s !== "object" || s === null || Array.isArray(s))
|
|
294
|
+
return s;
|
|
295
|
+
const row = s;
|
|
296
|
+
const params = row["params"];
|
|
297
|
+
if (params === null || typeof params !== "object" || Array.isArray(params))
|
|
298
|
+
return row;
|
|
299
|
+
const resolved = reg.resolveStageParams(params);
|
|
300
|
+
if (resolved === null)
|
|
301
|
+
return row;
|
|
302
|
+
return { ...row, resolved };
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
/** First-seen display strings walk the doc depth-first (the index
|
|
306
|
+
* law: model/service cap 80, name/intent/title cap 160). */
|
|
307
|
+
function docIndexFields(doc) {
|
|
308
|
+
const first = {};
|
|
309
|
+
const walk = (v) => {
|
|
310
|
+
if (typeof v === "string")
|
|
311
|
+
return;
|
|
312
|
+
if (Array.isArray(v)) {
|
|
313
|
+
for (const e of v)
|
|
314
|
+
walk(e);
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
if (typeof v !== "object" || v === null)
|
|
318
|
+
return;
|
|
319
|
+
for (const [k, e] of Object.entries(v)) {
|
|
320
|
+
if (first[k] === undefined &&
|
|
321
|
+
(k === "model" ||
|
|
322
|
+
k === "name" ||
|
|
323
|
+
k === "intent" ||
|
|
324
|
+
k === "title" ||
|
|
325
|
+
k === "service") &&
|
|
326
|
+
typeof e === "string") {
|
|
327
|
+
first[k] = e;
|
|
328
|
+
}
|
|
329
|
+
walk(e);
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
walk(doc);
|
|
333
|
+
const cap = (s, n) => s === undefined ? "" : s.length + 1 < n ? s : s.slice(0, n - 1);
|
|
334
|
+
let model = cap(first["model"], 80);
|
|
335
|
+
let disp = cap(first["name"], 160);
|
|
336
|
+
if (disp === "")
|
|
337
|
+
disp = cap(first["intent"], 160);
|
|
338
|
+
if (disp === "")
|
|
339
|
+
disp = cap(first["title"], 160);
|
|
340
|
+
if (model === "")
|
|
341
|
+
model = cap(first["service"], 80);
|
|
342
|
+
return { model, name: disp };
|
|
343
|
+
}
|
|
344
|
+
/** The drafts Remote owner: list/get over the storage domain. */
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
const benchTransactions = new Map();
|
|
360
|
+
async function withBenchWrite(draftId, body) {
|
|
361
|
+
const prev = benchTransactions.get(draftId) ?? Promise.resolve();
|
|
362
|
+
let release;
|
|
363
|
+
const gate = new Promise((r) => { release = r; });
|
|
364
|
+
const mine = prev.then(() => gate);
|
|
365
|
+
benchTransactions.set(draftId, mine);
|
|
366
|
+
await prev;
|
|
367
|
+
try {
|
|
368
|
+
return await body();
|
|
369
|
+
}
|
|
370
|
+
finally {
|
|
371
|
+
release();
|
|
372
|
+
if (benchTransactions.get(draftId) === mine)
|
|
373
|
+
benchTransactions.delete(draftId);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
let DraftsRemoteService = (() => {
|
|
377
|
+
let _classSuper = TypertRemoteService;
|
|
378
|
+
let _instanceExtraInitializers = [];
|
|
379
|
+
let _remoteExportList_decorators;
|
|
380
|
+
let _remoteExportGet_decorators;
|
|
381
|
+
let _remoteExportPatch_decorators;
|
|
382
|
+
return class DraftsRemoteService extends _classSuper {
|
|
383
|
+
static {
|
|
384
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
385
|
+
_remoteExportList_decorators = [Remote("list")];
|
|
386
|
+
_remoteExportGet_decorators = [Remote("get")];
|
|
387
|
+
_remoteExportPatch_decorators = [Remote("patch")];
|
|
388
|
+
__esDecorate(this, null, _remoteExportList_decorators, { kind: "method", name: "remoteExportList", static: false, private: false, access: { has: obj => "remoteExportList" in obj, get: obj => obj.remoteExportList }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
389
|
+
__esDecorate(this, null, _remoteExportGet_decorators, { kind: "method", name: "remoteExportGet", static: false, private: false, access: { has: obj => "remoteExportGet" in obj, get: obj => obj.remoteExportGet }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
390
|
+
__esDecorate(this, null, _remoteExportPatch_decorators, { kind: "method", name: "remoteExportPatch", static: false, private: false, access: { has: obj => "remoteExportPatch" in obj, get: obj => obj.remoteExportPatch }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
391
|
+
if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
benchAdmit() {
|
|
407
|
+
return benchAdmissionOf(this.ctx);
|
|
408
|
+
}
|
|
409
|
+
constructor(ctx) {
|
|
410
|
+
super(ctx, "draftsRemote", { namespace: "drafts" });
|
|
411
|
+
__runInitializers(this, _instanceExtraInitializers);
|
|
412
|
+
}
|
|
413
|
+
/** Endpoint `drafts/list` — the domain index, recency DESC. */
|
|
414
|
+
async remoteExportList(signal) {
|
|
415
|
+
return await this.list(signal);
|
|
416
|
+
}
|
|
417
|
+
/** Endpoint `drafts/get` — one doc by id. */
|
|
418
|
+
async remoteExportGet(draftId, signal) {
|
|
419
|
+
return (await this.get(draftId, signal));
|
|
420
|
+
}
|
|
421
|
+
/** The in-process API the adapters above delegate to. */
|
|
422
|
+
async list(signal) {
|
|
423
|
+
signal.throwIfAborted();
|
|
424
|
+
let drafts;
|
|
425
|
+
try {
|
|
426
|
+
drafts = await openDraftsDomain(this.ctx);
|
|
427
|
+
}
|
|
428
|
+
catch (error) {
|
|
429
|
+
throw new RemoteError("draft/store-failed", thrownMessage(error), {}, { cause: error });
|
|
430
|
+
}
|
|
431
|
+
const ents = [];
|
|
432
|
+
for (const [did, rec] of drafts.table("docs").entries()) {
|
|
433
|
+
ents.push({ mt: rec.updated_at, row: { draft_id: did, ...docIndexFields(rec.doc) } });
|
|
434
|
+
}
|
|
435
|
+
ents.sort((a, b) => b.mt - a.mt);
|
|
436
|
+
return {
|
|
437
|
+
count: ents.length,
|
|
438
|
+
drafts: ents.slice(0, 600).map((e) => e.row),
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
/** The in-process API the adapter above delegates to. */
|
|
442
|
+
async get(draftId, signal) {
|
|
443
|
+
signal.throwIfAborted();
|
|
444
|
+
if (!DRAFT_ID_RE.test(draftId)) {
|
|
445
|
+
throw new RemoteError("draft/bad-id", `bad draft id ${JSON.stringify(draftId)}`, {
|
|
446
|
+
draftId,
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
let drafts;
|
|
450
|
+
try {
|
|
451
|
+
drafts = await openDraftsDomain(this.ctx);
|
|
452
|
+
}
|
|
453
|
+
catch (error) {
|
|
454
|
+
throw new RemoteError("draft/store-failed", thrownMessage(error), {}, { cause: error });
|
|
455
|
+
}
|
|
456
|
+
const rec = drafts.table("docs").get(draftId);
|
|
457
|
+
if (rec === undefined) {
|
|
458
|
+
throw new RemoteError("draft/not-found", `draft not found: ${draftId}`, {
|
|
459
|
+
draftId,
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
const doc = rec.doc;
|
|
463
|
+
return { ...doc, stages: withResolvedStages(this.ctx, doc["stages"]) };
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
async remoteExportPatch(draftId, stageId, patch, expectRev, signal) {
|
|
488
|
+
return await this.patch(draftId, stageId, patch, expectRev, signal);
|
|
489
|
+
}
|
|
490
|
+
/** The patch arm the adapter above delegates to — one write
|
|
491
|
+
* transaction on the draft's own key, shared with the bench
|
|
492
|
+
* verbs (the round-14 race audit's R3: the author arm's
|
|
493
|
+
* spread-copy of the log forked against their in-place push). */
|
|
494
|
+
async patch(draftId, stageId, patch, expectRev, signal) {
|
|
495
|
+
return withBenchWrite(draftId, () => this.patchBody(draftId, stageId, patch, expectRev, signal));
|
|
496
|
+
}
|
|
497
|
+
async patchBody(draftId, stageId, patch, expectRev, signal) {
|
|
498
|
+
signal.throwIfAborted();
|
|
499
|
+
if (!DRAFT_ID_RE.test(draftId)) {
|
|
500
|
+
throw new RemoteError("draft/bad-id", `bad draft id ${JSON.stringify(draftId)}`, {
|
|
501
|
+
draftId,
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
if (typeof stageId !== "string" || stageId === "") {
|
|
505
|
+
throw new RemoteError("draft/bad-stage-id", `bad stage id ${JSON.stringify(stageId)}`, {
|
|
506
|
+
stageId,
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
if (patch === null || typeof patch !== "object" || Array.isArray(patch)) {
|
|
510
|
+
throw new RemoteError("draft/bad-patch", "patch must be a plain object", {});
|
|
511
|
+
}
|
|
512
|
+
// Admission: the door's fixed key set, unknown keys refused with
|
|
513
|
+
// the old voice (STAGE_PATCH_KEYS order).
|
|
514
|
+
const unknown = Object.keys(patch).filter((k) => !STAGE_PATCH_KEYS.includes(k));
|
|
515
|
+
if (unknown.length > 0) {
|
|
516
|
+
throw new RemoteError("draft/patch-unknown-keys", `unknown stage patch key(s): ${unknown.join(", ")} (want ${STAGE_PATCH_KEYS.join(", ")})`, { unknown });
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// vacuously "telemetry" (every() on no keys) — without this arm
|
|
520
|
+
// it would persist an unchanged doc and wake the stream for
|
|
521
|
+
// nothing. An empty patch is a caller bug: refuse with the
|
|
522
|
+
// door's own voice, like the bench verb refuses its own.
|
|
523
|
+
if (Object.keys(patch).length === 0) {
|
|
524
|
+
throw new RemoteError("draft/bad-patch", "patch is empty (nothing to patch)", {});
|
|
525
|
+
}
|
|
526
|
+
// THE INPUT KEYS ARE SHAPE-GATED AT THE DOOR (audit S-S8,
|
|
527
|
+
|
|
528
|
+
// wire type permits any JsonValue) and the next compile refused
|
|
529
|
+
// the poisoned row far from the write; `inputOutputs` stored
|
|
530
|
+
// verbatim on BOTH seats while the bench verb refuses the key
|
|
531
|
+
// outright. One shape per key now: inputs is the channel ->
|
|
532
|
+
// wiring record (the bench arm's own isObj law), inputOutputs
|
|
533
|
+
// is the per-slot output-name list (string or null per slot —
|
|
534
|
+
// the wire contract FlowStage declares). The compile seat keeps
|
|
535
|
+
// the deep wiring law; this gate only refuses what could never
|
|
536
|
+
// compile, at the write that would have poisoned the doc.
|
|
537
|
+
if ("inputs" in patch && !isObj(patch["inputs"])) {
|
|
538
|
+
throw new RemoteError("draft/bad-patch", "inputs must be an object (channel -> wiring)", {});
|
|
539
|
+
}
|
|
540
|
+
if ("inputOutputs" in patch) {
|
|
541
|
+
const io = patch["inputOutputs"];
|
|
542
|
+
if (!Array.isArray(io) ||
|
|
543
|
+
!io.every((entry) => entry === null || typeof entry === "string")) {
|
|
544
|
+
throw new RemoteError("draft/bad-patch", "inputOutputs must be a list of output names (string or null per input slot)", {});
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
let drafts;
|
|
548
|
+
try {
|
|
549
|
+
drafts = await openDraftsDomain(this.ctx);
|
|
550
|
+
}
|
|
551
|
+
catch (error) {
|
|
552
|
+
throw new RemoteError("draft/store-failed", thrownMessage(error), {}, { cause: error });
|
|
553
|
+
}
|
|
554
|
+
const rec = drafts.table("docs").get(draftId);
|
|
555
|
+
if (rec === undefined) {
|
|
556
|
+
// The old door's delivered:false envelope — a missing doc is a
|
|
557
|
+
// DOMAIN outcome, not an error.
|
|
558
|
+
return { draftId, stageId, delivered: false, reason: "draft not found" };
|
|
559
|
+
}
|
|
560
|
+
const doc = rec.doc;
|
|
561
|
+
// The domain schema guarantees stages is an array (schema-failing
|
|
562
|
+
// records are backed up and skipped at read) — no re-defense here.
|
|
563
|
+
const stages = doc["stages"];
|
|
564
|
+
const stage = stages.find((s) => typeof s === "object" && s !== null && s["id"] === stageId) ?? null;
|
|
565
|
+
if (stage === null) {
|
|
566
|
+
return { draftId, stageId, delivered: false, reason: "stage not found" };
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// even when I am just generating a song"): a patch body whose
|
|
570
|
+
// keys are ALL run telemetry (result and/or runStatus — never
|
|
571
|
+
// params/inputs/inputOutputs) is a RUN FACE, not an author move.
|
|
572
|
+
// The outcome observer writes one such body per poll state —
|
|
573
|
+
// run start, EVERY progress frame, terminal — and under the
|
|
574
|
+
// every-write-bumps law the bench's CAS rev ticked for the whole
|
|
575
|
+
// generation (held expect_revs went stale mid-run; the undo log
|
|
576
|
+
// filled with no-op 'patch' entries that consumed undo targets
|
|
577
|
+
// restoring params_before:{} — telemetry was never authorship).
|
|
578
|
+
// Telemetry now writes the stage row (result/runStatus + the
|
|
579
|
+
// derived history archive) and fires the doc-write notify, but
|
|
580
|
+
// does NOT fence, does NOT bump bench.rev, does NOT append to
|
|
581
|
+
// the bench log — last-write-wins is the poll's own semantics,
|
|
582
|
+
// and the rebase dance the client kept performing was this
|
|
583
|
+
// fence's disease, not its cure. Any body carrying an author key
|
|
584
|
+
// (params | inputs | inputOutputs) walks the full law unchanged.
|
|
585
|
+
const bench = benchBlockOf(doc);
|
|
586
|
+
// `health` walks the telemetry arm with result/runStatus (LOW-23,
|
|
587
|
+
|
|
588
|
+
// writes it, the recovery clears it, neither is authorship.
|
|
589
|
+
const telemetry = Object.keys(patch).every((k) => k === "result" || k === "runStatus" || k === "health");
|
|
590
|
+
// THE CAS WALK (the layered-params law): an AUTHOR patch is a
|
|
591
|
+
// bench author and joins the revision law every other author
|
|
592
|
+
// walks — fence on expectRev BEFORE any mutation (the record is
|
|
593
|
+
// the domain's cached object; a refusal must leave no trace),
|
|
594
|
+
// then bump and log below. A bench that moved under the caller
|
|
595
|
+
// refuses loud, never merges under it.
|
|
596
|
+
if (!telemetry) {
|
|
597
|
+
|
|
598
|
+
// null skipped the CAS fence the bench verbs mandate (their
|
|
599
|
+
// law: a non-number expect_rev is a guaranteed refusal) — an
|
|
600
|
+
// unfenced author write merged silently under a moved bench.
|
|
601
|
+
// The door speaks the bench's voice, and the refusal carries
|
|
602
|
+
// current_rev so a rebase-and-retry lands (the client's CAS
|
|
603
|
+
// predicate already names this code).
|
|
604
|
+
if (expectRev === null) {
|
|
605
|
+
throw new RemoteError("bench/cas-conflict", "expect_rev is required (the CAS law)", { current_rev: bench.rev });
|
|
606
|
+
}
|
|
607
|
+
if (bench.rev !== expectRev) {
|
|
608
|
+
throw new RemoteError("bench/cas-conflict", `bench moved under the patch (expect_rev ${expectRev}, current ${bench.rev}) — re-read and re-apply`, { current_rev: bench.rev, expect_rev: expectRev });
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
// THE TELEMETRY WRITE IS DEFERRED (the bench audit's gap 1,
|
|
612
|
+
// The arms run as one closure AFTER both gates pass. The closure
|
|
613
|
+
// takes the row as its PARAM (shadowing this section's binding):
|
|
614
|
+
// the R6 cure below re-resolves the LIVE row at call time.
|
|
615
|
+
const writeTelemetry = (stage) => {
|
|
616
|
+
if ("result" in patch) {
|
|
617
|
+
// THE OUTPUT HISTORY (stroke 40 — the operator's QOL law: the
|
|
618
|
+
// preview's mini row is a CARD OUTPUT HISTORY): a stage's
|
|
619
|
+
// result is the CURRENT run's face, and every poll state
|
|
620
|
+
// overwrites it — the live patch ({run_id, progress}) would
|
|
621
|
+
// destroy the previous run's outputs. Before any result
|
|
622
|
+
// overwrite, a result CARRYING outputs is archived onto the
|
|
623
|
+
// stage's history, newest first, capped (12), deduped by its
|
|
624
|
+
// url set: only UNIQUE outputs ride (the operator's word — a
|
|
625
|
+
// re-run landing the same urls never double-lists). The
|
|
626
|
+
// history is DERIVED here, never patched in — the estate owns
|
|
627
|
+
// the trail; the client reads it.
|
|
628
|
+
//
|
|
629
|
+
// THE PARAMS SNAPSHOT (the output-component stroke — "old
|
|
630
|
+
// generations that had different parameters"): the params that
|
|
631
|
+
// MADE a result are captured at ITS LANDING (a parallel
|
|
632
|
+
// server-derived key), never at overwrite time — params
|
|
633
|
+
// edited between runs must not retroactively re-label the old
|
|
634
|
+
// generation. The archive entry carries the snapshot; the
|
|
635
|
+
// client diffs it against the live params for the chip.
|
|
636
|
+
// THE TERMINAL-HOLDS LAW (bench-trail W3, judged PRE-write): a
|
|
637
|
+
// settled run's row never un-settles — a stale poll/tail pass
|
|
638
|
+
// re-stamping a non-terminal word for the SAME run_id writes
|
|
639
|
+
// NOTHING (result included; the tile's stuck face). A
|
|
640
|
+
// different run_id is a fresh press and runs.
|
|
641
|
+
const settledRunId = typeof stage["result"] === "object" && stage["result"] !== null && !Array.isArray(stage["result"])
|
|
642
|
+
? stage["result"]["run_id"]
|
|
643
|
+
: undefined;
|
|
644
|
+
const patchRunId = typeof patch["result"] === "object" && patch["result"] !== null && !Array.isArray(patch["result"])
|
|
645
|
+
? patch["result"]["run_id"]
|
|
646
|
+
: undefined;
|
|
647
|
+
if (isTerminalWord(stage["runStatus"]) && !isTerminalWord(patch["runStatus"])
|
|
648
|
+
&& typeof patchRunId === "string" && sameRunIdentity(settledRunId, patchRunId))
|
|
649
|
+
return;
|
|
650
|
+
// THE HUSK-DOWNGRADE REFUSAL (trail-defect-hunt §2): a
|
|
651
|
+
// terminal, url-less result for a run whose settled face
|
|
652
|
+
// carries urls is bookkeeping (the seat's own mirror husk
|
|
653
|
+
// racing the client's rich patch) — the hero slot never
|
|
654
|
+
// downgrades. A DIFFERENT run_id is a fresh press and runs.
|
|
655
|
+
if (isTerminalWord(patch["runStatus"]) && sameRunIdentity(settledRunId, patchRunId)
|
|
656
|
+
&& resultOutputUrls(stage["result"]).length > 0
|
|
657
|
+
&& resultOutputUrls(patch["result"]).length === 0)
|
|
658
|
+
return;
|
|
659
|
+
const prev = stage["result"];
|
|
660
|
+
const next = patch["result"];
|
|
661
|
+
|
|
662
|
+
// the lost-gen disease: "when I make a new gen it doesn't
|
|
663
|
+
// appear in history — clicking a history item overwrites the
|
|
664
|
+
// preview with no way to recover the most recent gen"). Two
|
|
665
|
+
// archives ride the SAME write, oldest-first so the order in
|
|
666
|
+
// the list is append-stable:
|
|
667
|
+
// 1. SUPERSEDE — a cleared/overwritten result that carried
|
|
668
|
+
// outputs is archived (the stroke-40 law, unchanged: a
|
|
669
|
+
// face that would LOSE urls never drops them).
|
|
670
|
+
// 2. LANDING — the incoming result itself, when it CARRIES
|
|
671
|
+
// outputs AND the write is not a LIVE face (the
|
|
672
|
+
// settled-landing law, bench-trail W1: the current run's
|
|
673
|
+
// urls never enter history before the card finishes; a
|
|
674
|
+
// mid-run face archives nothing). A result-only patch (no
|
|
675
|
+
// runStatus arm) is a pick/manual spell, not a run face —
|
|
676
|
+
// it lands unless the row reads a live word (the landing
|
|
677
|
+
// gate below).
|
|
678
|
+
// History[0] is ALWAYS the newest SETTLED gen. Live
|
|
679
|
+
// mid-run patches ({run_id, progress} — no urls) archive
|
|
680
|
+
// nothing either way, and the dedup makes re-landings
|
|
681
|
+
// idempotent.
|
|
682
|
+
const archiveInto = (urls, entry) => {
|
|
683
|
+
const hist = Array.isArray(stage["history"]) ? [...stage["history"]] : [];
|
|
684
|
+
// THE ONE-ROW-PER-RUN LAW (bench-trail W2): a run is one
|
|
685
|
+
// history entry — the media route and the gallery proxy
|
|
686
|
+
// spell the same file differently, so identity is the
|
|
687
|
+
// run_id, with the canonical-url set as the no-id fallback
|
|
688
|
+
const entryRunId = typeof entry["run_id"] === "string" ? entry["run_id"] : undefined;
|
|
689
|
+
if (entryRunId !== undefined) {
|
|
690
|
+
const already = hist.some((e) => sameRunIdentity(e["run_id"], entryRunId));
|
|
691
|
+
if (already)
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
const canon = urls.map(canonicalOutputUrl);
|
|
695
|
+
const already = hist.some((e) => sameUrlSet(resultOutputUrls(e).map(canonicalOutputUrl), canon));
|
|
696
|
+
if (!already) {
|
|
697
|
+
hist.unshift(entry);
|
|
698
|
+
stage["history"] = hist.slice(0, 12);
|
|
699
|
+
}
|
|
700
|
+
};
|
|
701
|
+
// the landing gate (bench-trail W1 vs the pinned pick-spell
|
|
702
|
+
// contract, remote.spec "empty {} snapshot"): a write that
|
|
703
|
+
// DECLARES a live run face (a non-terminal runStatus arm)
|
|
704
|
+
// never lands; a terminal arm lands; and a patch with NO
|
|
705
|
+
// runStatus arm at all is not a run face — it lands unless the
|
|
706
|
+
// row is mid-run (a live word), which is the stuck-row wound's
|
|
707
|
+
// re-add shape.
|
|
708
|
+
const LIVE_TRAIL_WORDS = new Set(["running", "queued", "pending", "stopping"]);
|
|
709
|
+
const landingTerminal = isTerminalWord(patch["runStatus"]) ||
|
|
710
|
+
(!("runStatus" in patch) && !LIVE_TRAIL_WORDS.has(String(stage["runStatus"])));
|
|
711
|
+
// the params arm rides only when the snapshot NAMES something —
|
|
712
|
+
// an undefined or empty {} snapshot carries no key (the chip
|
|
713
|
+
// diffs against live params; an empty diff is no chip at all)
|
|
714
|
+
const paramsArm = (snapshot) => snapshot !== undefined && typeof snapshot === "object" && !Array.isArray(snapshot)
|
|
715
|
+
&& Object.keys(snapshot).length > 0
|
|
716
|
+
? { params: snapshot }
|
|
717
|
+
: {};
|
|
718
|
+
if (resultOutputUrls(prev).length > 0 && !sameResultOutputs(prev, next)) {
|
|
719
|
+
archiveInto(resultOutputUrls(prev), {
|
|
720
|
+
...prev,
|
|
721
|
+
...paramsArm(stage["resultParams"]),
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
stage["result"] = patch["result"];
|
|
725
|
+
stage["resultParams"] = stage["params"];
|
|
726
|
+
const landingUrls = resultOutputUrls(next);
|
|
727
|
+
if (landingUrls.length > 0 && landingTerminal) {
|
|
728
|
+
archiveInto(landingUrls, {
|
|
729
|
+
...next,
|
|
730
|
+
...paramsArm(stage["resultParams"]),
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
if ("runStatus" in patch)
|
|
735
|
+
stage["runStatus"] = patch["runStatus"];
|
|
736
|
+
// THE HEALTH ARM (LOW-23): the transport word's own channel — a
|
|
737
|
+
// null value DELETES the field (the recovery clear; a stage that
|
|
738
|
+
// never escalated never carries it), the merge-patch semantics
|
|
739
|
+
// the params arm already owns.
|
|
740
|
+
if ("health" in patch) {
|
|
741
|
+
if (patch["health"] === null)
|
|
742
|
+
delete stage["health"];
|
|
743
|
+
else
|
|
744
|
+
stage["health"] = patch["health"];
|
|
745
|
+
}
|
|
746
|
+
};
|
|
747
|
+
// the params the row will HOLD once this write lands (undefined
|
|
748
|
+
// when the body carries no params arm) — judged as half of the
|
|
749
|
+
// (params, inputs) PAIR before either assignment rides.
|
|
750
|
+
let pendingParams;
|
|
751
|
+
|
|
752
|
+
|
|
753
|
+
// array (the batch-P corruption family) or boolean body answered
|
|
754
|
+
// delivered:true with a rev bump while merging NOTHING, and the
|
|
755
|
+
// bench verb refuses the identical body. The GUI's keystroke
|
|
756
|
+
// door speaks the bench's voice.
|
|
757
|
+
if ("params" in patch && patch["params"] !== null &&
|
|
758
|
+
(typeof patch["params"] !== "object" || Array.isArray(patch["params"]))) {
|
|
759
|
+
throw new RemoteError("bench/bad-body", "params must be an object", {});
|
|
760
|
+
}
|
|
761
|
+
// WHOLE-MAP DELETE (the type comment's promise, S-E1's second
|
|
762
|
+
// half): the per-key law at map scale — presence IS the write,
|
|
763
|
+
// and a null params value DELETES the whole map. The empty view
|
|
764
|
+
// rides the SAME pair-gate below (a delete can orphan a $spell
|
|
765
|
+
// into a dead wire exactly like a per-key null can).
|
|
766
|
+
const paramsWholeDelete = "params" in patch && patch["params"] === null;
|
|
767
|
+
if (paramsWholeDelete)
|
|
768
|
+
pendingParams = {};
|
|
769
|
+
if ("params" in patch && patch["params"] !== null && typeof patch["params"] === "object" && !Array.isArray(patch["params"])) {
|
|
770
|
+
// PER-KEY merge (never replace) — the old door's law, now with
|
|
771
|
+
// JSON Merge Patch null semantics: a null value DELETES the
|
|
772
|
+
// key (the layered-params unset path — presence IS the override
|
|
773
|
+
// mark, so removal is a first-class write, never a value trick).
|
|
774
|
+
const merged = stage["params"] !== null && typeof stage["params"] === "object" && !Array.isArray(stage["params"])
|
|
775
|
+
? { ...stage["params"] }
|
|
776
|
+
: {};
|
|
777
|
+
|
|
778
|
+
// every other params-bearing door (add, patch, load) unwraps the
|
|
779
|
+
// wire's {item:[…]} array shape before it stores, so storage
|
|
780
|
+
// holds clean arrays; this door merged the raw body, so the SAME
|
|
781
|
+
// patch through bench/patch and through drafts/patch produced
|
|
782
|
+
// two different stored rows — and the wrapped shape is poison
|
|
783
|
+
// for every reader downstream.
|
|
784
|
+
const incoming = unwrapItemArrays(patch["params"]);
|
|
785
|
+
for (const k of Object.keys(incoming)) {
|
|
786
|
+
if (incoming[k] === null)
|
|
787
|
+
delete merged[k];
|
|
788
|
+
else
|
|
789
|
+
merged[k] = incoming[k];
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// audit's CRITICAL): bench docs live in THIS table (a
|
|
793
|
+
// "bench-<session>" id satisfies DRAFT_ID_RE), and the GUI's
|
|
794
|
+
// param editor rides exactly this call (card-view's
|
|
795
|
+
// patchStageParams) — so every keystroke edited a live stage
|
|
796
|
+
// with the model board AND the lane relations unjudged, while
|
|
797
|
+
// the agent's bench/patch refused the identical body. The
|
|
798
|
+
// merged view (never the patch arm alone — a per-key merge
|
|
799
|
+
// composes with what the row already carried) walks the same
|
|
800
|
+
// composed admission, BEFORE the assignment: the domain's
|
|
801
|
+
// cached record is mutated in place below, so a refusal after
|
|
802
|
+
// the assignment would leak the refused merge.
|
|
803
|
+
const stageCard = typeof stage["card"] === "string" ? stage["card"] : "";
|
|
804
|
+
const admit = this.benchAdmit();
|
|
805
|
+
if (stageCard !== "" && admit !== undefined) {
|
|
806
|
+
// NO PROVENANCE (the audit's GAP 3): the restored carve is
|
|
807
|
+
// load-time grace; a patch is a live compose act judged by
|
|
808
|
+
// the DECL the next run composes from. Forwarding the row's
|
|
809
|
+
// provenance here made the grace sticky forever.
|
|
810
|
+
|
|
811
|
+
// audit's row 7): the same self-naming strip every bench
|
|
812
|
+
// door judges through — a model key naming the row's own
|
|
813
|
+
// card never survives to compose, so this door judges the
|
|
814
|
+
// stripped view or refuses what the run admits.
|
|
815
|
+
admit(stageCard, strippedForAdmission(stageCard, merged), undefined);
|
|
816
|
+
}
|
|
817
|
+
// DEFERRED (the write-seat audit's row 3, second half): the
|
|
818
|
+
// assignment waits for the wiring pair-gate below — the pair
|
|
819
|
+
// (params, inputs) is judged as ONE write, and a refused
|
|
820
|
+
// orphan must leak NEITHER half into the cached record.
|
|
821
|
+
pendingParams = merged;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// CRITICAL-2: the params arm walked the composed admission and
|
|
825
|
+
// this one did not — so a keystroke through the GUI's param
|
|
826
|
+
// editor could write a phantom wire or a $spell that the agent's
|
|
827
|
+
// bench/patch refuses on the identical body. The wiring gate is
|
|
828
|
+
// the bench domain's own (one implementation): the MERGED edges,
|
|
829
|
+
// judged against the live set BEFORE the assignment (the cached
|
|
830
|
+
// record mutates in place below).
|
|
831
|
+
|
|
832
|
+
// this door also patches FLOW-export drafts, whose inputs wire
|
|
833
|
+
// stages of their own document — a different law, judged where
|
|
834
|
+
// that document is compiled. The wiring gate belongs to the bench.
|
|
835
|
+
// A FLOW-EXPORT DOC'S INPUTS ARE ITS OWN DOCUMENT'S LAW
|
|
836
|
+
|
|
837
|
+
// below is the BENCH's wire law (a bench row is patched in place,
|
|
838
|
+
// so `null` must remove an edge). A flow-export draft replaces its
|
|
839
|
+
// inputs wholesale, which is what this door did before the wiring
|
|
840
|
+
// gate was added and what a later cut silently changed. The gate
|
|
841
|
+
// itself is bench-only — a flow doc's stages wire within their own
|
|
842
|
+
// document, judged where that document is compiled.
|
|
843
|
+
const isBenchDoc = draftId.startsWith(BENCH_ID_PREFIX);
|
|
844
|
+
if (!isBenchDoc) {
|
|
845
|
+
if ("inputs" in patch)
|
|
846
|
+
stage["inputs"] = patch["inputs"];
|
|
847
|
+
}
|
|
848
|
+
else {
|
|
849
|
+
const priorInputs = isObj(stage["inputs"]) ? stage["inputs"] : {};
|
|
850
|
+
const mergedInputs = { ...priorInputs };
|
|
851
|
+
if (isObj(patch["inputs"])) {
|
|
852
|
+
for (const [chan, ref] of Object.entries(patch["inputs"])) {
|
|
853
|
+
if (ref === null)
|
|
854
|
+
delete mergedInputs[chan];
|
|
855
|
+
else
|
|
856
|
+
mergedInputs[chan] = ref;
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
|
|
861
|
+
// `inputs`, so a params-only keystroke that dropped the
|
|
862
|
+
// "$image" spell orphaned the wire SILENTLY — the merged pair
|
|
863
|
+
// nobody judged was exactly the dead wire bench/patch refuses
|
|
864
|
+
// on the identical body. The gate now runs whenever EITHER
|
|
865
|
+
// half of the pair changes, over the PENDING params (the row's
|
|
866
|
+
// next view) and the merged inputs, BEFORE either lands.
|
|
867
|
+
if (pendingParams !== undefined || "inputs" in patch) {
|
|
868
|
+
// THE LIVE SET READS THE STAGES THE LOOKUP ABOVE ALREADY
|
|
869
|
+
|
|
870
|
+
// same value `stages.find` answered one screen up — a value
|
|
871
|
+
// that answers `find` is the array it walked — so the
|
|
872
|
+
// non-array arm and this row's own `?? ""` were dead legs
|
|
873
|
+
// (the row is the very one the finder selected BECAUSE its
|
|
874
|
+
// `id` equals the stage id asked for).
|
|
875
|
+
const liveIds = doc["stages"]
|
|
876
|
+
.map((r) => (isObj(r) ? String(r["id"] ?? "") : ""))
|
|
877
|
+
.filter((id) => id !== stageId);
|
|
878
|
+
admitWiring(stage, pendingParams !== undefined
|
|
879
|
+
? pendingParams
|
|
880
|
+
: (isObj(stage["params"]) ? stage["params"] : {}), mergedInputs, liveIds, stageId, draftId.replace(/^bench-/, ""),
|
|
881
|
+
|
|
882
|
+
// author seat tolerates the row's PRE wounds — a
|
|
883
|
+
// pre-broken input ref (dead before this patch) must not
|
|
884
|
+
// wedge every later keystroke with a 400 on the GUI's own
|
|
885
|
+
// param door. The tolerate set is built from the PRE row's
|
|
886
|
+
// inputs against the pre-live ids (the assignments below
|
|
887
|
+
// have not landed yet), so a fresh dead ref this patch
|
|
888
|
+
// introduces stays strict.
|
|
889
|
+
toleratedByRow([stage], new Set(liveIds)).get(stageId));
|
|
890
|
+
}
|
|
891
|
+
stage["inputs"] = mergedInputs;
|
|
892
|
+
}
|
|
893
|
+
if (pendingParams !== undefined) {
|
|
894
|
+
// THE STORED ROW IS THE JUDGED ROW (the bench audit's gap 3,
|
|
895
|
+
|
|
896
|
+
// the raw one — so a doc written here held a self-naming model
|
|
897
|
+
// key that add/load never store, and the next reader judged a
|
|
898
|
+
// different object than the one on disk. Strip once, judge that,
|
|
899
|
+
// store that. The whole-map delete takes the key out entirely —
|
|
900
|
+
// the row goes bare, the same shape a fresh add carries.
|
|
901
|
+
if (paramsWholeDelete)
|
|
902
|
+
delete stage["params"];
|
|
903
|
+
else
|
|
904
|
+
stage["params"] = strippedForAdmission(typeof stage["card"] === "string" ? stage["card"] : "", pendingParams);
|
|
905
|
+
}
|
|
906
|
+
if ("inputOutputs" in patch)
|
|
907
|
+
stage["inputOutputs"] = patch["inputOutputs"];
|
|
908
|
+
// EVERY GATE PASSED — the telemetry write lands now (deferred
|
|
909
|
+
// above so a refusal left no result behind; the run seat's own
|
|
910
|
+
// polls ride the same door and are judged by nothing, which is
|
|
911
|
+
// correct: a poll is not a composition).
|
|
912
|
+
// R6 CURE (round-14 race audit, LOW — telemetry-vs-remove window):
|
|
913
|
+
// the row this section resolved at its top can be REMOVED while
|
|
914
|
+
// the gates ran (remove_stage's resume section between the lookup
|
|
915
|
+
// and this call) — writing into the detached row then persisted
|
|
916
|
+
// the removal with the outcome written nowhere. Re-resolve the
|
|
917
|
+
// LIVE row here, immediately before the write: a still-present
|
|
918
|
+
// row takes the telemetry; a removed row skips the write (its
|
|
919
|
+
// next poll reads the honest delivered:false at the top lookup).
|
|
920
|
+
const liveStage = doc["stages"].find((s) => typeof s === "object" && s !== null && s["id"] === stageId);
|
|
921
|
+
if (liveStage !== undefined)
|
|
922
|
+
writeTelemetry(liveStage);
|
|
923
|
+
// THE BUMP (author patches only — the telemetry law above): the
|
|
924
|
+
// rev advances and the log carries the entry; a telemetry write
|
|
925
|
+
// persists the row WITHOUT touching the bench block (no bump, no
|
|
926
|
+
// log — the answer serves the CURRENT rev so a caller that treats
|
|
927
|
+
// it as the next CAS handle stays honest).
|
|
928
|
+
let rev = bench.rev;
|
|
929
|
+
|
|
930
|
+
// from cut to carried" — the toggle left NO trace: every params
|
|
931
|
+
// patch logged the summary "params", so four revs of edits could
|
|
932
|
+
// not be told apart from four model stamps. A params patch now
|
|
933
|
+
// names the keys it merged — the log alone answers "did the seam
|
|
934
|
+
// write ever land".) The spell: `params` alone when the patch
|
|
935
|
+
// carried an empty/non-object params arm (a door refusal would
|
|
936
|
+
// never let us get here), else `params:<inner keys sorted>`.
|
|
937
|
+
const paramsSummary = (p) => {
|
|
938
|
+
if (p === null || typeof p !== "object" || Array.isArray(p))
|
|
939
|
+
return "params";
|
|
940
|
+
const inner = Object.keys(p).sort();
|
|
941
|
+
return inner.length === 0 ? "params" : "params:" + inner.join(",");
|
|
942
|
+
};
|
|
943
|
+
const summary = Object.keys(patch).sort()
|
|
944
|
+
.map((k) => (k === "params" ? paramsSummary(patch["params"]) : k))
|
|
945
|
+
.join(",");
|
|
946
|
+
if (!telemetry) {
|
|
947
|
+
rev = bench.rev + 1;
|
|
948
|
+
const log = [...bench.log, {
|
|
949
|
+
rev,
|
|
950
|
+
at: Math.floor(Date.now() / 1000),
|
|
951
|
+
author: "user",
|
|
952
|
+
kind: "patch",
|
|
953
|
+
stage: stageId,
|
|
954
|
+
summary,
|
|
955
|
+
}];
|
|
956
|
+
doc["bench"] = { rev, log };
|
|
957
|
+
}
|
|
958
|
+
// THE AUDIT VOICE (the same night): the patch's arrival and its
|
|
959
|
+
// named keys also land in the service log — a UI write that dies
|
|
960
|
+
// client-side (never sent) leaves NO such line, so the silence is
|
|
961
|
+
// distinguishable from a refusal.
|
|
962
|
+
this.ctx.logger.info("drafts/patch %s %s rev=%d %s", draftId, stageId, rev, summary);
|
|
963
|
+
try {
|
|
964
|
+
await drafts.table("docs").put(draftId, {
|
|
965
|
+
updated_at: Math.floor(Date.now() / 1000),
|
|
966
|
+
doc: doc,
|
|
967
|
+
});
|
|
968
|
+
}
|
|
969
|
+
catch (error) {
|
|
970
|
+
/* v8 ignore next 7 -- a domain put failure requires a storage IO
|
|
971
|
+
* fault (disk/permission); the record is schema-valid by
|
|
972
|
+
* construction, so no in-process input can reach this arm. */
|
|
973
|
+
throw new RemoteError("draft/cannot-persist", thrownMessage(error), {}, { cause: error });
|
|
974
|
+
}
|
|
975
|
+
notifyBenchDocWrite(draftId);
|
|
976
|
+
return {
|
|
977
|
+
draftId,
|
|
978
|
+
stageId,
|
|
979
|
+
delivered: true,
|
|
980
|
+
patched: Object.keys(patch).sort(),
|
|
981
|
+
rev,
|
|
982
|
+
};
|
|
983
|
+
}
|
|
984
|
+
};
|
|
985
|
+
})();
|
|
986
|
+
export { DraftsRemoteService };
|
|
987
|
+
/** The bench Remote owner: ensure/get over the same drafts domain
|
|
988
|
+
* (the bench doc IS a draft doc, id "bench-<session>"). */
|
|
989
|
+
let BenchRemoteService = (() => {
|
|
990
|
+
let _classSuper = TypertRemoteService;
|
|
991
|
+
let _instanceExtraInitializers = [];
|
|
992
|
+
let _remoteExportGet_decorators;
|
|
993
|
+
let _subscribe_decorators;
|
|
994
|
+
let _remoteExportEnsure_decorators;
|
|
995
|
+
let _remoteExportPatch_decorators;
|
|
996
|
+
let _remoteExportAddStage_decorators;
|
|
997
|
+
let _remoteExportRemoveStage_decorators;
|
|
998
|
+
let _remoteExportClear_decorators;
|
|
999
|
+
let _remoteExportRewire_decorators;
|
|
1000
|
+
let _remoteExportReorder_decorators;
|
|
1001
|
+
let _remoteExportGroup_decorators;
|
|
1002
|
+
let _remoteExportUngroup_decorators;
|
|
1003
|
+
let _remoteExportUndo_decorators;
|
|
1004
|
+
return class BenchRemoteService extends _classSuper {
|
|
1005
|
+
static {
|
|
1006
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
1007
|
+
_remoteExportGet_decorators = [Remote("get")];
|
|
1008
|
+
_subscribe_decorators = [Remote({ mode: "stream" })];
|
|
1009
|
+
_remoteExportEnsure_decorators = [Remote("ensure")];
|
|
1010
|
+
_remoteExportPatch_decorators = [Remote("patch")];
|
|
1011
|
+
_remoteExportAddStage_decorators = [Remote("add-stage")];
|
|
1012
|
+
_remoteExportRemoveStage_decorators = [Remote("remove-stage")];
|
|
1013
|
+
_remoteExportClear_decorators = [Remote("clear")];
|
|
1014
|
+
_remoteExportRewire_decorators = [Remote("rewire")];
|
|
1015
|
+
_remoteExportReorder_decorators = [Remote("reorder")];
|
|
1016
|
+
_remoteExportGroup_decorators = [Remote("group")];
|
|
1017
|
+
_remoteExportUngroup_decorators = [Remote("ungroup")];
|
|
1018
|
+
_remoteExportUndo_decorators = [Remote("undo")];
|
|
1019
|
+
__esDecorate(this, null, _remoteExportGet_decorators, { kind: "method", name: "remoteExportGet", static: false, private: false, access: { has: obj => "remoteExportGet" in obj, get: obj => obj.remoteExportGet }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
1020
|
+
__esDecorate(this, null, _subscribe_decorators, { kind: "method", name: "subscribe", static: false, private: false, access: { has: obj => "subscribe" in obj, get: obj => obj.subscribe }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
1021
|
+
__esDecorate(this, null, _remoteExportEnsure_decorators, { kind: "method", name: "remoteExportEnsure", static: false, private: false, access: { has: obj => "remoteExportEnsure" in obj, get: obj => obj.remoteExportEnsure }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
1022
|
+
__esDecorate(this, null, _remoteExportPatch_decorators, { kind: "method", name: "remoteExportPatch", static: false, private: false, access: { has: obj => "remoteExportPatch" in obj, get: obj => obj.remoteExportPatch }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
1023
|
+
__esDecorate(this, null, _remoteExportAddStage_decorators, { kind: "method", name: "remoteExportAddStage", static: false, private: false, access: { has: obj => "remoteExportAddStage" in obj, get: obj => obj.remoteExportAddStage }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
1024
|
+
__esDecorate(this, null, _remoteExportRemoveStage_decorators, { kind: "method", name: "remoteExportRemoveStage", static: false, private: false, access: { has: obj => "remoteExportRemoveStage" in obj, get: obj => obj.remoteExportRemoveStage }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
1025
|
+
__esDecorate(this, null, _remoteExportClear_decorators, { kind: "method", name: "remoteExportClear", static: false, private: false, access: { has: obj => "remoteExportClear" in obj, get: obj => obj.remoteExportClear }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
1026
|
+
__esDecorate(this, null, _remoteExportRewire_decorators, { kind: "method", name: "remoteExportRewire", static: false, private: false, access: { has: obj => "remoteExportRewire" in obj, get: obj => obj.remoteExportRewire }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
1027
|
+
__esDecorate(this, null, _remoteExportReorder_decorators, { kind: "method", name: "remoteExportReorder", static: false, private: false, access: { has: obj => "remoteExportReorder" in obj, get: obj => obj.remoteExportReorder }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
1028
|
+
__esDecorate(this, null, _remoteExportGroup_decorators, { kind: "method", name: "remoteExportGroup", static: false, private: false, access: { has: obj => "remoteExportGroup" in obj, get: obj => obj.remoteExportGroup }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
1029
|
+
__esDecorate(this, null, _remoteExportUngroup_decorators, { kind: "method", name: "remoteExportUngroup", static: false, private: false, access: { has: obj => "remoteExportUngroup" in obj, get: obj => obj.remoteExportUngroup }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
1030
|
+
__esDecorate(this, null, _remoteExportUndo_decorators, { kind: "method", name: "remoteExportUndo", static: false, private: false, access: { has: obj => "remoteExportUndo" in obj, get: obj => obj.remoteExportUndo }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
1031
|
+
if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
1032
|
+
}
|
|
1033
|
+
constructor(ctx) {
|
|
1034
|
+
super(ctx, "benchRemote", { namespace: "bench" });
|
|
1035
|
+
__runInitializers(this, _instanceExtraInitializers);
|
|
1036
|
+
|
|
1037
|
+
// push spine is the typed `bench.changed` event, not a module Set.
|
|
1038
|
+
installBenchChangeSeam(ctx);
|
|
1039
|
+
}
|
|
1040
|
+
/** Endpoint `bench/get` — one session's bench block as the row. */
|
|
1041
|
+
async remoteExportGet(sessionKey, signal) {
|
|
1042
|
+
return await this.get(sessionKey, signal);
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
|
|
1046
|
+
|
|
1047
|
+
|
|
1048
|
+
|
|
1049
|
+
|
|
1050
|
+
|
|
1051
|
+
|
|
1052
|
+
|
|
1053
|
+
|
|
1054
|
+
async *subscribe(sessionKey, signal) {
|
|
1055
|
+
signal.throwIfAborted();
|
|
1056
|
+
for (;;) {
|
|
1057
|
+
|
|
1058
|
+
// watch attaches BEFORE the read, not after the yield. A write
|
|
1059
|
+
// landing while the consumer still holds the delivered row (the
|
|
1060
|
+
// generator suspended at the yield — no park listener attached)
|
|
1061
|
+
// once matched no listener and the park slept until the NEXT
|
|
1062
|
+
// write; the flag re-loops at once instead of parking past it.
|
|
1063
|
+
let changedMidFlight = false;
|
|
1064
|
+
const offWatch = onBenchChange((session) => {
|
|
1065
|
+
if (session === sessionKey)
|
|
1066
|
+
changedMidFlight = true;
|
|
1067
|
+
});
|
|
1068
|
+
try {
|
|
1069
|
+
// THE ZERO-STATE LAW (the stream tail's birth watch): a fresh
|
|
1070
|
+
// conversation's bench does not exist until its first write.
|
|
1071
|
+
// The gateway's RemoteStream classifies a REJECTED open as
|
|
1072
|
+
// TERMINAL (no retry — terminalStreamFailure), so a throwing
|
|
1073
|
+
// first read here would kill the browser's whole bench stream
|
|
1074
|
+
// the moment a conversation starts. The watch instead serves
|
|
1075
|
+
// the ZERO STATE (rev 0, empty stages — the same row shape the
|
|
1076
|
+
// client's emptyBench served in the poll era) and stays open:
|
|
1077
|
+
// the birth (ensure's notifyBenchChange) wakes the park and the
|
|
1078
|
+
// next read serves the born bench. `bench/get` still refuses
|
|
1079
|
+
// not-found for direct unary callers — only the WATCH degrades.
|
|
1080
|
+
let row;
|
|
1081
|
+
try {
|
|
1082
|
+
row = await this.get(sessionKey, signal);
|
|
1083
|
+
}
|
|
1084
|
+
catch (error) {
|
|
1085
|
+
if (error instanceof RemoteError &&
|
|
1086
|
+
error.code === "bench/not-found") {
|
|
1087
|
+
row = {
|
|
1088
|
+
draft_id: benchDraftIdOf(sessionKey),
|
|
1089
|
+
session: sessionKey,
|
|
1090
|
+
rev: 0,
|
|
1091
|
+
intent: "",
|
|
1092
|
+
stages: [],
|
|
1093
|
+
log: [],
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1096
|
+
else {
|
|
1097
|
+
throw error;
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
yield row;
|
|
1101
|
+
// wake on the session's next write (or abort). An abort that
|
|
1102
|
+
// landed while the generator was suspended AT A YIELD has no
|
|
1103
|
+
// park listener attached — the executor re-checks
|
|
1104
|
+
// signal.aborted AND the mid-flight watch flag (S-S3) so the
|
|
1105
|
+
// missed event still wakes the park. The stream then ends
|
|
1106
|
+
// CLEAN on abort (done: true — the holder released; a throw
|
|
1107
|
+
// would surface as an error on a normal close). One finish arm
|
|
1108
|
+
// removes BOTH listeners: no stale abort listener per park.
|
|
1109
|
+
await new Promise((resolve) => {
|
|
1110
|
+
if (signal.aborted || changedMidFlight) {
|
|
1111
|
+
resolve();
|
|
1112
|
+
return;
|
|
1113
|
+
}
|
|
1114
|
+
// One finish arm, no re-entry guard needed: finish detaches
|
|
1115
|
+
// BOTH listeners before resolving, so it can never run twice
|
|
1116
|
+
// (the old settled-guard was unreachable by construction).
|
|
1117
|
+
const offAbort = () => { signal.removeEventListener("abort", onAbort); };
|
|
1118
|
+
const finish = () => {
|
|
1119
|
+
offChange();
|
|
1120
|
+
offAbort();
|
|
1121
|
+
resolve();
|
|
1122
|
+
};
|
|
1123
|
+
const offChange = onBenchChange((session) => {
|
|
1124
|
+
if (session !== sessionKey)
|
|
1125
|
+
return;
|
|
1126
|
+
finish();
|
|
1127
|
+
});
|
|
1128
|
+
const onAbort = () => { finish(); };
|
|
1129
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1130
|
+
});
|
|
1131
|
+
if (signal.aborted)
|
|
1132
|
+
return;
|
|
1133
|
+
}
|
|
1134
|
+
finally {
|
|
1135
|
+
offWatch();
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
/** Endpoint `bench/ensure` — birth or load (the only creating arm). */
|
|
1140
|
+
async remoteExportEnsure(doc, signal) {
|
|
1141
|
+
return await this.ensure(doc, signal);
|
|
1142
|
+
}
|
|
1143
|
+
/** The in-process API the adapters above delegate to. Classified
|
|
1144
|
+
* bench failures pass through untouched; only the open itself
|
|
1145
|
+
* wraps as bench/store-failed. */
|
|
1146
|
+
async get(session, signal) {
|
|
1147
|
+
signal.throwIfAborted();
|
|
1148
|
+
let drafts;
|
|
1149
|
+
try {
|
|
1150
|
+
drafts = await openDraftsDomain(this.ctx);
|
|
1151
|
+
}
|
|
1152
|
+
catch (error) {
|
|
1153
|
+
throw new RemoteError("bench/store-failed", thrownMessage(error), {}, { cause: error });
|
|
1154
|
+
}
|
|
1155
|
+
const row = getBench(drafts, session);
|
|
1156
|
+
return { ...row, stages: withResolvedStages(this.ctx, row.stages) };
|
|
1157
|
+
}
|
|
1158
|
+
/** The in-process API the adapter above delegates to. */
|
|
1159
|
+
async ensure(doc, signal) {
|
|
1160
|
+
signal.throwIfAborted();
|
|
1161
|
+
if (typeof doc !== "object" || doc === null || Array.isArray(doc)) {
|
|
1162
|
+
throw new RemoteError("bench/bad-body", "body must be a JSON object", {});
|
|
1163
|
+
}
|
|
1164
|
+
let drafts;
|
|
1165
|
+
try {
|
|
1166
|
+
drafts = await openDraftsDomain(this.ctx);
|
|
1167
|
+
}
|
|
1168
|
+
catch (error) {
|
|
1169
|
+
throw new RemoteError("bench/store-failed", thrownMessage(error), {}, { cause: error });
|
|
1170
|
+
}
|
|
1171
|
+
const session = typeof doc["session"] === "string" ? doc["session"] : "";
|
|
1172
|
+
const row = await withBenchWrite(benchDraftIdOf(session), () => ensureBench(drafts, doc, benchAdmissionOf(this.ctx)));
|
|
1173
|
+
// ensureBench gates doc.session as a valid key before this line —
|
|
1174
|
+
// the row's own session can never differ here (no dead fallback).
|
|
1175
|
+
notifyBenchChange(String(doc["session"]));
|
|
1176
|
+
return row;
|
|
1177
|
+
}
|
|
1178
|
+
/** Endpoint `bench/patch` — params merge (+result/runStatus) with
|
|
1179
|
+
* CAS + log. */
|
|
1180
|
+
async remoteExportPatch(sessionKey, doc, signal) {
|
|
1181
|
+
return await this.patch(sessionKey, doc, signal);
|
|
1182
|
+
}
|
|
1183
|
+
/** Endpoint `bench/add-stage` — append a card stage. */
|
|
1184
|
+
async remoteExportAddStage(sessionKey, doc, signal) {
|
|
1185
|
+
return await this.addStage(sessionKey, doc, signal);
|
|
1186
|
+
}
|
|
1187
|
+
/** Endpoint `bench/remove-stage` — splice one stage out. */
|
|
1188
|
+
async remoteExportRemoveStage(sessionKey, doc, signal) {
|
|
1189
|
+
return await this.removeStage(sessionKey, doc, signal);
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
|
|
1193
|
+
|
|
1194
|
+
|
|
1195
|
+
async remoteExportClear(sessionKey, doc, signal) {
|
|
1196
|
+
return await this.clear(sessionKey, doc, signal);
|
|
1197
|
+
}
|
|
1198
|
+
/** Endpoint `bench/rewire` — set or clear one input binding. */
|
|
1199
|
+
async remoteExportRewire(sessionKey, doc, signal) {
|
|
1200
|
+
return await this.rewire(sessionKey, doc, signal);
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
|
|
1204
|
+
async remoteExportReorder(sessionKey, doc, signal) {
|
|
1205
|
+
signal.throwIfAborted();
|
|
1206
|
+
return withBenchWrite(benchDraftIdOf(sessionKey), async () => {
|
|
1207
|
+
const drafts = await this.open(signal);
|
|
1208
|
+
const row = await reorderBench(drafts, sessionKey, doc);
|
|
1209
|
+
notifyBenchChange(sessionKey);
|
|
1210
|
+
return row;
|
|
1211
|
+
});
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
|
|
1215
|
+
async remoteExportGroup(sessionKey, doc, signal) {
|
|
1216
|
+
signal.throwIfAborted();
|
|
1217
|
+
return withBenchWrite(benchDraftIdOf(sessionKey), async () => {
|
|
1218
|
+
const drafts = await this.open(signal);
|
|
1219
|
+
const row = await groupBench(drafts, sessionKey, doc, benchAdmissionOf(this.ctx));
|
|
1220
|
+
notifyBenchChange(sessionKey);
|
|
1221
|
+
return row;
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
1224
|
+
/** Endpoint `bench/ungroup` — spill a compound's children back. */
|
|
1225
|
+
async remoteExportUngroup(sessionKey, doc, signal) {
|
|
1226
|
+
signal.throwIfAborted();
|
|
1227
|
+
return withBenchWrite(benchDraftIdOf(sessionKey), async () => {
|
|
1228
|
+
const drafts = await this.open(signal);
|
|
1229
|
+
const row = await ungroupBench(drafts, sessionKey, doc, benchAdmissionOf(this.ctx));
|
|
1230
|
+
notifyBenchChange(sessionKey);
|
|
1231
|
+
return row;
|
|
1232
|
+
});
|
|
1233
|
+
}
|
|
1234
|
+
/** Endpoint `bench/undo` — walk the log tail, apply inverses. */
|
|
1235
|
+
async remoteExportUndo(sessionKey, doc, signal) {
|
|
1236
|
+
return await this.undo(sessionKey, doc, signal);
|
|
1237
|
+
}
|
|
1238
|
+
/** The in-process APIs the adapters above delegate to. The
|
|
1239
|
+
* domain's classified failures pass through untouched; only the
|
|
1240
|
+
* open itself wraps as bench/store-failed. */
|
|
1241
|
+
async open(signal) {
|
|
1242
|
+
signal.throwIfAborted();
|
|
1243
|
+
try {
|
|
1244
|
+
return await openDraftsDomain(this.ctx);
|
|
1245
|
+
}
|
|
1246
|
+
catch (error) {
|
|
1247
|
+
throw new RemoteError("bench/store-failed", thrownMessage(error), {}, { cause: error });
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
async patch(session, doc, signal) {
|
|
1251
|
+
signal.throwIfAborted();
|
|
1252
|
+
return withBenchWrite(benchDraftIdOf(session), async () => {
|
|
1253
|
+
const drafts = await this.open(signal);
|
|
1254
|
+
const row = await patchBench(drafts, session, doc, benchAdmissionOf(this.ctx), laneMigrationOf(this.ctx));
|
|
1255
|
+
notifyBenchChange(session);
|
|
1256
|
+
return row;
|
|
1257
|
+
});
|
|
1258
|
+
}
|
|
1259
|
+
async addStage(session, doc, signal) {
|
|
1260
|
+
signal.throwIfAborted();
|
|
1261
|
+
return withBenchWrite(benchDraftIdOf(session), async () => {
|
|
1262
|
+
const drafts = await this.open(signal);
|
|
1263
|
+
const row = await addStageBench(drafts, session, doc, benchAdmissionOf(this.ctx));
|
|
1264
|
+
notifyBenchChange(session);
|
|
1265
|
+
return row;
|
|
1266
|
+
});
|
|
1267
|
+
}
|
|
1268
|
+
async removeStage(session, doc, signal) {
|
|
1269
|
+
signal.throwIfAborted();
|
|
1270
|
+
return withBenchWrite(benchDraftIdOf(session), async () => {
|
|
1271
|
+
const drafts = await this.open(signal);
|
|
1272
|
+
const row = await removeStageBench(drafts, session, doc);
|
|
1273
|
+
notifyBenchChange(session);
|
|
1274
|
+
return row;
|
|
1275
|
+
});
|
|
1276
|
+
}
|
|
1277
|
+
async clear(session, doc, signal) {
|
|
1278
|
+
signal.throwIfAborted();
|
|
1279
|
+
return withBenchWrite(benchDraftIdOf(session), async () => {
|
|
1280
|
+
const drafts = await this.open(signal);
|
|
1281
|
+
const row = await clearBench(drafts, session, doc);
|
|
1282
|
+
notifyBenchChange(session);
|
|
1283
|
+
return row;
|
|
1284
|
+
});
|
|
1285
|
+
}
|
|
1286
|
+
async rewire(session, doc, signal) {
|
|
1287
|
+
signal.throwIfAborted();
|
|
1288
|
+
return withBenchWrite(benchDraftIdOf(session), async () => {
|
|
1289
|
+
const drafts = await this.open(signal);
|
|
1290
|
+
const row = await rewireBench(drafts, session, doc);
|
|
1291
|
+
notifyBenchChange(session);
|
|
1292
|
+
return row;
|
|
1293
|
+
});
|
|
1294
|
+
}
|
|
1295
|
+
async undo(session, doc, signal) {
|
|
1296
|
+
signal.throwIfAborted();
|
|
1297
|
+
return withBenchWrite(benchDraftIdOf(session), async () => {
|
|
1298
|
+
const drafts = await this.open(signal);
|
|
1299
|
+
const row = await undoBench(drafts, session, doc, benchAdmissionOf(this.ctx));
|
|
1300
|
+
notifyBenchChange(session);
|
|
1301
|
+
return row;
|
|
1302
|
+
});
|
|
1303
|
+
}
|
|
1304
|
+
};
|
|
1305
|
+
})();
|
|
1306
|
+
export { BenchRemoteService };
|
|
1307
|
+
//# sourceMappingURL=remote.js.map
|