---
id: 10624
title: 'Wake subscription canonicalization: one canonical appName per identity, retire stale duplicate'
state: CLOSED
labels:
  - bug
  - ai
  - regression
  - architecture
assignees:
  - neo-opus-ada
createdAt: '2026-05-03T10:34:08Z'
updatedAt: '2026-06-07T17:23:22Z'
githubUrl: 'https://github.com/neomjs/neo/issues/10624'
author: neo-opus-ada
commentsCount: 0
parentIssue: 10601
subIssues: []
subIssuesCompleted: 0
subIssuesTotal: 0
blockedBy: []
blocking:
  - '[x] 10625 All-agent-idle detection at heartbeat layer (substrate primitive for trio liveness)'
closedAt: '2026-05-03T11:15:18Z'
---
# Wake subscription canonicalization: one canonical appName per identity, retire stale duplicate

## Context

Live substrate evidence (2026-05-03) shows `@neo-gemini-pro` has two active `WAKE_SUBSCRIPTION` rows that differ only in `harnessTargetMetadata.appName` casing:

- `WAKE_SUB:2ac01429-f3e7-4396-9198-780e3b7db1c1` — `appName: \"Antigravity\"` (created 2026-04-29T21:37:27Z)
- `WAKE_SUB:b9211b22-f4e6-4405-9203-c33d8bdc4281` — `appName: \"antigravity\"` (created 2026-05-02T23:24:58Z, recent)

The same case-handling inconsistency is now manifesting in PR #10621's normalization patch as `agentIdentity: '@antigravity'` — an invented graph identity that is NOT a seeded `AgentIdentity` and conflicts with canonical wake-route direction (per @neo-gpt's Cycle 3 review).

This is the root substrate gap that produced @tobiu's empirical observation of duplicate wakes for Gemini.

## The Problem

`bridge-daemon.mjs` logs `appName` verbatim and dispatches via `osascript` to that exact app name. macOS app names are typically case-sensitive — \"Antigravity\" matches the actual app, \"antigravity\" either falls through silently or matches a different app.

When the subscription-creation/update path silently accepts both case forms as distinct entries, downstream consumers (bridge-daemon, MailboxService dispatch) split the wake delivery into two parallel routes. The agent appears to receive duplicate wakes, AND one of the two routes may be silently dropping (operationally indistinguishable from \"working\" until empirical inspection).

This is a substrate-truth defect of the same family as #10619 Cycle 1 (`AGENT_MEMORY` vs `MEMORY` label drift) and #10623 Cycle 1 (`\$.type` vs `\$.label` query drift). All three are \"silently-accepted-but-wrong-form\" patterns where the substrate doesn't enforce canonical input at the write boundary.

## The Architectural Reality

- Subscription writes flow through the wake-subscription service path (specifically the `subscribe` / `update` actions of `manage_wake_subscription` MCP tool).
- The MCP tool descriptor at `mcp__neo-mjs-memory-core__manage_wake_subscription` accepts `harnessTargetMetadata.appName` as a free-form string; no validation exists.
- `bridge-daemon.mjs` reads the appName field and passes it directly to `osascript -e 'tell application \"<appName>\"'` — verbatim.
- No seeded `AgentIdentity` registry / harness registry validation gate exists at sub-creation time.

## The Fix

Enforce canonical-form validation at subscription creation/update boundary:

1. **Validation:** `manage_wake_subscription` action `subscribe` / `update` MUST validate `harnessTargetMetadata.appName` against an explicit allow-list (currently: `\"Antigravity\"`, `\"Claude\"`; future identities added as harness registry expands). Lower-case or otherwise non-canonical values MUST throw at the MCP-tool boundary, not silently persist.
2. **Migration / cleanup:** existing duplicate row `WAKE_SUB:b9211b22-f4e6-4405-9203-c33d8bdc4281` (appName: \"antigravity\") MUST be retired/unsubscribed. Operator-driven cleanup; not auto-run as part of validation rollout.
3. **Test coverage:** spec asserts both directions — canonical accepted, non-canonical rejected with explicit error message identifying the allowed values.

## Acceptance Criteria

- [ ] `manage_wake_subscription` `subscribe` / `update` MCP tool rejects non-canonical `appName` values with explicit error
- [ ] Spec test: positive (canonical accepted) + negative (lowercase rejected with helpful error)
- [ ] Stale `WAKE_SUB:b9211b22-...` retired (post-merge operator step, NOT auto-migration)
- [ ] PR body cites empirical anchor (Gemini duplicate-wake observation) as ground-truth proof of fix necessity

## Out of Scope

- Migration sweep over all existing subscriptions (only the one known stale row is in scope; bulk migration is a separate concern if more drift is discovered)
- Identity-input normalization elsewhere (e.g., `checkSunsetted.mjs` accepting `neo-opus-ada` vs `@neo-opus-ada`) — that's a separate substrate hardening lane
- The `@antigravity` invented-graph-identity issue surfacing in PR #10621 — that's a separate concern in Gemini's lane; this ticket addresses the WAKE_SUBSCRIPTION layer specifically

## Avoided Traps

- ❌ **Auto-normalize via lowercasing** — silently rewriting input is a discoverability nightmare. Throw instead so the caller knows their input was wrong.
- ❌ **Add `\$.type` fallback / accept both** (parallel to the trap from PR #10623 review) — silently accepting wrong forms is the failure mode this ticket fixes; doing the same thing one layer up would defeat the purpose.
- ❌ **Bundle stale-row cleanup with validation rollout** — operator-coordinated cleanup is safer than autonomous deletion.

## Related

- Substrate-stack lane #5 (canonical routes) — parallel prerequisite to all-agent-idle detection (#1)
- @neo-gpt coordination note 2026-05-03 [MESSAGE:ee05f910 Antigravity vs antigravity duplicate sub]
- @neo-gpt PR #10621 Cycle 3 Request Changes — the `@antigravity` invented identity is the same case-handling defect manifesting in #10621's normalization claim

Origin Session ID: b1839431-cba1-4b6d-913f-27b09e472e67

Retrieval Hint: query_summaries(\"heartbeat liveness substrate-stack convergence trio coordination 2026-05-03\") + query_raw_memories(\"Antigravity antigravity duplicate wake subscription canonicalization\")

## Timeline

- 2026-05-03T10:34:09Z @neo-opus-ada added the `bug` label
- 2026-05-03T10:34:09Z @neo-opus-ada added the `ai` label
- 2026-05-03T10:34:09Z @neo-opus-ada added the `regression` label
- 2026-05-03T10:34:09Z @neo-opus-ada added the `architecture` label
- 2026-05-03T10:35:00Z @neo-opus-ada cross-referenced by #10625
- 2026-05-03T10:35:37Z @neo-opus-ada cross-referenced by #10626
- 2026-05-03T10:36:29Z @neo-opus-ada cross-referenced by #10627
- 2026-05-03T10:37:58Z @neo-opus-ada marked this issue as blocking #10625
- 2026-05-03T10:38:12Z @neo-opus-ada added parent issue #10601
- 2026-05-03T11:08:25Z @neo-gemini-pro cross-referenced by PR #10628
- 2026-05-03T11:15:19Z @tobiu referenced in commit `f329177` - "feat(memory-core): enforce canonical appName for wake subscriptions (#10624) (#10628)

Co-authored-by: tobiu <tobiasuhlig78@gmail.com>"
- 2026-05-03T11:15:19Z @tobiu closed this issue
- 2026-05-03T13:13:21Z @neo-opus-ada cross-referenced by #10636
- 2026-05-03T13:17:10Z @neo-opus-ada cross-referenced by PR #10637
- 2026-05-03T14:31:02Z @tobiu referenced in commit `b2071d5` - "fix(memory-core): include Codex in wake subscription canonical appNames (#10636) (#10637)

PR #10628 (closing #10624) introduced validAppNames = ['Antigravity', 'Claude'] and the deferral marker "future identities added as harness registry expands". Codex was deferred at write-time, propagated as oversight through implementation, and now blocks @neo-gpt's auto-bootstrap path: the MCP server restart triggers WakeSubscriptionService.bootstrap, which writes the canonical AgentIdentity.subscriptionTemplate, which has appName: 'Codex' — and validateMetadata throws "Invalid appName 'Codex'". Codex sub never created; @neo-gpt strands from Shape C wake delivery.

Empirical anchor: 3 osascript exit-1 failures in bridge-daemon log at 12:58:14 / 12:58:17 / 12:59:35 (PID 43092) trace to specific A2A messages between trio members; @neo-gpt cross-family memory anchor MESSAGE:6b8c7086 independently confirmed the regression class (TCC keystroke denial + Codex bootstrap rejection).

Trio canonicalization confirmed elsewhere: ai/scripts/checkAllAgentIdle.mjs:18 hardcodes NEO_TRIO_IDENTITIES with @neo-gpt explicitly.

Spec coverage extended symmetrically: 3 positive cases (Antigravity / Claude / Codex) + updated negative case error-message expectation. All 33 spec tests pass.

Co-authored-by: tobiu <tobiasuhlig78@gmail.com>"
- 2026-05-04T20:48:53Z @neo-gpt cross-referenced by #10717
- 2026-05-04T21:10:15Z @neo-gpt cross-referenced by PR #10719
- 2026-05-09T21:23:51Z @neo-gemini-pro assigned to @neo-opus-ada
- 2026-05-11T05:07:13Z @neo-opus-ada cross-referenced by #11182
- 2026-05-11T05:26:23Z @neo-gpt cross-referenced by PR #11183

