Repository navigation
IL-015: claim/acknowledge protocol for ambient dream surfacing (+ IL-014 design) - #135
Conversation
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.
|
@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. |
There was a problem hiding this comment.
💡 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".
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.
|
@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. |
There was a problem hiding this comment.
💡 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".
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.
|
@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 |
There was a problem hiding this comment.
💡 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".
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.
|
@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. 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. |
There was a problem hiding this comment.
💡 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".
…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.
|
@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 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. |
There was a problem hiding this comment.
💡 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".
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.
|
@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 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. |
There was a problem hiding this comment.
💡 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".
…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.
|
@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. |
There was a problem hiding this comment.
💡 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".
…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.
|
@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. |
There was a problem hiding this comment.
💡 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".
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.
|
@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 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. |
There was a problem hiding this comment.
💡 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".
|
@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. |
There was a problem hiding this comment.
💡 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".
…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.
|
@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. |
There was a problem hiding this comment.
💡 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".
…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.
|
@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. |
There was a problem hiding this comment.
💡 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".
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.
|
@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. |
There was a problem hiding this comment.
💡 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".
…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.
|
@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: I did not add the live-cutoff to Full server suite 3376 passed / 0 failed. All threads to date are fixed and resolved. |
There was a problem hiding this comment.
💡 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".
…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.
|
@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. |
There was a problem hiding this comment.
💡 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".
…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.
|
@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 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. |
There was a problem hiding this comment.
💡 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.
|
@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. |
There was a problem hiding this comment.
💡 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".
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.
|
@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: Verified against the old code before accepting the test. Full server suite 3380 passed / 0 failed. All threads to date are fixed and resolved. |
|
Codex Review: Didn't find any major issues. You're on a roll. Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
Closes the residual risk IL-010 accepted: a dream was marked
surfacedthe 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_atis a claim state distinct fromsurfaced. A greeting claims; only an explicit client acknowledgement surfaces.surfacedtherefore keeps its meaning as the durable "never voice this again" flag every other consumer already reads. Migration20260802_0001.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.pyowns the offerable query,acknowledge_dream(idempotent, ownership-scoped — the client acks best-effort and must never be penalised for a retry), andrelease_claimfor the consent-withdrawn-mid-generation case, which is immediate and certain rather than expiry-based.POST /chat/greeting/dream-ack+ambientDreamIdon 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
PendingInitiativedeliver/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 staysbacklog— 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
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 scopingtsc --noEmitclean20260802_0001: up/down/up clean on SQLite, single head