You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
service-storage: restore prefix enumeration cursor-shaped — list(prefix, { cursor, limit }) + adapter-conformance cases (cloud is the first-party caller the retirement could not see, twice) #6781
Part of objectstack-ai/cloud#1203 — maintainer ruling 2026-08-08: option B. Filed by the repo:cloud seat per the cross-seat transfer protocol; pm:queue only, routing label left to the triage seat (note the two-surface shape below — this card may want the contract-first split).
Why
#5540 removed list?(prefix) from IStorageService and #5541 removed the adapter implementations, both on the measurement "Nothing in the repo called either". True for this repo; false one repo over: cloud has two production callers the measurement could not see —
packages/service-cloud/src/environment-storage-cleanup.ts:92 — tenant attachment reclamation on environment delete (TS2339 compile-red at any post-retirement pin; cloud#935 is the incident where this sweep silently did nothing, orphaning deleted tenants' uploads forever);
packages/service-cloud/src/storage-service-blob-store.ts:78 — marketplace snapshot GC (structural type, so it breaks at runtime instead: TypeError swallowed into a warning; delisted package detail blobs stay fetchable at the Worker edge).
This blocks cloud#1197 (the .objectstack-sha bump that lands the #5852 cross-org escalation producer fix in deployment): the retirement and the #5852 fix landed the same morning, so no commit on main has one without the other. Full option analysis (A: cloud hand-rolls S3 pagination — recreates the retired defect one repo over; C: tracked-keys sweeps — right end-state for the env sweep but its backfill itself needs enumeration once) is on cloud#1203; the ruling chose B: restore it upstream, correctly shaped.
The shape — already prescribed, not invented here
Both the retirement note and #5266's option 2 reserved this route word for word: "if a first-party caller ever needs real bucket enumeration it returns cursor-shaped — list(prefix, { cursor, limit }) — with adapter-conformance cases proving both backends agree before it ships." Cloud is that first-party caller, twice.
storage-adapter-list-retirement.test.tsflips to pin the new contract (both adapters implement it, signatures match the spec) — flipped pins keep bearing load; do not just delete the retirement pins.
Changeset minor (new public API surface).
Consumer side (not this card)
Cloud picks the capability up by moving the pin — cloud#1197 / PR #1205 (parked draft) re-targets to a SHA covering this, and cloud-side behaviour-level tests for both callers are tracked under cloud#1203 (measured there: cloud's deploy pipeline cannot see this class of break — OS_SKIP_DTS=1 skips the only typechecking pass — so type-level red alone is not a regression guard). A follow-on cloud card (option C: tracked-keys reclamation for the environment sweep, using this list once for backfill) is filed after cloud#1197 lands.
Part of objectstack-ai/cloud#1203 — maintainer ruling 2026-08-08: option B. Filed by the
repo:cloudseat per the cross-seat transfer protocol;pm:queueonly, routing label left to the triage seat (note the two-surface shape below — this card may want the contract-first split).Why
#5540 removed
list?(prefix)fromIStorageServiceand #5541 removed the adapter implementations, both on the measurement "Nothing in the repo called either". True for this repo; false one repo over: cloud has two production callers the measurement could not see —packages/service-cloud/src/environment-storage-cleanup.ts:92— tenant attachment reclamation on environment delete (TS2339 compile-red at any post-retirement pin; cloud#935 is the incident where this sweep silently did nothing, orphaning deleted tenants' uploads forever);packages/service-cloud/src/storage-service-blob-store.ts:78— marketplace snapshot GC (structural type, so it breaks at runtime instead:TypeErrorswallowed into a warning; delisted package detail blobs stay fetchable at the Worker edge).This blocks cloud#1197 (the
.objectstack-shabump that lands the #5852 cross-org escalation producer fix in deployment): the retirement and the #5852 fix landed the same morning, so no commit on main has one without the other. Full option analysis (A: cloud hand-rolls S3 pagination — recreates the retired defect one repo over; C: tracked-keys sweeps — right end-state for the env sweep but its backfill itself needs enumeration once) is on cloud#1203; the ruling chose B: restore it upstream, correctly shaped.The shape — already prescribed, not invented here
Both the retirement note and #5266's option 2 reserved this route word for word: "if a first-party caller ever needs real bucket enumeration it returns cursor-shaped —
list(prefix, { cursor, limit })— with adapter-conformance cases proving both backends agree before it ships." Cloud is that first-party caller, twice.packages/spec/src/contracts/storage-service.ts—list(prefix: string, opts?: { cursor?: string; limit?: number })returning{ items: StorageFileInfo[]; nextCursor?: string }. Cursor-shaped so callers must handle pagination explicitly — the silent-1000-cap and the one-level-vs-recursive dialect split (service-storage:IStorageService.list(prefix)means two different things on the two shipped adapters (local: one level, directories as files; S3: recursive, silently capped at 1000) #5266's two measured defects in the OLD signature) become unrepresentable.readdirrecursive, directory entries skipped (never stat'd into results); S3:ListObjectsV2+ContinuationTokenloop honoringlimit.IStorageService.list(prefix)means two different things on the two shipped adapters (local: one level, directories as files; S3: recursive, silently capped at 1000) #5266 named: nested keys (a/b/cvisible underlist('a')), directory entries excluded, >1000 objects fully enumerated via cursor.SwappableStorageServicepass-through restored.storage-adapter-list-retirement.test.tsflips to pin the new contract (both adapters implement it, signatures match the spec) — flipped pins keep bearing load; do not just delete the retirement pins.Consumer side (not this card)
Cloud picks the capability up by moving the pin — cloud#1197 / PR #1205 (parked draft) re-targets to a SHA covering this, and cloud-side behaviour-level tests for both callers are tracked under cloud#1203 (measured there: cloud's deploy pipeline cannot see this class of break —
OS_SKIP_DTS=1skips the only typechecking pass — so type-level red alone is not a regression guard). A follow-on cloud card (option C: tracked-keys reclamation for the environment sweep, using thislistonce for backfill) is filed after cloud#1197 lands.Refs: cloud#1203 (ruling + analysis), cloud#935, #5266 (the dialect measurement;
tracking), #5540, #5541, cloud#1197 / cloud PR #1205.