Files
PaperClipAI/doc/plans/2026-10-28-skill-package-storage.md
T
DottaandPaperclip f1911a4c53 perf(skills): move repository packages out of Postgres (#18082)
<!-- entire-trail-link-start -->
https://entire.com/gh/paperclipai/-/trails/205/changes/paperclip/205
<!-- entire-trail-link-end -->

## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Agents receive selected company skills before each turn.
> - Imported skills can need files from the whole source repository.
> - The current path stores those files in Postgres and reads large JSON
payloads during preparation.
> - This pull request stores verified packages in the configured file or
object store and keeps preparation queries small.
> - It also converts old rows automatically and restores missing files
from the exact saved commit.
> - The result reduces database traffic while preserving skill versions
and repository dependencies.

## Linked Issues or Issue Description

**What existing behavior does this improve?**

Company skill import, version storage, and preparation before agent
turns.

**Current behavior**

Repository snapshot bodies live in JSONB. Skill preparation can read
those bodies again even when the files already exist on disk. Historical
packages and native assets also need a bounded cleanup path.

**Proposed behavior**

Store package archives and manifests through the existing storage
service. Keep version identities, receipts, hashes, and recovery
metadata in Postgres. Convert legacy rows after the stored package
passes read-back verification. A missing package can use legacy bytes or
its exact Git commit under current repository authorization. No separate
data-migration command is required.

**Reason and benefit**

The warm path sends no repository bodies through SQL and reads no object
bytes. Cleanup runs through the existing deadline coordinator. Current
versions, active runs, explicit pins, and authored content retain their
protection.

**Breaking changes**

The normal application schema upgrade is required. Single-version and
skill-file responses keep their existing shape. Version lists,
current-version summaries, and test-run version summaries now contain
file names and kinds without file bodies. Diff and restore fetch
selected versions through the existing single-version endpoint. Older
server builds must not write after conversion unless they support the
new package format. The storage override and rollback limits are
documented in `doc/skill-package-storage.md`.

**Additional context**

Related work: #15731 preserves repository dependencies; #17923 changes
version-cache scheduling; #18080 changes delivery to remote Codex homes.
This change owns package persistence, acquisition, and expiry. It does
not replace those delivery or scheduler changes.

## What Changed

- Add company-scoped archive receipts, exact-commit recovery recipes,
migration deadlines, and a durable object-deletion queue.
- Add bounded archive upload, read-back verification, extraction,
concurrent request coalescing, and verified local caches.
- Convert old snapshots and versions automatically. Keep legacy bytes
when verification fails.
- Recover missing files through current source authorization and the
original commit. Validate repository identity, file hashes, modes, and
the aggregate hash.
- Expire unused recoverable Git packages and native assets after 30
days. Preserve active pins and the durable copy of authored or unproven
content.
- Keep warm preparation queries metadata-only. Bound file digest caches
and avoid repeated body hashing when file identities are unchanged.
- Extend the existing storage contract with cancellation. Register
package work with the existing coordinator. Optional conversion retries
retain the original bytes and may wait for ingress without blocking
sleep.
- Add regression tests, operating guidance, a deep trace, benchmark
receipts, and an optional maintenance command.

## Verification

### Final master integration and review repair — October 29, 2026

Current head: `a6065424249e763dfeac37161f1b52a9f25bd9b7`. Commit
`f8bbdd3dbd` resolves the conflicts with master `76a796e964`; the final
two commits repair the Apex deletion findings below.

- Keep both package-maintenance and version-retention notifications in
their write transactions.
- Keep the shared run-pin predicate and master's controller-lease
deadline metadata. Package expiry reads the updated protected-version
result.
- Preserve every master migration entry. Generate migration **0415
(`0415_zippy_zemo.sql`)** after master's 0414. Its skill SQL is
byte-for-byte unchanged from the previously tested idempotent migration.
Retain only the five newest schema snapshots. Migration numbering and
safety checks pass.
- Repair failed file/folder deletion: preserve a complete temporary
subtree until transaction commit, restore on upload or SQL failure, and
restore callback failures before releasing the mutation lock. Both
PostgreSQL regressions reproduced missing files before the fix and now
pass. They cover binary data, executable permissions, empty folders,
unlisted files, unchanged database/version rows, successful retry after
upload-claim expiry, and historical reads.
- Reconcile lost commit replies under the same advisory and row locks.
Check the saved version and source identity before restoring. Keep
committed deletions intact and preserve backups when the database
outcome is unknown or a newer edit exists. Four real-PostgreSQL outcome
tests pass; the prior code failed three of them.
- Full workspace typecheck and production build pass on the final
commit. All **204** focused PostgreSQL package/cache/retention/authoring
and filesystem-boundary tests pass, with none skipped.
- Apex review **18 passes 5/5**, with zero new comments on the exact
final head. All **34** review conversations are resolved.
- [Final-head
CI](https://github.com/paperclipai/paperclip/actions/runs/40914116508)
completes successfully on attempt 2. The head has **55 successful
checks**, two neutral reports, two skipped checks, and a successful Snyk
status. No checks are pending or failing. All eight browser shards pass.
- Runner Vitest 2/2 timed out once in the unchanged ACPX
controller-replacement fixture. The same job passed on the preceding
commit. One unchanged CI retry passes all five ACPX recovery cases,
**2,253** Runner package cases and **126** native server integration
cases; their standard one and five skips remain. The initial timeout's
root cause is not established. Local diagnosis also hit the fixture's
eight-second session.open timeout with an authenticated Runner; no local
Runner pass is claimed.
- Both full local `pnpm test:run` attempts were stopped when Apex
identified deletion defects and source repair became necessary; neither
is claimed as a passing full local run. The final **204** focused tests,
full workspace typecheck, and production build pass. Full-suite
acceptance uses the final-head CI jobs. The superseded merge-head CI
included runners terminated by shutdown signals; each source repair
started a new full run.

- Auto-merge is enabled for squash merge. The final head is
conflict-free and all checks pass. GitHub's active master ruleset still
requires a code-owner approval for the dependency files. The existing
review requests are with `devinfoley`, `nickyleach`, and `forgottendev`;
no policy bypass was used.
- The earlier hosted stress and full native receipts below remain tied
to `a2e597d800`; they are not relabeled as a deployment of this final
integration. The earlier local PostgreSQL startup failure remains
recorded below; PostgreSQL ran successfully for this integration
verification.

### Full native skill execution — October 29, 2026

The full native-agent acceptance gap is closed on candidate
`a2e597d800eb989d565780e7aa947d5368676444`. Two qualifying native turns
used OpenAI API / `gpt-5.4-mini` in real Daytona sandboxes, on the same
disposable hosted TEST tenant as the stress test. A follow-up on the
same task consumed a newly saved skill version.

The task text asked for the assigned skill workflow. It did not contain
the proof nonce or expected receipt. The assigned authored skill
contained a Python verifier and a random package-local nonce. The agent
read the actual installed skill files and executed that verifier against
the imported, automatically converted 1,360-file repository package. It
checked the repository file count and reference/binary bytes, and
directly executed the repository's executable probe. Independent
verification matched the emitted JSON to the nonce and expected byte
hashes. The agent persisted the same receipt as its task response;
native finalization committed, workspace finalization succeeded, and
task state became Done.

| Qualifying turn | End-to-end run time | Recorded skills.prepare |
Repository files | Binary hex | Shell receipt SHA-256 |
|---|---:|---:|---:|---|---|
| Initial task | 47.881 s | 1822 ms | 1360 | `00ff07800a` |
`a1deb30dc095f2306c7aa9cc5ec396da12c68961cf5b809f4d7c20f7fc68493b` |
| Corrected follow-up, new nonce/version | 37.974 s | 377 ms | 1360 |
`00ff07800a` |
`dbcdc33fa1376b4edc136feae1cdf770560a73292467e93619efdda20e4282a7` |

Both successful shell receipts reported `probe: STRESS18082` and
`assetSha256:
e8ea7a2f15f7bb3b332a46adbef848465884c8996595aa234748befd544d1f3a`. Their
proof nonces, selected version IDs and receipt hashes differed, and both
ran on the same task. These are observed timings, not latency SLOs; they
include real provider work. The service stress-test timings remain a
separate measurement boundary.

Earlier failures remain in the evidence:

- The original five-minute Daytona allocation timeout never reached
model execution. After normal owned-stack restore, its durable cleanup
record was `expired` / `success`. The cause of that original allocation
timeout remains unknown; a later success establishes recovery only.
- Initial continuation setup used unsupported generic agent-update
fields and returned HTTP 409 before dispatch. Setup was corrected to use
the supported skill-assignment and versioned instruction commands.
- The intervening completed native turn read repository bytes and ran
the probe, but omitted the verifier JSON under the older test agent's
overly-specific instruction file. Its strict receipt validator failed;
it is not counted as a qualifying turn. Correcting the test agent's
instruction file and sending an explicit task-scoped wake produced the
qualifying follow-up above.
- The pinned application's immutable rate card omits `gpt-5.4-mini`, so
completed usage triggered the normal block-on-unpriced budget policy.
Supported append-only accounting corrections estimated the three
completed runs using the [official OpenAI model rate
card](https://developers.openai.com/api/docs/models/gpt-5.4-mini): $0.75
input, $0.075 cached input, $4.50 output per million tokens, assuming
standard processing. Estimates totaled 12.482655 cents. Original usage
and missing-price records are preserved; the $1 hard stop and
unpriced-usage guard stayed active.
- Model shell attempts included a missing `xxd` command and incorrect
guessed file paths. Those failed calls and subsequent successful actual
verifier calls remain visible in complete event streams. No
zero-tool-error claim is made.

No product source changed during this continuation. The previously
measured contention, cold-start and memory limits remain. The later
merge resolution is covered by the separate integration checks above;
these hosted receipts qualify their recorded candidate.


### Hosted stress test — October 29, 2026

**Hosted acceptance:** full native-agent execution and a changed-skill
follow-up pass on the recorded hosted candidate. The final integration
section above records the subsequent conflict resolution and its
separate checks.

Tested baseline `929e840595c8933ca485ac7a2e99413e1e11ca64` and this
exact candidate sequentially on one disposable TEST tenant, with
external Neon PostgreSQL and actual S3. The fixture contained **1,360
files / 22.4 MB / ten skills sharing one repository**. Measurements
cover deployed skill-preparation and native-context services in an
adjacent diagnostic process, not a complete agent turn. The container
had **24 vCPU / 24 GB**.

| Warm workload | Median before → after | p95 before → after | DB
returned value bytes/preparation before → after |
|---|---:|---:|---:|
| One skill, serial | 1,060 → 125 ms | 1,303 → 155 ms | 22,610,389 →
1,771 |
| One skill, four concurrent | 4,415 → 297 ms | 5,143 → 328 ms |
22,610,389 → 1,771 |
| Ten skills, serial | 5,723 → 535 ms | 6,057 → 600 ms | 224,739,030 →
10,671 |
| Ten skills, four concurrent | 24,410 → 1,986 ms | 24,833 → 2,221 ms |
224,739,030 → 10,671 |

- Candidate warm preparation performed **zero S3 GETs**. Ordinary-pool
queries fell from 16 to 9 for one skill and 61 to 9 for ten, excluding
dedicated lock connections. DB numbers measure returned values before
Drizzle decoding, not wire bytes. Baseline/candidate sample counts were
30/100, 32/100, 5/50 and 8/48 respectively; the small baseline ten-skill
samples are comparative evidence, not SLO estimates.
- **Application-UID soak:** 800 ten-skill preparations over 10.3
minutes, four workers paced by 2.5 seconds; **zero failures**, median
**559 ms**, p95 **775 ms**, p99 **910 ms**, zero warm S3 reads. All 726
accompanying health/company-list requests returned HTTP 200. Their
end-to-end p95 was 569/502 ms, including network latency. The probe
overlaps most of the soak. Soak DB-response capture was limited to its
first 60 seconds.
- **Important remaining costs:** an unpaced 16-way ten-skill burst had
p95 **8.32 seconds** because company-level preparation serializes. Cold
preparation took approximately **1.1–2.5 seconds** for one skill and
**7.4–10.0 seconds** for ten. Ten-skill diagnostic RSS remained about
**1.17 GiB** (baseline 1.18 GiB), and the soak peaked at **1.35 GiB**. A
concurrent whole-container sample was 3.45 GB. This does not establish
suitability for a small-memory host or eliminate per-entrypoint native
asset duplication.
- **Upgrade without a manual data migration passed:** legacy snapshot
JSON shrank from 22,465,691 bytes to `[]`, with a stored package and
zero pending conversions.
- **Recovery passed:** sixteen concurrent cold requests coalesced into
two S3 GETs; full bytes/binary/executable modes matched; same-size
corruption repaired; an injected storage failure preserved warm access
and failed cold access safely. A disabled public-Git source denied
recovery, and after the actual five-minute backoff its exact SHA
reconstructed all 229 files. Cross-company descriptor access was denied.
Private-Git credential revocation was not tested.
- **Retention passed:** reclaimed 27,754,496 allocated cache bytes,
deleted an unreferenced package row and both objects, preserved aged
authored history, and expired/reacquired reconstructible Git content. A
version pinned by the failed native run was correctly retained. Only
owned fixture timestamps were aged.
- **Browser UI passed:** authored skill creation, saved edit surviving
reload, version history, and restoration of earlier content.
- **Initial native attempt (superseded by the qualifying turns above):**
one real run failed at Daytona allocation after five minutes
(`setup_failed`), before any model execution. Provider cleanup also
reported failure. That attempt does not count as a passing native turn.
Its receipt and all evidence remain preserved; its cleanup later
recorded success after normal restoration.
- Initial A/B commands ran as SSH root. Diagnostic-owned lock/cache
permissions caused one setup HTTP 500 and the first UID-1000 soak to
fail; exact fixture paths were repaired. The final soak, retention and
recovery ran as application UID 1000. A fresh UID-1000 one-skill batch
also passed (30 samples, median 116 ms, p95 321 ms, 1,771 DB value
bytes/preparation). Earlier harness-export, SSH, npm provenance, backoff
and UI-wait failures remain recorded alongside their successful retries.
- Private evidence includes raw timings, assertions, HTTP probes,
screenshots and failure/cleanup receipts. Initial failed-campaign
cleanup suspended and retained the owned TEST tenant and released the
shared QA account lease at **15:22:41 UTC**. The tenant was subsequently
resumed under its existing ownership for the native execution
qualification above. Final cleanup completed at **15:53:09 UTC**: the
account lease is released and the tenant is suspended/retained under the
original October 30, 12:24:52 UTC retention deadline. Its separate stack
lease remains held; no active or pending-cleanup environment lease
remained. The original retention window is deliberately preserved even
after successful continuation.

- Earlier hosted-tested head:
`a2e597d800eb989d565780e7aa947d5368676444`, integrated with master
`929e840595`. The earlier merge conflicts were resolved at this head.
[Current-head
CI](https://github.com/paperclipai/paperclip/actions/runs/40884087629)
passed; all checks are complete with 55 successes, two neutral reports,
two skips, and no failures. [Apex review
15](https://github.com/paperclipai/paperclip/pull/18082#issuecomment-6480257258)
passed 5/5 with zero findings on this exact head. All review
conversations are resolved.
- Ten real-filesystem boundary cases and server typecheck pass locally
for the security fix. Coverage includes cache roots, descendants,
individual files, symlink aliases, recursive ancestors, a future
concurrent cache fill, and allowed sibling directories. [Current-head
private-project integration
tests](https://github.com/paperclipai/paperclip/actions/runs/40884087629/job/123483306405)
passed all 29 cases, including authorized and unauthorized imports of a
cached private package. Full typecheck, build, server suites, all eight
browser shards, and Runner checks passed in current-head CI. Local
database tests could not run because PostgreSQL could not initialize;
this limitation is recorded below.
- The earlier generated migration preserved the newest five snapshots.
The final integration renumbers its unchanged SQL to 0415. Migration
safety/numbering checks pass. Its SQL is byte-for-byte identical to the
previously verified idempotent skill migration; repeated application on
this latest schema was blocked by the same local database startup
failure.
- The broader verification before the final migration-only rebase passed
1,025 package, lifecycle, remote-launch, native-session, and
asset-retention tests, 52 focused UI tests, full workspace
typecheck/build, token gates, and map checks. The linked complete CI run
passed before the latest focused fixes.
- A disposable MinIO instance passed multipart upload, verified
read-back, and cold restoration of an 18 MB archive.
- Twenty warm samples with a synthetic 1,349-file, 22.8 MB repository
retained five metadata queries and zero object reads. Median preparation
was 256 ms for one selected skill and 691 ms for ten, with 5 ms emulated
network delay each way. These are local measurements, not production
latency guarantees.
- No production instance was changed. Earlier verification failures and
their disposition are retained below.

<details>
<summary>Earlier failures, review findings, fixes, and rerun
evidence</summary>

- The first CI pass found a stale inventory-span expectation and a
missing repository-read span. Both are fixed; all three observability
tests pass. One browser test copied page text into its YAML parser after
reload; its first unchanged-head retry passed, but the race recurred
after rebasing. The test now waits for the renamed URL and completed
reload before copying the editor, retaining its original assertions. The
earlier full local suite did not complete cleanly: it reported setup
timeouts and assertions, while isolated relevant reruns passed. The
complete current-head CI run closes that broad verification gap. No
production instance was changed.

- The first Apex review scored 2/5 and identified six issues. The fixes
protect authored history even when it shares an in-flight Git upload,
recheck upload expiry during orphan deletion, bound failed-upload
cleanup, retain per-version kind/encoding metadata, add sanitized
failure diagnostics, and correct the migration guide. The final focused
run passes all 140 tests; the preceding eight-file run passed all 149
tests, including local storage and S3 diagnostics. Server typecheck and
migration safety/numbering checks pass after the fixes. The second Apex
review scored 1/5 and found unbounded maintenance storage calls and
mismatched package/Git batch limits. Those are fixed with a 30-second
cancellable cleanup deadline, shared 100 MiB/10,000-file scanner and
storage limits, and bounded Git read batches. The proposed manual
lockfile edit was rejected with evidence of the enforced CI-owned
lockfile policy. All 19 storage/recovery unit tests and server
typechecking pass. The new database-backed cleanup/conversion tests were
locally skipped because the host exhausted its PostgreSQL semaphore
capacity (confirmed directly by initdb). That gap is closed by
current-head CI: [package-store
tests](https://github.com/paperclipai/paperclip/actions/runs/40771320775/job/123109300889)
pass all 18 cases and [package-worker
tests](https://github.com/paperclipai/paperclip/actions/runs/40771320775/job/123109301157)
pass all 7, with no skips in either suite. The corrected YAML browser
shard passes. A separate planning-fixture test failed its cleanup DELETE
with an activity-log foreign key violation after its behavior assertions
passed; its one unchanged-head retry passed. The third Apex review
scored 1/5 and identified a one-slot pool deadlock, projected path
limits, cleanup holding the preparation lock during archive reads, and a
publication/expiry race. These are fixed with transaction-local reads,
prefix-aware bounds, isolated verification, and shared package row locks
with a publication grace. The separate Paperclip review also found stale
historical inspection, eager history downloads, and conversion retries
blocking sleep. Those are fixed. Local PostgreSQL capacity recovered:
the new database regressions ran, including the one-slot fork, paused
storage during a warm turn, and a locked publication during expiry. The
expanded run passed 144/145 tests; its remaining assertion still
expected file bodies from the version list and was updated to fetch the
selected version. The corrected company-skill suite passes all 81 tests,
and the final package/projection run passes all 27. All 52 focused UI
tests pass after updating their query-provider fixtures; the first UI
run exposed a fixture remount, and the follow-up exposed an editing
syntax error, both corrected. Full workspace typecheck and build, token
gates, and map checks pass. The sandbox initially blocked the build
tool’s local IPC socket; the authorized build rerun passed. Master then
claimed migration 0401. The branch is rebased on f8226b0515, the
unchanged SQL is regenerated as 0402, and repeated application passed
twice on the combined schema. Post-rebase workspace typecheck passes.
All 1,025 package, lifecycle, remote-launch, native-session, and
asset-retention tests pass on the combined head. All CI checks passed
for head 9d80a3b52d. The fourth Apex review scored 3/5 and found a
save/edit lock-order cycle, a retry retaining an uncommitted source ID
in its recovery recipe, and a summary read racing conversion. Commit
b7d9237c6a fixes all three. The new concurrency and retry tests pass
within all 61 package/source tests, and server typecheck passes. Master
then claimed migration 0402. The branch is now rebased on 65fa1cd617 and
the unchanged SQL is regenerated as 0403. All 68 package, source, and
worker regressions, server typecheck, migration safety/numbering, and
repeated migration application pass on current head 62553671a0.
Subsequent verification and review outcomes are recorded below.


- The fifth Apex review scored 4/5 and found cached version reads
unnecessarily depending on object storage and conversion reads missing
the coordinator cancellation signal. Commit 1eb90d80be fixes both:
cached reads verify bytes without the preparation lock; manifest and
archive reads carry cancellation through conversion. The first focused
run passed 70/71 tests, including all three new cases, but the existing
diagnostic test observed no warning. Its fixture now sets an explicitly
expired migration deadline; the final worker suite passes all nine
cases. Server typecheck passes.


- The sixth Apex review scored 4/5 with one finding: concurrent cached
inventories were unbounded. Commit 4b7589e14c gives cached inventories
and previews a two-reader/32-waiter limit independent of cold storage.
The first expanded pressure test observed a third reader before the
preview path had the same bound; both paths now share the local limit.
All 73 focused cases pass, followed by all 29 package cases after the
final overload adjustment; server typecheck passes. The separate
Paperclip review found no must-fix or should-fix issues. Its optional
suggestion to persist durability verification was considered: cleanup
intentionally verifies current external bytes before discarding the last
local authored copy, and the missing-object regression requires that
check. Shared-package eviction happens on cold fills with at most two
attempts and a ten-second budget.


- The seventh Apex review reached 5/5 with one remaining P2:
timeout-bearing manifest reads bypassed request coalescing. The security
review also found repeated archive acquisition after a cold
administrative read. Commit 79a80a39d2 shares manifest loads with
per-caller cancellation and cancels the underlying work when the last
caller leaves; coalesces identical cold version/file requests; and
publishes verified restored packages into the shared cache. Publication
uses a short nonblocking lock, while eviction verification runs outside
it. The first expanded run passed 73/74 tests and exposed a missing
originating DB for cache publication inside read-only inspection;
transaction readers now pass that owner explicitly, keeping metadata on
the transaction. The corrected run passed 75/75. A final eviction test
initially expected the shared package to remain after only its separate
runtime projection was reused; it now explicitly accesses the shared
package during verification. All 32 final package tests pass, including
independent caller cancellation, one acquisition across
concurrent/repeated reads, and warm preparation during stalled eviction
verification. Server typecheck passes.


- The eighth Apex review scored 4/5 with one finding: decoded cold
inventories outlived the storage semaphore while waiting for cleanup.
Commit 9d7921d1ec adds a separate two-response/32-waiter lifetime bound
held through cleanup, preserving the independent warm-read queue and
forwarding cancellation through eviction verification. The new
regression pauses cleanup after two responses decode, verifies a third
archive stays queued, and completes warm preparation before releasing
cleanup. All 77 package/source/worker cases and server typecheck pass.
On the preceding head, serialized server shard 6/9 was interrupted with
“The operation was canceled” after 57 seconds of tests; the log contains
no failing assertion. Fresh latest-head CI reruns that shard along with
the other gates.

- The ninth Apex review scored 4/5 with one P1: cold responses could own
both permits while waiting on a one-slot ordinary database pool held by
a queued transaction reader. Commit a94f0e409f reads caller-scoped
metadata before admission and isolates all acquisition/cleanup SQL on a
private connection. The new real-PostgreSQL regression reproduces the
deadlock against the previous head and passes with the fix; all 78
focused cases and server typecheck pass. The previous CI run completed
with 54 successful checks, including both formerly interrupted/stuck
serialized shards.

- The tenth Apex review scored 4/5 with one P1: preloading legacy bodies
before admission could retain many full repositories in queued requests.
Commit 276e353a37 removes that preload and also bounds legacy manifest
derivation/fallback, using private connections inside the I/O slot. All
80 focused tests pass; the final synchronized package suite passes all
36 tests and server typecheck passes.

- Apex review 11 returned 5/5 with zero comments on 276e353a37. While it
ran, master 8bc051245f added migration 0403, creating a metadata
conflict. Merge commit e1783764d9 integrates master and generates
0404_clumsy_leo.sql; its skill SQL is byte-for-byte identical to the
previously verified migration. The combined schema passes migration
safety/numbering, repeated SQL application twice, all 80 focused
PostgreSQL tests, full workspace typecheck, and full build. The
preceding a94f0e409f CI run was superseded and cancelled while its final
runner job was active; no old-head success is claimed for that run.

- Final acceptance: Apex review 12 returned 5/5 with zero comments on
e1783764d9. All 28 review threads are resolved. The final head has 55
successful checks, two neutral reports, two skipped checks, and no
pending or failing checks; the PR workflow completed successfully.
Superseded run 40789121902 was cancelled to free its workflow slot for
the final head; normal cancellation did not finish promptly, so force
cancellation completed it.

- October 29 conflict resolution (867c00673b): master 929e840595 added
installed-selection reuse and bundled relocation, routine cleanup
notifications, a hardened YAML copy helper, and migrations through 0413.
The merge retains those behaviors together with package read scoping and
the public-selection fast path; restricted selections still reauthorize
and the full row read rechecks scope. Generated migration 0414 retains
the existing SQL unchanged. Workspace typecheck/build and migration
checks pass. Local database verification is unavailable: initdb reports
semget / “could not create semaphores: No space left on device”; all 168
cases skipped, and repeated migration application failed at startup.
Docker returned HTTP 500 during container creation, so no Docker test
pass is claimed. Current-head CI must close this gap.

- Apex review 14 scored 3/5 with one P1: a local import could copy
cached private package files into a company-wide skill. Commit
a2e597d800 rejects cache trees and ancestors through a canonical
filesystem boundary, including aliases and ancestors checked before a
concurrent fill. Ten local filesystem tests and server typecheck pass.
The existing private-project integration case now rejects package-cache
imports for both authorized and unauthorized users. The preceding merge
commit’s complete CI run passed, and logs confirm all 168 locally
skipped cases executed successfully: package store 36, sources 35,
package worker 9, runtime selection 4, observability 3, authoring 81.

- Apex review 15 returned 5/5 with zero new findings on a2e597d800. The
separate Paperclip review found no must-fix or should-fix issues. Its
optional suggestions were considered: unexpected filesystem cleanup
failures remain visible, the durable marker cannot replace checking that
bytes still exist before deleting a last local authored copy, and
administrative history batching remains outside the pre-turn
optimization. The cache-path literal centralization suggestion is also
optional; the current producer and guard use the same directory, covered
by the new integration regression. All review conversations are
resolved.

- Earlier October 29 CI/Apex acceptance: a2e597d800 was mergeable at
that check, Apex review 15 passed 5/5 with zero findings, and all review
conversations were resolved. The hosted verification above records the
later acceptance limits and renewed conflicts. Workflow 40884087629
completed successfully, with 55 successful checks, two neutral reports,
two skips, and no pending or failed checks. The project-skill-sources
job log confirms all 29 cases ran and passed, closing the latest
integration gap. The preceding merge workflow also confirmed all 168
focused database cases ran and passed. No local PostgreSQL or Docker
pass is claimed.

</details>

## Risks

- First use after eviction can require storage or Git access. Revoked
grants, removed sources, and unreachable commits can prevent
reconstruction. Recovery never substitutes a newer commit.
- Historical packages without a proven recipe keep durable bytes.
Cleanup favors retention when a run pin cannot be interpreted safely.
- New uploads and conversion use bounded work and persisted retries.
Large initial conversions still add temporary storage and database load.
- Optional cleanup can wait until the next server wake. Supported
notifications assume one owning server process.
- Per-entrypoint native tree duplication and cold preparation cost
remain. The final ten-skill cold sample took 47.6 seconds on the shared
host.
- Generic authored edits can still hold an existing edit transaction
while a package uploads.
- Explicit file/folder deletion uses temporary disk space for rollback.
An uncertain commit with a changed skill or unavailable database retains
that backup and logs its location for recovery.

## Model Used

OpenAI Codex, GPT-6 family, with reasoning, repository tools, code
execution, and tests. The exact served 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 (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-10-29 12:19:37 -05:00

32 KiB
Raw Blame History

Move installed skill packages out of PostgreSQL

Status: implemented in this checkout, including automatic conversion, exact-commit recovery and cache retention. Focused verification passed; the broad test run did not complete cleanly (details below). No production changes. Operational contract: skill package storage. Measured results: benchmark receipts. Investigation date: 2026-10-28. Code inspected: 1ed0af41867ba40fd1d23292d395588d18c2fcbe.

Decision

Store the exact audited installed package in the existing storage backend: S3-compatible object storage for hosted instances, persistent local storage for local installations. PostgreSQL retains package identities, storage receipts, versions, source tracking, permissions and run pins. A bounded, verified disk cache supplies execution and file reads.

New Git packages retain an immutable recipe with repository identity, exact commit, included-file manifest and packager version. Authored and unproven legacy packages retain independent durable bytes. Also retain the installed package's content hash: the package can exclude unsafe, oversized or unsupported files and can be a reduced repository subset. A commit SHA alone does not identify that transformation, provide the bytes when upstream disappears, or represent an edited local copy.

Preserve today's packaging and access behavior during the storage migration. In particular, do not switch all imports back to directory-only mode or infer a complete dependency graph from Markdown references. The original dependency-loss bug was real.

The first delivery removes file-content traffic from PostgreSQL for migrated repository packages, avoids repeated remote reads of the same immutable package, and stops creating packages for repositories with no installable selected skills. Deduplicating the final per-skill runtime transport is a separate, explicitly scoped follow-up.

What the trace established

Import and refresh

  1. Project workspace creation/update calls enqueueProjectSkillSource inside the workspace transaction. An eligible GitHub URL is sufficient to enqueue; this happens before discovery. Existing legacy projects have an explicit adoption path.
  2. skillSourceSyncService.importBinding checks authority, reserves bounded work credits, and requests packageMode: 'repository'. The scanner resolves a commit and uses a shallow, temporary Git repository to list paths and read blobs.
  3. github-skill-source.ts discovers entrypoints, builds and audits package files, and filters unsupported/unsafe files. Repository mode normally includes the supported tree, with a skill-folder-plus-reference fallback for oversized repositories and a further skill-only fallback. It does not store Git history or .git metadata.
  4. skillSourceService.publish hashes scan.repositoryFiles and inserts their contents into company_skill_repository_snapshots.file_inventory. Deduplication is by company and content hash. The insert happens before the selected-valid-skill loop; a nonempty package can therefore be stored with no discovered or selected usable skills. ON CONFLICT DO NOTHING avoids a duplicate row, but still sends the proposed file array to PostgreSQL whenever publication reaches that insert. The earlier unchanged commit/tree shortcut avoids this path for qualifying automatic checks.
  5. Each selected skill version references that snapshot and its original entrypoint. Publication also passes candidate.files into createVersion, which persists those contents again in company_skill_versions.file_inventory. Thus the full repository is shared, but the selected skills' own files have an additional persisted copy.
  6. Automatic refresh already skips downloading when the commit or root tree is unchanged. It uses saved setup authority, leases, budgets and five-minute deadlines. An included file change changes the package hash and versions every selected skill. This conservative invalidation preserves access to shared and dynamically chosen dependencies.

The similarly named fields are different: company_skills.file_inventory and company_skill_sources.repository_package.files are file metadata; repository snapshot and version file_inventory fields contain actual contents. Moving the former alone would not address the measured payload.

Source pointers: project attachment, binding and identity, worker, scanner, publication, snapshot/version schema.

Reads and execution

company-skills.ts::runtimeVersionInventory selects the entire repository snapshot, then repositorySkillProjection builds a generated discovery wrapper plus the full original tree under .paperclip-repository/.

Consumer Current unnecessary or amplified work Replacement
Runtime preparation Loads JSONB before checking the materialized directory and before entering the per-version shared promise Resolve a small package descriptor; coalesce package loading before I/O; validate disk files against hashes
Skill audit Materialization loads the repository; audit requests the inventory again Reuse the verified package handle and manifest; audit actual delivered bytes
Skill detail/file tree Loads contents, then discards them to keep paths and kinds Read the package manifest only
One supporting file Falls back to loading the whole repository inventory Read the requested file from the verified local package cache
Fork, create-from-existing, disconnect-and-keep Projects repository files; editable versions can persist a full projected tree inline Copy from a package handle; save resulting snapshots to the same external package store
Cache retention hasRestorableSnapshot reads repository JSONB on a process-cache miss to validate entrypoints Verify external durability before eviction; never evict the last usable copy on storage failure
Version API / Studio test context Version objects and one heartbeat join directly consume inline version inventories Metadata-only internal queries plus explicit package hydration where content is required

The existing per-version disk cache does not prevent the initial database read. Existing cache locks serialize publication, pinning and eviction, but do not turn repository identity into a shared content cache.

Each selected entrypoint also gets its own full runtime copy. Native remote launch loops over skill bundles and stages each directory separately. Moving storage alone does not remove this disk/transport multiplication. Never assume 79 installed skills means all 79 are assigned or transferred on every run.

Source pointers: runtime, audit, browsing and version creation, projection, cache locking and restorability, Studio context, remote staging, company export.

Evidence and limits

The preceding Railway investigation recorded six snapshot reads returning 93.6 MB in a 15-minute window: 12.94% of observed PostgreSQL response bytes. A later metadata inspection found a package containing 1,349 files, 22,611,267 serialized JSON bytes, and 79 current referencing skills. These are prior observations, not a fresh benchmark of this proposal. The measured build was 71b448b9; the later example was observed on b25f3e2a. The principal import/runtime files inspected here match the earlier linked 2e5bbde8 revision. Do not extrapolate either capture to every current instance.

Serialized JSON bytes are neither compressed on-disk PostgreSQL size nor raw repository size. PostgreSQL can compress/store large values out of line using TOAST. Selecting the complete inventory still returns that value to the application. New snapshot payloads also add database storage, WAL, backup and decoding work; those costs were not quantified here.

Removing this traffic does not establish a 12.94% latency improvement, solve all Railway slowness, or justify a specific CPU reduction. The capture measures bytes, not causality for every slow request.

Why this implementation arose

  • #14713, September 30, introduced GitHub-synchronized skills on the existing immutable-version foundation.
  • #15731, October 12, fixed missing shared scripts, policies, sibling skills and credits by preserving repository packages. It explicitly chose shared database snapshots and self-contained per-skill delivery.
  • #16066, October 13, extended that behavior to automatic project imports and refresh.
  • #16308 preserved referenced files for oversized repositories. #16433 subsequently made old disk copies disposable, with database restoration as the guarantee.

The #15731 description reports strong byte/mode, offline, pinning and real runtime proof, and explicitly acknowledges per-skill runtime duplication. Its inline reviews caught audit/projection mismatches, lost binary/mode information, unsafe files, expensive regexes, and per-blob Git process overhead. The review was not indifferent to correctness or cost.

The inspected acceptance evidence does not establish a bound on PostgreSQL response bytes for warm preparation, one-file browsing, or multiple entrypoints sharing a large package. The supported explanation is that a legitimate dependency-preservation feature reused a content-bearing version contract, while performance proof focused on acquisition and functional delivery. Automatic adoption enlarged the affected workload. Original author sessions were not identified, so this is not a claim about unrecorded reasoning.

The preventive change is a storage boundary whose normal return type cannot contain a repository's bytes, backed by real SQL-byte and storage-read assertions for shared packages.

Proposed implementation

1. One package service, existing storage providers

Add an owning service such as skill-package-store.ts for encoding, publication receipts, manifest reads, verified extraction and cache acquisition. company-skills.ts consumes it; the heartbeat only asks for a prepared skill. Provider details stay under server/src/storage.

The existing storage API supports streamed uploads, reads, HEAD and deletion, with local and S3 providers. Reuse this infrastructure. Do not put packages in user-visible issue attachments or give them attachment retention.

Use a versioned tar+gzip bundle of raw file bytes and a separate bounded JSON manifest. The manifest contains original path, kind, byte length, SHA-256 and executable flag for each file. Archive entries live under files/; reserved format metadata stays outside that namespace. Preserve binary bytes and supported modes. No base64 repository JSON is needed in the stored archive. Stream encoding/compression to a bounded temporary file, hash it, then stream upload. Do not synchronously compress on the server event loop.

Keep the existing semantic content hash for migrated packages, so migration does not create new skill revisions. Separately record archive and manifest SHA-256 values and format version. Do not silently change the current hashing order or rely on an S3 ETag as a content checksum. Any future hash-algorithm change needs its own versioned identity.

A manifest is sufficient for a file tree; a cold file read may fetch the archive once. An archive deliberately favors simple bulk materialization over random remote file reads. If cold single-file traffic proves material, add an indexed/range-readable format later. Do not start with thousands of remote objects and database rows per package file.

2. Metadata schema and contracts

Extend the existing snapshot row with storage state/format and a receipt containing the provider, server-owned archive/manifest keys, hashes, compressed/uncompressed sizes and file count. Keep the existing snapshot ID and (company_id, content_hash) uniqueness. Add company-scoped relational constraints where a new package reference is introduced.

During migration a snapshot is one of: legacy inline; inline plus verified external copy; external only. An explicit discriminator distinguishes absent payload from an empty package. Keep pending upload attempts separate from externally readable versions, using small preparation receipts/leases in the owning snapshot storage metadata. A final version may reference only a ready package.

Repository-backed version rows retain their snapshot and entrypoint references but stop duplicating candidate.files. Preserve the directory-local view as a manifest projection. Existing historical version inventories must be migrated from their actual stored bytes; do not assume an older or checkpointed inventory exactly matches a current projection. If it differs, store that inventory as its own immutable package.

Support ordinary local, directory-only and forked versions with the same package reference in a second compatible writer path. Keep a bounded inline legacy reader until backfill finishes. Small searchable skill Markdown/metadata can remain on company_skills; the goal is to remove file-tree payloads, not prohibit useful database text.

Split internal VersionMetadata from content-bearing responses. Public CompanySkillVersion.fileInventory, the version routes, Studio and export consumers currently expect contents: preserve their response behavior through explicit hydration from storage, or add an opt-in metadata view and move clients to it. Never return an empty inventory to an old caller merely because the database payload was cleared. A storage location or opaque digest is not an authorization capability and need not be exposed.

3. Publication without long database transactions

authorize and scan immutable Git revision
  -> audit/filter the installed package
  -> obtain a bounded preparation claim for (company, content hash)
  -> write manifest + archive to unique attempt-owned storage keys
  -> verify stored lengths, hashes and manifest/file agreement
  -> short DB transaction: recheck source revision/lease/authority;
     publish ready receipt, versions, current pointers and activity together
  -> settle preparation receipt and release temporary files

Perform provider I/O outside the publication transaction. Refresh's current source and lease fencing still decides whether the result may be published. A failed upload keeps all installed versions. A failed or uncertain commit must be reconciled before cleanup; never delete an object simply because the transaction call returned an error.

Use immutable unique attempt keys, with logical deduplication in PostgreSQL. This matters: createStorageService.putFile currently deletes its upload key on failure. Reusing a single deterministic key across writers could let a losing/failed writer delete a winner. Do not use that API as a create-if-absent content-addressed store. A concurrent winner's receipt is retained; the losing attempt's own keys become cleanup candidates only after its outcome is known. Preserve pending receipts through restart and company deletion.

Amazon S3's strong read-after-write consistency helps verify a successful object write, but does not make an S3 write and SQL commit atomic. Qualify other supported S3-compatible backends separately. Pass cancellation through the storage facade; its current public read API omits the provider's AbortSignal capability, and uploads need bounded cancellation/settlement too. Avoid abandoned streams after timeouts.

4. Shared verified cache before per-skill projection

Use a cache identity containing instance/storage namespace, company, format and package content identity. Reauthorize the skill/version before every use. Deduplicating content within a company must not expand a private project's audience; authorization stays on the referencing skill/source, including forks and detached copies.

Acquire a package-scoped shared promise before opening legacy payloads or object storage. One cold request fetches/verifies/extracts the package; concurrent consumers reuse it. Different service instances in one process share this work. Use filesystem locking and atomic publication for processes sharing a volume; replicas with separate disks may each need one fetch. Keep cache ownership distinct from authorization and version pins.

Validate compressed size, expanded size, file count, normalized paths, duplicate/colliding entries, per-file digest and executable mode. Reject links, devices and traversal; never run repository code on extraction. An extracted directory or .complete marker alone is not proof of byte integrity. Warm runtime validation compares actual disk files with the hashed manifest, including unexpected files and modes, without rereading SQL payloads. Audit continues to inspect actual content and may require new policy scans independently of package immutability.

Keep the current self-contained .paperclip-repository skill layout initially. Build it from the shared package cache and reuse the manifest in materialization and audit. Preserve the existing company cache lock through final publication and durable run pinning. Stage slow downloads before that lock, then recheck authority/version identity inside it; if the caller already holds an outer preparation lock, change that owner deliberately rather than weakening pin safety. Cache eviction must respect active handles during staging.

Bound cached disk bytes, active downloads, expanded temporary bytes and in-memory manifests; do not introduce an unbounded process-wide map of decoded repository arrays. The existing 61 MiB package cap is not a bound on concurrent copies or historical cache growth.

5. Avoid content work when nothing can be installed

After listing the immutable tree, return a successful empty discovery when no valid entrypoint path exists, before batching support blobs and constructing a repository package. Persist the source's checked commit/tree and next check, not its contents. The initial shallow Git fetch still transfers Git objects; do not claim zero acquisition cost.

At publication, require at least one selected, valid candidate needing a package. A scan with only excluded/invalid skills can retain candidate metadata and warnings without a durable payload. A scan in which upstream skills disappear retains previously installed versions and their packages. A later commit can still discover and import a new skill.

Keep current unchanged-commit/tree checks and work-budget accounting. Changing the five-minute refresh policy or adding provider events is independent work, not a prerequisite for moving bytes out of PostgreSQL.

Alternatives considered

Alternative Assessment
Store SHA; clone from upstream on use Small database, but loses offline/deleted/revoked-upstream recovery and adds execution-time network work; does not reproduce filtered packages by itself
Durable bare Git mirror with retained refs Useful for acquisition deduplication; requires durable backup, pruning/GC rules, isolation and a filtered-export contract; does not naturally cover edited/generated skills
Self-contained Git bundles in object storage Viable for raw Git retention; Git distinguishes self-contained bundles from incremental bundles with prerequisites. More Git state than the installed-package contract needs
Compress JSONB or add a large RAM cache Can reduce a symptom; keeps database payload storage and cold-read cost, and adds heap pressure
Always keep only a skill directory Reintroduces known missing shared-file dependencies
Per-file content-addressed storage Better cross-version dedup and single-file access; adds object count, manifest graph and garbage-collection complexity before that benefit is measured
Immutable package archive + manifest + disk cache Recommended first implementation: existing providers, bounded bulk I/O, exact installed bytes, stable version references

The Git bundle documentation describes prerequisite verification. A bundle or mirror is a real retained copy; a remote SHA is only a reference. A reusable Git acquisition cache remains worthwhile if profiling shows repeated shallow fetches dominate, but it must preserve the scanner's credential and caller isolation.

Automatic conversion, acquisition and retention

The accepted follow-up removes the manual migration requirement. New writes default to external packages, and a finite delivery worker converts legacy rows, independently verifies storage and then clears duplicate payloads. It uses indexed row deadlines, transaction notifications and restart reconstruction, rather than per-turn work or an empty-table polling interval. The CLI is optional diagnostics. See the current operational contract for upgrade/rollback details.

New Git imports persist a restore recipe alongside the package: repository identity, source authorization reference, exact commit, precise included-file manifest and packager version. Warm queries exclude this larger metadata. A cold miss first uses local verified files, then stored bytes, then legacy DB bytes, then the original Git commit using current source authorization. Every recovered file must match the saved size/mode/hash; moving branches and current filtering rules cannot change it. Historical packages without a provable recipe remain durable instead of inferring an origin from mutable current skill metadata.

Unused Git archives become eligible after 30 days, keeping current versions, explicit pins and active/recovering runs. Metadata and recipes survive expiry. Retired storage keys have another 30-day deletion grace. Locally authored and edited packages remain durable while referenced. Canonical extraction retains its bounded cold-fill eviction; version directories use existing retention; native runtime assets now have a 30-day idle lifetime, pin-aware cleanup and a shared publication/deletion lock. A pin can conservatively defer cleanup until the next relevant mutation/use or restart.

Source and package writes remain company-scoped. Saved grants are rechecked on Git acquisition, with a persisted five-minute failure backoff and existing singleflight. No upstream network request, storage HEAD, cleanup scan, or TTL write is added to warm package preparation. Native access timestamps are coalesced hourly on disk.

Lifecycle ownership remains in the package/native asset services. index.ts only registers them with the existing coordinator. migration_at/maintenance_at and the existing deletion receipts are durable truth; no additional worker queue table was introduced. Conversion retains the original bytes and may wait for ingress without blocking idle sleep. Optional retention may wait for the next ingress wake. This does not establish deployed sleep or a Cloud alarm contract.

Delivery slices and proof

Slice Owning files/area Exit condition
A. Storage boundary and compatibility New skill-package-store.ts; storage facade; DB/shared version descriptors; company-skills.ts readers Real local and S3-compatible upload/restore round trips; old rows work; metadata reads contain no file bytes; no behavior change yet
B. Publisher and runtime skill-sources.ts, scanner, version/fork/copy writers, package cache, cache retention New package bytes never enter SQL; two or more selected skills share one cold package load; warm preparation does not reload it; zero-skill scan persists no package
C. Consumers and migration Version routes/client contracts, Studio context, export/disconnect, bounded migration command Old API content behavior preserved; historical pins and copies restore; full current/legacy consumer matrix passes; backfill can restart safely
D. Qualification and rollout Existing skill suites and runner journey; operational docs/maps Byte/performance evidence, fault injection, staged backfill, rollback boundary and cold recovery demonstrated

Slices A–D form the complete database-storage change. Keep runtime transport deduplication separate: add a shared package bundle plus entrypoint descriptors to the runtime context, transfer each package once, and construct each discovery entrypoint in the destination. Preserve the existing fallback for adapters that require independent directories. Do not use host-only symlinks or shared writable hardlinks as a shortcut; qualify read-only mounts, copy-on-write copies or destination-local materialization against each supported transport. This later change reduces transfer/disk multiplication; it is not needed to remove DB bytes.

Use the existing skill-sources, skill-version-cache, skill-version-retention, project skills and native runner suites. Add a representative large synthetic package and an assembled PostgreSQL/storage benchmark, not mocks that eliminate serialization and I/O.

Scenario Required evidence
1,349 files / approximately 22.6 MB JSON / 1, 2 and many entrypoints Record exact fixture size, actual PostgreSQL DataRow bytes, object GET count/bytes, CPU, peak RSS, disk bytes and preparation duration; sizes need not reproduce private content
One cold preparation using several skills from one package Zero repository file-content bytes in SQL; at most one payload download per package per shared cache scope; equal delivered bytes/modes
Warm preparation, audit and restart with intact disk cache Zero repository payload bytes from SQL; zero payload GETs while cache is valid; actual integrity checks retained
File tree and one-file read Tree reads manifest only; first file read may fetch one archive, repeated reads reuse it; unrelated package contents never pass through SQL
Unchanged refresh and no-skill repository No new package/version payload; empty scan remains discoverable on a later commit; existing installed content survives removal/failure
Storage failure, corrupt archive/manifest/cache, cancellation No partially published version; old installation remains; no silent SHA refetch; unrelated requests remain responsive
Concurrent import, uncertain commit, process crash, deletion No winner object deleted; persisted attempt reconciliation; stable IDs and source lease/revision fencing
Private project, revoked access, detached/forked content Fresh application authorization on cache hits; no company/project boundary widening
Pins, cold restore, Studio, forks, disconnect, export, retention Equivalent supported files, binaries/modes and discovery policy; historical semantics preserved without upstream
Backfill interrupted at each boundary Retry changes no version IDs or contents; no early payload clearing; dual-reader rollback works

Report SQL byte reduction separately from object-store traffic and execution latency. The target is zero package file-content bytes in PostgreSQL responses for migrated packages, not zero database queries or zero network bytes. No percentage CPU/latency saving is claimed until measured on the replacement.

Update doc/PRODUCT.md, doc/SPEC-implementation.md, doc/SPEC.md, doc/DATABASE.md, doc/DEVELOPING.md and recoverable-cleanup documentation to replace the database-byte restoration guarantee with durable-package restoration. Update affected skills/version feature and access map entries, and run node scripts/check-maps.mjs. Generate schema migrations with pnpm db:generate and follow migration snapshot rules. Run focused checks first; before a PR-ready implementation handoff run pnpm -r typecheck, pnpm test:run and pnpm build, plus the affected real runner journey. See the operational contract and verification record below for the checks actually completed in this checkout.

Implementation verification record

Implemented the storage migration, dual reader and guarded writer, bounded migration CLI, deletion receipts, package/runtime caches, metadata batching and native asset reuse. The approved follow-up adds default-on writes, automatic verified conversion, immutable Git restore recipes, persisted acquisition backoff, automatic object cleanup and native asset retention. The public API continues to hydrate explicit file-content responses. New source packages are prepared before publication transactions; existing generic authoring transactions keep their serialization boundary and use an independent upload connection.

  • Full workspace typecheck passed; the server was rebuilt after the final refinements.
  • Full repository build passed. Migration numbering/safety and map contract checks passed.
  • Focused package, source, company-skill, retention/cache, concurrency, heartbeat preparation and native context suites passed. The final migration/retention rerun passed 36 tests; the final project-source/authoring-route rerun passed 92 tests.
  • Real loopback S3-compatible multipart upload and historical restoration passed; its disposable server/data were removed after verification.
  • The benchmark records 60 warm samples across 1, 2 and 10 selected entrypoints, plus cold and fresh-service cases. It includes actual PostgreSQL wire bytes and native asset creation. It does not prove deployed Railway latency or a live agent turn.
  • The serial full-suite invocation was deliberately cancelled and replaced with the repository's supported CI groups/shards for the same inventory. The broad run recorded setup timeouts and assertions in the skill concurrency, native recovery, workspace and other suites. It was stopped to isolate those failures before the remaining chat, route and workspace groups completed. The skill concurrency case, two representative recovery cases and the workspace assertion all passed on isolated reruns. This is not a clean full-suite result or complete CI qualification.

Per-entrypoint runtime/transport deduplication remains the separately scoped follow-up from this design. Writers and automatic conversion now default on; the explicit false override remains available for a coordinated reader-first rolling upgrade.

Automatic lifecycle follow-up

The user approved automatic conversion, acquisition on miss and cleanup. This is implemented without a required data-migration command. Final proof: 30 focused tests passed, followed by 15 package/worker cases including additional missing-object and retention regressions; 73 source/native/retention integration cases passed. Idle-safety coverage passed, workspace typecheck/build passed, and the final server rebuild passed. The supported fresh-cluster test path bypassed shared-template startup failures. Two actual regressions found during development—JSONB key reordering during recipe verification and empty native-cache startup—were fixed and covered in the passing run.

Forty new warm benchmark samples (one and ten selected skills) plus cold/fresh-service samples retain five ordinary SQL queries and zero object/body reads. Receipts and timing limits are in the operational guide and the linked JSON. No production cleanup, deployment or data migration was performed. The earlier broad full-suite limitation remains open.