mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-03 01:27:09 +01:00
Exercise lease, selection, and recovery behavior through the registered scheduler owner. Co-Authored-By: Paperclip <noreply@paperclip.ing>
117 lines
7.9 KiB
Markdown
117 lines
7.9 KiB
Markdown
# External object refresh deadlines
|
|
|
|
External-object refresh uses the delivery coordinator. The server registers the
|
|
service in both heartbeat-scheduler configurations. It no longer calls the
|
|
company-wide aggregate sweep on each heartbeat tick.
|
|
|
|
`external-objects.ts` owns automatic eligibility, resolver selection, refresh
|
|
claims, provider results and the next deadline. `index.ts` supplies registration
|
|
and admission gates. The existing scoped deadline helper owns wake coalescing,
|
|
page reconciliation, fair company selection, retry backoff and shutdown.
|
|
|
|
## Durable obligations and quiet states
|
|
|
|
An automatic refresh needs an active company, a retained mention in that company,
|
|
a non-null `next_refresh_at` and an available resolver. Injected resolvers,
|
|
including custom `url/link` resolvers, remain supported. A plugin resolver needs a
|
|
ready plugin with `external.objects.read`, a matching provider/object declaration,
|
|
and no company setting that disables it. Built-in resolvers remain available
|
|
independently of optional plugin support.
|
|
|
|
Terminal objects are passive. The existing exception remains: GitHub
|
|
`not_found` snapshots without `data.probeVerified=true` need a repository-visibility
|
|
probe before their terminal verdict is definitive. A null deadline retains its
|
|
existing never-refresh meaning. Registration does not change stored timestamps.
|
|
|
|
The worker reconstructs eligible deadlines in pages of at most 250 rows. A
|
|
company notification reads that company only. Ordinary ingestion uses object IDs
|
|
or stable provider/object/external identity hints, including concurrent upserts,
|
|
so one mention write does not reconstruct all company history. A due callback reads only its
|
|
object. Pages select scheduling columns and retain compact deadlines in memory.
|
|
Dispatch uses fair batches of at most 50 objects. The deadline is the later of
|
|
`next_refresh_at` and the current refresh lease expiry. Lease renewals and atomic
|
|
refresh tokens continue to exclude another claimant.
|
|
|
|
Unsupported generic links, orphan objects, disabled plugins, empty configuration
|
|
and retained terminal history do not schedule fallback discovery. Manual refresh
|
|
retains its existing `no_resolver` result. Actual provider failures retain their
|
|
saved retry deadline. Unexpected failures retain scoped coordinator backoff;
|
|
restart reconstructs the saved object and any unexpired claim.
|
|
|
|
The supported-resolver and retained-reference approach follows the related draft
|
|
[PR #17298](https://github.com/paperclipai/paperclip/pull/17298) by
|
|
[@kaluli123123](https://github.com/kaluli123123). This change adapts that eligibility
|
|
pattern to scoped scheduling, adds dynamic plugin/company permission predicates,
|
|
and fences automatic result persistence. It does not modify that draft PR.
|
|
|
|
## Producer and transaction audit
|
|
|
|
| Mutation | Owner and activation |
|
|
| --- | --- |
|
|
| Issue title/description, comment and document references | Existing sync methods register object intent before mention replacement and stable identity intent before object upsert. Routes, runtime tools, adapters and plugin paths reuse these methods. |
|
|
| Comment/document disappearance | Sync derives the retained mention owners and registers only those companies before deleting obsolete mentions. |
|
|
| Last mention removal and reattachment | Mention replacement invalidates company deadlines. Retained objects stay available for reattachment. |
|
|
| Issue deletion | Issue service registers its company before cascading mention deletion. |
|
|
| Company status/archive/deletion, including budget pause/resume | Company and budget services register company reconstruction before status or deletion writes. |
|
|
| Feature enable/disable | Instance settings registers global reconstruction before the experimental-settings write. |
|
|
| Plugin manifest/status/uninstall | Plugin registry registers reconstruction before lifecycle writes. Installed plugins acquire support through the ordinary ready transition. |
|
|
| Plugin company configuration and enabled setting | Registry writes register only the affected company. There is no plugin-readiness poll. |
|
|
| Manual and automatic resolver result | Result persistence registers object intent in the same transaction. Automatic persistence rechecks reference and resolver eligibility. Lost support releases the worker's own claim without publishing the stale result. |
|
|
| Normalized GitHub ingress | The event service registers company intent with the snapshot write. It clears the old refresh token, so an older provider response cannot overwrite that event. |
|
|
| Import and restore | No independent object-table importer exists in the source baseline. Supported reference ingestion uses the ordinary sync boundary. Startup reconstructs restored durable objects. |
|
|
|
|
Notifications are awaited before writes. Caller-owned and nested transactions
|
|
retain the outer commit boundary through the existing database-work protocol.
|
|
Rollback does not publish the write. Notifications received during a scan or an
|
|
active resolver remain dirty until a successful reconciliation.
|
|
|
|
The supported live writer model is one shared `createDb` signal owner, including
|
|
its transactions and child pools. These process-local signals do not cover
|
|
independent SQL clients or other replicas. Such writers must invoke the supported
|
|
service boundary or arrange explicit reconstruction/restart. Existing normalized
|
|
GitHub ingress uses the supported boundary. No permanent database listener or
|
|
periodic global safety scan is added.
|
|
|
|
## Acceptance evidence
|
|
|
|
The focused suite uses real PostgreSQL with a controlled simulated provider.
|
|
Ordinary sync and registry/settings writes create work; the fixture does not
|
|
pre-create the tested resolved outcome. Assertions independently read saved
|
|
objects. Tests measure owner reads and provider calls across former tick windows.
|
|
|
|
Coverage includes empty and terminal quiet, unsupported links, orphan removal and
|
|
reattachment, feature/company eligibility, plugin readiness and permission loss
|
|
during a held provider call, exact and earlier deadlines, durable provider retry,
|
|
rollback and outer commit after nested savepoint release, startup and paginated
|
|
reconstruction, inactive companies, never-refresh rows, first work during drain,
|
|
warm standby, failed refresh after restart, lease renewal, duplicate exclusion,
|
|
and shutdown while a provider call remains active. The GitHub ingress suite also
|
|
checks a real normalized event superseding a held resolver response.
|
|
|
|
The conservative external-object host-sleep blocker remains. An in-process
|
|
deadline cannot wake stopped compute. This change proves worker quiet and
|
|
reactivation in the tested process; it does not establish Cloud alarm coverage,
|
|
host/database suspension or deployed savings.
|
|
|
|
|
|
## Host cancellation and explicit legacy entry points
|
|
|
|
The resolver contract accepts an optional host `AbortSignal`. The worker passes
|
|
its coordinator signal through refresh to the resolver. This reuses the narrow
|
|
provider-owned capability introduced with merge-confirmation work: GitHub combines
|
|
host cancellation with a ten-second request budget for real fetch, response-body
|
|
reads and visibility probes. Plugin RPC payloads remain unchanged. The host
|
|
observes cancellation while retaining handlers for any eventual plugin/custom
|
|
result; it releases only its own claim and cannot publish that late result.
|
|
Startup does not await provider work. Shutdown closes the active GitHub HTTP
|
|
request, and its saved due row can resume immediately after restart.
|
|
|
|
The old `refreshDueObjects*` batch helpers are removed. Existing selection,
|
|
lease, concurrent manual refresh, terminal-history and not-found recovery tests
|
|
now enter through the registered scheduler owner. The removed baseline control
|
|
recorded six SQL reads over three empty aggregate ticks; the current quiet-work
|
|
acceptance measures zero recurring SQL after settlement. The scheduled check retains its
|
|
post-refresh scoped read to revalidate current company, reference and resolver
|
|
eligibility and recover an authoritative lease after a competing writer; all such
|
|
reads target one object rather than rebuilding a company.
|