Context
Live substrate evidence (2026-05-03) shows @neo-gemini-3-1-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:
- 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.
- 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.
- Test coverage: spec asserts both directions — canonical accepted, non-canonical rejected with explicit error message identifying the allowed values.
Acceptance Criteria
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-4-7 vs @neo-opus-4-7) — 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")
Context
Live substrate evidence (2026-05-03) shows
@neo-gemini-3-1-prohas two activeWAKE_SUBSCRIPTIONrows that differ only inharnessTargetMetadata.appNamecasing: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 seededAgentIdentityand 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.mjslogsappNameverbatim and dispatches viaosascriptto 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_MEMORYvsMEMORYlabel drift) and #10623 Cycle 1 (\$.typevs\$.labelquery 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
subscribe/updateactions ofmanage_wake_subscriptionMCP tool).mcp__neo-mjs-memory-core__manage_wake_subscriptionacceptsharnessTargetMetadata.appNameas a free-form string; no validation exists.bridge-daemon.mjsreads the appName field and passes it directly toosascript -e 'tell application \"<appName>\"'— verbatim.AgentIdentityregistry / harness registry validation gate exists at sub-creation time.The Fix
Enforce canonical-form validation at subscription creation/update boundary:
manage_wake_subscriptionactionsubscribe/updateMUST validateharnessTargetMetadata.appNameagainst 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.WAKE_SUB:b9211b22-f4e6-4405-9203-c33d8bdc4281(appName: "antigravity") MUST be retired/unsubscribed. Operator-driven cleanup; not auto-run as part of validation rollout.Acceptance Criteria
manage_wake_subscriptionsubscribe/updateMCP tool rejects non-canonicalappNamevalues with explicit errorWAKE_SUB:b9211b22-...retired (post-merge operator step, NOT auto-migration)Out of Scope
checkSunsetted.mjsacceptingneo-opus-4-7vs@neo-opus-4-7) — that's a separate substrate hardening lane@antigravityinvented-graph-identity issue surfacing in PR #10621 — that's a separate concern in Gemini's lane; this ticket addresses the WAKE_SUBSCRIPTION layer specificallyAvoided Traps
\$.typefallback / 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.Related
@antigravityinvented identity is the same case-handling defect manifesting in #10621's normalization claimOrigin 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")