@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/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