mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-01 06:42:06 +01:00
100 lines
6.2 KiB
Markdown
100 lines
6.2 KiB
Markdown
# Join request retention while idle
|
|
|
|
Pending join requests already have durable activity timestamps. Retention now
|
|
uses their next 90-day archive deadline instead of including them in the main
|
|
maintenance tick's attention-feed scan. The regular attention UI still includes
|
|
join requests. This changes automatic retention discovery, not access approval.
|
|
|
|
## Owner and eligibility
|
|
|
|
`join-request-retention.ts` owns the candidate query, bounded scan, and deadline.
|
|
A candidate is a pending_approval request belonging to an active company, with
|
|
an invite whose company matches the request. Kept and already archived items do
|
|
not schedule work. Invite expiration or revocation does not remove the existing
|
|
attention item and therefore does not disable its retention. No provider policy
|
|
is interpreted by the worker or shared scheduler.
|
|
|
|
The next deadline is the earliest eligible updatedAt plus 90 days. The partial
|
|
pending index orders updated_at and id. Resolved request history is excluded by
|
|
the index predicate. Each pass attempts at most 100 due rows. The private cursor
|
|
preserves database timestamp precision so a failed page cannot starve later work.
|
|
Errors keep the shared 30-second retry; successful full pages continue until
|
|
work is exhausted. No eligible rows means no scheduled SQL or network scan.
|
|
|
|
Each candidate is re-read under its request row lock before retention changes.
|
|
This preserves a concurrent request update or status transition. The shared
|
|
retention owner still controls keep, archive/revive, version checks, activity
|
|
logging, and notification creation. Archiving does not approve or reject a join.
|
|
Join requests have no origin agent, so their archival creates no agent delivery.
|
|
Company and invite joins preserve existing visibility policy; this does not add
|
|
cross-table serializability beyond the existing retention contract.
|
|
|
|
## Producer audit
|
|
|
|
| Producer | Notification boundary |
|
|
| --- | --- |
|
|
| Invite acceptance creates a pending request | Existing acceptance transaction, before the request insert |
|
|
| Supported gateway invite replay changes activity | Small writeJoinRequestChange transaction around the existing SQL update |
|
|
| Automatic human approval after acceptance | Same helper around the status update |
|
|
| Explicit approval and rejection | Same helper around the status update |
|
|
| Slack self-service access request | Existing platform transaction, before the request insert |
|
|
| Company pause, resume, archive, or deletion | Company-level retention notification inside the existing transaction |
|
|
| Budget-driven company pause or resume | Same company notification in the accounting transaction |
|
|
| Keep/unkeep, manual archive/revive, accepted archive proposal | Existing typed retention notification now routes join_request to its worker |
|
|
|
|
Provider calls, membership grants, and unrelated route behavior stay outside the
|
|
small SQL mutation helper. Nested callers retain the existing outer transaction
|
|
settlement and uncertain-outcome protocol. The access router itself requires a
|
|
root database connection; it is not wrapped in a caller transaction.
|
|
|
|
API-key consumption only permits an already-approved request. It cannot create
|
|
pending retention work and does not acquire a new wrapper or signal. Invite
|
|
acceptance that reuses a human request does not update that request. Revocation,
|
|
expiry, and acceptedAt changes do not change the feed predicate. Invite/company
|
|
identity is immutable in supported routes; company deletion is the invite delete
|
|
owner. A future producer that changes these facts must join the same contract.
|
|
Process-local signals do not cover independent SQL writers or another instance.
|
|
|
|
## Lifecycle and limits
|
|
|
|
Startup reconstructs pending deadlines. The worker is registered only when the
|
|
existing heartbeat maintenance switch is enabled. Writes during drain retain a
|
|
wake without passive SQL. Failed work retains its retry across drain. Shutdown
|
|
joins an admitted archive and stops before subsequent candidates; restart picks
|
|
up the durable remainder. There is no new queue table or permanent DB listener.
|
|
The in-process deadline is not a Cloud alarm. Existing unsupported-future-work
|
|
sleep blockers remain in place.
|
|
|
|
## Verification
|
|
|
|
Seventeen new real-PostgreSQL cases pass. They cover actual invite acceptance,
|
|
rejection, human approval, gateway replay, and Slack access requests; empty and
|
|
resolved-history quiet; saved deadlines; startup reconstruction; keep and manual
|
|
archival controls; company pause/resume; unchanged expired/revoked invite policy;
|
|
normal versus maintenance feeds; nested commit/rollback; first writes and failed
|
|
work during drain; concurrent source updates; 101-row microsecond paging with a
|
|
full failed page; and shutdown/restart.
|
|
|
|
Another 139 consumer cases pass: 71 invitation/startup cases, 21 approval worker
|
|
cases, and 47 shared retention/attention/budget cases. This is 156 selected tests,
|
|
not a full local suite claim. The first consumer command used a nonexistent
|
|
retention test filename and ran only the approval suite; the corrected selection
|
|
ran the actual retention suite against its dedicated external database.
|
|
|
|
Initial test failures exposed fixture assumptions: the access router cannot run
|
|
on a transaction handle; unresolved recovery cannot be expected to become idle;
|
|
Slack links require actual endpoint/principal ownership rows; and shutdown tests
|
|
must establish admission and deterministic candidate order. Those fixtures were
|
|
corrected without weakening assertions or production checks. The first replay
|
|
fixture lacked required gateway credentials. The final fixture supplies test-only
|
|
credentials. A reused transaction spy retained admission calls during the quiet
|
|
assertion; restoring it before the quiet measurement fixed the measurement.
|
|
|
|
Migration 0387 applies, the full migration set replays, and the concurrent index
|
|
statement replays idempotently. Migration safety and numbering checks pass; the
|
|
five newest schema snapshots are retained. The actual next-deadline query with
|
|
40,000 resolved requests and one pending request uses the pending index and
|
|
executes in 0.017 ms locally. The probe rolls back its data. This is query-plan
|
|
evidence, not production latency or measured savings. Workspace typechecking and the production build pass. Fresh PR CI/review remain
|
|
required before readiness.
|