LearnNewsExamplesServices
Frontmatter
id16243
titleJoined boundedRetryGate waiters share nested delivery values
stateClosed
labels
bugai
assigneesneo-kimi-iris
createdAtAug 1, 2026, 1:54 AM
updatedAtAug 1, 2026, 2:29 AM
githubUrlhttps://github.com/neomjs/neo/issues/16243
authorneo-kimi-iris
commentsCount0
parentIssue16222
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[ ] 16224 TenantRepoSync suppression never retries: green sweeps over a permanently starved Knowledge Base, [ ] 16223 miniSummary backfill retries silently-empty generations forever, burning the provider for nothing
closedAtAug 1, 2026, 2:29 AM

Joined boundedRetryGate waiters share nested delivery values

neo-kimi-iris
neo-kimi-iris commented on Aug 1, 2026, 1:54 AM

Context

The A+FU terminal review of PR #16239 (pullrequestreview-4832880658) named one non-blocking follow-up at merged head 1383ebbee3: "fanout() clones only the top-level annotation. Two joined callers therefore share nested delivery values: mutating waiter A changed waiter B, while the cache stayed pristine." The review's explicit guidance: "clone per waiter or narrow the stronger 'any other delivered copy' claim before Related: #16223 and #16224 adopt the family primitive" — the hardening transfers to the next family-adoption lane; it is not deferred #16222 correctness.

The Problem

boundedRetryGate.mjs fanout() delivers each waiter {...annotated, gate: {...}} — a top-level spread. Nested objects/arrays inside the result (e.g. detail, tags) are shared BY REFERENCE across every joined waiter's delivered copy. Two joined callers can therefore mutate each other's received truth (the reviewer's joined-waiter probe: mutating waiter A's nested value changed waiter B's). The gate's internal cache is NOT affected (closed separately — annotate/settle-cache/snapshot all deep-clone), so this is a caller-to-caller alias, invisible while the only production consumer (HealthService) ignores scheduled deliveries. The moment a family consumer (#16223 miniSummary backfill, #16224 TenantRepoSync) consumes tick()/runNow() results, joined callers share mutable state.

The Architectural Reality

ai/services/shared/boundedRetryGate.mjs:

  • annotate() deep-clones the result body (structuredClone) when building the shared annotated result — ONCE per settle/serve.
  • fanout(annotated, waiter) then spreads that ONE annotated object per waiter — the per-waiter gate fields (coalesced, demandedKey, demandedGenId) are fresh, but the result body's NESTED values still alias the single annotated object.

The isolation boundary ends one level too early: per-waiter metadata is isolated, per-waiter nested result state is not.

The Fix

In fanout(), structuredClone the annotated result per waiter before attaching the per-waiter gate fields — a waiter can never observe another waiter's mutations. Deliveries are small (health-result-sized); the per-waiter clone cost is negligible and bounded by the waiter count.

Alternative considered and rejected: narrowing the JSDoc isolation claim instead of hardening — leaves the trap armed for exactly the consumers the primitive exists to serve (the family adoption lanes).

Contract Ledger

Target Surface Source of Authority Proposed Behavior Fallback Docs Evidence
boundedRetryGate delivery results (tick() / runNow() resolutions) ai/services/shared/boundedRetryGate.mjs fanout() Every waiter's delivered result is an independent deep copy; mutating one waiter's nested value never changes another waiter's none — isolation is unconditional module JSDoc names per-waiter delivery copies explicitly joined-waiter nested-mutation spec (below)

Decision Record impact

none — no ADR governs the retry gate; ADR-0019 unaffected (no config surface touched).

Acceptance Criteria

  • Two joined waiters (same generation) receive delivery results whose nested objects/arrays are independent copies: mutating waiter A's nested value leaves waiter B's pristine (new spec, the exact shape of the review's joined-waiter probe).
  • The gate cache remains pristine under the same probe (existing isolation specs stay green).
  • boundedRetryGate.mjs JSDoc names the per-waiter delivery-copy guarantee explicitly.
  • Full test/playwright/unit/ai/services/shared/ + memory-core/ suites green at the landing head; no public contract shape change (isolation only).

Out of Scope

  • The family adoption itself — #16223 and #16224 adopt the gate in their own lanes; this ticket only discharges the precondition the A+FU review named.
  • Any API/annotation shape change (keys, fields, vocabulary).

Related

Source: #16222 (closed by PR #16239) · the A+FU review pullrequestreview-4832880658 · adoption lanes #16223 / #16224 (blocked-by this hardening per the review's guidance) · merged head 1383ebbee3.

Live latest-open sweep: checked latest 20 open issues at 2026-07-31T23:55Z; no equivalent found. A2A in-flight sweep (last 30 messages, all read-states): no competing claim on this scope. KB semantic sweep: unavailable (collection mid-rebuild, count 0) — substituted live GitHub + grep over resources/content/**.

Origin Session ID: 05b5fdc9-1f2b-4b45-a2c9-4b64ed5f15cd

Retrieval Hint: query_raw_memories("boundedRetryGate fanout joined waiter nested alias structuredClone A+FU follow-up")

tobiu referenced in commit 12b3e80 - "fix(ai): isolate joined-waiter delivery copies in boundedRetryGate (#16243) (#16245)" on Aug 1, 2026, 2:29 AM
tobiu closed this issue on Aug 1, 2026, 2:29 AM