mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-11 23:36:51 +02:00
feat(server): add a one-click relink action for detached custom-image templates (#11641)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Paperclip environments can use captured custom images for agent runs > - A configuration fingerprint change can detach a valid custom-image template > - Operators need a safe way to confirm that the image still matches the boot source > - This pull request adds a guarded relink action with drift classification and audit logging > - The benefit is a deliberate relink without a new sandbox boot or provider snapshot ## Linked Issues or Issue Description **Subsystem affected** Cross-cutting environment, server, and UI behavior. **Problem or motivation** A custom-image template detaches when the environment configuration fingerprint changes. The runtime then uses the base image, even when the boot source did not change. The only prior remedy required a full re-capture. **Proposed solution** Add an operator-triggered relink action. Classify configuration drift from a server-owned boot-relevant snapshot. Relink knob-only drift without confirmation. Require explicit confirmation for boot-source or unclassified drift. Guard the route for instance administrators and record a safe activity event. **Alternatives considered** Keep requiring a full re-capture. This adds a sandbox boot and provider snapshot for cases where the image remains correct. **Roadmap alignment** The roadmap has no matching custom-image relink item. This change addresses an environment operation gap. **Additional context** The relink response exposes raw drift values only in the transient 409 response to the instance administrator. The service never persists or logs fingerprints or configuration values. Reserved identity-path segments fail closed. ## What Changed - Add `relinkActiveTemplate` with drift classification and conditional fingerprint update. - Persist a server-owned boot-relevant configuration snapshot during capture. - Add the guarded relink route with strict request validation and activity logging. - Add the relink action and confirmation flow to the environment page. - Add service, route, UI, and OpenAPI coverage. ## Verification - Run the focused service suite: `pnpm vitest run server/src/services/environment-custom-images-service.test.ts`. - Run the focused route suite: `pnpm vitest run server/src/routes/environment-custom-image-routes.test.ts`. - Run the focused UI suite: `pnpm vitest run ui/src/pages/CompanyEnvironments.test.tsx`. - Run server and UI TypeScript checks. - Confirm the OpenAPI snapshot matches the new route. - Confirm all required GitHub checks pass on commit `e46fdcfe94a719be854adf8849d30714e5b70b93`. - Confirm Greptile reports 5/5 with no unresolved review threads. ## Risks The relink action can keep an image after configuration drift. The service requires explicit confirmation for boot-source or unclassified drift. Reserved path segments produce a safe unresolved marker and never enter stored values. ## Model Used OpenAI GPT-5 Codex. The model used repository inspection, GitHub operations, and PR preparation with tool use and code execution. The runtime did not expose a context-window value or a separate reasoning-mode value. ## 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>
This commit is contained in:
1 parent
fe803bedf1
commit
120ae5428f
12 files changed
+1393
-15
No files matched your search
@@ -54,6 +54,32 @@ export interface EnvironmentCustomImageRollbackResult {
|
||||
supersededTemplate: EnvironmentCustomImageTemplate;
|
||||
}
|
||||
|
||||
export type EnvironmentCustomImageRelinkClassification =
|
||||
| "knob_only"
|
||||
| "boot_source_drift"
|
||||
| "unclassified";
|
||||
|
||||
export interface EnvironmentCustomImageRelinkResult {
|
||||
template: EnvironmentCustomImageTemplate;
|
||||
classification: EnvironmentCustomImageRelinkClassification;
|
||||
}
|
||||
|
||||
export interface EnvironmentCustomImageDriftedPath {
|
||||
path: string;
|
||||
from?: unknown;
|
||||
to?: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* The 409 conflict body a relink returns when the server cannot re-stamp without
|
||||
* an operator confirmation. `driftedPaths` carries `from`/`to` only for paths
|
||||
* that passed the secret containment check; excluded paths carry the name only.
|
||||
*/
|
||||
export interface EnvironmentCustomImageRelinkConflict {
|
||||
classification: Exclude<EnvironmentCustomImageRelinkClassification, "knob_only">;
|
||||
driftedPaths: EnvironmentCustomImageDriftedPath[];
|
||||
}
|
||||
|
||||
function companyIdQuery(companyId: string): string {
|
||||
return `companyId=${encodeURIComponent(companyId)}`;
|
||||
}
|
||||
@@ -162,6 +188,15 @@ export const environmentsApi = {
|
||||
`/environments/${environmentId}/custom-image-template/rollback?${companyIdQuery(companyId)}`,
|
||||
{},
|
||||
),
|
||||
relinkCustomImageTemplate: (
|
||||
environmentId: string,
|
||||
companyId: string,
|
||||
options: { confirmBootSourceDrift?: boolean } = {},
|
||||
) =>
|
||||
api.post<EnvironmentCustomImageRelinkResult>(
|
||||
`/environments/${environmentId}/custom-image-template/relink?${companyIdQuery(companyId)}`,
|
||||
{ confirmBootSourceDrift: options.confirmBootSourceDrift === true },
|
||||
),
|
||||
disableCustomImageTemplate: (
|
||||
environmentId: string,
|
||||
companyId: string,
|
||||
|
||||
Reference in new issue
Block a user