fix: recover legacy sandbox cwd and preserve staging lock verification
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
parent
74ed77e21d
commit
ebfdbfb0b1
|
|
@ -3,6 +3,13 @@ name: Docker cloud
|
|||
on:
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
inputs:
|
||||
staging_artifact_base_url:
|
||||
type: string
|
||||
default: ""
|
||||
staging_lock_sha256:
|
||||
type: string
|
||||
default: ""
|
||||
|
||||
permissions: {}
|
||||
|
||||
|
|
@ -98,9 +105,16 @@ jobs:
|
|||
node-version: 24
|
||||
|
||||
- name: Refresh lockfile for Docker build context
|
||||
env:
|
||||
STAGING_ARTIFACT_BASE_URL: ${{ inputs.staging_artifact_base_url }}
|
||||
EXPECTED_LOCK_SHA256: ${{ inputs.staging_lock_sha256 }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
pnpm install --resolution-only --ignore-scripts --no-frozen-lockfile
|
||||
if [ -n "$STAGING_ARTIFACT_BASE_URL" ]; then
|
||||
[[ "$EXPECTED_LOCK_SHA256" =~ ^[a-f0-9]{64}$ ]]
|
||||
echo "$EXPECTED_LOCK_SHA256 pnpm-lock.yaml" | sha256sum --check --strict
|
||||
fi
|
||||
|
||||
changed="$(git status --porcelain)"
|
||||
if [ -z "$changed" ]; then
|
||||
|
|
|
|||
|
|
@ -427,6 +427,9 @@ jobs:
|
|||
build-and-push-cloud:
|
||||
if: github.event_name != 'push' || github.ref != 'refs/heads/master'
|
||||
uses: ./.github/workflows/docker-cloud.yml
|
||||
with:
|
||||
staging_artifact_base_url: ${{ inputs.staging_artifact_base_url || '' }}
|
||||
staging_lock_sha256: ${{ inputs.staging_lock_sha256 || '' }}
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
|
|
|||
|
|
@ -105,6 +105,9 @@ workspace, environment, provider sandbox, working directory, and conversation
|
|||
all match. An explicit conflicting identity is never overwritten. Local and SSH
|
||||
session matching remain separate; a replacement sandbox cannot inherit a
|
||||
conversation merely because its working-directory path is the same.
|
||||
Old codecs that recorded the host checkout path are translated to the sandbox
|
||||
path only when both the saved workspace and its local realization prove that
|
||||
exact host path and project-workspace ID. Arbitrary path changes still reset.
|
||||
Codex configuration refresh replaces only the managed auth/config/skills entries;
|
||||
it preserves the sandbox's rollout files and SQLite state, including WAL files.
|
||||
Those provider-session files stay outside shared work-folder collections.
|
||||
|
|
@ -584,6 +587,8 @@ that digest before installing dependencies, failing if registry resolution
|
|||
has changed. Transfer those artifacts to the staging bucket using conditional
|
||||
creates, publishing the manifest last. Do not create GitHub releases or publish
|
||||
npm packages for this flow.
|
||||
The reusable Cloud build workflow receives those staging inputs explicitly;
|
||||
splitting the workflow must not drop the app/migrator dependency-integrity gate.
|
||||
|
||||
Cloud enables this lane only in staging through
|
||||
`CLOUD_HARNESS_STAGING_ARTIFACT_BASE_URL`. Resolve `preview:<full SHA>` through
|
||||
|
|
|
|||
|
|
@ -89,7 +89,12 @@ describe("staging dependency integrity", () => {
|
|||
});
|
||||
|
||||
it("checks the same lock in both staging app build contexts", () => {
|
||||
const contexts = workflow.split(" - name: Refresh lockfile for Docker build context").slice(1);
|
||||
const caller = workflow.split(" build-and-push-cloud:")[1].split(" # Moves")[0];
|
||||
expect(caller).toContain("uses: ./.github/workflows/docker-cloud.yml");
|
||||
expect(caller).toContain("staging_artifact_base_url: ${{ inputs.staging_artifact_base_url || '' }}");
|
||||
expect(caller).toContain("staging_lock_sha256: ${{ inputs.staging_lock_sha256 || '' }}");
|
||||
const contexts = [workflow, cloudWorkflow].flatMap((source) =>
|
||||
source.split(" - name: Refresh lockfile for Docker build context").slice(1));
|
||||
expect(contexts).toHaveLength(2);
|
||||
for (const context of contexts) {
|
||||
const refresh = context.split(" - name:")[0];
|
||||
|
|
|
|||
|
|
@ -89,6 +89,25 @@ describe("legacy sandbox conversation persistence", () => {
|
|||
expect(recoverLegacySandboxSession(input)).toBe(input.params);
|
||||
}
|
||||
});
|
||||
it.each(["codex_local", "claude_local"])("recovers %s host cwd only from the exact recorded workspace", (adapterType) => {
|
||||
const input = fixture(); input.adapterType = adapterType;
|
||||
input.params = { ...input.params, cwd: "/host/project", workspaceId: "project-workspace" };
|
||||
const context = input.previousRun!.contextSnapshot as Record<string, any>;
|
||||
context.paperclipWorkspace = { cwd: "/host/project", workspaceId: "project-workspace" };
|
||||
context.paperclipEnvironment.workspaceRealization.local = { path: "/host/project", projectWorkspaceId: "project-workspace" };
|
||||
expect(recoverLegacySandboxSession(input)).toMatchObject({ cwd: target.remoteCwd, remoteExecution: adapterExecutionTargetSessionIdentity(target) });
|
||||
for (const mutate of [
|
||||
(copy: typeof input) => { copy.params!.cwd = "/another/project"; },
|
||||
(copy: typeof input) => { copy.params!.workspaceId = "another-workspace"; },
|
||||
(copy: typeof input) => { delete (copy.previousRun!.contextSnapshot as any).paperclipWorkspace; },
|
||||
(copy: typeof input) => { (copy.previousRun!.contextSnapshot as any).paperclipEnvironment.workspaceRealization.local.path = "/another/project"; },
|
||||
(copy: typeof input) => { (copy.previousRun!.contextSnapshot as any).paperclipEnvironment.remoteCwd = "/another/remote"; },
|
||||
]) {
|
||||
const changed = structuredClone(input); mutate(changed);
|
||||
expect(recoverLegacySandboxSession(changed)).toBe(changed.params);
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps legacy host-lease matching when no physical identity is available", () => {
|
||||
const oldTarget = { ...target, sandboxLeaseAcquisition: undefined };
|
||||
const saved = adapterExecutionTargetSessionIdentity(oldTarget);
|
||||
|
|
|
|||
|
|
@ -36,6 +36,16 @@ export function recoverLegacySandboxSession(input: {
|
|||
const context = record(previous.contextSnapshot);
|
||||
const environment = record(context.paperclipEnvironment);
|
||||
const realization = record(environment.workspaceRealization);
|
||||
const workspace = record(context.paperclipWorkspace);
|
||||
const local = record(realization.local);
|
||||
// Old CLI codecs persisted the host checkout path even when the provider
|
||||
// conversation lived in the sandbox. Translate only the exact path and
|
||||
// workspace recorded by the successful host run; never accept another path.
|
||||
const savedHostCwd = typeof params.cwd === "string" && params.cwd.length > 0
|
||||
&& params.cwd === workspace.cwd && params.cwd === local.path
|
||||
&& typeof params.workspaceId === "string" && params.workspaceId.length > 0
|
||||
&& params.workspaceId === workspace.workspaceId
|
||||
&& params.workspaceId === local.projectWorkspaceId;
|
||||
if (previous.status !== "succeeded"
|
||||
|| previous.companyId !== input.companyId || previous.agentId !== input.agentId
|
||||
|| previous.responsibleUserId !== input.responsibleUserId
|
||||
|
|
@ -45,13 +55,14 @@ export function recoverLegacySandboxSession(input: {
|
|||
|| environment.driver !== "sandbox" || environment.id !== target.environmentId
|
||||
|| realization.provider !== target.providerKey
|
||||
|| realization.providerLeaseId !== target.sandboxLeaseAcquisition.providerLeaseId
|
||||
|| params.cwd !== target.remoteCwd || environment.remoteCwd !== params.cwd
|
||||
|| environment.remoteCwd !== target.remoteCwd
|
||||
|| (params.cwd !== target.remoteCwd && !savedHostCwd)
|
||||
|| (params.remoteExecution != null && (
|
||||
saved.leaseId !== environment.leaseId || saved.environmentId !== environment.id
|
||||
|| saved.providerKey !== target.providerKey || saved.remoteCwd !== params.cwd
|
||||
))) return params;
|
||||
return {
|
||||
...params, remoteExecution: adapterExecutionTargetSessionIdentity(target),
|
||||
...params, cwd: target.remoteCwd, remoteExecution: adapterExecutionTargetSessionIdentity(target),
|
||||
...(input.adapterType === "claude_local" && !params.mcpServerIdentity
|
||||
? { legacyPlatformMcpSession: true } : {}),
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue