- modules/schedules: registerScheduledJob({key, defaults, run}) registry; defaults are
synced into scheduled_job without overriding user-changed enabled/schedule
- scheduler runs only on the io worker, one instance via pg_try_advisory_lock held on a
dedicated connection; due = enabled and next_run_at reached, or run requested;
jobs run in parallel, never re-entrant; interval next = finish + period, cron next match;
run-now does not move next_run_at and merges duplicate requests; skip advances one period;
history trimmed to the last 50 runs; interrupted runs marked failed on takeover
- agent_check jobs: trigger condition checked first, goal template rendered, runAgent
enqueued via startRun(trigger=schedule) with allowlist and budget, one run per time slot;
allowRecover uses the autoInvestigate grant check; results harvested back into the run
history and optionally reported as an info decision; 3 built-in checks, off by default
- sources-identity (feeds.poll, sites.snapshot, identity.review), acquisition-organize
(downloads.sync, cache.threshold, downloads.orchestrate, slots.reevaluate,
subscriptions.search, subscriptions.metadata, library.reconcile) and file-manager
(trash.purge) register jobs instead of running their own setInterval; default
intervals unchanged; backup registered as an unconfigured placeholder
- endpoints under /schedules (admin, console session only)
TODO: pi refreshPiTokens and auth-settings session cleanup still own their timers.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- POST /files/probe writes media_asset.media_info, so scope organize.read -> organize.execute
- e2e e6-w4a-endpoints -> e10-w4a-endpoints (number clashed with e6-auto-recover)
- trash: reusing a consumed approval / retired planId is rejected and the file stays
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- downloads.ts observe: re-read download_task FOR UPDATE inside the tx and merge
from the locked row; qB sync could overwrite imported back to importing when
verifyAndImport committed between the sync read and write (E8 flake).
- agent finalize: accept summaryPrefix so the recovery verdict is written with
the terminal status in one update (E6 read a budget_exceeded run before the
"未恢复:…" annotation landed); recover-summary step kept, now idempotent no-op.
- organize.ts: drop local slotComplete and the once-subscription completion
write; completion/history are decided only by applySlotEvent(verify_passed)
(W5-C follow-up, removes double write). Manual slot organize uses the shared
subscriptions.slotComplete.
- vitest: server split into unit (parallel) and pg (file-serial, groupOrder 1)
projects; packages/tasks file-serial; explicit worker concurrency in e2e.
- docs: W6 stabilization record in 04-v0.1-acceptance.md.
Test expectations unchanged. Full `deno task ci` x3 green: 369s / 341s / 337s.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- completion by season episode_total ledger (applySlotEvent verify_passed hook overrides organize's
complete): unknown total keeps tracking and records suspected-missing; pending never completes;
once / continuous current-season completion writes subscription_history
- evaluation: missing = release units ∩ episodeRange − covered − in-flight; series slots may hold
several unit-disjoint in-flight plans; overlapping/busy releases are deferred (slot_deferral) and
re-evaluated when a plan ends (next_evaluate_at due) or on the periodic slot re-evaluation
- per-unit upgrade via slot_unit ledger; site scope (params.siteIds); site facts in evaluation context
- season packs: after submit read qB files and setFilePriority(0) for covered/in-flight episodes;
whole pack with recorded reason when undeterminable; removed downloads release their reservation
- searchSubscription task (title/aliases × enabled sites via liveSearch, snapshot + rate limiter):
on create, every searchIntervalHours (default 24h) and POST /subscriptions/{id}/search
- refreshMetadata writes season totals (only grows, floored by covered, lock respected) and turns
metadata_pending subscriptions active; plan loop refreshes tracked series every 6h
- endpoints: subscription progress, episode-total, subscription-history; e9 continuous-series e2e
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- POST /organization-records: sourcePaths must resolve (realpath) under a
registered library_root or downloader path-mapping host path; `..` and
symlink escapes rejected (400), dryRun included. Move requires an admin
session, or an API key with an automation_grant covering the entity or a
matching one-time approval (consumed on use), else 403. Manual Move from a
managed download_task with unsatisfied H&R returns 409 and opens a
decision; never silently downgraded to Copy.
- Series slot completion: total episode count is unknown while units are
provisional, so the slot is not satisfied and the subscription keeps
tracking; coverage is registered per actual file (creating provisional
episode units), and already_in_library files count as verified presence.
- POST /engine/runs/{id}/retry (admin): re-enqueue by original task name and
raw input with idempotency key retry:<id>:<n>, audited; retry button on
the run detail page.
- E7 regular series e2e, E8 AT24 tag removal / task disappearance e2e.
- All workspace members and root set to version 0.1.0; CHANGELOG.md.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- settings.agent.autoInvestigateMode investigate|recover, investigateMaxAttempts, recoverVerifySec
- new capability download.recheck (execute, replay never): qB force recheck / resume, no source swap/delete
- autoInvestigate moves to agent/investigate.ts: recover mode runs execute with a recovery allowlist
only when an active grant (published policy if bound) covers the recovery actions; otherwise degrades
to read-only investigation and leaves suggestion + recheck option on the decision
- runAgent verifies recovery against business facts (download_task/task/source_release) before finalize;
verified -> decision resolved by system:auto-recover, run summary '已恢复(系统核实)'; otherwise stays
awaiting_user; per-root-cause attempt cap
- e2e E6: missingFiles -> download_failed -> recheck -> verified; no grant degrades; swap rejected;
dedupe; attempts exhausted; budget exceeded keeps attention
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The business row settles before the engine marks the workflow completed; under CI load the
window is a few hundred ms and the direct assertion flaked.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Capabilities: all 14 registered (AO: inventory.query, download.plan/submit, organize.plan/execute,
policy.preview/apply; agent: task.inspect/retry/cancel); startup asserts count == 14.
Registry injects server-side authorize (automation_grant by action/scope/policy version/budget,
one-shot approval bound to plan id + payload hash + revision + expiry; model claims ignored;
automation switch), audit_event, server idempotency keys and a capability_invocation op ledger
(migration 0021) used for crash reconciliation. download.plan creates draft plans only;
download.submit promotes draft -> reserved after authorization.
- Agent session API: POST/GET /agent/runs, GET /agent/runs/{id} (events by seq, messages, report),
POST .../messages (wake), POST .../abort; automation-grants, approvals, capabilities invoke, tasks.
- runAgent task: Pi or fake provider from settings (apiKey via secrets), events persisted to
agent_run_event then published, resume + ledger reconciliation when pendingOperations > 0,
waits for goal-linked downloads/imports and reports from business tables (submitted != imported).
- Background triggers: decision.created -> bounded plan_only investigation (dedupe per decision);
decision.updated(resolved) -> resolveDecision executes chosen option.
- W2 closure: all OpenAPI routes registered in contracts api.ts (+ subset test); TMDB key from
settings secret_ref with env fallback; PUT /sites/{id}/credentials documented as alias of
/site-accounts; completion events documented as existing status events.
- e2e (real PG + OpenWorkflow + testkit mocks): RSS->library, agent delegation with grant/deny,
background continuation, worker kill -9 recovery (single qB add), auto investigation dedupe.
Co-Authored-By: Claude Opus 5.5 <[email protected]>