Repository navigation
fix(stateless): contain watch loss and preserve Desktop chats - #849
Conversation
…, and preserve template - HCC: keep applied template during unknown channel inventory, block suspension without changing deployment, and add wake capability to the containment gate - Desktop: fence chat deletion to the issuing binding generation and authority scope; distinguish confirmed host revocation from uncertain 401/403; clear drafts and tabs on scope changes; reverify access on fresh authorization - IPC: add scoped chat:delete contract with binding-generation fence - Gate: extend selective hold budget and wire wake capability - Tests: cover cold-start template preservation, cross-principal delete, and host authority classification
HCC now keeps the applied Deployment bound to the current Host UID, reports missing applies truthfully, and derives held readiness from the full PVC, Service, and NetworkPolicy boundary. Desktop captures an environment/user/team deletion fence, persists scoped tombstones with retryable cleanup, separates unconfirmed authorization failures from confirmed revocation, fences authority/navigation epochs, and isolates no-chat drafts by agent. The stateless cache-containment gate gains wake capability, renewable two-cycle holds, CI contract coverage, and an explicit owned-profile T2 entry point.
* origin/dev: (23 commits) fix(evendrive): epoch-guard breadcrumb reconstruction against stale retries fix(evendrive): rebuild move-recovery ancestry from live crumb state fix(evendrive): label the stale trail when post-move path reconstruction fails fix(evendrive): rebuild breadcrumb trail after a crumb move; defer link-opened mutations fix(gfs): align inherited access and breadcrumb versions revert: remove backend changes from GFS UI PR fix: rate limit GFS routes before auth fix: clear CI and CodeQL findings fix: resolve GFS access review regressions style(evendrive): use the 2xs font token for the sticky EvenDrive list header (N3) fix(evendrive): skip empty file-alignment grant after a successful parent update (N2) fix(evendrive): treat re-selecting the displayed merged role as a no-op (N1) docs(evendrive): unify inherited-walk semantics at both walk sites (R1-L3) refactor(evendrive): extract pure inherited-access merge core with property tests (R1-M2) fix(evendrive): keep every contributing ancestor and edit them honestly (R1-H1) fix(evendrive): describe and restore focus in the parent-role dialog (R1-L2) feat(evendrive): use hard-drive icon for EvenDrive nav and header fix(evendrive): surface total inherited-derivation failures in share dialogs (R1-M3) fix(evendrive): judge parent-role bits by the folder's affordances (R1-M1) fix(evendrive): revoke file's direct shares on parent-role confirm (R1-H2) ...
|
@claude please perform a full re-review of PR #849 at head CI/CodeQL is fully green (41/41 checks). Please review the complete PR and the corrections made in response to the earlier internal review, with special attention to:
Please report each finding with file/line references and severity. Distinguish blocking defects from non-blocking observations and coverage gaps. |
|
Claude finished @alfredolopez80's task in 2m 27s —— View job Full re-review of PR #849 @
|
|
@claude please re-review the corrections at PR #849 head CI/CodeQL is fully green again (41/41 checks). Your findings went through independent adversarial review. The surviving corrections are:
F4 was intentionally not changed: the adversarial reviews found no reachable pre-mount call and replacing the non-null callback with a no-op could lose a revocation. Unbounded tombstones remain intentionally deferred to durable server-side deletion state in PR B. Please verify these corrections against your original findings and report any remaining blocking defects with file/line evidence and severity. |
|
Claude finished @alfredolopez80's task in 2m 55s —— View job Re-review of corrections @
|
…-containment * origin/dev: fix(web-search): exclude fallback-only containers from prose fix(web-search): recover malformed comments and inert closes fix(web-search): preserve XHTML self-closing content fix(web-search): bound malformed HTML scanning fix(web-search): respect HTML context and bound CI image build fix(web-search): address title and argument review findings fix(web-search): address internal review regressions and bounded tooling test(web-search): allow runner group to read ephemeral TLS fixtures fix(web-search): cancel upstream work on HTTP disconnect fix(web-search): release fetch slots on HTTP protocol switches fix(web-search): validate fetch destinations and bound response resources
|
Local validation completed on profile clerum-fix-stateless-incident-containment-21489a9a at head 1e15b4c:
Evidence: .local-notes/infra/runs/20260925T155633Z-1e15b4cae555-cec1a6ead8d84c5ebb7286e855360256/evidence.json |
…ecipe triggers Closes R3-L11 (PR #849 review round 3). stripTrailingSentencePunctuation stripped only `.!?`, while DIRECT_TRIGGER_HEAD captures the recipe token up to the next whitespace, so `run daily-report, please` captured `daily-report,` and was not recognised as a direct trigger. The strip set is now `.!?,;:`; a comma followed by a non-filler tail is still rejected. The adversarial linear-time bound is raised from 100 ms to 1000 ms: it guards against super-linear backtracking, not latency, and shared CI runners made the tighter bound flaky. Fail-first: `recovers a direct named recipe request with a natural tail: run daily-report, please` (and `run daily-report,`, `please run daily-report, now`, `run daily-report;`, `run daily-report: now`) failed at 65bc16c (5 failed | 36 passed). Paired control `run daily-report, and delete it` stays false; disabling the tail check turns it red. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Closes R3-L17 (PR #849 review round 3). The T1 reporter sanitizer built the JSON-escaped spellings of the password with json.dumps and its default ensure_ascii=True only, which writes non-ASCII characters as \uXXXX. The Vitest JSON reporter writes with JSON.stringify, which keeps them literal, so a password such as `Qzä"ss` survived as `Qzä\"ss` in the report. The sanitizer now adds the once and twice escaped spellings for ensure_ascii in (True, False). Fail-first: new case 2a in scripts/tests/test-minikube-t1-reporter-report.sh writes the fixture with Node's JSON.stringify (message and a quoted inner JSON document) for two passwords; at 65bc16c it failed with `FAIL: non-ASCII password Qzä"ss was not redacted in both spellings`. Disabling password redaction entirely turns the suite red as well. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Closes R3-L18 (PR #849 review round 3). scripts/tests/test-autoresearch-gates.sh exists and is executable but no workflow or Makefile target ran it, so the autoresearch gate wrappers had no CI coverage. The shell-syntax job, which already runs every sibling scripts/tests/test-*.sh contract, now runs it too. The script needs only git and mktemp, sets its own fixture git identity, and the job already checks out with fetch-depth: 0. Evidence: before this change `grep -n test-autoresearch-gates .github/workflows/*.yml` matched nothing (exit 1); the script passes locally with 10 PASS lines, 0 FAIL, EXIT=0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Closes F5 (PR #849 review). t2_evidence_init opens every evidence file with a `preflight RUNNING` phase. The standalone planner closes it, but neither certification writer did: `make minikube-t2` (scripts/minikube/ t2.sh) and the T1 standalone lane (scripts/e2e/minikube-real-postgres.sh) left `preflight RUNNING` as the phase's latest status, so a run with attestationStatus PASS still reported an open preflight. t2.sh now writes `preflight PASS` at the end of run_preflight_plan, after local-preflight and the planner have passed. The T1 lane writes it once the marker, process and control-postgres readiness checks pass, before the suites start. For certification evidence a `preflight PASS` event changes neither attestationStatus nor laneAttestationStatus, so the T2 contract and the attestation schema are unchanged. Fail-first: two static assertions in scripts/tests/ test-minikube-t2-contract.sh failed at 65bc16c with `FAIL: T2 certification evidence never closes the preflight phase opened by t2_evidence_init`. Removing either new write turns its assertion red. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…handler handleSessionsList decoded query.cursor with decodeSessionsCursor, which returns null for a malformed cursor or one minted for another user/agent scope, and then treated null exactly like "no cursor": the store was queried from the start and page 1 came back as if it were the requested page. The handler now throws `Invalid sessions cursor` before reading the store when a cursor was supplied but does not decode for the scope. The HTTP route (handleSessionsListRoute) already answers 400 `Invalid sessions cursor` through parseSessionsCursorParam before calling the handler, so its contract is unchanged; the guard covers every other caller of the SessionsListHandler contract. Fail-first: in sessionRouteHandlers.test.ts a malformed cursor and a cursor from another agent scope resolved with page 1 at 65bc16c (`promise resolved ... instead of rejecting`). They now reject and the store spy records 0 reads; a valid-cursor control returns the next page with exactly 1 store read. Disabling the guard turns both cases red. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The renewal mock added for the fresh-bootstrap case built its token key from a string concatenation, so its type widened to an index signature and `tsc --noEmit` on the test project failed to assign it to GfsHostToken. A literal key type keeps the same runtime value and makes the mock assignable again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… intent When the GFS renewal of a Ready held runtime failed, the catch reported a controller error through enqueueControllerError, which also enqueues an administrative `failed` outcome. The pass then ends with the runtime deployed and Ready and reports `succeeded`, so one administrative intent received both outcomes for the same generation and control-api stored both. The catch now emits only the controller_error telemetry; the pass's single outcome is the Ready runtime. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
forwardCancelToHost replaced a missing userId with 'desktop-app', so mcp-host would run its cancel ownership check against a stand-in identity. The route always passes the verified token subject, which is typed as a string; the forwarder now requires it and throws before any upstream request when it is empty. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t resolution mutatingCallSignal only listened for the response 'close' event, but the signal is built after the host resolution and wake-hold work. A client that disconnected during that window had already fired 'close', so the listener never ran and the approve, deny, set-model or cancel POST was still sent upstream with nobody waiting for it. The signal now starts aborted when the response is already closed without having finished. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ne rejects A session catalog request issued before invalidateSessionCatalog() that rejects afterwards used to delete its cache key unconditionally, evicting the newer request cached under the same key and forcing a redundant upstream call. The rejection handler now evicts only its own entry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ent leaves The session rename PATCH used a plain upstream timeout, so a client that disconnected (before or after host resolution) left the upstream call running until the timeout. It now uses mutatingCallSignal with that same timeout. The R3-L6 disconnect table covers the route, and the test token carries host:session:write so the request reaches the upstream. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds the composition property requested in review: merging cursor page A and then page B equals one merge of both pages when they report disjoint chats. A witness checks both pages reached the result, so a merge that ignores its sessions fails. Overlapping pages do not compose by design (a later report updates the title, updatedAt stays from the first report); fast-check finds that counterexample when page B is allowed to overlap. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@claude please run a full re-audit and a closing audit of this PR at head Scope, in this order:
Evidence at this head:
Please report a table (source comment, id, verdict, evidence), then a list of anything still open with severity (Blocker / High / Medium / Low), then a final line: MERGE-READY or NOT MERGE-READY with the blocking items. |
|
Claude finished @alfredolopez80's task in 8m 13s —— View job Closing re-audit @
|
| Source | Id | Verdict | Evidence @ 051cefaa |
|---|---|---|---|
| R3 | R3-H1 McpServer relist → token downgrade+rollout | FIXED | frontsOAuthServer collapse removed; normal path DEFERS (hostReconciler.ts:2169, status :5272); test lifecycle.test.ts:1931 asserts issue not called + zero deployment writes; 2327789e |
| R3 | R3-H2 refreshGfsOnly rolls w/o mint |
FIXED | consumes bootstrap when Ready+same revision (hostReconciler.ts:1934-1940); test asserts bootstrap-state='consumed', rollout-required≠'true'; 7eed4e73 |
| R3 | R3-M1 idempotent GETs lost post-wake retry | FIXED | 7 GETs+/messages pass retryUntilDeadline:true, mutating routes maxAttempts=1 (wakeAndHold.ts:709); real-http test 3 reads vs 2; a9a11cd0 |
| R3 | R3-M2 revocation arrives as "uncertain" | DEFERRED-OK | option B (confirmed only while a cached token <5 min exists) documented in body; option A → #941 |
| R3 | R3-M3 hand-built 403 fixtures (no witness) | FIXED | IPC-shaped ipcHostAccessRevoked/ipcGenericForbidden + same-scope controls that fire the classifier; 777278c0 |
| R3 | R3-L1 sidecar failure rejects committed index | FIXED | sidecar written before index, each atomic; test witnesses sidecar write + unchanged catalog; 0432fdbc |
| R3 | R3-L2 crash window drops tombstone | FIXED | sidecar is a superset a valid index ignores, realigned next save; 0432fdbc |
| R3 | R3-L3 sidecar claims untested | FIXED | df328604 covers EACCES/EIO rethrow, wrong-shape→empty+warn, precedence |
| R3 | R3-L4 cancel/rename 8s first attempt | cancel FIXED / rename DEFERRED-OK | cancel first attempt no client timeout (rpc.ts:1402-1407); rename keeps 8s by design (idempotent, Desktop retries) |
| R3 | R3-L5 download header wait ⊇ redaction | REFUTED | headers use upstreamTimeoutMs, body re-armed with artifactDownloadTimeoutMs (rpc.ts:1560-1585); docx/pdf skip redaction, text bounded 50 MB |
| R3 | R3-L6 mutating: no deadline/abort on disconnect | FIXED | mutatingCallSignal aborts on res.closed+once('close') incl. during host resolution & rename; ab924072/2279431a/70b7ef4e |
| R3 | R3-L7 only /messages pins deadline pre-resolve | FIXED | wakeDeadlineMs computed before resolveHostConnectionForUser in all 12 held routes; describe.each test; 51ea9a15 |
| R3 | R3-L8 subject_mismatch not a revocation |
FIXED (code) |
hostAccessDenial.ts:25-28 → host_access_denied; test host-access-denial-code.route.test.ts:226; cd8e1159. PR body summary still lists it under host_access_revoked |
| R3 | R3-L9 denial reasons hand-copied | FIXED | cross-service guard test reads all 3 sources & asserts agreement; ba864331 |
| R3 | R3-L10 invalid cursor w/o status never dropped | FIXED | over-long cursor dropped, 408 non-terminal, 409 terminal; a8e36982 + mcp-host 7e56e5fc |
| R3 | R3-L11 run daily-report, please no trigger |
FIXED | strips .!?,;:, filler now/again/please/immediately; revert-sensitive test; 07c16b0f |
| R3 | R3-L12 cache-loss ready:false for new hosts |
DEFERRED-OK | fail-closed choice documented in Known deferred boundaries; → #942 |
| R3 | R3-L13 failed held GFS mint → deployed:false |
FIXED | keeps deployed:true, telemetry not failed-intent; test asserts single succeeded+<1000ms; 98685c9d+6ec74d72 |
| R3 | R3-L14 hand-written credential fixture | FIXED | mintedRuntimeCredentialRecord from a real mint pass + assertProducerRuntimeAnnotations; aafb5022/9f4d347c |
| R3 | R3-L15 ipcHostWaking accepts any channel |
FIXED | 777278c0 |
| R3 | R3-L16 dead rename revoked-matcher alt | FIXED | 62f6968f |
| R3 | R3-L17 T1 reporter leaks non-ASCII password | FIXED | both ensure_ascii spellings redacted (minikube-real-postgres.sh:284-293); Node-built fixtures assert; 15dd1ba6 |
| R3 | R3-L18 autoresearch gates not in CI | FIXED | ci-public.yml:538 runs the self-test; c6690eac |
| Claude r1 | F1 read-path revocation title leak | FIXED (prior) | resolved in earlier rounds |
| Claude r1 | F2 dropped bindingGeneration fence |
FIXED (prior) | consumed in rename fence |
| Claude r1 | F3 approval-notif lacks block guard | DEFERRED-OK | mitigated by UI notif filter + server 403 |
| Claude r1 | F4 non-null hideHostAccessRef.current! |
DEFERRED-OK | intentional; no reachable pre-mount path |
| Trigger | F5 | not located | no finding labelled F5 appears in the PR thread text available to me — please point me at it if it exists (flagging per "no verdict = open") |
| Trigger | F6 T0 attestation lost when T1 fails | ROUTED → #940 | listed in Follow-ups |
| R1/R2 | R2-B1, R2-H1, R1-H1..H5 | FIXED (prior + hardened here) | jozer approved at 65bc16c26; HCC/rpc hardening this round |
| R1 | R1-M1 / O1 hold fail-open on admin revocation | DEFERRED-OK | OAuth re-grant now fail-closed; channel-ingress residue → #942 |
| R1 | R1-M4 tombstone scoped to session team | FIXED | deletion identity = env+user (chatStore.ts:108-110), survives team hop |
| R1 | R1-M6 exact 403 body not unique | FIXED | header contract x-host-access-denial-reason, body pinned {"error":"Forbidden"} |
| R1 | R1-M7 getIndex O(N²) cleanup |
FIXED | per-agent on read + full sweep once per bind; 48e3b7fb |
| R1 | R1-M11 fake upsertMessages diverges |
FIXED | delegates to a real ChatStore; f48785b2 |
| R1 | R1-M8 / D3 uncertain acts like revoked | DECISION | documented (hidden+deselected until re-verified); not re-litigated |
| R1 | R1-M14 HostWizard stateless creation | DECISION | kept in PR by design; documented |
| jozer | #3 team-hop UI race | DEFERRED-OK → #945 | durable main-process suppression holds |
| GHAS | 1552/1556/1558/1559/1599/1600/1601/1602/1605 | FIXED | CodeQL run success at this head; intent-recovery keeps \S{1,128}, disjoint classes, no nested quantifiers |
2. Follow-up issues
The PR body routes each deferred item to a specific issue: #940 (attestation/F6), #941 (mint-403 revocation code / R3-M2 option A), #942 (channel-ingress scope + wake template / R1-M1 residue + R3-L12), #943 (turn duplicated on reconcile), #944 (verifyRpcToken empty sub), #945 (team-hop race / jozer #3), #825 (durable deletion), #930 (flaky grok leak assertion). Every deferred boundary in the body has a matching number, and no deferred item is left un-routed.
gh is not permitted in this sandbox (requires approval on a private repo), so I could not independently confirm that #940–#945/#825/#930 actually exist and that each issue body describes the residue accurately. The routing is present and internally consistent in the PR description; please confirm the issues themselves out-of-band.
3. Regression sweep (65bc16c26..051cefaa8)
No new code regression found. The delta is hardening; every negative assertion I checked carries a liveness witness. Specifically on the areas you flagged:
mutatingCallSignal(rpc.ts:200-213): aborts on the already-firedcloseduring host resolution (res.closed && !res.writableFinished) and on later disconnect (res.once('close')), guarded bywritableFinishedso a normal completion never aborts — correct.- Tombstone sidecar order (
0432fdbc): sidecar-before-index, atomic, a sidecar failure rejects before any catalog change — correct. - Catalog request invalidation (
b29077da): the late rejection evicts only its own entry via identity check (get(key) === entry), so an invalidated request can't clobber the newer one — correct, revert-sensitive test with a witness. - Task cancel (
f0dab25a): now rejects an empty caller subject before any request (was a'desktop-app'stand-in identity) — a genuine hardening.
4. PR-description accuracy — 2 stale statements (code correct, doc wrong)
subject_mismatchmapping (R3-L8). The summary's rpc-proxy bullet listshost_access_revokedfor "team_membership_missing,directory_grant_missingandsubject_mismatch". The shipped code (hostAccessDenial.ts:25-28, commitcd8e1159) maps only the first two to revoked;subject_mismatch → host_access_denied. The code is the safer choice (does not hide an agent the user still owns). Fix the body sentence to dropsubject_mismatchfrom the revoked list. Fix this →- Intent-recovery timing claim. The body says the 200k-char test "asserts it finishes under 100 ms"; the assertion in
toolUseLoopIntentRecovery.test.tswas relaxed to<1000 ms(commit07c16b0f) to avoid CI flake. The super-linear-backtracking guard still holds; only the stated bound is stale.
5. Decisions — not re-litigated
Control-UI stateless creation in-PR, uncertain-hosts-hidden (option B), never-pruned tombstones (until #825), first-mutating-attempt-no-timeout / rename keeps 8 s, and the T2 attestation change deferred to #940 — all confirmed present and consistent in the code and body; left as your decisions.
Still open (by severity)
- Blocker / High / Medium: none.
- Low (documentation only): the two stale PR-body statements above (§4). Neither reflects a code defect.
- Deferred with issues (not blocking): R3-M2/host access: return a machine-readable revocation code on the RPC token mint 403 #941, R1-M1+R3-L12/hcc: re-observe channel ingress and refresh the wake template after a CommunicationChannel cache outage #942, F6/minikube-t2: persist the T0 attestation independently of the T1 outcome #940, jozer docs: minikube-first onboarding, capability tour, deeper architecture #3/desktop-app: short team-hop notification race in the renderer chat path #945, tombstones/stateless(storage): preserve one canonical conversation store across lifecycle transitions #825, plus F3/F4 and the D3 decision.
- Verification gap (not a defect): could not confirm minikube-t2: persist the T0 attestation independently of the T1 outcome #940–desktop-app: short team-hop notification race in the renderer chat path #945/stateless(storage): preserve one canonical conversation store across lifecycle transitions #825/test(control-api): grokSubscriptionOAuth assertNoLeak fails at random on the OAuth state #930 exist from this sandbox (
ghblocked); F5 could not be located in the thread text.
MERGE-READY
Every blocking finding from every round is fixed with a revert-sensitive test, the delta introduces no regression, CI/CodeQL are green, and the residuals are documented-and-routed decisions. The only cleanups are the two non-blocking PR-body wording corrections in §4; I'd fix those before merge but they don't gate it. Please independently confirm the follow-up issues exist (I couldn't from here). Consistent with the PR's own scope, this contains #396/#826 without fully closing them.
This is my read from reviewing the diff, not a formal GitHub approval.
|
Thanks. Follow-up on the closing audit at §4 PR description, both Low items fixed in the body:
Verification gaps you reported:
No code changed; the head stays at |
Summary
PR A contains the immediate #396/#826 incident containment on top of current
dev.active,suspendedordraining) is preserved, not rewritten toactive; the effective view is flaggedsuspensionBlocked, so no new suspend or drain is issued while the channel inventory is unknown. The existing stateless Deployment template, mounts, PVC, and SQLite path are kept while reconciliation recovers; HCC does not replace them with a fallback layout. The cache-derived effective view (effectiveLifecycleFromCache) still reportsstate: 'active'for a held Host that has no persisted state yet. Confirmed channel, Desktop, and placement incompatibilities continue to follow the existing rejection policy.spec.desktop, unsatisfiable SharedFileSystem co-location) or that has stateless disabled is forced to durablestate=activewith one replica, including when it wassuspendedordraining, so a rejected Host never sits suspended without an operator-visible condition. This is documented in theassessLifecyclecontract.host_access_revokedclears cached remote data. Any other 403 is treated as uncertain: persisted local data is kept, but the Host is hidden and deselected until it is verified again. Uncertain Hosts are hidden rather than shown because, until the mint 403 carries a revocation code (host access: return a machine-readable revocation code on the RPC token mint 403 #941), a real revocation reaches Desktop as uncertain whenever no cached RPC token exists for that Host; showing it would expose cached transcripts of a revoked Host in the most common case. Revocation is confirmed only while a cached token younger than 5 minutes exists for that Host (option B); option A is tracked in host access: return a machine-readable revocation code on the RPC token mint 403 #941. Late notifications and task results cannot resurrect a deleted chat or a chat of a revoked Host in the same team scope.errortext and add a machine-readablecode:host_access_revokedforteam_membership_missinganddirectory_grant_missing, andhost_access_deniedfor every other own denial (host_disabled,host_missing, missing claim,subject_mismatch, 404 without a direct-run binding, auserIdorhostRefmismatch in the lookup response, empty URL, unknown reason). Passthrough 403 bodies from mcp-host never get acode.run daily-report). Direct recipe triggers accept a trailing.,!,?,,,;or:on the recipe name and the filler wordsnow,again,please,immediately; any other tail is not a trigger. It stays linear-time on adversarial input (a 200k-character test asserts it finishes under 1000 ms) and still rejects shell-like text and tokens over 128 characters.clerum-devcluster until an operator can create one from the UI. Shipping the UI change here lets the team test the whole path on dev right after merge, without opening a PR whose only purpose is to expose the creation flow. The change is limited to the explicit creation option and its payload/copy; it does not change how existing Hosts are created or reconciled.beforeAllor import error) has no assertion results, so its file-level message is now printed after theFAILED FILE:line. The reporter redaction keeps the JSON valid when a DSN appears inside a JSON string (it no longer consumes the escape that closes the string) and redacts the branch database password verbatim, JSON-escaped once and twice in bothensure_asciispellings, and percent-encoded by Python and JavaScript. The macOS stress suite bounds its HTTP agent below the kernel accept queue, and the 150k-deep fixture is verified by an iterative depth measurement instead of a V8 build-specific serializer stack limit.PR B remains responsible for the durable DB migration from #825 and the progressive catalog from #827. Local Desktop deletion markers are not server-side cross-device tombstones.
Exact-head verification
051cefaa84a18d9dbb6e9573a229c39fb761ea28, the current PR head (review-fix round 3 below). Every result in this section was produced on that exact head.origin/devaaa85813009d0748735e2edfb907865c269969d1, which includes feat(files): add FileReference v1, inline file attachment ingress and structured GFS references #822 (file reference ingress); it is an ancestor of the head (checked by the launcher before the run and again after it).051cefaa8: 42 of 42 check runs completed with conclusionsuccess(verified withgh apion the commit). The previous certified head65bc16c26also had 42 of 42 and aMINIKUBE_T2_PASS.MERGEABLEandBLOCKEDonly by the required review.Canonical local Minikube T0/T1/T2 certification on the branch-owned profile/context, run with
make minikube-t2(each figure read from the run log and the harnessevidence.json):full-reconcile(200 s), thenpre-gate-sync(724 s) ran the package suites and renewed the port-forward hold before the final preflight.NOT_RUN; they are not part of this verdict.preflight PASSafter the planner), per086648635.051cefaa8. Earlier attempts on the previous head65bc16c26failed for reasons outside the product code and are not counted: a random-state false positive in the unrelatedcontrol-api/test/services.grokSubscriptionOAuth.test.tsleak assertion (itsassertNoLeakregex is case-insensitive and can match the random base64urlstate; tracked in test(control-api): grokSubscriptionOAuth assertNoLeak fails at random on the OAuth state #930), a dirty tree caused bynext buildrewritingprofile-ui/next-env.d.ts(fixed in this PR), and stalenode_modulesinworkflow-recipeson the local worktree (reinstalled withnpm ci).Review-fix round (head
65bc16c26, aftera696391a6)Thirteen commits since
a696391a6address the round-1/round-2 review findings and the findings found while verifying them, plus two merges oforigin/dev(713391c36, thenaaa858130with #822). Each fix ships a test that fails when the fix is reverted (mutation or revert proof), and each negative assertion has a liveness witness.6df1be439).x-host-access-denial-reason(body unchanged), rpc-proxy maps it tocode; wake hold default 48000 ms in both; artifact download deadline; retry only for/messages; 1 s admission floor. rpc-proxy strips a reservedcodefrom a passthrough mcp-host 403 (ce53e25ec). rpc-proxy never re-sends a mutating POST after the response headers arrive: a body failure after the headers returns 502 without a second request. A connection error before the headers is treated as a stopped Host, and approve, deny, set-model and cancel are sent once more after the wake. The first mutating attempt has no client-side timeout by design; it is aborted only when the calling client disconnects. Deploy control-api before rpc-proxy.codeonly; tombstone sidecar; cleanup retry by environment and user; home team pinned during a team hop.me.teamIdsource, toast dedupe, and the chat store is rebound after a failed team restore (13f5aa0f0)..,!,?,,,;or:on the recipe name and the filler wordsnow,again,please,immediately; any other tail is not a trigger.f3d709789), reporter contract test registered.aaa858130(nine conflicts): package versions kept at this branch's higher values (hcc 0.1.368, mcp-host 0.1.473, rpc-proxy 0.1.102, withreleaseManifest.ts); the stress test takes dev'smaxSockets: 100;useAgentChatControllerbuilds the feat(files): add FileReference v1, inline file attachment ingress and structured GFS references #822 file references before the send clears the composer and keeps this branch's chat-store rebind and deletion fences; thesendAgentMessage.fileReferencesaccepted-ack witness readsupsertMessages, which is how this branch persists the user turn on an async ack.next-env.d.tsis now the production variant (./.next/types/...), which is whatnext buildwrites, so the T0 build no longer dirties the tree and the exact-head preflight no longer stops withDEVELOPMENT_SCOPE_REQUIRED(cc111db4d); this followscontrol-ui, which already tracked that variant.tsc --noEmitand the package builds are part of the T0 run above; the Desktop UI project still reports only the three pre-existing errors inui/src/lib/imageHeaderDimensions.ts.Review-fix round 3 (head
051cefaa8, after65bc16c26)Thirty commits address the round-3 review (comment 5896495722) and the findings of two follow-up audits of every review comment on this PR. Each fix is test-first: the test was red before the fix, and a vacuity mutation turns it red again.
oauth:user-tokenis deferred instead of downgraded when the McpServer read is unobservable (2327789ed); a fresh bootstrap is consumed on a GFS-only refresh when the Deployment runs its revision (7eed4e73c); a Ready runtime stays deployed when the held GFS renewal fails (98685c9d9), and that failure is reported as telemetry, not as a failed administrative intent (6ec74d72d); the draining revert requires a stateless fresh read (e073d00b4); the runtime credential fixture is derived from a real mint pass (aafb50229).a9a11cd0e); the deadline is pinned before host resolution (51ea9a154); mutating upstream calls are aborted when the client disconnects (ab9240721), including a client that left during host resolution (2279431a5) and the session rename (70b7ef4ec); task cancel refuses an empty caller subject (f0dab25a6);subject_mismatchis a denial, not a revocation (cd8e11598); cross-service guard for the host-access denial contract (ba864331d).0432fdbcd, tests indf3286049); pending chat cleanups are swept per agent on read and once per bind (48e3b7fbe); over-long session cursors are dropped and 408 is no longer terminal (a8e36982c).30ecd0dfe), and a late rejection of an invalidated request keeps the newer entry (b29077dae); IPC-shaped 403 fixtures with same-scope controls (777278c0f); the unreachable rename alternative is removed from the revoked matcher (62f6968f0); the chat fixture delegates to the realChatStore(f48785b21); the catalog merge composes over disjoint pages (051cefaa8).07c16b0fb); an invalid sessions cursor is rejected (7e56e5fc7).15dd1ba6c); the preflight phase is closed in certification evidence (086648635); the autoresearch gate self-test runs in CI (c6690eac2).Review items resolved without a code change:
docx/pdfare binary formats and skip redaction; text redaction is bounded at 50 MB and measured at 1.2 s for 100 MB.70b7ef4eca client that leaves also releases the upstream call.HOST_ACCESS_*constants; remaining literals in untouched tests are accepted.65bc16c26and by every commit here.updatedAtstays from the first report; this is intended and documented in the test.Follow-ups
verifyRpcTokenaccepts an emptysub(pre-existing): rpc-proxy: strengthen authentication identity validation #944.Size
Diff against
origin/dev: 202 files, +21345/−1866 (desktop-app 84, rpc-proxy 36, host-context-controller 22, scripts 20, mcp-host 15, control-api 8, control-ui 6). The change spans every hop of one incident path (Host hold in HCC, wake-and-hold in rpc-proxy, revocation handling in Desktop) and the certification harness that proves it; splitting by service would ship a hold without its client behavior or a client behavior without its server. The autoresearch gate scripts were merged fromdevand are not part of the change.Security review and CodeQL
\S{1,128}bound, uses non-overlapping character classes, has no nested quantifiers, and checks the tail with string operations instead of a second regular expression.Known deferred boundaries
falseis used, and a retainedtrueis neither re-granted nor revoked until the read succeeds or the credential reaches its renewal window, at which point HCC mints fail-closed without the scope.hasChannelIngressduring the hold still comes from the retained annotation, and GFS-only renewal does not consult OAuth; both are the deferred boundary tracked in hcc: re-observe channel ingress and refresh the wake template after a CommunicationChannel cache outage #942. A failed GFS renewal during the hold keeps the runtimedeployed:trueand reports the failure in the status message.ready:falsewith 'Waiting for authoritative scope observation', even when its pods are Running; the first synced mint writes the annotation and the status clears. This is the fail-closed choice pinned by the B2 integration test.codecontract above is a separate product-contract change.me.teamIdsource inuseAppController, because Remove Team Token PR1: User session, live authorization, and aggregate all-team access #334→Remove Team Token PR2: Runtime actions use validated access paths #594→Remove Team Token PR2.1: Host-route admission security prerequisite #797→Remove Team Token PR2.2: Host-ref authority and validation #795 keepSessionMe.teamIdwhile deprecating the team-bound session server-side; the known merge conflicts with that stack are the package manifests and lockfiles,external-rest-api/src/releaseManifest.ts,rpc-proxy/src/routes/rpc.tsandrpc-proxy/src/services/wakeAndHold.ts(plus its test). A corruptindex.jsonis quarantined, and the tombstones are recovered from the.tombstones.jsonsidecar, which is read only when the index is missing, malformed or quarantined.saveIndexwrites the.tombstones.jsonsidecar before the index whenever the tombstone or pending-cleanup lists change, each file with its own atomic rename, and a failure in either write rejects the operation before the index is committed. No sidecar is written for an agent that never had a tombstone. A crash between the two writes leaves the sidecar a superset of the index, which a valid index ignores and the next save realigns. Everychat:getIndexretries the pending cleanups of that agent only; a full sweep runs once per authority bind. An unreadable sidecar (invalid JSON or wrong shape) logs one warning and is treated as empty; a filesystem error such as EACCES or EIO is rethrown. Ifindex.jsonand the sidecar are both unreadable, no other copy exists and a chat deleted locally can be hydrated again from the server, which is not deleted server-side. Quarantining the sidecar and surfacing the loss in the renderer were evaluated and deliberately not adopted here; durable server-side deletion in stateless(storage): preserve one canonical conversation store across lifecycle transitions #825/PR B is the fix for that case. This PR does not claim that local deletion is permanent.x-host-access-denial-reasonheader on its 403 (the 21-byte{"error":"Forbidden"}body is unchanged) and rpc-proxy maps it tocode; rpc-proxy alone would reporthost_access_deniedfor every own denial.scripts/minikube/t2-common.shhas no net diff againstdev, and the earlier planner-only PID auto-clean was removed. A dead, malformed or duplicate registered port-forward record still fails the final exact-head preflight withPORT_FORWARD_CONFLICTand is never deleted;test-minikube-t2-process-owner.shcovers the dead-PID and malformed-PID cases.Gate boundaries
NOT_RUNinside the final exact-head T2 command; they are separate evidence lanes and are not claimed as part of that verdict.Refs #824, #396, #826.
🤖 Generated with Claude Code