@namzu/sdk 0.2.0 → 0.3.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/CHANGELOG.md +53 -2
- package/dist/agents/ReactiveAgent.d.ts.map +1 -1
- package/dist/agents/ReactiveAgent.js +3 -2
- package/dist/agents/ReactiveAgent.js.map +1 -1
- package/dist/agents/SupervisorAgent.d.ts.map +1 -1
- package/dist/agents/SupervisorAgent.js +5 -2
- package/dist/agents/SupervisorAgent.js.map +1 -1
- package/dist/bridge/a2a/index.d.ts +1 -1
- package/dist/bridge/a2a/index.d.ts.map +1 -1
- package/dist/bridge/a2a/index.js +1 -1
- package/dist/bridge/a2a/index.js.map +1 -1
- package/dist/bridge/a2a/message.d.ts +0 -2
- package/dist/bridge/a2a/message.d.ts.map +1 -1
- package/dist/bridge/a2a/message.js +0 -26
- package/dist/bridge/a2a/message.js.map +1 -1
- package/dist/bridge/a2a/task.d.ts +4 -3
- package/dist/bridge/a2a/task.d.ts.map +1 -1
- package/dist/bridge/a2a/task.js +4 -4
- package/dist/bridge/a2a/task.js.map +1 -1
- package/dist/contracts/api.d.ts +6 -38
- package/dist/contracts/api.d.ts.map +1 -1
- package/dist/contracts/ids.d.ts +1 -1
- package/dist/contracts/ids.d.ts.map +1 -1
- package/dist/contracts/index.d.ts +3 -5
- package/dist/contracts/index.d.ts.map +1 -1
- package/dist/contracts/index.js +1 -1
- package/dist/contracts/index.js.map +1 -1
- package/dist/contracts/schemas.d.ts +1 -31
- package/dist/contracts/schemas.d.ts.map +1 -1
- package/dist/contracts/schemas.js +1 -7
- package/dist/contracts/schemas.js.map +1 -1
- package/dist/index.d.ts +2 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -3
- package/dist/index.js.map +1 -1
- package/dist/manager/agent/__tests__/lifecycle.test.js +27 -13
- package/dist/manager/agent/__tests__/lifecycle.test.js.map +1 -1
- package/dist/manager/agent/lifecycle.d.ts +9 -0
- package/dist/manager/agent/lifecycle.d.ts.map +1 -1
- package/dist/manager/agent/lifecycle.js +93 -31
- package/dist/manager/agent/lifecycle.js.map +1 -1
- package/dist/manager/index.d.ts +2 -0
- package/dist/manager/index.d.ts.map +1 -1
- package/dist/manager/index.js +1 -0
- package/dist/manager/index.js.map +1 -1
- package/dist/manager/run/persistence.d.ts +3 -1
- package/dist/manager/run/persistence.d.ts.map +1 -1
- package/dist/manager/run/persistence.js +5 -0
- package/dist/manager/run/persistence.js.map +1 -1
- package/dist/manager/thread/__tests__/lifecycle.test.d.ts +2 -0
- package/dist/manager/thread/__tests__/lifecycle.test.d.ts.map +1 -0
- package/dist/manager/thread/__tests__/lifecycle.test.js +216 -0
- package/dist/manager/thread/__tests__/lifecycle.test.js.map +1 -0
- package/dist/manager/thread/lifecycle.d.ts +105 -0
- package/dist/manager/thread/lifecycle.d.ts.map +1 -0
- package/dist/manager/thread/lifecycle.js +186 -0
- package/dist/manager/thread/lifecycle.js.map +1 -0
- package/dist/rag/retriever.js +2 -2
- package/dist/runtime/query/__tests__/context.test.js +8 -7
- package/dist/runtime/query/__tests__/context.test.js.map +1 -1
- package/dist/runtime/query/context-cache.d.ts +3 -3
- package/dist/runtime/query/context-cache.d.ts.map +1 -1
- package/dist/runtime/query/context-cache.js +2 -2
- package/dist/runtime/query/context-cache.js.map +1 -1
- package/dist/runtime/query/context.d.ts +12 -21
- package/dist/runtime/query/context.d.ts.map +1 -1
- package/dist/runtime/query/context.js +3 -1
- package/dist/runtime/query/context.js.map +1 -1
- package/dist/runtime/query/index.d.ts +13 -15
- package/dist/runtime/query/index.d.ts.map +1 -1
- package/dist/runtime/query/index.js +1 -0
- package/dist/runtime/query/index.js.map +1 -1
- package/dist/session/__tests__/integration/_fixtures.d.ts +11 -4
- package/dist/session/__tests__/integration/_fixtures.d.ts.map +1 -1
- package/dist/session/__tests__/integration/_fixtures.js +23 -6
- package/dist/session/__tests__/integration/_fixtures.js.map +1 -1
- package/dist/session/__tests__/integration/archive-gate.test.d.ts +15 -0
- package/dist/session/__tests__/integration/archive-gate.test.d.ts.map +1 -0
- package/dist/session/__tests__/integration/archive-gate.test.js +214 -0
- package/dist/session/__tests__/integration/archive-gate.test.js.map +1 -0
- package/dist/session/__tests__/integration/capacity-caps.test.js +13 -6
- package/dist/session/__tests__/integration/capacity-caps.test.js.map +1 -1
- package/dist/session/__tests__/integration/e2e-spawn.test.js +14 -2
- package/dist/session/__tests__/integration/e2e-spawn.test.js.map +1 -1
- package/dist/session/__tests__/integration/event-stream-ordering.test.js +14 -7
- package/dist/session/__tests__/integration/event-stream-ordering.test.js.map +1 -1
- package/dist/session/__tests__/integration/handoff-broadcast-e2e.test.js +26 -14
- package/dist/session/__tests__/integration/handoff-broadcast-e2e.test.js.map +1 -1
- package/dist/session/__tests__/integration/handoff-illegal-transition.test.js +30 -20
- package/dist/session/__tests__/integration/handoff-illegal-transition.test.js.map +1 -1
- package/dist/session/__tests__/integration/handoff-single-e2e.test.js +25 -9
- package/dist/session/__tests__/integration/handoff-single-e2e.test.js.map +1 -1
- package/dist/session/__tests__/integration/hierarchy-lifecycle.test.js +11 -10
- package/dist/session/__tests__/integration/hierarchy-lifecycle.test.js.map +1 -1
- package/dist/session/__tests__/integration/prev-artifact-dag.test.js +5 -4
- package/dist/session/__tests__/integration/prev-artifact-dag.test.js.map +1 -1
- package/dist/session/__tests__/integration/retention-archive.test.js +3 -2
- package/dist/session/__tests__/integration/retention-archive.test.js.map +1 -1
- package/dist/session/__tests__/integration/spawn-rollback.test.d.ts +26 -0
- package/dist/session/__tests__/integration/spawn-rollback.test.d.ts.map +1 -0
- package/dist/session/__tests__/integration/spawn-rollback.test.js +236 -0
- package/dist/session/__tests__/integration/spawn-rollback.test.js.map +1 -0
- package/dist/session/__tests__/integration/summary-materialization-e2e.test.js +2 -1
- package/dist/session/__tests__/integration/summary-materialization-e2e.test.js.map +1 -1
- package/dist/session/__tests__/integration/tenant-isolation.test.js +14 -5
- package/dist/session/__tests__/integration/tenant-isolation.test.js.map +1 -1
- package/dist/session/errors.d.ts +79 -0
- package/dist/session/errors.d.ts.map +1 -1
- package/dist/session/errors.js +57 -0
- package/dist/session/errors.js.map +1 -1
- package/dist/session/handoff/__tests__/broadcast.test.js +49 -31
- package/dist/session/handoff/__tests__/broadcast.test.js.map +1 -1
- package/dist/session/handoff/__tests__/capacity.test.js +21 -18
- package/dist/session/handoff/__tests__/capacity.test.js.map +1 -1
- package/dist/session/handoff/__tests__/single.test.js +39 -30
- package/dist/session/handoff/__tests__/single.test.js.map +1 -1
- package/dist/session/handoff/assignment.d.ts +13 -1
- package/dist/session/handoff/assignment.d.ts.map +1 -1
- package/dist/session/handoff/broadcast.d.ts +7 -0
- package/dist/session/handoff/broadcast.d.ts.map +1 -1
- package/dist/session/handoff/broadcast.js +16 -1
- package/dist/session/handoff/broadcast.js.map +1 -1
- package/dist/session/handoff/single.d.ts +7 -0
- package/dist/session/handoff/single.d.ts.map +1 -1
- package/dist/session/handoff/single.js +13 -1
- package/dist/session/handoff/single.js.map +1 -1
- package/dist/session/hierarchy/__tests__/session.test.js +2 -0
- package/dist/session/hierarchy/__tests__/session.test.js.map +1 -1
- package/dist/session/hierarchy/index.d.ts +1 -0
- package/dist/session/hierarchy/index.d.ts.map +1 -1
- package/dist/session/hierarchy/index.js.map +1 -1
- package/dist/session/hierarchy/session.d.ts +15 -3
- package/dist/session/hierarchy/session.d.ts.map +1 -1
- package/dist/session/hierarchy/session.js.map +1 -1
- package/dist/session/hierarchy/thread.d.ts +54 -0
- package/dist/session/hierarchy/thread.d.ts.map +1 -0
- package/dist/session/hierarchy/thread.js +2 -0
- package/dist/session/hierarchy/thread.js.map +1 -0
- package/dist/session/migration/id-prefix.d.ts +8 -13
- package/dist/session/migration/id-prefix.d.ts.map +1 -1
- package/dist/session/migration/id-prefix.js +8 -13
- package/dist/session/migration/id-prefix.js.map +1 -1
- package/dist/session/retention/__tests__/archive.test.js +3 -2
- package/dist/session/retention/__tests__/archive.test.js.map +1 -1
- package/dist/session/summary/__tests__/materialize.test.js +4 -3
- package/dist/session/summary/__tests__/materialize.test.js.map +1 -1
- package/dist/store/index.d.ts +0 -2
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/index.js +0 -1
- package/dist/store/index.js.map +1 -1
- package/dist/store/session/__tests__/disk.test.js +32 -5
- package/dist/store/session/__tests__/disk.test.js.map +1 -1
- package/dist/store/session/__tests__/memory.test.js +50 -9
- package/dist/store/session/__tests__/memory.test.js.map +1 -1
- package/dist/store/session/disk.d.ts +2 -1
- package/dist/store/session/disk.d.ts.map +1 -1
- package/dist/store/session/disk.js +61 -0
- package/dist/store/session/disk.js.map +1 -1
- package/dist/store/session/index.d.ts.map +1 -1
- package/dist/store/session/index.js +3 -4
- package/dist/store/session/index.js.map +1 -1
- package/dist/store/session/memory.d.ts +2 -1
- package/dist/store/session/memory.d.ts.map +1 -1
- package/dist/store/session/memory.js +13 -0
- package/dist/store/session/memory.js.map +1 -1
- package/dist/store/thread/disk.d.ts +41 -0
- package/dist/store/thread/disk.d.ts.map +1 -0
- package/dist/store/thread/disk.js +229 -0
- package/dist/store/thread/disk.js.map +1 -0
- package/dist/store/thread/index.d.ts +4 -0
- package/dist/store/thread/index.d.ts.map +1 -0
- package/dist/store/thread/index.js +6 -0
- package/dist/store/thread/index.js.map +1 -0
- package/dist/store/thread/memory.d.ts +23 -0
- package/dist/store/thread/memory.d.ts.map +1 -0
- package/dist/store/thread/memory.js +90 -0
- package/dist/store/thread/memory.js.map +1 -0
- package/dist/types/agent/base.d.ts +17 -21
- package/dist/types/agent/base.d.ts.map +1 -1
- package/dist/types/agent/factory.d.ts +8 -2
- package/dist/types/agent/factory.d.ts.map +1 -1
- package/dist/types/agent/task.d.ts +18 -11
- package/dist/types/agent/task.d.ts.map +1 -1
- package/dist/types/ids/index.d.ts +5 -9
- package/dist/types/ids/index.d.ts.map +1 -1
- package/dist/types/ids/index.js +4 -4
- package/dist/types/ids/index.js.map +1 -1
- package/dist/types/rag/retrieval.d.ts +4 -3
- package/dist/types/rag/retrieval.d.ts.map +1 -1
- package/dist/types/run/config.d.ts +6 -5
- package/dist/types/run/config.d.ts.map +1 -1
- package/dist/types/run/metadata.d.ts +5 -18
- package/dist/types/run/metadata.d.ts.map +1 -1
- package/dist/types/session/ids.d.ts +4 -13
- package/dist/types/session/ids.d.ts.map +1 -1
- package/dist/types/session/ids.js +3 -6
- package/dist/types/session/ids.js.map +1 -1
- package/dist/types/session/index.d.ts +1 -1
- package/dist/types/session/index.d.ts.map +1 -1
- package/dist/types/session/store.d.ts +32 -10
- package/dist/types/session/store.d.ts.map +1 -1
- package/dist/types/session/store.js +3 -8
- package/dist/types/session/store.js.map +1 -1
- package/dist/types/thread/index.d.ts +2 -0
- package/dist/types/thread/index.d.ts.map +1 -0
- package/dist/types/thread/index.js +5 -0
- package/dist/types/thread/index.js.map +1 -0
- package/dist/types/thread/store.d.ts +86 -0
- package/dist/types/thread/store.d.ts.map +1 -0
- package/dist/types/thread/store.js +22 -0
- package/dist/types/thread/store.js.map +1 -0
- package/dist/utils/id.d.ts +1 -12
- package/dist/utils/id.d.ts.map +1 -1
- package/dist/utils/id.js +3 -23
- package/dist/utils/id.js.map +1 -1
- package/package.json +6 -11
- package/src/agents/ReactiveAgent.ts +3 -2
- package/src/agents/SupervisorAgent.ts +5 -2
- package/src/bridge/a2a/index.ts +0 -1
- package/src/bridge/a2a/message.ts +0 -32
- package/src/bridge/a2a/task.ts +8 -7
- package/src/contracts/api.ts +6 -42
- package/src/contracts/ids.ts +1 -1
- package/src/contracts/index.ts +2 -8
- package/src/contracts/schemas.ts +1 -8
- package/src/index.ts +0 -4
- package/src/manager/agent/__tests__/lifecycle.test.ts +34 -13
- package/src/manager/agent/lifecycle.ts +114 -35
- package/src/manager/index.ts +3 -0
- package/src/manager/run/persistence.ts +7 -1
- package/src/manager/thread/__tests__/lifecycle.test.ts +286 -0
- package/src/manager/thread/lifecycle.ts +217 -0
- package/src/rag/retriever.ts +2 -2
- package/src/runtime/query/__tests__/context.test.ts +9 -8
- package/src/runtime/query/context-cache.ts +4 -4
- package/src/runtime/query/context.ts +15 -22
- package/src/runtime/query/index.ts +15 -16
- package/src/session/__tests__/integration/_fixtures.ts +36 -8
- package/src/session/__tests__/integration/archive-gate.test.ts +288 -0
- package/src/session/__tests__/integration/capacity-caps.test.ts +13 -6
- package/src/session/__tests__/integration/e2e-spawn.test.ts +20 -2
- package/src/session/__tests__/integration/event-stream-ordering.test.ts +14 -7
- package/src/session/__tests__/integration/handoff-broadcast-e2e.test.ts +39 -13
- package/src/session/__tests__/integration/handoff-illegal-transition.test.ts +54 -19
- package/src/session/__tests__/integration/handoff-single-e2e.test.ts +40 -9
- package/src/session/__tests__/integration/hierarchy-lifecycle.test.ts +13 -10
- package/src/session/__tests__/integration/prev-artifact-dag.test.ts +12 -5
- package/src/session/__tests__/integration/retention-archive.test.ts +5 -3
- package/src/session/__tests__/integration/spawn-rollback.test.ts +313 -0
- package/src/session/__tests__/integration/summary-materialization-e2e.test.ts +4 -2
- package/src/session/__tests__/integration/tenant-isolation.test.ts +16 -6
- package/src/session/errors.ts +89 -0
- package/src/session/handoff/__tests__/broadcast.test.ts +56 -28
- package/src/session/handoff/__tests__/capacity.test.ts +26 -20
- package/src/session/handoff/__tests__/single.test.ts +45 -28
- package/src/session/handoff/assignment.ts +13 -1
- package/src/session/handoff/broadcast.ts +26 -1
- package/src/session/handoff/single.ts +23 -1
- package/src/session/hierarchy/__tests__/session.test.ts +9 -1
- package/src/session/hierarchy/index.ts +1 -0
- package/src/session/hierarchy/session.ts +15 -3
- package/src/session/hierarchy/thread.ts +55 -0
- package/src/session/migration/id-prefix.ts +8 -13
- package/src/session/retention/__tests__/archive.test.ts +5 -3
- package/src/session/summary/__tests__/materialize.test.ts +6 -4
- package/src/store/index.ts +0 -3
- package/src/store/session/__tests__/disk.test.ts +57 -6
- package/src/store/session/__tests__/memory.test.ts +84 -9
- package/src/store/session/disk.ts +57 -1
- package/src/store/session/index.ts +3 -4
- package/src/store/session/memory.ts +13 -1
- package/src/store/thread/disk.ts +261 -0
- package/src/store/thread/index.ts +7 -0
- package/src/store/thread/memory.ts +104 -0
- package/src/types/agent/base.ts +17 -21
- package/src/types/agent/factory.ts +8 -3
- package/src/types/agent/task.ts +19 -11
- package/src/types/ids/index.ts +8 -15
- package/src/types/rag/retrieval.ts +4 -3
- package/src/types/run/config.ts +6 -5
- package/src/types/run/metadata.ts +5 -18
- package/src/types/session/ids.ts +4 -15
- package/src/types/session/index.ts +1 -2
- package/src/types/session/store.ts +34 -11
- package/src/types/thread/index.ts +5 -0
- package/src/types/thread/store.ts +92 -0
- package/src/utils/id.ts +3 -24
- package/dist/store/conversation/memory.d.ts +0 -43
- package/dist/store/conversation/memory.d.ts.map +0 -1
- package/dist/store/conversation/memory.js +0 -108
- package/dist/store/conversation/memory.js.map +0 -1
- package/dist/types/conversation/index.d.ts +0 -14
- package/dist/types/conversation/index.d.ts.map +0 -1
- package/dist/types/conversation/index.js +0 -2
- package/dist/types/conversation/index.js.map +0 -1
- package/src/store/conversation/memory.ts +0 -144
- package/src/types/conversation/index.ts +0 -15
|
@@ -14,10 +14,12 @@
|
|
|
14
14
|
import { describe, expect, it } from 'vitest'
|
|
15
15
|
import { InMemorySessionStore } from '../../../store/session/memory.js'
|
|
16
16
|
import { createUserMessage } from '../../../types/message/index.js'
|
|
17
|
-
import type { ProjectId, SubSessionId, SummaryId } from '../../../types/session/ids.js'
|
|
17
|
+
import type { ProjectId, SubSessionId, SummaryId, ThreadId } from '../../../types/session/ids.js'
|
|
18
18
|
import { TenantIsolationError } from '../../errors.js'
|
|
19
19
|
import { DEFAULT_TENANT, OTHER_TENANT, agentActor, userActor } from './_fixtures.js'
|
|
20
20
|
|
|
21
|
+
const TEST_THREAD_ID = 'thd_test' as ThreadId
|
|
22
|
+
|
|
21
23
|
async function seedTenantAResources() {
|
|
22
24
|
const store = new InMemorySessionStore()
|
|
23
25
|
const project = await store.createProject(
|
|
@@ -25,11 +27,11 @@ async function seedTenantAResources() {
|
|
|
25
27
|
DEFAULT_TENANT,
|
|
26
28
|
)
|
|
27
29
|
const parent = await store.createSession(
|
|
28
|
-
{ projectId: project.id, currentActor: userActor('usr_a') },
|
|
30
|
+
{ threadId: TEST_THREAD_ID, projectId: project.id, currentActor: userActor('usr_a') },
|
|
29
31
|
DEFAULT_TENANT,
|
|
30
32
|
)
|
|
31
33
|
const child = await store.createSession(
|
|
32
|
-
{ projectId: project.id, currentActor: agentActor('agt_a') },
|
|
34
|
+
{ threadId: TEST_THREAD_ID, projectId: project.id, currentActor: agentActor('agt_a') },
|
|
33
35
|
DEFAULT_TENANT,
|
|
34
36
|
)
|
|
35
37
|
const sub = await store.createSubSession(
|
|
@@ -200,7 +202,11 @@ describe('Integration — tenant isolation', () => {
|
|
|
200
202
|
const { store, project } = await seedTenantAResources()
|
|
201
203
|
await expect(
|
|
202
204
|
store.createSession(
|
|
203
|
-
{
|
|
205
|
+
{
|
|
206
|
+
threadId: TEST_THREAD_ID,
|
|
207
|
+
projectId: project.id,
|
|
208
|
+
currentActor: userActor('usr_intruder', OTHER_TENANT),
|
|
209
|
+
},
|
|
204
210
|
OTHER_TENANT,
|
|
205
211
|
),
|
|
206
212
|
).rejects.toBeInstanceOf(TenantIsolationError)
|
|
@@ -232,7 +238,11 @@ describe('Integration — tenant isolation', () => {
|
|
|
232
238
|
OTHER_TENANT,
|
|
233
239
|
)
|
|
234
240
|
const otherChild = await store.createSession(
|
|
235
|
-
{
|
|
241
|
+
{
|
|
242
|
+
threadId: TEST_THREAD_ID,
|
|
243
|
+
projectId: otherProject.id,
|
|
244
|
+
currentActor: userActor('usr_other', OTHER_TENANT),
|
|
245
|
+
},
|
|
236
246
|
OTHER_TENANT,
|
|
237
247
|
)
|
|
238
248
|
|
|
@@ -264,7 +274,7 @@ describe('Integration — tenant isolation', () => {
|
|
|
264
274
|
await store.createProject({ tenantId: DEFAULT_TENANT, name: 'del' }, DEFAULT_TENANT)
|
|
265
275
|
).id
|
|
266
276
|
const lonely = await store.createSession(
|
|
267
|
-
{ projectId: project, currentActor: userActor('usr_lonely') },
|
|
277
|
+
{ threadId: TEST_THREAD_ID, projectId: project, currentActor: userActor('usr_lonely') },
|
|
268
278
|
DEFAULT_TENANT,
|
|
269
279
|
)
|
|
270
280
|
await expect(store.deleteSession(lonely.id, OTHER_TENANT)).rejects.toBeInstanceOf(
|
package/src/session/errors.ts
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import type { SessionId, TenantId } from '../types/ids/index.js'
|
|
11
|
+
import type { ThreadId } from '../types/session/ids.js'
|
|
12
|
+
import type { SessionStatus } from './hierarchy/session.js'
|
|
11
13
|
import type { WorkspaceBackendKind } from './workspace/driver.js'
|
|
12
14
|
|
|
13
15
|
/**
|
|
@@ -68,3 +70,90 @@ export class WorkspaceBackendError extends Error {
|
|
|
68
70
|
this.details = details
|
|
69
71
|
}
|
|
70
72
|
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Raised by {@link import('../types/thread/store.js').ThreadStore.updateThread}
|
|
76
|
+
* when the supplied {@link Thread.ownerVersion} does not match the persisted
|
|
77
|
+
* record. The caller must re-read via `getThread`, re-apply its intended
|
|
78
|
+
* mutation on top of the fresh record, and retry. Mirrors the Session
|
|
79
|
+
* handoff CAS pattern (§6.1).
|
|
80
|
+
*/
|
|
81
|
+
export class StaleThreadError extends Error {
|
|
82
|
+
readonly details: {
|
|
83
|
+
threadId: ThreadId
|
|
84
|
+
expectedVersion: number
|
|
85
|
+
actualVersion: number
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
constructor(details: { threadId: ThreadId; expectedVersion: number; actualVersion: number }) {
|
|
89
|
+
super(
|
|
90
|
+
`Stale Thread ${details.threadId}: expected ownerVersion=${details.expectedVersion}, actual=${details.actualVersion}`,
|
|
91
|
+
)
|
|
92
|
+
this.name = 'StaleThreadError'
|
|
93
|
+
this.details = details
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Raised by the spawn path (and any caller that enforces the open-thread
|
|
99
|
+
* precondition) when a Thread is in `'archived'` state and would-be mutations
|
|
100
|
+
* require it to be `'open'`. Convention #5: deny-by-default — archival is a
|
|
101
|
+
* hard read-only boundary.
|
|
102
|
+
*/
|
|
103
|
+
export class ThreadClosedError extends Error {
|
|
104
|
+
readonly details: {
|
|
105
|
+
threadId: ThreadId
|
|
106
|
+
op: string
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
constructor(details: { threadId: ThreadId; op: string }) {
|
|
110
|
+
super(`Thread ${details.threadId} is archived; operation '${details.op}' rejected`)
|
|
111
|
+
this.name = 'ThreadClosedError'
|
|
112
|
+
this.details = details
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Raised by {@link import('../manager/thread/lifecycle.js').ThreadManager.archive}
|
|
118
|
+
* and `.delete` when the Thread's session-presence precondition is violated:
|
|
119
|
+
*
|
|
120
|
+
* - `op: 'archive'` — at least one Session under the Thread is in a
|
|
121
|
+
* non-terminal state (`active | locked | awaiting_hitl | awaiting_merge`).
|
|
122
|
+
* The caller must first quiesce those sessions (let them reach `idle`,
|
|
123
|
+
* `failed`, or `archived`) before flipping the Thread to archived.
|
|
124
|
+
* - `op: 'delete'` — the Thread still has at least one attached Session.
|
|
125
|
+
* Callers must either archive + tombstone those sessions (`deleteSession`)
|
|
126
|
+
* before calling `deleteThread`, or accept that deletion is not yet safe.
|
|
127
|
+
*
|
|
128
|
+
* `blockingSessions` carries the first {@link THREAD_NOT_EMPTY_SAMPLE_LIMIT}
|
|
129
|
+
* offenders with their current status so operator tooling can surface an
|
|
130
|
+
* actionable list without unbounded error payloads on large threads.
|
|
131
|
+
* `totalBlockingSessions` holds the full count even when the sample is
|
|
132
|
+
* truncated. Convention #5: deny-by-default — no implicit cascade, no silent
|
|
133
|
+
* no-op.
|
|
134
|
+
*/
|
|
135
|
+
export const THREAD_NOT_EMPTY_SAMPLE_LIMIT = 50
|
|
136
|
+
|
|
137
|
+
export class ThreadNotEmptyError extends Error {
|
|
138
|
+
readonly details: {
|
|
139
|
+
threadId: ThreadId
|
|
140
|
+
tenantId: TenantId
|
|
141
|
+
op: 'archive' | 'delete'
|
|
142
|
+
blockingSessions: ReadonlyArray<{ sessionId: SessionId; status: SessionStatus }>
|
|
143
|
+
totalBlockingSessions: number
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
constructor(details: {
|
|
147
|
+
threadId: ThreadId
|
|
148
|
+
tenantId: TenantId
|
|
149
|
+
op: 'archive' | 'delete'
|
|
150
|
+
blockingSessions: ReadonlyArray<{ sessionId: SessionId; status: SessionStatus }>
|
|
151
|
+
totalBlockingSessions: number
|
|
152
|
+
}) {
|
|
153
|
+
super(
|
|
154
|
+
`Thread ${details.threadId} ${details.op} blocked: ${details.totalBlockingSessions} session(s) still attached`,
|
|
155
|
+
)
|
|
156
|
+
this.name = 'ThreadNotEmptyError'
|
|
157
|
+
this.details = details
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { ThreadManager } from '../../../manager/thread/lifecycle.js'
|
|
2
3
|
import type { ActorRef } from '../../../session/hierarchy/actor.js'
|
|
3
4
|
import {
|
|
4
5
|
type ExecFile,
|
|
@@ -7,6 +8,7 @@ import {
|
|
|
7
8
|
} from '../../../session/workspace/git-worktree.js'
|
|
8
9
|
import { WorkspaceBackendRegistry } from '../../../session/workspace/registry.js'
|
|
9
10
|
import { InMemorySessionStore } from '../../../store/session/memory.js'
|
|
11
|
+
import { InMemoryThreadStore } from '../../../store/thread/memory.js'
|
|
10
12
|
import type { SessionId, TenantId, UserId } from '../../../types/ids/index.js'
|
|
11
13
|
import type { ProjectId } from '../../../types/session/ids.js'
|
|
12
14
|
import { generateHandoffId } from '../../../utils/id.js'
|
|
@@ -56,7 +58,11 @@ interface DepsBundle {
|
|
|
56
58
|
events: MockedHandoffEventSink
|
|
57
59
|
}
|
|
58
60
|
|
|
59
|
-
function buildDeps(
|
|
61
|
+
function buildDeps(
|
|
62
|
+
store: InMemorySessionStore,
|
|
63
|
+
threadStore: InMemoryThreadStore,
|
|
64
|
+
execOverride?: ExecFile,
|
|
65
|
+
): DepsBundle {
|
|
60
66
|
const exec: ExecFile = execOverride ? execOverride : async (_file, _args) => okExec()
|
|
61
67
|
const driver = new GitWorktreeDriver({
|
|
62
68
|
repoRoot: '/repo',
|
|
@@ -73,29 +79,36 @@ function buildDeps(store: InMemorySessionStore, execOverride?: ExecFile): DepsBu
|
|
|
73
79
|
onBroadcastRollback: vi.fn<(ev: HandoffBroadcastRollbackEvent) => void>(),
|
|
74
80
|
}
|
|
75
81
|
|
|
82
|
+
const threadManager = new ThreadManager({ threadStore, sessionStore: store })
|
|
76
83
|
return {
|
|
77
84
|
deps: {
|
|
78
85
|
store,
|
|
79
86
|
workspaceRegistry: registry,
|
|
80
87
|
capacity: new DefaultCapacityValidator(store),
|
|
81
88
|
events,
|
|
89
|
+
threadManager,
|
|
82
90
|
},
|
|
83
91
|
events,
|
|
84
92
|
}
|
|
85
93
|
}
|
|
86
94
|
|
|
87
|
-
async function seedIdle(store: InMemorySessionStore) {
|
|
95
|
+
async function seedIdle(store: InMemorySessionStore, threadStore: InMemoryThreadStore) {
|
|
88
96
|
const project = await store.createProject({ tenantId: tenant, name: 'p' }, tenant)
|
|
97
|
+
const thread = await threadStore.createThread(
|
|
98
|
+
{ projectId: project.id, title: 'handoff-broadcast-test' },
|
|
99
|
+
tenant,
|
|
100
|
+
)
|
|
89
101
|
const session = await store.createSession(
|
|
90
|
-
{ projectId: project.id, currentActor: user('usr_source') },
|
|
102
|
+
{ threadId: thread.id, projectId: project.id, currentActor: user('usr_source') },
|
|
91
103
|
tenant,
|
|
92
104
|
)
|
|
93
|
-
return { project, session }
|
|
105
|
+
return { project, thread, session }
|
|
94
106
|
}
|
|
95
107
|
|
|
96
108
|
function buildAssignments(
|
|
97
109
|
sourceSessionId: SessionId,
|
|
98
110
|
projectId: ProjectId,
|
|
111
|
+
threadId: Awaited<ReturnType<InMemoryThreadStore['createThread']>>['id'],
|
|
99
112
|
expectedOwnerVersion: number,
|
|
100
113
|
recipients: ActorRef[],
|
|
101
114
|
broadcastId = 'bc_1',
|
|
@@ -105,6 +118,7 @@ function buildAssignments(
|
|
|
105
118
|
mode: 'broadcast' as const,
|
|
106
119
|
sourceSessionId,
|
|
107
120
|
tenantId: tenant,
|
|
121
|
+
threadId,
|
|
108
122
|
projectId,
|
|
109
123
|
sourceActor: user('usr_source'),
|
|
110
124
|
recipientActor,
|
|
@@ -116,16 +130,18 @@ function buildAssignments(
|
|
|
116
130
|
|
|
117
131
|
describe('executeBroadcastHandoff', () => {
|
|
118
132
|
let store: InMemorySessionStore
|
|
133
|
+
let threadStore: InMemoryThreadStore
|
|
119
134
|
|
|
120
135
|
beforeEach(() => {
|
|
121
136
|
store = new InMemorySessionStore()
|
|
137
|
+
threadStore = new InMemoryThreadStore()
|
|
122
138
|
})
|
|
123
139
|
|
|
124
140
|
it('happy path: 3 recipients → source ends in awaiting_merge with 3 new children', async () => {
|
|
125
|
-
const { project, session } = await seedIdle(store)
|
|
126
|
-
const { deps, events } = buildDeps(store)
|
|
141
|
+
const { project, thread, session } = await seedIdle(store, threadStore)
|
|
142
|
+
const { deps, events } = buildDeps(store, threadStore)
|
|
127
143
|
|
|
128
|
-
const assignments = buildAssignments(session.id, project.id, 0, [
|
|
144
|
+
const assignments = buildAssignments(session.id, project.id, thread.id, 0, [
|
|
129
145
|
user('usr_bob'),
|
|
130
146
|
user('usr_carol'),
|
|
131
147
|
user('usr_dan'),
|
|
@@ -149,7 +165,7 @@ describe('executeBroadcastHandoff', () => {
|
|
|
149
165
|
})
|
|
150
166
|
|
|
151
167
|
it('rollback on mid-fan-out failure (2nd recipient worktree add fails): source reverts, rollback emits accurate partialState', async () => {
|
|
152
|
-
const { project, session } = await seedIdle(store)
|
|
168
|
+
const { project, thread, session } = await seedIdle(store, threadStore)
|
|
153
169
|
|
|
154
170
|
let addCount = 0
|
|
155
171
|
const exec: ExecFile = async (_file, args) => {
|
|
@@ -159,9 +175,9 @@ describe('executeBroadcastHandoff', () => {
|
|
|
159
175
|
}
|
|
160
176
|
return okExec()
|
|
161
177
|
}
|
|
162
|
-
const { deps, events } = buildDeps(store, exec)
|
|
178
|
+
const { deps, events } = buildDeps(store, threadStore, exec)
|
|
163
179
|
|
|
164
|
-
const assignments = buildAssignments(session.id, project.id, 0, [
|
|
180
|
+
const assignments = buildAssignments(session.id, project.id, thread.id, 0, [
|
|
165
181
|
user('usr_b'),
|
|
166
182
|
user('usr_c'),
|
|
167
183
|
user('usr_d'),
|
|
@@ -196,7 +212,7 @@ describe('executeBroadcastHandoff', () => {
|
|
|
196
212
|
})
|
|
197
213
|
|
|
198
214
|
it('rollback performs full cleanup via deleteSubSession/deleteSession (no status-flip stopgap)', async () => {
|
|
199
|
-
const { project, session } = await seedIdle(store)
|
|
215
|
+
const { project, thread, session } = await seedIdle(store, threadStore)
|
|
200
216
|
|
|
201
217
|
let addCount = 0
|
|
202
218
|
const exec: ExecFile = async (_file, args) => {
|
|
@@ -206,9 +222,12 @@ describe('executeBroadcastHandoff', () => {
|
|
|
206
222
|
}
|
|
207
223
|
return okExec()
|
|
208
224
|
}
|
|
209
|
-
const { deps } = buildDeps(store, exec)
|
|
225
|
+
const { deps } = buildDeps(store, threadStore, exec)
|
|
210
226
|
|
|
211
|
-
const assignments = buildAssignments(session.id, project.id, 0, [
|
|
227
|
+
const assignments = buildAssignments(session.id, project.id, thread.id, 0, [
|
|
228
|
+
user('usr_b'),
|
|
229
|
+
user('usr_c'),
|
|
230
|
+
])
|
|
212
231
|
|
|
213
232
|
await expect(executeBroadcastHandoff(deps, assignments, tenant)).rejects.toThrow()
|
|
214
233
|
|
|
@@ -224,7 +243,7 @@ describe('executeBroadcastHandoff', () => {
|
|
|
224
243
|
})
|
|
225
244
|
|
|
226
245
|
it('rollback idempotency: worktree dispose throwing during rollback does not bubble a secondary failure', async () => {
|
|
227
|
-
const { project, session } = await seedIdle(store)
|
|
246
|
+
const { project, thread, session } = await seedIdle(store, threadStore)
|
|
228
247
|
|
|
229
248
|
let addCount = 0
|
|
230
249
|
let removeCount = 0
|
|
@@ -242,9 +261,12 @@ describe('executeBroadcastHandoff', () => {
|
|
|
242
261
|
}
|
|
243
262
|
return okExec()
|
|
244
263
|
}
|
|
245
|
-
const { deps, events } = buildDeps(store, exec)
|
|
264
|
+
const { deps, events } = buildDeps(store, threadStore, exec)
|
|
246
265
|
|
|
247
|
-
const assignments = buildAssignments(session.id, project.id, 0, [
|
|
266
|
+
const assignments = buildAssignments(session.id, project.id, thread.id, 0, [
|
|
267
|
+
user('usr_b'),
|
|
268
|
+
user('usr_c'),
|
|
269
|
+
])
|
|
248
270
|
|
|
249
271
|
// Outer failure is the PRIMARY one — the secondary dispose failure is
|
|
250
272
|
// swallowed. Primary wraps in WorkspaceBackendError (create op).
|
|
@@ -262,11 +284,15 @@ describe('executeBroadcastHandoff', () => {
|
|
|
262
284
|
})
|
|
263
285
|
|
|
264
286
|
it('dedupe: two assignments targeting same recipient → rejected pre-lock (no side effects)', async () => {
|
|
265
|
-
const { project, session } = await seedIdle(store)
|
|
266
|
-
const { deps, events } = buildDeps(store)
|
|
287
|
+
const { project, thread, session } = await seedIdle(store, threadStore)
|
|
288
|
+
const { deps, events } = buildDeps(store, threadStore)
|
|
267
289
|
|
|
268
290
|
const bob = user('usr_bob')
|
|
269
|
-
const assignments = buildAssignments(session.id, project.id, 0, [
|
|
291
|
+
const assignments = buildAssignments(session.id, project.id, thread.id, 0, [
|
|
292
|
+
bob,
|
|
293
|
+
bob,
|
|
294
|
+
user('usr_dan'),
|
|
295
|
+
])
|
|
270
296
|
|
|
271
297
|
await expect(executeBroadcastHandoff(deps, assignments, tenant)).rejects.toThrow(
|
|
272
298
|
/duplicate recipient/,
|
|
@@ -281,11 +307,11 @@ describe('executeBroadcastHandoff', () => {
|
|
|
281
307
|
})
|
|
282
308
|
|
|
283
309
|
it('width cap: 9 recipients exceeds default maxWidth=8 → rejected before source lock', async () => {
|
|
284
|
-
const { project, session } = await seedIdle(store)
|
|
285
|
-
const { deps, events } = buildDeps(store)
|
|
310
|
+
const { project, thread, session } = await seedIdle(store, threadStore)
|
|
311
|
+
const { deps, events } = buildDeps(store, threadStore)
|
|
286
312
|
|
|
287
313
|
const recipients = Array.from({ length: 9 }, (_, i) => user(`usr_${i}`))
|
|
288
|
-
const assignments = buildAssignments(session.id, project.id, 0, recipients)
|
|
314
|
+
const assignments = buildAssignments(session.id, project.id, thread.id, 0, recipients)
|
|
289
315
|
|
|
290
316
|
await expect(executeBroadcastHandoff(deps, assignments, tenant)).rejects.toThrow(
|
|
291
317
|
/Delegation capacity exceeded/,
|
|
@@ -298,12 +324,13 @@ describe('executeBroadcastHandoff', () => {
|
|
|
298
324
|
})
|
|
299
325
|
|
|
300
326
|
it('concurrent broadcast on same source: second attempt rejected with HandoffVersionConflict', async () => {
|
|
301
|
-
const { project, session } = await seedIdle(store)
|
|
302
|
-
const { deps } = buildDeps(store)
|
|
327
|
+
const { project, thread, session } = await seedIdle(store, threadStore)
|
|
328
|
+
const { deps } = buildDeps(store, threadStore)
|
|
303
329
|
|
|
304
330
|
const firstAssignments = buildAssignments(
|
|
305
331
|
session.id,
|
|
306
332
|
project.id,
|
|
333
|
+
thread.id,
|
|
307
334
|
0,
|
|
308
335
|
[user('usr_b'), user('usr_c')],
|
|
309
336
|
'bc_1',
|
|
@@ -322,6 +349,7 @@ describe('executeBroadcastHandoff', () => {
|
|
|
322
349
|
const second = buildAssignments(
|
|
323
350
|
session.id,
|
|
324
351
|
project.id,
|
|
352
|
+
thread.id,
|
|
325
353
|
0, // stale — actual is 1
|
|
326
354
|
[user('usr_d'), user('usr_e')],
|
|
327
355
|
'bc_2',
|
|
@@ -332,16 +360,16 @@ describe('executeBroadcastHandoff', () => {
|
|
|
332
360
|
})
|
|
333
361
|
|
|
334
362
|
it('empty assignments → throws a descriptive error', async () => {
|
|
335
|
-
const { deps } = buildDeps(store)
|
|
363
|
+
const { deps } = buildDeps(store, threadStore)
|
|
336
364
|
await expect(executeBroadcastHandoff(deps, [], tenant)).rejects.toThrow(
|
|
337
365
|
/assignments must not be empty/,
|
|
338
366
|
)
|
|
339
367
|
})
|
|
340
368
|
|
|
341
369
|
it('single-row broadcast → rejected (caller must use executeSingleHandoff)', async () => {
|
|
342
|
-
const { project, session } = await seedIdle(store)
|
|
343
|
-
const { deps } = buildDeps(store)
|
|
344
|
-
const assignments = buildAssignments(session.id, project.id, 0, [user('usr_b')])
|
|
370
|
+
const { project, thread, session } = await seedIdle(store, threadStore)
|
|
371
|
+
const { deps } = buildDeps(store, threadStore)
|
|
372
|
+
const assignments = buildAssignments(session.id, project.id, thread.id, 0, [user('usr_b')])
|
|
345
373
|
|
|
346
374
|
await expect(executeBroadcastHandoff(deps, assignments, tenant)).rejects.toThrow(
|
|
347
375
|
/single-recipient handoffs must use executeSingleHandoff/,
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest'
|
|
2
2
|
import type { ActorRef } from '../../../session/hierarchy/actor.js'
|
|
3
3
|
import { InMemorySessionStore } from '../../../store/session/memory.js'
|
|
4
|
+
import { InMemoryThreadStore } from '../../../store/thread/memory.js'
|
|
4
5
|
import type { AgentId, SessionId, TenantId, UserId } from '../../../types/ids/index.js'
|
|
6
|
+
import type { ProjectId, ThreadId } from '../../../types/session/ids.js'
|
|
5
7
|
import { DefaultCapacityValidator, DelegationCapacityExceeded } from '../capacity.js'
|
|
6
8
|
|
|
7
9
|
const tenant = 'tnt_alpha' as TenantId
|
|
@@ -16,18 +18,22 @@ function agent(): ActorRef {
|
|
|
16
18
|
|
|
17
19
|
async function seedProject(store: InMemorySessionStore) {
|
|
18
20
|
const project = await store.createProject({ tenantId: tenant, name: 'p' }, tenant)
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
+
const threadStore = new InMemoryThreadStore()
|
|
22
|
+
const thread = await threadStore.createThread({ projectId: project.id, title: 'default' }, tenant)
|
|
23
|
+
const root = await store.createSession(
|
|
24
|
+
{ threadId: thread.id, projectId: project.id, currentActor: user() },
|
|
25
|
+
tenant,
|
|
26
|
+
)
|
|
27
|
+
return { project, thread, root }
|
|
21
28
|
}
|
|
22
29
|
|
|
23
30
|
async function spawnChild(
|
|
24
31
|
store: InMemorySessionStore,
|
|
25
32
|
parentId: SessionId,
|
|
26
|
-
projectId:
|
|
27
|
-
|
|
28
|
-
: Parameters<InMemorySessionStore['createSession']>[0]['projectId'],
|
|
33
|
+
projectId: ProjectId,
|
|
34
|
+
threadId: ThreadId,
|
|
29
35
|
): Promise<{ childId: SessionId }> {
|
|
30
|
-
const child = await store.createSession({ projectId, currentActor: user() }, tenant)
|
|
36
|
+
const child = await store.createSession({ threadId, projectId, currentActor: user() }, tenant)
|
|
31
37
|
await store.createSubSession(
|
|
32
38
|
{
|
|
33
39
|
parentSessionId: parentId,
|
|
@@ -51,11 +57,11 @@ describe('DefaultCapacityValidator', () => {
|
|
|
51
57
|
|
|
52
58
|
it('depth: chain of 4 (root→c1→c2→c3→c4) allows a 5th (depth 5) to pass when limit = 5', async () => {
|
|
53
59
|
const store = new InMemorySessionStore()
|
|
54
|
-
const { project, root } = await seedProject(store)
|
|
55
|
-
const c1 = await spawnChild(store, root.id, project.id)
|
|
56
|
-
const c2 = await spawnChild(store, c1.childId, project.id)
|
|
57
|
-
const c3 = await spawnChild(store, c2.childId, project.id)
|
|
58
|
-
const c4 = await spawnChild(store, c3.childId, project.id)
|
|
60
|
+
const { project, thread, root } = await seedProject(store)
|
|
61
|
+
const c1 = await spawnChild(store, root.id, project.id, thread.id)
|
|
62
|
+
const c2 = await spawnChild(store, c1.childId, project.id, thread.id)
|
|
63
|
+
const c3 = await spawnChild(store, c2.childId, project.id, thread.id)
|
|
64
|
+
const c4 = await spawnChild(store, c3.childId, project.id, thread.id)
|
|
59
65
|
|
|
60
66
|
const validator = new DefaultCapacityValidator(store)
|
|
61
67
|
// Ancestry of c4: root→c1→c2→c3→c4 = length 5. Spawning under c4 = depth 5.
|
|
@@ -64,11 +70,11 @@ describe('DefaultCapacityValidator', () => {
|
|
|
64
70
|
|
|
65
71
|
it('depth: over-limit throws DelegationCapacityExceeded with dimension=depth', async () => {
|
|
66
72
|
const store = new InMemorySessionStore()
|
|
67
|
-
const { project, root } = await seedProject(store)
|
|
68
|
-
const c1 = await spawnChild(store, root.id, project.id)
|
|
69
|
-
const c2 = await spawnChild(store, c1.childId, project.id)
|
|
70
|
-
const c3 = await spawnChild(store, c2.childId, project.id)
|
|
71
|
-
const c4 = await spawnChild(store, c3.childId, project.id)
|
|
73
|
+
const { project, thread, root } = await seedProject(store)
|
|
74
|
+
const c1 = await spawnChild(store, root.id, project.id, thread.id)
|
|
75
|
+
const c2 = await spawnChild(store, c1.childId, project.id, thread.id)
|
|
76
|
+
const c3 = await spawnChild(store, c2.childId, project.id, thread.id)
|
|
77
|
+
const c4 = await spawnChild(store, c3.childId, project.id, thread.id)
|
|
72
78
|
|
|
73
79
|
const validator = new DefaultCapacityValidator(store)
|
|
74
80
|
try {
|
|
@@ -94,9 +100,9 @@ describe('DefaultCapacityValidator', () => {
|
|
|
94
100
|
|
|
95
101
|
it('width: existing 5 + pending 3 = 8 passes exactly at the limit', async () => {
|
|
96
102
|
const store = new InMemorySessionStore()
|
|
97
|
-
const { project, root } = await seedProject(store)
|
|
103
|
+
const { project, thread, root } = await seedProject(store)
|
|
98
104
|
for (let i = 0; i < 5; i++) {
|
|
99
|
-
await spawnChild(store, root.id, project.id)
|
|
105
|
+
await spawnChild(store, root.id, project.id, thread.id)
|
|
100
106
|
}
|
|
101
107
|
const validator = new DefaultCapacityValidator(store)
|
|
102
108
|
await expect(validator.validateWidth(root.id, 3, 8, tenant)).resolves.toBeUndefined()
|
|
@@ -104,9 +110,9 @@ describe('DefaultCapacityValidator', () => {
|
|
|
104
110
|
|
|
105
111
|
it('width: existing 6 + pending 3 = 9 exceeds 8, throws dimension=width', async () => {
|
|
106
112
|
const store = new InMemorySessionStore()
|
|
107
|
-
const { project, root } = await seedProject(store)
|
|
113
|
+
const { project, thread, root } = await seedProject(store)
|
|
108
114
|
for (let i = 0; i < 6; i++) {
|
|
109
|
-
await spawnChild(store, root.id, project.id)
|
|
115
|
+
await spawnChild(store, root.id, project.id, thread.id)
|
|
110
116
|
}
|
|
111
117
|
const validator = new DefaultCapacityValidator(store)
|
|
112
118
|
try {
|