Files
PaperClipAI/server
Nicky LeachandPaperclip 0b65926822 fix(heartbeat): preserve inherited policy after workspace binding (#17924)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Follow-up runs restore saved sessions when execution configuration
is unchanged.
> - A project can inherit workspace policy from its storage plugin.
> - The initial run resolves those defaults before it binds a workspace.
> - The first bound follow-up skipped that resolution and changed the
session fingerprint.
> - This pull request resolves the same effective policy before and
after binding.
> - Unchanged follow-ups keep their saved session and workspace.

## Linked Issues or Issue Description

**What happened?**

A repository-backed project with no explicit workspace policy inherits
an isolated Git worktree policy from its storage plugin. After the first
run binds a workspace, the next run parsed only the raw project policy.
It dropped the inherited provider and strategy fields. Core treated this
representation change as a `workspaceConfig` change and discarded the
saved session, although the workspace had not changed.

**Expected behavior**

Binding a workspace must not change the effective project policy. Real
policy changes must still invalidate session compatibility. The
workspace must keep its recorded storage owner.

**Steps to reproduce**

1. Install a default project storage plugin with isolated Git worktrees.
2. Create a project with a repository and no explicit workspace policy.
3. Run a task, then submit its first follow-up.
4. Observe a session reset for `workspaceConfig` despite an unchanged
workspace.

**Paperclip version or commit**

Reproduced on master `50b932a142216331b8d476042b454bc123739c40`.
Related: #17447 fixed the separate source-free project case. A duplicate
PR search found no matching repository-backed fix.

## What Changed

- Resolve effective project policy for bound repository-backed
workspaces through the same company-scoped resolver as initial
admission. The source-free project guard remains.
- Add a PostgreSQL heartbeat regression covering initial binding, two
follow-ups, a real policy change, and retained provider ownership.
- Settle the synthetic adapter fixture as Done and reopen each explicit
follow-up. This prevents an automatic review-path recovery from hiding
the first bound admission.
- Add the verification recipe to the session feature map.

## Verification

- The new regression fails before the production change: the first
explicit follow-up receives `sessionId: null` instead of the saved
session.
- All six tests in
`server/src/__tests__/heartbeat-session-preparation.test.ts` pass after
the fix.
- A targeted run across that file and
`project-workspace-provider.integration.test.ts` passed 17 cases,
including inherited policy, model changes, incompatible explicit policy,
provider availability, no fallback after disablement, and retained
local/plugin ownership.
- Commands: from `server/`, `node ../node_modules/vitest/vitest.mjs run
src/__tests__/heartbeat-session-preparation.test.ts`; targeted controls
used the same runner with `-t 'inherited|effective model|retains both
plugin and local workspace ownership|never falls
back|unavailable|explicit settings|compatible explicit'`.
- `node scripts/check-maps.mjs` and `git diff --check` pass.
- Initial fixture attempts hit source-binding, provider-execution,
missing working-directory, and branch-inspection guards. The fixture was
corrected; those guards remain unchanged. An automatic review recovery
also hid the initial transition until fixture settlement was corrected.
- Tests use real PostgreSQL, real policy resolution, and heartbeat
persistence. Provider execution is synthetic; a temporary local working
directory replaces environment realization for the test adapter.
- Current head `4d21b5180960659576e5950e906056fa4dabb54c` removes two
comments after standard review; executable code is unchanged from the
locally verified commit. Standard Greptile scores this head 5/5 with no
unresolved threads. GitHub CI is now green on this head: 54 successful
checks and two intentional Storybook skips. Standard Greptile and Apex
both score this head 5/5. Apex found no actionable issues; no unresolved
threads or merge conflicts remain.
- CI job
[workspaces-b](https://github.com/paperclipai/paperclip/actions/runs/40564712367/job/122406649223)
was interrupted by a runner shutdown signal at 19:09:39 UTC. Its
available log shows no test assertion failure; completed groups passed.
The serialized server shard
[1/9](https://github.com/paperclipai/paperclip/actions/runs/40564712367/job/122406648374)
also received a runner shutdown at 19:12:03 UTC; its final test summary
reports 532 passing tests and no failed assertion. Both interrupted jobs
passed in the failed-jobs-only rerun, and the final verify aggregate
passed. The first retry request was rejected while the containing run
was still active; it was accepted after the other jobs settled.
- Local verification is targeted as requested. Full test, build, and
typecheck coverage is delegated to GitHub CI. No live deployment or
latency claim is made.

## Risks

Bound repository runs now read the current storage-plugin defaults
through one provider-list query. An ambiguous or incompatible effective
policy can fail admission instead of being skipped. Existing plugin
preparation still uses the recorded workspace owner, so a changed
default does not move existing storage to another provider. Explicit
policy changes still rotate the session. No schema or API changes are
included.

## Model Used

OpenAI GPT-6 through Codex, using repository inspection, code editing,
shell tools, and targeted test execution. The exact serving model ID and
context window 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 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 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-10-27 14:39:06 -07:00
..