Vault metadata pagination
Status: Released in CLI 0.16.0 and Python 0.13.0; staged backend and hosted desktop/mobile pagination acceptance passed. Production backend acceptance remains separate. Tracking: Vault #241.
List returns metadata only. It never decrypts entries or generates OTP codes.
CLI and Python keep their complete-list behavior, following pages of 100 entries.
Retired entries are omitted unless the owner explicitly includes them. Expired
entries remain visible as metadata with expiresAt; listing grants no secret access.
dreamlake vault -p alice/remote list
# Owner-only: also show retired metadata.
dreamlake vault list -p alice/remote --include-deletedentries = client.vault.list(prefix="alice/remote")
entries = client.vault.list(prefix="alice/remote", include_deleted=True)For a browser, TUI, or batch job, request one page at a time. CLI --limit or
--cursor selects one-page output, including nextCursor. Python list_page
returns the same object. nextCursor: null marks completion. Keep the prefix,
account/access key, and retired filter unchanged when continuing. A page contains
1–200 entries at most (100 by default); an empty final page is valid.
dreamlake vault -p alice/remote list --limit 50 > first-page.json
# The cursor is metadata, not a credential.
cursor=$(jq -r '.nextCursor // empty' first-page.json)
if [ -n "$cursor" ]; then
dreamlake vault -p alice/remote list --limit 50 --cursor "$cursor"
fipage = client.vault.list_page(prefix="alice/remote", limit=50)
while True:
for entry in page["entries"]:
print(entry["name"])
if page["nextCursor"] is None:
break
page = client.vault.list_page(
prefix="alice/remote", limit=50, cursor=page["nextCursor"]
)HTTP: GET /v1/vault/entries?prefix=alice/remote&limit=50 returns
{entries, nextCursor}. Continue with the returned cursor query parameter;
includeDeleted=true is owner-only. Page size may change between requests.
Cursors are validated query positions bound to the account, prefix, filter and
access-key scopes, not authorization grants or encrypted secrets. Every page
reauthorizes and filters in Mongo before applying its limit. Editing a valid
position can only skip already-authorized names; clients should treat it as opaque.
Malformed or mismatched cursors return HTTP 400 without echoed input.
Ordering and concurrent changes
Names use binary ASCII ordering, with one name per tenant. Traversal is not a snapshot: new names after the last returned name may appear; names inserted before it require a fresh listing. Retired or deleted entries may disappear between pages. Deleting the boundary entry does not invalidate the cursor. A replaced entry at the same already-returned name is not revisited. No cursor retains a read grant; revoked/expired access keys are checked again, and recreated entries do not inherit a scoped key's old identity permission.
Compatibility and delivery boundary
Requests without limit or cursor retain the legacy unbounded response for
older HTTP/UI clients. New clients tolerate an older server that omits
nextCursor, treating its response as complete. This compatibility behavior is
not a claim that all API listing is bounded. The deployed UI uses dreamlake-ai/lib/vault.ts (createVaultClient.list);
hosted staging verification passed at d44ca49 against backend c49dc1d.
Legacy route removal remains a separate compatibility decision.
Tests cover 207 entries, cross-client continuation, prefix/account/scope isolation, retired and expired metadata, invalid cursors, recreated entry identity, and concurrent insert/delete behavior in real Mongo/HTTP. Client regressions reject non-progressing pages and strip unsolicited secret fields. Isolated fixture acceptance is separate from production deployment and browser acceptance.