@cat-factory/orchestration 0.220.0 → 0.222.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.
- package/dist/container/dependencies.d.ts +22 -1
- package/dist/container/dependencies.d.ts.map +1 -1
- package/dist/container/foundation.d.ts +2 -0
- package/dist/container/foundation.d.ts.map +1 -1
- package/dist/container/foundation.js +4 -1
- package/dist/container/foundation.js.map +1 -1
- package/dist/container/platform-modules.d.ts +2 -0
- package/dist/container/platform-modules.d.ts.map +1 -1
- package/dist/container/platform-modules.js +2 -2
- package/dist/container/platform-modules.js.map +1 -1
- package/dist/container/runtime.d.ts +2 -0
- package/dist/container/runtime.d.ts.map +1 -1
- package/dist/container/runtime.js +12 -1
- package/dist/container/runtime.js.map +1 -1
- package/dist/container-content-libraries.d.ts +8 -2
- package/dist/container-content-libraries.d.ts.map +1 -1
- package/dist/container-content-libraries.js +12 -1
- package/dist/container-content-libraries.js.map +1 -1
- package/dist/container.d.ts +15 -1
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +5 -1
- package/dist/container.js.map +1 -1
- package/dist/modules/board/BoardService.d.ts +32 -33
- package/dist/modules/board/BoardService.d.ts.map +1 -1
- package/dist/modules/board/BoardService.js +121 -94
- package/dist/modules/board/BoardService.js.map +1 -1
- package/dist/modules/board/internalAnchors.d.ts +17 -0
- package/dist/modules/board/internalAnchors.d.ts.map +1 -0
- package/dist/modules/board/internalAnchors.js +55 -0
- package/dist/modules/board/internalAnchors.js.map +1 -0
- package/dist/modules/board/layoutWrites.d.ts +4 -2
- package/dist/modules/board/layoutWrites.d.ts.map +1 -1
- package/dist/modules/board/layoutWrites.js +24 -9
- package/dist/modules/board/layoutWrites.js.map +1 -1
- package/dist/modules/board/taskTypeCreationDefaults.d.ts +26 -7
- package/dist/modules/board/taskTypeCreationDefaults.d.ts.map +1 -1
- package/dist/modules/board/taskTypeCreationDefaults.js +45 -8
- package/dist/modules/board/taskTypeCreationDefaults.js.map +1 -1
- package/dist/modules/bootstrap/BootstrapService.d.ts +13 -1
- package/dist/modules/bootstrap/BootstrapService.d.ts.map +1 -1
- package/dist/modules/bootstrap/BootstrapService.js +21 -3
- package/dist/modules/bootstrap/BootstrapService.js.map +1 -1
- package/dist/modules/execution/PostMergeBoardController.js +4 -4
- package/dist/modules/execution/PostMergeBoardController.js.map +1 -1
- package/dist/modules/execution/RunAdmission.d.ts.map +1 -1
- package/dist/modules/execution/RunAdmission.js +3 -2
- package/dist/modules/execution/RunAdmission.js.map +1 -1
- package/dist/modules/execution/RunDispatcher.d.ts.map +1 -1
- package/dist/modules/execution/RunDispatcher.js +8 -6
- package/dist/modules/execution/RunDispatcher.js.map +1 -1
- package/dist/modules/execution/RunLifecycleController.d.ts.map +1 -1
- package/dist/modules/execution/RunLifecycleController.js +9 -5
- package/dist/modules/execution/RunLifecycleController.js.map +1 -1
- package/dist/modules/initiative/InitiativeLoopService.d.ts.map +1 -1
- package/dist/modules/initiative/InitiativeLoopService.js +16 -2
- package/dist/modules/initiative/InitiativeLoopService.js.map +1 -1
- package/dist/modules/initiative/InitiativeService.js +1 -1
- package/dist/modules/initiative/InitiativeService.js.map +1 -1
- package/dist/modules/recurring/RecurringPipelineService.js +1 -1
- package/dist/modules/recurring/RecurringPipelineService.js.map +1 -1
- package/dist/validation/validateRegistrations.d.ts +40 -5
- package/dist/validation/validateRegistrations.d.ts.map +1 -1
- package/dist/validation/validateRegistrations.js +177 -28
- package/dist/validation/validateRegistrations.js.map +1 -1
- package/package.json +11 -11
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AddEpicInput, AddFrameInput, AddModuleInput, AddServiceFromRepoInput, AddTaskInput, BlockEditActor, ReparentInput, ResizeBlockInput, UpdateBlockInput } from '@cat-factory/contracts';
|
|
2
2
|
import type { Block, BlockStatus, Position, PreloadedBlocks } from '@cat-factory/kernel';
|
|
3
|
-
import type { BlockRepository, Clock, ExecutionEventPublisher, ExecutionRepository, GitHubRepo, GroupCacheHandle, InitiativeRepository, DocumentRepository, Logger, RepoProjectionRepository, ResolveRunRepoContext, RiskPolicyRepository, ServiceFragmentDefaultsRepository, ServiceRepository, TaskRepository, TaskTypeRegistry, TaskTypeSuppressionRepository, WorkspaceMountRepository, WorkspaceRepository } from '@cat-factory/kernel';
|
|
3
|
+
import type { BlockRepository, Clock, ExecutionEventPublisher, ExecutionRepository, GitHubRepo, GroupCacheHandle, InitiativeRepository, DocumentRepository, Logger, RepoProjectionRepository, ResolveRunRepoContext, RiskPolicyRepository, ServiceFragmentDefaultsRepository, ServiceRepository, TaskRepository, TaskTypeRegistry, TaskTypeSuppressionRepository, PromptFragmentSource, WorkspaceMountRepository, WorkspaceRepository } from '@cat-factory/kernel';
|
|
4
4
|
import type { IdGenerator } from '@cat-factory/kernel';
|
|
5
5
|
import type { ReviewFrictionNotificationReader } from './reviewFrictionGuard.js';
|
|
6
6
|
import type { WorkspaceSettingsReader } from './workspaceSettingsReader.js';
|
|
@@ -90,6 +90,12 @@ export interface BoardServiceDependencies {
|
|
|
90
90
|
* Absent (tests / an unwired facade) ⇒ nothing is suppressed, today's behaviour.
|
|
91
91
|
*/
|
|
92
92
|
taskTypeSuppressionRepository?: TaskTypeSuppressionRepository;
|
|
93
|
+
/**
|
|
94
|
+
* Where a new task's per-TASK-TYPE default fragment ids are read from: the app-owned source (this
|
|
95
|
+
* deployment's registry, or the mothership's on a mothership-mode node). Absent ⇒ a new task
|
|
96
|
+
* carries only its explicit/service picks and a registered type's own standing context.
|
|
97
|
+
*/
|
|
98
|
+
promptFragmentSource?: PromptFragmentSource;
|
|
93
99
|
/**
|
|
94
100
|
* The acting workspace's runtime settings, read by two collaborators:
|
|
95
101
|
* - the opt-in review-debt friction guard on task creation
|
|
@@ -159,6 +165,11 @@ export declare class BoardService {
|
|
|
159
165
|
private readonly reviewFrictionGuard;
|
|
160
166
|
/** The external `/api/v1` board surface (see publicBoardReads.ts). */
|
|
161
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;
|
|
162
173
|
/**
|
|
163
174
|
* The board's LAYOUT writes — drag a container to a new spot, drag its border to new bounds
|
|
164
175
|
* (see layoutWrites.ts). Both split their write between a frame's per-board mount override and
|
|
@@ -183,25 +194,33 @@ export declare class BoardService {
|
|
|
183
194
|
* those a caller reaches for.
|
|
184
195
|
*/
|
|
185
196
|
private readonly riskPolicySelection;
|
|
186
|
-
constructor({ workspaceRepository, blockRepository, executionRepository, idGenerator, clock, repoProjectionRepository, repoProjectionCache, serviceRepository, workspaceMountRepository, serviceFragmentDefaultsRepository, initiativeRepository, documentRepository, taskRepository, executionEventPublisher, taskTypeRegistry, taskTypeSuppressionRepository, workspaceSettings, reviewFrictionNotifications, resolveRunRepoContext, riskPolicyRepository, logger, }: BoardServiceDependencies);
|
|
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);
|
|
187
198
|
/**
|
|
188
|
-
* Push a
|
|
199
|
+
* Push a board-changed signal for a successful mutation. `originWorkspaceId` MUST be
|
|
189
200
|
* the workspace that physically HOMES the affected block (its `homeWorkspaceId`), not
|
|
190
201
|
* necessarily the acting workspace: {@link FanOutEventPublisher} resolves the block's service
|
|
191
|
-
*
|
|
202
|
+
* (and thus every workspace that mounts it) by looking the block up under this origin, so
|
|
192
203
|
* passing a mounter's id for a block homed elsewhere would find nothing and collapse the
|
|
193
|
-
* fan-out to that one board. Naming a block lets the change reach every mount;
|
|
204
|
+
* fan-out to that one board. Naming a block lets the change reach every mount; name none for
|
|
194
205
|
* a signal that should reach the origin workspace only (e.g. a per-workspace frame-layout
|
|
195
206
|
* move). Best-effort: swallow any failure so a missed push never fails the already-persisted
|
|
196
|
-
* mutation
|
|
207
|
+
* mutation, since the client reconciles by re-fetching its snapshot.
|
|
197
208
|
*
|
|
198
|
-
*
|
|
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.
|
|
216
|
+
*
|
|
217
|
+
* When `originConnectionId` is given (a user-driven mutation: move / reparent / field edit,
|
|
199
218
|
* carrying the acting tab's connection id), the realtime transport SKIPS delivering this
|
|
200
219
|
* echo back to that connection: its REST response already carried the authoritative result,
|
|
201
220
|
* so refreshing off its own event would only race an in-flight drag (snapping a block back to
|
|
202
221
|
* a stale position) or trigger a redundant board-wide re-hydrate on every inspector edit. Every
|
|
203
|
-
* OTHER subscriber still receives the
|
|
204
|
-
*
|
|
222
|
+
* OTHER subscriber still receives the signal. Engine-driven board changes pass no origin id, so
|
|
223
|
+
* they fan out to everyone as before.
|
|
205
224
|
*/
|
|
206
225
|
private emitBoardChanged;
|
|
207
226
|
/**
|
|
@@ -291,36 +310,16 @@ export declare class BoardService {
|
|
|
291
310
|
* Pass `UNATTRIBUTED_BLOCK_EDITOR` for a caller with no workspace tier (see its doc).
|
|
292
311
|
*/
|
|
293
312
|
addTask(workspaceId: string, containerId: string, input: AddTaskInput, editor: BlockEditActor, createdBy?: string | null): Promise<Block>;
|
|
294
|
-
/**
|
|
295
|
-
* Create a HEADLESS internal task — a top-level, `internal: true` block used purely to anchor
|
|
296
|
-
* a public-API run (an external "initiative breakdown"). It is EXCLUDED from every board
|
|
297
|
-
* projection (see the snapshot/board reads), so it never renders in the UI; deliberately does
|
|
298
|
-
* NOT emit a `block-added` event (nothing should flash onto a live board). Returns the block so
|
|
299
|
-
* the caller can start an execution on it. The engine writes status onto it like any block.
|
|
300
|
-
*/
|
|
313
|
+
/** Public-API: create the anchor block a public-API run hangs off. */
|
|
301
314
|
createInternalTask(workspaceId: string, input: {
|
|
302
315
|
title: string;
|
|
303
316
|
description: string;
|
|
304
317
|
}): Promise<Block>;
|
|
305
|
-
/**
|
|
306
|
-
* Fetch a HEADLESS internal anchor block by id, or null when no block with that id exists in
|
|
307
|
-
* the workspace OR it is not `internal`. The public-API job reads use this to confine an
|
|
308
|
-
* external key to the runs IT created (an `internal` block) — never an arbitrary board
|
|
309
|
-
* execution that merely shares the key's workspace. See PublicApiController.
|
|
310
|
-
*/
|
|
318
|
+
/** Public-API: an anchor by id, or null when the block is absent or not `internal`. */
|
|
311
319
|
getInternalTask(workspaceId: string, blockId: string): Promise<Block | null>;
|
|
312
|
-
/**
|
|
313
|
-
* Delete a HEADLESS internal anchor block. Used by the public API to roll back the anchor when
|
|
314
|
-
* the run it was created for fails to start, so a failed dispatch never leaves an orphan
|
|
315
|
-
* `internal` block behind (it renders nowhere and is invisible to the cap, so it would just
|
|
316
|
-
* accumulate). A headless anchor has no children/service subtree, so a direct delete is enough.
|
|
317
|
-
*/
|
|
320
|
+
/** Public-API: roll an anchor back when the run it was created for fails to start. */
|
|
318
321
|
deleteInternalTask(workspaceId: string, blockId: string): Promise<void>;
|
|
319
|
-
/**
|
|
320
|
-
* How many of the workspace's headless internal "initiative" runs are still in flight — the
|
|
321
|
-
* concurrency backstop the public API checks before starting another, so a single (possibly
|
|
322
|
-
* leaked) key can't spin up unbounded LLM runs. A SQL `COUNT`, not a load-and-count.
|
|
323
|
-
*/
|
|
322
|
+
/** Public-API: how many anchored runs are in flight — the concurrency backstop. */
|
|
324
323
|
countActiveInternalTasks(workspaceId: string): Promise<number>;
|
|
325
324
|
/** Public-API: the workspace's board services (visible service frames). */
|
|
326
325
|
listServices(workspaceId: string): Promise<Block[]>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BoardService.d.ts","sourceRoot":"","sources":["../../../src/modules/board/BoardService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,aAAa,EACb,cAAc,EACd,uBAAuB,EACvB,YAAY,EACZ,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,gBAAgB,EACjB,MAAM,wBAAwB,CAAA;AAC/B,OAAO,KAAK,
|
|
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
|
|
@@ -64,7 +70,7 @@ export class BoardService {
|
|
|
64
70
|
* those a caller reaches for.
|
|
65
71
|
*/
|
|
66
72
|
riskPolicySelection;
|
|
67
|
-
constructor({ workspaceRepository, blockRepository, executionRepository, idGenerator, clock, repoProjectionRepository, repoProjectionCache, serviceRepository, workspaceMountRepository, serviceFragmentDefaultsRepository, initiativeRepository, documentRepository, taskRepository, executionEventPublisher, taskTypeRegistry, taskTypeSuppressionRepository, workspaceSettings, reviewFrictionNotifications, resolveRunRepoContext, riskPolicyRepository, logger, }) {
|
|
73
|
+
constructor({ workspaceRepository, blockRepository, executionRepository, idGenerator, clock, repoProjectionRepository, repoProjectionCache, serviceRepository, workspaceMountRepository, serviceFragmentDefaultsRepository, initiativeRepository, documentRepository, taskRepository, executionEventPublisher, taskTypeRegistry, taskTypeSuppressionRepository, promptFragmentSource, workspaceSettings, reviewFrictionNotifications, resolveRunRepoContext, riskPolicyRepository, logger, }) {
|
|
68
74
|
this.workspaceRepository = workspaceRepository;
|
|
69
75
|
this.blockRepository = blockRepository;
|
|
70
76
|
this.executionRepository = executionRepository;
|
|
@@ -94,6 +100,7 @@ export class BoardService {
|
|
|
94
100
|
});
|
|
95
101
|
this.taskTypeDefaults = createTaskTypeCreationDefaults({
|
|
96
102
|
taskTypeRegistry,
|
|
103
|
+
promptFragmentSource,
|
|
97
104
|
taskTypeSuppressionRepository,
|
|
98
105
|
logger: this.log,
|
|
99
106
|
});
|
|
@@ -115,36 +122,49 @@ export class BoardService {
|
|
|
115
122
|
resolveBlock: (workspaceId, id) => this.resolveBlock(workspaceId, id),
|
|
116
123
|
frameMount: (workspaceId, block) => this.frameMount(workspaceId, block),
|
|
117
124
|
projectForWorkspace: (workspaceId, block) => this.projectForWorkspace(workspaceId, block),
|
|
118
|
-
emitBoardChanged: (originWorkspaceId,
|
|
125
|
+
emitBoardChanged: (originWorkspaceId, change) => this.emitBoardChanged(originWorkspaceId, change),
|
|
119
126
|
});
|
|
120
127
|
this.publicReads = new PublicBoardReads({
|
|
121
128
|
blockRepository,
|
|
122
129
|
requireWorkspace: (workspaceId) => this.requireWorkspace(workspaceId),
|
|
123
130
|
addTask: (workspaceId, containerId, input, editor, createdBy) => this.addTask(workspaceId, containerId, input, editor, createdBy),
|
|
124
131
|
});
|
|
132
|
+
this.internalAnchors = createInternalAnchors({
|
|
133
|
+
blockRepository,
|
|
134
|
+
idGenerator,
|
|
135
|
+
requireWorkspace: (workspaceId) => this.requireWorkspace(workspaceId),
|
|
136
|
+
});
|
|
125
137
|
}
|
|
126
138
|
/**
|
|
127
|
-
* Push a
|
|
139
|
+
* Push a board-changed signal for a successful mutation. `originWorkspaceId` MUST be
|
|
128
140
|
* the workspace that physically HOMES the affected block (its `homeWorkspaceId`), not
|
|
129
141
|
* necessarily the acting workspace: {@link FanOutEventPublisher} resolves the block's service
|
|
130
|
-
*
|
|
142
|
+
* (and thus every workspace that mounts it) by looking the block up under this origin, so
|
|
131
143
|
* passing a mounter's id for a block homed elsewhere would find nothing and collapse the
|
|
132
|
-
* fan-out to that one board. Naming a block lets the change reach every mount;
|
|
144
|
+
* fan-out to that one board. Naming a block lets the change reach every mount; name none for
|
|
133
145
|
* a signal that should reach the origin workspace only (e.g. a per-workspace frame-layout
|
|
134
146
|
* move). Best-effort: swallow any failure so a missed push never fails the already-persisted
|
|
135
|
-
* mutation
|
|
147
|
+
* mutation, since the client reconciles by re-fetching its snapshot.
|
|
136
148
|
*
|
|
137
|
-
*
|
|
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,
|
|
138
158
|
* carrying the acting tab's connection id), the realtime transport SKIPS delivering this
|
|
139
159
|
* echo back to that connection: its REST response already carried the authoritative result,
|
|
140
160
|
* so refreshing off its own event would only race an in-flight drag (snapping a block back to
|
|
141
161
|
* a stale position) or trigger a redundant board-wide re-hydrate on every inspector edit. Every
|
|
142
|
-
* OTHER subscriber still receives the
|
|
143
|
-
*
|
|
162
|
+
* OTHER subscriber still receives the signal. Engine-driven board changes pass no origin id, so
|
|
163
|
+
* they fan out to everyone as before.
|
|
144
164
|
*/
|
|
145
|
-
async emitBoardChanged(originWorkspaceId,
|
|
165
|
+
async emitBoardChanged(originWorkspaceId, change) {
|
|
146
166
|
try {
|
|
147
|
-
await this.events?.boardChanged(originWorkspaceId,
|
|
167
|
+
await this.events?.boardChanged(originWorkspaceId, change);
|
|
148
168
|
}
|
|
149
169
|
catch {
|
|
150
170
|
// best-effort; the REST response already carried the mutation
|
|
@@ -272,7 +292,8 @@ export class BoardService {
|
|
|
272
292
|
};
|
|
273
293
|
const serviceId = await this.registerService(workspaceId, block);
|
|
274
294
|
await this.blockRepository.insert(workspaceId, block, serviceId);
|
|
275
|
-
|
|
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 });
|
|
276
297
|
return block;
|
|
277
298
|
}
|
|
278
299
|
/**
|
|
@@ -363,7 +384,8 @@ export class BoardService {
|
|
|
363
384
|
directory: directory ?? null,
|
|
364
385
|
});
|
|
365
386
|
await this.blockRepository.insert(workspaceId, block, serviceId);
|
|
366
|
-
|
|
387
|
+
// A service FRAME, so no payload (see `addBlock`).
|
|
388
|
+
await this.emitBoardChanged(workspaceId, { reason: 'block-added', blockId: block.id });
|
|
367
389
|
return block;
|
|
368
390
|
}
|
|
369
391
|
/**
|
|
@@ -413,8 +435,12 @@ export class BoardService {
|
|
|
413
435
|
createdAt: this.clock.now(),
|
|
414
436
|
};
|
|
415
437
|
await this.workspaceMountRepository.upsert(mount);
|
|
416
|
-
// Fan out from the frame's HOME so every board mounting the shared service refreshes.
|
|
417
|
-
|
|
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
|
+
});
|
|
418
444
|
}
|
|
419
445
|
// The frame block is the one homed on ANOTHER board, carrying that board's coordinates —
|
|
420
446
|
// return it placed where THIS board just mounted it, or the SPA drops the imported service
|
|
@@ -512,13 +538,21 @@ export class BoardService {
|
|
|
512
538
|
// `pr-reviewer` knows WHICH PR to review from its prompt.
|
|
513
539
|
block.description = buildReviewDescription(taskType, block.taskTypeFields, block.description);
|
|
514
540
|
// The best-practice fragments the task OWNS from creation: the create form's picks or the
|
|
515
|
-
// service's standing standards, unioned with the type's defaults
|
|
516
|
-
// standing context
|
|
517
|
-
//
|
|
518
|
-
|
|
541
|
+
// service's standing standards, unioned with the type's defaults, a registered operation's
|
|
542
|
+
// standing context, and whichever of its CONDITIONAL entries hold against the values collected
|
|
543
|
+
// above. Derived by `taskTypeCreationDefaults.ts`, which owns the precedence rules and STATES a
|
|
544
|
+
// custom type this process does not register.
|
|
545
|
+
//
|
|
546
|
+
// `block.taskTypeFields` and not `input.taskTypeFields`: the fields have been validated and
|
|
547
|
+
// SANITIZED by now, so a value for a field hidden by its own `showWhen` is already gone, and a
|
|
548
|
+
// conditional rule keyed on one must reduce to false to match what the row actually freezes.
|
|
549
|
+
const fragmentIds = await this.taskTypeDefaults.fragmentIdsFor({
|
|
519
550
|
taskType,
|
|
520
551
|
explicit: input.fragmentIds,
|
|
521
552
|
serviceFragmentIds: service?.serviceFragmentIds,
|
|
553
|
+
// `?? undefined` because the row spells "no per-case values" as null while the reduction
|
|
554
|
+
// takes an absent bag. Both mean the same thing to it: no conditional entry holds.
|
|
555
|
+
fields: block.taskTypeFields ?? undefined,
|
|
522
556
|
});
|
|
523
557
|
if (fragmentIds.length) {
|
|
524
558
|
block.fragmentIds = fragmentIds;
|
|
@@ -558,66 +592,30 @@ export class BoardService {
|
|
|
558
592
|
block.technical = input.technical;
|
|
559
593
|
await this.blockRepository.insert(homeWorkspaceId, block, await this.serviceForContainer(blocks, container));
|
|
560
594
|
// Origin = the block's HOME (the mounted service's home when added to a shared board), so
|
|
561
|
-
// the fan-out reaches every workspace mounting the service, not just the acting one.
|
|
562
|
-
|
|
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 });
|
|
563
599
|
return block;
|
|
564
600
|
}
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
*/
|
|
572
|
-
async createInternalTask(workspaceId, input) {
|
|
573
|
-
await this.requireWorkspace(workspaceId);
|
|
574
|
-
const block = {
|
|
575
|
-
id: this.idGenerator.next('task'),
|
|
576
|
-
title: input.title.trim() || 'Initiative',
|
|
577
|
-
// `type` is the service/repo CLASSIFICATION (frontend/service/library/…), orthogonal to the
|
|
578
|
-
// `level` hierarchy; there is no task-specific BlockType. This anchor is a standalone,
|
|
579
|
-
// never-rendered, repo-less `level:'task'` block, so `type` is irrelevant to it — 'service'
|
|
580
|
-
// is just the neutral default (a normal task inherits its parent service's type instead).
|
|
581
|
-
type: 'service',
|
|
582
|
-
description: input.description ?? '',
|
|
583
|
-
position: { x: 0, y: 0 },
|
|
584
|
-
status: 'planned',
|
|
585
|
-
progress: 0,
|
|
586
|
-
dependsOn: [],
|
|
587
|
-
executionId: null,
|
|
588
|
-
level: 'task',
|
|
589
|
-
parentId: null,
|
|
590
|
-
internal: true,
|
|
591
|
-
};
|
|
592
|
-
await this.blockRepository.insert(workspaceId, block);
|
|
593
|
-
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);
|
|
594
607
|
}
|
|
595
|
-
/**
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
* external key to the runs IT created (an `internal` block) — never an arbitrary board
|
|
599
|
-
* execution that merely shares the key's workspace. See PublicApiController.
|
|
600
|
-
*/
|
|
601
|
-
async getInternalTask(workspaceId, blockId) {
|
|
602
|
-
const block = await this.blockRepository.get(workspaceId, blockId);
|
|
603
|
-
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);
|
|
604
611
|
}
|
|
605
|
-
/**
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
* `internal` block behind (it renders nowhere and is invisible to the cap, so it would just
|
|
609
|
-
* accumulate). A headless anchor has no children/service subtree, so a direct delete is enough.
|
|
610
|
-
*/
|
|
611
|
-
async deleteInternalTask(workspaceId, blockId) {
|
|
612
|
-
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);
|
|
613
615
|
}
|
|
614
|
-
/**
|
|
615
|
-
* How many of the workspace's headless internal "initiative" runs are still in flight — the
|
|
616
|
-
* concurrency backstop the public API checks before starting another, so a single (possibly
|
|
617
|
-
* leaked) key can't spin up unbounded LLM runs. A SQL `COUNT`, not a load-and-count.
|
|
618
|
-
*/
|
|
616
|
+
/** Public-API: how many anchored runs are in flight — the concurrency backstop. */
|
|
619
617
|
countActiveInternalTasks(workspaceId) {
|
|
620
|
-
return this.
|
|
618
|
+
return this.internalAnchors.countActiveInternalTasks(workspaceId);
|
|
621
619
|
}
|
|
622
620
|
// --- Public-API board reads/writes -----------------------------------------
|
|
623
621
|
// Delegated to {@link PublicBoardReads}: the external `/api/v1` surface is its own cohesive
|
|
@@ -685,7 +683,8 @@ export class BoardService {
|
|
|
685
683
|
};
|
|
686
684
|
await this.blockRepository.insert(homeWorkspaceId, block, containerServiceId);
|
|
687
685
|
// Origin = the block's HOME so a module added to a mounted service fans out to all mounts.
|
|
688
|
-
|
|
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 });
|
|
689
688
|
created.push(block);
|
|
690
689
|
n += 1;
|
|
691
690
|
}
|
|
@@ -723,7 +722,7 @@ export class BoardService {
|
|
|
723
722
|
parentId,
|
|
724
723
|
};
|
|
725
724
|
await this.blockRepository.insert(workspaceId, block);
|
|
726
|
-
await this.emitBoardChanged(workspaceId, 'block-added', block
|
|
725
|
+
await this.emitBoardChanged(workspaceId, { reason: 'block-added', block });
|
|
727
726
|
return block;
|
|
728
727
|
}
|
|
729
728
|
/**
|
|
@@ -745,9 +744,12 @@ export class BoardService {
|
|
|
745
744
|
}
|
|
746
745
|
}
|
|
747
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.
|
|
748
749
|
// Origin = the task's HOME so the fan-out resolves the (possibly mounted) service's boards.
|
|
749
|
-
await this.
|
|
750
|
-
|
|
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;
|
|
751
753
|
}
|
|
752
754
|
/** Move a block to a new spot on the board (a frame's position is its per-board override). */
|
|
753
755
|
async moveBlock(workspaceId, id, position, originConnectionId) {
|
|
@@ -796,17 +798,24 @@ export class BoardService {
|
|
|
796
798
|
// `customTaskTypeFields` (the half that may be patched) and the row stores the whole
|
|
797
799
|
// `taskTypeFields`, so this is where the request type becomes the repository's.
|
|
798
800
|
await this.blockRepository.update(homeWorkspaceId, id, narrow.customTaskTypeFields(effective, block));
|
|
801
|
+
const updated = assertFound(await this.blockRepository.get(homeWorkspaceId, id), 'Block', id);
|
|
799
802
|
// Origin = the block's HOME so editing a shared block fans out to every board mounting it.
|
|
800
|
-
// Forward the acting tab's connection id so the realtime transport SKIPS echoing this
|
|
801
|
-
//
|
|
802
|
-
//
|
|
803
|
-
//
|
|
804
|
-
//
|
|
805
|
-
|
|
806
|
-
//
|
|
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
|
|
807
816
|
// frame edit (rename, threshold, and above all a RESIZE) must not hand the SPA the block's
|
|
808
817
|
// own, never-updated coordinates to upsert.
|
|
809
|
-
return this.projectForWorkspace(workspaceId,
|
|
818
|
+
return this.projectForWorkspace(workspaceId, updated);
|
|
810
819
|
}
|
|
811
820
|
/** Move a block into a new container at a new local position. */
|
|
812
821
|
async reparent(workspaceId, id, input, originConnectionId) {
|
|
@@ -844,8 +853,14 @@ export class BoardService {
|
|
|
844
853
|
const destService = await this.serviceForContainer(destBlocks, parent);
|
|
845
854
|
await this.blockRepository.setService(blockHome, [...descendantIds(destBlocks, id)], destService ?? null);
|
|
846
855
|
}
|
|
847
|
-
// Origin = the block's HOME so the re-stamped subtree fans out to every mounting board.
|
|
848
|
-
|
|
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
|
+
});
|
|
849
864
|
return assertFound(await this.blockRepository.get(blockHome, id), 'Block', id);
|
|
850
865
|
}
|
|
851
866
|
// Cross-home: the block and its new parent belong to two services homed in different
|
|
@@ -886,10 +901,14 @@ export class BoardService {
|
|
|
886
901
|
// service's mounts (and that board). Source side: the block is gone from its old service, so
|
|
887
902
|
// the block→service join can't resolve it anymore — notify the captured source boards
|
|
888
903
|
// directly (origin-only) so they refresh the subtree away.
|
|
889
|
-
await this.emitBoardChanged(parentHome,
|
|
904
|
+
await this.emitBoardChanged(parentHome, {
|
|
905
|
+
reason: 'block-reparented',
|
|
906
|
+
blockId: id,
|
|
907
|
+
originConnectionId,
|
|
908
|
+
});
|
|
890
909
|
for (const ws of sourceFanout) {
|
|
891
910
|
if (ws !== parentHome) {
|
|
892
|
-
await this.emitBoardChanged(ws, 'block-reparented',
|
|
911
|
+
await this.emitBoardChanged(ws, { reason: 'block-reparented', originConnectionId });
|
|
893
912
|
}
|
|
894
913
|
}
|
|
895
914
|
return assertFound(await this.blockRepository.get(parentHome, id), 'Block', id);
|
|
@@ -956,9 +975,10 @@ export class BoardService {
|
|
|
956
975
|
await this.blockRepository.deleteMany(homeWorkspaceId, [...doomed]);
|
|
957
976
|
await pruneDanglingEdges(this.blockRepository, homeWorkspaceId, blocks, doomed);
|
|
958
977
|
// The block + any shared service are now gone, so fan out per captured target (blockId is
|
|
959
|
-
// unresolvable post-delete)
|
|
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.
|
|
960
980
|
for (const ws of fanoutTargets) {
|
|
961
|
-
await this.emitBoardChanged(ws, 'block-removed'
|
|
981
|
+
await this.emitBoardChanged(ws, { reason: 'block-removed' });
|
|
962
982
|
}
|
|
963
983
|
}
|
|
964
984
|
/**
|
|
@@ -983,7 +1003,11 @@ export class BoardService {
|
|
|
983
1003
|
}
|
|
984
1004
|
await this.blockRepository.update(homeWorkspaceId, id, { archived });
|
|
985
1005
|
// Origin = the block's HOME so archiving a shared service fans out to every board mounting it.
|
|
986
|
-
|
|
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
|
+
});
|
|
987
1011
|
// Always a frame (asserted above), so it owes the caller this board's layout override.
|
|
988
1012
|
return this.projectForWorkspace(workspaceId, assertFound(await this.blockRepository.get(homeWorkspaceId, id), 'Block', id));
|
|
989
1013
|
}
|
|
@@ -1028,8 +1052,11 @@ export class BoardService {
|
|
|
1028
1052
|
}
|
|
1029
1053
|
}
|
|
1030
1054
|
// Origin = the target's HOME so toggling an edge on a shared task fans out to all mounts.
|
|
1031
|
-
|
|
1032
|
-
|
|
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;
|
|
1033
1060
|
}
|
|
1034
1061
|
}
|
|
1035
1062
|
/**
|