@cat-factory/orchestration 0.221.0 → 0.223.0

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.
Files changed (39) hide show
  1. package/dist/modules/board/BoardService.d.ts +24 -31
  2. package/dist/modules/board/BoardService.d.ts.map +1 -1
  3. package/dist/modules/board/BoardService.js +107 -89
  4. package/dist/modules/board/BoardService.js.map +1 -1
  5. package/dist/modules/board/internalAnchors.d.ts +17 -0
  6. package/dist/modules/board/internalAnchors.d.ts.map +1 -0
  7. package/dist/modules/board/internalAnchors.js +55 -0
  8. package/dist/modules/board/internalAnchors.js.map +1 -0
  9. package/dist/modules/board/layoutWrites.d.ts +4 -2
  10. package/dist/modules/board/layoutWrites.d.ts.map +1 -1
  11. package/dist/modules/board/layoutWrites.js +24 -9
  12. package/dist/modules/board/layoutWrites.js.map +1 -1
  13. package/dist/modules/bootstrap/BootstrapService.d.ts +13 -1
  14. package/dist/modules/bootstrap/BootstrapService.d.ts.map +1 -1
  15. package/dist/modules/bootstrap/BootstrapService.js +21 -3
  16. package/dist/modules/bootstrap/BootstrapService.js.map +1 -1
  17. package/dist/modules/execution/PostMergeBoardController.js +4 -4
  18. package/dist/modules/execution/PostMergeBoardController.js.map +1 -1
  19. package/dist/modules/execution/RunAdmission.d.ts.map +1 -1
  20. package/dist/modules/execution/RunAdmission.js +3 -2
  21. package/dist/modules/execution/RunAdmission.js.map +1 -1
  22. package/dist/modules/execution/RunDispatcher.d.ts.map +1 -1
  23. package/dist/modules/execution/RunDispatcher.js +8 -6
  24. package/dist/modules/execution/RunDispatcher.js.map +1 -1
  25. package/dist/modules/execution/RunLifecycleController.d.ts.map +1 -1
  26. package/dist/modules/execution/RunLifecycleController.js +9 -5
  27. package/dist/modules/execution/RunLifecycleController.js.map +1 -1
  28. package/dist/modules/execution/linked-context.d.ts +12 -10
  29. package/dist/modules/execution/linked-context.d.ts.map +1 -1
  30. package/dist/modules/execution/linked-context.js +14 -15
  31. package/dist/modules/execution/linked-context.js.map +1 -1
  32. package/dist/modules/initiative/InitiativeLoopService.d.ts.map +1 -1
  33. package/dist/modules/initiative/InitiativeLoopService.js +16 -2
  34. package/dist/modules/initiative/InitiativeLoopService.js.map +1 -1
  35. package/dist/modules/initiative/InitiativeService.js +1 -1
  36. package/dist/modules/initiative/InitiativeService.js.map +1 -1
  37. package/dist/modules/recurring/RecurringPipelineService.js +1 -1
  38. package/dist/modules/recurring/RecurringPipelineService.js.map +1 -1
  39. package/package.json +11 -11
@@ -165,6 +165,11 @@ export declare class BoardService {
165
165
  private readonly reviewFrictionGuard;
166
166
  /** The external `/api/v1` board surface (see publicBoardReads.ts). */
167
167
  private readonly publicReads;
168
+ /**
169
+ * The headless `internal` anchor blocks a public-API run hangs off (see internalAnchors.ts).
170
+ * Never board state: no event announces one, and every projection filters it out.
171
+ */
172
+ private readonly internalAnchors;
168
173
  /**
169
174
  * The board's LAYOUT writes — drag a container to a new spot, drag its border to new bounds
170
175
  * (see layoutWrites.ts). Both split their write between a frame's per-board mount override and
@@ -191,23 +196,31 @@ export declare class BoardService {
191
196
  private readonly riskPolicySelection;
192
197
  constructor({ workspaceRepository, blockRepository, executionRepository, idGenerator, clock, repoProjectionRepository, repoProjectionCache, serviceRepository, workspaceMountRepository, serviceFragmentDefaultsRepository, initiativeRepository, documentRepository, taskRepository, executionEventPublisher, taskTypeRegistry, taskTypeSuppressionRepository, promptFragmentSource, workspaceSettings, reviewFrictionNotifications, resolveRunRepoContext, riskPolicyRepository, logger, }: BoardServiceDependencies);
193
198
  /**
194
- * Push a coarse board-changed signal for a successful mutation. `originWorkspaceId` MUST be
199
+ * Push a board-changed signal for a successful mutation. `originWorkspaceId` MUST be
195
200
  * the workspace that physically HOMES the affected block (its `homeWorkspaceId`), not
196
201
  * necessarily the acting workspace: {@link FanOutEventPublisher} resolves the block's service
197
- * and thus every workspace that mounts it by looking the block up under this origin, so
202
+ * (and thus every workspace that mounts it) by looking the block up under this origin, so
198
203
  * passing a mounter's id for a block homed elsewhere would find nothing and collapse the
199
- * fan-out to that one board. Naming a block lets the change reach every mount; pass `null` for
204
+ * fan-out to that one board. Naming a block lets the change reach every mount; name none for
200
205
  * a signal that should reach the origin workspace only (e.g. a per-workspace frame-layout
201
206
  * move). Best-effort: swallow any failure so a missed push never fails the already-persisted
202
- * mutation the client reconciles by re-fetching its snapshot.
207
+ * mutation, since the client reconciles by re-fetching its snapshot.
208
+ *
209
+ * Pass `block` (rather than only `blockId`) when the change is FULLY described by that one
210
+ * block, so subscribers patch it in place instead of re-reading the whole board. That is the
211
+ * difference between a spawned task costing one small payload and costing a snapshot on every
212
+ * open board. Withhold it for a structural change whose new shape a single block cannot state:
213
+ * a removal or a reparent moves a block BETWEEN parents, and a cascade touches rows the event
214
+ * never names. A frame payload is dropped at the wire by `deliverableBoardBlock`, so naming one
215
+ * here is safe but pointless.
203
216
  *
204
- * When `originConnectionId` is given (a user-driven mutation move / reparent / field edit
217
+ * When `originConnectionId` is given (a user-driven mutation: move / reparent / field edit,
205
218
  * carrying the acting tab's connection id), the realtime transport SKIPS delivering this
206
219
  * echo back to that connection: its REST response already carried the authoritative result,
207
220
  * so refreshing off its own event would only race an in-flight drag (snapping a block back to
208
221
  * a stale position) or trigger a redundant board-wide re-hydrate on every inspector edit. Every
209
- * OTHER subscriber still receives the coarse signal and refreshes. Engine-driven board changes
210
- * pass no origin id, so they fan out to everyone as before.
222
+ * OTHER subscriber still receives the signal. Engine-driven board changes pass no origin id, so
223
+ * they fan out to everyone as before.
211
224
  */
212
225
  private emitBoardChanged;
213
226
  /**
@@ -297,36 +310,16 @@ export declare class BoardService {
297
310
  * Pass `UNATTRIBUTED_BLOCK_EDITOR` for a caller with no workspace tier (see its doc).
298
311
  */
299
312
  addTask(workspaceId: string, containerId: string, input: AddTaskInput, editor: BlockEditActor, createdBy?: string | null): Promise<Block>;
300
- /**
301
- * Create a HEADLESS internal task — a top-level, `internal: true` block used purely to anchor
302
- * a public-API run (an external "initiative breakdown"). It is EXCLUDED from every board
303
- * projection (see the snapshot/board reads), so it never renders in the UI; deliberately does
304
- * NOT emit a `block-added` event (nothing should flash onto a live board). Returns the block so
305
- * the caller can start an execution on it. The engine writes status onto it like any block.
306
- */
313
+ /** Public-API: create the anchor block a public-API run hangs off. */
307
314
  createInternalTask(workspaceId: string, input: {
308
315
  title: string;
309
316
  description: string;
310
317
  }): Promise<Block>;
311
- /**
312
- * Fetch a HEADLESS internal anchor block by id, or null when no block with that id exists in
313
- * the workspace OR it is not `internal`. The public-API job reads use this to confine an
314
- * external key to the runs IT created (an `internal` block) — never an arbitrary board
315
- * execution that merely shares the key's workspace. See PublicApiController.
316
- */
318
+ /** Public-API: an anchor by id, or null when the block is absent or not `internal`. */
317
319
  getInternalTask(workspaceId: string, blockId: string): Promise<Block | null>;
318
- /**
319
- * Delete a HEADLESS internal anchor block. Used by the public API to roll back the anchor when
320
- * the run it was created for fails to start, so a failed dispatch never leaves an orphan
321
- * `internal` block behind (it renders nowhere and is invisible to the cap, so it would just
322
- * accumulate). A headless anchor has no children/service subtree, so a direct delete is enough.
323
- */
320
+ /** Public-API: roll an anchor back when the run it was created for fails to start. */
324
321
  deleteInternalTask(workspaceId: string, blockId: string): Promise<void>;
325
- /**
326
- * How many of the workspace's headless internal "initiative" runs are still in flight — the
327
- * concurrency backstop the public API checks before starting another, so a single (possibly
328
- * leaked) key can't spin up unbounded LLM runs. A SQL `COUNT`, not a load-and-count.
329
- */
322
+ /** Public-API: how many anchored runs are in flight — the concurrency backstop. */
330
323
  countActiveInternalTasks(workspaceId: string): Promise<number>;
331
324
  /** Public-API: the workspace's board services (visible service frames). */
332
325
  listServices(workspaceId: string): Promise<Block[]>;
@@ -1 +1 @@
1
- {"version":3,"file":"BoardService.d.ts","sourceRoot":"","sources":["../../../src/modules/board/BoardService.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"BoardService.d.ts","sourceRoot":"","sources":["../../../src/modules/board/BoardService.ts"],"names":[],"mappings":"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"}
@@ -7,6 +7,7 @@ import { pruneDanglingEdges, reclaimDoomedEntities } from './removal-cascade.js'
7
7
  import { canReparent, descendantIds, gridSlot, serviceOf, tasksOf, unfinishedTasksUnder, wouldCreateCycle, } from './board.logic.js';
8
8
  import { ReviewFrictionGuard } from './reviewFrictionGuard.js';
9
9
  import { resolveNewServiceFrameDefaults } from './newServiceFrameDefaults.js';
10
+ import { createInternalAnchors } from './internalAnchors.js';
10
11
  import { PublicBoardReads } from './publicBoardReads.js';
11
12
  import { buildReviewDescription, resolveReviewTaskTarget } from './reviewTaskTarget.js';
12
13
  import { createBlockPatchNarrowing } from './blockPatchNarrowing.js';
@@ -40,6 +41,11 @@ export class BoardService {
40
41
  reviewFrictionGuard;
41
42
  /** The external `/api/v1` board surface (see publicBoardReads.ts). */
42
43
  publicReads;
44
+ /**
45
+ * The headless `internal` anchor blocks a public-API run hangs off (see internalAnchors.ts).
46
+ * Never board state: no event announces one, and every projection filters it out.
47
+ */
48
+ internalAnchors;
43
49
  /**
44
50
  * The board's LAYOUT writes — drag a container to a new spot, drag its border to new bounds
45
51
  * (see layoutWrites.ts). Both split their write between a frame's per-board mount override and
@@ -116,36 +122,49 @@ export class BoardService {
116
122
  resolveBlock: (workspaceId, id) => this.resolveBlock(workspaceId, id),
117
123
  frameMount: (workspaceId, block) => this.frameMount(workspaceId, block),
118
124
  projectForWorkspace: (workspaceId, block) => this.projectForWorkspace(workspaceId, block),
119
- emitBoardChanged: (originWorkspaceId, reason, blockId, originConnectionId) => this.emitBoardChanged(originWorkspaceId, reason, blockId, originConnectionId),
125
+ emitBoardChanged: (originWorkspaceId, change) => this.emitBoardChanged(originWorkspaceId, change),
120
126
  });
121
127
  this.publicReads = new PublicBoardReads({
122
128
  blockRepository,
123
129
  requireWorkspace: (workspaceId) => this.requireWorkspace(workspaceId),
124
130
  addTask: (workspaceId, containerId, input, editor, createdBy) => this.addTask(workspaceId, containerId, input, editor, createdBy),
125
131
  });
132
+ this.internalAnchors = createInternalAnchors({
133
+ blockRepository,
134
+ idGenerator,
135
+ requireWorkspace: (workspaceId) => this.requireWorkspace(workspaceId),
136
+ });
126
137
  }
127
138
  /**
128
- * Push a coarse board-changed signal for a successful mutation. `originWorkspaceId` MUST be
139
+ * Push a board-changed signal for a successful mutation. `originWorkspaceId` MUST be
129
140
  * the workspace that physically HOMES the affected block (its `homeWorkspaceId`), not
130
141
  * necessarily the acting workspace: {@link FanOutEventPublisher} resolves the block's service
131
- * and thus every workspace that mounts it by looking the block up under this origin, so
142
+ * (and thus every workspace that mounts it) by looking the block up under this origin, so
132
143
  * passing a mounter's id for a block homed elsewhere would find nothing and collapse the
133
- * fan-out to that one board. Naming a block lets the change reach every mount; pass `null` for
144
+ * fan-out to that one board. Naming a block lets the change reach every mount; name none for
134
145
  * a signal that should reach the origin workspace only (e.g. a per-workspace frame-layout
135
146
  * move). Best-effort: swallow any failure so a missed push never fails the already-persisted
136
- * mutation the client reconciles by re-fetching its snapshot.
147
+ * mutation, since the client reconciles by re-fetching its snapshot.
137
148
  *
138
- * When `originConnectionId` is given (a user-driven mutation move / reparent / field edit
149
+ * Pass `block` (rather than only `blockId`) when the change is FULLY described by that one
150
+ * block, so subscribers patch it in place instead of re-reading the whole board. That is the
151
+ * difference between a spawned task costing one small payload and costing a snapshot on every
152
+ * open board. Withhold it for a structural change whose new shape a single block cannot state:
153
+ * a removal or a reparent moves a block BETWEEN parents, and a cascade touches rows the event
154
+ * never names. A frame payload is dropped at the wire by `deliverableBoardBlock`, so naming one
155
+ * here is safe but pointless.
156
+ *
157
+ * When `originConnectionId` is given (a user-driven mutation: move / reparent / field edit,
139
158
  * carrying the acting tab's connection id), the realtime transport SKIPS delivering this
140
159
  * echo back to that connection: its REST response already carried the authoritative result,
141
160
  * so refreshing off its own event would only race an in-flight drag (snapping a block back to
142
161
  * a stale position) or trigger a redundant board-wide re-hydrate on every inspector edit. Every
143
- * OTHER subscriber still receives the coarse signal and refreshes. Engine-driven board changes
144
- * pass no origin id, so they fan out to everyone as before.
162
+ * OTHER subscriber still receives the signal. Engine-driven board changes pass no origin id, so
163
+ * they fan out to everyone as before.
145
164
  */
146
- async emitBoardChanged(originWorkspaceId, reason, blockId, originConnectionId) {
165
+ async emitBoardChanged(originWorkspaceId, change) {
147
166
  try {
148
- await this.events?.boardChanged(originWorkspaceId, reason, blockId, originConnectionId);
167
+ await this.events?.boardChanged(originWorkspaceId, change);
149
168
  }
150
169
  catch {
151
170
  // best-effort; the REST response already carried the mutation
@@ -273,7 +292,8 @@ export class BoardService {
273
292
  };
274
293
  const serviceId = await this.registerService(workspaceId, block);
275
294
  await this.blockRepository.insert(workspaceId, block, serviceId);
276
- await this.emitBoardChanged(workspaceId, 'block-added', block.id);
295
+ // A service FRAME, so no payload: its position is the per-board mount, not this row.
296
+ await this.emitBoardChanged(workspaceId, { reason: 'block-added', blockId: block.id });
277
297
  return block;
278
298
  }
279
299
  /**
@@ -364,7 +384,8 @@ export class BoardService {
364
384
  directory: directory ?? null,
365
385
  });
366
386
  await this.blockRepository.insert(workspaceId, block, serviceId);
367
- await this.emitBoardChanged(workspaceId, 'block-added', block.id);
387
+ // A service FRAME, so no payload (see `addBlock`).
388
+ await this.emitBoardChanged(workspaceId, { reason: 'block-added', blockId: block.id });
368
389
  return block;
369
390
  }
370
391
  /**
@@ -414,8 +435,12 @@ export class BoardService {
414
435
  createdAt: this.clock.now(),
415
436
  };
416
437
  await this.workspaceMountRepository.upsert(mount);
417
- // Fan out from the frame's HOME so every board mounting the shared service refreshes.
418
- await this.emitBoardChanged(home.workspaceId, 'block-added', home.block.id);
438
+ // Fan out from the frame's HOME so every board mounting the shared service refreshes. A
439
+ // FRAME, and each target board reads its own mount, so there is no payload to carry.
440
+ await this.emitBoardChanged(home.workspaceId, {
441
+ reason: 'block-added',
442
+ blockId: home.block.id,
443
+ });
419
444
  }
420
445
  // The frame block is the one homed on ANOTHER board, carrying that board's coordinates —
421
446
  // return it placed where THIS board just mounted it, or the SPA drops the imported service
@@ -567,66 +592,30 @@ export class BoardService {
567
592
  block.technical = input.technical;
568
593
  await this.blockRepository.insert(homeWorkspaceId, block, await this.serviceForContainer(blocks, container));
569
594
  // Origin = the block's HOME (the mounted service's home when added to a shared board), so
570
- // the fan-out reaches every workspace mounting the service, not just the acting one.
571
- await this.emitBoardChanged(homeWorkspaceId, 'block-added', block.id);
595
+ // the fan-out reaches every workspace mounting the service, not just the acting one. A new
596
+ // task is fully described by itself, so it rides along and every board patches it in place
597
+ // rather than re-reading a snapshot: this is the event an initiative loop fires per spawn.
598
+ await this.emitBoardChanged(homeWorkspaceId, { reason: 'block-added', block });
572
599
  return block;
573
600
  }
574
- /**
575
- * Create a HEADLESS internal task — a top-level, `internal: true` block used purely to anchor
576
- * a public-API run (an external "initiative breakdown"). It is EXCLUDED from every board
577
- * projection (see the snapshot/board reads), so it never renders in the UI; deliberately does
578
- * NOT emit a `block-added` event (nothing should flash onto a live board). Returns the block so
579
- * the caller can start an execution on it. The engine writes status onto it like any block.
580
- */
581
- async createInternalTask(workspaceId, input) {
582
- await this.requireWorkspace(workspaceId);
583
- const block = {
584
- id: this.idGenerator.next('task'),
585
- title: input.title.trim() || 'Initiative',
586
- // `type` is the service/repo CLASSIFICATION (frontend/service/library/…), orthogonal to the
587
- // `level` hierarchy; there is no task-specific BlockType. This anchor is a standalone,
588
- // never-rendered, repo-less `level:'task'` block, so `type` is irrelevant to it — 'service'
589
- // is just the neutral default (a normal task inherits its parent service's type instead).
590
- type: 'service',
591
- description: input.description ?? '',
592
- position: { x: 0, y: 0 },
593
- status: 'planned',
594
- progress: 0,
595
- dependsOn: [],
596
- executionId: null,
597
- level: 'task',
598
- parentId: null,
599
- internal: true,
600
- };
601
- await this.blockRepository.insert(workspaceId, block);
602
- return block;
601
+ // --- Headless internal anchors ---------------------------------------------
602
+ // Delegated to {@link createInternalAnchors}: the top-level `internal: true` blocks that anchor
603
+ // a public-API run and render on no board, ever. See that file for why the four belong together.
604
+ /** Public-API: create the anchor block a public-API run hangs off. */
605
+ createInternalTask(workspaceId, input) {
606
+ return this.internalAnchors.createInternalTask(workspaceId, input);
603
607
  }
604
- /**
605
- * Fetch a HEADLESS internal anchor block by id, or null when no block with that id exists in
606
- * the workspace OR it is not `internal`. The public-API job reads use this to confine an
607
- * external key to the runs IT created (an `internal` block) — never an arbitrary board
608
- * execution that merely shares the key's workspace. See PublicApiController.
609
- */
610
- async getInternalTask(workspaceId, blockId) {
611
- const block = await this.blockRepository.get(workspaceId, blockId);
612
- return block?.internal ? block : null;
608
+ /** Public-API: an anchor by id, or null when the block is absent or not `internal`. */
609
+ getInternalTask(workspaceId, blockId) {
610
+ return this.internalAnchors.getInternalTask(workspaceId, blockId);
613
611
  }
614
- /**
615
- * Delete a HEADLESS internal anchor block. Used by the public API to roll back the anchor when
616
- * the run it was created for fails to start, so a failed dispatch never leaves an orphan
617
- * `internal` block behind (it renders nowhere and is invisible to the cap, so it would just
618
- * accumulate). A headless anchor has no children/service subtree, so a direct delete is enough.
619
- */
620
- async deleteInternalTask(workspaceId, blockId) {
621
- await this.blockRepository.deleteMany(workspaceId, [blockId]);
612
+ /** Public-API: roll an anchor back when the run it was created for fails to start. */
613
+ deleteInternalTask(workspaceId, blockId) {
614
+ return this.internalAnchors.deleteInternalTask(workspaceId, blockId);
622
615
  }
623
- /**
624
- * How many of the workspace's headless internal "initiative" runs are still in flight — the
625
- * concurrency backstop the public API checks before starting another, so a single (possibly
626
- * leaked) key can't spin up unbounded LLM runs. A SQL `COUNT`, not a load-and-count.
627
- */
616
+ /** Public-API: how many anchored runs are in flight — the concurrency backstop. */
628
617
  countActiveInternalTasks(workspaceId) {
629
- return this.blockRepository.countActiveInternal(workspaceId);
618
+ return this.internalAnchors.countActiveInternalTasks(workspaceId);
630
619
  }
631
620
  // --- Public-API board reads/writes -----------------------------------------
632
621
  // Delegated to {@link PublicBoardReads}: the external `/api/v1` surface is its own cohesive
@@ -694,7 +683,8 @@ export class BoardService {
694
683
  };
695
684
  await this.blockRepository.insert(homeWorkspaceId, block, containerServiceId);
696
685
  // Origin = the block's HOME so a module added to a mounted service fans out to all mounts.
697
- await this.emitBoardChanged(homeWorkspaceId, 'block-added', block.id);
686
+ // A module is a sub-frame with no mount of its own, so its payload is correct everywhere.
687
+ await this.emitBoardChanged(homeWorkspaceId, { reason: 'block-added', block });
698
688
  created.push(block);
699
689
  n += 1;
700
690
  }
@@ -732,7 +722,7 @@ export class BoardService {
732
722
  parentId,
733
723
  };
734
724
  await this.blockRepository.insert(workspaceId, block);
735
- await this.emitBoardChanged(workspaceId, 'block-added', block.id);
725
+ await this.emitBoardChanged(workspaceId, { reason: 'block-added', block });
736
726
  return block;
737
727
  }
738
728
  /**
@@ -754,9 +744,12 @@ export class BoardService {
754
744
  }
755
745
  }
756
746
  await this.blockRepository.update(homeWorkspaceId, taskId, { epicId });
747
+ // Re-read BEFORE emitting so the event carries the task it just changed: membership is a
748
+ // field ON the task, so the one block states the whole change and subscribers patch it.
757
749
  // Origin = the task's HOME so the fan-out resolves the (possibly mounted) service's boards.
758
- await this.emitBoardChanged(homeWorkspaceId, 'epic-assigned', taskId);
759
- return assertFound(await this.blockRepository.get(homeWorkspaceId, taskId), 'Block', taskId);
750
+ const updated = assertFound(await this.blockRepository.get(homeWorkspaceId, taskId), 'Block', taskId);
751
+ await this.emitBoardChanged(homeWorkspaceId, { reason: 'epic-assigned', block: updated });
752
+ return updated;
760
753
  }
761
754
  /** Move a block to a new spot on the board (a frame's position is its per-board override). */
762
755
  async moveBlock(workspaceId, id, position, originConnectionId) {
@@ -805,17 +798,24 @@ export class BoardService {
805
798
  // `customTaskTypeFields` (the half that may be patched) and the row stores the whole
806
799
  // `taskTypeFields`, so this is where the request type becomes the repository's.
807
800
  await this.blockRepository.update(homeWorkspaceId, id, narrow.customTaskTypeFields(effective, block));
801
+ const updated = assertFound(await this.blockRepository.get(homeWorkspaceId, id), 'Block', id);
808
802
  // Origin = the block's HOME so editing a shared block fans out to every board mounting it.
809
- // Forward the acting tab's connection id so the realtime transport SKIPS echoing this coarse
810
- // signal back to it: the REST response already carried the authoritative block (the SPA
811
- // upserts it), so a self-echo would only trigger a redundant board-wide re-hydrate the same
812
- // "don't refresh off your own mutation" contract move/reparent already follow. Every OTHER
813
- // subscriber still receives the signal and refreshes.
814
- await this.emitBoardChanged(homeWorkspaceId, 'block-updated', id, originConnectionId);
815
- // A frame's position/size come from THIS board's mount, not the row we just re-read — so a
803
+ // Forward the acting tab's connection id so the realtime transport SKIPS echoing this back to
804
+ // it: the REST response already carried the authoritative block (the SPA upserts it), so a
805
+ // self-echo would only trigger a redundant board-wide re-hydrate, the same "don't refresh off
806
+ // your own mutation" contract move/reparent already follow. Every OTHER subscriber receives
807
+ // the change, carrying the edited block. The UNPROJECTED row is what rides: the projection
808
+ // below is for THIS board, and a fan-out reaches boards with different mounts. That only ever
809
+ // matters for a frame, whose payload `deliverableBoardBlock` drops at the wire anyway.
810
+ await this.emitBoardChanged(homeWorkspaceId, {
811
+ reason: 'block-updated',
812
+ block: updated,
813
+ originConnectionId,
814
+ });
815
+ // A frame's position/size come from THIS board's mount, not the row we just re-read, so a
816
816
  // frame edit (rename, threshold, and above all a RESIZE) must not hand the SPA the block's
817
817
  // own, never-updated coordinates to upsert.
818
- return this.projectForWorkspace(workspaceId, assertFound(await this.blockRepository.get(homeWorkspaceId, id), 'Block', id));
818
+ return this.projectForWorkspace(workspaceId, updated);
819
819
  }
820
820
  /** Move a block into a new container at a new local position. */
821
821
  async reparent(workspaceId, id, input, originConnectionId) {
@@ -853,8 +853,14 @@ export class BoardService {
853
853
  const destService = await this.serviceForContainer(destBlocks, parent);
854
854
  await this.blockRepository.setService(blockHome, [...descendantIds(destBlocks, id)], destService ?? null);
855
855
  }
856
- // Origin = the block's HOME so the re-stamped subtree fans out to every mounting board.
857
- await this.emitBoardChanged(blockHome, 'block-reparented', id, originConnectionId);
856
+ // Origin = the block's HOME so the re-stamped subtree fans out to every mounting board. No
857
+ // payload: a reparent moves the whole SUBTREE between parents, and the moved block alone
858
+ // cannot state what its descendants' service stamps became.
859
+ await this.emitBoardChanged(blockHome, {
860
+ reason: 'block-reparented',
861
+ blockId: id,
862
+ originConnectionId,
863
+ });
858
864
  return assertFound(await this.blockRepository.get(blockHome, id), 'Block', id);
859
865
  }
860
866
  // Cross-home: the block and its new parent belong to two services homed in different
@@ -895,10 +901,14 @@ export class BoardService {
895
901
  // service's mounts (and that board). Source side: the block is gone from its old service, so
896
902
  // the block→service join can't resolve it anymore — notify the captured source boards
897
903
  // directly (origin-only) so they refresh the subtree away.
898
- await this.emitBoardChanged(parentHome, 'block-reparented', id, originConnectionId);
904
+ await this.emitBoardChanged(parentHome, {
905
+ reason: 'block-reparented',
906
+ blockId: id,
907
+ originConnectionId,
908
+ });
899
909
  for (const ws of sourceFanout) {
900
910
  if (ws !== parentHome) {
901
- await this.emitBoardChanged(ws, 'block-reparented', null, originConnectionId);
911
+ await this.emitBoardChanged(ws, { reason: 'block-reparented', originConnectionId });
902
912
  }
903
913
  }
904
914
  return assertFound(await this.blockRepository.get(parentHome, id), 'Block', id);
@@ -965,9 +975,10 @@ export class BoardService {
965
975
  await this.blockRepository.deleteMany(homeWorkspaceId, [...doomed]);
966
976
  await pruneDanglingEdges(this.blockRepository, homeWorkspaceId, blocks, doomed);
967
977
  // The block + any shared service are now gone, so fan out per captured target (blockId is
968
- // unresolvable post-delete) every board that showed the block refreshes it away.
978
+ // unresolvable post-delete): every board that showed the block refreshes it away. There is
979
+ // nothing to carry, and a delete CASCADES, so the refresh is the point.
969
980
  for (const ws of fanoutTargets) {
970
- await this.emitBoardChanged(ws, 'block-removed', null);
981
+ await this.emitBoardChanged(ws, { reason: 'block-removed' });
971
982
  }
972
983
  }
973
984
  /**
@@ -992,7 +1003,11 @@ export class BoardService {
992
1003
  }
993
1004
  await this.blockRepository.update(homeWorkspaceId, id, { archived });
994
1005
  // Origin = the block's HOME so archiving a shared service fans out to every board mounting it.
995
- await this.emitBoardChanged(homeWorkspaceId, archived ? 'block-archived' : 'block-restored', id);
1006
+ // Always a FRAME (asserted above) and archiving hides its whole subtree, so no payload.
1007
+ await this.emitBoardChanged(homeWorkspaceId, {
1008
+ reason: archived ? 'block-archived' : 'block-restored',
1009
+ blockId: id,
1010
+ });
996
1011
  // Always a frame (asserted above), so it owes the caller this board's layout override.
997
1012
  return this.projectForWorkspace(workspaceId, assertFound(await this.blockRepository.get(homeWorkspaceId, id), 'Block', id));
998
1013
  }
@@ -1037,8 +1052,11 @@ export class BoardService {
1037
1052
  }
1038
1053
  }
1039
1054
  // Origin = the target's HOME so toggling an edge on a shared task fans out to all mounts.
1040
- await this.emitBoardChanged(homeWorkspaceId, 'dependency-toggled', targetId);
1041
- return assertFound(await this.blockRepository.get(homeWorkspaceId, targetId), 'Block', targetId);
1055
+ // Re-read first so the event carries the target: `dependsOn` lives on it, so one block
1056
+ // states the whole change (the SOURCE block is untouched).
1057
+ const updated = assertFound(await this.blockRepository.get(homeWorkspaceId, targetId), 'Block', targetId);
1058
+ await this.emitBoardChanged(homeWorkspaceId, { reason: 'dependency-toggled', block: updated });
1059
+ return updated;
1042
1060
  }
1043
1061
  }
1044
1062
  /**