Repository navigation
fix(ssh,agent,chart,security): the SSH tunnel verified no host key, and four more - #509
Merged
Merged
Conversation
…nd four more Round 14 of the BACKLOG sweep. Five entries closed, each measured on both arms with a control, and five new findings recorded with their measurements. D11 - the SSH tunnel accepted any host key it was offered. `createSSHTunnel` passed no `hostVerifier`, and ssh2 has no default: read out of its own source, the library takes the branch that logs "Host accepted by default (no verification)" and completes the handshake. So the tunnel connected to whatever answered on the bastion's address, and everything it carries - the database password among it - was readable to anything that could occupy that address. The policy chosen is trust-on-first-use pinned per connection: requiring a pasted fingerprint up front makes tunnelling unusable for someone reaching their own bastion, and TOFU is both what every SSH client does on first contact and strictly better than verifying nothing. Driven against a REAL `openssh-server` container, three arms: no pin accepted and reported `SHA256:JWLQciyX8RYEeuK+bwRLW/YSpStz0/L7BlbVcejL1/U`, byte-identical to `ssh-keyscan | ssh-keygen -lf -`; the correct pin connected; a wrong pin was refused naming both fingerprints. Before the fix the mismatch arm logged `Tunnel created` - it connected. B68 - a conversation was single-connection by record, not by database. Every link checked `connectionId`, so a connection edited to address another server kept its id and a follow-up was handed the earlier steps' claims about the old database while reading the new one. Nothing refused, nothing wrong to look at. Each run now records `connectionIdentity` - the same fingerprint the held snapshot is filed under - and a follow-up whose database does not match declines instead of carrying. That fingerprint gained the SSH tunnel: host and port are resolved at the far end of the hop, so the same `db.internal:5432` reached through two different bastions is two different databases, and the old fingerprint could not see it. Rotating a credential or renaming a connection keeps the conversation, and a predecessor that recorded no identity at all is carried rather than refused, so no conversation in flight across a deploy is ended by a silence. B30 - a green ledger probe promised the wrong thing. It ran `ensureDataDir`'s four steps; the world calls `initDataDir`, which then reads and parses `version.txt`. A truncated or incompatible one answered green, the rail rendered, and the run died at the first Start. The probe now reads an existing `version.txt` itself, mirroring upstream's parse (read out of `@workflow/world-local/dist/init.js`), and reports `LEDGER_INCOMPATIBLE` - a reason distinct from `LEDGER_UNAVAILABLE`, because the path is writable and the action is replacing one file. Two properties are load bearing and asserted in every arm: it never writes, because the only upstream entry point that answers this question initialises the ledger as a side effect; and an ABSENT `version.txt` stays green, because that is the fresh install. A version that parses but differs is green too - upstream hands a mismatch to `upgradeVersion`, which logs and returns. Verified live: a corrupt file flips the route to `LEDGER_INCOMPATIBLE` with the file named, and the file is byte-identical afterwards with no probe file left behind. AU2 - no security header reached the paths the proxy matcher deliberately skips: `public/`, `/monaco/vs/*.js`, `_next/static`. Header delivery and auth redirection were two concerns sharing one matcher. `next.config.ts`'s `headers()` now delivers the two that are meaningful on a subresource, reading their values from the same module the proxy uses rather than spelling them twice. The four that were left out are the point of the change: a `next.config` header set is baked at BUILD time while `src/lib/security/config.ts` reads its env per process, so a copy of the CSP would strand the `CSP_REPORT_ONLY` escape hatch, and a copy of HSTS - host-scoped, so the last value the browser receives wins - would let a request for `/logo.svg` silently downgrade the policy the document just set. Verified over real HTTP on all three paths, including `_next/static`, which no unit test can reach; documents still carry exactly one of each header. N2 - `AUTH_COOKIE_SECURE` was reachable in every distribution channel except the chart, and it is the one setting a plain-HTTP LAN or home-server install most needs. Now `config.authCookieSecure`, rendered through the configmap like its siblings. Three-state, and that is the substance: it renders on `kindIs "invalid"` rather than truthiness, because truthiness would silently drop `false` - the value that matters most here. The precedent is the chart's own `agent.enabled`. All three arms rendered and checked, `src/lib/auth.ts` confirmed to treat absent as unset, and the operator's verbatim chart copy mirrored. Chart 0.1.51 is released, so `Chart.yaml` version is bumped to 0.1.52 by hand; `appVersion` and `package.json` are untouched. New entries, each with its measurement: D30 (nothing writes the durable SSH pin, so the protection is process-scoped and resets on restart), D31 (the live SSH arm was driven once by hand and no fixture keeps it true), AU3 (CORP/COOP are the two headers that would protect a subresource and the two nobody has decided on), B75 (a connection repointed mid-flight is still carried by a resumed drive), B76 (`declined: "unavailable"` collapses six causes into one sentence). The rail's sentence for that collapse was corrected: the earlier step "could not be carried into this question" rather than "could not be reached", which was false for a repoint. Gates, all run locally: format, lint (0 errors), typecheck, knip, 352/352 core files, 33/33 component groups, 197 evals, build, build:lib, attw, coverage 43539/43539 lines (100.00%).
|
cevheri
added a commit
that referenced
this pull request
Aug 27, 2026
…ence, and four more (#512) * fix(db,agent,security,docs): the MySQL health panel fabricated an absence, and four more D32 - MySQL's health slow-query read asked for `LEFT(sql_text, 100)` from `performance_schema.events_statements_summary_by_digest`, a column that table does not have, and a bare catch turned the resulting `ERROR 1054` into the row `{ query: "Performance schema not available", calls: 0, avgTime: "N/A" }`. Measured on four live servers (MySQL 26.7.0 on and off, Percona 8.4.11-11, MariaDB 12.3.2): the sentence appeared on every one of them, including the two where `@@performance_schema` is 1 and the Queries panel beside it returned real rows. The health read now uses the statement the panel uses, one mapper feeds both, and an unreadable source empties the line instead of describing itself - a fabricated absence is exactly what the absence rule (#477) exists to prevent. `getSlowQueries()` no longer swallows either, so a denied grant (1142) or an absent tenant schema (1049) reaches the Queries panel as the server's own sentence. AU3 - `Cross-Origin-Opener-Policy: same-origin` is delivered from `securityHeaders()`, and `Cross-Origin-Resource-Policy` is refused with the reason recorded beside the header rule: CORP acts only on a subresource, the only delivery path that reaches a subresource is `next.config.ts`'s build-time `headers()`, and CORP's correct value is the one in the set that depends on deployment topology. What that refusal gives up is now tracked as AU4. `next.config.ts` derives its baked set by probing `securityHeaders()` rather than restating a list of names, so a header that varies with an option cannot be baked by accident. B76 - `declined: "repointed"` is split out of the collapsed `"unavailable"` code, and the five that stay collapsed each carry the reason they have no distinct remedy. The first draft of the rail's sentence claimed the decline persists until the connection is pointed back; it does not - the route writes the new identity onto the run it opens, so exactly one question is declined. Both halves of the corrected sentence are pinned, and a route test pins the behaviour. B12 - a statement that failed at the database records the span the tracker charged it, so the rail's database-time meter no longer counts completed reads only. The charge is a delta, pinned by a two-execution harness run; a ledger entry written before this change folds as a statement with no duration rather than a measured zero. B38 - a run whose workflow sends statements is refused on an engine with no read-only execution path at the point it is opened: 400 with the posture's own sentence, before a run id, a capability probe or a model turn is spent. The `operations` workflow still opens on the same connection, because it sends no statement at all. The drift guard behind the refusal now measures every provider in the `DatabaseType` union rather than restating the list. Also restores two backlog entries #510 overwrote when it branched before #509 merged, renumbered D34/D35, and repoints the citation that named the wrong entry. Verified in Chrome against the built app: COOP present on the document and absent on `/logo.svg` and every `/monaco/vs/*` response, editor, five Monaco workers, a query and the ERD export all unaffected; the B38 refusal answering 400 with no run opened, and an `operations` run succeeding on the same connection; the fabricated MySQL sentence gone from the monitoring panel. B79 and B80 record what that drive found and could not reach. * docs(comments): cite the change that landed, not the backlog entry it closed Deleting an entry when the work lands is this repo's convention, and T4 records what it costs: nothing checks a citation from a source comment, so every comment naming a deleted id goes silently wrong. Closing D32, B12, B38, B76 and AU3 would have left fifty-odd of them across `src/` and `tests/`. Each one was read rather than substituted. Where the sentence explains why the code is the way it is, it now cites #512, the change that made it so. Where the id was the NAME of a defect class rather than a pointer - "a marker keyed on the throw would be emitted for something other than off-ness, D32 one level up" - the class is named instead, because a PR number does not carry that meaning. Where a comment described the defect in the present tense although the same round had fixed it, the sentence was wrong and not just its id: `digestTableMockExecute`'s docblock claimed the shared fixture still invents digest rows. And where what survives is a residual rather than the closed entry, the citation moved to the entry that still exists - T6 for the shared fixture nothing pins, B78 for the delta that rests on one frozen concurrency constant. `grep -rnE '\b(D32|B12|B38|B76|AU3)\b' src tests next.config.ts e2e scripts` is now empty. Comment-only: `bun run test` 13062 pass / 0 fail, typecheck and format clean. * docs(api,agent,tests): the new refusal was missing from its own contract, and a ledger claim had gone stale Two external reviews of #512 were checked finding by finding. What held: - `docs/API_DOCS.md` documented `POST /api/agent/runs` refusals without the 400 this branch added, so the contract still promised `202` for `investigation` on MySQL followed by an `engine-unsupported` run. The refusal is now listed with the message it actually sends, and a note says what changed for a client that read the old shape. - `docs/AGENT.md` said in its opening claims that any other engine "fails profiled acquisition and the run ends `engine-unsupported`", which now describes only the fallback: the route refuses before a run id exists. Both gates are named in the order they run, and the endpoint table row that omitted the 400 carries it too. - Four citations named ids this branch deleted. `docs/AGENT_GUIDE.md` was the worst of them, because the sentence itself had gone stale: it still told the reader a failed statement records no duration, which is exactly what stopped being true. It now says what a failed statement records and what an older refusal does not. `docs/providers/README.md` (two rows) and the provenance prose in `docs/BACKLOG.md` name the change instead of the closed id; the one surviving mention says outright that the id was reused, which is why citing it is unsafe. What did not hold, and was refused rather than adopted: - A finding placed `elapsedMs: 0` in a file that does not exist. The line number was right for `src/components/agent/timeline.ts`, and the observation under it was real: the fold asks `=== undefined`, the two tests around it separate `undefined` from `30`, and a truthiness rewrite passes both while moving a statement the tracker charged 0 ms into the count of statements that measured nothing. A test now pins the measured zero, and reverting the comparison fails that test alone. - SonarCloud's three `act()` reports are dated before this branch and their rule is wrong here: `fireEvent` inside `await act` is what flushes the pending start, not a redundant wrapper. The `toHaveLength` nit on this branch's own test is taken. - The nested-ternary smell in `AgentRail.tsx` is declined, and the reason is the finding underneath it. Three of the four decline sentences are asserted; `connectionDropped` appears in no test in the repository, and all four arms being one expression is why 100% line coverage cannot see that. Extracting the selection would fail the coverage gate until the fourth arm is driven, which is the honest order. Filed as T7. - B77 gains the half a review pointed at: an unreadable digest source leaves the health list empty on purpose, and the projection then hands the model `slowQueryCount: 0` — the absence rule one level up from the row #512 removed. Gates: format, lint, typecheck, knip clean; 13063 pass / 0 fail; build exit 0; coverage 43652/43652 lines (100.00%).
cevheri
added a commit
that referenced
this pull request
Aug 27, 2026
… no longer holds them (#519) A `docs/BACKLOG.md` entry LEAVES the file when the work lands, and 208 citations across 101 files had outlived theirs - 30 dead ids. A reader who followed one found an empty grep and had to decide whether the comment or the file was wrong. Nothing measured it: the structural guard asks whether the index is derived from the bodies, and the one in `agent-documentation.test.ts` asks the same of `docs/AGENT.md` alone, scoped to the M2 section's `B` ids. Each dead id is replaced by the PR that closed it, verified one at a time by showing the entry present at that commit's parent and absent at the commit. That is the rule this pins: a citation of `docs/BACKLOG.md` says *this is open* and has to resolve, while a claim about work already done cites the immutable record that carries the reasoning. A new guard in `tests/unit/backlog-structure.test.ts` holds it, so closing an entry now means rewriting its citations in the same PR - the lockstep the provider triad already runs on. **The count arrived in three instalments, and both jumps were the pattern's fault rather than the tree's.** Worth stating, because a guard that under-reports reads exactly like a clean one, and the handover this round started from carried the first number as if it were the answer: - Matching line by line found 77. Three citations wrap across a line break in a block comment, so the scan runs over whole files now and derives the line from the match offset. - Those 80 were all of ONE form. An id parenthesised in the syntax of a PR reference - `#U9` - accounted for another 128, more than the form anyone was looking for, and 43 named one dead id. GitHub renders no link for it, so a reader takes it for a pull request. - Four of the dead ids were removed by #518, the round before this one, which reported zero new dangling citations. True of the form it measured; false of the other. **Three of the 208 were not pointer faults but stale CLAIMS**, and each argued for behaviour the code no longer has: - `context-snapshot.ts` told a reader the hold "does NOT carry the database that connection currently points at", four paragraphs above the comment describing the key that does. #509 made the first half false; `connectionIdentity` hashes host, port, database and the role fields, and the map is keyed on it. - `goal-verifier.ts` deferred the terminal-status vocabulary to the owner and said the recording "moves when B24 is ratified". It was ratified 2026-08-13 in the shape the module was built for, and `finalize` has been writing `goalVerdict` since. - `AGENT_DEMO.md` told a demo driver not to show a Redis draft because "an open question the owner has not ruled on" hung over it. It was ruled on and declined on 2026-08-22, with the reasoning recorded at `planningDerivedGroupingsRule`. Two documentation claims that a closure had falsified came out with them. `ADDING_A_PROVIDER.md` said the Operations tab's per-table button title "is still the hardcoded Analyze"; the tab reads `maintenanceOperationSpecs[...].label` through `maintenanceControl()` and falls back to the generic verb, so it never read `analyzeAction` and the hardcoded title is only the fallback. `docs/providers/couchbase.md` described a global Reindex card answering *"The reindex operation requires a target"* - #496 withdrew that card (`global: false`) rather than rewording it, which leaves the provider's three `reindexGlobal*` labels read by nobody. Recorded in the doc, not changed in the code: whether they stay is the owner's call. The guard's limit is stated rather than implied. A third form is not checked and cannot be: the bare parenthesised id. 232 are in the tree and 47 distinct ids among them name no live entry, but the shape is not the backlog's alone - `(S256)` is a PKCE code-challenge method and `(T6)` a task number from issue #331. A bare id matching a live entry needs no help; one that does not is either a dead citation or not a citation at all, and nothing in the text says which. The guard's own fixtures were five of the first eighty-four sites it reported, and its docblock example one more after that. They are interpolated from the document now, so the source text is unscannable while the runtime string still exercises the shape, and no example is pinned to an entry that will close. Non-vacuity is a positive control on each extractor plus a floor on the file scan - never on the number of citations, which falls every time an entry closes. Proven by mutation: one line carrying both forms turns it red on both, and nothing else moved. No rendered string changed anywhere in this PR - `reviewedBy` is validated for non-emptiness and never displayed - so there is nothing here a browser could verify, and no browser check is claimed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Round 14 of the
docs/BACKLOG.mdsweep. Five entries closed, five new findings recorded.Every fix was measured on both arms with a control arm, and three of the five were driven against
something real rather than a mock.
Ordered by cost/value, cheapest-and-highest first.
What is fixed
D11 - the SSH tunnel accepted any host key it was offered (security)
createSSHTunnelpassed nohostVerifier, and ssh2 has no default. Read out of the library'sown source (
node_modules/ssh2/lib/protocol/kex.js), with the callback absent it takes the branchthat logs "Host accepted by default (no verification)" and completes the handshake. So the tunnel
connected to whatever answered on the bastion's address, and everything the tunnel carries - the
database password among it - was readable to anything that could occupy that address. Nothing in the
product ever showed a fingerprint a user could compare.
Policy decided: trust-on-first-use, pinned per connection. Requiring a pasted fingerprint up
front makes tunnelling unusable for someone reaching their own bastion; TOFU is what every SSH
client does on first contact and is strictly better than verifying nothing. It is additive to
SSHTunnelConfig, so no storage migration.Driven against a real
openssh-servercontainer, three arms:SHA256:JWLQciyX8RYEeuK+bwRLW/YSpStz0/L7BlbVcejL1/U- byte-identical tossh-keyscan -p 2226 127.0.0.1 | ssh-keygen -lf -Non-vacuity: before the fix the mismatch arm logged
Tunnel created for pin-mismatch- itconnected. The fingerprint format was separately checked against
ssh-keygen -lfon generateded25519 and RSA keys, 3 of 3 exact.
The fingerprint is classified
publicinconnection-secrets.tsand stored in the clear onpurpose: it authenticates the bastion to Studio, grants no access, and has to be readable for the
comparison to be possible at all.
B68 - a conversation was single-connection by record, not by database
Every link checked
connectionId, which is an identity check on the record. A connection editedto address another server keeps its id, so a follow-up was handed the earlier steps' claims about
the old database while reading the new one. Nothing refused, nothing wrong to look at.
Each run now records
connectionIdentity- the same fingerprint the held context snapshot is filedunder - and a follow-up whose database does not match its predecessor's declines instead of
carrying.
That fingerprint gained the SSH tunnel, which was a real omission:
host/portare resolved atthe far end of the hop, so the same
db.internal:5432reached through two different bastions is twodifferent databases, and the old fingerprint could not see it. Excluded on purpose: the password
(rotating a credential must not end a conversation) and the host-key pin (it records what the
connection trusts, not where it goes).
Three arms, all green: the named repoint test carries no steps; an unchanged connection still
carries; and a rotated password / renamed connection / rotated bastion credential still carries -
the arm that proves the identity is about the database and not the record.
Then driven for real in Chrome via Playwright, against a live Gemini model - four actual agent
runs on the embedded SQLite sample, not a harness:
arun_55cfff9e…) succeeded with no thread notice, which is correct for a runthat starts its own conversation. Its ledger's
run-openedevent carries"connectionIdentity":"55c9b0fe3a6ed062…"- live proof the identity is actually recorded, not justplumbed in tests.
produces, staged by editing the recorded value rather than by faking any product behaviour, byte
length preserved so the ledger's NUL framing stayed intact. The run declined, and the rail
rendered the corrected sentence: "The earlier step could not be carried into this question, so it
started on its own." - asserted positively, and asserted NOT to contain "could not be reached".
from the ledger rather than from the absence of a notice, which would have been ambiguous:
run-openedshowsthread: { threadId: "arun_caac3db2…", steps: [ … ] }.One process note, since it changes how much the middle arm is worth: an earlier attempt at the
control was invalid - a
.click()issued from insidepage.evaluatedid not drive React the way areal click does, and I was re-reading the previous run's state and calling it a control. Redone with
a genuine Playwright click, which is where arm 3 above comes from.
B30 - a green ledger probe promised the wrong thing
The probe ran
ensureDataDir's four steps. The world callsinitDataDir, which then reads andparses
version.txt. A truncated, empty or incompatible one answered green: the rail rendered,the operator clicked Start, and the run died when the world was built.
The probe now reads an existing
version.txtitself, mirroring upstream's parse (read out of@workflow/world-local/dist/init.js), and reportsLEDGER_INCOMPATIBLE- deliberately distinctfrom
LEDGER_UNAVAILABLE, because the path is writable and the action is replacing one file, so anoperator sent to a permission would find nothing wrong with the directory.
Two properties are load bearing and asserted in every arm:
initDataDir,which writes
version.txtas a side effect - calling it would turn a page-load visibility probeinto something that initialises the ledger.
version.txtstays green. That is the fresh install; refusing it would make everyfirst run report an unavailable agent.
A version that parses but differs is green too - upstream hands a mismatch to
upgradeVersion,which logs and returns. Refusing it would have been the mirror-image error: removing a working rail.
Verified live against the running app: a corrupt file flips
GET /api/agent/configto{"enabled":false,"reason":"LEDGER_INCOMPATIBLE","detail":"…records a version this release cannot read (\"truncated-no-at-sign\")…"}, the file is byte-identical afterwards, and no probe file is leftbehind.
And verified in a real Chrome via Playwright, three arms, because the point of the entry is what
the operator sees:
version.txt)agent-rail-panel, Start and objective all renderversion.txt=truncated-no-at-signenabled:false,LEDGER_INCOMPATIBLEagent-*element in the DOMenabled:trueThe third arm is the one that matters: it makes the withholding causally attributable to that file
rather than to load order or a cache. Before this change the corrupt file answered green, so the rail
rendered and the operator met the failure at the first Start.
AU2 - no security header reached the paths the proxy matcher skips
src/proxy.ts's matcher excludes.*\..*,_next/staticand_next/image, soproxy()never ranfor
public/,/monaco/vs/*.jsor_next/static- header delivery and auth redirection were twoconcerns sharing one matcher. The dot exclusion is correct for auth and stays.
next.config.ts'sheaders()now delivers the two headers meaningful on a subresource -X-Content-Type-Options: nosniffandX-Frame-Options: DENY- reading their values from the samemodule
proxy()uses rather than spelling them twice.The four that were left out are the point of the change. A
next.configheader set is baked atbuild time, while
src/lib/security/config.tsreads its env per process. So a copy of theCSP would strand the
CSP_REPORT_ONLYescape hatch that exists for an operator who cannot rebuild aprebuilt image; and a copy of HSTS - which is host-scoped, so the last value the browser receives
wins - would let a request for
/logo.svgsilently downgrade the policy the document just set.Referrer-PolicyandPermissions-Policyact on a document and are inert on an image or script.Verified over real HTTP, which is the only thing that can prove delivery:
_next/staticwas the one path the implementing agent could not claim from Next's own docs. Itworks, so AU2's scope did not need narrowing.
N2 - the chart could not set the one flag a plain-HTTP install needs
AUTH_COOKIE_SECUREwas reachable in every distribution channel except the chart, and it is theanswer for a browser reaching the app over plain HTTP on a non-loopback host - otherwise cookies
carry
Secure, the browser rejects them, and login silently loops. Already diagnosed on threechannels. Chart users had to reach for
extraEnv.Now
config.authCookieSecure, rendered throughtemplates/configmap.yamllike its siblings.Three-state, and that is the substance: it renders on
kindIs "invalid"rather than truthiness,because truthiness would silently drop
false- the value that matters most here. The precedent isthe chart's own
agent.enabled. All three arms rendered and checked:src/lib/auth.tswas read to confirm absent really reaches the app as unset, and the operator'sverbatim chart copy is mirrored (
operatorCopyViolations()empty).Chart 0.1.51 is already released, so
Chart.yamlversionis bumped to 0.1.52 by hand.appVersionandpackage.jsonare untouched;chart:bumpwould not have done it, sinceappVersionis already in sync.Also
The rail's sentence for a declined conversation said the earlier step "could not be reached". That
became false for a repoint - the step can be reached, the database moved - so it now reads "could
not be carried into this question", which is true of all six causes
declined: "unavailable"collapses. The stale claim in
src/proxy.ts's matcher comment and the overstated header claim inSECURITY.mdwere corrected too.New backlog entries (with measurements)
sshTunnel, and an accepted fingerprint is not written back - so the shipped protection is theprocess-scoped memory: a key that changes within a server's lifetime is refused, a restart
trusts afresh. Stated plainly in
docs/providers/README.mdrather than implied.still runs the mocked handshake.
Cross-Origin-Resource-Policy/Cross-Origin-Opener-Policyare the two headers thatwould genuinely protect a subresource and the two nobody has decided on. Both are constant, so the
build-time objection does not apply; neither has been measured against Monaco's same-origin
workers, where a wrong CORP value breaks the editor.
to close it now exists; what a run should do when its connection moves under it is undecided.
declined: "unavailable"collapses six causes, so one sentence covers all of them and isspecific about none. The repoint case is the one whose remedy differs.
Verification
All gates run locally, re-run after the rebase onto the moved
main(a pre-rebase pass does notvouch for the merged tree):
formatlinttypecheckknipbuildbuild:lib+attwcoverage:checkRebase note
mainmoved while this was in flight (#503, #505 merged). Rebased, anddocs/BACKLOG.mdconflictedbecause #503 had also claimed the id
D30. Resolved by keeping their entry and renumbering mineto D32/D33, then fixing the cross-references in
docs/providers/README.md. My entries also citedD11, which this PR deletes - reworded, since a citation to a deleted entry is exactly the rotT4records.
What was NOT verified
Narrowed after a Playwright pass in a real Chrome (see B30 and B68 above - both are now verified in
the browser, B68 against a live model). What genuinely remains:
recorded identity, which is the condition a repoint produces; it did not edit a connection through
the dialog and wait out the seed cache. That the two are equivalent is an inference from the code,
not an observation.
AU2was verified over real HTTP rather than through the browser's network panel.curl -Ion/logo.svg,/monaco/vs/loader.jsand a hashed_next/staticchunk is the same evidence a browserwould show, and stronger than a unit test, but no page was loaded to observe it.
helm templaterenders plus a read of howsrc/lib/auth.tstreats unset.Not merged - for review.