Skip to content

IL-015: claim/acknowledge protocol for ambient dream surfacing (+ IL-014 design) - #135

Merged
G9000 merged 24 commits into
mainfrom
worktree-il-015-dream-receipt
Aug 3, 2026
Merged

G9000 merged 24 commits into
mainfrom
worktree-il-015-dream-receipt

Conversation

@G9000

@G9000 G9000 commented Aug 1, 2026

Copy link
Copy Markdown
Owner

Closes the residual risk IL-010 accepted: a dream was marked surfaced the moment a greeting claimed it, before the response reached the browser — so a reload, tab close, or dropped connection consumed the dream without ever voicing it. That was a deliberate trade (re-offering a dream the user did see is worse), but it's a real loss, and this removes the trade instead of picking a side.

The protocol

  • dream_journal.claimed_at is a claim state distinct from surfaced. A greeting claims; only an explicit client acknowledgement surfaces. surfaced therefore keeps its meaning as the durable "never voice this again" flag every other consumer already reads. Migration 20260802_0001.
  • An unacknowledged claim expires after dream_claim_ttl_minutes (default 10) and the dream becomes offerable again. The asymmetry is deliberate: a lost ack costs one repeat after the TTL; a lost expiry costs permanent silence. And unlike the old behavior, the failure is no longer invisible.
  • dream_receipt.py owns the offerable query, acknowledge_dream (idempotent, ownership-scoped — the client acks best-effort and must never be penalised for a retry), and release_claim for the consent-withdrawn-mid-generation case, which is immediate and certain rather than expiry-based.
  • POST /chat/greeting/dream-ack + ambientDreamId on the greeting response. The client acks on display — not on fetch, not on stash — in both the fresh-fetch and one-shot paths, so a greeting that was stashed but never shown correctly lets its claim expire.

Shape mirrors IL-003's PendingInitiative deliver/ack, which solved the same problem for initiatives.

Also: IL-014 design (not implemented)

docs/superpowers/specs/2026-08-02-il-014-subthreshold-crystallization-design.md. The ticket stays backlog — its acceptance requires design sign-off before an implementation ticket is cut, so this is the design, not the code. It proposes the buffer mechanism (reusing IL-004 topic keys, soul-store, field-encrypted) and takes a position on each of the four tensions.

One product question is flagged rather than decided: should the pre-crystallization buffer be visible to the user? It's the only mechanism in Inner Life that accumulates content the user hasn't seen, and the answer changes both the schema and the consent surface.

Validation

  • Full suite: 3363 passed, 0 failed, 10 skipped
  • test_inner_life_ambient_dream.py: 28 passed — including expiry re-offering the same dream, an acknowledged dream never returning however much time passes, ack idempotence, and ownership scoping
  • Desktop: 111/111, tsc --noEmit clean
  • Migration 20260802_0001: up/down/up clean on SQLite, single head

IL-010 marked a dream surfaced the moment a greeting claimed it, before
the response reached the browser — a reload, tab close, or dropped
connection consumed the dream without ever voicing it. That was accepted
deliberately (re-offering a dream the user DID see is worse), but it is
a real loss and this removes the trade rather than picking a side.

- dream_journal.claimed_at is a claim state distinct from surfaced. A
  greeting claims; only an explicit client acknowledgement surfaces,
  which keeps surfaced as the durable 'never voice this again' flag
  every other consumer already reads. Migration 20260802_0001.
- An unacknowledged claim expires after dream_claim_ttl_minutes
  (default 10) and the dream becomes offerable again. The asymmetry is
  deliberate: a lost ack costs one repeat after the TTL, while a lost
  expiry costs permanent silence — and unlike before, the failure is no
  longer invisible.
- services/agent/inner_life/dream_receipt.py owns the offerable query,
  acknowledge (idempotent, ownership-scoped) and release (consent
  withdrawn mid-generation, which is immediate and certain rather than
  expiry-based).
- POST /chat/greeting/dream-ack plus ambientDreamId on the greeting
  response; the client acks on DISPLAY — not on fetch or stash — in both
  the fresh-fetch and one-shot paths, so a stashed-but-never-shown
  greeting correctly lets the claim expire.

Also: IL-014 design doc (docs/superpowers/specs/) — the ticket stays
backlog per its approval gate. It takes a position on all four tensions
and flags one product question that needs an answer before any code:
whether the pre-crystallization buffer is visible to the user, which
changes both the schema and the consent surface.

Suite 3363 passed / 0 failed; 28 ambient-dream tests; desktop 111/111;
migration up/down/up clean on SQLite.
@G9000

G9000 commented Aug 1, 2026

Copy link
Copy Markdown
Owner Author

@codex review — scope: P1 and genuinely important only. Correctness bugs, data loss, security/privacy leaks, concurrency races, consent/safety regressions. Please skip nitpicks: style, naming, comment wording, test-count bookkeeping, and ticket/metadata process findings. If nothing important remains, just say so.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8a35ae1b2e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/server/src/anima_server/services/agent/proactive.py
Regression introduced by this ticket: IL-015 made the greeting path
claim without surfacing, but IL-003's three dream_residue paths still
selected on surfaced alone. An initiative tick overlapping an
unacknowledged greeting could therefore generate, deliver and mark the
SAME dream — duplicate disclosure of intimate content through two
channels, which is precisely what the surfaced flag existed to prevent.

All three paths (grow signal, gather_drive_material, post-fire marking)
now use offerable_dream_query, so:
- a live claim is invisible to initiatives;
- an EXPIRED claim is re-admitted, so a greeting that never reached the
  browser doesn't cost the dream its second chance;
- the marking path sets surfaced directly and clears any claim —
  initiative delivery is confirmed by the PendingInitiative poll/ack, so
  it needs no second receipt protocol.

Two regression tests: an in-flight claim hides the dream from the
initiative path and reappears after expiry, and an acknowledged dream is
never re-voiced as an initiative. Suite 3365 passed / 0 failed.
@G9000

G9000 commented Aug 2, 2026

Copy link
Copy Markdown
Owner Author

@codex review — scope: P1 and genuinely important only. Correctness bugs, data loss, security/privacy leaks, concurrency races, consent/safety regressions. Please skip nitpicks: style, naming, comment wording, test-count bookkeeping, and ticket/metadata process findings. If nothing important remains, just say so.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 30fcd2676a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/desktop/src/pages/dashboard/Dashboard.tsx Outdated
G9000 added 2 commits August 2, 2026 13:44
While preparing to implement IL-014 (sub-threshold moment crystallization)
I found the capability already ships as IL-004, merged 2026-07-18. IL-004's
goal is verbatim the same behaviour: accumulate below-threshold candidates
as weighted latent traces per topic, synthesize a provenanced memory when
cumulative weight crosses the threshold. Admission, accumulation with decay
and cap, crystallization, soul-store scoping, vault export and
right-to-forget are all in place and tested.

IL-014 was filed from the v1.1 comparative analysis without first checking
whether the behaviour existed. Recording that rather than quietly dropping
the ticket.

The design gate's blocking question — should the pre-crystallization buffer
be visible to the user, given it accumulates content they never saw — turned
out to be moot. IL-004 stores no content: LatentTrace.evidence_refs holds
identifiers only (candidate id, source message ids, content hash), so there
is no hidden content to expose and no consent surface is needed.

One genuine refinement survives the comparison and is filed as IL-016:
IL-004 lets weight accumulate inside a single conversation, so one long
session can crystallize a "pattern" that was really a single occasion.
Requiring evidence from >=2 distinct sessions would make a crystallized
memory mean "recurring" rather than "discussed at length".

- IL-014 -> done (closed as delivered, no code written)
- IL-016 -> backlog, P3, depends on IL-004
- Design doc rewritten as the investigation record with the evidence table
- IL-000 follow-ups registry updated for both
Codex P1, and a real regression from this PR. The Dashboard stashes a
dream-bearing greeting whose fetch resolves after unmount, so the next
mount can still voice it. Under IL-010 that copy was safe indefinitely —
the dream was already consumed. Under IL-015 it is not: the claim expires
after dream_claim_ttl_minutes, the dream becomes offerable again, and an
initiative or a fresh greeting can voice it while the stale copy still
sits in sessionStorage. Returning to the Dashboard then discloses the same
intimate narrative a second time, and acknowledging it afterwards cannot
undo that.

The stash now dies with the claim behind it:

- The greeting response carries `ambientDreamExpiresAt`
  (`claimed_at + dream_claim_ttl_minutes`). The server states the deadline
  because the TTL is server configuration — the client cannot derive it.
- Each stashed entry records that deadline; every read of the queue prunes
  expired entries and persists the prune, so an expired dream is gone from
  storage rather than merely hidden.
- A response that arrives already stale is never stashed, and a mount whose
  stash expired between the peek and the take falls through to a fresh
  greeting instead of showing nothing.
- A LIVE greeting whose claim expired during a very slow request degrades to
  the dream-free `handoffMessage` rather than voicing a dream the server may
  have re-offered.
- No stated deadline (older server, lost field) falls back to a bounded
  10-minute lifetime — never an immortal stash.

Dropping an expired stash is cheap now: an unacknowledged dream is not
consumed, so the server simply offers it again.

Boundary checked in both directions: the claim still suppresses re-offering
AT the stated deadline and only becomes offerable past it, so the client
stops showing its copy no later than the server starts re-offering — never
the reverse.

_finalize_ambient_dream returns a `_VoicedDream` record instead of a
widening tuple, so the three call sites cannot swap fields.

Tests: 34 in test_inner_life_ambient_dream.py (4 new — stated expiry equals
claim + TTL, expiry boundary vs the server's own predicate, no dream / no
expiry, released claim / no expiry); 21 in greetingCache.test.ts (7 new —
held-past-deadline drop, storage prune, live sibling survives, already-stale
response refused, fallback lifetime, unparseable deadline, non-dream
greetings never expire). Desktop: 118 passed, tsc clean.
@G9000

G9000 commented Aug 2, 2026

Copy link
Copy Markdown
Owner Author

@codex review — scope: P1 and genuinely important only. Correctness bugs, data loss, security/privacy leaks, concurrency races, consent/safety regressions. Please skip nitpicks: style, naming, comment wording, test-count bookkeeping, and ticket/metadata process findings. If nothing important remains, just say so.

Since your last pass (9f7955d): stashed dream-bearing greetings now expire with the server-side claim — the response carries ambientDreamExpiresAt (claimed_at + dream_claim_ttl_minutes), the one-shot queue prunes expired entries on every read, an already-stale response is never stashed, and a live response whose claim expired mid-request degrades to the dream-free handoffMessage. _finalize_ambient_dream now returns a record rather than a widening tuple. Worth a look at the claim/expiry boundary in particular: whether the client can ever still show a copy after the server has re-offered the same dream.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9f7955d445

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/desktop/src/pages/dashboard/Dashboard.tsx Outdated
Codex P1, and right on both counts.

Round 2 had the client decide "is this dream still mine to voice?" by
comparing a server timestamp against `Date.now()`. That is check-then-act
against a clock the server does not control: a skewed device, or a render
delayed past the deadline, concludes "still mine" after the claim lapsed and
another channel took the dream — and the same intimate narrative is
disclosed twice. The acknowledgement was also unscoped, so a stale client
could mark the dream surfaced and clear a NEWER greeting's claim while that
greeting was mid-disclosure.

Both decisions are now scoped to a claim generation:

- `claimed_at` doubles as the token — every claim writes a fresh instant, so
  it names one generation. The greeting carries it as
  `ambientDreamClaimToken`.
- `POST /chat/greeting/dream-claim` re-asserts that generation atomically,
  immediately before voicing. It RENEWS the claim rather than surfacing it,
  so a client that dies between confirming and painting still loses nothing
  — which is the whole point of IL-015 and the reason confirmation cannot
  simply be an early ack.
- Any uncertain answer — no token, locally expired, refused, request failed
  — voices the dream-free copy. A missing `handoffMessage` blanks the
  greeting rather than leaking the sentence we just declined to voice.
- `acknowledge_dream` requires the token, so a superseded ack is a no-op
  that leaves the live claim untouched.

The client-side expiry stays as a cheap pre-filter and as storage hygiene
(no decrypted dream lingering in sessionStorage), but it is no longer
load-bearing for correctness.

The voicing decision moved out of the component into a pure
`voiceableGreeting(greeting, confirm)`, so the branch that actually prevents
the double disclosure is unit-tested rather than buried in an effect.

Tests: server 39 in test_inner_life_ambient_dream.py (5 new — renewal hands
back a fresh token and spends the old one; a claim the server already
re-offered is refused while the live claim survives; a stale ack neither
surfaces nor clears the newer claim; garbage/foreign/acknowledged tokens
refuse; renewal extends the unofferable window). Desktop 125 (7 new —
confirmed/refused/failed/expired/token-less/non-dream paths and the blanking
fallback). Full server suite 3371 passed, 0 failed. tsc and ruff clean.
@G9000

G9000 commented Aug 2, 2026

Copy link
Copy Markdown
Owner Author

@codex review — scope: P1 and genuinely important only. Correctness bugs, data loss, security/privacy leaks, concurrency races, consent/safety regressions. Please skip nitpicks: style, naming, comment wording, test-count bookkeeping, and ticket/metadata process findings. If nothing important remains, just say so.

Since your last pass (7808767): the client no longer decides from its own clock whether a dream is still its to voice. claimed_at is now a claim-generation token handed to the client, POST /chat/greeting/dream-claim re-asserts it atomically before voicing (renewing the claim, not surfacing it), acknowledge_dream is claim-scoped so a stale ack cannot clear a newer claim, and every uncertain answer degrades to the dream-free copy.

Worth attacking in particular: the renew-not-surface choice (does anything still consume a dream that was never displayed?), the token equality round-trip through storage, and whether any interleaving of confirm / ack / expiry / initiative can still voice the same narrative twice.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7808767947

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/server/src/anima_server/api/routes/chat.py
Comment thread apps/desktop/src/pages/dashboard/Dashboard.tsx Outdated
G9000 added 2 commits August 2, 2026 15:34
…rendered

Two Codex P1s, both real.

1. Consent, not just ownership. The confirm endpoint checked that the caller
   still held the claim, which says nothing about whether the user still
   wants dreams voiced. A claim is taken when the greeting is generated —
   for a stashed greeting, up to a whole TTL earlier — so an opt-out in that
   window was still answered with a dream. `confirm_claim` now re-reads the
   presence config under the same per-user `presence_consent_lock` the
   config PUT holds through its commit, and refuses unless the master switch
   is on and sharing is still `ambient`.

   The claim is deliberately NOT released on refusal: clearing it
   unconditionally would also clear a NEWER greeting's claim (the round-3
   bug), and an unvoiced claim lapses within the TTL anyway. Re-enabling
   ambient inside that window makes the same claim voiceable again.

2. Acknowledge only what was actually shown. The ack fired from the fetch
   handler, immediately after `setBrief`. Both dashboard surfaces that
   render a greeting (`profile`, `greeting`) can be closed by the user, and
   closed nodes are filtered out of the graph entirely — so a dream could be
   marked surfaced FOREVER while nothing on screen ever voiced it. That is
   precisely the loss IL-015 exists to prevent, reintroduced through the
   back door.

   The receipt is now reported by whichever node actually rendered the
   dream, from an effect that runs after the commit that put the text on
   screen, and deduped by claim generation (`dreamReceiptKey` =
   `dreamId:claimToken`) because both nodes report and effects re-run. A
   re-claimed dream owes a NEW receipt, so an earlier one cannot suppress a
   later disclosure. A failed ack releases the dedupe so a later render
   retries.

Tests: server 42 in test_inner_life_ambient_dream.py (3 new — opt-out
refuses confirmation and re-enabling makes the same claim voiceable again;
master switch off refuses; the consent read and the renewal both sit inside
the lock). Desktop 128 (3 new — receipt keyed by claim generation, a
re-claim owes a new receipt, nothing owed for greetings with no voiceable
dream including one stripped by a refused confirmation). Full server suite
3374 passed, 0 failed. tsc and ruff clean.
@G9000

G9000 commented Aug 2, 2026

Copy link
Copy Markdown
Owner Author

@codex review — scope: P1 and genuinely important only. Correctness bugs, data loss, security/privacy leaks, concurrency races, consent/safety regressions. Please skip nitpicks: style, naming, comment wording, test-count bookkeeping, and ticket/metadata process findings. If nothing important remains, just say so.

Since your last pass (a722564): confirmation now re-reads consent under presence_consent_lock and refuses unless ambient sharing is still on, and acknowledgement is driven by the node that actually rendered the dream (deduped by claim generation) rather than by the fetch handler.

Worth attacking: whether any path can still (a) voice a dream after consent was withdrawn, (b) consume one that was never displayed, or (c) disclose the same narrative through two channels — including interleavings of confirm / ack / expiry / initiative, and the case where a claim is left unreleased after a refused confirmation.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 37ab87163f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/desktop/src/pages/dashboard/nodes/GreetingNode.tsx Outdated
Comment thread apps/desktop/src/pages/dashboard/Dashboard.tsx Outdated
Two more Codex P1s, both client-side, both right.

1. Mounted is not seen. A React effect runs after the commit even when the
   Dashboard mounted in a background or minimized window. Closing that
   window before ever looking at it acknowledged the dream anyway — marking
   it surfaced forever without it having been displayed to anyone.

   Receipts now go through `useDreamShownReceipt`: reported immediately when
   `document.visibilityState` is "visible", deferred to the next
   `visibilitychange` otherwise, and never reported at all if the page is
   never shown. A dream that stays hidden simply lets its claim lapse and is
   offered again, which is the correct failure.

2. Deleting a dedupe entry is not a retry. The failed-ack path removed the
   receipt key and stopped, which only made a hypothetical future render
   eligible — and nothing re-renders on that deletion. A transient network
   failure therefore left a DISPLAYED dream unacknowledged; the claim lapsed
   and the same narrative could be disclosed again through another channel.

   `deliverDreamReceipt` now retries on a bounded backoff (2s/6s/20s/60s),
   makes no attempt at or past the claim deadline (a late receipt would
   surface a claim that is no longer ours), and treats `acknowledged: false`
   as a definitive answer rather than something to retry.

Desktop 133 passed (5 new: retried until the receipt lands, stopped when the
next attempt would miss the deadline, nothing attempted once already stale,
definitive refusal not retried, backoff exhausted rather than looping). tsc
clean. No server changes this round.
@G9000

G9000 commented Aug 2, 2026

Copy link
Copy Markdown
Owner Author

@codex review — scope: P1 and genuinely important only. Correctness bugs, data loss, security/privacy leaks, concurrency races, consent/safety regressions. Please skip nitpicks: style, naming, comment wording, test-count bookkeeping, and ticket/metadata process findings. If nothing important remains, just say so.

Since your last pass (5fd90ad): receipts are gated on document.visibilityState (deferred to the next reveal, never reported for a page that is never shown), and a dropped acknowledgement is retried on a bounded backoff that refuses to attempt past the claim deadline. Desktop-only changes this round.

One residual I chose not to close, called out in the thread: if the page stays hidden past the claim TTL while the greeting is rendered, the dream can be re-offered elsewhere and the hidden copy is still on screen when the window is finally revealed. Tell me if you think that window is worth a re-confirm on reveal.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5fd90addb4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/desktop/src/pages/dashboard/nodes/useDreamShownReceipt.ts Outdated
…eveal

Codex P1 — and it is the residual I flagged in round 5 and chose to leave
open. The pushback is right, and the failure is worse than I described.

Round 5 deferred the RECEIPT to the next reveal but left the greeting
RENDERED the whole time. So a window hidden past the claim deadline still
had the stale dream on screen the moment it was revealed, while
`deliverDreamReceipt` correctly declined to acknowledge an expired claim —
leaving the row unsurfaced and offerable. That is not a small window: it is
a guaranteed later duplicate disclosure of text the user has already been
shown.

Fixed by withholding instead of hiding after the fact. A dream is now only
ever rendered while the page is visible AND its claim is live
(`displayableGreeting`), so nothing dream-bearing is painted into a window
the user cannot see and there is no stale frame to expose on reveal. When a
hidden window is revealed with a lapsed claim, the claim is re-confirmed
first; a refusal strips the dream from the greeting permanently rather than
leaving stale text one render away from the screen.

The acknowledgement path now reads the DISPLAYED greeting rather than the
fetched one, so a dream withheld for being hidden or stale can never be
marked surfaced. `useDreamShownReceipt` keeps a plain visibility assert as
an independent second guard; its deferred-reveal listener is gone, since
reveal is now handled where re-confirmation can happen.

Desktop 138 passed (5 new: withheld while hidden, withheld when the claim
lapsed even if visible, shown untouched when visible and live, ordinary
greetings unaffected, and a withheld dream owes no receipt). tsc clean. No
server changes this round; the round-4 suite (3374 passed, 0 failed) still
covers it.
@G9000

G9000 commented Aug 2, 2026

Copy link
Copy Markdown
Owner Author

@codex review — scope: P1 and genuinely important only. Correctness bugs, data loss, security/privacy leaks, concurrency races, consent/safety regressions. Please skip nitpicks: style, naming, comment wording, test-count bookkeeping, and ticket/metadata process findings. If nothing important remains, just say so.

Since your last pass (cba0fb6): a dream is only rendered while the page is visible and its claim is live, a lapsed claim is re-confirmed on reveal before the dream may reappear, and the acknowledgement path reads the displayed greeting rather than the fetched one. Desktop-only changes.

Worth attacking: any remaining path where a dream reaches the screen without a live confirmed claim, or where one is marked surfaced without having been displayed.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cba0fb61ec

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/desktop/src/pages/dashboard/Dashboard.tsx Outdated
Comment thread apps/desktop/src/pages/dashboard/nodes/useDreamShownReceipt.ts Outdated
…sibility

Two more Codex P1s, both real.

1. Consent, on every reveal — not just on expiry. Round 6 re-confirmed a
   claim on reveal only when it had EXPIRED. So a greeting confirmed while
   the window was hidden, followed by the user disabling ambient sharing
   from another window, still showed its dream on reveal: the claim was
   within its TTL, no confirmation was attempted, and the server never got
   to apply the consent gate it grew in round 4.

   Confirmation now happens in exactly one place — a visibility-gated effect
   — and never at fetch time. A Dashboard that mounts hidden neither shows a
   dream nor reserves one, and every hidden-to-visible transition asks
   again. Display additionally requires that the server approved THIS claim
   generation (`approvedClaimToken`), so an approval earned by an earlier
   claim cannot authorise a later one. The loader is held while a
   dream-bearing greeting awaits its first confirmation, so the dream
   sentence is never seen appearing late.

2. Page visibility is not element visibility. The dashboard is a pannable
   canvas: both greeting surfaces can be mounted, in a foreground window,
   and entirely off screen — or sitting behind the full-screen gallery
   lightbox. Either way the old check reported the dream as shown and
   consumed it.

   Receipts are now gated on an IntersectionObserver against the node's own
   element, and withheld altogether while an overlay covers the canvas
   (lightbox, thread preview, episode reader), since occlusion is not
   something an observer can see. Both are conservative in the same
   direction: no receipt means the claim lapses and the dream returns.

Desktop 139 passed (display rule now covers unapproved and
approved-for-another-claim). tsc clean. No server changes this round.
@G9000

G9000 commented Aug 2, 2026

Copy link
Copy Markdown
Owner Author

@codex review — scope: P1 and genuinely important only. Correctness bugs, data loss, security/privacy leaks, concurrency races, consent/safety regressions. Please skip nitpicks: style, naming, comment wording, test-count bookkeeping, and ticket/metadata process findings. If nothing important remains, just say so.

Since your last pass (fd82308): confirmation happens only in a visibility-gated effect (never at fetch), display requires server approval of that exact claim generation, and receipts require the node's element to actually intersect the viewport with no overlay covering the canvas. Desktop-only changes.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fd82308d6a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/desktop/src/pages/dashboard/Dashboard.tsx
Comment thread apps/desktop/src/pages/dashboard/Dashboard.tsx Outdated
Comment thread apps/desktop/src/pages/dashboard/Dashboard.tsx
Three Codex P1s, all the same underlying gap: `approvedClaim` was granted
once and never revoked, so it outlived the two conditions that justified it.

1. It survived the page being hidden. A claim approved while visible was
   trusted on reveal, so ambient sharing disabled from another window in the
   meantime went unnoticed and the dream was disclosed after the opt-out.
   Approval is now cleared whenever the page goes hidden, and every reveal
   re-confirms — which is where the server's consent check lives.

2. It survived the claim deadline. Nothing re-renders as time passes, so a
   greeting sitting off-viewport kept a stale approval; an initiative could
   re-offer the narrative and panning the node into view later would show it
   again while its acknowledgement was rejected as expired. A timer now
   expires the approval exactly at the deadline, after which the dream is
   withheld until re-confirmed.

3. The occlusion gate only knew about the three Dashboard-owned modals. The
   Layout independently renders an initiative card above the canvas, which
   an IntersectionObserver happily reports through. Rather than enumerate
   every overlay, the receipt hit-tests the element under the greeting's own
   centre point — with an ancestor allowance, so the canvas pane swallowing
   the hit is not mistaken for occlusion.

A dream this client already ACKNOWLEDGED is exempt from both new expiries.
It is durably surfaced and the user has demonstrably read it, so leaving it
on screen discloses nothing new — and without the exemption the sentence
would vanish from a greeting the user is still looking at.

Desktop 140 passed (display rule now covers hidden/expired/unapproved/
approved-for-another-claim and the acknowledged exemption, including that
the exemption is scoped to the acknowledged generation). tsc clean. No
server changes this round.
@G9000

G9000 commented Aug 2, 2026

Copy link
Copy Markdown
Owner Author

@codex review — scope: P1 and genuinely important only. Correctness bugs, data loss, security/privacy leaks, concurrency races, consent/safety regressions. Please skip nitpicks: style, naming, comment wording, test-count bookkeeping, and ticket/metadata process findings. If nothing important remains, just say so.

Since your last pass (f601a81): display approval now has a lifetime — cleared whenever the page goes hidden (so every reveal re-confirms and re-checks consent) and expired by a timer at ambientDreamExpiresAt (so an off-viewport greeting cannot be panned into view on a stale approval). Occlusion is no longer an enumeration of known overlays: the receipt hit-tests the element at the greeting's own centre point, with an ancestor allowance so the canvas pane swallowing the hit is not read as occlusion.

One deliberate exemption to attack if you disagree: a claim whose receipt this client already delivered is exempt from both expiries, on the grounds that the dream is durably surfaced and the user has read it, so re-showing it is not a new disclosure — without it the sentence would vanish mid-read on every window switch.

All previously raised threads are fixed and resolved.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f601a81b5a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/desktop/src/pages/dashboard/nodes/useDreamShownReceipt.ts Outdated
Comment thread apps/desktop/src/pages/dashboard/nodes/useDreamShownReceipt.ts
@G9000

G9000 commented Aug 2, 2026

Copy link
Copy Markdown
Owner Author

@codex review — scope: P1 and genuinely important only. Correctness bugs, data loss, security/privacy leaks, concurrency races, consent/safety regressions. Please skip nitpicks: style, naming, comment wording, test-count bookkeeping, and ticket/metadata process findings. If nothing important remains, just say so.

Since your last pass (c07daec): no client deadline is a cross-clock comparison any more — the claim's TTL is derived from the two server timestamps and re-anchored to device time on arrival; receipt retries repeat their final interval until the claim deadline; hit-test samples outside the viewport are discarded rather than counted as clear, and a null inside it counts as covered.

All threads to date are fixed and resolved. If what remains is narrower than "a dream can be voiced twice or consumed unseen", say so plainly and I will merge — I would rather ship this and file anything residual than keep iterating on the visibility heuristic.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c07daec284

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/desktop/src/lib/greetingCache.ts Outdated
…onse

Codex P1, and it catches a claim I made in the round-10 reply that was
simply backwards. I wrote that network latency shortens the client's window;
it lengthens it. The server starts the TTL when it takes the claim, so
anchoring to the response's ARRIVAL grants the client the entire round trip
as extra display time — and for a greeting that is not milliseconds, since
the claim is taken before two LLM calls that can run for seconds.

Deadlines are now anchored to the instant the REQUEST WAS SENT. The server's
claim cannot predate the request, so `requestedAt + TTL` is always at or
before the server's own deadline, however slow generation, IPC, or a
suspended webview made the round trip. Both call sites stamp before the
call: the greeting fetch and the claim confirmation.

The stash no longer re-anchors. The greeting reaches it already anchored to
its request, and re-anchoring on stash handed it a fresh full TTL it never
had — the same overrun by another route.

Desktop 143 passed (new: a two-minute round trip still yields
request-start + TTL, not arrival + TTL). tsc clean. No server changes.
@G9000

G9000 commented Aug 2, 2026

Copy link
Copy Markdown
Owner Author

@codex review — scope: P1 and genuinely important only. Correctness bugs, data loss, security/privacy leaks, concurrency races, consent/safety regressions. Please skip nitpicks: style, naming, comment wording, test-count bookkeeping, and ticket/metadata process findings. If nothing important remains, just say so.

Since your last pass (5972535): claim deadlines are anchored to the instant the request was SENT rather than when the response arrived, so the client's window can only sit at or inside the server's however slow the round trip; the stash no longer re-anchors a greeting that already carries an anchored deadline.

All threads to date are fixed and resolved.

A note on where this stands: the server side has been unchanged for six rounds, and the last several findings have been successive precision refinements of the client-side timing and visibility model. I am happy to keep fixing anything that can still cause a dream to be voiced twice or consumed unseen — please flag those plainly. If what remains is narrower than that, say so and I will merge and file it as a follow-up on IL-015 rather than hold the PR.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 597253503b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/desktop/src/pages/dashboard/nodes/useDreamShownReceipt.ts Outdated
…of it

Codex P1. The hit test skipped samples that fell outside the viewport and
accepted a single clear one, so a greeting panned half off screen passed —
while the dream sentence, appended at the end of the paragraph, was still
below the edge. Navigating away then consumed a dream whose own sentence was
never visible.

All three samples (5%, 50%, 95% of the text element's height) must now be
inside the viewport and resolve to the greeting, so the whole text block has
to be readable before a receipt is owed.

Not done by splitting the dream sentence into its own element, as suggested:
that means locating the sentence inside the message string, and the static
greeting path does not necessarily append it at the end. Requiring the whole
paragraph subsumes the sentence without depending on where it sits.

A false "not visible" remains recoverable — the recheck timer keeps asking
while a receipt is owed — whereas a false "visible" consumes the dream for
good, which is why the check keeps failing closed.

Desktop 143 passed, tsc clean. No server changes.
@G9000

G9000 commented Aug 2, 2026

Copy link
Copy Markdown
Owner Author

@codex review — scope: P1 and genuinely important only. Correctness bugs, data loss, security/privacy leaks, concurrency races, consent/safety regressions. Please skip nitpicks: style, naming, comment wording, test-count bookkeeping, and ticket/metadata process findings. If nothing important remains, just say so.

Since your last pass (124f67e): the occlusion hit test no longer skips off-viewport samples. All three points spanning the greeting text's height must be inside the viewport and resolve to the greeting, so the whole text block — including the appended dream sentence — has to be readable before a receipt is owed.

I did not take the suggested split-the-sentence-into-its-own-element approach; the reasoning is on the thread (the static-greeting path does not necessarily append the sentence at the end, so locating it in the string would be wrong there). Push back if you think the whole-paragraph requirement misses a case.

All threads to date are fixed and resolved.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 124f67e635

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/desktop/src/pages/dashboard/nodes/useDreamShownReceipt.ts Outdated
Codex P1. Three point samples do not establish that a text block is fully
visible: the 5%/95% insets left room for a clipped edge to hide part of the
appended dream line, and only the centre x-coordinate was ever tested, so
horizontal clipping was not checked at all.

The element's whole bounding box must now be inside the viewport. The
samples are reduced to the job they are actually good for — detecting an
overlay stacked on top.

One exemption: a text block LARGER than the viewport is not subject to the
bounds test. It could never satisfy it, so the receipt would be starved
forever and the dream re-offered on every claim expiry — the failure this
ticket exists to prevent. There the sampled points carry the check, as
before.

Desktop 143 passed, tsc clean. No server changes.
@G9000

G9000 commented Aug 2, 2026

Copy link
Copy Markdown
Owner Author

@codex review — scope: P1 and genuinely important only. Correctness bugs, data loss, security/privacy leaks, concurrency races, consent/safety regressions. Please skip nitpicks: style, naming, comment wording, test-count bookkeeping, and ticket/metadata process findings. If nothing important remains, just say so.

Since your last pass: the greeting element's full bounding box must be inside the viewport before a receipt is owed — all four edges, not sampled points — with the samples now used only for occlusion. A text block larger than the viewport is exempt from the bounds test, since an unsatisfiable rule would starve the receipt forever and guarantee the dream is re-offered.

All threads to date are fixed and resolved.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8e04559c62

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/desktop/src/pages/dashboard/nodes/useDreamShownReceipt.ts Outdated
…d-block exemption real

Two Codex P1s — one the first server finding in six rounds, and a genuine
hole.

1. `confirm_claim` matched the claim GENERATION but not its liveness. Between
   expiry and the next writer the row already satisfies
   `offerable_dream_query`, so an initiative may have selected the same
   narrative; renewing there authorised a second, concurrent disclosure of
   it. The update now also requires `claimed_at >= claim_cutoff(now)` — the
   same cutoff the offerable predicate uses, so the two can never both
   consider the claim theirs.

   `acknowledge_dream` deliberately does NOT get the same gate, though the
   review asked for both. The two answer different questions. Confirmation
   authorises a FUTURE disclosure, so an expired generation must lose it. An
   acknowledgement reports a disclosure that already happened: the user has
   read this narrative. Refusing a receipt that arrives moments after the
   deadline would leave the dream offerable and guarantee it is voiced at
   them a second time — the exact repeat this module exists to prevent —
   while accepting it creates no new disclosure, since a rival claimant
   already holds its own copy either way. Both behaviours are now tested
   with that reasoning written down.

2. Round 13's exemption for text blocks larger than the viewport was
   unsatisfiable: fixed 5%/95% samples of an oversized block can never both
   be on screen, so those receipts were starved forever and the dream
   re-offered even after the user panned through and read it. Probes are now
   clamped to the visible span — a no-op for a block that fits, since its
   full bounds are already required, and the thing that makes the exemption
   real for one that does not.

Tests: ambient-dream file 44 passed (2 new — an expired generation cannot be
confirmed and leaves the row offerable; a late acknowledgement is still
honoured). Full server suite 3376 passed, 0 failed. Desktop 143 passed. tsc
and ruff clean.
@G9000

G9000 commented Aug 2, 2026

Copy link
Copy Markdown
Owner Author

@codex review — scope: P1 and genuinely important only. Correctness bugs, data loss, security/privacy leaks, concurrency races, consent/safety regressions. Please skip nitpicks: style, naming, comment wording, test-count bookkeeping, and ticket/metadata process findings. If nothing important remains, just say so.

Since your last pass: confirm_claim now requires the matched generation to still be live (claimed_at >= claim_cutoff(now), the same cutoff the offerable predicate uses), and the oversized-text-block exemption is real — occlusion probes are clamped to the visible span instead of using fixed fractions that an oversized block can never satisfy.

I did not add the live-cutoff to acknowledge_dream, and would welcome a challenge if you think the reasoning is wrong: confirmation authorises a future disclosure while an acknowledgement reports one that already happened, so refusing a late receipt leaves the dream offerable and guarantees a repeat at someone who has already read it, whereas accepting it creates no new disclosure. It remains claim-scoped, so a superseded generation still cannot hijack a newer claim.

Full server suite 3376 passed / 0 failed. All threads to date are fixed and resolved.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2fb553fe00

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/desktop/src/pages/dashboard/nodes/useDreamShownReceipt.ts
…sing tall text

Codex P1. Clamping the occlusion probes to the visible slice let an
oversized block be acknowledged while only its TOP had ever been on screen —
the appended dream sentence never seen.

Rounds 13, 14 and 15 were three attempts to special-case a text block taller
than the viewport: require its full bounds (unsatisfiable), exempt it from
the bounds check (starved the receipt), clamp probes to the visible slice
(acknowledged unseen text). Each fixed one direction and broke the other,
which is the signal that the special case itself was wrong.

It is gone. Visible, unobscured spans now ACCUMULATE in the element's own
coordinates across checks, and the receipt is owed once the union covers the
whole text. There is no branch: a block that fits satisfies the union on its
first check, exactly as before; one that does not requires the user to
actually pan through it, which is what "was it read" means for text too tall
to show at once.

Horizontally there is no accumulation — a line clipped at the side is not
being read, and no panning story makes half a line legible.

Residual, stated rather than hidden: coverage is sampled on the 1s recheck
tick, so a very fast pan can leave an unsampled band. The cost is a missing
receipt, which means the dream is re-offered later rather than consumed
unseen — the safe direction.

Desktop 143 passed, tsc clean. No server changes.
@G9000

G9000 commented Aug 2, 2026

Copy link
Copy Markdown
Owner Author

@codex review — scope: P1 and genuinely important only. Correctness bugs, data loss, security/privacy leaks, concurrency races, consent/safety regressions. Please skip nitpicks: style, naming, comment wording, test-count bookkeeping, and ticket/metadata process findings. If nothing important remains, just say so.

Since your last pass: the oversized-text-block special case is gone. Visible unobscured spans accumulate in the element's own coordinates across checks, and the receipt is owed once the union covers the whole text — one rule for every size, with the tall case simply requiring the user to pan through.

All threads to date are fixed and resolved.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 110e5ed857

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/server/src/anima_server/services/agent/inner_life/initiative.py Outdated
Comment thread apps/desktop/src/pages/dashboard/nodes/useDreamShownReceipt.ts Outdated
…usion in 2D

Two Codex P1s.

1. Server, and the more serious. IL-003's post-delivery marker re-ran
   `offerable_dream_query` instead of using the dream the message was
   GENERATED from. If that dream is acknowledged through the ambient channel
   during the LLM call — which takes seconds — the query returns the NEXT
   offerable dream and marks it surfaced. A dream nobody ever voiced is
   burned, silently, exactly the loss IL-015 exists to prevent.

   `gather_drive_material` now reports which row it used through an optional
   `source_ids` sink, `_fire` returns that id alongside the delivery result,
   and the marker pins on it. The offerable predicate still applies on top,
   so a dream claimed by an in-flight greeting is never stolen.

   The regression test was checked against the OLD marker first and fails
   there: two share-worthy dreams, the voiced one acknowledged mid-
   generation, and the older one must still be waiting its turn.

2. All three occlusion probes shared the horizontal centre, so an overlay
   pinned to one side of the canvas covered the ends of every line while the
   middle stayed clear — words under it are just as unread. Probes are now a
   3x3 grid across the visible span.

Full server suite 3377 passed, 0 failed. Desktop 143 passed. tsc and ruff
clean.
@G9000

G9000 commented Aug 2, 2026

Copy link
Copy Markdown
Owner Author

@codex review — scope: P1 and genuinely important only. Correctness bugs, data loss, security/privacy leaks, concurrency races, consent/safety regressions. Please skip nitpicks: style, naming, comment wording, test-count bookkeeping, and ticket/metadata process findings. If nothing important remains, just say so.

Since your last pass: the initiative's post-delivery marker is pinned to the dream the message was generated from (threaded out of gather_drive_material via source_ids, still filtered by the offerable predicate), and occlusion probes are a 3x3 grid rather than a single column.

The pinned-marker regression test was verified to fail against the old marker before being accepted. Full server suite 3377 passed / 0 failed. All threads to date are fixed and resolved.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f7bfaf516e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Codex P1 — the inverse of round 1's race, and a real ordering hole.

Round 1 stopped an initiative from stealing a dream a greeting had claimed.
The mirror case was left open: the initiative itself reserved nothing while
the model generated, which takes seconds. A greeting arriving in that window
could claim the same narrative; the post-delivery marker then correctly
refused to steal the live claim — but the initiative had ALREADY been
delivered, so both channels voiced the same intimate dream and nothing could
retract it.

The dream is now reserved BEFORE delivery: a conditional claim pinned to the
row the message was generated from, carrying the offerable predicate. If a
greeting won the race the rowcount is 0 and the fire is abandoned before
anything reaches the client — an undelivered attempt in the provenance log,
which is the honest record. Claim and delivery sit in one short transaction,
so the database's own write serialisation keeps the two channels apart
rather than a check-then-act across a seconds-long LLM call.

The post-delivery marker now pins by id alone: ownership is established by
that reservation, so re-testing the offerable predicate would fail against
our own claim.

Verified the regression test fails without the reservation: a greeting
claims mid-generation, and afterwards there must be no PendingInitiative,
the greeting's claim intact, and the log row undelivered.

Full server suite 3378 passed, 0 failed. Desktop 143 passed. tsc and ruff
clean.
@G9000

G9000 commented Aug 2, 2026

Copy link
Copy Markdown
Owner Author

@codex review — scope: P1 and genuinely important only. Correctness bugs, data loss, security/privacy leaks, concurrency races, consent/safety regressions. Please skip nitpicks: style, naming, comment wording, test-count bookkeeping, and ticket/metadata process findings. If nothing important remains, just say so.

Since your last pass (ebd174a): the initiative reserves its dream before delivering rather than marking it afterwards — a conditional claim pinned to the generated row, carrying the offerable predicate, with the fire abandoned if a greeting won the race. Claim and delivery are in one transaction so write serialisation separates the channels.

Both of the last two server regression tests were verified to fail against the old code before being accepted. Full server suite 3378 passed / 0 failed. All threads to date are fixed and resolved.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ebd174a9eb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/server/src/anima_server/services/agent/inner_life/dream_receipt.py Outdated
Codex P1, and an inconsistency of my own making. Rounds 3 and 4 scoped
`confirm_claim` and `acknowledge_dream` to the caller's claim generation, and
round 4's reply argued that "clearing unconditionally would clear a NEWER
greeting's claim" as the reason NOT to release on consent refusal — while
`release_claim` itself stayed id-only. The hazard I named was still live on
the one path that does release.

Generation can outlast the claim TTL: the TTL is configurable to any
positive value and two LLM calls sit inside the window. A first request
reaching its final consent check can therefore find the dream already
reclaimed by a second greeting. Releasing by id alone cleared THAT claim,
unprotecting a disclosure already in flight — and if the second greeting had
rendered, its acknowledgement then failed and the dream became offerable
again after consent was re-enabled.

`release_claim` now takes the user id and the claim token, matches on
`claimed_at == token`, and returns whether the caller's own claim was the
one released. A superseded request logs and changes nothing.

Verified the regression test fails against the id-only version: the first
greeting's claim lapses, a second claims the dream, the first releases — the
second's claim must survive and its acknowledgement must still land.

Full server suite 3380 passed, 0 failed. Desktop 143 passed. ruff clean.
@G9000

G9000 commented Aug 3, 2026

Copy link
Copy Markdown
Owner Author

@codex review — scope: P1 and genuinely important only. Correctness bugs, data loss, security/privacy leaks, concurrency races, consent/safety regressions. Please skip nitpicks: style, naming, comment wording, test-count bookkeeping, and ticket/metadata process findings. If nothing important remains, just say so.

Since your last pass: release_claim is scoped to the caller's own claim generation (user + token, matching claimed_at), so a request whose claim lapsed during generation can no longer clear the claim of the greeting that took the dream after it. Every mutation on the row — confirm, acknowledge, release, and the initiative's reservation — is now generation-scoped.

Verified against the old code before accepting the test. Full server suite 3380 passed / 0 failed. All threads to date are fixed and resolved.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. You're on a roll.

Reviewed commit: ae408a935e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@G9000
G9000 merged commit 6bce8f8 into main Aug 3, 2026
@G9000
G9000 deleted the worktree-il-015-dream-receipt branch August 3, 2026 05:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant