Skip to content

Reload adoption covers render fingerprints only: a delivery reminder's turn is not restored, so the same boundary can repeat it #267

Closed

Description

@nanami-0713

Version

0683e5f (main, after #262). Cross-checked against open PR #264 — its diff does not touch the adoption branch or the delivery ledger, so this applies on top of it as well.

Summary

The user/message adoption branch exists because the host re-observes our own correction messages after a plugin reload (src/plugin/fence-feedback.ts:466-467):

Our own correction (re-observed after a plugin reload): adopt its render-failure fingerprints so a second boundary cannot repeat it.

That promise holds for kind=render corrections (hex fingerprints → markersIn → correctedSpec dedupes at :279), but not for kind=delivery reminders:

  1. missingBodyCorrectionText (:302) writes the synthetic marker [genui-fence-repair #turn-<N>]. markersIn extracts the literal string turn-7, which can never equal a fence fingerprint — so the adoption branch (:469-472) adds dead entries to correctedSpec and nothing else.
  2. deliveryRemindedTurns is Set<number> (:221), and no code path parses turn-<N> back into it; correctionsTurn/correctionsThisTurn are not restored either. After a mid-turn reload the ledgers restart at zero.

Consequence

The reload+replay premise is the adoption branch's own: when the host replays the turn's events, a replayed validate_dsh_ui tool/call re-sets validatedThisTurn (:438) and replayed render results re-set deliveredThisTurn. If the turn still has no delivery, the next boundary re-sends the same nothing-delivered reminder for the same turn — consuming the second, reserved correction slot with a duplicate (the new state counts it as attempt 1). Bounded by MAX_CORRECTIONS_PER_TURN = 2, so no storm; but it is exactly the repeat the adoption comment says cannot happen, and the reserved slot from #263 (Gap A) is what absorbs it.

The unit suite pins only the render side (tests/fence-feedback.spec.ts:503-518 "so a reload cannot repeat it").

Repro sketch on the repo harness

// twin of the existing adoption test, delivery flavor — currently steers:
h.emitSession({ type: 'user/message', data: {
  content: [{ type: 'text', text: missingBodyCorrectionText(7) }],
  source: { kind: FEEDBACK_SOURCE_KIND, form: 'notice', summary: 'x' },
}})
h.emitSession({ type: 'tool/call', data: { name: 'validate_dsh_ui' } })  // replayed validate
h.boundary({ turn: 7, signal: new AbortController().signal })
expect(h.steer).not.toHaveBeenCalled()   // ← fails today: duplicate reminder, attempt counted as 1

Suggested direction

In the adoption branch, additionally recognize delivery reminders — parse #turn-(\d+) (or the status=nothing_delivered marker body) and:

  • add the parsed turn number to deliveryRemindedTurns (restores silence), and
  • restore correctionsTurn = N / correctionsThisTurn = 1 (budget symmetry, so the reload doesn't grant an extra slot).

correctedSpec should then skip the turn-N string, keeping the render ledger clean.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions