Keep manual receipt cursor progress when chat is disabled. Follow saved cleanup deadlines after actual child-process and PostgreSQL work settles, and preserve the exact failure-backoff boundary.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Keep virtual time fixed while actual filesystem and database work settles, then advance the coordinator deadline. Preserve the three-second page-gap limit and snapshot grace assertions; starvation mutation remains detected.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Observe the target adapter-start event, then await its existing run-specific completion barrier. Preserve succeeded, execution-count and review-promotion assertions, including deliberately slow completion beyond the old terminal poll.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
The deadline worker retains its incomplete-subtree error and classifier tests. Remove the obsolete reporter and its two isolated tests after its aggregate callers were removed.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Handle completion notifications in the deterministic agent instead of replaying the original plan command. Wait for the real completion turn and retain exact plan revision, output, project, and assignee assertions.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Observe the specific succeeded retry before closing its task and draining all execution. Once execution has settled, advance bounded fake cleanup time while joining worker passes; retain the existing timeout, exact retry identity, output and zero-SQL assertions.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Separate retry-schedule notifications from ordinary queued-run eligibility. Preserve exact deadlines and prove native producer commit/rollback behavior without global retry scans on ordinary agent or issue edits.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Lock and re-read project pause eligibility before publishing budget intent. Cover actual project Retry parking/release and concurrent pause admission without no-op hints.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Distinguish a budget or agent refusal saved on the existing receipt from genuine recovery failure. Preserve HTTP409 messages and details, grant each new wait reason its existing finite grace, and retain failure backoff for unsaved or unexpected errors. Prove actual Retry admission,25-minute SQL quiet, independent warm producer-to-process release, missing/cancelled receipts and real PostgreSQL write failure.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Preserve company reconstruction for graph, run and eligibility changes. A real PostgreSQL producer regression verifies one assignment check among1000 open tasks, with broader eligibility reconstruction still intact.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip manages AI agents and their task execution.
> - Runner checks qualified native programs before it starts a provider.
> - Installation checks and command opening can hash the same program
again.
> - A new sidecar process repeats this work on a later warm task.
> - This pull request stores the computed digest in a private filesystem
cache.
> - Every cache hit still checks the independently selected trusted
digest and current file metadata.
> - An unchanged program can start without another full native file
read.
## Linked Issues or Issue Description
**What existing behavior does this improve?**
Repeated **qualified ACPX** native executable admission in Runner.
Claude, ACPX-Codex, and Grok use this verifier in both Core-owned and
plugin-backed launches. The ordinary native Codex app-server launcher is
a different path and is not changed. A retained provider that opens no
new native command has no saved hash work here. The measured Claude task
path does not require a comment backlog.
**Current behavior**
Installation verification and final command opening each read and hash
the native executable. The qualified Linux x64 Claude file is
241,667,256 bytes. A new verification and command lease reads
483,334,512 native bytes. Another Node process repeats both reads.
**Proposed behavior**
Store the actual computed SHA-256 digest by device, inode, size,
nanosecond modification time, and nanosecond change time. Reuse it
across Runner and sidecar processes. Compare each hit with the current
build-owned digest. Hash the file on a miss or changed identity. This
replaces the earlier Claude-only deferral design in this same PR.
**Reason and benefit**
The first verification and command lease reads the native file once.
Later processes read none of its native bytes while its identity stays
the same. Related: #16372 established qualified executable admission.
This change is independent of other preparation PRs. It does not change
provider pins or profile versions.
## What Changed
- Add a shared filesystem digest cache under the absolute OS temporary
root at `paperclip-runner-<uid>/executable-digests-v1`.
- Use private directories and files, versioned hash-only filenames,
bounded JSON entries, and atomic temporary-file replacement. Cache files
contain a version and the computed digest. They contain no credentials
or source paths.
- Fall back to full hashing when the cache is missing, unreadable,
malformed, or unusable. Keep current expected-digest selection outside
the cache.
- Keep cache data outside task homes. Neither `HOME` nor task cwd
selects the path. The OS temporary-root selection honors absolute
platform `TMPDIR`, `TMP`, or `TEMP` overrides. A relative root disables
caching. Hosts must keep that root outside task workspaces. Temp cleanup
causes a later rehash.
- Keep the no-follow executable open, regular-file and size checks,
execute permission, descriptor ownership, before/after identity checks,
and preparation-to-launch identity comparison. Cache publication occurs
before the final identity checks.
- Use this path for standalone installation checks and final command
opening. Remove the earlier pending-digest type and Claude-only launch
helper. Runtime host and profile selection keep their original
structure.
- Test changed content, all fingerprint fields, symlinks, mode changes,
inode replacement, mutation during lookup/publication, concurrent cache
writers, wrong trusted pins, and cache failures.
- Include a deliberate trust-boundary test: a process running as the
same OS user can plant a matching cached digest for changed bytes. It
opens a test descriptor and executes nothing.
In plain language: Runner remembers the hash for the same file. It still
checks which file it opened and which hash the build requires. The cache
trusts the OS user who can write it.
## Verification
From `packages/paperclip-runner`:
- `../../node_modules/.bin/vitest run
src/drivers/acpx/executable-digest-cache.test.ts
src/drivers/acpx/installation-integrity.test.ts
src/drivers/acpx/runtime-host.test.ts` — 177 passed, 6 platform-specific
skips.
- `../../node_modules/.bin/tsc -p tsconfig.json --noEmit` — passed.
- `git diff --check` and the added-line secret, PII, private-address,
and code-comment scan — passed.
The offline comparison uses baseline
`45a3a92e6dc52d20923b9aa34a22fee2ec5443f9` and treatment
`c4356df12032448de90516ae329d8f939af10984`. Each sample starts a
different Node process. Each normal sample runs the actual qualified
installation verifier and opens a command lease. Seven samples per case
follow one warmup. The cache is removed before each cold case. The next
process reuses its entry. Baseline order alternates around the treatment
pair. Command digest and package paths match in every pair.
| Verifier plus command opening | Baseline | Cold cache | Later process
hit |
| --- | ---: | ---: | ---: |
| Full native reads | 2 | 1 | 0 |
| Native bytes read | 483,334,512 | 241,667,256 | 0 |
| Local median, ms | 1041.26 | 602.70 | 130.17 |
| Local range, ms | 994.20–1133.11 | 580.27–999.83 | 121.96–179.45 |
| Median whole child process, ms | 1379.96 | 988.75 | 523.01 |
Raw verifier-plus-open samples in ms:
- Baseline: 1133.11, 1074.40, 1061.99, 1041.26, 994.20, 1036.12,
1037.70.
- Cold: 647.13, 646.33, 999.83, 584.83, 580.27, 583.37, 602.70.
- Later process hit: 179.45, 138.37, 134.39, 130.17, 121.96, 122.33,
126.68.
Separate direct-opener fault probes use a disposable copy of that same
native file:
| Change | Native bytes read | Result | Local open, ms |
| --- | ---: | --- | ---: |
| First copied file | 241,667,256 | Accepted | 526.14 |
| Next process, different HOME and task cwd | 0 | Accepted | 24.91 |
| Changed timestamps | 241,667,256 | Accepted after hashing | 531.40 |
| New inode, identical bytes | 241,667,256 | Accepted after hashing |
536.70 |
| Corrupt byte, same size and exactly restored mtime | 241,667,256 |
Rejected | Whole process: 841.36 |
| Different independent trusted pin | 0 | Rejected | Whole process:
374.76 |
The corruption probe keeps `mtimeNs` exactly equal before and after, at
`1000000000000000000`. Its changed `ctimeNs` invalidates the entry.
Cache expiry is not required. The changed-pin probe rejects the old
actual digest without a native read.
The cached image is
`sha256:99ce80690a0c13328f41fd84156cc73e1ac562eaed3120fd41d04d7acdfb325d`.
The Node binary is `/opt/paperclip/runner/libexec/node`, version
`v24.20.0`. The artifact is Claude SDK `0.3.286`, with expected digest
`sha256:fe503f65c6289d59c23e5b21ae44f03583f997dd33a2cbfc75ab4f96fb8fc73f`.
The container used a read-only root, a disposable writable tmpfs, and
disabled network. No provider process, model request, credential, or
deployment was used.
**These elapsed values are diagnostics from Linux x64 CPU emulation on
an ARM64 Docker host. They are not production latency or TTFT
estimates.** The exact byte counts, cross-process reuse, output
equality, and invalidation results are the portable evidence.
The saved phase 0 exports have no directly comparable native executable
hashing span. Their broad `runner.transport.attach` spans include other
work. A production comparison within 20% is therefore unavailable for
this seam. The earlier local 919 ms control was also an emulation
diagnostic; it is not a production baseline.
The initial Docker attempt was denied access to the local socket by the
sandbox. The same offline command then ran with local socket permission.
An initial fault probe restored mtime through a `Date`, which did not
prove exact nanosecond equality. Its raw report is retained as
historical; the final probe sets and verifies an exact mtime before and
after corruption. The prior Claude-only implementation and its checks
and measurements remain historical evidence. They do not qualify this
replacement.
The superseded home-directory cache head had one browser recovery test
timeout in its GitHub workflow. It expected a saved title after session
recovery. The failure is retained. No browser code was changed; the
current head reruns CI after moving the cache outside task homes.
No full local CI suite was run, as requested. GitHub CI supplies full
regression coverage. Current head
`c4356df12032448de90516ae329d8f939af10984` passed the complete [GitHub
workflow](https://github.com/paperclipai/paperclip/actions/runs/41145223331),
including all browser shards. Its latest checks have 56 successes and 2
intended skips, including both security scans started when the PR became
ready. Standard Greptile reviewed this exact head at 5/5 with no open
findings. Apex then reviewed the same head at 5/5 under the documented
OS-user trust policy. It reported no new findings. There are no
unresolved review threads. The PR is ready for review and remains
unmerged.
## Risks
- This deliberately weakens protection against the same OS user. That
user can write the cache and can plant a chosen digest for a file
fingerprint. A cache hit trusts that record; metadata identity is not a
fresh content hash. The explicit fault test documents this accepted
policy.
- A read-only administrator-owned Runner can use this user-writable
cache. The cache has no signature, secret, or daemon. It does not
require a Launcher or ROM change.
- Other users cannot use normal cache permissions to write these
entries. Cache errors fall back to hashing. Removing the cache directory
restores full hashing. Old fingerprints leave small unused entries until
the cache is removed or OS temp cleanup evicts it. Temp eviction costs
one later full hash.
- A changed file normally rehashes. A digest that differs from the
independent current pin still fails admission. Existing command,
package, credential, process-lifetime, and final dispatch checks remain.
- No schema, public protocol, package dependency, profile version, or
provider pin changes.
## Model Used
OpenAI Codex, GPT-6 family, with reasoning, code editing, test
execution, and tool use. This session does not expose a more precise
serving model ID or context-window capacity.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used with the details available to this
session
- [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 enhancement
template
- [x] I have not referenced internal or instance-local Paperclip issues
or links
- [x] My branch name describes the change and contains no internal
ticket ID
- [x] The targeted local tests pass; the full local suite was not run as
requested
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation
- [x] I have considered and documented risks
- [x] All Paperclip CI gates are green for the replacement head
- [x] Standard Greptile is 5/5 with no open findings for the replacement
head
- [x] Apex is 5/5 on the current head with no unresolved findings
- [x] I will address review findings before requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
Reconcile failed ephemeral Cloud leases through a signed cleanup-only absence observation when local historical inspection is unavailable. Preserve failure outcomes, exact resource bindings, retention and row-version fences; grant no run-continuation authority.
All 54 current-head checks/statuses passed. Greptile is 5/5, Medium, with no findings or unresolved threads. The 38 focused tests pass; both new reconciliation cases fail without the fallback. Isolated baseline and branch idle suites each pass all 225 cases. The original broader local teardown timeout is retained in the PR, and the full local run remains in progress.
Cloud protocol support is already serving in staging. Exact candidate composition and hosted provider sleep/wake qualification follow this merge. Production has not been changed.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Release workflows publish npm packages and multi-platform images.
> - A master push can finish its image before the npm canary dispatch
starts.
> - The dispatch currently builds that same source again on two runners.
> - This PR verifies and reuses the signed master image by digest.
> - Missing images retain the native build path. Channel promotion still
follows npm.
## Linked Issues or Issue Description
**What existing behavior does this improve?**
The Docker workflow dispatched after npm canary publication.
**Current behavior**
Every canary dispatch builds both architectures, even when master has
already published the same source. This consumes runner capacity and
delays the image channel update.
**Proposed behavior**
Resolve the full-SHA master image. Verify its platform index, digest and
signed source identity. Retag that immutable digest. Build normally when
the image does not exist.
**Reason and benefit**
Remove duplicate image builds while keeping missing-image recovery and
the current npm channel check. A recent duplicate used about 25
runner-minutes. The saving depends on whether the master image exists.
## What Changed
- Add a canary-only reuse job before the native matrix.
- Require signed provenance from the canonical Docker workflow on master
at the exact source and signer commit. Read the proof from GHCR.
- Reuse the validated digest or build both platforms when the full-SHA
image is absent. Stop on registry and verification errors.
- Keep the serialized npm dist-tag promotion after either path. Keep
other build lanes independent.
- Add failure tests, register them in CI and update release
documentation.
## Verification
- Passed 603 tests: `node --test '.github/scripts/tests/*.test.mjs'
scripts/standard-image-contract.test.mjs
scripts/reuse-canary-image.test.mjs
scripts/__tests__/release-verify-workflow.test.mjs
scripts/cloud-source-verification.test.mjs`.
- Passed `actionlint -shellcheck='' .github/workflows/docker.yml
.github/workflows/pr-trusted.yml` and `git diff --check`.
- Full actionlint reports the same six existing shellcheck findings as
the unchanged master workflow. No new findings.
- A published master image passed the exact provenance command. This was
a read-only check. No images were retagged.
- Gitleaks and manual diff inspection found no secrets or private
references.
- Attempted `pnpm -r typecheck`, `pnpm test:run` and `pnpm build`. They
cannot start because this isolated checkout has no installed
dependencies (`tsc` and `tsx` are absent). Repository CI passed on
`3c46f0152d4db94a7f393bc79f466aa6c2c745e2` after the documented
browser-test rerun. All checks are complete. The original failure and
prior baseline occurrence remain recorded in the PR comments; its root
cause remains unknown.
## Risks
- The image can appear just before its attestation. Verification then
fails closed. Rerun after the master producer completes.
- Reuse preserves the first build's resolved dependencies and tool
versions. It intentionally does not rebuild the same source to refresh
floating inputs.
- A skipped or unfinished master image still needs a full build. No
speedup is claimed for that path.
- End-to-end reuse and runner savings require observation after merge.
No live publication was performed.
## Model Used
OpenAI `gpt-6.1-sol`, through the Codex harness in T3 Code, with xhigh
reasoning effort and shell/code tools. The context window size is not
exposed by this runtime.
## 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>