## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Agents can propose secrets and bindings for approval. > - Pending proposals already contain their expiry and encrypted value. > - The server scans for expired proposals on every maintenance tick, even when none exist. > - Committed proposal changes and saved expiry times provide the required triggers. > - This PR uses those triggers and preserves scrubbing, approval checks, and recovery. > - Empty and completed proposal history no longer causes recurring expiry queries. ## Linked Issues or Issue Description Refs #17443. This is the second PR in the sleep migration stack and targets its branch. **What problem does this solve?** The periodic proposal scan contacts PostgreSQL when no proposal needs maintenance. That obstructs idle database behavior across tenant instances. **What is the proposed solution?** Use transaction-aware notifications for creation and resolution. Reconstruct the earliest pending expiry at startup. Run cleanup at that deadline, or retry while reconciliation remains unfinished. **Why is this useful?** It removes an idle scan while retaining security checks and physical ciphertext cleanup. No queue table or migration is required. ## What Changed - Added intent notifications before secret/binding creation, approval, terminal transitions, and linked interaction resolution. The existing outer transaction owns settlement. - Kept expiry selection, ciphertext scrubbing, dependent rejection, and authorization in the secret-proposal service. - Registered a startup and deadline worker in place of the aggregate maintenance sweep. Empty/terminal states disarm; overdue batches and failures retain retries. - Reused shared scoped deadline dispatch for secret expiry. The service owns company-filtered SQL and expiry policy. Custom-image expiry continues to use `registerDeadlineWork`. - Added real PostgreSQL producer, outer-transaction, concurrent-write, expiry, restart, interaction resolution, and drain/resume tests. Updated startup wiring fixtures and documented the supported writer/lifecycle contract. ## Verification - Landing reconciliation at `c10eb1bcf752b058713e05d3345e5cf38dd3fa72`: 160 tests pass across the complete secret-proposal, scoped deadline, company batch, coordinator, startup, custom-image service and custom-image worker suites; no cases skipped in the accepted runs. Server typecheck passes. This integrates master after #17443 landed; fresh remote CI and Ponytail review are required before merge. Current head: `5bbe17ad9ebc4573b08ed1a9323644ef9608ca09`. This remains a single-duty PR stacked on #17443. - Integrated pinned master `8548173fc9efdc0fa07644fb92dcc0b6851a95e5` with normal merges. Every branch passed generated-schema checks and its target-branch migration-order gate. Existing SQL is unchanged; the 25 stack migrations follow master’s new migration. - Duty-specific producer coverage, lifecycle tests, and earlier failure records: [2026-10-08-instance-sleep-and-scheduler-inventory.md](https://github.com/paperclipai/paperclip/blob/5bbe17ad9ebc4573b08ed1a9323644ef9608ca09/doc/plans/2026-10-08-instance-sleep-and-scheduler-inventory.md), [2026-10-24-recurring-server-sleep-stack.md](https://github.com/paperclipai/paperclip/blob/5bbe17ad9ebc4573b08ed1a9323644ef9608ca09/doc/plans/2026-10-24-recurring-server-sleep-stack.md). - Combined tip `51e6cbe67979b55b340ea9f0f434c5b5c682ccd6`: **355 startup/cancellation/recovery/dispatch/preservation tests**, **75 connection-management tests**, and production server typechecking pass. These combined checks do not mean every intermediate PR independently ran the full repository suite. - Real master-to-stack database upgrade passes: **379 unchanged prior migration receipts plus 25 new migrations**, with the gateway responsibility fixture preserved. - Owning-branch checks: 84 runner installation/settlement cases plus runner typechecking and generated TypeScript/Rust profile agreement; 253 GitHub review workflow cases. Stop-attribution and held-worker readiness regressions failed before their fixes. - Failure accounting: the initial combined run passed 349 cases and failed six idle assertions in a reused preservation database containing old cancelled runs. Repeating the combined command with a fresh preservation database passed. Earlier invalid project selection, merge import/type errors, and the interrupted full local suite are not counted as passing checks. - **Latest-head CI and review remain required.** Prior CI findings include GitHub idle observation, route teardown, attention cleanup, touch-picker E2E, workspace health, and voice-cleanup timing; local passes do not erase those failures. No full local repository-suite, deployed sleep, fleet cost, or live Copilot qualification claim is made. ## Risks - Notifications cover supported writers sharing the database signal owner. Independent SQL writers and other process roots require a separate ingress contract; startup recovery alone is not live replica support. - The shared coordinator and scoped worker also serve later stack duties. Their defaults remain intact; existing voice, chat, native, and startup consumers are included in integrated verification. - Startup now checks overdue proposals immediately. During drain, physical cleanup waits; approval still rejects expired proposals synchronously. - Each due company pass remains bounded to 100 rows. Failed scopes back off from 30 seconds to five minutes; healthy overdue work keeps draining. Self-generated write hints coalesce and do not erase a failed sweep's backoff. - Heartbeat-disabled behavior and Cloud sleep-safety gates remain unchanged. This does not establish deployed suspension or external wake coverage. Tool review and gateway token cleanup remain separate slices. ## Model Used OpenAI Codex, GPT-6 family, with reasoning and code execution. The exact serving model identifier and context window were not exposed. ## 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 - [ ] All Paperclip CI gates are green - [ ] 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>
5.3 KiB
Secret-proposal expiry and idle behavior
Secret proposals already store status, expiresAt, and encrypted pending
values. This worker uses those records; it adds no queue table or migration.
Ownership and activation
secret-proposals.ts still owns quotas, authority checks, approval, expiry,
ciphertext scrubbing, dependent binding rejection, audit records, and interaction
reflection. expiryDeadlines(companyId?) returns one saved deadline per pending
company. Normal write notifications reload only the affected company. Startup
reconstruction loads all companies once. The manual nextExpiryAt() query remains
available to existing callers.
Terminal history does not create a deadline. Approval still rejects an expired
proposal synchronously, even when maintenance is paused.
The supported producers register secretProposalExpiry intent before their
writes in the owning transaction:
createWithinQuota: both secret and binding creation, after quota admission.markApproved: ordinary approval and prerequisite approval during a cascade.transition: rejection, withdrawal, expiry, and dependent binding rejection.- Interaction resolution: rejection, withdrawal/cancellation, expiry, and failed acceptance execution that resolves a linked pending binding.
- Company deletion: a company hint removes its cached deadline after deletion commits.
Each producer includes its company ID. The remaining proposal write only attaches an interaction ID and does not change expiry eligibility. Independent SQL writers or another DB root are not supported live producers of these process-local signals; startup reconstructs their persisted rows, but it is not a live replica contract.
The shared database signal boundary defers notification until the outer transaction settles. Nested savepoints, rollback, and uncertain commit outcomes retain the existing protocol. Callers do not pass recovery counters or reconnect the database.
Scheduling and lifecycle
registerSecretProposalExpiryWork runs once at startup while heartbeat scheduling
is enabled. It replaces the secret-proposal sweep in the aggregate maintenance
tick. Disabled heartbeat scheduling keeps its existing behavior.
The worker reloads the affected company's saved minimum expiry after a committed change. It does not sweep future work. At a due deadline it sweeps at most 100 expired proposals for that company, then reads that company's next deadline. Companies rotate fairly between bounded passes. Retained history creates no work. No pending proposal, unresolved write, or failed reconciliation means no recurring query. Unscoped legacy hints still reconstruct all companies.
scoped-deadline-work.ts owns scope invalidation, cached deadlines, fair dispatch,
and failure backoff. It owns no SQL or proposal policy. Failed loads and failed
company sweeps back off independently from 30 seconds to five minutes. A new
write reloads that company without erasing its failed sweep's backoff. Other
companies keep their own deadlines, including already-due work. The coordinator's
optional nextRunAtOnError contract permits immediate eligible work only after
the worker has recorded per-item backoffs. Failed transaction reconciliation and
failed pending-state inspection retain the coordinator's normal retry.
Secret expiry opts out of the shared helper's default one-second pass spacing so
newly learned short deadlines are not clamped. Other scoped consumers retain that
default. Custom-image expiry continues to use deadline-work.ts unchanged. No
shared scheduler or startup wiring interprets secret-proposal policy.
A request stays pending until reconciliation succeeds. Changes arriving during a pass, first writes during drain, and failures followed by drain retain a request. Passive drain performs no proposal SQL. Resume reconciles retained requests; restart reconstructs deadlines. Coordinator shutdown unsubscribes and waits for active work. Deferred physical cleanup does not extend approval authorization.
Verification and limits
The PostgreSQL route suite exercises actual service producers, nested outer commit and rollback, quiet empty and terminal states, quiet future deadlines, automatic ciphertext scrubbing and dependent-card resolution, startup reconstruction, withdrawal, interaction rejection/failed execution, failure retry, and drain/resume. It also verifies that expired approval fails while cleanup is paused. The existing coordinator suite verifies fallback retry and uncertainty boundaries. New cases record actual SQL to prove normal writes and due expiry stay company-scoped; they also cover five-second deadlines, earlier replacement deadlines, company deletion, and another company making progress while one sweep fails. A deterministic shared-worker test covers a 250ms replacement deadline.
The worker tests use a controlled clock and a real PostgreSQL database. Existing route tests cover quotas, isolation, authorization, concurrency, and lifecycle semantics. The test-only database URL must refer to a disposable migrated database: the suite deletes its fixture tables. Ordinary CI uses its isolated embedded DB.
This is worker quiet, not proof of host or database suspension. No Cloud alarm or sleep-eligibility exemption is added. Existing sleep-safety checks remain in force; a local expiry timer cannot wake a stopped host. Tool review expiry and gateway session cleanup are separate migrations.