|
@@ -79,15 +79,15 @@ function message(overrides: Partial<TeamMessageSnapshot> = {}): TeamMessageSnaps
|
|
|
describe('Agent Teams projection events', () => {
|
|
describe('Agent Teams projection events', () => {
|
|
|
it('projects current-team records independently from inherited records', () => {
|
|
it('projects current-team records independently from inherited records', () => {
|
|
|
const records: SessionEvent[] = [
|
|
const records: SessionEvent[] = [
|
|
|
- event('team/member', { version: 1, teamId: TeamId('ancestor'), member: member() }, SessionSeq(0)),
|
|
|
|
|
- event('team/member', { version: 1, teamId: TEAM, member: member() }, SessionSeq(1)),
|
|
|
|
|
|
|
+ event('team/member', { version: 2, teamId: TeamId('ancestor'), member: member() }, SessionSeq(0)),
|
|
|
|
|
+ event('team/member', { version: 2, teamId: TEAM, member: member() }, SessionSeq(1)),
|
|
|
event('team/member', {
|
|
event('team/member', {
|
|
|
- version: 1,
|
|
|
|
|
|
|
+ version: 2,
|
|
|
teamId: TEAM,
|
|
teamId: TEAM,
|
|
|
member: member({ phase: 'active' }),
|
|
member: member({ phase: 'active' }),
|
|
|
}, SessionSeq(2)),
|
|
}, SessionSeq(2)),
|
|
|
- event('team/task', { version: 1, teamId: TEAM, task: task({ id: TeamTaskId('task-7') }) }, SessionSeq(3)),
|
|
|
|
|
- event('team/message/queued', { version: 1, teamId: TEAM, message: message() }, SessionSeq(4)),
|
|
|
|
|
|
|
+ event('team/task', { version: 2, teamId: TEAM, task: task({ id: TeamTaskId('task-7') }) }, SessionSeq(3)),
|
|
|
|
|
+ event('team/message/queued', { version: 2, teamId: TEAM, message: message() }, SessionSeq(4)),
|
|
|
]
|
|
]
|
|
|
const projected = project(ROOT, records)
|
|
const projected = project(ROOT, records)
|
|
|
const state = teamState(projected)
|
|
const state = teamState(projected)
|
|
@@ -103,53 +103,53 @@ describe('Agent Teams projection events', () => {
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
it('enforces teammate identity and lifecycle', () => {
|
|
it('enforces teammate identity and lifecycle', () => {
|
|
|
- const base = event('team/member', { version: 1, teamId: TEAM, member: member() }, SessionSeq(0))
|
|
|
|
|
|
|
+ const base = event('team/member', { version: 2, teamId: TEAM, member: member() }, SessionSeq(0))
|
|
|
expect(() => projectTeam(ROOT, [event('team/member', {
|
|
expect(() => projectTeam(ROOT, [event('team/member', {
|
|
|
- version: 1,
|
|
|
|
|
|
|
+ version: 2,
|
|
|
teamId: TEAM,
|
|
teamId: TEAM,
|
|
|
member: member({ phase: 'active' }),
|
|
member: member({ phase: 'active' }),
|
|
|
}, SessionSeq(0))])).toThrow(/must begin provisioning/)
|
|
}, SessionSeq(0))])).toThrow(/must begin provisioning/)
|
|
|
expect(() => projectTeam(ROOT, [base, event('team/member', {
|
|
expect(() => projectTeam(ROOT, [base, event('team/member', {
|
|
|
- version: 1,
|
|
|
|
|
|
|
+ version: 2,
|
|
|
teamId: TEAM,
|
|
teamId: TEAM,
|
|
|
member: member({ name: 'renamed', phase: 'active' }),
|
|
member: member({ name: 'renamed', phase: 'active' }),
|
|
|
}, SessionSeq(1))])).toThrow(/immutable identity/)
|
|
}, SessionSeq(1))])).toThrow(/immutable identity/)
|
|
|
expect(() => projectTeam(ROOT, [base, event('team/member', {
|
|
expect(() => projectTeam(ROOT, [base, event('team/member', {
|
|
|
- version: 1,
|
|
|
|
|
|
|
+ version: 2,
|
|
|
teamId: TEAM,
|
|
teamId: TEAM,
|
|
|
member: member({ phase: 'active' }),
|
|
member: member({ phase: 'active' }),
|
|
|
}, SessionSeq(1)), event('team/member', {
|
|
}, SessionSeq(1)), event('team/member', {
|
|
|
- version: 1,
|
|
|
|
|
|
|
+ version: 2,
|
|
|
teamId: TEAM,
|
|
teamId: TEAM,
|
|
|
member: member({ phase: 'failed' }),
|
|
member: member({ phase: 'failed' }),
|
|
|
}, SessionSeq(2))])).toThrow(/invalid active -> failed/)
|
|
}, SessionSeq(2))])).toThrow(/invalid active -> failed/)
|
|
|
|
|
|
|
|
const duplicateName = member({ id: SessionId('child-b') })
|
|
const duplicateName = member({ id: SessionId('child-b') })
|
|
|
expect(() => projectTeam(ROOT, [base, event('team/member', {
|
|
expect(() => projectTeam(ROOT, [base, event('team/member', {
|
|
|
- version: 1,
|
|
|
|
|
|
|
+ version: 2,
|
|
|
teamId: TEAM,
|
|
teamId: TEAM,
|
|
|
member: duplicateName,
|
|
member: duplicateName,
|
|
|
}, SessionSeq(1))])).toThrow(/name .* reused/)
|
|
}, SessionSeq(1))])).toThrow(/name .* reused/)
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
it('enforces task revision continuity', () => {
|
|
it('enforces task revision continuity', () => {
|
|
|
- const first = event('team/task', { version: 1, teamId: TEAM, task: task() }, SessionSeq(0))
|
|
|
|
|
|
|
+ const first = event('team/task', { version: 2, teamId: TEAM, task: task() }, SessionSeq(0))
|
|
|
expect(() => projectTeam(ROOT, [event('team/task', {
|
|
expect(() => projectTeam(ROOT, [event('team/task', {
|
|
|
- version: 1,
|
|
|
|
|
|
|
+ version: 2,
|
|
|
teamId: TEAM,
|
|
teamId: TEAM,
|
|
|
task: task({ revision: 2 }),
|
|
task: task({ revision: 2 }),
|
|
|
}, SessionSeq(0))])).toThrow(/begin at revision 1/)
|
|
}, SessionSeq(0))])).toThrow(/begin at revision 1/)
|
|
|
expect(() => projectTeam(ROOT, [first, event('team/task', {
|
|
expect(() => projectTeam(ROOT, [first, event('team/task', {
|
|
|
- version: 1,
|
|
|
|
|
|
|
+ version: 2,
|
|
|
teamId: TEAM,
|
|
teamId: TEAM,
|
|
|
task: task({ revision: 3 }),
|
|
task: task({ revision: 3 }),
|
|
|
}, SessionSeq(1))])).toThrow(/revision is not contiguous/)
|
|
}, SessionSeq(1))])).toThrow(/revision is not contiguous/)
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
it('rejects every invalid persisted task dependency relation', () => {
|
|
it('rejects every invalid persisted task dependency relation', () => {
|
|
|
- const first = event('team/task', { version: 1, teamId: TEAM, task: task() }, SessionSeq(0))
|
|
|
|
|
|
|
+ const first = event('team/task', { version: 2, teamId: TEAM, task: task() }, SessionSeq(0))
|
|
|
const second = event('team/task', {
|
|
const second = event('team/task', {
|
|
|
- version: 1,
|
|
|
|
|
|
|
+ version: 2,
|
|
|
teamId: TEAM,
|
|
teamId: TEAM,
|
|
|
task: task({
|
|
task: task({
|
|
|
id: TeamTaskId('task-2'),
|
|
id: TeamTaskId('task-2'),
|
|
@@ -159,7 +159,7 @@ describe('Agent Teams projection events', () => {
|
|
|
const invalid: Array<{ records: SessionEvent[]; message: RegExp }> = [
|
|
const invalid: Array<{ records: SessionEvent[]; message: RegExp }> = [
|
|
|
{
|
|
{
|
|
|
records: [event('team/task', {
|
|
records: [event('team/task', {
|
|
|
- version: 1,
|
|
|
|
|
|
|
+ version: 2,
|
|
|
teamId: TEAM,
|
|
teamId: TEAM,
|
|
|
task: task({ blockedBy: [TeamTaskId('missing')] }),
|
|
task: task({ blockedBy: [TeamTaskId('missing')] }),
|
|
|
}, SessionSeq(0))],
|
|
}, SessionSeq(0))],
|
|
@@ -167,7 +167,7 @@ describe('Agent Teams projection events', () => {
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
records: [event('team/task', {
|
|
records: [event('team/task', {
|
|
|
- version: 1,
|
|
|
|
|
|
|
+ version: 2,
|
|
|
teamId: TEAM,
|
|
teamId: TEAM,
|
|
|
task: task({ blockedBy: [TeamTaskId('task-1')] }),
|
|
task: task({ blockedBy: [TeamTaskId('task-1')] }),
|
|
|
}, SessionSeq(0))],
|
|
}, SessionSeq(0))],
|
|
@@ -182,7 +182,7 @@ describe('Agent Teams projection events', () => {
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
records: [first, second, event('team/task', {
|
|
records: [first, second, event('team/task', {
|
|
|
- version: 1,
|
|
|
|
|
|
|
+ version: 2,
|
|
|
teamId: TEAM,
|
|
teamId: TEAM,
|
|
|
task: task({ revision: 2, blockedBy: [TeamTaskId('task-2')] }),
|
|
task: task({ revision: 2, blockedBy: [TeamTaskId('task-2')] }),
|
|
|
}, SessionSeq(2))],
|
|
}, SessionSeq(2))],
|
|
@@ -190,7 +190,7 @@ describe('Agent Teams projection events', () => {
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
records: [first, second, event('team/task', {
|
|
records: [first, second, event('team/task', {
|
|
|
- version: 1,
|
|
|
|
|
|
|
+ version: 2,
|
|
|
teamId: TEAM,
|
|
teamId: TEAM,
|
|
|
task: task({ revision: 2, status: 'deleted' }),
|
|
task: task({ revision: 2, status: 'deleted' }),
|
|
|
}, SessionSeq(2))],
|
|
}, SessionSeq(2))],
|
|
@@ -205,7 +205,7 @@ describe('Agent Teams projection events', () => {
|
|
|
|
|
|
|
|
it('leaves numeric allocation unchanged for a branded nonstandard task id', () => {
|
|
it('leaves numeric allocation unchanged for a branded nonstandard task id', () => {
|
|
|
const state = projectTeam(ROOT, [event('team/task', {
|
|
const state = projectTeam(ROOT, [event('team/task', {
|
|
|
- version: 1,
|
|
|
|
|
|
|
+ version: 2,
|
|
|
teamId: TEAM,
|
|
teamId: TEAM,
|
|
|
task: task({ id: TeamTaskId('external-task') }),
|
|
task: task({ id: TeamTaskId('external-task') }),
|
|
|
}, SessionSeq(0))])
|
|
}, SessionSeq(0))])
|
|
@@ -214,16 +214,16 @@ describe('Agent Teams projection events', () => {
|
|
|
|
|
|
|
|
it('rejects a persisted numeric task id outside the safe integer range', () => {
|
|
it('rejects a persisted numeric task id outside the safe integer range', () => {
|
|
|
expect(() => projectTeam(ROOT, [event('team/task', {
|
|
expect(() => projectTeam(ROOT, [event('team/task', {
|
|
|
- version: 1,
|
|
|
|
|
|
|
+ version: 2,
|
|
|
teamId: TEAM,
|
|
teamId: TEAM,
|
|
|
task: task({ id: TeamTaskId('task-9007199254740992') }),
|
|
task: task({ id: TeamTaskId('task-9007199254740992') }),
|
|
|
}, SessionSeq(0))])).toThrow(/persisted Agent Teams team\/task payload is invalid/)
|
|
}, SessionSeq(0))])).toThrow(/persisted Agent Teams team\/task payload is invalid/)
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
it('enforces mailbox queue and acknowledgement relations', () => {
|
|
it('enforces mailbox queue and acknowledgement relations', () => {
|
|
|
- const queued = event('team/message/queued', { version: 1, teamId: TEAM, message: message() }, SessionSeq(0))
|
|
|
|
|
|
|
+ const queued = event('team/message/queued', { version: 2, teamId: TEAM, message: message() }, SessionSeq(0))
|
|
|
const delivered = event('team/message/delivered', {
|
|
const delivered = event('team/message/delivered', {
|
|
|
- version: 1,
|
|
|
|
|
|
|
+ version: 2,
|
|
|
teamId: TEAM,
|
|
teamId: TEAM,
|
|
|
messageId: TeamMessageId('message-1'),
|
|
messageId: TeamMessageId('message-1'),
|
|
|
targetId: CHILD,
|
|
targetId: CHILD,
|
|
@@ -241,42 +241,42 @@ describe('Agent Teams projection events', () => {
|
|
|
it('validates every current-version persisted payload before projecting it', () => {
|
|
it('validates every current-version persisted payload before projecting it', () => {
|
|
|
const malformed = [
|
|
const malformed = [
|
|
|
{
|
|
{
|
|
|
- ...event('team/member', { version: 1, teamId: TEAM, member: member() }, SessionSeq(0)),
|
|
|
|
|
- data: { version: 1, teamId: TEAM, member: { ...member(), name: 42 } },
|
|
|
|
|
|
|
+ ...event('team/member', { version: 2, teamId: TEAM, member: member() }, SessionSeq(0)),
|
|
|
|
|
+ data: { version: 2, teamId: TEAM, member: { ...member(), name: 42 } },
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
- ...event('team/task', { version: 1, teamId: TEAM, task: task() }, SessionSeq(0)),
|
|
|
|
|
- data: { version: 1, teamId: TEAM, task: { ...task(), blockedBy: [42] } },
|
|
|
|
|
|
|
+ ...event('team/task', { version: 2, teamId: TEAM, task: task() }, SessionSeq(0)),
|
|
|
|
|
+ data: { version: 2, teamId: TEAM, task: { ...task(), blockedBy: [42] } },
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
- ...event('team/message/queued', { version: 1, teamId: TEAM, message: message() }, SessionSeq(0)),
|
|
|
|
|
|
|
+ ...event('team/message/queued', { version: 2, teamId: TEAM, message: message() }, SessionSeq(0)),
|
|
|
data: {
|
|
data: {
|
|
|
- version: 1,
|
|
|
|
|
|
|
+ version: 2,
|
|
|
teamId: TEAM,
|
|
teamId: TEAM,
|
|
|
message: { ...message(), content: [{ type: 'text', text: 42 }] },
|
|
message: { ...message(), content: [{ type: 'text', text: 42 }] },
|
|
|
},
|
|
},
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
...event('team/message/delivered', {
|
|
...event('team/message/delivered', {
|
|
|
- version: 1,
|
|
|
|
|
|
|
+ version: 2,
|
|
|
teamId: TEAM,
|
|
teamId: TEAM,
|
|
|
messageId: TeamMessageId('message-1'),
|
|
messageId: TeamMessageId('message-1'),
|
|
|
targetId: CHILD,
|
|
targetId: CHILD,
|
|
|
}, SessionSeq(0)),
|
|
}, SessionSeq(0)),
|
|
|
data: {
|
|
data: {
|
|
|
- version: 1,
|
|
|
|
|
|
|
+ version: 2,
|
|
|
teamId: TEAM,
|
|
teamId: TEAM,
|
|
|
messageId: TeamMessageId('message-1'),
|
|
messageId: TeamMessageId('message-1'),
|
|
|
targetId: 42,
|
|
targetId: 42,
|
|
|
},
|
|
},
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
- ...event('team/member', { version: 1, teamId: TEAM, member: member() }, SessionSeq(0)),
|
|
|
|
|
- data: { version: 1, teamId: TEAM, member: member(), unexpected: true },
|
|
|
|
|
|
|
+ ...event('team/member', { version: 2, teamId: TEAM, member: member() }, SessionSeq(0)),
|
|
|
|
|
+ data: { version: 2, teamId: TEAM, member: member(), unexpected: true },
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
- ...event('team/task', { version: 1, teamId: TEAM, task: task() }, SessionSeq(0)),
|
|
|
|
|
- data: { version: 1, teamId: 42, task: task() },
|
|
|
|
|
|
|
+ ...event('team/task', { version: 2, teamId: TEAM, task: task() }, SessionSeq(0)),
|
|
|
|
|
+ data: { version: 2, teamId: 42, task: task() },
|
|
|
},
|
|
},
|
|
|
] as unknown as SessionEvent[]
|
|
] as unknown as SessionEvent[]
|
|
|
|
|
|
|
@@ -289,7 +289,7 @@ describe('Agent Teams projection events', () => {
|
|
|
it('retains merge-extensible content blocks while rejecting malformed core variants', () => {
|
|
it('retains merge-extensible content blocks while rejecting malformed core variants', () => {
|
|
|
const extension = { type: 'plugin/custom', payload: { value: 1 } } as never
|
|
const extension = { type: 'plugin/custom', payload: { value: 1 } } as never
|
|
|
const state = projectTeam(ROOT, [event('team/message/queued', {
|
|
const state = projectTeam(ROOT, [event('team/message/queued', {
|
|
|
- version: 1,
|
|
|
|
|
|
|
+ version: 2,
|
|
|
teamId: TEAM,
|
|
teamId: TEAM,
|
|
|
message: message({ content: [extension] }),
|
|
message: message({ content: [extension] }),
|
|
|
}, SessionSeq(0))])
|
|
}, SessionSeq(0))])
|
|
@@ -298,23 +298,23 @@ describe('Agent Teams projection events', () => {
|
|
|
|
|
|
|
|
it('records unsupported event versions without applying them', () => {
|
|
it('records unsupported event versions without applying them', () => {
|
|
|
const invalid = event('team/task', {
|
|
const invalid = event('team/task', {
|
|
|
- version: 2 as 1,
|
|
|
|
|
|
|
+ version: 1 as 2,
|
|
|
teamId: TEAM,
|
|
teamId: TEAM,
|
|
|
task: task(),
|
|
task: task(),
|
|
|
}, SessionSeq(0))
|
|
}, SessionSeq(0))
|
|
|
const later = event('team/task', {
|
|
const later = event('team/task', {
|
|
|
- version: 1,
|
|
|
|
|
|
|
+ version: 2,
|
|
|
teamId: TEAM,
|
|
teamId: TEAM,
|
|
|
task: task(),
|
|
task: task(),
|
|
|
}, SessionSeq(1))
|
|
}, SessionSeq(1))
|
|
|
const state = project(ROOT, [invalid, later])
|
|
const state = project(ROOT, [invalid, later])
|
|
|
- expect(state.failure).toMatch(/unsupported Agent Teams event version 2/)
|
|
|
|
|
|
|
+ expect(state.failure).toMatch(/unsupported Agent Teams event version 1/)
|
|
|
expect(isEmptyState(state)).toBe(true)
|
|
expect(isEmptyState(state)).toBe(true)
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
it('isolates unsupported inherited Team records from the current Team', () => {
|
|
it('isolates unsupported inherited Team records from the current Team', () => {
|
|
|
const inherited = event('team/task', {
|
|
const inherited = event('team/task', {
|
|
|
- version: 2 as 1,
|
|
|
|
|
|
|
+ version: 1 as 2,
|
|
|
teamId: TeamId('ancestor'),
|
|
teamId: TeamId('ancestor'),
|
|
|
task: task(),
|
|
task: task(),
|
|
|
}, SessionSeq(0))
|
|
}, SessionSeq(0))
|
|
@@ -326,12 +326,12 @@ describe('Agent Teams projection events', () => {
|
|
|
it('ignores malformed current-version records inherited from another Team', () => {
|
|
it('ignores malformed current-version records inherited from another Team', () => {
|
|
|
const inherited = {
|
|
const inherited = {
|
|
|
...event('team/task', {
|
|
...event('team/task', {
|
|
|
- version: 1,
|
|
|
|
|
|
|
+ version: 2,
|
|
|
teamId: TeamId('ancestor'),
|
|
teamId: TeamId('ancestor'),
|
|
|
task: task(),
|
|
task: task(),
|
|
|
}, SessionSeq(0)),
|
|
}, SessionSeq(0)),
|
|
|
data: {
|
|
data: {
|
|
|
- version: 1,
|
|
|
|
|
|
|
+ version: 2,
|
|
|
teamId: TeamId('ancestor'),
|
|
teamId: TeamId('ancestor'),
|
|
|
task: { ...task(), subject: 42 },
|
|
task: { ...task(), subject: 42 },
|
|
|
},
|
|
},
|