Skip to content

fix(review): preserve PR 507 remediation stack - #520

Draft
jmagar wants to merge 10 commits into
mainfrom
claude/pr-507-review-issues-ba75e1
Draft

fix(review): preserve PR 507 remediation stack#520
jmagar wants to merge 10 commits into
mainfrom
claude/pr-507-review-issues-ba75e1

Conversation

@jmagar

@jmagar jmagar commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Summary

Preserve and review the complete PR #507 remediation lineage as one superset branch.

Why This Exists

Nine shared remediation commits plus a final review-fix commit were local-only and at risk of becoming orphaned.

Resolution

The complete ten-commit superset is published here; the shorter review-fixes-507 branch is retained only as historical ancestry.

Reviewer Considerations

Confirm which fixes already landed through other histories before rebasing. Pay particular attention to protected routes, stable error kinds, skill exposure, source reachability, and test isolation.

Behavior Changes

Restores and hardens runtime, gateway, authorization, skill exposure, and portability behavior from the review stack.

Verification

  • Exact local superset published
  • Current-main deduplication and CI remain required

Risk

High overlap with later branches; deduplicate before making ready.

jmagar and others added 10 commits August 27, 2026 09:41
… backstop

Three review findings from PR #507.

`gateway.add`, `gateway.import`, and `gateway.reload` were reachable from a
protected subset route. None takes an upstream name `ensure_visible` can check
— the name does not exist yet, or there is none — so an admin-scoped token
minted for an unrelated subset could add a stdio upstream and reload it into
existence. `gateway.test` already refuses an inline spec for exactly this
execution risk. Adds `GatewayEnrichmentScope::deny_on_subset_route` and applies
it to all three.

`gateway.skills.list` ignored the route scope entirely: it validated the
`upstream` filter against the full config and enumerated every upstream's
skills. Its `not_found` message also confirmed which names exist. Scope is now
enforced before the existence probe, so a hidden upstream is indistinguishable
from an absent one, and the unfiltered listing is restricted to route-visible
upstreams.

The HTTP transport backstop applied a fixed 30s cap to every non-`/mcp` route.
`/v1` handlers relay upstream calls too (palette execute, gateway dispatch,
`gateway.oauth.wait` at 120s), so this returned a bare 504 for calls that had
already succeeded — the regression documented on `LabConfig::http_request_timeout`
— and blinded the upstream circuit breaker, which declines to count `Cancelled`
as a failure precisely because the backstop is derived to exceed the upstream
deadline. Since HTTP_REQUEST_TIMEOUT_MARGIN is 30s the configured value is
always >= the old cap, so the path split was dead logic; the backstop is now
config-derived on every route.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The `land:` merges behind #502/#503 dropped `mod` declarations while leaving
the files on disk. Where something referenced the missing symbols that showed
up as a broken build; where nothing did, it was silent. `paginate.rs`,
`listing_bounds_tests.rs`, and `listing_timeout_tests.rs` were orphaned that
way and eleven tests stopped running without a single warning — rustc never
sees an undeclared file, so there is no `dead_code` lint to fire.

Adds `xtask::module_reachability`, which walks `crates/*/src` and asserts every
`.rs` file is declared by its parent (or attached by a sibling `#[path]`, as
`dispatch.rs` does for `dispatch_tests.rs`). Wired into `just lint`.

Resolves the three existing orphans it found:

- `listing_timeout_tests.rs` is declared again; both tests pass unchanged.
- `paginate.rs` is deleted. `catalog_pagination.rs::collect_bounded` supersedes
  it with strictly tighter bounds (64-page cap plus per-page item, cursor-byte,
  and deadline caps), and the wall-clock cap it also owned was already restored
  into `resources_list.rs` as `catalog_listing_timeout`.
- `listing_bounds_tests.rs` is deleted rather than revived: 8 of its 9 tests
  assert the superseded contract, in which a looping cursor yielded a truncated
  but successful listing. `collect_bounded` deliberately makes a bounds breach
  a hard error — see `a_bounds_breach_is_still_an_error_not_an_empty_catalog`.

`upstream/CLAUDE.md` documented `paginate.rs` as live, including a wall-clock
guarantee that had not been compiled in months; corrected to describe
`catalog_pagination.rs`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ox probe

Two regressions where the land merges dropped *call sites*, and the repair
completed the deletion instead of restoring the callers.

`project_operator_skills` emitted five of its seven fields. `identity` and the
structured `exposure` decision are a documented contract — the worked JSON in
docs/guides/SKILLS_AND_LOADOUTS.md shows both, and the reason strings are named
API vocabulary — and dropping them left `SkillExposureDecision::status` /
`reason` / `matched_pattern` dead on the operator path, so an operator could no
longer tell "no pattern matched" from "never advertised".

`sandbox_absent` lost its two call sites and was then deleted outright. `msb
remove` reports failure both when cleanup genuinely broke and when the sandbox
was already gone (swept by a prior pass, or a crashed microVM); only the
listing separates them. Without the probe every benign already-absent sweep
recorded `cleanup_failed`, emitted a warning, and left the owner permanently
accounted as leaked. Both branches probe again.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CI never validated this branch — every job died on the runner's missing
`python3` — so none of this was caught.

Reverts the `gateway.add` / `gateway.import` / `gateway.reload` subset-route
deny added in 02fb557. Investigation showed it was wrong: the PR's own
docs/runtime/OAUTH.md enumerates the restricted operations and these are
deliberately not among them, and
`gateway_add_through_mcp_protected_route_suppresses_hidden_enrichment_suggestion`
explicitly asserts a subset route may add a gateway. The route bounds which
named upstreams you may touch; it does not forbid creating new ones when the
token carries gateway admin. (The residual asymmetry — `gateway.test` refuses
an inline stdio spec while `gateway.add` reconciles one into existence — is
left for the maintainer to judge.) The `gateway.skills.list` scoping from that
commit stands: it is a discovery operation over existing upstreams, which the
doc does cover.

`gateway.import_pending.approve` gained an `ensure_visible` check that
contradicts the pre-existing
`gateway_pending_import_approve_through_mcp_protected_route_suppresses_hidden_enrichment_suggestion`.
A pending import names a *discovered* server that is not configured yet, so it
is never in the route's visible set and the check could only ever reject.
Approving creates an upstream exactly as `gateway.add` does; the scope is
threaded through so the enrichment suggestion is suppressed, which is the
actual intent. Reverted, and the action dropped from the enumeration test that
asserted the opposite.

`Cancelled` was threaded through five layers with dedicated tests proving the
kind survives each hop, then folded into `internal_error` at the MCP surface.
The resource handler additionally collapsed `Cancelled`, `Timeout`, and
`TooLarge` into one `upstream_error`. These are distinct documented kinds with
different recovery advice (docs/dev/ERRORS.md) — `cancelled` is explicitly not
automatically retryable, so an agent told only "could not be fetched" retries
work the pool already classified as withdrawn. Cancellation is caller-driven
and no longer logs at ERROR, which was paging operators for healthy upstreams
on client disconnects. `handler_operational_failures_are_redacted_upstream_errors`
now asserts the oversized and upstream-fault branches separately, which its
loop always exercised but could not distinguish.

`trusted_read_only_tools` has no production reader — the read-only gate uses
the upstream's own `readOnlyHint` — but stays settable via
`gateway.code_mode.set` and advertised in the catalog. Setting it now warns
rather than silently accepting an inert security control.

Also reverts the `sandbox_absent` restoration from 68c67eb: the probe was
superseded by this branch's `reconcile_failed_cleanups`, and
`absent_failed_cleanup_is_reconciled_and_active_count_recovers` asserts a
failed cleanup stays accounted until absence is proven by reconciliation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Takes the suite from 6 failures to 0. These were invisible because CI never
ran on this branch, and `main` has not compiled for 30 commits, so nothing
here had been executed in a long time.

Microsandbox reconciliation decided liveness with `Path::new("/proc/{pid}")
.exists()`. `/proc` is Linux-only, so on every other Unix each owner read as
dead and reconciliation deleted sandboxes belonging to *live* processes,
including the running one. Replaced with a `kill(pid, 0)` probe that fails
closed: `ESRCH` is death, `EPERM` (owned by another user) is life, and anything
we cannot classify is left alone. A pid outside the platform's range cannot
name a live process, so that is treated as proof of death — which is how the
reconciler still collects the dead owner the test plants.

Four tests read the developer's machine instead of a fixture:

- `settings_config_update_dispatch_persists_and_rejects_stale_previous` loaded
  the real `~/.labby/.env`. On any host that actually runs Labby that file
  defines `LABBY_MCP_HTTP_PORT`, so `mcp.port` reported as env-shadowed and the
  test failed for reasons unrelated to it. Now pins `set_test_lab_home`, as its
  sibling test already did.
- `gateway_import_result_has_correct_shape` claims to exercise "empty
  discovery" but walked the real home for editor MCP configs, then tried to
  import whatever it found. Adds a `#[cfg(test)]` discovery-home override
  mirroring `set_test_config_toml_path`, and pins it at an empty directory.
- `disabled_code_mode_app_denies_cached_resource_reads` and
  `stateless_http_fresh_tools_list_observes_code_mode_app_state_changes`
  disabled the app through the mirrored session atomic. A manager-backed server
  reads the published config directly so a config mutation cannot race that
  atomic, and `mcp_ui_enabled` now defaults to false, so neither test was
  toggling anything. Both drive the config, preserving their actual intent.

`timeout_and_cancellation_clean_private_staging_and_release_capacity` waited a
fixed 5ms for staging cleanup to follow an aborted acquisition, which is not
reliably enough under parallel load. Polls to a deadline instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Correctness

- access/store.rs: a Skill Library commit that succeeded was reported as a
  failure when the lease rollback failed, so callers retried and duplicated
  work that had already landed. The executor outcome is now authoritative and
  the rollback failure is recorded as observability.
- access/store.rs: arbitrary executor closures now run under the connection
  mutex, so one panic poisoned it and made every subsequent authorization
  permanently unavailable. Recover from poisoning instead; an unwind drops the
  transaction, which rolls back.
- artifacts/store.rs: a failed workspace promotion or symlink rejection leaked
  the fully materialized staging tree with nothing to reap it.
- Cargo.toml: the non-optional labby-gateway dev-dependency enabled `testkit`
  for every test build, making `proxy-testkit` inert and pulling the gateway
  crate into slice builds the feature contract exists to isolate. Gate the
  testkit-dependent tests on `proxy-testkit` instead.

Observability

- api/router.rs: four authorization failure arms on the new project-scoped
  protected-route binding logged nothing, so an empty project tool list had no
  discoverable cause. Also replaces a request-path `expect` with a graceful
  rejection.
- skill_library/auth.rs: log the concrete AccessStoreError before collapsing
  Locked/Corrupt/DiskFull into one opaque kind; audit and log malformed
  canonical identifiers; promote a `debug_assert` guarding a hardcoded
  permission to a real check.
- gateway/projection.rs: redact upstream error text before it reaches an
  operator-visible warning, as every other upstream-derived string here does.
- gateway/dispatch.rs: `gateway.code_mode.set` now discards the retired
  `trusted_read_only_tools` value rather than persisting it, and the field is
  no longer serialized back out, so no surface presents it as a live control.
  Deletes the matcher it left behind.

Tests

- Regression coverage for the read-only Code Mode gate (both directions), the
  pid-liveness portability fix, workspace rollback and recovery-tree cleanup,
  and bounded weak-generation growth.
- Replaces a tautological timeout test and a wall-clock "did not finish in
  500ms" linearization assertion with assertions that can actually fail.
- RAII guards for the process-global test home overrides, which leaked to
  unrelated tests on any panic or early return.
- xtask module-reachability now reports a directory no module file can
  declare; previously an entire orphaned subtree passed the gate silently.

Docs

- CODE_MODE.md: state plainly that the read-only gate now rests entirely on the
  upstream's own readOnlyHint, with no operator-held second allowlist.
- OAUTH.md: complete the scoped-operation list and document the creation
  carve-out (`gateway.add`, `import_pending.approve`) and the gateway-global
  mutation surface that scope alone gates.
- upstream/CLAUDE.md: add rows for the three restored pool modules.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions github-actions Bot added documentation Improvements or additions to documentation dependencies Pull requests that update a dependency file labels Sep 1, 2026
@jmagar jmagar closed this Sep 2, 2026
@jmagar jmagar reopened this Sep 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant