Commit Graph
61 Commits
Author SHA1 Message Date
rootandClaude Opus 5.5 7862573944 feat(cache): TMDB image proxy, posters, cache admin API and settings cards
GET /img/tmdb/{size}/{file} ported from MP ImageHelper / fetch_image
(sanitized cache path under DATA_DIR/cache/images, GLOBAL_IMAGE_CACHE
read switch, md5 ETag / 304, day-based cleanup job). Cache module adds
cache.sweep / cache.images_clean jobs and /cache/tmdb, /cache/images
list and clear endpoints. Library list/detail and the search palette
show posters with the neutral placeholder as fallback; metadata settings
get TMDB cache and image cache cards. Runbook docs/runbook/cache.md and
porting notes docs/porting/moviepilot-tmdb-cache.md.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 22:26:29 +08:00
rootandClaude Opus 5.5 946fca5e55 feat(cache): PG kv_cache ported from MP TTLCache, TMDB request and recognize caches
Migration 0042_cache adds UNLOGGED kv_cache. PgCache mirrors MP
runtime/cache.py (region, per-key TTL, TLRU maxsize eviction, @cached as
cachedCall); only the storage moves from memory to PG. TMDB request
responses (MP TMDb.request @cached) and recognize results (MP TmdbCache:
same key, {id:0} negative cache with EMPTY_RESULT_CACHE_TTL, type-conflict
checks, backfill after manual assignment) live in kv_cache, shared by all
processes. Settings cache.tmdbTtlHours / tmdbMaxSize / emptyResultTtlSec
map to META_CACHE_EXPIRE / CONF.tmdb / EMPTY_RESULT_CACHE_TTL. TMDB
details now also store posterPath / backdropPath.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 22:26:29 +08:00
rootandClaude Opus 5.5 fb84a71f80 style(copy): fix remaining punctuation violations and make check:copy strict
Auto-fix of 602 string literals across 74 files (half-width punctuation next to
Chinese, parenthesised Chinese, quotes); biome reflow; e2/e6 assertions follow
the new full-width summaries. check:copy now runs with --strict in ci.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 21:34:51 +08:00
rootandClaude Opus 5.5 0fccfe257d fix(web): settings side nav lists MCP, capabilities and passkey anchors
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 21:34:51 +08:00
rootandClaude Opus 5.5 410d4eef6f chore: sort mcp export and gen:types output, sort deno.json imports
HEAD failed biome organizeImports on packages/contracts/src/api.ts; types.ts
now matches deno task gen:types on a fully migrated dev database.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 21:09:25 +08:00
rootandClaude Opus 5.5 74c2ed45aa feat(contracts,db): agent.plan / agent.propose_options, interaction events, proposeFirst replaces run mode
- agent_run_event gains plan_updated, options_proposed/resolved, approval_requested/resolved (migration 0040)
- AgentRunStartInput.proposeFirst; mode deprecated (plan_only maps to proposeFirst); mode dropped from AgentRun API
- POST /agent/runs/{id}/choice; agent.optionsTimeoutSec/optionsTimeoutAction/approvalTimeoutSec settings

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 21:07:01 +08:00
rootandClaude Opus 5.5 73ee3c6614 feat(mcp): contracts, capability stats migration and MCP SDK dependency
- contracts/mcp.ts: capability stats/tools, MCP server status/config schemas, mcpToolVisible
- api.ts: GET /capabilities/stats, GET /capabilities/tools, GET /mcp/status, PATCH /mcp/config
- migration 0039: mcp_server_config single row + audit_event capability index
- deno.json: npm:@modelcontextprotocol/[email protected].1

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 20:27:51 +08:00
rootandClaude Opus 5.5 efb5d206c1 test(db,contracts): register the 0037 tables and agent_run.await_wake in schema checks
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 20:22:45 +08:00
rootandClaude Opus 5.5 b0e5d539ed feat(auth): TOTP setup endpoint, recovery codes, passkeys and master key rotation
- GET /auth/totp/setup returns the pending secret and otpauth URL (created
  on first call, reused on refresh) for the console to render as a QR code.
- Enabling TOTP issues 8 one-time recovery codes (argon2id hashes in
  admin_recovery_code); login accepts recoveryCode in place of totpCode,
  disable accepts either, and recovery_codes regenerates with the password.
- Passkeys via @simplewebauthn/server: register/list/rename/delete under
  the console session, public login options/verify that reuse the login
  rate limit and session cookies. Challenges are one-time rows in
  webauthn_challenge; RP ID and origins come from WEBAUTHN_RP_ID /
  WEBAUTHN_ORIGINS, falling back to the request host. Signature counter
  regressions are rejected. SessionInfo gains passkeyEnabled.
- --role rotate-master-key re-encrypts the secret table from
  SECRET_MASTER_KEY to SECRET_MASTER_KEY_NEW one row per transaction,
  skips rows already on the new key (resumable, swap keys to roll back),
  logs progress and audits secret.master_key_rotate. Secrets now record
  the master key fingerprint in kdf_params.kid.
- Runbook: recovery codes, passkey configuration, rotation steps and
  rollback.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 20:22:07 +08:00
rootandClaude Opus 5.5 c17d4b90bd feat(browser): complete camofox BrowserProvider with tabs, interaction, download capture and control lease
Contracts for browser API and browser.* agent capabilities; adapter implements
close/list/restore sessions, tabs + reconcile, history, click/type/select/scroll/press,
extract, waitForDownload (staged via DownloadSink, sha256), stale-ref guard and
automation/human actor check. testkit mock camofox gains an interactive site
(login redirect, captcha, click-driven page changes, downloads, PNG screenshots).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 20:17:46 +08:00
rootandClaude Opus 5.5 70d56645fe feat(subscriptions): private rule groups, MP quality presets, save path, reset and calendar API
- include/exclude sync to a per-subscription private rule group (rule_group.hard, migration 0036);
  evaluation merges chain hard conditions; private groups hidden from GET /rules, soft-deleted with
  the subscription and rebuilt on resubscribe
- MP resolution / quality / effect map to presets written onto the subscription's own slots
  (default slot template on create); UHD / Atmos go to the private group include
- downloadSavePath mapped to the downloader path and passed as qB savepath; libraryRootId used
  for the first organize when the work has no path binding; keywords appended to active search
- POST /subscriptions/{id}/reset releases stale in-flight plans, clears deferrals, re-evaluates
- GET /subscriptions/calendar from episode air dates; refreshMetadata writes TMDB season
  episode air dates for tracked seasons

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 20:14:40 +08:00
rootandClaude Opus 5.5 52402864d6 style(copy): punctuation rules and site-wide fix with a CI check (QA U11)
- glossary: 10 punctuation rules (full-width in Chinese, CJK/Latin spacing,
  full-width parens around Chinese, single ellipsis, 「」 quotes, period rules)
- scripts/check-copy-punctuation.mjs: checks string literals, templates and JSX
  text (not comments, types, keys, match/compare strings); --fix, --strict,
  --by-file; wired into deno task ci as check:copy in warn mode
- --fix applied to web, server and contracts; files other lanes were editing
  (subscriptions, sites, browser, agent, auth/account, inventory) left for them
- tests updated for the changed messages

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 20:09:29 +08:00
rootandClaude Opus 5.5 abc69886ea feat(contracts,server,web): files G6–G8 fields and identify S-7 contract
- G6: FileRoot.pendingCount/pendingPartial for download roots (same rule as the
  unidentified filter, recursive, cached 5 min, invalidated by files.changed)
- G7: FileEntry.parsedHint from parseReleasePath (file + parent dirs); the web
  subline uses it and no longer parses names itself
- G8: FileIdentifyCandidate.posterUrl (TMDB poster_path via adapter and
  catalog.lookup, local entities from metadata.posterPath); thumbnails in the
  identify panel
- S-7 contract: FilesIdentifyResult.remoteTaskId/remotePending,
  MediaServerInventory.partial, EventType identify.completed with
  IdentifyCompletedPayload; identify panel (useIdentifyLive) and organize wizard
  merge remote results by root + path, buffering events that beat the response

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 20:09:29 +08:00
rootandClaude Opus 5.5 f6063af12d fix(db): migrator introspects only the target schema
Kysely's Migrator checks for its tables via introspection.getTables(),
which reflects every schema and calls has_schema_privilege /
pg_get_serial_sequence per schema. When another test file drops its schema
at the same moment those functions fail with "schema … does not exist" —
the intermittent server PG test failures on the shared dev database.
createMigrator now hands the Migrator a proxy whose introspection only
looks at the migration schema (or current_schema()). A regression test
churns schemas while migrating; it fails on the old migrator.

Also samples EngineRunQuery's comma-separated status/triggeredBy in the
contracts schema test.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 17:15:04 +08:00
rootandClaude Opus 5.5 363f85b1a1 feat(server,contracts,web): dashboard library counts and engine runs triggeredBy/multi-status filters
- GET /dashboard: automation.library {entities, movies, series, seasons,
  episodes, assets} and automation.slotSatisfiedRate; home media overview
  shows the counts instead of placeholders.
- GET /engine/runs: `status` accepts comma-separated values; new
  `triggeredBy` filter (comma-separated) evaluated server-side by scanning
  batches and paging with the last inspected row's cursor (scan cap 2000).
- Task stream: source filter and the "active" tab (running,pending) are now
  one paginated server query.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 16:49:11 +08:00
rootandClaude Opus 5.5 04947c4b78 test(contracts): sample full enum-keyed counts for SubscriptionOverviewPage
zod v4 records keyed by an enum require every key; the generic sample only
produced {k: "v"}, so the positive example failed after W9-E.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 16:29:29 +08:00
rootandClaude Opus 5.5 81bf010e3e feat(sources,organize): site agent capabilities and organize.minSizeBytes setting (W10-pre2)
- site.list / site.health_check / site.refresh_login / site.user_data capabilities
  over the W9-A site center services; outputs whitelisted (no secret refs,
  cookies, passwords, login names); execute kinds need automation grant.
  IDs use snake_case (CapabilityId regex); registry count 17 -> 21.
- settings.organize.minSizeBytes (default 50 MiB); file-manager identify /
  organize and planImport take request > setting > port default.
- tests and runbook notes (sites, files).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 16:02:30 +08:00
rootandClaude Opus 5.5 410d7fd636 feat(subscriptions): overview groups, coverage matrix, multi-version auto-fill, form field normalization (W9-E)
- 0035: subscription.last_progress_at, acquisition_slot.position (primary version)
- GET /subscriptions: server-side groups + counts, card fields, sort/filter, offset cursor
- GET /subscriptions/{id}/coverage: season x episode (movie: edition) x slot matrix, 30s cache
- auto-fill for non-primary slots: inherit/on/off, per-subscription daily budget, upgrade priority
- params normalization with MP/legacy aliases; duplicate 409; pause/resume/resubscribe/batch/fill/slot-order

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 15:54:29 +08:00
rootandClaude Opus 5.5 99a3e17210 feat(file-manager): suggest-name via naming engine; plan.naming source from override chain (W9-H G9)
- POST /files/suggest-name renders the effective template (entity > root >
  global) with the same variables as organize: file name segment for files,
  work/season dir segment for directories; identity_uncertain when no entity
- planImport plan.naming.templateSource/templateVersion now come from
  effectiveNaming + the registered template version (fixes the leftover
  DEFAULT_TEMPLATES reference after the naming chain landed)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 15:26:34 +08:00
rootandClaude Opus 5.5 dfb08ab552 feat(contracts,db): naming settings, override fields and template versions (0032)
- settings.naming {movie, tv, variety, lockFileNameForBound}, default = MP templates;
  library section gains #naming anchor
- naming API schemas and Api.naming endpoints (variables, preview, templates, overrides)
- 0032: library_root.naming, media_entity.naming_override, naming_template_version
  (version 1 = upgraded v1 templates, 2 = MP default, fingerprint-deduplicated)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 15:24:54 +08:00
rootandClaude Opus 5.5 e8745115f4 feat(system): process heartbeat + /system/health, engine runs business fields (W9-I)
- process_heartbeat / process_heartbeat_sample (0034): api / worker / worker:agent / all
  upsert every 15s (event loop lag last/max, concurrency, running runs, SSE connections, memory)
- GET /system/health (admin): processes with 45s stale flag, SSE, DB latency/size/connections,
  disks (DATA_DIR / library roots / download dirs, cache thresholds), mount sentinel checks
  with IO timeout, engine pending/oldest/24h failure rate, schedules
- GET /system/health/history?hours=: bucketed lag / running / SSE series
- /engine/runs and /engine/runs/{id}: business {kind, refId, title, triggeredBy} joined from
  run input / task / agent_run / tasks_run_meta / outbox; businessKind / businessRefId filters
- docs/runbook/ops.md: field meanings and alert thresholds

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 15:23:37 +08:00
rootandClaude Opus 5.5 88bbed409c feat(contracts): file manager v2 backend gaps G1-G9 (W9-H)
- FilesOrganizeItem.external (TMDB candidate, xor mediaEntityId); request
  minSizeBytes / onExisting (skip|conflict|replace_with_verify) / naming
  (template|keep) / refreshMetadata
- FilesOrganizeGroup: per-file action/existing/skipReason, excluded[]
  (below_min_size), entity, naming{mode,templateSource,templateVersion},
  targetSegments, refreshTaskId
- identify: minSizeBytes, item size/skipped/records, excludedBySize
- new endpoints: entity-from-candidate, scrape-batch, tree, record-sources,
  suggest-name; FilesErrorReason not_implemented

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 15:08:08 +08:00
rootandClaude Opus 5.5 0105e5c6ac feat(rules): rule_group/rule_atom (0033), /rules endpoints, evaluation via referenced rule groups
- migration 0033: rule_group (layers, hard excludes, scope, source, revision,
  fingerprint, default_order) and rule_atom; acquisition_slot.rule_group_ids;
  preference tags migrated into rule groups (same id/fingerprint), suppressions
  linked; seeded default groups for acquisition / upgrade / search
- contracts: RuleGroup/RuleAtom/RuleExpr/RuleDefaults, parse/serialize/preview
  schemas; Api.rules, Api.ruleAtoms, Api.rulePolicies, Api.ruleSuppressions
- server: GET/POST/PATCH/DELETE /rules, /rules/{id}/revisions, /rules/{id}/preview,
  /rules/parse, /rules/serialize, /rules/keywords, /rules/defaults, /rules/atoms;
  disable/delete writes suppression, agent re-add -> 409
- /preference-tags now aliases rule groups; /policy-revisions and
  /preference-suppressions kept as aliases of /rules/policies, /rules/suppressions
- evaluateAcquisition resolves slot -> subscription -> default chain (+ upgrade
  defaults) and records pri / rule_group@revision / per-layer trace

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 15:08:00 +08:00
rootandClaude Opus 5.5 a0946bf480 feat(search): global search aggregation backend (W9-G)
- GET /search?q=&scope=all|media|releases|actions&limit=: local works (title_alias
  exact/prefix/trgm + name prefix, slot status summary), TMDB candidates (cached,
  localEntityId), cross-site releases, page/action jump list; tookMs per group
- cross-site: enabled sites via shared per-site limiter, 15s AbortSignal timeout,
  site concurrency cap 4, same-query coalescing, 30s cache; partial + failedSites;
  results upserted to source_release; inventory overlay via GET /inventory
- GET /search/releases: site/resolution/source/size/free/H&R filters, sort, cursor,
  facets; shares the cache
- POST /search/releases/{siteId}/{siteReleaseId}/plan: forwards in-process to
  POST /acquisition-plans (same auth, validation, audit, rejections)
- contracts/search.ts, runbook, tests (partial/timeout, coalesce, cache, overlay,
  filters/sort, plan)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 15:05:58 +08:00
rootandClaude Opus 5.5 e2d1e21968 feat(contracts): site center schemas, endpoints and migration 0031
Site health/proxy, SiteAccount login state (challenge, lastLoginAt, lastError,
userData), SiteUserData history table, SiteCredentialsBody by kind,
refresh-login/health-check/user-data/delete endpoints, SitesSettings defaults.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 14:40:07 +08:00
rootandClaude Sonnet 5 623aa0d3b7 chore(release): v0.2.0 version and changelog
Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-28 13:53:33 +08:00
rootandClaude Opus 5.5 3e37ac8ae7 refactor(agent): runAgent uses Agent upstreams only; deprecate legacy settings.pi fields
pi.provider / baseUrl / model / thinkingLevel / apiKeySecretRef are now
DEPRECATED_SETTINGS_FIELDS: kept in the schema for stored JSON, dropped
from PATCH /settings and from the settings sections, never read by the
server. runAgent fails with a clear message when no upstream is set.
The agent demo gate checks the default upstream provider; e2e seeds a
fake upstream as default. OpenAPI summaries say Agent upstream.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 13:45:40 +08:00
rootandClaude Opus 5.5 fb7727cf66 feat(identity): open identity_unresolved decisions for resources with no candidates
no_match resources (and lookup failures after auto-review gives up) now
open a decision in the inbox with options to set a TMDB ID, pick a local
work, or dismiss; ambiguous decisions get the same manual options.
Resolve accepts identity {tmdb | mediaEntityId} and assigns with a manual
lock. The inbox option dialog collects the TMDB type/ID or searches local
works; the identity pool links each pending item to its inbox filter.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 13:44:57 +08:00
rootandClaude Opus 5.5 31a54186c7 copy: second-round copy review across console, settings, agent and server messages
- Trim descriptions, empty states, confirm buttons and toasts to facts
- Unify terms per glossary: 待处理/待识别/整理记录/运行记录/凭证/子会话/Agent 上游
- Subscription-login termsNote states facts only (borrowed client identity, ToS/ban risk)
- Shorter, concrete goal templates for built-in Agent checks
- Human-readable nouns instead of table names in instance CRUD errors
- Audit labels for pi_upstream / schedule / automation_grant actions
- Glossary: add 上游、订阅登录、凭证、定时任务、巡检、运行记录、子会话、停止、持续授权

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 13:29:42 +08:00
rootandClaude Opus 5.5 c67f6a9682 feat(web): settings with 9 sidebar sections and Agent upstream management
- /console/settings#<section>[/<anchor>]; legacy ?tab= redirected in clientLoader
- SETTINGS_SECTIONS in contracts with field-ownership test
- Agent section: upstream list, create wizard (provider -> credentials/model -> login/test/default),
  edit drawer with OAuth browser/device login, test, set default, delete
- sites section with site registration, snapshot/rate-limit drawer and account credentials
- path mappings moved into downloader / media-server drawers; metadata and account sections

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 03:05:51 +08:00
rootandClaude Opus 5.5 459991421e feat(agent): Pi subagents via agent.spawn / agent.await
- agent_run gains depth / spawn_reason (0029); agent_run_event types
  subagent_spawned / subagent_event / subagent_completed
- agent.spawn (propose, medium, replay never): allowlist ⊆ parent, mode not
  above parent, budget carved from parent's remainder, idempotent per run+input;
  settings.pi.subagents {enabled, maxConcurrent, maxDepth} (subagentsEnabled kept as alias)
- child runs claim a concurrency slot under an advisory lock (waitForFact while
  queued); agent.await polls business tables without calling the model
- parent abort cascades to all descendants; parent runtime budget counts
  descendant usage; child events mirrored into the parent stream by parent seq
- authorize caps every child call by all ancestors' allowlists/kinds
- session page shows parent link, inherited info and the subagent tree
- e2e E11; runbook section on subagents

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 02:49:37 +08:00
rootandClaude Opus 5.5 5843527317 fix(schedules): cron sample for schema test, lint cleanups
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 02:39:56 +08:00
rootandClaude Opus 5.5 f8f70d8e09 feat(contracts,db): scheduled_job registry tables and schedules API contracts
- 0028_scheduled_jobs: scheduled_job (stable key, group, kind task|agent_check,
  enabled/schedule with defaults + customized flag, config, last_* / next_run_at /
  run_requested_at / running_since, counters) and scheduled_job_run history
- contracts/schedules.ts: ScheduleSpec (interval | 5-field cron), AgentCheckConfig
  (goal template, capability allowlist, budget, trigger condition, allowRecover,
  reportAsDecision), board / run views
- Api.schedules: list, create (agent_check only), update, remove, run-now, skip,
  runs; all admin + console session only

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 02:32:22 +08:00
rootandClaude Opus 5.5 b9dbd54e37 feat(contracts,db): Pi upstream contracts and pi_upstream table
- contracts/pi.ts: 41 builtin provider ids + openai-compatible + fake, auth kinds,
  upstream row/view/create/update, provider capability table (oauth modes, termsNote),
  test/models/oauth start/complete schemas
- settings.pi gains defaultUpstreamId; legacy single-upstream fields kept for fallback
- api.ts: /pi/providers, /pi/upstreams CRUD, test, models, oauth start/complete/logout
  (writes and login are console-session only)
- migration 0027: pi_upstream, agent_run.upstream_id, one-time conversion of saved
  legacy settings.pi into a default upstream

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 02:23:09 +08:00
rootandClaude Fable 5.1 16ab2a6c84 docs(plan): v0.2 adds Pi subagents and scheduled-jobs board
Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-09-28 02:09:39 +08:00
rootandClaude Opus 5.5 d3de2c8971 fix(files,test): probe requires organize.execute; rename duplicate E6 to E10; trash approval replay test
- 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]>
2026-09-28 00:50:29 +08:00
rootandClaude Opus 5.5 2e3c2098fd docs: S-9 sync READMEs and porting notes with W5 code
- contracts README lists files.ts / auth.ts / browser.ts / redact.ts and auth:"session"
- adapters README fs row: FsProxy and the ./fsworker export
- server README: file-manager module, worker:agent and internal fsworker roles
- moviepilot-rust-transfer §7: compiled-binary fsworker and trash_item done (W5-E)
- 04 S9 row marked fixed (W6-B TOTP entry)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 00:50:29 +08:00
rootandClaude Opus 5.5 17cf3cbf97 fix(files,security): S-1 per-source Move authorization, S-2 session-only annotation, S-5 restore guards
S-1 (/files/move, /files/organize action=move):
- organize-guard: authorizeMoveEntities checks every source's media entities
  (declared entity plus entities of registered assets under the source);
  one-time approval still covers the whole request; any source that cannot
  be attributed to an entity only passes with an approval; denial lists every
  unauthorized item; optional non-consuming pre-check
- transfer: entities per source (directory = registered assets beneath it)
- organize: all move groups pre-checked before any group executes; any
  denial -> whole request 403 approval_required
S-2:
- api.ts: GET /files/download and POST /files/trash/purge marked
  auth:"session"; files routes attach requireAdminSession from the registry
  annotation; handler-side purge check removed, download keeps a same-rule
  defense check; permanent delete uses isAdminSession
- DEV key rule written down: equals an admin-scope key for session-only
  endpoints, still admin for Move/delete approval bypass (as authorizeMove)
- api-registry.test: session list updated; new reverse assertion that no
  non-session endpoint answers 403 to an admin-scope bearer
S-5 (trash restore):
- restore-to requires a writable managed library root (isWritableTarget)
- in-place restore rebuilds missing parents level by level without
  following symlinks (mkdirWithin), realpath-checked per level
- cross-volume restore capped by FILES_TRASH_CROSS_MAX_BYTES; cross-volume
  copies (trash in / restore out) go through fsproxy
Tests: multi-entity move 403 + listing, unattributed source, organize foreign
asset 403, download/purge 403 for DEV and admin-scope keys, restore target
not writable, symlinked parent, rebuild parents, cross-volume cap.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 00:45:56 +08:00
rootandClaude Opus 5.5 47bd04aec0 fix(adapters,security): S-4 rss/browser redaction use the shared contracts key table
- contracts: isSensitiveQueryParam (whole-name match on SENSITIVE_QUERY_PARAMS
  or isSensitiveKey), the single source for query credential names
- rss/redact: drop local SENSITIVE_QUERY_KEYS; param checks go through
  isSensitiveQueryParam (+ caller extras); REDACTED re-exported from contracts
- browser/redact: CREDENTIAL_PARAM built from SENSITIVE_QUERY_PARAMS, JSON keys
  also checked with isSensitiveKey; keeps only its own rules (path passkey,
  cookie names, a11y password fields); remove stray regex statement
- redact-consistency.test: 24 credential spellings (pass_key, c_secure_pass,
  refresh_token, password, uid_key, auth, ...) masked by contracts, rss and
  browser alike; plain params untouched; rss extract/unredact round-trip

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 00:45:56 +08:00
rootandClaude Opus 5.5 36aa99b8aa feat(files): W5-A2 move/copy via planImport, organize overrides, delete records, download
- /files/move|copy go through planImport's new `transfer` variant (keep names, directory
  expanded to per-file plan) → organization_record + verifyAndImport/executeRecord: per-file
  ledger resumable, fsproxy, cross-volume directory move = per-file copy→verify→delete then
  remove empty source dirs; shared plan store + plan_stale; asset_location follows per file,
  path_binding revised when a whole directory moved (finalizeTransfer); request waits up to
  FILES_TRANSFER_WAIT_SEC, else executed.pending
- /files/organize per-item overrides take effect: season/episodes (domain planOrganize
  overrides), contentUnitId coverage, targetDir (no binding), operation, slotId (satisfied
  after verify); warnings only for inapplicable overrides
- delete/trash/restore write organization_record (action trash|delete|restore, file-level
  results; migration 0026)
- GET /files/download (admin session, nfo/subtitle/image, size cap, streamed, audited)
- GET /files/records?ids|planIds for the organize page
- fs race errors map to 503/404/403/400 instead of 500
- runbook updated

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-27 23:53:50 +08:00
rootandClaude Opus 5.5 1dd460d359 feat(db,contracts): W5-C subscription units migration 0025, contracts and mapping
- subscription: status pending (TMDB not ready: searchable/evaluable, not completable),
  last_searched_at / next_search_at
- content_unit (season): episode_total ledger with source / lock / timestamp
- acquisition_plan: unit_keys (Slot × unit reservation) and file_selection; drop the
  one-in-flight-per-slot unique index (checked under slot row lock per unit instead)
- new tables slot_deferral, slot_unit (per-unit quality ledger), subscription_history
- contracts: SubscriptionParams, SubscriptionHistory, SlotDeferral, SlotUnit, SubscriptionProgress,
  EpisodeTotalSet, CandidateEvaluation pri/trace/unitKeys and decision deferred;
  endpoints subscriptions.search/progress/setEpisodeTotal and subscription-history
- mapping: evaluation messages via describeViolation, pri/trace passthrough

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-27 23:33:56 +08:00
rootandClaude Opus 5.5 09f6e3a3e5 feat(db,contracts): trash_item migration and file manager contract refinements
- 0024_trash: trash_item table + unique idempotency index on artifact(files_idem)
- files.ts: TrashItemRow, delete planId/planExpiresAt, organize group warnings,
  scrape returns refreshMetadata taskId
- registry tests list the new table/row schema

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-27 23:02:14 +08:00
rootandClaude Opus 5.5 ceff9cf51f feat(ao): W4-E3 manual organize guard, series completion, engine retry, v0.1.0
- 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]>
2026-09-27 22:47:11 +08:00
rootandClaude Opus 5.5 b86f75c011 feat(contracts): file manager API schemas and /files endpoint registry
Adds packages/contracts/src/files.ts (roots/list/stat, mkdir/rename,
move/copy/organize two-phase dryRun->planId, delete with reference checks
and trash, identify/scrape/probe) and registers Api.files in api.ts.
Implementation lands in a follow-up commit (W5-A).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-27 22:37:04 +08:00
rootandClaude Opus 5.5 86af723b2a feat(agent): W4-E2 in-grant auto recovery for decisions with verified outcome
- 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]>
2026-09-27 22:35:03 +08:00
rootandClaude Opus 5.5 f48e1aac96 fix(security): W4-E1 approvals admin-session only, shared redact table, TMDB key redaction, prod mock gate
- POST /approvals and PATCH /approvals/{id} require a console admin session
  (EndpointDef auth:"session"); API keys incl. admin scope get 403, so an
  organize.execute key can no longer approve its own plan (S1)
- tests for single-use approval checks: expired, revision/payload hash
  mismatch, consumed, revoked, pending/rejected, plan mismatch; re-approve
  after plan change succeeds once and cannot be replayed (S4)
- one redact key table in @umt/contracts shared by server logger and agent
  redactForModel: rsskey, torrent_pass, authkey, downhash, c_secure_pass,
  passkey variants, x-api-key, cookie headers, nested objects/arrays (S2)
- TMDB network errors: message and cause redacted via redactUrl (S3)
- web Agent ?mock=1 only in DEV builds or when pi.provider is fake (S7)
- EndpointDef auth annotations for /auth/*, asserted against
  PUBLIC_AUTH_PATHS (S10); doc drift fixes (S11)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-27 22:24:44 +08:00
rootandClaude Opus 5.5 f6457a29db feat(settings): agent.sessionDir setting shown on settings page
null keeps the deployment default (AGENT_SESSION_DIR / DATA_DIR/agent-sessions).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-27 22:15:43 +08:00
rootandClaude Opus 5.5 f9c20c67dc perf(fs): 1MiB stream chunks, full sha256 via sha256sum subprocess, walk cap
- copy streams use 1MiB highWaterMark
- forced full sha256 uses Deno.Command(sha256sum), falls back to in-process stream;
  method recorded as hashVia and counted
- per-file organize.transfer timing log
- ORGANIZE_WALK_MAX_FILES caps manual path walks; dryRun returns truncated

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-27 22:12:16 +08:00
rootandClaude Opus 5.5 effaa32b70 feat(browser): W4-C BrowserProvider contract and camofox-browser adapter skeleton
- contracts/browser.ts: provider kind, per-capability {supported, verified} flags,
  session/tab/control-lease rows, NavigationBudget, redacted BrowserSnapshot,
  DownloadArtifact with blob/secret refs, BrowserErrorKind
- adapters/browser: BrowserProvider interface, camofox client (health, capabilities
  static + /openapi.json probe, openSession/navigate/snapshot/screenshot; rest
  not_implemented for M2), none provider, redactSnapshot, profile serial lock,
  domain allowlist budget; Bearer access key via BrowserSecretResolver
- testkit/browser: mock camofox server (auth, fixtures with secrets, faults)
- db 0023: browser_provider/session/tab/control_lease with one-active-lease and
  one-live-session-per-profile partial unique indexes; types regenerated
- docs/design/browser-provider.md: contract, camofox OpenAPI 1.17.0 mapping,
  unverified items and M2 prototype checklist; Playwright is not a product browser

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-27 21:39:29 +08:00
rootandClaude Opus 5.5 1023642ef9 feat(web): W4-A wire console to real endpoints
- Agent page uses contracts (AgentRun/AgentRunDetail/AgentGoalReport) instead of a local
  mirror; ?mock=1 stays as an explicit demo switch; run snapshot refreshes on agent.run_status;
  stream auto-follow only while the user stays near the bottom (abort stays reachable)
- settings: Pi provider select incl. fake, new Agent runtime card (budgets, fakeScript)
- download trace uses /tasks?subjectId= and /organization-records?downloadTaskId= instead of
  time-window engine run matching and path matching; new business-task tab with cancel/retry
- workbench download dialog posts /acquisition-plans and shows per-slot rejection reasons
- preference tag revision history; subscription search by title/kind; identity/alias/subscription
  lists show entity titles; slot cards show current asset summary; policy preview sample titles
- DecisionSummary / IdentityRevisionList / ReleaseIdentityResult / TaskAccepted / PolicyPreviewSummary /
  OrganizePlanArtifactContent taken from contracts
- docs: SSE for console (cookie) vs GET /events?after=&limit= JSON replay for API keys

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-27 21:32:11 +08:00