Context
Surfaced 2026-05-08 during PR #10933 (Phase 3 unit-row re-add) CI run 25524203756. Originally classified as G5#3 in the #10924 G5 triage matrix. Triage hypothesis was "auto-resolve post-G4-merge"; falsified empirically by the unit-row first activation in CI.
Skip-guarded in PR #10933 commit 8e6c3fd78 per the established pattern; this ticket tracks the proper investigation + fix.
The Problem
Test test/playwright/unit/ai/mcp/server/memory-core/services/PermissionService.spec.mjs:165 flakes with:
expect(node.type).toBe('BroadcastSentinel');
Expected: "BroadcastSentinel"
Received: undefined
The test pre-seeds AGENT:* as a BroadcastSentinel via GraphService.upsertNode, then asserts the type survives grantPermission. Empirically node.type is undefined — meaning either:
- A sibling spec re-upserted
AGENT:* with a different shape (no type field), OR
- The singleton
GraphService was re-initialized between the seed and the read, dropping the in-memory state
workers:1 substrate amplifies cross-pollution. Per grep, 4 specs touch AGENT:*:
test/playwright/unit/ai/mcp/server/memory-core/services/MailboxService.spec.mjs
test/playwright/unit/ai/mcp/server/memory-core/services/WriteSideInvariant.spec.mjs
test/playwright/unit/ai/mcp/server/memory-core/services/GraphService.spec.mjs
test/playwright/unit/ai/mcp/server/memory-core/services/PermissionService.spec.mjs (this one)
Whichever sibling runs first and writes AGENT:* without a type field clobbers the BroadcastSentinel shape.
The Architectural Reality
test/playwright/unit/ai/mcp/server/memory-core/services/PermissionService.spec.mjs:165-181 — the flaky test
ai/mcp/server/memory-core/services/GraphService.mjs#upsertNode — last-writer-wins on the id key; updates fields per the upsert payload
- 4 spec-files all write
AGENT:* to the same singleton GraphService
workers:1 serializes execution → cumulative state pollution
The Fix (TBD via investigation)
Two candidate paths:
- Spec-level: each spec uses a UNIQUE
AGENT:* analog (e.g., AGENT:*permission-test, AGENT:*mailbox-test) so writes don't collide on the same node ID
- Symmetric beforeEach+afterEach: each spec resets
AGENT:* to a known-clean shape at the start AND end of each test (per feedback_symmetric_spec_cleanup pattern from earlier session)
Investigation needed to determine which sibling clobbers first + which fix shape minimizes blast radius.
Acceptance Criteria
Out of Scope
- Refactoring GraphService.upsertNode to require explicit type field (architectural change beyond spec-isolation scope)
- Migrating GraphService to per-test instances
Avoided Traps
- Increasing
retries: 2 → 5: papers over without addressing root cause
- Skip-guard as permanent solution: applied as immediate ship-the-PR move + tracked here for proper fix
Related
- Surfacing CI run: 25524203756
- Triage origin: #10924 G5 row (G5#3)
- Triage correction: #10924 comment 4401547656
- Sibling state-pollution patterns: G5#2 (KBRecorderService — singleton-data), #10934 (FileSystemIngestor — singleton SQLite-close), #10935 (TransportService residual)
- Substrate config:
test/playwright/playwright.config.unit.mjs workers: 1 in CI
- Skip-guard commit:
8e6c3fd78 on PR #10933
Origin Session ID: 7e897a0b-33ce-4d6c-b1a9-a1ff93e4e571
Retrieval Hint: query_raw_memories(query="PermissionService AGENT:* singleton cross-pollution workers 1 flake G5#3 #10924 PR 10933")
Context
Surfaced 2026-05-08 during PR #10933 (Phase 3 unit-row re-add) CI run 25524203756. Originally classified as G5#3 in the #10924 G5 triage matrix. Triage hypothesis was "auto-resolve post-G4-merge"; falsified empirically by the unit-row first activation in CI.
Skip-guarded in PR #10933 commit
8e6c3fd78per the established pattern; this ticket tracks the proper investigation + fix.The Problem
Test
test/playwright/unit/ai/mcp/server/memory-core/services/PermissionService.spec.mjs:165flakes with:expect(node.type).toBe('BroadcastSentinel'); Expected: "BroadcastSentinel" Received: undefinedThe test pre-seeds
AGENT:*as aBroadcastSentinelviaGraphService.upsertNode, then asserts the type survivesgrantPermission. Empiricallynode.typeisundefined— meaning either:AGENT:*with a different shape (notypefield), ORGraphServicewas re-initialized between the seed and the read, dropping the in-memory stateworkers:1substrate amplifies cross-pollution. Per grep, 4 specs touchAGENT:*:test/playwright/unit/ai/mcp/server/memory-core/services/MailboxService.spec.mjstest/playwright/unit/ai/mcp/server/memory-core/services/WriteSideInvariant.spec.mjstest/playwright/unit/ai/mcp/server/memory-core/services/GraphService.spec.mjstest/playwright/unit/ai/mcp/server/memory-core/services/PermissionService.spec.mjs(this one)Whichever sibling runs first and writes
AGENT:*without atypefield clobbers the BroadcastSentinel shape.The Architectural Reality
test/playwright/unit/ai/mcp/server/memory-core/services/PermissionService.spec.mjs:165-181— the flaky testai/mcp/server/memory-core/services/GraphService.mjs#upsertNode— last-writer-wins on theidkey; updates fields per the upsert payloadAGENT:*to the same singleton GraphServiceworkers:1serializes execution → cumulative state pollutionThe Fix (TBD via investigation)
Two candidate paths:
AGENT:*analog (e.g.,AGENT:*permission-test,AGENT:*mailbox-test) so writes don't collide on the same node IDAGENT:*to a known-clean shape at the start AND end of each test (perfeedback_symmetric_spec_cleanuppattern from earlier session)Investigation needed to determine which sibling clobbers first + which fix shape minimizes blast radius.
Acceptance Criteria
WORKERS=1AGENT:*without preservingtype8e6c3fd78npm run test-unitinvocations on CI substrateOut of Scope
Avoided Traps
retries: 2 → 5: papers over without addressing root causeRelated
test/playwright/playwright.config.unit.mjsworkers: 1in CI8e6c3fd78on PR #10933Origin Session ID:
7e897a0b-33ce-4d6c-b1a9-a1ff93e4e571Retrieval Hint:
query_raw_memories(query="PermissionService AGENT:* singleton cross-pollution workers 1 flake G5#3 #10924 PR 10933")