Commit Graph
4 Commits
Author SHA1 Message Date
Nicky LeachandClaude Opus 5.5 369e238295 fix(projects): make workspace storage the project's only placement choice (#20835)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - A repository project gets its working files from core staging or
from a workspace storage plugin
> - #20459 reported that an installed storage plugin attached every new
repository project, so sandbox agents could not use those projects
> - #20587 fixed creation with an **Available in** choice that selects
storage
> - #20651 then saved an environment on every new project and used it
before the agent default to place runs. That solved a problem that did
not exist: the project needs storage, not a run environment
> - Creation saves the first environment of the chosen group, so a
project silently sends every agent's runs to that environment. Project
Configuration also shows two controls (Environment and Workspace
storage) for one decision
> - This pull request removes the project run environment and makes
storage the only placement choice a project owns. Storage is fixed at
creation
> - The benefit is that runs follow the agent again, and project
settings show one clear, read-only fact about storage

## Linked Issues or Issue Description

Refs #20459. Follow-up to #20587 and #20651.

Refs #11644. That issue asks for a project-level run environment
override. This pull request removes the override that #20651 added for
it, because the override is not necessary to fix #20459. #11644 stays a
separate feature request.

Related: #18176 (open) repairs placement through the legacy
workspace-policy field. This pull request does not restore project
placement, so it conflicts with the direction of #18176.

## What Changed

- **Remove the project run environment.** Migration `0466` drops
`projects.environment_id`. Run placement no longer reads the project.
Runs use the agent default, then the instance default, then local
execution. The low-trust task sandbox and the enforced sandbox policies
keep their precedence.
- **Keep the #20651 recovery and binding guards.** A recovered run still
uses its admitted environment lease (source `run`). Company-binding
checks before admission and during lease creation stay.
- **Keep the creation choice.** **Available in** in New Project still
selects storage. It still shows only when the environments need more
than one storage owner. That can only occur when a storage plugin is
installed. `POST /projects` with `environmentId` selects the storage of
that environment and does not save the environment.
- **Fix storage at creation.** `PATCH /projects/:id` rejects any storage
change with `409 project_storage_locked`, from core to a plugin and from
a plugin to core. Before, a core project could move to a plugin.
- **Drop legacy writes.** Older clients can still send
`executionWorkspacePolicy.environmentId`. The service removes it on
create and update.
- **Simplify Project Configuration.** Remove the Environment and
Workspace storage selectors. Show an advisory **Available in** list of
the environments that can use the project's storage. Show it when a
storage plugin is installed or when the project uses one. When the
project's plugin is not installed, show **Storage plugin unavailable**.
Without a storage plugin, show nothing.
- **Issue workspace card.** Remove the "project default" environment
label.
- **Shared contract.** Remove `Project.environmentId`, the
`environmentId` project field, and `expectedStorageProviderPluginId`
from the update schema. The create schema keeps `environmentId` and
`expectedStorageProviderPluginId` from #20587.
- **Docs and maps.** Update `doc/project-environment-compatibility.md`,
`docs/api/goals-and-projects.md`, and the project feature and
agent-access maps.
- **SDK source pins.** Refresh
`tests/runner-e2e/source-sdk-public-inputs.json` for the two shared
files. The shared file blobs return to their values before #20651. The
refresh also corrects the `pnpm-lock.yaml` pin and `lockSha256`:
`master` pins an older lockfile blob than the one it contains.

## Verification

- Targeted server suites pass (227 tests): `project-environments`,
`project-workspace-provider.integration`,
`heartbeat/environment-selection`, `execution-workspace-policy`,
`environment-selection-route-guards`, `environment-run-orchestrator`,
`openapi-routes`.
- Related server suites pass (349 tests): `environment-service`,
`environment-routes`, `plugin-workspaces.integration`, `issues-service`.
- Targeted UI suites pass: `ProjectProperties.concurrency`,
`IssueWorkspaceCard`, `NewProjectDialog`,
`NewProjectDialog.managed-sandbox`, `ProjectDetail`.
- New tests:
- Creation saves storage, not an environment. Core and plugin projects
both reject a storage change with `project_storage_locked`.
  - A legacy policy `environmentId` is dropped on create and update.
- A new run follows the agent default for any project. A recovered run
keeps its admitted lease after the agent default changes.
- Project Configuration has no storage or environment selector. The note
is hidden without a plugin. It lists the core or plugin environments for
the project's storage. It shows **Storage plugin unavailable** for a
removed plugin.
- `pnpm --filter @paperclipai/server typecheck`, `pnpm --filter
@paperclipai/ui typecheck`, and `pnpm --filter @paperclipai/shared
typecheck` pass.
- `pnpm --filter @paperclipai/db check:migrations`, `pnpm
check:token-gates`, `node scripts/check-maps.mjs`, and `node
tests/runner-e2e/source-sdk-public-inputs.mjs <HEAD> --check` pass.
- Not run locally: the full `pnpm test:run`, `pnpm build`, and browser
suites. CI supplies that coverage.
- Manual check: open Project Configuration on an instance with a storage
plugin. Confirm that there is no Environment or Workspace storage
selector, and that **Available in** lists the environments.

## Risks

- **Migration.** `0466` drops `projects.environment_id` and its foreign
key. The migration deletes the saved project environments. Apply it
before you serve the new revision. An old server revision that reads
`projects` fails until it stops. This is the same pattern as earlier
drop-column migrations (`0229`, `0289`).
- **Behavior change for existing projects.** Projects created after
#20651 shipped have a saved environment that overrides the agent. After
this change, their runs follow the agent default again. This is the
intended fix. Before #20651, a project environment selection was not
applied to runs (#11644), so no older behavior is lost.
- **No path from core to a plugin.** A core project, including one
created before a plugin was installed, cannot move to the plugin. To use
plugin storage, create a new project.
- **API.** A `PATCH` that sends `environmentId` or
`expectedStorageProviderPluginId` no longer has an effect. The schema
strips these fields.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`) by Anthropic, in Claude Code, with
tool use (shell, file edits, test runs). Extended thinking was
available.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 08:00:21 -08:00
Nicky LeachandPaperclip 98ce93dd4e Choose project storage from compatible environments (#20587)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Projects supply repository files to agent runs through core staging
or a storage plugin.
> - A storage plugin's default claim silently attached new projects even
when their agents used a different environment.
> - An environment name or driver type does not establish shared storage
access. The existing binding and task-driver contracts do.
> - This pull request groups creation choices by that contract and
selects storage from the creator's effective environment.
> - The benefit is a visible, validated choice and a clear error before
an incompatible managed workspace operation.

## Linked Issues or Issue Description

Fixes #20459.

Related: #20156 covers a separate provider-failure diagnostic. The PR
search found no other fix for #20459. ROADMAP.md has no duplicate
project-creation change.

## What Changed

- Show **Available in** when configured environments require different
project storage. Each option names the environments that can use the
same storage owner. Hide the selector when all choices are compatible.
- Derive compatibility from repository staging and managed binding
ownership. Local, SSH, and sandbox environments use core staging.
Managed storage requires a registered task driver from the same
installed storage plugin. Separate storage plugins remain incompatible.
Unknown plugin contracts and the fake sandbox are excluded.
- Follow the creating agent's environment, or the person's primary
agent, then the instance default and local fallback. Apply managed-only
and forced Kubernetes policy. The product has no separate user or
company environment-default field today.
- Add a company-authorized discovery endpoint and an optional creation
`environmentId`. Validate the choice again on save. Store the storage
owner without overriding agent run environments. Preserve explicit API
storage choices and existing project ownership.
- Keep the hidden repository-free private-task project independent of
environment availability. Submit the observed storage owner as
`expectedStorageProviderPluginId` so a changed contract fails at save
without setting a workspace policy. Instance task-isolation defaults
stay inherited.
- Reject an incompatible managed workspace before a provider operation.
Name the storage and selected environment in the error. Add regression
tests, API documentation, creation-story fixtures, and minimal
corrections to obsolete map text.

## Verification

- **102 tests passed across 7 files:** project environment integration,
installed storage policy, repository persistence, project environment
routes, project creation telemetry routes, OpenAPI, and
NewProjectDialog. Run with `node node_modules/vitest/vitest.mjs run
server/src/__tests__/project-environments.test.ts
server/src/__tests__/project-workspace-provider.integration.test.ts
server/src/__tests__/project-repositories-persistence.test.ts
server/src/__tests__/project-routes-env.test.ts
server/src/__tests__/project-goal-telemetry-routes.test.ts
server/src/__tests__/openapi-routes.test.ts
ui/src/components/NewProjectDialog.test.tsx --silent`.
- The review fixes cover both archived-default private-task cases and
changed-storage rejection. With both instance isolation flags enabled, a
new core repository project keeps a null policy and resolves to an
isolated Git worktree. The complete affected set passes all 102 tests.
- The CI query-error policy failure was fixed by using the shared query
view. The form and shared query-view suites pass **24 tests**, including
cached choices during a transient refetch failure. `node
scripts/check-query-error-rendering.mjs --enforce --summary` reports
zero findings.
- CI exposed stale SDK source hashes and heartbeat fixtures that paired
plugin storage with Local. Refreshed exactly four shared-source hashes
with the maintained generator (runtime pins unchanged). All **53 SDK
privacy/source-proof tests**, including real built SDK output, pass. All
**7 corrected managed-storage heartbeat scenarios** pass; the fixtures
declare the owning plugin's task environment and substitute transport at
the existing execution seam.
- Shared and UI TypeScript checks pass. The UI production build passes.
`pnpm check:token-gates`, `node scripts/check-maps.mjs`, `node
scripts/check-module-boundaries.mjs`, and diff checks pass.
- Tested UI states: compatible choices, incompatible groups, default
within a group, manual override, create failure, loading, discovery
failure and retry, and an unavailable default. Tests use the production
form and existing design tokens.
- The integration tests use real isolated PostgreSQL on macOS arm64 with
Node 26.12.0. They cover user/agent defaults, storage persistence,
policy restrictions, company and authentication denials, and rejection
before a provider call. Discovery uses saved configuration and makes no
worker or provider requests.
- Local verification limits: `pnpm -r typecheck` and `pnpm build` stop
in unchanged adapter-utils code because the installed ACPX types lack
`inheritProcessEnv`. A direct server check also finds an unchanged AI
SDK type-version mismatch. The existing heartbeat test `inherits the
instance default environment across companies while preserving explicit
agent overrides` times out at line 518 on both this change and
unmodified master with the same dependencies. The full `pnpm test:run`
attempt passed all six chat shards (1,948 tests), then the Git workspace
expiration suite reported 14 failures. I stopped that broad run. Two
sampled failures reproduce on unchanged master with the same
`unknown_worktree_git_files` refusal; the other expiration failures were
not independently compared. A later broad workspace-preparation run also
hit workspace validation failures and was stopped; the seven corrected
fixture scenarios pass independently. These limits do not establish a
full local suite pass. Hosted CI remains required.
- Initial missing-package and loopback-denial failures were resolved by
repairing isolated dependency links, building the SDK, and running
database tests with loopback access. No deployed or live-provider
execution is claimed.

## Risks

- Creation defaults now follow the effective environment. A plugin's
`defaultForProjects` claim alone no longer selects storage. This is the
intended behavior change.
- Compatibility covers repository project storage. It does not guarantee
arbitrary host paths, retained-workspace moves, adapter/image support,
credentials, or worker readiness. Matching provider configs cannot prove
a cross-plugin storage contract.
- A person's primary agent supplies the personal default because there
is no separate personal environment setting. An unavailable configured
default requires a valid explicit choice or a configuration repair.
- No migration runs. Existing projects keep their storage owner. This
change does not detach existing managed data or provide a storage
migration.
- Full local verification has the limits listed above. CI and standard
Greptile review are pending.

## Model Used

OpenAI Codex, based on GPT-6, with reasoning, tool use, and code
execution. The exact serving snapshot and context-window size were not
exposed in this session.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-11-10 17:38:31 +00:00
Nicky LeachandClaude Opus 5.5 be20d4ad97 fix(projects): keep projects on the storage chosen at creation (#19036)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - A project stores its work in Core/local storage or in a storage
plugin
> - Today, Core finds the installed default each time it reads a project
> - So installing a default storage plugin moves existing projects,
without a saved change
> - Agents that cannot reach the new storage then lose access to new
workspaces
> - This pull request makes Core apply the default once, when it creates
a project
> - The benefit is that a project stays on its storage until a person
moves it

## Linked Issues or Issue Description

No issue exists. This describes the problem. I searched open pull
requests for related work and found no duplicate.

**What existing behavior does this improve?**

How Core chooses the storage for a project when a plugin claims
`defaultForProjects`.

**Current behavior**

Installing such a plugin changes the storage of every existing project
with no explicit choice. Disabling the plugin changes it back. Nothing
is saved, so the project has no fixed owner. Execution environments and
storage are separate contracts, so an agent can lose access to new
workspaces on a project that it used before.

**Proposed behavior**

Core saves the storage choice on the project when it creates the
project. A project with no saved choice uses Core/local storage. A
plugin-backed project stays attached to its plugin. A Core/local project
can move to a plugin.

**Reason and benefit**

A project stays on its storage until a person moves it, so installing or
disabling a plugin does not change where work goes.

**Alternatives considered**

- Keep the default at read time and add a process that saves a choice on
existing projects when a plugin arrives. This adds a background step and
a race with new projects.
- Add a new database column. The existing policy field already holds the
choice, so a migration is not necessary.

## What Changed

- `resolveProjectWorkspaceProvider` reads only the saved choice. It no
longer applies an installed default.
- `withDefaultProjectStorage` applies the default when Core creates a
project, including a personal private project. With more than one
default, a project with no explicit choice is refused. This is the
existing conflict rule, now applied at creation.
- `projectService.update` refuses a change of storage on a plugin-backed
project. An update that omits the field keeps the attachment.
- Plugin lifecycle delivery sends project events to a storage provider
only for projects attached to it, and for deleted projects. Core
acknowledges the other events for that provider. Other plugins continue
to receive all project events.
- The project settings control has no "Inherit" option. It is locked on
a plugin-backed project.
- `ProjectWorkspaceProviderConfiguration.source` no longer has the
`"default"` value.
- A project with no repository or folder ignores its saved storage
choice when a task runs. A storage plugin cannot manage this type of
project, so its tasks use a plain task directory. Without this change,
the second run of a task reset the agent session, and remote agents
could not use an empty task directory.
- The plugin SDK README and the plugin authoring guide describe the new
rules.

## Verification

- `npx vitest run
server/src/__tests__/project-workspace-provider.integration.test.ts
server/src/__tests__/plugin-lifecycle-delivery.test.ts
server/src/__tests__/plugin-workspaces.integration.test.ts
packages/plugins/sdk/tests/workspace-provider.test.ts`: 93 tests pass.
- `npx vitest run src/components/ProjectProperties.concurrency.test.tsx`
in `ui/`: 9 tests pass.
- `pnpm --filter @paperclipai/server typecheck` and `pnpm --filter
@paperclipai/ui typecheck` pass.
- `npx vitest run` on `heartbeat-session-preparation`,
`workspace-preparation`, `remote-workspace-source`,
`project-workspace-provider.integration`, and
`plugin-workspaces.integration` in `server/`: 231 tests pass.
- I did not run the full test suite locally. CI runs it.
- I did not test this change on a running instance.

## Risks

- **Behavior change at upgrade:** an existing project that uses a plugin
only through the installed default, with no saved choice, moves to
Core/local storage after upgrade. Its plugin files stay in place, but
new work does not use them until an operator sets
`storageProviderPluginId` on the project. This pull request has no
automatic backfill.
- **Missed lifecycle events:** a provider does not receive events for a
Core/local project. If the project moves to that provider later, the
update event starts its reconcile.
- **One-way move:** a person cannot move a plugin-backed project back
through the API or the UI. This is intended. If a plugin is disabled,
the project fails closed, as it does today for an explicit choice.
- This pull request does not stop a person from assigning an agent to a
project with storage that the agent cannot reach. That is follow-up
work.
- This changes core plugin behavior, and I did not discuss it in `#dev`
first. Maintainers can tell me if they want a discussion before review.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), through Claude Code, with tool
use and code execution.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 13:07:29 -08:00
Nicky LeachandPaperclip 6204239cfc Support installed default project workspace providers (#17247)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Storage plugins already own project storage and task checkout
preparation.
> - Core only selected these plugins through an explicit project policy.
> - Installation could therefore leave new and existing projects on
incompatible Core preparation.
> - This pull request adds one generic manifest declaration and one
shared effective-policy resolver.
> - Project configuration shows inheritance while durable workspace
bindings retain their owners.

## Linked Issues or Issue Description

**Subsystem affected**

Plugin contracts, project configuration, and execution workspace
preparation.

**Problem or motivation**

An installed storage provider cannot become the company default.
Projects without an explicit provider keep Core preparation, even when
their execution environment cannot use a Core Git checkout.

**Proposed solution**

A provider declares supported `workspaceProvider.defaultForProjects`
mode and strategy. Company-enabled installations supply inherited
defaults. Explicit plugin selections and explicit Core/local
(`storageProviderPluginId: null`) win. Conflicting defaults require an
operator choice. The declaration and implementation contain no
provider-specific identifiers.

**Alternatives considered**

Rewriting every project would lose the distinction between inheritance
and overrides. Selecting by worker health would silently change storage
on an outage.

**Roadmap alignment**

Extends the existing workspace and sandbox provider contracts. The
roadmap has no duplicate default-provider implementation. Related public
work includes #17243, which covers prepared execution directories rather
than provider selection.

## What Changed

- Add a validated generic default-provider declaration and document it
in the SDK.
- Resolve provider selection and Git defaults consistently in project
responses, source selection, heartbeat source resolution, reservation,
and preparation.
- Keep stored policy separate from effective configuration. Add the
normal project configuration selector for inheritance, explicit plugins,
and Core/local.
- Preserve existing local and plugin workspace ownership. Pin selection
through preparation and keep provider failures visible.
- Add database, API, manifest, heartbeat, and UI regressions. Update the
verification maps.

## Verification

- Targeted Vitest runs cover manifest validation, project defaults and
API access, explicit overrides, existing ownership, new/imported task
inheritance, heartbeat routing, source-free tasks, selection pinning
during setup, and UI configuration.
- Combined policy, provider integration, and heartbeat run: 58 tests
pass; 60 unrelated cases excluded. After the final shared-provider
fallback fix, all 47 policy tests pass, including shared and
operator-mode regressions.
- Server and UI TypeScript checks, SDK build, verification-map
validation, credential-pattern scan, added-comment scan, and `git diff
--check` pass.
- A downstream integration uses this Core resolver and preparation
service with a real storage hook and mocked remote infrastructure. A new
Git project reaches the hook without a stored provider ID.
- Standard Greptile and Apex both scored 5/5 on
5d42998466. All review threads are
resolved.
- Final GitHub CI is fully green on
5d42998466. Browser shard 3 initially hit
a PostgreSQL deadlock during cleanup cancellation, which left an
experiment flag enabled for a later test. A failed-job retry passed all
26 browser tests. The workflow completed successfully on attempt 2. Full
final verification was delegated to CI at the user's request.
- No live provider or hosted tenant run was performed.

## Risks

- Installing one default-claiming provider changes effective selection
for unconfigured projects. Existing explicit settings and workspace
ownership remain intact; incompatible explicit settings fail clearly.
- Each configuration/admission resolution reads installed plugin
metadata and the selected company's enablement. Project list reads share
one catalog query. Selection does not call workers or external
providers.
- Deploy the Core contract before any downstream manifest uses the
declaration. No migration or backfill is required. A downstream
immutable image pin must include this change before packaging its
declaration.
- No merge or deployment is authorized by this PR.

## Model Used

OpenAI GPT-6 through Codex, with reasoning, repository tools, and code
execution. The exact served model ID and context-window size are not
exposed in this session.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have described the issue in-PR following the relevant issue
template
- [x] I have not referenced internal/instance-local Paperclip issues or
links
- [x] My branch name describes the change and contains no internal
ticket id
- [x] I have run targeted tests locally and they pass; full gates are
delegated to CI at the user's request
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green on
5d42998466ad42400564ba34f48124f40c53fbc5; the branch has no merge
conflicts
- [x] Standard Greptile is 5/5 on 5d4299846 after all fixes, with no
unresolved threads
- [x] Apex is 5/5 on 5d42998466, after
standard Greptile 5/5 on that same head
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-22 17:12:29 -07:00