@melite/estate-drafts 0.1.4-alpha → 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/README.md ADDED
@@ -0,0 +1,56 @@
1
+ ---
2
+ description: The drafts and bench Typert remote owner — draft docs in a storage domain plus the shared bench mutation surface, mounted as host Cordis services.
3
+ kind: package-library
4
+ ---
5
+
6
+ # @melite/estate-drafts
7
+
8
+ `@melite/estate-drafts` owns the composer's draft docs and the shared bench.
9
+ It mounts the drafts storage-domain seat, `DraftsRemoteService`, and
10
+ `BenchRemoteService` on the host fiber; the host's typert gateway claims the
11
+ endpoints through its discovery walk, and the storage facility arrives via
12
+ the host's storageDomain seat (lazy open on first call). The estate bundle
13
+ mounts it before the runs registry, whose submit gate re-uses this package's
14
+ draft-id law.
15
+
16
+ ## Contract
17
+
18
+ - Storage: table `docs` keyed by draft id (`DRAFT_ID_RE`: 1..96 chars of
19
+ `[A-Za-z0-9_-]`, path-safe — the SSOT the queue re-exports).
20
+ Schema-failing records are backed up and skipped; the global slot holds
21
+ the one-time legacy-import marker.
22
+ - Draft docs: six contract keys (`draft_id`, `status`, `createdAt`,
23
+ `completedAt`, `intent`, `stages`) plus caller extras verbatim — the serve
24
+ read is deliberately loose; the write doors are strict.
25
+ - Bench: `ensure`, `get`, `patch`, `add-stage`, `remove-stage`, `reorder`,
26
+ `group`, `rewire`, `undo`, `clear`, keyed by the caller's session key,
27
+ with change notification through the bench change seam.
28
+ - Failures are the protocol's ONE RemoteError with `draft/*` and `bench/*`
29
+ codes declared next to their throws.
30
+ - Declared exports: `.` (plugin + services), `./types` (shared row types),
31
+ `./typert` (generated host-face declaration), `./remote` (generated
32
+ browser client), `./package.json`.
33
+
34
+ ## Model Experience
35
+
36
+ ### Request context and condition
37
+
38
+ #### What the model sees
39
+
40
+ None — the package contributes no model-visible input.
41
+
42
+ #### Token effect
43
+
44
+ Zero-direct token effect.
45
+
46
+ #### KV Cache effect
47
+
48
+ Independent — no conversation state.
49
+
50
+ ## Known Limitations and Deferred Work
51
+
52
+ - **Bench access is session-key scoped** — a caller outside the editor's
53
+ session model must carry the session key itself; there is no cross-session
54
+ bench view.
55
+ - **The loose serve read is deliberate** — extras ride back unvalidated; a
56
+ writer bug can land extras the read will not catch.
package/lib/bench.d.ts CHANGED
@@ -48,6 +48,15 @@ declare module "@deepseek-ai/dsh-typert-protocol" {
48
48
  readonly session: string;
49
49
  readonly stage: string;
50
50
  };
51
+ /** The anchored reorder's target is gone (the F1-residue cure,
52
+ * round 25): the id the drop named as its anchor is not on the
53
+ * CURRENT list — a second author removed it between the press
54
+ * and the apply. The honest re-ask, never a silent fallback to
55
+ * the stale numeric slot. */
56
+ "bench/anchor-gone": {
57
+ readonly session: string;
58
+ readonly anchor: string;
59
+ };
51
60
 
52
61
 
53
62
 
@@ -188,7 +197,7 @@ export interface BenchPatchRow {
188
197
 
189
198
 
190
199
 
191
- export declare function patchBench(drafts: Domain<DraftsDomainSpec>, session: string, body: Record<string, unknown>, admit?: StageAdmission): Promise<BenchPatchRow>;
200
+ export declare function patchBench(drafts: Domain<DraftsDomainSpec>, session: string, body: Record<string, unknown>, admit?: StageAdmission, migrate?: LaneMigration): Promise<BenchPatchRow>;
192
201
  /** The stage row: the arm's answer (stage id + rev). */
193
202
  export interface BenchStageRow {
194
203
  readonly draft_id: string;
@@ -217,12 +226,37 @@ export type StageAdmission = (card: string, params: Record<string, unknown>, pro
217
226
 
218
227
 
219
228
 
229
+
230
+
231
+ export type LaneMigration = (card: string, params: Record<string, unknown>, priorParams: Record<string, unknown>) => Record<string, unknown>;
232
+
233
+
234
+
235
+
236
+
220
237
  export declare function strippedForAdmission(card: string, params: Record<string, unknown>): Record<string, unknown>;
221
238
  /** The wiring admission over ONE row (params = the MERGED view the
222
239
  * next run composes; inputs = the MERGED wiring; liveIds = the
223
240
  * bench's stage ids after the write; selfId = the row's own id —
224
241
  * self-wiring is a cycle, refused). Throws bench/* named. */
225
242
  export declare function admitWiring(row: Record<string, unknown>, params: Record<string, unknown>, inputs: Record<string, unknown>, liveIds: readonly string[], selfId: string, session: string, tolerate?: ReadonlySet<string>): void;
243
+ /** The un-wedge law's tolerance table: for every row in the list
244
+ * being JUDGED, the upstreams it wires that are absent from the
245
+ * PRE-verb live set — refs the verb did not create (they were
246
+ * already dead, or ride a restored snapshot the log itself
247
+ * recorded) and must not be refused for. Keyed by row/child id.
248
+ *
249
+ * The walk is over the JUDGED rows (not the pre rows): a verb
250
+ * like clear leaves no pre rows at all, and undo restores rows
251
+ * the pre state doesn't hold — the ref a row CARRIES is the
252
+ * thing to classify, against what was live before the verb. A
253
+ * ref newly introduced by a verb (rewire's `from`, a patch's
254
+ * spell changes) never rides this table: those seats build their
255
+ * tolerate sets from the PRE ROW's own inputs, so a fresh dead
256
+ * target stays strict. */
257
+ /** Exported for the drafts seat (audit S-S1): the un-wedge table is
258
+ * the same law at every author seat. */
259
+ export declare function toleratedByRow(stages: Array<Record<string, unknown>>, preLive: ReadonlySet<string>): Map<string, Set<string>>;
226
260
  /** add_stage — append a card stage with optional wiring (the
227
261
  * runner's input convention rides the row). */
228
262
  export declare function addStageBench(drafts: Domain<DraftsDomainSpec>, session: string, body: Record<string, unknown>, admit?: StageAdmission): Promise<BenchStageRow>;
@@ -233,6 +267,12 @@ export declare function addStageBench(drafts: Domain<DraftsDomainSpec>, session:
233
267
 
234
268
 
235
269
 
270
+
271
+
272
+
273
+
274
+
275
+
236
276
  export declare function reorderBench(drafts: Domain<DraftsDomainSpec>, session: string, body: Record<string, unknown>): Promise<BenchReorderRow>;
237
277
  /** The reorder row: the move's coordinates for the caller's optimistic
238
278
  * update (from → to, both POST-splice-clamp). */
@@ -309,6 +349,13 @@ export interface BenchChangeSeam {
309
349
  /** Install the seam (the plugin does this with its Context; tests do
310
350
  * it with their own). Pass null to detach. */
311
351
  export declare function setBenchChangeSeam(seam: BenchChangeSeam | null): void;
352
+ /** Detach the seam ONLY if `seam` is still the installed one — the
353
+ * ownership stamp that keeps dispose honest: a fiber that installed
354
+ * EARLIER (a newer install displaced it) detaches nothing, so the
355
+ * live seam survives the older fiber's unwind.
356
+ * @param seam - the seam instance the caller installed.
357
+ */
358
+ export declare function clearBenchChangeSeam(seam: BenchChangeSeam): void;
312
359
 
313
360
 
314
361
 
package/lib/bench.js CHANGED
@@ -15,6 +15,7 @@
15
15
  import { unwrapItemArrays } from '@melite/comfyui-card-kit';
16
16
  import { benchDraftIdOf, BIRTH_STRIP_KEYS, LOAD_CARRY_KEYS, normalizeBenchStage, SELF_NAMED_FAMILIES, stripKeys } from '@melite/contracts/benchStage';
17
17
  import { RemoteError } from "@deepseek-ai/dsh-typert-protocol";
18
+ import { trailDedupe } from "./trail.js";
18
19
 
19
20
 
20
21
 
@@ -438,7 +439,14 @@ export async function ensureBench(drafts, body, admit) {
438
439
  continue;
439
440
  for (const key of MEMORY_KEYS) {
440
441
  if (prev[key] !== undefined && row[key] === undefined) {
441
- row[key] = prev[key];
442
+ // THE TRAIL HEAL AT CARRY (trail-defect-hunt §6): a memory
443
+ // carry of history passes through the one-row-per-run dedup —
444
+ // a pre-law duplicated trail (the operator's re-add wound,
445
+ // one run under two url spellings) never fossilises forward
446
+ // through load.
447
+ row[key] = key === "history" && Array.isArray(prev[key])
448
+ ? trailDedupe(prev[key])
449
+ : prev[key];
442
450
  }
443
451
  }
444
452
  }
@@ -635,7 +643,7 @@ const PATCH_KEYS = ["stage", "params", "result", "runStatus", "expect_rev", "aut
635
643
 
636
644
 
637
645
 
638
- export async function patchBench(drafts, session, body, admit) {
646
+ export async function patchBench(drafts, session, body, admit, migrate) {
639
647
  refuseUnknown(body, PATCH_KEYS);
640
648
  if ("result" in body || "runStatus" in body) {
641
649
  throw new RemoteError("bench/bad-body", "run outcomes ride the drafts/patch door (result/runStatus refused on the bench verb — it authors params only)", {});
@@ -704,7 +712,25 @@ export async function patchBench(drafts, session, body, admit) {
704
712
  // row (a group, an undo) judged a different object than the one
705
713
  // on disk. The merge strips before judging AND before storing:
706
714
  // one row, one view.
707
- const stored = strippedForAdmission(typeof st["card"] === "string" ? st["card"] : "", cur);
715
+
716
+ // incident — live on :3088): the merge above is where a lane
717
+ // switch assembles its view, and BEFORE this seat the switch
718
+ // REFUSED itself — the prior lane's members (voice on
719
+ // CustomVoice) ride the merged view, the new lane's surface
720
+ // (VoiceDesign) does not take them, and the admission refused
721
+ // "drop it or switch lane" while the form HIDES the row on the
722
+ // new lane: the named cure unclickable, the switch unlandable,
723
+ // the studio wedged between "the studio shows your edit; the
724
+ // bench does not". The registry drops the stale carry here —
725
+ // BEFORE the strip, the admission, and the store, so the row
726
+ // that lands is the row the next run composes (one view, every
727
+ // seat). The undo contract: undoing the switch restores the
728
+ // axes, not the dropped members — the author re-picks on the
729
+ // lane they return to.
730
+ const migrated = migrate !== undefined && isRecord(st["params"])
731
+ ? migrate(typeof st["card"] === "string" ? st["card"] : "", cur, st["params"])
732
+ : cur;
733
+ const stored = strippedForAdmission(typeof st["card"] === "string" ? st["card"] : "", migrated);
708
734
  // THE COMPOUND IS JUDGED BY ITS MEMBERS HERE TOO (the walls
709
735
 
710
736
  // belongs to a compound judged the COMPOUND ROW as a whole, and
@@ -969,7 +995,9 @@ function liveIdsOf(stages) {
969
995
  * spell changes) never rides this table: those seats build their
970
996
  * tolerate sets from the PRE ROW's own inputs, so a fresh dead
971
997
  * target stays strict. */
972
- function toleratedByRow(stages, preLive) {
998
+ /** Exported for the drafts seat (audit S-S1): the un-wedge table is
999
+ * the same law at every author seat. */
1000
+ export function toleratedByRow(stages, preLive) {
973
1001
  const out = new Map();
974
1002
  const rowOf = (r, id) => {
975
1003
  const inputs = r["inputs"];
@@ -1159,7 +1187,13 @@ export async function addStageBench(drafts, session, body, admit) {
1159
1187
  return { draft_id: loaded.draftId, session, rev: newRev, stage: stageId };
1160
1188
  }
1161
1189
  const REMOVE_KEYS = ["stage", "expect_rev", "author"];
1162
- const REORDER_KEYS = ["stage", "to", "expect_rev", "author"];
1190
+ const REORDER_KEYS = ["stage", "to", "expect_rev", "author", "after"];
1191
+
1192
+
1193
+
1194
+
1195
+
1196
+
1163
1197
 
1164
1198
 
1165
1199
 
@@ -1179,6 +1213,10 @@ export async function reorderBench(drafts, session, body) {
1179
1213
  if (typeof toRaw !== "number" || !Number.isInteger(toRaw) || toRaw < 0) {
1180
1214
  throw new RemoteError("bench/bad-body", "to must be a non-negative integer index", {});
1181
1215
  }
1216
+ const afterRaw = body["after"];
1217
+ if (afterRaw !== undefined && typeof afterRaw !== "string") {
1218
+ throw new RemoteError("bench/bad-body", "after must be a stage id string when present", {});
1219
+ }
1182
1220
  const author = authorOk(body["author"])
1183
1221
  ? body["author"]
1184
1222
  : "agent";
@@ -1187,8 +1225,20 @@ export async function reorderBench(drafts, session, body) {
1187
1225
  if (from < 0) {
1188
1226
  throw new RemoteError("bench/stage-not-found", "stage not found: " + stageId, { session, stage: stageId });
1189
1227
  }
1228
+ // anchor resolution: re-derive the target from the id the press
1229
+ // SAW, not the numeric slot it guessed. Unchanged list → the same
1230
+ // `to` the press sent (identity, never a behavior change); a
1231
+ // shifted list → the drop's intent survives the shift.
1232
+ let toTarget = toRaw;
1233
+ if (afterRaw !== undefined) {
1234
+ const anchor = stages.findIndex((s) => s["id"] === afterRaw);
1235
+ if (anchor < 0) {
1236
+ throw new RemoteError("bench/anchor-gone", "the card you dropped after is gone — drop again", { session, anchor: afterRaw });
1237
+ }
1238
+ toTarget = anchor;
1239
+ }
1190
1240
  const [row] = stages.splice(from, 1);
1191
- const to = Math.min(toRaw, stages.length);
1241
+ const to = Math.min(toTarget, stages.length);
1192
1242
  stages.splice(to, 0, row);
1193
1243
  loaded.doc["stages"] = stages;
1194
1244
  const newRev = await appendAndSave(drafts, loaded, {
@@ -1196,7 +1246,7 @@ export async function reorderBench(drafts, session, body) {
1196
1246
  kind: "reorder",
1197
1247
  stage: stageId,
1198
1248
  summary: "reorder " + String(row["card"] ?? stageId),
1199
- inverse: { stage: stageId, from, to },
1249
+ inverse: { stage: stageId, from, to, ...(afterRaw !== undefined ? { after: afterRaw } : {}) },
1200
1250
  });
1201
1251
  return { draft_id: loaded.draftId, session, rev: newRev, stage: stageId, from, to };
1202
1252
  }
@@ -1523,9 +1573,13 @@ const UNDO_KEYS = ["n", "author", "expect_rev"];
1523
1573
  export async function undoBench(drafts, session, body, admit) {
1524
1574
  refuseUnknown(body, UNDO_KEYS);
1525
1575
  const loaded = await loadBench(drafts, session);
1526
- if (body["expect_rev"] !== undefined) {
1527
- casCheck(loaded.block, body["expect_rev"], session);
1528
- }
1576
+ // THE FENCE IS NOT OPTIONAL (audit W-E2): undo reverts writes —
1577
+ // exactly the mutations the CAS law fences. It was the only verb
1578
+ // that skipped the check when expect_rev was absent, so an
1579
+ // un-read revert raced any concurrent writer and silently
1580
+ // double-applied. Same voice as every verb: absent expect_rev
1581
+ // refuses bench/cas-conflict with current_rev.
1582
+ casCheck(loaded.block, body["expect_rev"], session);
1529
1583
  const n = typeof body["n"] === "number" &&
1530
1584
  Number.isInteger(body["n"]) &&
1531
1585
  body["n"] > 0
@@ -1600,6 +1654,13 @@ export async function undoBench(drafts, session, body, admit) {
1600
1654
  }
1601
1655
  }
1602
1656
  const baseIds = liveIdsOf(baseRows);
1657
+ // THE TRAIL HEAL AT UNDO (trail-defect-hunt §6): a reinserted
1658
+ // pre-law row's duplicated history never rides back in — same
1659
+ // dedup the load carry applies.
1660
+ for (const r of stagesOf(undoDoc)) {
1661
+ if (Array.isArray(r["history"]))
1662
+ r["history"] = trailDedupe(r["history"]);
1663
+ }
1603
1664
  const restored = stagesOf(undoDoc);
1604
1665
  admitWiringList(restored, session, toleratedByRow(restored.filter((r) => baseIds.has(String(r["id"] ?? ""))), baseIds));
1605
1666
  })();
@@ -1828,6 +1889,16 @@ let benchChangeSeam = null;
1828
1889
  export function setBenchChangeSeam(seam) {
1829
1890
  benchChangeSeam = seam;
1830
1891
  }
1892
+ /** Detach the seam ONLY if `seam` is still the installed one — the
1893
+ * ownership stamp that keeps dispose honest: a fiber that installed
1894
+ * EARLIER (a newer install displaced it) detaches nothing, so the
1895
+ * live seam survives the older fiber's unwind.
1896
+ * @param seam - the seam instance the caller installed.
1897
+ */
1898
+ export function clearBenchChangeSeam(seam) {
1899
+ if (benchChangeSeam === seam)
1900
+ benchChangeSeam = null;
1901
+ }
1831
1902
 
1832
1903
 
1833
1904
 
@@ -1844,8 +1915,12 @@ export function onBenchChange(fn) {
1844
1915
  try {
1845
1916
  fn(session);
1846
1917
  }
1847
- catch {
1848
- /* a listener's fault never breaks the write */
1918
+ catch (listenerError) {
1919
+ // listenerError: the contract keeps a subscriber's fault from
1920
+ // breaking the write, but the fault is not silence — the row
1921
+ // names it like the runs seam's own wrap does
1922
+ console.warn("[estate-drafts] bench change listener refused for session " +
1923
+ JSON.stringify(session) + ":", listenerError);
1849
1924
  }
1850
1925
  });
1851
1926
  }
package/lib/index.d.ts CHANGED
@@ -1,12 +1,15 @@
1
1
  import type { Context, Plugin } from "@deepseek-ai/cordis";
2
+ import type { DraftsConfig } from "./store.js";
2
3
  export { BenchRemoteService, DraftsRemoteService, installBenchChangeSeam } from "./remote.js";
3
4
  export { resolveStorageRoot, openDraftsDomain, } from "./store.js";
5
+ export type { DraftsConfig } from "./store.js";
4
6
  export { DRAFT_ID_RE, draftIdOk } from "./spec.js";
7
+ export type { CharacterRecord } from "./spec.js";
5
8
  export type { BenchChangeSeam } from "./bench.js";
6
9
  export { setBenchChangeSeam } from "./bench.js";
7
10
  export type { BenchEnsureRow, BenchGetRow, DraftListResult, DraftListRow, } from "./remote.js";
8
11
  export declare const name = "estate-drafts";
9
- export declare function apply(ctx: Context): void;
12
+ export declare function apply(ctx: Context, config?: DraftsConfig): void;
10
13
  declare const plugin: Plugin;
11
14
  export default plugin;
12
15
  //# sourceMappingURL=index.d.ts.map
package/lib/index.js CHANGED
@@ -1,10 +1,14 @@
1
1
  import { BenchRemoteService, DraftsRemoteService } from "./remote.js";
2
+ import { DraftsDomainSeatService } from "./store.js";
2
3
  export { BenchRemoteService, DraftsRemoteService, installBenchChangeSeam } from "./remote.js";
3
4
  export { resolveStorageRoot, openDraftsDomain, } from "./store.js";
4
5
  export { DRAFT_ID_RE, draftIdOk } from "./spec.js";
5
6
  export { setBenchChangeSeam } from "./bench.js";
6
7
  export const name = "estate-drafts";
7
- export function apply(ctx) {
8
+ export function apply(ctx, config = {}) {
9
+ // the domain seat mounts FIRST — the remote services' writers open
10
+ // the drafts domain through it (openDraftsDomain probes this seat)
11
+ new DraftsDomainSeatService(ctx, config);
8
12
  new DraftsRemoteService(ctx);
9
13
  new BenchRemoteService(ctx);
10
14
  }
package/lib/remote.d.ts CHANGED
@@ -2,6 +2,7 @@ import type { Context } from "@deepseek-ai/cordis";
2
2
  import { TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
3
3
  import { STAGE_PATCH_KEYS, type StagePatchKey } from "@melite/contracts/benchStage";
4
4
  export { STAGE_PATCH_KEYS, type StagePatchKey };
5
+ import { type LaneMigration } from "./bench.js";
5
6
  import type { BenchEnsureRow, BenchGetRow, BenchPatchRow, BenchReorderRow, BenchRewireRow, BenchClearRow, BenchStageRow, BenchUndoRow } from "./bench.js";
6
7
  export type { BenchEnsureRow, BenchGetRow, BenchPatchRow, BenchReorderRow, BenchRewireRow, BenchStageRow, BenchUndoRow, } from "./bench.js";
7
8
  /** Endpoint `drafts/patch` answer — the old door's delivered envelope:
@@ -34,6 +35,11 @@ export interface DraftListResult {
34
35
  * boundary contains unconstrained unknown data"); open card
35
36
  * payloads (stage params, results, history) are honestly JSON all
36
37
  * the way down, so the wire face says exactly that. */
38
+
39
+
40
+
41
+
42
+
37
43
  export type JsonValue = string | number | boolean | null | JsonValue[] | {
38
44
  readonly [key: string]: JsonValue;
39
45
  };
@@ -98,6 +104,7 @@ declare module "@deepseek-ai/cordis" {
98
104
  /**
99
105
  * A bench write landed. Emitted after every successful bench verb
100
106
  * (and after the drafts-patch door's bench-shaped write).
107
+ * @mode emit
101
108
  * @param session - the bench session whose doc changed.
102
109
  */
103
110
  "bench.changed": (session: string) => void;
@@ -121,21 +128,21 @@ declare module "@deepseek-ai/cordis" {
121
128
 
122
129
 
123
130
 
131
+
124
132
  export declare function installBenchChangeSeam(ctx: Context): () => void;
125
133
  /** ── THE OUTPUT HISTORY HELPERS (stroke 40) ──────────────────────
126
- * A result's OUTPUT urls, one spelling: the artifacts name→url map
127
- * first, the flat assetUrls list second, the lone url third (the
128
- * same harvest order the client's preview reads — the history and
129
- * the hero can never disagree about what a run produced). */
130
- export declare function resultOutputUrls(result: unknown): string[];
131
- /** Same OUTPUTS (the uniqueness key): two results match when their
132
- * url sets are equal — a re-run landing the same urls is not a new
133
- * history entry (the operator's "when it is unique"). */
134
+ * resultOutputUrls now lives in trail.ts (the cycle-free seam) and
135
+ * is re-exported below with the trail laws. ───────────────────── */
134
136
  export declare function sameResultOutputs(a: unknown, b: unknown): boolean;
135
137
 
136
138
 
137
139
 
138
140
 
141
+ export { canonicalOutputUrl, resultOutputUrls, isTerminalTrailWord as isTerminalWord, trailDedupe } from "./trail.js";
142
+
143
+
144
+
145
+
139
146
 
140
147
 
141
148
 
@@ -191,8 +198,16 @@ export declare function laneAdmissionOf(ctx: Context): ((card: string, params: R
191
198
 
192
199
 
193
200
 
201
+
202
+
203
+
204
+ export declare function laneMigrationOf(ctx: Context): LaneMigration | undefined;
205
+
206
+
207
+
208
+
209
+
194
210
  export declare function benchAdmissionOf(ctx: Context): ((card: string, params: Record<string, unknown>, provenance: unknown) => void) | undefined;
195
- /** The drafts Remote owner: list/get over the storage domain. */
196
211
  export declare class DraftsRemoteService extends TypertRemoteService {
197
212
 
198
213
 
@@ -240,8 +255,12 @@ export declare class DraftsRemoteService extends TypertRemoteService {
240
255
 
241
256
 
242
257
  remoteExportPatch(draftId: string, stageId: string, patch: DraftStagePatch, expectRev: number | null, signal: AbortSignal): Promise<DraftPatchRow>;
243
- /** The patch arm the adapter above delegates to. */
258
+ /** The patch arm the adapter above delegates to — one write
259
+ * transaction on the draft's own key, shared with the bench
260
+ * verbs (the round-14 race audit's R3: the author arm's
261
+ * spread-copy of the log forked against their in-place push). */
244
262
  patch(draftId: string, stageId: string, patch: Record<string, unknown>, expectRev: number | null, signal: AbortSignal): Promise<DraftPatchRow>;
263
+ private patchBody;
245
264
  }
246
265
  /** The bench Remote owner: ensure/get over the same drafts domain
247
266
  * (the bench doc IS a draft doc, id "bench-<session>"). */