From 37437e80e4d69e5bf0e6b5f417e0baccc5f52475 Mon Sep 17 00:00:00 2001 From: Josh Patra Date: Thu, 20 Aug 2026 11:29:28 -0400 Subject: [PATCH] chore(repo): consolidate documentation and structure --- .github/FUNDING.yml | 12 -- .github/ISSUE_TEMPLATE/feature-request.md | 2 +- .github/pull_request_template.md | 17 ++ .gitignore | 8 + .stylelintrc.json | 13 -- AGENTS.md | 124 +++++++++++++ CONTRIBUTING.md | 13 +- README.md | 138 ++++++++------- agent_docs/latest_session_work.md | 79 --------- agent_docs/project_progress.md | 164 ------------------ .../OperationalLogRegressionContractTests.cs | 2 +- .../PlaylistPersistenceServiceTests.cs} | 2 +- .../Protocols/SubsonicProtocolAdapterTests.cs | 2 +- .../DurableStateTransferServiceTests.cs | 2 +- ...cs => LibraryPlaylistStorageModelTests.cs} | 2 +- .../MediaCacheKeyNamespaceContractTests.cs | 2 +- allstarr.sh | 2 +- ...lpers.cs => JellyfinController.Helpers.cs} | 4 - ...nicController.cs => SubsonicController.cs} | 2 +- .../Favorites/FavoriteModelConfiguration.cs | 2 +- .../ManagedFileOwnershipEntity.cs | 2 +- .../ManagedFileOwnershipModelConfiguration.cs | 2 +- ...vices.cs => PlaylistPersistenceService.cs} | 1 + ... => AllstarrDbContext.LibraryPlaylists.cs} | 2 +- ...> AllstarrDbContext.MetadataEnrichment.cs} | 3 +- allstarr/Core/Storage/AllstarrDbContext.cs | 4 +- .../Storage/DurableStateTransferService.cs | 30 ++-- ...Entities.cs => LibraryPlaylistEntities.cs} | 0 ...ities.cs => MetadataEnrichmentEntities.cs} | 0 allstarr/allstarr.http | 6 - .../wwwroot/images/providers/squidwtf.svg | 1 - apis/steering/performance-audit.md | 120 ------------- apis/steering/webui-design.md | 66 ------- docs/README.md | 15 +- docs/operations/apple-download-provider.md | 3 +- docs/operations/configuration.md | 8 +- docs/operations/spotify-lyrics-sidecar.md | 2 +- docs/user-guide.md | 123 +++++++++++++ 38 files changed, 406 insertions(+), 574 deletions(-) create mode 100644 .github/pull_request_template.md delete mode 100644 .stylelintrc.json create mode 100644 AGENTS.md delete mode 100644 agent_docs/latest_session_work.md delete mode 100644 agent_docs/project_progress.md rename allstarr.Tests/{Storage/Phase4PersistenceServiceTests.cs => Playlists/PlaylistPersistenceServiceTests.cs} (99%) rename allstarr.Tests/Storage/{Phase4DurableModelTests.cs => LibraryPlaylistStorageModelTests.cs} (99%) rename allstarr/Controllers/{Helpers.cs => JellyfinController.Helpers.cs} (99%) rename allstarr/Controllers/{SubSonicController.cs => SubsonicController.cs} (99%) rename allstarr/Core/Playlists/{Phase4PersistenceServices.cs => PlaylistPersistenceService.cs} (99%) rename allstarr/Core/Storage/{AllstarrDbContext.Phase4.cs => AllstarrDbContext.LibraryPlaylists.cs} (99%) rename allstarr/Core/Storage/{AllstarrDbContext.Phase6Enrichment.cs => AllstarrDbContext.MetadataEnrichment.cs} (96%) rename allstarr/Core/Storage/{Phase4DurableEntities.cs => LibraryPlaylistEntities.cs} (100%) rename allstarr/Core/Storage/{Phase6EnrichmentEntities.cs => MetadataEnrichmentEntities.cs} (100%) delete mode 100644 allstarr/allstarr.http delete mode 100644 allstarr/wwwroot/images/providers/squidwtf.svg delete mode 100644 apis/steering/performance-audit.md delete mode 100644 apis/steering/webui-design.md create mode 100644 docs/user-guide.md diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 3e2e6b23..08416eac 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,15 +1,3 @@ -# These are supported funding model platforms - github: [SoPat712] -patreon: # Replace with a single Patreon username -open_collective: # Replace with a single Open Collective username ko_fi: joshpatra -tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel -community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry -liberapay: # Replace with a single Liberapay username -issuehunt: # Replace with a single IssueHunt username -lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry -polar: # Replace with a single Polar username buy_me_a_coffee: treeman183 -thanks_dev: # Replace with a single thanks.dev username -custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md index 08c44883..82b9f7d6 100644 --- a/.github/ISSUE_TEMPLATE/feature-request.md +++ b/.github/ISSUE_TEMPLATE/feature-request.md @@ -32,7 +32,7 @@ Add any other context or screenshots about the feature request here. ## Safe diagnostics from Allstarr (optional) - Sensitive values stay redacted in this block. -- Allstarr Version: [e.g. v3.0.0-beta.1] +- Allstarr Version: [e.g. v3.1.0-beta.1] - Backend Type: [e.g. Jellyfin] - Capability and provider involved: [e.g. recommendations / Last.fm] - Provider account scope: [global / user / library / not applicable] diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..935afd07 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,17 @@ +## What changed + +Describe the user-visible or protocol-visible result and why this owner is the right place for it. + +## Risk + +- Compatibility impact: +- Storage or migration impact: +- Authentication, authorization, or secret-handling impact: + +## Verification + +List the focused checks and affected Release/WebUI lanes you ran. Do not include credentials, private URLs, provider payloads, or generated logs. + +- [ ] Focused automated coverage passes +- [ ] Documentation is updated when behavior or setup changed +- [ ] The diff contains no generated artifacts or unrelated edits diff --git a/.gitignore b/.gitignore index 4d6cd865..5f06691b 100644 --- a/.gitignore +++ b/.gitignore @@ -120,3 +120,11 @@ sampleMissingPlaylists/ /webui/build/ /webui/playwright-report/ /webui/test-results/ + +# Local design tooling +/.agents/ +/.codex/ +/.impeccable/ +/webui/.impeccable/ +/PRODUCT.md +/agent_docs/ diff --git a/.stylelintrc.json b/.stylelintrc.json deleted file mode 100644 index 07131da2..00000000 --- a/.stylelintrc.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": ["stylelint-config-standard"], - "rules": { - "alpha-value-notation": null, - "color-function-alias-notation": null, - "color-function-notation": null, - "custom-property-empty-line-before": null, - "declaration-empty-line-before": null, - "media-feature-range-notation": null, - "no-descending-specificity": null, - "selector-class-pattern": null - } -} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..e69f3ec5 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,124 @@ +# Working on Allstarr + +This is the single repository guide for coding agents and automated contributors. Human contributors should also read [CONTRIBUTING.md](CONTRIBUTING.md). + +## Product goal + +Allstarr is a self-hosted music gateway in front of a Jellyfin or Subsonic/OpenSubsonic backend. It preserves the listener's normal client while adding provider-neutral search, matching, playback, playlists, lyrics, scrobbling, history, and discovery. + +The application is moving toward a public beta. Favor changes that make ordinary setup and daily use clearer, safer, faster, and easier to verify. Do not add speculative frameworks, duplicate owners, or edge-case machinery without a demonstrated user or protocol need. + +## Start with the right source of truth + +Read only what the task needs: + +1. [README.md](README.md) for supported product behavior and installation. +2. [docs/README.md](docs/README.md) for the documentation map. +3. [docs/architecture/overview.md](docs/architecture/overview.md) before changing ownership or boundaries. +4. [DESIGN.md](DESIGN.md) before changing the WebUI. +5. [CONTRIBUTING.md](CONTRIBUTING.md) for validation and pull-request expectations. +6. The nearest operation, protocol, extension, or module document for the area being changed. + +Running code, migrations, tests, and checked-in configuration are authoritative when documentation disagrees. Fix drift in the same change. + +## Repository map + +| Path | Responsibility | +| --- | --- | +| `allstarr/Program.cs` | Application composition and middleware order | +| `allstarr/Controllers/` | Admin APIs and Jellyfin/Subsonic protocol surfaces | +| `allstarr/Core/Capabilities/` | Provider contracts and registration | +| `allstarr/Core/Routing/` | Capability and account selection | +| `allstarr/Core/Matching/` | Canonical identity, evidence, and match decisions | +| `allstarr/Core/Playlists/` | Playlist ingestion, projection, and synchronization | +| `allstarr/Core/Playback/` | Playback observations and client sessions | +| `allstarr/Core/Intelligence/` | Listening history, recommendations, and AudioMuse integration | +| `allstarr/Core/Jobs/` | Durable jobs, schedules, leases, retries, and outbox | +| `allstarr/Core/Storage/` | PostgreSQL model, migrations, and state transfer | +| `allstarr/Core/Extensions/` | Extension package lifecycle and permissions | +| `allstarr/Services/` | Built-in provider adapters and external gateways | +| `webui/` | Svelte 5/SvelteKit administration interface | +| `allstarr.Tests/` | .NET unit, integration, protocol, and migration coverage | +| `webui/tests/` | Browser behavior and responsive coverage | +| `tools/tests/` | Qualification, timing, and live smoke tools | +| `sidecars/apple-gateway/` | Bounded Apple/GAMDL compatibility gateway | +| `docs/` | User, operator, architecture, protocol, and extension documentation | + +Keep responsibilities modular. Extend the existing owner instead of creating a second matching, routing, playlist, credential, cache, or background-work system. + +## Product invariants + +- One deployment exposes either Jellyfin or Subsonic/OpenSubsonic, never both catch-all protocol surfaces. +- PostgreSQL is the only durable database. Audio, artwork, cache payloads, backups, and the encryption key ring remain files. +- Original backend library files are read-only inputs. Only explicitly owned managed, cache, download, or kept paths may be written. +- Provider credentials are encrypted and resolved only for the exact tenant, user, library, capability, and account scope. +- Local backend objects pass through unchanged. A matched item uses the complete original backend object; a virtual item must be internally consistent and clearly external. +- Provider capabilities are interchangeable typed contracts. Built-ins and extensions meet at the same registry without letting extensions replace reserved built-in IDs. +- Stateful or retryable work uses the durable job system. Do not launch detached controller tasks for downloads, matching, playlist changes, scrobbling, imports, or extension lifecycle work. +- Optional providers and sidecars degrade their own capability when unavailable; they must not prevent core startup or native proxy use. +- Never expose secrets, tokens, cookies, signed media URLs, private identifiers, or raw provider payloads in logs, errors, fixtures, or documentation. + +## Change workflow + +1. Define the user-visible or protocol-visible acceptance condition. +2. Trace the request through its existing controller, core owner, persistence boundary, adapter, and projection. +3. Fix the shared cause in that owner; avoid route-specific or provider-specific copies. +4. Add the smallest deterministic regression that would have caught the problem. +5. Run focused checks while iterating and the affected lane at the integration boundary. +6. Update the owning documentation when behavior, setup, architecture, permissions, or recovery changes. +7. Review the final diff for unrelated edits, generated output, credentials, and weakened assertions. + +Preserve unrelated work in a dirty tree. Stage exact files only; never use `git add .`, destructive resets, or broad cleanup commands. + +## Verification + +Use the smallest relevant checks first. + +### .NET + +```bash +dotnet build allstarr.sln -c Release --no-restore -p:TreatWarningsAsErrors=true +dotnet test allstarr.Tests/allstarr.Tests.csproj -c Release --no-build --filter "FullyQualifiedName~AreaBeingChanged" +dotnet format allstarr.sln --no-restore --verify-no-changes --verbosity minimal +``` + +PostgreSQL integration tests require an isolated database through `ALLSTARR_TEST_POSTGRES`. Release validation runs both CI lanes: `Lane!=ReleaseCritical` and `Lane=ReleaseCritical`. + +### WebUI + +```bash +cd webui +npm run check +npm test +npm run build +npm run check:budgets +npm run test:e2e:existing-build +``` + +Run browser checks after the production build when using `test:e2e:existing-build`. Preserve keyboard, responsive, light/dark, reduced-motion, and no-overflow coverage for touched flows. + +### Deployment and protocol work + +- Validate the affected Compose profile with `docker compose ... config --quiet`. +- Use fixtures and mocked providers in automated tests; never require live personal credentials. +- Protocol changes need request/response fixtures and the relevant qualification checks in `tools/tests/`. +- Migration, backup, restore, and destructive behavior require an isolated target and exact ownership checks. + +Do not weaken discovery, assertions, isolation, compatibility, accessibility, or security to make a gate pass. + +## WebUI rules + +Follow [DESIGN.md](DESIGN.md). Reuse the existing Svelte, Bits UI, Tailwind, Lucide, and shared component system before adding a dependency or page-specific control. Keep the interface dense where comparison matters and explanatory where setup or empty state needs guidance. + +Integrations owns Services, Accounts, Extensions, and Routing. Intelligence owns listening history, imports, discovery, automation, and its built-in AudioMuse connection. Settings owns deployment and operator behavior. Do not scatter the same configuration across these areas. + +## Documentation rules + +- `README.md` is the public product and installation entry point. +- `docs/user-guide.md` explains the dashboard and common workflows. +- `docs/operations/` owns deployment and recovery procedures. +- `docs/architecture/` owns durable boundaries and code ownership. +- `docs/extensions/` owns the public extension contract. +- Module README files stay beside specialized code or tools. + +Describe shipped behavior, not aspirations. Prefer one canonical explanation and link to it instead of copying it. Do not commit local agent state, prompts, session logs, design-tool state, private infrastructure details, generated reports, or credentials. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a7be61c9..e1dcb81e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,7 +24,7 @@ dotnet test allstarr.sln ## Before You Change Code -Read the [architecture overview](docs/architecture/overview.md) and the owning operations or SDK document for the area you are changing. +Read the repository [agent guide](AGENTS.md), the [architecture overview](docs/architecture/overview.md), and the owning operation or SDK document for the area you are changing. The agent guide is intentionally tool-neutral so a contributor can point any coding agent at one file. In particular: @@ -40,10 +40,11 @@ In particular: ## Tests And Fixtures -Every behavior change, bug fix, contract change, and migration rule needs focused coverage. Run the smallest relevant tests while iterating, then run the full Release suite before asking for review: +Every behavior change, bug fix, contract change, and migration rule needs focused coverage. Run the smallest relevant tests while iterating. PostgreSQL integration tests require an explicitly isolated database through `ALLSTARR_TEST_POSTGRES`. CI splits the Release matrix into two lanes, and both are required before release: ```bash -dotnet test allstarr.sln -c Release +dotnet test allstarr.sln -c Release --filter "Lane!=ReleaseCritical" +dotnet test allstarr.sln -c Release --filter "Lane=ReleaseCritical" ``` Useful focused examples: @@ -58,7 +59,7 @@ Provider and external-gateway tests use local fixtures, fake providers, or mocke or live provider calls to the automated suite. Apple gateway tests must not assume wrapper-v2 itself implements the Allstarr search/download contract. -Migration work must be checked against an explicitly isolated disposable PostgreSQL database. +Migration work must be checked against an explicitly isolated disposable PostgreSQL database. WebUI changes must also pass `npm run check`, `npm test`, `npm run build`, `npm run check:budgets`, and the affected Playwright coverage from `webui/`. ## Provider Extensions @@ -68,7 +69,9 @@ Do not bundle provider packages or auto-enroll users in an external registry. ## Documentation -Update the owner document when behavior changes. Keep the root docs useful to operators and contributors; keep detailed invariants in the appropriate steering reference. Use the project's direct, normal voice. Prefer exact statements over promotional claims, and label planned behavior as planned. +Update the owner document when behavior changes. Keep README and the user guide useful to operators; keep detailed invariants in architecture, operation, protocol, extension, or module documents. Use the project's direct, normal voice. Prefer exact statements over promotional claims, and do not put planned behavior in user documentation. + +Do not commit agent prompts, session handoffs, local design-tool state, generated test reports, private deployment details, or duplicate planning documents. `AGENTS.md` is the only public instruction entry point for coding agents. Check local Markdown links after renaming or removing files. Never paste real secrets, signed URLs, account names, or private library paths into examples. diff --git a/README.md b/README.md index 66a5d62c..67ddb8c1 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,27 @@ # Allstarr -[![Build Status](https://github.com/SoPat712/allstarr/actions/workflows/docker.yml/badge.svg?branch=main)](https://github.com/SoPat712/allstarr/actions/workflows/docker.yml) +[![Build Status](https://github.com/SoPat712/allstarr/actions/workflows/ci.yml/badge.svg)](https://github.com/SoPat712/allstarr/actions/workflows/ci.yml) [![Docker Image](https://img.shields.io/badge/docker-ghcr.io%2Fsopat712%2Fallstarr-blue)](https://github.com/SoPat712/allstarr/pkgs/container/allstarr) [![License](https://img.shields.io/badge/license-GPL--3.0-green)](LICENSE) -Allstarr is a self-hosted music gateway for Jellyfin and Subsonic-compatible clients. Put it in front of Jellyfin or a server such as Navidrome, connect the providers you actually use, and keep one familiar client while Allstarr handles search, matching, streaming, downloads, playlists, lyrics, scrobbling, favorites, and recommendations. +**Your music, connected.** -Allstarr does not put songs inside Postgres. Audio stays as normal files in the mounted `downloads`, `kept`, cache, and managed-library folders. Postgres holds control-plane state and bounded disposable metadata cache entries; artwork and other media cache payloads stay on bounded disk. +Allstarr is a self-hosted music gateway for Jellyfin and Subsonic/OpenSubsonic clients. It sits in front of an existing media server, preserves normal local-library behavior, and adds provider-neutral search, matching, playback, playlists, lyrics, scrobbling, listening history, and discovery. -## Before You Install +> **Beta status:** `3.1.0-beta.1` is a breaking fresh-install baseline intended for testing. Keep the previous deployment stopped and available for rollback. Do not let two Allstarr versions write the same cache, download, kept, or managed-library paths. -The `v3.1.0-beta.1` overhaul release is a breaking fresh-install baseline. Do not reuse the old Redis-to-Valkey conversion overlay or expect legacy Redis, mapping, extension, or job state to import automatically. Keep the old stack stopped for rollback, attach the existing backend library read-only when practical, and give the separate version 3 deployment its own writable download, kept, cache, and managed-library roots. Never let both versions write the same media roots. Then use the administrator WebUI to preview and import the safe parts of the old `.env`. Deployment values remain a checklist, personal accounts are reconnected by their owners, and the original file is never replaced. Follow the [legacy environment upgrade procedure](docs/operations/legacy-env-import.md#supported-workflow) before cutting clients over. +## What Allstarr owns -Standard Compose runs Allstarr and Postgres. It exposes one client protocol per deployment because the Jellyfin and Subsonic surfaces both own catch-all routes. Choose `Jellyfin` or `Subsonic`; a Subsonic deployment can use Navidrome as its backend. +- PostgreSQL stores users, encrypted account references, jobs, matches, playlist state, intelligence data, health, and audit records. +- Audio and artwork remain ordinary files in mounted cache, download, kept, and managed-library folders. +- The encryption key ring remains a separate file that must be backed up with the database. +- The original backend library is treated as read-only input. -## Quick Start +Allstarr does not put songs in PostgreSQL and is not a replacement for Jellyfin, Navidrome, or another media server. + +## Quick start + +Requirements: Docker with Compose, a Jellyfin or Subsonic/OpenSubsonic backend, and a private network or authenticated access proxy. ```bash git clone https://github.com/SoPat712/allstarr.git @@ -22,87 +29,88 @@ cd allstarr ./allstarr.sh init ``` -Edit `.env`. Select `BACKEND_TYPE` and review the image tag, listeners, security opt-ins, and mounted paths. Complete the backend URL, credentials, library, and user mapping through onboarding after startup. +Review `.env`, choose `BACKEND_TYPE`, and confirm the bind addresses and mounted paths. Then start the stack: ```bash ./allstarr.sh up curl --fail http://127.0.0.1:5274/health/ready ``` -`allstarr.sh` remembers optional profiles, validates the merged Compose model, creates secrets with private file -permissions, and never deletes volumes. For a normal upgrade, run `./allstarr.sh upgrade`; it briefly stops the -stack, creates a private portable export under `allstarr-backups/`, then updates and restarts the saved profile. -The export includes configuration, the encryption keyring, provider profiles, Postgres, mappings, playlist -caches, and durable application state. Downloaded and kept music stay in their existing host-mounted folders. -To move or recover an installation, initialize the destination and run -`./allstarr.sh restore /path/to/allstarr-upgrade-….tar.gz --confirm-replace`. Restore validates the archive, -creates a rollback backup of the destination, replaces its saved state, and restarts it if it was running. +Open the dashboard at `http://localhost:5275`. Sign in with the selected backend, complete onboarding, choose the music library, and connect only the services you use. Music clients connect to `http://localhost:5274`. -The default `release` mode runs reviewed images. Beta testers and contributors who want the checked-out commit can -run `./allstarr.sh mode source`, then `./allstarr.sh up`. For later source updates, run -`./allstarr.sh update`; it refuses tracked local changes, fast-forwards the current tracked branch, rebuilds, and -recreates the services. The same volumes and optional-provider profiles remain attached in either mode. +The dashboard binds to loopback by default. LAN or reverse-proxy access requires an explicit trusted-network policy; see [configuration](docs/operations/configuration.md). Keep Allstarr behind a private network, VPN, or authenticated proxy because it can access media-server and provider accounts. -Apple downloads are optional and are not part of the default installation. The Apple profile builds the repository's -small gateway with GAMDL 3.8.2 and the official wrapper-v2 0.0.2 source. Allstarr never supplies Apple binaries, so -the operator provides one legally obtained compatible APK/APKM through Sources > Apple download, then runs -`./allstarr.sh install-apple x86_64`. Removing the profile disables Apple download routes without -changing Postgres, media, or the persistent wrapper login session. See -[Apple download provider setup](docs/operations/apple-download-provider.md). +Read the [user guide](docs/user-guide.md) for the dashboard map, setup order, listening-history imports, playlist modes, matching, cache, and Intelligence. -Spotify lyrics are optional too. To run the pinned private-network sidecar, add -it with `./allstarr.sh enable spotify-lyrics`, then run `./allstarr.sh up`. Follow the -[Spotify lyrics sidecar guide](docs/operations/spotify-lyrics-sidecar.md). Importing an old `.env` can restore the -endpoint URL, but it cannot start the sidecar or pass a cookie to it. +## Upgrade or recover -Client traffic uses `http://localhost:5274`. The separate dashboard is on `http://localhost:5275`. Standard Compose publishes the dashboard on host loopback and only trusts the container gateway needed to cross that mapping. LAN or reverse-proxy access requires `ADMIN_BIND_ADDRESS=0.0.0.0`, `ADMIN_BIND_ANY_IP=true`, and an explicit `ADMIN_TRUSTED_SUBNETS` CIDR. Please keep it behind a private network, VPN, or authenticated access proxy. This software has meaningful access to your media server and provider accounts. +`allstarr.sh` remembers enabled optional profiles, validates Compose, protects generated secrets, and never deletes volumes during normal operation. -The complete install, backup, restore, and rollback instructions live in [the storage runbook](docs/operations/storage.md). Configuration keys are explained in [the configuration guide](docs/operations/configuration.md). +```bash +./allstarr.sh upgrade +./allstarr.sh restore /path/to/allstarr-upgrade-….tar.gz --confirm-replace +``` -## What It Does +`upgrade` creates a portable state export before updating. The export includes PostgreSQL state, configuration, key-ring material, provider profiles, mappings, playlist state, and durable work. Downloaded and kept music remain in their mounted folders and need their own backup policy. -- Proxies either the Jellyfin or Subsonic/OpenSubsonic surface while preserving native backend authentication and normal pass-through behavior. -- Merges local results with policy-eligible metadata providers and streams or downloads through separately selected capability routes. -- Keeps every real media file in accessible folders. Managed files have ownership, checksum, placement, and job records so Allstarr knows what it is allowed to change. -- Matches one real recording to local copies and multiple provider identities. Matching is provider-neutral and records why a link was accepted or rejected. -- Imports provider playlists as virtual views or materializes exact local matches into Jellyfin or Navidrome/Subsonic. Materialization preserves order, reuses existing tracks, supports reconcile or explicit recreate mode, and does not download unmatched entries. -- Runs long work as durable, inspectable jobs with retries, leases, cancellation, idempotency, and visible failure state. -- Supports opt-in favorite workflows for download, tagging, managed placement, and backend refresh. Unfavorite does not delete music. -- Collects opt-in listening signals and can build explained playlists from Jellyfin InstantMix, Last.fm similarity, ListenBrainz collaborative filtering, MusicBrainz-enriched local relationships, local rules, and an optional self-hosted AudioMuse connection configured in Intelligence. -- Scrobbles to Last.fm and ListenBrainz through durable delivery checkpoints. -- Installs verified provider extensions through the provider SDK permission and lifecycle boundary. No third-party registry is added automatically. +Beta testers and contributors can run the checked-out source instead of a published image: -Provider availability depends on the configured accounts, optional sidecars, permissions, and health. Missing optional services reduce capability instead of taking the whole application down. +```bash +./allstarr.sh mode source +./allstarr.sh up +``` -## Storage At A Glance +Later source updates use `./allstarr.sh update`; the command requires a clean tracked tree, fast-forwards the current branch, rebuilds, and recreates enabled services. See [deployment profiles](docs/operations/deployment-profiles.md) and the [storage runbook](docs/operations/storage.md) before production use. -| Location | Purpose | Authoritative? | -| --- | --- | --- | -| Postgres | Users, accounts, secret references, jobs, matches, playlists, intelligence, health, audits | Yes, for application state | -| PostgreSQL cache table and bounded `/app/cache` media tier | Rebuildable metadata, search, lyrics, and artwork acceleration | No | -| `downloads` / `kept` / managed roots | Playable audio and related files | Yes, for media | -| `/app/state/backups` | Verified database backup artifacts and manifests | Copy these off the host | -| key-ring file | Keys used to open encrypted application secrets | Yes, back it up separately | +## Product map -A database backup does not contain your songs or encryption key ring. Back up those separately. +- **Home** shows current playback, listeners, health, storage, work, and recent activity. +- **Library** owns provider playlists, match review, cached audio, and kept audio. +- **Intelligence** owns listening history, imports, recommendations, automation, and the built-in AudioMuse connection. +- **Integrations** owns Services, encrypted Accounts, extension packages, health, and provider Routing. +- **Activity** explains completed and failed work with correlation details. +- **Settings** owns deployment-level behavior, matching, playback, cache policy, maintenance, backup, and recovery. -## Clients And Backends +## Capabilities -Allstarr supports Jellyfin clients and Subsonic/OpenSubsonic clients through the selected deployment surface. Client behavior varies, especially around search, offline indexing, playlists, and lyrics. See [client compatibility](docs/operations/client-compatibility.md) for the tested list and reporting checklist. +- Presents one selected Jellyfin or Subsonic/OpenSubsonic surface while relaying native backend behavior. +- Merges local results with configured metadata and playable providers. +- Matches one recording to a local item and multiple provider identities with reviewable evidence. +- Projects provider playlists as virtual views or materializes exact local matches into the backend without silently downloading unresolved entries. +- Routes streaming, download, lyrics, and artwork through typed, account-aware capabilities. +- Runs imports, matching, downloads, playlist changes, scrobbling, and other long work as durable inspectable jobs. +- Supports opt-in listening history and imports from Spotify Extended Streaming History, Last.fm, ListenBrainz, Koito, and Maloja exports. +- Builds explained recommendations from enabled sources and an optional self-hosted AudioMuse server configured inside Intelligence. +- Installs verified third-party provider extensions through an explicit registry, permission review, staged activation, and rollback boundary. + +Provider availability depends on connected accounts, optional sidecars, permissions, and health. Missing optional services reduce only the affected capability. + +## Optional services + +Spotify lyrics and Apple/GAMDL download support are explicit Compose profiles, not default dependencies. + +```bash +./allstarr.sh enable spotify-lyrics +./allstarr.sh install-apple x86_64 +./allstarr.sh up +``` + +The Apple profile requires a legally obtained compatible APK/APKM supplied by the operator. Allstarr does not distribute Apple binaries. Follow the [Apple provider](docs/operations/apple-download-provider.md) and [Spotify lyrics](docs/operations/spotify-lyrics-sidecar.md) guides. ## Documentation -- [Architecture](docs/architecture/overview.md) -- [Configuration](docs/operations/configuration.md) -- [Client compatibility](docs/operations/client-compatibility.md) -- [Storage operations](docs/operations/storage.md) -- [Deployment profiles and optional services](docs/operations/deployment-profiles.md) -- [Extension SDK](docs/extensions/sdk-v1.md) -- [Contributing](CONTRIBUTING.md) +| Need | Start here | +| --- | --- | +| Use the dashboard | [User guide](docs/user-guide.md) | +| Install and configure | [Configuration](docs/operations/configuration.md) | +| Back up, restore, or move | [Storage runbook](docs/operations/storage.md) | +| Check a client | [Client compatibility](docs/operations/client-compatibility.md) | +| Understand the system | [Architecture overview](docs/architecture/overview.md) | +| Build an extension | [Extension SDK](docs/extensions/sdk-v1.md) | +| Contribute code | [Contributing](CONTRIBUTING.md) | +| Guide a coding agent | [Agent guide](AGENTS.md) | -## Why “Allstarr”? - -The goal is to bring the useful parts of different music services into one library experience and let every provider be good at the part it actually does well. +The complete index is in [docs/README.md](docs/README.md). ## License diff --git a/agent_docs/latest_session_work.md b/agent_docs/latest_session_work.md deleted file mode 100644 index 25cd3535..00000000 --- a/agent_docs/latest_session_work.md +++ /dev/null @@ -1,79 +0,0 @@ -# Latest Session Work - -## AudioMuse built-in correction and truthful import receipts — deployed, 2026-08-20 - -- AudioMuse is a built-in Allstarr Intelligence integration, not an extension. Intelligence → Automation owns its self-hosted server URL, optional API token, and optional AudioMuse server selector. -- The new typed adapter covers health, recommendations, similarity, text/lyrics search, paths, blends, map pages, clustering playlists, and analysis jobs while retaining exact user/library provider-account scope. -- The configuration action no longer depends on provider readiness: an unconfigured or unhealthy AudioMuse connection can always be created or edited directly in Intelligence. -- Completed history imports are now non-interactive receipts instead of disabled selection rows. Overview shows the effective retention, and a completed receipt with zero currently retained listens explains that the original files must be re-imported. -- Revision `1edd625e6ae20c3eff5e29175a6a870b67fa731f` is pushed and deployed to both Allstarr stacks on `192.168.1.116`; both application containers are healthy on image `sha256:dfa13b6d908101fb7e0325ebd8c5f3b299948fa8d7898f4aad07def3f47a45ac`. -- Local verification: affected non-database .NET 68/68, Svelte diagnostics clean, WebUI unit 46/46, production build and budgets green, and focused responsive browser checks 3/3. The six PostgreSQL import tests were not run because `ALLSTARR_TEST_POSTGRES` was not configured; no storage code changed. -- Live browser verification: Intelligence → Automation exposes the built-in AudioMuse connection and its server URL, optional token, and optional music-server fields without an extension prerequisite. Intelligence → Import shows 20 completed receipts, no unusable include checkboxes, and a truthful re-import warning because no imported listens are currently retained. The browser console contains no errors. - -## Unlimited history display and retention — deployed, 2026-08-20 - -- Revision `218a703fe74180d835c7c2398b554891edcc13e4` is pushed and deployed to both Allstarr stacks on `192.168.1.116`; both clean checkouts and both application containers use that revision. -- Intelligence Overview and History now open on **All time** and omit `from`/`to` query bounds until the user chooses a finite or custom range. -- The backend accepts valid history reporting windows longer than ten years, eliminating `listening_history_period_invalid` for legitimate old imports. -- Retention remains separately controlled: `0` is still the default and means unlimited. Existing saved policies are not rewritten, and no history is deleted unless the user chooses a finite retention or explicitly clears/removes it. -- Verification: focused .NET contract 2/2, Svelte diagnostics clean, unit 46/46, production build and budgets green at 47.3 KiB initial JavaScript and 23.8 KiB CSS, and focused mobile/desktop browser checks 2/2. -- Live verification: both application containers are healthy on image `sha256:d717f94c2a15c97befff49f4c4d2ba46162bde1ba0945b4ef64d3915edbea127`; both internal readiness responses report PostgreSQL ready, both trusted-LAN WebUIs return HTTP 200, and neither startup log contains an error- or critical-level entry. - -## Superseded AudioMuse extension placement — deployed, 2026-08-20 - -- Revision `218a703f` first moved the AudioMuse connection task into Intelligence but incorrectly retained an extension prerequisite. The current local correction replaces that model with a built-in typed provider. -- The existing schema-driven `ConnectSourceDialog` remains the single encrypted account editor. -- Services still owns shared audience and diagnostics; Extensions no longer owns AudioMuse installation or permissions. -- Provider schema and accounts are fetched only when Automation is opened, avoiding extra requests on Intelligence Overview, History, Import, and Discover. -- Verification: Svelte diagnostics clean, unit 46/46, production build and budgets green at 47.3 KiB initial JavaScript and 23.8 KiB CSS, and focused mobile/desktop Intelligence plus Services browser flows 4/4. -- The implementation from `18c84037` is included in deployed revision `218a703f`. - -## Exact import undo and Spotify video exclusion — 2026-08-20 - -- Revision `e6e7a2dc59843732753af801ecf956fda63007c8` is pushed and deployed to both Allstarr stacks on `192.168.1.116`; both containers are healthy on image `sha256:d5603bf21c67fdfeab4c788952f0343a7448ec0b89dcf6a0b5f03964775ea030`. -- Completed imports now expose a confirmed **Undo import** action. Removal is scoped by tenant, user, backend, library, and exact import provenance, and deletes only that import's stored listens, delivery checkpoints, durable import record, and temporary artifact. Active imports must be cancelled first. -- Spotify `Streaming_History_Video_*` files are rejected before staging with guidance to choose `Streaming_History_Audio` JSON files. -- Verification: focused .NET/PostgreSQL 8/8, Svelte diagnostics clean, unit 46/46, production build and budgets green, and focused browser interaction 1/1. The disposable local PostgreSQL container was removed. Both live readiness endpoints are green, deployed UI assets contain the undo action, and startup logs contain no failure-level entries. - -## Unlimited listening history and truthful imports — 2026-08-19 - -- Revision `9336e6c7c9e62d2cb10312a20c2b2587b127d243` is pushed and deployed to both Allstarr stacks on `192.168.1.116`; both containers are healthy on image `sha256:2b7a5d3a827acd0ccaffd6291497457c4f0e87c8070230868fe410331863a20a`. -- Listening-history retention now supports `0` as unlimited. New policy defaults are unlimited, while existing saved policies are preserved until the user changes them. -- Import previews and apply jobs use the exact saved retention policy. Finite policies show rows outside retention before apply, and completed imports are loaded from durable storage after navigation or restart. -- Live verification showed the existing `joshp / Music` policy remains `10 years`, the new `Unlimited` option is available, and all prior completed Spotify import records are visible under Intelligence → Import. -- Shared disclosures now have explicit labels and consistent affordances; the mobile extension-permission dialog keeps confirmation and actions visible; trusted LAN extension installation no longer requires the remote-install switch. -- Verification: focused .NET/PostgreSQL 49/49, Svelte diagnostics clean, unit 46/46, production build and budgets green at 47.3 KiB initial JavaScript and 23.8 KiB CSS, and focused browser 5/5. The exact disposable PostgreSQL container was removed. - -## Delivered dashboard refinement — 2026-08-19 - -- Application revision `f951adef2d45ca1d2582ccf1a0e3f8d1b9940649` is pushed and deployed to both Allstarr stacks on `192.168.1.116`. -- The five post-delivery WebUI commits clarify Intelligence imports and controls, adopt the Material control-room system, streamline Home and mobile matching, align storage tables and filter labels, and keep nested segmented tabs from scrolling the page away from its heading. -- Current exact-source evidence: Svelte diagnostics clean, unit 46/46, production build and budgets green at 47.2 KiB initial JavaScript and 23.5 KiB CSS, and browser 93/93 without retries. -- Responsive light-theme screenshots were reviewed at 390×844 and 1280×800. The Integrations heading remains fully visible when the off-screen Extensions tab activates. -- The live follow-up smoke covered 12 desktop routes and 8 mobile routes with no overflow or console errors. It exposed stale durable mappings in Home's managed-audio count; Home now counts only mapped files that still exist, and its `0 cached · 0 kept` result agrees with both storage inventories. -- The focused storage regression lane passed 7/7. GitHub Actions run `32323295754` is green across WebUI, build/test, release-critical, format, Apple, Compose, and release-manifest jobs. -- Both application containers are healthy on image `sha256:ed0affbd62d1c177c3fbb5cfe9739a39602274aaff5cd8be211c19d56e1b208b`; PostgreSQL and all provider sidecars were left running and Navidrome was not modified. -- Reproducible dependencies, build output, and test artifacts are removed after verification to keep the checkout near 77 MiB. - -## Completed delivery — 2026-08-19 - -- Completed all phases of the rich-dashboard and provider-parity package and deployed application revision `e42f38deaa2047b8e4f3e9850e1bf09aad715efb` to both Jellyfin and Subsonic stacks. -- Matching now queries verified identities plus title+artist, title+album, title, artist, and album; deduplicates the union; and scores it through the existing decision engine. `Crush` by Selena Gomez & The Scene guards the weak-artist regression. -- The review dialog exposes every credible candidate, final and raw confidence, every component score, reasons, warnings, normalized titles, source/candidate ISRCs, artist overlap, album evidence, duration delta, route, and all provider IDs through an accessible disclosure. Manual provider search exposes the same available scoring evidence. -- Cached/Kept distinguish indexed, referenced, and diagnostic files. Cache-mode full streams publish only completed managed artifacts; unknown and interrupted files are never adopted. -- External relationships are provider-neutral in Jellyfin and Subsonic. Lyrics use source-native lookup, Odesli identity translation, and distinct configured fallbacks without fetching media merely for lyrics. -- The live Jellyfin kit exact-compares native music counts in addition to existing full native objects, native artwork bytes, dynamic external traversal, external playback/lyrics, Finer query-key file, Musiver playlist shape, Feishin-class headers, and WebSocket sessions. -- Replaced an unawaited download-sidecar test with a public, awaited PostgreSQL behavior test that proves the exact audio file, adjacent lyrics sidecar, and durable mapping are removed together. -- The final provider gateway fix publishes songs only from implementations with a usable streaming or download-backed route. Metadata-only Apple and Spotify extensions remain available for enrichment and lyrics without creating unplayable Jellyfin/Subsonic audio rows. -- Local release gates pass: WebUI check clean, unit 46/46, build/budgets green at 46.2 KiB initial JavaScript and 22.7 KiB CSS, browser 92/92; PostgreSQL fast lane 2,213/2,213; release-critical 104/104; Apple gateway 20/20; format clean; three Compose profiles valid; deterministic Subsonic 5/5; release-manifest self-tests 2/2; shell syntax and diff checks clean. -- The fast PostgreSQL lane has no test above three seconds. Release-critical deliberately retains the measured migration, rollback, lineage, state-transfer, backup/restore, clone-pool, and 10,000-track contracts. -- Local sandboxed .NET test processes cannot create MSBuild named pipes, so the final fast lane ran with approved escalation against the tmpfs-only OrbStack PostgreSQL container `allstarr-codex-fastlane-b9c962`. The exact container was removed after verification. -- GitHub Actions run `32286289552` is green for the exact deployed revision across every required job. -- Live Jellyfin qualification passed 181/181 provider/client checks, 194/194 actor-bound checks including the exact private throwaway playlist lifecycle and cleanup, and 5/5 WebSocket checks. -- Live OpenSubsonic/Navidrome qualification passed 67/67 checks. Browser-only responsive qualification passed 27/27 route/viewport checks with no console errors. -- Both server checkouts are clean at `e42f38de`; both app containers are healthy on image `sha256:dc0bb67a64d747009a0776c8ff242854cc38e4215c6a180894faf731568ba4fc`. -- The disposable PostgreSQL container and temporary redacted live-report directories were removed. No unrelated service or provider playlist was changed. - -## Next entry point - -This package is complete. Preserve the unrelated dirty files and begin a new scoped package for any newly observed behavior. diff --git a/agent_docs/project_progress.md b/agent_docs/project_progress.md deleted file mode 100644 index 03f46808..00000000 --- a/agent_docs/project_progress.md +++ /dev/null @@ -1,164 +0,0 @@ -# Project Progress - -## Active package — rich control dashboard and provider parity - -### Outcome - -Turn the existing administrator WebUI into one coherent, data-rich music control dashboard while preserving native Jellyfin/Subsonic fidelity and the existing provider-neutral backend owners. - -This is a replacement and consolidation package. Do not create parallel matching, routing, scrobbling, caching, scheduling, or recommendation systems. Delete each old UI surface after its replacement passes. - -### Verified delivery state - -- Application revision `218a703fe74180d835c7c2398b554891edcc13e4` is pushed and deployed to both Jellyfin and Subsonic stacks. -- Both server checkouts are clean at that revision. Both app containers use image `sha256:d717f94c2a15c97befff49f4c4d2ba46162bde1ba0945b4ef64d3915edbea127` and are healthy; both readiness responses report PostgreSQL ready, both trusted-LAN WebUIs return HTTP 200, and startup logs contain no error- or critical-level entries. -- GitHub Actions run `32323295754` is green across build/test, release-critical, Apple, WebUI, format, Compose, and release-manifest jobs. -- Current WebUI baseline is 47.3 KiB initial JavaScript and 23.8 KiB CSS; unit is 46/46. The import, retention, disclosure, audio-quality, and extension-permission browser slice is 5/5. -- Completed listening-history imports can be undone by exact import provenance; this removes only their stored listens, checkpoints, saved record, and temporary artifact. Spotify `Streaming_History_Video_*` exports are rejected before staging. -- Canvas UI remains blocked by MIT plus Commons Clause redistribution terms. A focused replacement review found no permissive renderer that would delete more code than it adds, so use Svelte 5, shadcn-svelte, Bits UI, Lucide, CSS, SVG, and native Web Animations. -- Koito `a079fa693569d21e03c00df163f20ac5e137c490`, Explo `4fc75874de691ff1e26b10d88b859cfac8ee2992`, and Multi-Scrobbler `bc28de66b14db1c99eb79ad75d1cdf4c9dfff7cc` are MIT reference inputs. Adapt useful behavior and presentation into existing owners; do not import their application architectures. -- LAN/VPN access was enabled only after the local release gates passed, then used for exact-revision deployment and qualification. - -### Product rules - -- Show every useful supported fact, using overview → expansion → detail so primary screens remain scannable. -- Never display invented popularity, media, listening, readiness, latency, or provider facts. -- Work only from observed production behavior, captured client shapes, public protocol contracts, and donor workflows deliberately adopted here. Do not build speculative edge cases. -- Preserve authentication, authorization, secret redaction, destructive-operation, migration, data-loss, and protocol safety coverage. -- Matched native items relay the complete backend object unchanged. Virtual objects expose every available field with stable, internally consistent relationships. - -## Phase 0 — package and donor baseline - -- [x] Record exact donor UI paths, adaptation boundaries, licenses, and destination owners in the reference ledger. -- [x] Capture current route request counts, response sizes, render timings, long tasks, bundle sizes, and table geometry. -- [x] Record the old Signal Boot implementation and current shared UI owners before editing. - -Acceptance: reproducible before-state evidence exists and no donor code enters production without provenance. - -## Phase 1 — shared shell, tables, motion, and visual language - -- [x] Restore Signal Boot for real authentication/bootstrap work with no artificial delay, reduced motion, and retryable failure state. -- [x] Standardize desktop rows, artwork, numeric columns, actions, gutters, and mobile cards across playlists, Integrations, Activity, Mappings, Cached, Kept, and Intelligence history. -- [x] Centralize provider colors/icons so Jellyfin, Deezer, Apple, Spotify, YouTube, and extensions remain distinguishable. -- [x] Add restrained native motion for focused artwork, mapping decisions, delivered scrobbles, and expanding detail; never reorder content under the pointer. -- [x] Delete superseded per-page table, loading, icon, and animation CSS. - -Acceptance: geometry is aligned at desktop/tablet/mobile, keyboard and reduced-motion behavior pass, and no renderer dependency is added. - -## Phase 2 — unified Integrations hub - -- [x] Rename Sources to Integrations with Services, Accounts, Extensions, and Routing tabs. -- [x] Group logical services with their built-in, extension, sidecar, backend, and account implementations. -- [x] Put configuration beside the exact implementation; keep lifecycle/permissions in Extensions and secrets/audience in Accounts. -- [x] Move quality, provider ordering, local preference, and extension penalty to Routing; leave only app-wide system/maintenance controls in Settings. -- [x] Add stable service/implementation projection facts and deep links; redirect old Sources/Accounts/Extensions routes. -- [x] Show capability coverage, readiness, latest probe, CTS, managed p95, last failure, account scope, version, and routing priority without ambiguous blanks. -- [x] Delete duplicate provider/account/extension/settings forms after the replacement passes. - -Acceptance: one route owns discovery, setup, health, accounts, configuration, extensions, and routing without duplicating durable state. - -## Phase 3 — Intelligence and imports - -- [x] Replace the five equal tabs with Overview, History & Imports, Discover, and Automation. -- [x] Adapt Koito period controls, heatmap, totals, streaks, top music, history density, and recap presentation. -- [x] Adapt Explo recommendation, generated-playlist, schedule, run-now, next-run, and prior-run presentation. -- [x] Redesign imports as multi-file drag/drop with automatic previews, valid files selected by default, one batch summary, per-file detail, and Add all ready files. -- [x] Add bounded grouped daily/monthly and source/provider/client aggregates using the existing listening-occurrence authority. -- [x] Refresh Overview, History, and Discover after imports and replace generic prerequisites with exact Integrations deep links. - -Acceptance: imported and live history agree, recommendations use saved data, and rich analytics require no second database. - -## Phase 4 — Home and Activity - -- [x] Add one aggregate Home read endpoint composed from existing owners while retaining individual endpoints for compatibility. -- [x] Show active listeners, playlist/playable/unresolved totals, cache/kept usage, playable-source health, jobs, scrobbles, recent trend, top music, and actionable setup problems. -- [x] Expand now playing with user/client/device, actual implementation, cache route, progress, scrobble threshold, and per-target delivery state. -- [x] Reuse existing delivery checkpoints/activity state; add no second persistence or realtime channel. -- [x] Adapt Multi-Scrobbler status, now-playing, retry, and per-target outcome patterns. -- [x] Redesign Activity with colored icons, provider accents, rich filters, concise summaries, retry/auth state, and expandable redacted details. - -Acceptance: Home loads through the aggregate plus now-playing update, and every stat and scrobble mark reflects durable state. - -## Phase 5 — Mappings, Cached, and Kept - -- [x] Search verified identities first, then run title+artist, title+album, title, artist, and album queries and score the deduplicated union. -- [x] Prevent weak artist mismatches from becoming automatic suggestions and keep every credible candidate accessible with complete scoring evidence. -- [x] Bump the matcher revision and use the existing preview/rematch job for unresolved, suggested, ambiguous, and stale decisions only. -- [x] Add the Selena Gomez `Crush` regression and protect accepted, pinned, rejected, and manual decisions. -- [x] Present Mappings as All, Review, Unresolved, and History with full score/identity/route details on expansion; accepted rows leave Review immediately. -- [x] Add cache/kept totals, provider/lifecycle facts, last access, expiry, quality, publication, references, filters, and previewed bulk actions. -- [x] Reindex only completed files with valid Allstarr ownership metadata; show unknown files as diagnostics and never adopt/delete them automatically. - -Acceptance: automatic suggestions are credible, manual decisions remain authoritative, and Cached/Kept agree with managed-file owners. - -## Phase 6 — external object, streaming, and lyrics parity - -- [x] Use one provider-neutral external relationship projection for primary albums, credited tracks, and Appears On in Jellyfin and Subsonic. -- [x] Keep external IDs, artwork, relationships, pagination, and traversal stable and internally consistent. -- [x] Discover and qualify every ready streaming or download-backed implementation dynamically. -- [x] Verify metadata → PlaybackInfo → bounded audio → range/cancellation → artwork → lyrics while recording the selected implementation/account. -- [x] Preserve configured quality and return a truthful failure instead of silently substituting another track. -- [x] Run source-native lyrics first, then Odesli identity translation and distinct configured fallbacks without downloading media merely to find lyrics. - -Acceptance: native objects remain exact, virtual objects satisfy the full client contract, unrelated Appears On albums fail, and real external playback failures are classified. - -## Phase 7 — performance, release, and live delivery - -- [x] Keep initial JS/CSS within existing budgets and reject unexplained growth above 10%; keep route chunks under 100 KiB gzip. -- [x] Use grouped queries, lazy routes/artwork, keyed row updates, and off-screen content visibility; add no charting/rendering/virtualization framework. -- [x] Expand the Jellyfin kit to union-key native comparison, native artwork/count parity, dynamic external traversal, every ready playable provider, and checked Finer/Feishin/Musiver request shapes. -- [x] Run focused owner tests, WebUI check/unit/build/budget/E2E, both PostgreSQL lanes, format, Apple gateway, Compose, shell, and deterministic protocol kits. -- [x] Ask the user to enable LAN/VPN only after the exact final SHA is locally green. -- [x] After exact-revision authorization, push, deploy, run bounded browser/provider/client qualification, and record the deployed SHA. - -Acceptance: local release evidence is complete before LAN access, no unrun gate is called passing, and live failures are separated into provider/configuration versus Allstarr defects. - -### Local release evidence - -- WebUI: check clean, unit 46/46, build and budgets green at 46.2 KiB initial JavaScript and 22.7 KiB CSS, browser 92/92 without retries. -- Backend: PostgreSQL fast lane 2,213/2,213 and release-critical lane 104/104; the slow release-critical tests remain protected migration, lineage, state-transfer, backup, clone-pool, and 10,000-track contracts. -- Supporting gates: format clean, Apple gateway 20/20, all three Compose profiles valid, deterministic Subsonic 5/5, release-manifest self-tests 2/2, shell syntax clean. -- The live Jellyfin kit now exact-compares native music counts in addition to existing full native objects, artwork bytes, dynamic external traversal, playback, Finer, Feishin, Musiver, and WebSocket contracts. - -### Live release evidence - -- Jellyfin provider/client qualification: 181 checks, zero failures; Apple GAMDL, Deezer, and YouTube Music delivered bounded audio. Metadata/lyrics-only extensions are no longer advertised as playable tracks. -- Actor-bound Jellyfin qualification: 194 checks, zero failures; the exact private throwaway playlist passed create, rename, add, reorder, remove, share, unshare, mix, delete, and direct 404 cleanup verification. -- Jellyfin WebSocket qualification: 5/5 for header authentication, bidirectional frames, Sessions delivery, and invalid-token rejection. -- OpenSubsonic/Navidrome qualification: 67 checks, zero failures across password/token auth, XML/JSON, playlists, browse/search, artwork, lyrics, exact range bytes, concurrency, cancellation, and direct-vs-Allstarr shape parity. -- Browser-only responsive qualification: 27/27 route/viewport checks across desktop, tablet, and mobile with no overflow, crash state, missing main heading, or console error. -- External sources without range support were retained as truthful bounded progressive delivery rather than falsely advertising seek support. - -### Post-delivery WebUI refinement — delivered - -- Application revision `f951adef2d45ca1d2582ccf1a0e3f8d1b9940649` includes the Material control-room visual system, clearer Intelligence import controls, denser Home/review/storage workflows, truthful empty-value filter labels, horizontal-only segmented-tab activation, and truthful managed-audio totals that ignore missing mapped files. -- The segmented-tab fix prevents nested mobile tabs from moving the document vertically. The browser regression waits for mounted content and settled layout, then proves zero document scroll and a fully visible page heading. -- Exact-revision WebUI evidence: Svelte diagnostics clean, unit 46/46, production build and budgets green at 47.2 KiB initial JavaScript and 23.5 KiB CSS, browser 93/93 without retries, design detector clean, and responsive light-theme screenshots reviewed at 390×844 and 1280×800. -- Two proposed shared-control/artwork changes were measured and discarded before commit: loading a Bits UI checkbox into the root shell raised initial JavaScript to 69.6 KiB, and blank provider logos were traced to the test fixture's intentionally empty SVG rather than production assets. -- Live browser qualification covered 12 desktop routes and 8 mobile routes with no document overflow, console error, or heading displacement. Home now reports `0 cached · 0 kept`, matching the live Cached and Kept inventories. - -### Post-delivery AudioMuse setup placement — built-in correction deployed - -- [x] Register AudioMuse as a built-in Intelligence and health capability; it is not an extension package. -- [x] Keep its self-hosted server URL, optional API token, and optional multi-server selector in Intelligence → Automation. -- [x] Reuse the encrypted Source account dialog and durable account store; add no second credential store. -- [x] Perform real account-bound `/api/health` checks and route recommendation, search, path, blend, map, clustering-playlist, and analysis calls through the typed built-in adapter. -- [x] Keep Services available for shared account audience and diagnostics without making Extensions a prerequisite. -- [x] Verify the correction locally: affected non-database .NET 68/68, Svelte diagnostics clean, WebUI unit 46/46, production build and budgets green, and focused mobile/desktop browser checks 3/3. -- [x] Commit and push revision `1edd625e6ae20c3eff5e29175a6a870b67fa731f`, deploy it to both Allstarr stacks, and verify both application containers healthy on image `sha256:dfa13b6d908101fb7e0325ebd8c5f3b299948fa8d7898f4aad07def3f47a45ac`. -- [x] Verify live in the signed-in browser that Intelligence → Automation exposes **Connect AudioMuse**, its form contains the server URL, optional token, and optional music-server selector, and no extension prerequisite or link remains. -- [x] Verify live import truthfulness: 20 completed receipts use “added when imported,” no disabled include checkboxes remain, and the zero-retained state directs the user to re-import the original files. The browser console contains no errors. - -### Post-delivery unlimited history range — deployed - -- [x] Default Overview and History reporting to **All time**, with no `from`/`to` bounds sent until the user chooses a finite or custom range. -- [x] Remove the artificial ten-year reporting-window rejection while retaining ordered-date validation. -- [x] Keep listening retention defaulted to `0` (unlimited); do not migrate saved user choices or delete retained history. -- [x] Preserve finite 30-day, 90-day, one-year, and custom reporting choices. -- [x] Verify the default/unbounded contracts: focused .NET 2/2, Svelte diagnostics clean, unit 46/46, production build and budgets green, and focused responsive browser checks 2/2. -- [x] Commit the implementation as `218a703f`. -- [x] Push and deploy the exact revision after authorization. - -## Next action - -Continue the remaining active checklist from the next open implementation gate; the AudioMuse placement and truthful import-receipt correction are deployed and live-verified. diff --git a/allstarr.Tests/Jobs/OperationalLogRegressionContractTests.cs b/allstarr.Tests/Jobs/OperationalLogRegressionContractTests.cs index 0e6fde3c..b23c43e0 100644 --- a/allstarr.Tests/Jobs/OperationalLogRegressionContractTests.cs +++ b/allstarr.Tests/Jobs/OperationalLogRegressionContractTests.cs @@ -21,7 +21,7 @@ public sealed class OperationalLogRegressionContractTests public void EndpointUsage_UsesRetentionBoundedAuditEventsWithoutCsvFiles() { var helper = File.ReadAllText(FindRepositoryFile( - "allstarr", "Controllers", "Helpers.cs")); + "allstarr", "Controllers", "JellyfinController.Helpers.cs")); var diagnostics = File.ReadAllText(FindRepositoryFile( "allstarr", "Controllers", "DiagnosticsController.cs")); var audit = File.ReadAllText(FindRepositoryFile( diff --git a/allstarr.Tests/Storage/Phase4PersistenceServiceTests.cs b/allstarr.Tests/Playlists/PlaylistPersistenceServiceTests.cs similarity index 99% rename from allstarr.Tests/Storage/Phase4PersistenceServiceTests.cs rename to allstarr.Tests/Playlists/PlaylistPersistenceServiceTests.cs index 57319263..92657d74 100644 --- a/allstarr.Tests/Storage/Phase4PersistenceServiceTests.cs +++ b/allstarr.Tests/Playlists/PlaylistPersistenceServiceTests.cs @@ -10,7 +10,7 @@ using Microsoft.EntityFrameworkCore; namespace allstarr.Tests; -public sealed class Phase4PersistenceServiceTests : IAsyncLifetime +public sealed class PlaylistPersistenceServiceTests : IAsyncLifetime { private PostgresTestDatabase _database = null!; private TestDbContextFactory _factory = null!; diff --git a/allstarr.Tests/Protocols/SubsonicProtocolAdapterTests.cs b/allstarr.Tests/Protocols/SubsonicProtocolAdapterTests.cs index 855e10cf..fbd48d38 100644 --- a/allstarr.Tests/Protocols/SubsonicProtocolAdapterTests.cs +++ b/allstarr.Tests/Protocols/SubsonicProtocolAdapterTests.cs @@ -148,7 +148,7 @@ public sealed class SubsonicProtocolAdapterTests public void ControllerRecordsEachRepeatedFavoriteTrackInsteadOfACommaJoinedId() { var controller = File.ReadAllText(FindRepositoryFile( - "allstarr", "Controllers", "SubSonicController.cs")); + "allstarr", "Controllers", "SubsonicController.cs")); Assert.Contains("parameters.GetAllValues(\"id\")", controller, StringComparison.Ordinal); Assert.DoesNotContain("var itemId = parameters.GetValueOrDefault(\"id\", \"\");\n if (!string.IsNullOrWhiteSpace(itemId))", controller, StringComparison.Ordinal); diff --git a/allstarr.Tests/Storage/DurableStateTransferServiceTests.cs b/allstarr.Tests/Storage/DurableStateTransferServiceTests.cs index 651b2a46..832cf947 100644 --- a/allstarr.Tests/Storage/DurableStateTransferServiceTests.cs +++ b/allstarr.Tests/Storage/DurableStateTransferServiceTests.cs @@ -1905,7 +1905,7 @@ public sealed class DurableStateTransferServiceTests : IAsyncLifetime [Theory] [InlineData("canonical_recordings")] [InlineData("provider_track_identities")] - public async Task Import_RejectsTargetContainingOnlyPhase2IdentityState(string table) + public async Task Import_RejectsTargetContainingOnlyIncompleteIdentityState(string table) { var artifact = await _service.ExportAsync( Path.Combine(_root, "transfers"), diff --git a/allstarr.Tests/Storage/Phase4DurableModelTests.cs b/allstarr.Tests/Storage/LibraryPlaylistStorageModelTests.cs similarity index 99% rename from allstarr.Tests/Storage/Phase4DurableModelTests.cs rename to allstarr.Tests/Storage/LibraryPlaylistStorageModelTests.cs index 7cccb4ab..bd658a43 100644 --- a/allstarr.Tests/Storage/Phase4DurableModelTests.cs +++ b/allstarr.Tests/Storage/LibraryPlaylistStorageModelTests.cs @@ -4,7 +4,7 @@ using Microsoft.EntityFrameworkCore; namespace allstarr.Tests; -public sealed class Phase4DurableModelTests +public sealed class LibraryPlaylistStorageModelTests { [Fact] public async Task PostgresModel_PersistsScopedMatchAndOrderedPlaylistEvidence() diff --git a/allstarr.Tests/Storage/MediaCacheKeyNamespaceContractTests.cs b/allstarr.Tests/Storage/MediaCacheKeyNamespaceContractTests.cs index f283e9bb..1c697644 100644 --- a/allstarr.Tests/Storage/MediaCacheKeyNamespaceContractTests.cs +++ b/allstarr.Tests/Storage/MediaCacheKeyNamespaceContractTests.cs @@ -6,7 +6,7 @@ public sealed class MediaCacheKeyNamespaceContractTests [ Path.Combine("allstarr", "Controllers", "JellyfinController.cs"), Path.Combine("allstarr", "Controllers", "JellyfinController.PlaylistHandler.cs"), - Path.Combine("allstarr", "Controllers", "SubSonicController.cs"), + Path.Combine("allstarr", "Controllers", "SubsonicController.cs"), Path.Combine("allstarr", "Services", "Jellyfin", "JellyfinProxyService.cs") ]; diff --git a/allstarr.sh b/allstarr.sh index a825a167..b9d1e45b 100755 --- a/allstarr.sh +++ b/allstarr.sh @@ -117,7 +117,7 @@ prepare_apple() { fi done fi - [[ -n "$input" && ( -f "$input" || -d "$input" ) ]] || die "no staged Apple package found; upload an .apk/.apkm in Sources > Apple download first" + [[ -n "$input" && ( -f "$input" || -d "$input" ) ]] || die "no staged Apple package found; upload an .apk/.apkm in Integrations > Services > Apple Music – GAMDL first" case "$arch" in x86_64) ;; arm64-v8a) runtime=linux/arm64 ;; diff --git a/allstarr/Controllers/Helpers.cs b/allstarr/Controllers/JellyfinController.Helpers.cs similarity index 99% rename from allstarr/Controllers/Helpers.cs rename to allstarr/Controllers/JellyfinController.Helpers.cs index f853d11f..03deedd2 100644 --- a/allstarr/Controllers/Helpers.cs +++ b/allstarr/Controllers/JellyfinController.Helpers.cs @@ -9,8 +9,6 @@ namespace allstarr.Controllers; public partial class JellyfinController { - #region Helpers - /// /// Helper to handle proxy responses with proper status code handling. /// @@ -340,6 +338,4 @@ public partial class JellyfinController return (item, finalScore); }).ToList(); } - - #endregion } diff --git a/allstarr/Controllers/SubSonicController.cs b/allstarr/Controllers/SubsonicController.cs similarity index 99% rename from allstarr/Controllers/SubSonicController.cs rename to allstarr/Controllers/SubsonicController.cs index abbcaf61..9ce34d69 100644 --- a/allstarr/Controllers/SubSonicController.cs +++ b/allstarr/Controllers/SubsonicController.cs @@ -123,7 +123,7 @@ public partial class SubsonicController : ControllerBase HttpContext.RequestAborted); } - // Extract all parameters (query + body) + // Reuse the authenticated query and form projection when available. private async Task ExtractAllParameters() { if (HttpContext.Items.TryGetValue(SubsonicAuthFilter.RequestParametersItemKey, out var value) && diff --git a/allstarr/Core/Favorites/FavoriteModelConfiguration.cs b/allstarr/Core/Favorites/FavoriteModelConfiguration.cs index e8f83455..1307c91c 100644 --- a/allstarr/Core/Favorites/FavoriteModelConfiguration.cs +++ b/allstarr/Core/Favorites/FavoriteModelConfiguration.cs @@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore; namespace allstarr.Core.Favorites; -/// Phase 6 model slice. The consolidated Phase 6 migration wires this once all lanes land. +/// Configures durable favorite state, policy, and delivery entities. public static class FavoriteModelConfiguration { public static void Configure(ModelBuilder modelBuilder) diff --git a/allstarr/Core/ManagedFiles/ManagedFileOwnershipEntity.cs b/allstarr/Core/ManagedFiles/ManagedFileOwnershipEntity.cs index c2276ecd..a000f3af 100644 --- a/allstarr/Core/ManagedFiles/ManagedFileOwnershipEntity.cs +++ b/allstarr/Core/ManagedFiles/ManagedFileOwnershipEntity.cs @@ -1,7 +1,7 @@ namespace allstarr.Core.ManagedFiles; // Persistence shape is intentionally separate from the immutable placement result. -// Phase 6 migration wiring is consolidated by the storage owner. +// Managed-file ownership remains part of the shared durable storage model. public sealed class ManagedFileOwnershipEntity { public Guid Id { get; set; } diff --git a/allstarr/Core/ManagedFiles/ManagedFileOwnershipModelConfiguration.cs b/allstarr/Core/ManagedFiles/ManagedFileOwnershipModelConfiguration.cs index 2f315d04..af5bacbb 100644 --- a/allstarr/Core/ManagedFiles/ManagedFileOwnershipModelConfiguration.cs +++ b/allstarr/Core/ManagedFiles/ManagedFileOwnershipModelConfiguration.cs @@ -5,7 +5,7 @@ namespace allstarr.Core.ManagedFiles; public static class ManagedFileOwnershipModelConfiguration { - // Call from AllstarrDbContext.OnModelCreating during the consolidated Phase 6 migration. + // Called once from AllstarrDbContext.OnModelCreating. public static void ConfigureManagedFileOwnership(this ModelBuilder modelBuilder) { modelBuilder.Entity(entity => diff --git a/allstarr/Core/Playlists/Phase4PersistenceServices.cs b/allstarr/Core/Playlists/PlaylistPersistenceService.cs similarity index 99% rename from allstarr/Core/Playlists/Phase4PersistenceServices.cs rename to allstarr/Core/Playlists/PlaylistPersistenceService.cs index f1e82b2b..407b09a9 100644 --- a/allstarr/Core/Playlists/Phase4PersistenceServices.cs +++ b/allstarr/Core/Playlists/PlaylistPersistenceService.cs @@ -117,6 +117,7 @@ public interface IPlaylistPersistenceService Task RecordRunAsync(ProtocolExecutionContext context, Guid linkId, PlaylistRunInput input, IReadOnlyList results, CancellationToken cancellationToken = default); } +/// Owns durable playlist links, source snapshots, previews, and sync-run evidence. public sealed class PlaylistPersistenceService : IPlaylistPersistenceService { private static readonly JsonSerializerOptions PreviewJson = new() { PropertyNameCaseInsensitive = true }; diff --git a/allstarr/Core/Storage/AllstarrDbContext.Phase4.cs b/allstarr/Core/Storage/AllstarrDbContext.LibraryPlaylists.cs similarity index 99% rename from allstarr/Core/Storage/AllstarrDbContext.Phase4.cs rename to allstarr/Core/Storage/AllstarrDbContext.LibraryPlaylists.cs index a378d272..4f6a8952 100644 --- a/allstarr/Core/Storage/AllstarrDbContext.Phase4.cs +++ b/allstarr/Core/Storage/AllstarrDbContext.LibraryPlaylists.cs @@ -4,7 +4,7 @@ namespace allstarr.Core.Storage; public sealed partial class AllstarrDbContext { - private static void ConfigurePhase4LibraryAndPlaylists(ModelBuilder modelBuilder) + private static void ConfigureLibraryAndPlaylists(ModelBuilder modelBuilder) { ConfigureLibraryAndMatching(modelBuilder); ConfigurePlaylistSchedules(modelBuilder); diff --git a/allstarr/Core/Storage/AllstarrDbContext.Phase6Enrichment.cs b/allstarr/Core/Storage/AllstarrDbContext.MetadataEnrichment.cs similarity index 96% rename from allstarr/Core/Storage/AllstarrDbContext.Phase6Enrichment.cs rename to allstarr/Core/Storage/AllstarrDbContext.MetadataEnrichment.cs index 82a79a57..0902a70f 100644 --- a/allstarr/Core/Storage/AllstarrDbContext.Phase6Enrichment.cs +++ b/allstarr/Core/Storage/AllstarrDbContext.MetadataEnrichment.cs @@ -8,8 +8,7 @@ public sealed partial class AllstarrDbContext public DbSet MetadataEnrichmentPlans => Set(); public DbSet MetadataEnrichmentApplications => Set(); - // Called by the consolidated Phase 6 model hook. Kept separate so the Phase 6 migration can be generated once. - internal static void ConfigurePhase6Enrichment(ModelBuilder modelBuilder) + internal static void ConfigureMetadataEnrichment(ModelBuilder modelBuilder) { modelBuilder.Entity(entity => { diff --git a/allstarr/Core/Storage/AllstarrDbContext.cs b/allstarr/Core/Storage/AllstarrDbContext.cs index 5d455f88..76a4ed59 100644 --- a/allstarr/Core/Storage/AllstarrDbContext.cs +++ b/allstarr/Core/Storage/AllstarrDbContext.cs @@ -83,13 +83,13 @@ public sealed partial class AllstarrDbContext(DbContextOptions tenants, IReadOnlyCollection users, IReadOnlyCollection backendIdentities, @@ -978,7 +978,7 @@ public sealed class DurableStateTransferService (!secretById.TryGetValue(eventCredential, out var eventSecret) || eventSecret.TenantId != favoriteEvent.TenantId || eventSecret.RevokedAt != null) || !IsRequiredText(favoriteEvent.EventKey, 64) || !IsRequiredText(favoriteEvent.CorrelationId, 100) || !IsJsonObject(favoriteEvent.PolicySnapshotJson, 64 * 1024)) - RejectPhase6Archive("a favorite event is malformed or crosses its tenant, owner, or job boundary"); + RejectManagedMediaArchive("a favorite event is malformed or crosses its tenant, owner, or job boundary"); } foreach (var action in favoriteActions) @@ -987,7 +987,7 @@ public sealed class DurableStateTransferService favoriteEvent.TenantId != action.TenantId || favoriteEvent.OwnerUserId != action.OwnerUserId || !ValidOwner(action.TenantId, action.OwnerUserId) || !Enum.IsDefined(action.State) || !IsRequiredText(action.ActionType, 100) || !IsRequiredText(action.IdempotencyKey, 300) || action.AttemptCount < 0) - RejectPhase6Archive("a favorite action is malformed or crosses its event scope"); + RejectManagedMediaArchive("a favorite action is malformed or crosses its event scope"); } foreach (var state in favoriteStates) @@ -996,7 +996,7 @@ public sealed class DurableStateTransferService favoriteEvent.TenantId != state.TenantId || favoriteEvent.OwnerUserId != state.OwnerUserId || !ValidOwner(state.TenantId, state.OwnerUserId) || state.Protocol is not ("jellyfin" or "subsonic") || !IsRequiredText(state.BackendInstanceId, 200) || !IsRequiredText(state.ItemId, 500)) - RejectPhase6Archive("a favorite state is malformed or crosses its last-event scope"); + RejectManagedMediaArchive("a favorite state is malformed or crosses its last-event scope"); } var policyKeys = new HashSet<(Guid, Guid?, FavoriteActionPolicyScope, string, string, string?)>(); @@ -1021,7 +1021,7 @@ public sealed class DurableStateTransferService !IsOptionalText(policy.LibraryScopeId, 300) || policy.CreatedAt == default || policy.UpdatedAt < policy.CreatedAt || policy.Revision <= 0 || !policyKeys.Add((policy.TenantId, policy.OwnerUserId, policy.Scope, policy.Protocol, policy.BackendInstanceId, policy.LibraryScopeId))) - RejectPhase6Archive("a favorite action policy is malformed, duplicated, or crosses its tenant, user, actor, or backend scope"); + RejectManagedMediaArchive("a favorite action policy is malformed, duplicated, or crosses its tenant, user, actor, or backend scope"); } foreach (var file in managedFiles) @@ -1040,7 +1040,7 @@ public sealed class DurableStateTransferService !IsRequiredText(file.ScopeKey, 1000) || !IsOptionalText(file.LibraryScopeId, 300) || !IsSafeManagedPath(file.TargetRootPath, file.CanonicalPath) || file.SourceJobId is { } jobId && !ValidJob(jobId, file.TenantId, file.OwnerUserId)) - RejectPhase6Archive("a managed file is malformed, unsafe, or crosses its tenant, owner, or job boundary"); + RejectManagedMediaArchive("a managed file is malformed, unsafe, or crosses its tenant, owner, or job boundary"); } var referenceKeys = new HashSet<(Guid ManagedFileId, string ReferenceKey)>(); @@ -1055,7 +1055,7 @@ public sealed class DurableStateTransferService reference.CreatedAt < file!.CreatedAt || (reference.ReleasedAt is { } releasedAt && releasedAt < reference.CreatedAt) || reference.Revision <= 0 || !referenceKeys.Add((reference.ManagedFileId, reference.ReferenceKey))) - RejectPhase6Archive("a managed file reference is malformed, repeated, or crosses its file ownership scope"); + RejectManagedMediaArchive("a managed file reference is malformed, repeated, or crosses its file ownership scope"); if (reference.ReleasedAt is null) activeReferenceCounts[reference.ManagedFileId] = activeReferenceCounts.GetValueOrDefault(reference.ManagedFileId) + 1; @@ -1064,7 +1064,7 @@ public sealed class DurableStateTransferService foreach (var file in managedFiles) { if (file.ReferenceCount != activeReferenceCounts.GetValueOrDefault(file.Id)) - RejectPhase6Archive("a managed file reference count does not match its durable active references"); + RejectManagedMediaArchive("a managed file reference count does not match its durable active references"); } foreach (var plan in enrichmentPlans) @@ -1076,7 +1076,7 @@ public sealed class DurableStateTransferService !IsNormalizedSha256(plan.Fingerprint) || !IsJsonArray(plan.SourceRevisionsJson, 1024 * 1024) || !IsJsonArray(plan.DecisionsJson, 1024 * 1024) || !IsJsonObject(plan.TagsJson, 1024 * 1024) || !IsJsonObject(plan.PathValuesJson, 1024 * 1024)) - RejectPhase6Archive("a metadata enrichment plan is malformed or crosses its file, tenant, owner, or job boundary"); + RejectManagedMediaArchive("a metadata enrichment plan is malformed or crosses its file, tenant, owner, or job boundary"); } var applicationKeys = new HashSet<(Guid, Guid, Guid, string)>(); @@ -1091,7 +1091,7 @@ public sealed class DurableStateTransferService (!IsRequiredText(application.ErrorCode, 100) || !IsRequiredText(application.SafeErrorMessage, 1000)) || application.State != MetadataEnrichmentApplicationState.Failed && (application.ErrorCode != null || application.SafeErrorMessage != null)) - RejectPhase6Archive("a metadata enrichment application is malformed or crosses its plan scope"); + RejectManagedMediaArchive("a metadata enrichment application is malformed or crosses its plan scope"); } } @@ -1515,7 +1515,7 @@ public sealed class DurableStateTransferService workspace.CreatedAt == default || workspace.Revision < 0 || !publicWorkspaceIds.Add(workspace.WorkspaceId) || !workspaceKeys.Add((workspace.TenantId, workspace.DurableJobId, workspace.ProviderId, workspace.ProviderAccountId, workspace.IdempotencyKey))) - RejectPhase6Archive("a provider download workspace is malformed, repeated, or crosses its tenant, owner, job, provider-account, or library scope"); + RejectManagedMediaArchive("a provider download workspace is malformed, repeated, or crosses its tenant, owner, job, provider-account, or library scope"); } var artifactIdentities = new HashSet<(Guid, string)>(); @@ -1543,7 +1543,7 @@ public sealed class DurableStateTransferService artifact.CreatedAt == default || artifact.VerifiedAt < artifact.CreatedAt || artifact.Revision < 0 || !artifactIdentities.Add((artifact.WorkspaceRecordId, artifact.ProviderArtifactId)) || !jobProviderKeys.Add((artifact.TenantId, artifact.DurableJobId, artifact.ProviderId))) - RejectPhase6Archive("a provider download artifact is malformed, repeated, unsafe, or crosses its workspace, tenant, owner, job, provider-account, or managed-file scope"); + RejectManagedMediaArchive("a provider download artifact is malformed, repeated, unsafe, or crosses its workspace, tenant, owner, job, provider-account, or managed-file scope"); } } @@ -1580,8 +1580,8 @@ public sealed class DurableStateTransferService catch (JsonException) { return false; } } - private static void RejectPhase6Archive(string reason) => throw new BackupVerificationException( - $"State transfer Phase 6 data is invalid because {reason}."); + private static void RejectManagedMediaArchive(string reason) => throw new BackupVerificationException( + $"State transfer managed-media data is invalid because {reason}."); private static Dictionary IndexUnique( IEnumerable values, diff --git a/allstarr/Core/Storage/Phase4DurableEntities.cs b/allstarr/Core/Storage/LibraryPlaylistEntities.cs similarity index 100% rename from allstarr/Core/Storage/Phase4DurableEntities.cs rename to allstarr/Core/Storage/LibraryPlaylistEntities.cs diff --git a/allstarr/Core/Storage/Phase6EnrichmentEntities.cs b/allstarr/Core/Storage/MetadataEnrichmentEntities.cs similarity index 100% rename from allstarr/Core/Storage/Phase6EnrichmentEntities.cs rename to allstarr/Core/Storage/MetadataEnrichmentEntities.cs diff --git a/allstarr/allstarr.http b/allstarr/allstarr.http deleted file mode 100644 index 1e4703af..00000000 --- a/allstarr/allstarr.http +++ /dev/null @@ -1,6 +0,0 @@ -@allstarr_HostAddress = http://localhost:5274 - -GET {{allstarr_HostAddress}}/weatherforecast/ -Accept: application/json - -### diff --git a/allstarr/wwwroot/images/providers/squidwtf.svg b/allstarr/wwwroot/images/providers/squidwtf.svg deleted file mode 100644 index 0f96b4e1..00000000 --- a/allstarr/wwwroot/images/providers/squidwtf.svg +++ /dev/null @@ -1 +0,0 @@ -WTF \ No newline at end of file diff --git a/apis/steering/performance-audit.md b/apis/steering/performance-audit.md deleted file mode 100644 index 54382f75..00000000 --- a/apis/steering/performance-audit.md +++ /dev/null @@ -1,120 +0,0 @@ -# Allstarr v3 Performance Audit - -## Goal - -Keep playlist ingestion, matching, provider routing, and target reconciliation responsive as libraries and playlists grow. Performance changes must preserve provider order, matching outcomes, and durable idempotency. - -## Priority Findings - -### P0: Remove per-track persistence queries - -`PlaylistOrchestrationService` currently performs existence and latest-version queries for individual source tracks. At the collector limit this can produce hundreds of thousands of database round trips. - -Required change: - -- Load existing snapshots for distinct source hashes in bounded chunks. -- Build latest-version and content-hash dictionaries in memory. -- Insert missing snapshots as one tracked batch. -- Keep SQL command count constant or chunk-bounded as track count grows. - -### P0: Bound local-library candidate work - -Matching currently has paths that compare every source entry with every scoped library track and paths that load the entire scoped library for each playlist run. - -Required change: - -- Query exact ISRC candidates first. -- Build normalized title and artist match keys for unresolved source tracks. -- Query projected candidate columns only. -- Score each distinct source identity once. -- Retain only the best candidates needed by the review UI. -- Add a scoped normalized match-key index when the persisted key shape is finalized. - -### P0: Claim idempotency before remote writes - -Concurrent playlist runs can pass the same preflight check and both mutate the media server. - -Required change: - -- Persist an in-progress sync run under the existing idempotency key before any remote write, or acquire a database advisory lock for the link and key. -- Return the existing operation when a duplicate request arrives. -- Ensure failed claims are recoverable by the durable retry policy. - -### P1: Deduplicate provider searches - -Repeated source IDs can execute the same complete provider walk multiple times. - -Required change: - -- Group source entries by stable provider identity. -- Match one representative per distinct identity. -- Project the accepted route back to every source position. -- Reuse ISRC and normalized-query results within one run. - -### P1: Make provider timeouts real - -Metadata fan-out currently stops awaiting timed-out work but can leave provider operations running after the concurrency slot is released. - -Required change: - -- Create a linked cancellation token per provider operation. -- Apply the configured timeout with `CancelAfter`. -- Keep non-cancellable extension work inside a separate bounded executor until it actually completes. -- Never report an available concurrency slot while its underlying request is still active. - -### P1: Select latest match versions in SQL - -Virtualization and orchestration materialize all historical decision versions and group them in memory. - -Required change: - -- Select the latest decision per external snapshot in the database. -- Use no-tracking projections for read-only matching and virtualization paths. -- Return one decision row per source entry. - -### P1: Reduce Jellyfin reconciliation complexity - -Current order reconciliation contains repeated linear searches and can issue one sequential move request per item. - -Required change: - -- Use sets and position maps for membership and lookup. -- Prefer bulk delete/add operations when exact reordering is not required. -- When preserving order with moves, calculate a minimal move plan using a longest-increasing-subsequence strategy. - -### P2: Batch cache validation - -Cache validation can perform two Redis reads per track and repeated linear scans. - -Required change: - -- Skip mapping probes when another condition already requires a rebuild. -- Fetch cache keys in batches. -- Use the existing ID set for constant-time membership checks. - -### P2: Stop ISRC fan-out after a priority winner - -ISRC lookup currently invokes every provider and waits for the slowest result. - -Required change: - -- Follow configured provider priority. -- Stop after the first verified playable result. -- Optionally speculate over a small top-priority window, cancelling lower-priority work when the winner is known. - -## Acceptance Measurements - -1. Database scaling test: - Measure SQL command count, returned rows, elapsed time, and allocations for 100, 1,000, and 10,000 source tracks. Command count must be constant or chunk-bounded. - -2. Provider and cache call test: - Run 500 entries containing repeated source IDs against instrumented fake providers, Redis, and Jellyfin. Calls must scale with unique identities, and observed provider concurrency must never exceed its configured limit. - -3. Algorithm benchmark: - Benchmark local matching and target reconciliation at 100, 1,000, and 5,000 tracks, including a reversed target playlist. CPU time and allocations must avoid quadratic growth while producing equivalent output. - -4. Timeout drain test: - Force provider timeouts and assert active operation count returns to zero before the concurrency slot is reused. - -5. Idempotency race test: - Start concurrent runs with the same idempotency key and assert exactly one target mutation sequence and one durable run owner. diff --git a/apis/steering/webui-design.md b/apis/steering/webui-design.md deleted file mode 100644 index f99a69f5..00000000 --- a/apis/steering/webui-design.md +++ /dev/null @@ -1,66 +0,0 @@ -# Allstarr WebUI Design Contract - -## Product direction - -Allstarr is a dense music control surface, not a generic administration form. Every -screen must make the primary action obvious, preserve provider and artwork context, -and progressively disclose technical identifiers. - -## Shared layout - -- Use `view-stack`, `view-header`, `section-heading`, `panel`, and `card`. -- Page tabs use the shared rounded `subnav` treatment. Do not create page-specific - tab components. -- Desktop content is bounded by `--page-max`; mobile content uses one column and no - horizontal document overflow. -- Controls use `--control-height`, `--control-font-size`, and spacing tokens. -- Settings disclosures use compact closed summaries and only expand to fit content. - -## Navigation - -- Primary rail: Home, Library, Sources, Event log, Settings. -- Library tabs: Playlists, Mappings, Cached, Kept. -- Settings tabs: General, Accounts, Provider routing, Extensions, Maintenance. -- Tabs remain deep-linkable, keyboard navigable, and horizontally scrollable on - narrow screens. - -## Dialogs - -- Every modal uses a fixed viewport backdrop and a dialog surface rendered above - route content. -- Dialogs trap focus, close on Escape, restore focus, lock background scrolling, - and reset when the route changes. -- Mobile dialogs occupy the viewport and keep the title and primary action visible. -- Nested details use a dialog stack; they must never render at document-flow offsets. - -## Music identity - -- Prefer album or playlist artwork. Fall back to a provider icon, then a neutral - music glyph. -- Source and target identities are presented as `source -> target` with provider - logos, title, artist, album, outcome, and confidence. -- ISRCs, provider IDs, backend IDs, correlation IDs, route provenance, quality, - cache age, and raw failures live in expandable technical details. - -## States and feedback - -- Loading states use stable skeletons and never flash false zero/unknown metrics. -- Empty states explain the next useful action. -- Success, warning, failure, and review states use consistent semantic pills. -- Destructive operations require explicit confirmation and describe retained data. -- Connectivity uses four bars, a textual state, and exact measured latency in a - tooltip or details region. - -## Responsive and accessibility requirements - -- Test 390x844, 768x1024, 1280x800, and 1440x900. -- No clipped buttons, stale desktop sidebar overlays, or nested horizontal scrollers. -- Preserve visible focus, meaningful labels, reduced motion, contrast, touch targets, - and screen-reader status announcements. -- Tables become semantic card rows on mobile without losing labels or actions. - -## Engineering rule - -Add or improve a shared primitive before adding a page-specific override. Repeated -markup, spacing, tabs, status pills, provider badges, dialog shells, metrics, and -empty states must be extracted rather than copied. diff --git a/docs/README.md b/docs/README.md index 449a9764..c49c2f56 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,10 +1,11 @@ # Allstarr documentation -These documents describe the code that is currently shipped. Planned work does not belong here; implementation planning lives in the ignored `apis/steering` workspace. +These documents describe the code that is currently shipped. Start with one audience and follow links to the owning document instead of reading the whole tree. ## Start here -- [Architecture overview](architecture/overview.md): runtime boundaries and ownership. +- [User guide](user-guide.md): dashboard map, setup order, imports, playlists, cache, and Intelligence. +- [Architecture overview](architecture/overview.md): runtime boundaries and code ownership. - [Configuration](operations/configuration.md): deployment-owned values, durable settings, and secrets. - [Deployment profiles](operations/deployment-profiles.md): install, update, optional services, backup, and restore. - [Storage](operations/storage.md): PostgreSQL ownership, migration, backup, and recovery. @@ -18,10 +19,18 @@ These documents describe the code that is currently shipped. Planned work does n - [Client compatibility](operations/client-compatibility.md) - [Jellyfin v12 music surface](operations/jellyfin-music-surface-v12.md) +## Contributor guides + +- [Repository agent guide](../AGENTS.md) +- [Contributing](../CONTRIBUTING.md) +- [WebUI design system](../DESIGN.md) +- [Test and qualification tools](../tools/tests/README.md) +- [Provider capability module](../allstarr/Core/Capabilities/README.md) + ## Documentation rules 1. Describe current behavior only. 2. Link to the owning code instead of duplicating long lists that can drift. -3. Keep deployment choices in operator guides and unfinished work in `apis/steering`. +3. Keep deployment choices in operator guides and unfinished work out of public user documentation. 4. Do not document SQLite, Redis, Valkey, AIO images, Compose overlays, bundled extension registries, or automatic legacy-state conversion as supported runtime features. 5. When code and documentation disagree, fix the documentation in the same completed implementation chunk. diff --git a/docs/operations/apple-download-provider.md b/docs/operations/apple-download-provider.md index 7dedfb9a..45469beb 100644 --- a/docs/operations/apple-download-provider.md +++ b/docs/operations/apple-download-provider.md @@ -11,7 +11,8 @@ GAMDL search and download HTTP gateway by itself. ## Prepare wrapper-v2 Obtain Apple Music for Android legally from a source you are permitted to use. Allstarr does not download or -redistribute that package. Open Sources > Apple download in the WebUI, upload the APK/APKM, and run: +redistribute that package. Open **Integrations > Services > Apple Music – GAMDL** in the dashboard, manage the +Apple download setup, upload the APK/APKM, and run: ```bash ./allstarr.sh install-apple x86_64 diff --git a/docs/operations/configuration.md b/docs/operations/configuration.md index 2cc70cb4..8bb6efe8 100644 --- a/docs/operations/configuration.md +++ b/docs/operations/configuration.md @@ -32,13 +32,17 @@ PostgreSQL is mandatory. There is no SQLite, Redis, or Valkey runtime option. ## Durable settings -Non-secret product behavior belongs in tenant-scoped PostgreSQL settings and is edited through **Settings**. Examples include provider routing priorities, cache policy, matching thresholds, playlist behavior, download quality, and diagnostics policy. +Non-secret product behavior belongs in tenant-scoped PostgreSQL settings and is edited through the dashboard surface that owns it. General playback, cache, matching, playlist, and diagnostics policy lives under **Settings**. Provider priority lives under **Integrations > Routing**. `DurableRuntimeSettingsService` owns validation, typing, revisions, and optimistic concurrency. Controllers must not add a second environment or JSON owner for these settings. ## Provider accounts -Provider credentials are encrypted and persisted as provider accounts with explicit tenant, user/shared scope, capability, and access policy. Accounts are managed under **Settings > Accounts**. Source availability and routing are shown under **Sources**. +Provider credentials are encrypted and persisted as provider accounts with explicit tenant, user/shared scope, capability, and access policy. Services and their configuration are managed under **Integrations > Services**. Credentials and audience policy live under **Integrations > Accounts**; capability priority lives under **Integrations > Routing**. + +Extensions are package implementations, not a second account system. Their install, update, permission, rollback, and removal lifecycle lives under **Integrations > Extensions**. Once active, their Services and Accounts use the same Integrations surfaces as built-in providers. + +AudioMuse is a built-in Intelligence integration rather than an extension. Its self-hosted URL, optional token, and optional music-server selector live under **Intelligence > Automation**; shared health remains visible in Integrations. A shared account is not automatically available to every user. Administrators must set its access policy explicitly. diff --git a/docs/operations/spotify-lyrics-sidecar.md b/docs/operations/spotify-lyrics-sidecar.md index c6d98371..8979d57e 100644 --- a/docs/operations/spotify-lyrics-sidecar.md +++ b/docs/operations/spotify-lyrics-sidecar.md @@ -20,7 +20,7 @@ The checked-in Compose file points at a pinned image from `akashrchandran/spotif ./allstarr.sh logs spotify-lyrics ``` -Provider readiness and lyrics routing are visible in the Sources and Settings surfaces. An unhealthy lyrics service degrades only the capability that depends on it; it must not make playlist discovery or the core proxy unavailable. +Provider readiness and lyrics routing are visible in Integrations under Services and Routing. An unhealthy lyrics service degrades only the capability that depends on it; it must not make playlist discovery or the core proxy unavailable. ## Update or disable diff --git a/docs/user-guide.md b/docs/user-guide.md new file mode 100644 index 00000000..d116ac84 --- /dev/null +++ b/docs/user-guide.md @@ -0,0 +1,123 @@ +# User guide + +Allstarr has two surfaces: + +- Music clients connect to the Jellyfin or Subsonic-compatible port, normally `5274`. +- Administrators and permitted users use the dashboard, normally on port `5275`. + +The dashboard controls how Allstarr connects sources, matches music, projects playlists, stores temporary or kept files, and learns from listening. It is not a second music player. + +## First setup + +1. Start the stack and open the dashboard. +2. Sign in with a user from the selected Jellyfin or Subsonic backend. +3. Complete onboarding: confirm the backend connection, choose a music library, and verify the user mapping. +4. Open **Integrations → Services** to see built-in and installed capabilities. +5. Open **Integrations → Accounts** to connect personal or explicitly shared provider accounts. +6. Open **Integrations → Routing** to choose the fallback order for each capability. +7. Test ordinary local playback in a music client before adding provider playlists or external playback. + +Administrators see deployment and shared-account controls. A non-administrator sees only the libraries, accounts, and actions allowed for that backend identity. + +## Dashboard map + +### Home + +Home is the operational summary. It shows active listening sessions, the source serving playback, scrobble progress, storage totals, provider health, durable work, and recent activity. Start here when playback or background work seems wrong. + +### Library + +- **Playlists** connects provider playlists and controls how each is exposed to the selected backend. +- **Mappings** reviews unresolved or ambiguous provider tracks and preserves accepted decisions for later syncs and playback. +- **Cached** shows disposable provider audio that may be evicted by cache policy. +- **Kept** shows explicitly retained audio and related sidecars. Kept media is not deleted by cache cleanup. + +### Intelligence + +- **Overview** summarizes the selected library's listening profile and generated output. +- **History** searches, filters, corrects, exports, or removes retained listening events. +- **Import** previews listening-history exports before adding anything. +- **Discover** explains recommendations and lets a permitted user create a playlist from a completed run. +- **Automation** controls automatic history, retention, recommendation signals, schedules, listening-app keys, and the built-in AudioMuse connection. + +AudioMuse is not an extension. Connect a self-hosted AudioMuse server directly in **Intelligence → Automation**. Integrations still reports its health because it participates in the shared capability system. + +### Integrations + +- **Services** lists every built-in or extension-backed capability and its readiness. +- **Accounts** stores encrypted personal or shared credentials and audience policy. +- **Extensions** installs, updates, reviews permissions, disables, rolls back, and removes provider packages. +- **Routing** orders the eligible fallback services for metadata, streaming, download, lyrics, playlists, scrobbling, and other typed capabilities. + +A Service is an implementation. An Account is a credential and access policy for that Service. An Extension is an optional package that can add Services. Routing decides which ready Service/account pair is tried for a capability. These are related but not interchangeable settings. + +### Activity + +Activity groups operational events by outcome and shows the actor, target, duration, source, and correlation details. Use it with container logs when a durable job or provider call fails. + +### Settings + +Settings owns deployment behavior rather than provider credentials: playback quality, matching preferences, cache behavior, maintenance, backup, restore, and other operator policy. Controls that affect one feature stay near that feature when possible. + +## Connect a source + +1. Open **Integrations → Services** and select the Service. +2. Read its capabilities and current readiness. +3. Open **Configuration** for operator-managed fields, or use **Connect account** for encrypted user/shared credentials. +4. Choose the smallest audience that needs access. +5. Save and test the connection. +6. Confirm its capability appears ready before changing Routing. + +Sensitive values are never returned to the browser after saving. Leaving a secret field blank while editing keeps the saved secret unless the form explicitly says otherwise. + +## Import listening history + +Open **Intelligence → Import** and choose or drop one or more supported files. Allstarr accepts: + +- Spotify Extended Streaming History audio JSON files; +- Last.fm, ListenBrainz, Koito, and Maloja JSON, JSONL, or ZIP exports. + +Each file is limited to 64 MB and is previewed before import. Video-only Spotify history is rejected. Review completed, skipped, duplicate, and outside-retention counts before applying the preview. + +Retention and reporting range are separate: + +- **Retention** controls how long saved listening events remain. `Unlimited` keeps them until you remove them. +- **Overview/History range** controls what the dashboard reports and defaults to all time. + +Imports stay private inside Allstarr unless a separate listening-app or scrobbling action is enabled. A completed receipt records what happened at import time; it cannot recreate events that were later removed. Re-upload the original export if the receipt says zero listens are currently retained. + +## Add and review a provider playlist + +1. Open **Library → Playlists** and add a playlist from a connected playlist-capable account. +2. Choose the visible source view and destination behavior described by the form. +3. Preview the effect before enabling scheduled changes. +4. Open **Mappings** for ambiguous or unresolved tracks. +5. Accept only a candidate that represents the same recording. Use interactive search when automatic candidates are wrong. + +An accepted match is reusable across playlist sync, search, playback, and later rematches. A matched local item is returned as the complete native backend object. A genuinely external item keeps a stable virtual identity and provider label. + +Virtual playlists do not silently mutate the source service. Backend materialization adds only resolved local items unless the workflow explicitly says it will download or write back. + +## Cached versus kept + +- A **cached** track is a disposable playback/download artifact. It can be evicted by age or size policy and fetched again. +- A **kept** track was explicitly retained and is managed separately from cache cleanup. +- A database backup does not contain either audio folder. Back up kept/downloaded media according to your own storage policy. + +If playback succeeded but Cached is empty, check the selected storage mode, provider route, durable job, and Activity outcome. A remote stream may not create a complete cache file until the provider download finishes and publishes atomically. + +## Listening and scrobbling + +Automatic history is opt-in. Enable it under **Intelligence → Automation** for the selected library. Listening apps can receive a private key there and may optionally forward completed listens to connected Last.fm or ListenBrainz accounts. + +Scrobbling is checkpointed so a provider is not sent the same completed listen twice. Home and History show what Allstarr observed; Activity shows delivery failures and retries. + +## Safety and recovery + +- Keep the dashboard on a trusted network or behind an authenticated proxy. +- Use a separate provider account when a service allows it, and grant the smallest audience required. +- Preview imports, legacy configuration, playlist changes, and destructive maintenance actions. +- Back up PostgreSQL, the Allstarr key ring, configuration, and retained media as separate assets. +- Use `allstarr.sh upgrade` before an update that should have a rollback artifact. + +For exact procedures, see [configuration](operations/configuration.md), [storage and recovery](operations/storage.md), and [client compatibility](operations/client-compatibility.md).