Skip to content

next: device log-token refresh for synced collections - #618

Merged
callumalpass merged 4 commits into
mainfrom
next/collection-log-token
Oct 6, 2026
Merged

callumalpass merged 4 commits into
mainfrom
next/collection-log-token

Conversation

@callumalpass

Copy link
Copy Markdown
Contributor

This is control's approved small follow-up: a device log-token refresh for synced daemon collections (control 20:47). Control owns these paths (owner review requested); security-2 review requested.

POST /v1/next/collections/:id/log-token {device_id, challenge, sig}

  • Auth: connector, plus a device proof in its own domain: H("mdbase/v1/collection-log-token", cbor[challenge, connector, device, collection]), using a fresh, single-use challenge.
  • Checks: all in one transaction under share locks, held through the mint (5 s lock_timeout; contention answers 503 busy):
    • connector not revoked, account not suspended, device present with its exact keys;
    • collection current: synced (private or cloud copy) and not left;
    • an acknowledged enrolment: an outbox row in an appended batch containing exactly this device tuple (device, account = the connector's account, kind, sign/KEM/Noise keys). A queued or pending enrolment is not enough;
    • no device-revoke for the device, appended or pending.
  • Answer: only {token, expires_at}. The token is role 0, carries the device, its sign key and claim 5 = this collection, and lasts 15 min. No keys, no log URL, no control-plane credential.
  • Scope: a device credential only. It never enrols, re-enrols or reactivates. The log enforces its current ACL on every RPC. Private collections get exactly the same device credential and nothing more.

Tests (Postgres):

  • private and cloud copy: token claims and issuer signature, with exact expiry;
  • a member's own device can refresh; another account's device cannot;
  • refused: a pending enrolment, a changed key tuple, a revocation, a left collection;
  • auth: no connector auth, the wrong collection, a proof from another domain, a replayed challenge, nil IDs;
  • a suspended account is refused.

Results: src/features/next 148 passed (Postgres 16, serial); server pnpm test 779 passed; architecture and changelog checks pass.

Merge note: the app.ts hunk overlaps #615's nextLog change (same variable); it's a trivial resolution.

POST /v1/next/collections/:id/log-token {device_id, challenge, sig}:
connector auth plus a device proof in its own domain
(mdbase/v1/collection-log-token over challenge, connector, device,
collection) with a fresh single-use challenge. In one transaction under
share locks: current connector/account/device with exact keys, a
current synced collection (private or cloud copy, not left), an
acknowledged (appended-batch) enrolment of exactly this device tuple
for this account, and no device-revoke; then mints a role-0 token for
that device and collection (15 min). Answers only {token, expires_at}.
A device credential, not membership, approval or admission.
Review (security-2) on 639cb4e: fold the account's membership ops in
outbox order (a member-set counts once its batch is appended; a
member-remove counts even while pending) and refuse unless it is a
current member; require runtime = 'next'. Tests: removed member refused
until re-added and acknowledged; shadow runtime refused.
Latest effective membership op (outbox order, then op ordinal; member-remove
even pending, member-set only appended) projected with LIMIT 1 instead of
loading every matching batch; statement_timeout 5s fails closed as busy.
Regression test for intra-batch ordering, other accounts' ops and many batches.
@callumalpass

Copy link
Copy Markdown
Contributor Author

Control touched-path SOURCE OWNER ACK at exact 5bb4465. Reviewed the route, app wiring, PostgreSQL regression source, and the 5a-to-5bb bounded-query delta. Current connector/account/device whole-tuple locks, separate proof domain and single-use challenge, NEXT runtime/current collection, acknowledged exact enrollment, latest effective account membership, and revocation guards remain required through mint. Latest membership is one projected row in outbox/intra-operation order, with pending removals effective and pending sets excluded; SQL waits/statements are bounded. Private refresh remains credential-only: no service actor, enrollment, key delivery or Ready assertion. This is independent source review, not independent PostgreSQL execution, actual log ACL or runtime acceptance; security and normal CI qualification remain separate.

@callumalpass

Copy link
Copy Markdown
Contributor Author

security: no blocking findings at 5bb4465 (scoped ordinary device log-token refresh). Reviewed the actual device-proof/single-use challenge, locked current full identity, acknowledged exact enrollment, current effective membership, pending revoke/removal refusal and next-runtime checks. The changed SQL returns only the latest effective account membership operation, preserving outbox/intra-batch order and pending-readd semantics, with lock/statement bounds. This verdict is source review; author PG/mutation results and control SOURCEOWNERACK are separate, not my independent PG execution. Role-0 exact-collection 15-minute credential only: no enrollment, epoch key, admission/Ready, private-device join expansion or portable CP freshness authority. Current log ACL remains authoritative on every RPC; native custody/refresh adapter and live acceptance remain separate.

@callumalpass
callumalpass added this pull request to the merge queue Oct 5, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Oct 6, 2026
@callumalpass
callumalpass enabled auto-merge October 6, 2026 00:10
@callumalpass
callumalpass added this pull request to the merge queue Oct 6, 2026
Merged via the queue into main with commit c9bf0cf Oct 6, 2026
14 checks passed
@callumalpass
callumalpass deleted the next/collection-log-token branch October 6, 2026 00:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant