Captures the owner's requirements and decisions from the planning conversation, current branch/worktree state, open tasks 17-19, pending owner decisions and the controller's rulings so a new agent can resume. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3.1 KiB
Task 9: controller rulings (binding, in addition to the brief)
-
Base. Your branch task/9 starts at feat/phase1. Task 8 (the sync engine, which provides
runSyncinpackages/core/src/sync/run.ts) is on branchtask/8, still in a fix round. First rungit merge task/8. Resolve the conflicts in docs/ARCHITECTURE.md, DECISIONS.md and TODO.md by keeping BOTH sides' content: DECISIONS newest-first, ARCHITECTURE combining both module lists. Commit the merge. Task 8 will change internals later, butrunSync(deps, plugin, connectionId, mode)andRunSummaryare stable. -
Rate limits. Add an optional
rateLimit?: { perMinute: number; burst?: number }toPluginManifestin packages/plugin-sdk/src/types.ts.makePluginContextbuildsctx.httpwithcreateHttpClient({ ratePerMinute: manifest.rateLimit?.perMinute ?? 60, burst: manifest.rateLimit?.burst ?? 5, userAgent: "STTracker/0.1 (+self-hosted)" }). Then setrateLimiton each built-in plugin manifest (plugins/*/index.ts):- trakt: perMinute 100
- hardcover: perMinute 50
- mangabaka: perMinute 150
- lastfm: perMinute 240
- listenbrainz: perMinute 60
- csv: none
Remove the now-unused local
RATE_PER_MINUTEconstants if nothing uses them. -
Secrets. Config fields whose manifest type is
"secret"are persisted ONLY inside the encryptedcredentials_encblob (encryptJsonfrom@sttracker/core/crypto, with the instance secret), never in plainconfig.createConnection/updateConnectionsplit the incoming config accordingly.makePluginContextmerges the decrypted secrets back intoctx.config, andctx.credentials.get()returns the decrypted blob (secrets plus any OAuth tokens a plugin saved).credentials.save(c)mergescinto the blob and persists it encrypted IMMEDIATELY (Review focus 5).listConnectionsnever returns secrets: config only, plus ahasCredentials: boolean.
-
Lock. Sync jobs use
lockKey = "sync:<connectionId>".enqueuereturns null if a queued or running job holds the same lockKey. The lock check and insert must be race-safe on Postgres: wrap them in a transaction withpg_advisory_xact_lock(hashtext(lockKey))on Postgres (SQLite is already serialized). -
Job claim.
update jobs set status='running', locked_at=… where id=? and status='queued', checkingnumAffectedRows === 1n. The dialect mapsnumAffectedRowsfor both databases. -
Weekly full. The sync handler runs
fullif the plugin lackspullChanges, if the connection's last successful full run is older than 7 days (look atsource_runswith modefulland statusok), or if the payload saysmode: "full". Otherwisechanges. -
Plugin loading uses
import(pathToFileURL(file).href + "?v=" + Date.now())for reload. The echo fixture plugin lives under packages/core/test/fixtures/plugins/echo/index.ts. A broken plugin fixture (a default export without a manifest) sits next to it. -
Docs: ARCHITECTURE (the job worker, registry and connections are real) and DECISIONS (top) for the rate-limit manifest field, the secret split and the lock.