Skip to content

interim - #102

Draft
alichherawalla wants to merge 785 commits into
mainfrom
codex/chat-flow-runtime-integration
Draft

alichherawalla wants to merge 785 commits into
mainfrom
codex/chat-flow-runtime-integration

Conversation

@alichherawalla

Copy link
Copy Markdown
Contributor

Interim snapshot preserved before the pre-migration recovery branch.

… able to fail the search

Two defects in the degradation slice committed as 8bf3675, plus the groundwork for a third that
needs a line in another owner's file.

AN OBSOLETE REPLY COULD CLEAR A LIVE DEGRADATION. `semanticHits` awaited the source pass with no
claim check after it, so a vector reply belonging to a query the user had already typed past could
return, look like success, and clear a standing "semantic search is not running" that was still true
for the query actually in flight. The claim is now checked after the last await, before anything is
reported - and on the FAILURE side too, which the report did not name but has the identical shape: a
fault raised for a dead query would have published a degradation nobody was waiting for. Superseded
now takes precedence over the fault and rethrows. No answer from a dead query publishes anything,
success or failure.

REPORTING COULD FAIL THE SEARCH IT WAS ONLY OBSERVING. The previous comment claimed the reporter
could never fail the caller; that was false, and false in committed code. Its catch called the
diagnostic writer, which can itself throw through its stream - and the fault branch logged BEFORE
returning the fallback, so a throwing logger destroyed keyword results that were perfectly good. Now
one guard around the whole reporting body, no nesting, and the catch is silent rather than logging
through the thing that just threw - the same answer `stream-guards.ts` already gives for a throwing
console stream, and for the same stated reason. The degradation report is ordered BEFORE the
diagnostic, so a throwing logger cannot cost the user the visible signal.

The comment now separates what is guaranteed from what is attempted: the caller always gets its
result; the projection report and the log line are attempts; if both fail the degradation is lost,
and that trade is stated rather than implied. Still `{ reason }` only - no query text, no truncation,
no hash, with a comment saying why so it does not regress.

BOUNDED OBSOLETE WORK, no retries: a checkpoint after the vector read, which was the longest
unguarded stretch and gated N per-hit probes for a dead query, and one after the source pass. Plus
`claim.release()` in a `finally`, so the per-stream map is bounded by IN-FLIGHT streams rather than
growing for the lifetime of the process - guarded so only the newest holder deletes, and an older
request cannot erase a live token. No backoff, no scheduler, no retry loop.

THE PREMISE I WAS GIVEN WAS WRONG, AND THE TRUTH IS WORSE. The stream identity is not a global
constant here - `claimStream` takes it as a parameter and the map is keyed by it, so this file
hardcodes nothing. But NO production caller passes one: not the Pro `search:universal` handler, not
the command palette's path, not the tools or IPC one-shots. So the supersede machinery documented in
this file is unreachable from the UI and exercised only by tests, and with two windows the bug is not
that they cancel each other - it is that neither cancels anything. `searchStreamId(surface, senderId)`
is exported here, pure and Electron-free, beside the map that consumes it so the two cannot drift;
the handler line that would use it belongs to another owner and is being requested, not edited.

Gates: focused eslint 0 · no typecheck diagnostic names this file · verify-model-architecture 0.
Code-gated only, nothing executed. The multi-window case CANNOT be demonstrated by reading this file
and is NOT fixed until that handler line lands.

Claude-Session: https://claude.ai/code/session_01RwwvfNHkF7ohUnbpZ75oZu
…ry beside it

Three defects in the reconciliation committed as d2dad8a.

THE COMPUTER USE READ COULD STILL SUPPRESS A VALID TEXT & VISION LIST. Moving its state assignment
earlier decoupled nothing: both reads were one `Promise.all`, so a rejection from the optional
computer-use read rejected the combined promise and a perfectly good model-control projection was
never applied. The reported defect surviving through a different door. They are two independent reads
now, still started together so they still run concurrently, each with its own failure state - so
neither can block or suppress the other. And no guessed empty array: an unread computer-use section
says it is unavailable rather than collapsing into "nothing selected", which is the same argument the
main inventory already makes, applied one level down.

A REJECTED IPC STILL ESCAPED TWO HANDLERS. `choose` and `chooseComputerUse` had `try/finally` with no
`catch`, and the control client calls the bridge directly - so a missing bridge, a dead main process
or a serialization failure rejected out of an async function invoked from a void click handler.
`finally` cleared the spinner and the rejection went unhandled: the user saw the spinner stop and
nothing else, indistinguishable from a successful no-op. The same shape already fixed in `load`, two
functions away. Both now surface the transport failure inline.

`Outcome.ok` DID NOT MEAN THE UNLOAD HAPPENED. `ok` means the command was carried and answered; only
one of the five success statuses means the model left memory, and the code asserted that meaning for
all of them - so a non-completed unload would have reported "Unloaded" beside a still-resident model.
Labelled honestly: this is HARDENING, not a live user-visible fix. The unload path returns `completed`
on its only success today, so no other status can currently arrive. The type permitted five and the
code assumed one, which is worth closing on its own terms without claiming a bug the user could hit.

Unchanged: no visual redesign, no new UX surface - the computer-use error reuses that section's
existing paragraph and the unload fix reuses a state value the map already had. The
memory-confirmation UI is still unbuilt and still the recorded gap: a model whose memory advice is
`caution` returns `confirmation_required`, which this panel now reconciles and reports but cannot
confirm through.

Gates: focused eslint 0 · no typecheck diagnostic names this file · verify-model-architecture 0. No
test suite was run. Code-gated only: the app was not run, and the first live step worth taking is to
make the computer-use read reject and confirm the Text & Vision list still populates.

Claude-Session: https://claude.ai/code/session_01RwwvfNHkF7ohUnbpZ75oZu
Two defects, one of them introduced by the previous fix in this file.

PENDING WAS BEING RENDERED AS FAILURE. Distinguishing "unread" from "empty" left a third state
unnamed: on first render the inventory was neither read nor failed, and the panel asserted the models
could not be read while the request was simply in flight. That fired on EVERY open for EVERY user,
for as long as the read took - the original defect wearing the opposite face, since an unread list had
claimed to be empty and a pending list now claimed to have failed. A boolean could not hold three
states, so the section read is a discriminated union of pending, read and unavailable: only a
projection that actually arrived makes "nothing downloaded" true, and only a real refusal or
rejection makes "unavailable" true. Pending uses the spinner this file already uses elsewhere, not a
new affordance. Computer Use had the same shape and gets the same treatment - it no longer flashes
"no model is selected" before its read lands.

A STALE REFRESH COULD REVERT A NEWER COMMAND. The mount refresh is issued before the user does
anything but can resolve after an activate or unload has applied its own fresher projection, and the
projection was written unconditionally - so the panel reverted to the pre-command state, which reads
to a user exactly like the original defect: a click that appears to do nothing, or an active row that
flips back on its own.

Ownership is expressed with `operationId`, the API's OWN correlation field - accepted on every intent,
honoured by the control owner when supplied, and echoed on every success - rather than a revision the
projection does not carry or an ordering manufactured client-side. One ref holds the id allowed to
write; an answer that no longer owns the store is dropped entirely, checking the ECHOED id so the
guard proves the answer belongs to our request. One store with a rule about who may write to it: no
second store, no parallel cache, no diffing.

Two deliberate scoping decisions, because a blanket guard would have been wrong. Per-modality unload
status is NOT the shared store - an unload of Voice is not superseded by an activate of Text, and
guarding it globally would have stranded that modality's spinner on "unloading" forever. And `busy`
is per-request now, since a stale answer clearing a newer request's spinner is the same stale-answer
bug one layer down.

Both are reachable, not latent - the first on every panel open, the second whenever a user clicks
inside the catalog read's window, which widens toward its ten-second bound.

Gates: focused eslint 0 · no typecheck diagnostic names this file · verify-model-architecture 0 · no
test suites run. Code-gated only: the app was not run. The first live step is to slow the catalog read
and confirm a spinner appears rather than the failure claim, then select a model mid-read and confirm
the selection sticks.

Claude-Session: https://claude.ai/code/session_01RwwvfNHkF7ohUnbpZ75oZu
… later microtask

The callee checked the claim before resolving, which looked sufficient and is not. The caller resumes
in a LATER MICROTASK, and another request can claim the stream in that gap - so a check that passed
inside `semanticHits` can already be stale by the time `semanticHitsOrDegrade` publishes on it. The
check now happens immediately after the await, before the success report, exactly as the failure path
already does.

The callee's check stays: it protects its own boundary, which is a different question from whether
the caller may publish.

What this prevents is the worst version of a stale answer, not merely a useless one: clearing a
degradation that is still TRUE for the query now in flight. A user whose semantic search is genuinely
not running would have seen the indicator disappear because a superseded query happened to succeed.

Gates: focused eslint 0 · no typecheck diagnostic names this file. Code-gated only; a microtask race
is exactly what source review cannot demonstrate.

Claude-Session: https://claude.ai/code/session_01RwwvfNHkF7ohUnbpZ75oZu
Readiness was assembled per surface from whatever each could reach, so the shell, the permission gate
and the settings screen could disagree about whether setup was finished. They now read the one shared
projection through a narrow host-facts adapter, so there is a single answer and the surfaces render it
rather than deriving it.

The behaviour this fixes: a device with a paired Sync peer or a saved usable remote route is set up,
whether or not it can reach anything at this moment. Treating unreachable as unconfigured meant a
working install could be asked to onboard again after a network blip - the least forgivable time to
demand setup, because the user has already done it.

Post-onboarding resource mode is reachable from Settings rather than existing only during the
first-run flow, so the choice can be revisited by someone who now knows what their machine does. The
setup panel also observes a refusal, offers a retry, and cannot be overwritten by a stale
initialization - the same in-flight ownership rule applied across this round, since an initialization
that resolves late must not overwrite what the user has since done.

Gates: typecheck:node and typecheck:web carry ZERO production diagnostics · verify-model-architecture
0 with zero temporary entries · the startup consumer contract passes 0/6 packages.

Code-reviewed and source-gated only. No onboarding journey has been run, and readiness is precisely
the kind of thing that only a real first launch proves: a fresh profile, a paired-but-offline device,
and a configured install after a network drop are three separate live checks that have not happened.

Claude-Session: https://claude.ai/code/session_01RwwvfNHkF7ohUnbpZ75oZu
`predev` verified the Shared artifacts, and the other ways this app actually starts did not - the
gateway and start paths, and the direct platform builds, all went straight past it. A gate on one
entry point is a gate people route around without meaning to, and the unguarded paths here were the
consequential ones: the direct builds produce something shippable, so the check was absent exactly
where a stale or missing Shared artifact costs the most.

The same existing verifier is prepended to each path. Deliberately not a second variant: two checks
that can disagree are worse than one that is sometimes missed, because then neither can be trusted.

No build or test is executed by this change - the verifier is read-only, and it stays the single
canonical check rather than gaining a launch-specific twin.

Gates: the verifier passes 0/6 packages. Not a build claim: no packaged or production build has been
run, and this only ensures the check happens when one is.

Claude-Session: https://claude.ai/code/session_01RwwvfNHkF7ohUnbpZ75oZu
…s to

Connector auth keyed work by provider rather than by the account inside it, so a second Google
account was not a second account - it was the first one overwritten. Subject identity now
distinguishes them, and credential guards cover the whole operation rather than only its start, so an
in-flight generation cannot be completed against a different account than the one it began with.

That is the substance of the multiple-account journey: not more rows in a list, but every stored
token, every refresh and every read naming which account it belongs to, so two accounts can coexist
without silently becoming one.

Gates: typecheck:node and typecheck:web carry zero production diagnostics · focused lint clean.

Code-reviewed and source-gated. No account has been connected, no token issued or refreshed, and no
second account added - all of which are live steps, and the ones that matter most here, since an
identity mistake is invisible until two real accounts exist.

Claude-Session: https://claude.ai/code/session_01RwwvfNHkF7ohUnbpZ75oZu
A native OCR read had no bound, so a helper that stopped answering took the caller with it - and the
caller here is the path meeting evidence and capture depend on, so one wedged read stalled work that
had nothing to do with it. The read is bounded and typed now: a timeout is an outcome the caller can
see and act on, rather than a promise that never settles.

The Swift dispatch side is corrected to match, so the helper and its caller agree about what
finishing means.

Gates: typecheck:node and typecheck:web carry zero production diagnostics.

Code-reviewed and source-gated: no extraction has been run, and a timeout path in particular is only
proved by making the helper actually stop answering.

Claude-Session: https://claude.ai/code/session_01RwwvfNHkF7ohUnbpZ75oZu
Child commits first, then the parent gitlink, so the parent never references a commit that does not
exist yet. Picks up pro `0e04244` (meeting end detection, live frames, consent controls, and the
search stream wiring released in the same file) and `805b8ee` (per-account Google identity,
deterministic equal-time ordering, whole-operation credential guards).

Deliberately NOT included: `resources/bin/text-extractor`, a tracked generated binary that a
development helper refresh left dirty. Whether a rebuilt artifact belongs in a source commit is a
repository policy question, not something to answer by sweeping it in with unrelated work.

Claude-Session: https://claude.ai/code/session_01RwwvfNHkF7ohUnbpZ75oZu
…erywhere

Five copies of `'Alt+Space'` existed - the onboarding tour, the Pro catalogue, the dictation overlay,
the Voice screen, and the controller that actually registers the hotkey. Exporting a sixth constant
would have been the wrong fix; the point is that the string the app ADVERTISES and the string it
REGISTERS must be the same one, not four that happen to match.

`src/shared/dictation-defaults.ts` is that owner. It sits in the shared layer for a concrete reason
rather than by convention: the open build cannot import from the optional private Pro submodule,
while Pro imports `@offgrid/core/shared/*` freely - and that asymmetry is exactly why the copies
appeared. All five sites, Pro's controller included, now read from it.

Onboarding was the last hardcoded "Hold Option+Space", telling a Windows or Linux reader to hold a key
their keyboard has no name for - the same defect the shared formatter was written to fix, pointed the
other way. Both static open-build surfaces now format the default through it.

Where a surface can read the user's CONFIGURED chord it does, and the default is only its fallback -
the overlay and the Voice screen both prefer the real setting. The two static surfaces cannot reach
settings, so a formatted default is the honest best available there, which is stated in the module
rather than left for someone to discover.

The key code and modifier live beside the accelerator because they are the same chord in the encoding
the native helper needs, and they are deliberately literal rather than derived - with a note that they
change together, since a mismatch between them is the failure this consolidation exists to prevent.

Gates: typecheck:node and typecheck:web carry ZERO production diagnostics · focused eslint 0 ·
verify-model-architecture 0 · startup consumer contract 0/6 · git diff --check 0.

Code-gated only: nothing was launched. The non-mac spelling is derived from the formatter's modifier
table and has not been seen rendered on Windows or Linux, and a rebound chord has not been seen
displayed.

Claude-Session: https://claude.ai/code/session_01RwwvfNHkF7ohUnbpZ75oZu
Child first, then the parent gitlink. Picks up pro b1f7422, so the registering controller and the
four display surfaces share one owner for the default chord.

Claude-Session: https://claude.ai/code/session_01RwwvfNHkF7ohUnbpZ75oZu
CI resolves Pro and Shared by branch with a fallback rather than by verified commit, and the apps and
Shared are on different branches right now - so a branch lookup selects the wrong Shared source, and
no green run exists against any head reviewed here. Recorded as a release-gate task rather than
started, because editing release YAML now would produce evidence about a pipeline nobody has run.

The Pro sink comment about library refusals arriving as runtime is still accurate, so it stays until
the typed arm it describes is actually integrated. Removing it early would assert a capability the
tree does not have, which is the same defect inverted.

Claude-Session: https://claude.ai/code/session_01RwwvfNHkF7ohUnbpZ75oZu
…the wrong one

A fresh isolated profile showed five interrupted downloads and a zero-byte model it had never seen.
Nothing had leaked into the profile - the app was reading the REAL machine's model directory, because
`desktopModelDownloads` resolved `llm.getModelsDir()` at module evaluation, which happens on first
import of the composition root, before `app.setPath('userData', ...)` ran. Database isolation was
never the question; a store captured a path before the path was decided.

The hazard was already documented fourteen lines from the violation: `llm.ts` resolves its own
active-model path lazily and its comment names `OFFGRID_USER_DATA` as the reason. So the rule existed
and nothing enforced it. The entry is now thin - stdout, protocol, safeStorage, product name and
userData are established, and only then is the application graph imported - so no store can capture a
path that has not been set. An explicit override that cannot be applied FAILS CLOSED rather than
quietly continuing in the wrong directory.

Worth stating because of what it nearly cost: the leak was read-only only because no retry or clear
was clicked. Either would have written to real user state from a profile the operator believed was
isolated.

Startup also stopped competing with itself. `models.text.prepare` and the demo seeder were both fired
un-awaited, and the seeder issued its own prepare - so the newest-wins per-modality lane refused the
startup stage as `superseded`. One named stage runner now serves both the headless and windowed
paths, and the seeder waits on its SETTLED result instead of racing it, so its own deadline measures
seeding rather than a model load. It takes the facade from the composition-root module rather than
the access proxy, because that proxy throws until the root is registered and this stage runs
concurrently with `application.start`.

An empty profile is now a first-class answer rather than an error: no selection returns `unconfigured`
and startup succeeds, while a malformed or offline SELECTED route still reaches prepare and keeps its
failure. Onboarding completing is not a prepared model, and the seeder treats those differently -
curated artifacts unless a model is genuinely prepared and ready. The seeder also stopped claiming
success it did not have: outer failures propagate, knowledge-indexing failures aggregate, and the
completion marker resets before replacement so a failed forced re-seed cannot leave an old success
standing over partial data.

An unexpected ready-callback rejection is logged and routed to the normal quit path instead of
escaping. The architecture gate's queue-owner guard now covers both the old entry and the moved one -
a filename-keyed check silently stops protecting anything when the code moves, which is the failure
mode that passes for the wrong reason.

`src/main/env.d.ts` restores the `?asset` ambient types for the web program, which had excluded the
one entry that used them; a reference file rather than a `types` array, since that array would replace
automatic `@types` resolution for the very files it exists to type.

Gates: typecheck:node and typecheck:web both ZERO production diagnostics · verify-model-architecture 0
with zero temporary entries · startup consumer contract 0/6 · focused eslint 0 with no limit raised or
suppressed.

Live evidence, precisely: a fresh isolated profile completed onboarding through the real UI and
reached /models with no failure banner, storage confined to the temp profile. That exercised the
UNCONFIGURED path - no text model was selected or downloaded - so the prepared branch, live seeding
and `active.ready` remain unobserved, as does anything Pro-gated in a profile with no licence.

Claude-Session: https://claude.ai/code/session_01RwwvfNHkF7ohUnbpZ75oZu
Some valid PDFs failed to extract with `bad XRef entry` while others succeeded, which read like a
malformed-file problem and is not one. The same bytes parse or fail depending on the TYPE handed to
`pdf-parse`: a 610-byte PDF with a classic cross-reference table fails as a `Buffer` and succeeds as a
plain `Uint8Array`, while a 12 KB PDF parses either way. `fs.promises.readFile` returns a Buffer, so
every document through this bridge was taking the failing form.

That selectivity is why it went unnoticed and why it matters: this is not a demo-fixture defect, it is
user PDFs of a particular shape silently failing to index. The file that exposed it was byte-identical
across a failing run and a passing one - proof it was never the producer - and four fresh reads at
different slab offsets failed identically, so it is the type rather than an offset.

The signature says `Uint8Array` now rather than `Buffer`, so the constraint is stated where a caller
will see it instead of living in a comment nobody reads before changing this line back.

Nothing else changed: no fixture edit, no dependency change, no version pin. The PDF producer is
untouched, deliberately.

Gates: focused eslint 0 · no typecheck diagnostic names this file.

Live evidence, narrow and real: after this change a fresh isolated profile indexed all three seeded
documents including the PDF, where the same profile previously failed that one file. That confirms the
fix for this shape of PDF; it does not establish which other real-world PDFs were affected, and I have
not surveyed that.

Claude-Session: https://claude.ai/code/session_01RwwvfNHkF7ohUnbpZ75oZu
…istered

A profile without Pro logged repeated unregistered `meeting:get-state` and `capture:self-view` calls.
The renderer asked for meeting state and reported self-view unconditionally, while those handlers only
exist when Pro is active - so every free install produced a stream of failures for work it was never
entitled to do, and real failures had to be found among them.

Both callers now use the activation result the app already computes - `isPro && proReady &&
proActivation === 'full'` - rather than trying and discarding the error. Asking a question only when
someone can answer it is the fix; catching the rejection would have kept the noise and merely hidden
it.

The self-view call also loses an `as any` and a discarded promise. It was cast to reach an optional
method that the typed bridge does exposes, and its rejection went nowhere - so a genuine reporting
failure was indistinguishable from the entitlement noise. It is typed and its failure is logged now,
and the effect re-runs when activation changes rather than only when the view mode does, so a profile
that becomes entitled mid-session starts reporting without a reload.

The recorder hook gained cleanup and late-reply invalidation in the same slice: a reply from a request
issued before teardown, or before a newer one, can no longer publish into state nobody is watching -
the same stale-answer rule applied across this round.

Gates: focused eslint 0 · no typecheck diagnostic names either file · production diagnostics remain 0
in both Desktop projects.

Code-gated only. The isolated profile has NO Pro licence, so the entitled branch has never run: what a
live check showed is the absence of the unregistered-handler noise, not that meeting state or
self-view reporting works. Those need a licensed profile.

Claude-Session: https://claude.ai/code/session_01RwwvfNHkF7ohUnbpZ75oZu
Four surfaces told every reader to hold the DEFAULT chord, so a user who rebound dictation was given
instructions that did not work on their own machine - and the copy could not know better, because the
static catalogue and the onboarding tour are evaluated at import time in the OPEN build, where the
configured value lives behind Pro IPC.

`useDictationShortcut` reads the configured value at render instead, and the surfaces project it. The
static catalogue keeps describing the canonical default and exposes a projection for the runtime copy,
so nothing calls a hook from a module-scope array.

The read is careful in ways worth keeping. A generation counter drops a stale reply, so a slower
earlier read cannot overwrite a newer one - the same stale-answer rule applied across this round. It
re-reads on window focus, so a chord rebound in another window is reflected without a reload. It
carries its own timeout, and on any failure it says the shortcut is unavailable and points at Voice
rather than silently showing a default that may be wrong. And it states plainly what it is NOT: a
read of the configured value, not proof the hotkey is registered or that the user is entitled to it.

Every rendering still goes through the shared spelling, so a Windows or Linux reader is never told to
hold a key their keyboard has no name for.

Gates: focused eslint 0 across all five files · no typecheck diagnostic names any of them ·
typecheck:node carries no production diagnostic.

NOT claiming a clean tree: `typecheck:web` currently has 2 production errors, both `ModelsScreen.tsx`
`refreshLane` used before declaration, from another worker's in-flight diff that is deliberately not
committed here.

Code-gated only: no rebound chord has been observed rendering, on any platform, and the unavailable
path has not been exercised.

Claude-Session: https://claude.ai/code/session_01RwwvfNHkF7ohUnbpZ75oZu
`control({type:'activate'})` answers `confirmation_required` for a model whose memory advice is
`caution`, and this picker had nowhere to confirm it - so after the earlier fix it reconciled and
reported honestly, and the model still could not be loaded from this surface. It was the only models
surface missing the flow; the Models screen and the storage panel both had it.

The notice follows the storage panel's shape rather than the Models screen's native dialog, and
renders the domain's own advice verbatim. Declining issues no command at all.

The ownership questions this raised are the substance of the change, because a confirmation is a
SECOND command and consent is only meaningful against the intent that produced it:

Stale consent is retired at ISSUE, not at reply. `setConfirmation(null)` lives inside `claimControl`,
so any newer command - choose, unload, confirm, refresh - retires a pending notice the moment it is
issued, whether or not it later succeeds. The hole this closes was real and reachable: previously
`unload` never cleared the notice and `choose` cleared it only on reply, so a user could be shown a
confirmation for A, click B, have B refused, and still accept a confirmation for a model they had
moved on from - consent given for a different question.

The confirm leg takes a FRESH claim. Reusing the activate's id would let an answer to the
already-completed activate publish; the single-use `confirmationId` survives the new claim because it
travels by value in the call rather than being read back from state.

Busy now separates display from identity in one value: the row spinner keys off the model, the
`finally` clears by the request's own operationId. Keying cleanup on the model collapsed two requests
for the same model into one identity, so a double-click let the first reply strand the second - the
same stale-answer defect narrowed to the case a user produces most easily.

The confirmation flow pushed the component past its line limit. Rather than raise or suppress the
gate, four cohesive pieces were extracted as mechanical moves within the file - same markup, same
behaviour. More structural churn than a fix like this ideally carries, and recorded as such.

Gates: focused eslint 0 · ZERO production diagnostics in typecheck:node and typecheck:web at this
commit · verify-model-architecture 0 · git diff --check 0. That web count is measured with another
worker's `ModelsScreen.tsx` still dirty in the tree and deliberately not committed here.

Code-gated only, and every claim above is an ORDERING property that typecheck cannot prove and the
app was not run to observe. A live check needs a model large enough to trigger `caution`: accept and
confirm it loads and the active row reconciles, decline and confirm nothing changes, then click A,
get the notice, click B, let B be refused, and confirm A's notice is gone rather than still
acceptable.

Claude-Session: https://claude.ai/code/session_01RwwvfNHkF7ohUnbpZ75oZu
Two defects, and the second was a design error in how I briefed the first.

The screen narrowed on `status === 'completed'` before applying a projection, at five separate call
sites - refresh, cancel-download, download, remove, activate - and a sixth path returned early when a
user DECLINED the confirmation, leaving the pre-click state on screen behind a dialog they had just
dismissed. Every `ModelControlSuccess` variant carries a fresh projection, and the two non-completed
ones carry their own typed failure, so nothing had to be narrowed and no reason string had to be
invented. A model that installs but does not activate now moves the grid to real state and says why in
the engine's own words, instead of leaving the previous model checked and silent.

Then the harder half. Applying at six sites widened a race that was already there: replies could
publish in any order, so a slow refresh could land after a switch and revert it. The first attempt
gave each command its own lane, which made every writer internally consistent and left the shared
projection exactly as racy as before - an old refresh owned its own lane, passed its own guard, and
stomped a newer switch anyway. Per-writer ownership is the wrong model when the writers share one
piece of state.

Two questions, separated. WHETHER A REPLY MAY PUBLISH is one screen-wide authority: every command
claims it at issue, and a reply publishes only if its ECHOED operation id is still the newest intent,
whichever command produced it. WHETHER A REPLY MAY CLEAN UP ITS OWN spinner or notice stays
per-operation, deliberately - a background refresh holding publication must not also gate
`setSwitching(null)`, or a newer refresh strands the switch spinner forever.

One consequence recorded in the code rather than left to be rediscovered: a download completing while
a newer command holds publication has its projection refused, and that is not lost work - the
download-completed subscription calls refresh, which claims the authority itself and republishes.

The first version of this fix could never have run: an ownership hook was referenced in a
`useCallback` dependency array above its own `const`, which throws at render. Typecheck did catch it,
as TS2448/TS2454, and it was caught by a canonical run rather than by the greps I had been repeating -
which is the more useful lesson.

Gates: focused eslint 0 errors with the warning profile IDENTICAL to baseline - 44 pre-existing
prettier warnings, none in changed lines · ZERO production diagnostics in typecheck:node and
typecheck:web · verify-model-architecture 0 · git diff --check 0.

Code-gated only, and the limitation is not theoretical here: a typecheck-green render crash shipped in
the previous iteration of this same file. Nothing has been rendered. `installed_not_active` and
`projector_installed_not_ready` remain unexercised - both need main-process conditions unreachable from
source. First live step is simply mounting this screen; nothing else matters if that fails.

Claude-Session: https://claude.ai/code/session_01RwwvfNHkF7ohUnbpZ75oZu
…refusal

The first is a consequence of the publication authority rather than a flaw in it: a mutation that
settles after losing publication has no completion event to correct the screen, so unlike the
download path its refusal is permanent. Recorded with the constraint that the discarded projection
must not be re-applied - it is stale by definition, which is why it lost.

The second is the unread-versus-empty distinction the picker surface already draws and the Models
screen still does not, on the surface a user checks first when they think something is missing.

Claude-Session: https://claude.ai/code/session_01RwwvfNHkF7ohUnbpZ75oZu
…imestamp

Pairs with the Shared provenance publisher. The gate that guards every launch path now validates the
ordered build's content proof - inputs, artifacts and resolved dependency bindings - rather than
comparing modification times. The existing runtime-export and declaration checks are unchanged and
still run.

This closes the gap recorded when the gate was last repaired: mtime could not see a package rebuilt
against a stale dependency, so the most dangerous state during a multi-package migration was the one
it reported as healthy.

It is READ-ONLY and it FAILS CLOSED. Verified: with no proof present it exits 1 and names the missing
provenance file with the remedy, rather than passing because it could not tell - which is the specific
defect this gate had before and the reason it is worth being strict now.

OPERATIONAL CONSEQUENCE, stated because it will surprise someone: this gate is wired into eight launch
paths, and no proof exists in any tree yet. Until an ordered `npm run build` in shared/ publishes one,
every one of those paths refuses. That is the intended behaviour, not a regression - but it means the
next person to run dev must build shared first, and the error says so.

Gates: `node --check` · focused eslint 0 with no warnings · gate exits 1 with no proof, as designed.

No build was run and no proof was generated or hand-written. The first genuine evidence is a
successful ordered artifact refresh, still outstanding.

Claude-Session: https://claude.ai/code/session_01RwwvfNHkF7ohUnbpZ75oZu
Two ways this screen reported a state the system was not in.

The refresh failure wrote to `switchError` under the refresh lane, so an old refresh could overwrite
the notice about an activation the user had just performed - losing the message they needed in favour
of a stale one - and a later SUCCESSFUL refresh never cleared it, because the success path did not
touch that state. The screen went on saying your models could not be read after a retry had read them
perfectly well. It has its own state and its own strip now, cleared on a successful refresh that
still owns its lane. Deliberately gated on the LANE rather than publication: whether a newer intent
wins the projection has no bearing on whether this particular inventory read worked.

The refused cancel was worse, because the first fix traded one false claim for another. It had
optimistically erased the row before the cancel was accepted - so a refused cancel left the user
believing they had stopped a download that was still running. Marking the row `failed` instead fixed
the erasure and introduced a different lie: the COMMAND failed, the transfer did not, and the bytes
were still arriving under a row that said otherwise.

Now the transfer's own status, failure kind, percent and byte counts are left alone, and the cancel
command's failure travels in one added field on the row's existing shape - no second store, no second
source of download state. The row keeps painting what the download is actually doing, with the
command's refusal beside it, and it is removed only when a cancel is genuinely accepted.

Gates: focused eslint 0 errors with the warning profile identical to baseline - 44 prettier and 6
structural, none in changed lines · ZERO production diagnostics in typecheck:node and typecheck:web ·
verify-model-architecture 0 · git diff --check 0.

Code-gated only: nothing here has been rendered. Mounting this screen remains the first live step,
because a typecheck-green render crash shipped from this file once already and no gate I can run
would catch a second. `installed_not_active` and `projector_installed_not_ready` are still
unexercised - both need main-process conditions unreachable from source.

Claude-Session: https://claude.ai/code/session_01RwwvfNHkF7ohUnbpZ75oZu
… rest is fine

The strip told users things that were not true, in three ways.

It printed the raw internal domain identifier, so someone whose semantic search had degraded read
"rag is unavailable." A codeword, in the one place the app explains itself when something is wrong.

It read only the FIRST degraded entry, so a second was silently dropped - and because startup stages
publish theirs during boot while runtime degradations arrive later, the entry a user could actually
act on was the one most likely hidden. Every degraded domain is now named in plain words, appended
after any stage message rather than replacing it, and de-duplicated by label because the list is
keyed by REPORTER, so two reporters of one domain would otherwise read "Search and Search".

And it said "unavailable" for a state the contract defines as "up but not whole". That is provably
wrong rather than a matter of taste: a `rag` degradation is published when the SEMANTIC half of
search cannot run, and the keyword half still returns real results - so a user told search was
unavailable could search and get hits. LIMITED is true for every domain by that same definition, and
it is deliberately uniform: the snapshot says a domain is reduced, never which capability of it, so
no per-domain fallback claim is invented for `sync`, `speech`, `automation` or `use`.

"Everything else works." is dropped rather than softened. The strip is handed a list of what IS
degraded; it is never told that nothing else is, so any version of that sentence is a claim about the
whole application made from a list of its problems.

Deliberately NOT rendering each entry's `reason`: that field is reporter-authored free text, so
showing it raw would reintroduce the codeword defect this fixes. A curated mapping would need an
audit of every `reportDegraded` call site.

Two decisions left as decisions rather than guessed: a `late` stage still leads the sentence ahead of
a live outage, and `rag` reads as "Search" here while `STAGE_LABELS` calls the same domain "Opening
your knowledge base". Both recorded; neither encoded as a hierarchy nobody asked for.

Gates: focused eslint 0 errors and 0 warnings · zero production diagnostics in typecheck:node and
typecheck:web.

Code-gated only: no degradation has been rendered. Two simultaneous entries in particular need a
failing embedding model on top of a startup degradation, which was not staged.

Claude-Session: https://claude.ai/code/session_01RwwvfNHkF7ohUnbpZ75oZu
`setSettings` mutated `performanceMode`, the runtime fields and `userExplicit` before persisting, so a
failed write left the rejected value in memory and `getSettings` reported it as current. The user was
told the save failed and the app went on behaving as though it had succeeded.

That contradiction was already visible from the other end: the setup panel treats a refusal as a
value and reports "resource-mode persistence" failure, with its own comment stating that a refusal
must not resolve as a success that stored nothing. The caller was honest; the owner was not.

Now the raw mutable snapshot covers the patch fields, the derived public settings are preserved, pins
are restored on failure, and nothing is emitted until the write lands - no sync, no restart on a
rejected save, so nothing downstream acts on a value that was never stored. The file itself is
replaced through a unique exclusive temp and a rename, so a failed write leaves the previous JSON
intact rather than a truncated one.

Gates: focused eslint 0 · zero production diagnostics in typecheck:node and typecheck:web.

Code-gated only: no rejected save has been produced, and the rollback path is argued from source
rather than observed.

Claude-Session: https://claude.ai/code/session_01RwwvfNHkF7ohUnbpZ75oZu
…e saved, not when a code arrives

The pending registration was resolved on ARRIVAL OF THE AUTHORIZATION CODE, while `finishAuth` still
had to exchange it and save the tokens. Everything between those two moments ran against a
registration the app already considered finished - so a user who removed or reconnected the connector
in that window could have a token save land against the registration they had just torn down. Stale
credentials, written after the user asked for them to be gone.

The lifetime now ends where it should: cancellation runs through commit and through a failure
`finally`, so the registration is released once the exchange has actually settled either way, and a
removal during the exchange is honoured rather than raced.

Preserved deliberately: the PKCE `invalid_client` retries, and background refresh. Both are legitimate
behaviour that a narrower "cancel everything on removal" would have broken, and neither is what this
fixes.

Gates: focused eslint 0 · zero production diagnostics in typecheck:node and typecheck:web.

Code-gated only. The race has NOT been reproduced - it needs a removal timed inside the exchange
window - so the ordering is argued from the lifetime boundary, not observed. The entitled paths
remain unexercised: the isolated profile has no Pro licence and no account has been connected.

Claude-Session: https://claude.ai/code/session_01RwwvfNHkF7ohUnbpZ75oZu
…s on

Pairs with the Shared provenance change. The gate now hands the verifier the exact set of contract
packages Desktop requires, derived from the same contract table it already checks symbols against -
one source of truth for both questions, so the set of names verified for exports cannot drift from the
set verified for provenance.

Clipboard stays Desktop-local by intent and is not in that set.

Gates: `node --check` · focused eslint 0 · gate exit 0 against the refreshed Shared artifacts.

Claude-Session: https://claude.ai/code/session_01RwwvfNHkF7ohUnbpZ75oZu
…until the user retests

Three confirmed defects with owners, recorded before the fixes so the record shows what the user saw
rather than what the fix claims. They compound: duplicate legacy and canonical manifest rows create
two models where there is one, the storage refresh paints live progress onto every row sharing an id
so the stale one reads as running, and an identity mismatch hides a present model from the text list
while the vision list shows it.

Recorded with the standing rule that none of it is reproduced or verified by touching the user's
downloads, registry or model files.

Claude-Session: https://claude.ai/code/session_01RwwvfNHkF7ohUnbpZ75oZu
…st housekeeping note

A missing device cap is reported as a failed registration when nothing was registered - a refusal to
start wearing a downstream failure's label, in the same function that documents the identical
mistake six lines later. Found by a test, not by manual testing; recorded rather than fixed because
it is production source outside the test phase.

The dist note is deliberately narrow: current chunks carry the new contract, an old chunk lingers,
and the ambiguity is worth removing rather than the contract being stale.

Claude-Session: https://claude.ai/code/session_01RwwvfNHkF7ohUnbpZ75oZu
Register-without-unregister leaves a stopped application behind the singleton proxy, which every
lifecycle test papers over by registering the next one; and the CRM channel name lives in two
production files, one of which a renderer test cannot import without pulling electron into jsdom.
Both found while repairing tests, both recorded rather than fixed in the test phase.

Claude-Session: https://claude.ai/code/session_01RwwvfNHkF7ohUnbpZ75oZu
…ement gate tests must respect

The CRM projection hook publishes after unmount two ways - a listener with no cancelled-guard, which
the test catches, and an uncancelled catch-up read reachable through the real transport, which it
does not. The correct pattern is one file away. Recorded as production with the assertion left red.

The recorder note exists so nobody stubs their way past a second gate and calls it green.

Claude-Session: https://claude.ai/code/session_01RwwvfNHkF7ohUnbpZ75oZu
…ves in production

`loadProFeaturesMain` dynamically imports `@offgrid/pro/main`. The production build resolves that to
the real `pro/main/index.ts` when the submodule is present and to the stub when it is not
(`electron.vite.config.ts:19`). The test config aliased `@offgrid/pro` and `@offgrid/pro/renderer` to
the stub and left `/main` unresolved - so under test the import threw ERR_MODULE_NOT_FOUND, which
`loadProFeaturesMainNow` correctly reports as a Pro activation failure whenever Pro is enabled. Every
entitlement-gain test therefore logged an activation failure that no production build can produce -
111 lines in one run of two files, and two tests blocked outright.

The alias now mirrors production exactly. A real Off Grid module when we have it, per the testing
doctrine; the stub only where production would also have the stub.

What it did, measured: the two blocked files' activation-failure noise fell 111 -> 43, and the module
now LOADS - which surfaces the real residual those tests were hiding behind the resolution error: the
real `pro/main` graph imports `BrowserWindow` from `electron`, and under `ELECTRON_RUN_AS_NODE` the
per-file electron mocks in those two tests do not provide it. That is a mock-surface repair in the
test files, owned by the test worker, and it is the same root cause as the eleven CRM suites whose
mocks predate `ipcMain` being reached at application construction.

Verified not to regress core: the one core file that also fails (`diagnostics-log.integration.test.ts`,
3 tests) fails identically with the alias removed - pre-existing, recorded separately.

Gates: focused eslint 0. No production semantics changed, nothing masked, no threshold or exclusion
touched. Nothing under shared/ touched.

Claude-Session: https://claude.ai/code/session_01RwwvfNHkF7ohUnbpZ75oZu
fix(sync): consume canonical Desktop status
Route remote screen work to the selected server
fix: complete session reliability and sync updates
fix: include specialist selection projection
@coderabbitai

coderabbitai Bot commented Sep 11, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sonarqubecloud

Copy link
Copy Markdown

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