81 Commits
Author SHA1 Message Date
Evan JarrettandClaude Opus 5 2b71be59f7 billing: require a managed hold for paid features
Entitlements were keyed on the Stripe subscription alone, so a subscriber
who switched to a self-hosted hold kept paying for features the appview
cannot deliver, and could still reach checkout.

  - billing.ActiveHoldChecker and Manager.onManagedHold gate every
    entitlement. An empty default hold counts as managed: the user has no
    explicit preference and falls back to the operator's primary managed
    hold.
  - The checker reads the primary DB, not the read replica. A hold switch
    writes default_hold_did to the primary, and replica lag would keep
    paid features alive after a switch away.
  - db.GetUserDefaultHoldDID is the clean default-hold signal, unlike
    GetUserHoldDID which falls back to a manifest hold_endpoint (a URL,
    not a DID).
  - Jetstream fails closed: an unresolvable hold reference is cached raw
    rather than left empty, since an empty value reads as managed.
  - UI: the billing tab is hidden on self-hosted, a cancel/manage banner
    appears when a self-hosted user still has an active plan, the image
    advisor returns managed_hold_required instead of upgrade_required,
    and the checkout route returns 403. The portal stays open so existing
    subscribers can still cancel.

Two consistency fixes fall out of wiring this up:

The settings UI reads the resolved default_hold_did rather than the raw
profile.DefaultHold. The profile field is the record value as written and
may be a URL-form reference; jetstream resolves it to a DID on the way
into the DB, and the server-side gate reads that resolved value. Comparing
the raw form against managed DIDs would show the "you are self-hosted"
banner and hide billing from a user whose entitlements say otherwise.

HasAIAdvisor falls back to the free tier's AIAdvisor setting when
off-managed instead of a hard false, matching GetWebhookLimits. Losing a
managed hold should drop a user to free-tier entitlements, not below them.

BEHAVIOR CHANGE for existing paying users on self-hosted holds: they lose
the AI advisor, supporter badge and paid webhook limits as soon as this
deploys, while Stripe keeps charging them. The only notice is the banner
on /settings/storage, which they have to visit to see. Decide on a
migration (notification, or a one-time reconciliation over active
subscriptions) before shipping this.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 21:14:58 -05:00
Evan Jarrett 6758996300 add SBOM package diffing, verify hold-service captain records
- diff view gains a Packages tab with added/removed/changed/unchanged
  package tables and purl-derived type/license/upstream links
- captain records verified against the DID's atcr_hold service before
  caching (processor + batch backfill), preventing forged holds
- fix empty-handle updates clobbering cached handles and colliding on
  the UNIQUE constraint
- move fillPrevCIDs into repo.go; DirectRepoOperator is now canonical,
  repomgr kept as a test oracle
- surface read-only crew status in hold selector
- reconcile docs
2026-06-13 12:49:03 -05:00
Evan Jarrett ab4a4ebf9d admin panel long running imrovements, billing fixes, ui cleanup
1. Multiple registry domains + per-user domain preference

The biggest feature. The appview can serve several registry domains (e.g. buoy.cr, atcr.io),
and users can now pick which one shows up in their pull/push commands.

- Lexicon/record: adds registryDomain (and documents ociClient)
to the sailor profile (lexicons/.../profile.json, pkg/atproto/lexicon.go).
- DB: new registry_domain column on users (schema.sql + migration 0027),
with GetUserByDID/Handle reads, UpdateUserRegistryDomain writer,
and Jetstream caching it on profile updates (writes unconditionally so clearing propagates).
- UI/handlers: new UpdateRegistryDomainHandler + /api/profile/registry-domain route,
a <select> in the user settings panel (only shown when >1 domain configured), and resolveRegistryURL()
which falls back to the primary domain if the user's pref is stale/removed. Tests added for all of it.

2. default_hold_did removed → first managed_holds entry is the default

Consolidates two overlapping config fields into one. ServerConfig.DefaultHoldDID is gone;
PrimaryHoldDID() now returns managed_holds[0]. managed_holds is now REQUIRED.
Updated in config, validation, server wiring, test harness, example YAML, and the deploy template.

3. Admin long-running operations → generic background-job framework

New pkg/hold/admin/jobs.go introduces a reusable startJob/jobRegistry pattern
 (a detached context.Background() job + a /admin/api/jobs/{key}/status polling endpoint).
This replaces the bespoke scan-backfill goroutine state machine, and now also wraps crew tier remap and crew import
all three previously looped synchronously on the request context and got 504'd/cancelled mid-run by the reverse proxy.
 Forms switched from POST-redirect to htmx fragments (job_progress.html, job_result.html, crew_import_results.html)
 the old crew_import_results.html page and scan_backfill_progress.html partial were deleted.
This is also captured as a new rule in CLAUDE.md.

4. Cascade-delete manifest on last-tag deletion

DeleteTagHandler now, after removing the last tag pointing to a digest, cascade-deletes the manifest itself
 (PDS + DB + hold blob purge) — but only if it's not a child of a manifest list (multi-arch parent).
 New GetTagDigest and ShouldCascadeDeleteManifest queries back it, plus cascade_delete_test.go.
 Also switches tag rkey computation to the atproto.RepositoryTagToRKey helper.

5. Billing simplification

Drops the OwnerBadge config option (hold-owner supporter badge).
The user-profile template no longer special-cases an "owner" badge value (only "Captain").
Example tiers renamed to the nautical scheme (deckhand/bosun/quartermaster).

6. Build/deploy: go generate always runs via Make

make generate is now a phony target that always runs go generate ./... (regenerating cbor_gen, icon sprites, etc.),
 and build-trixie depends on it. The deploy tooling (provision.go/update.go)
drops its own runGenerate calls since the Makefile handles it.

7. New cmd/firehose-tap tool (untracked)

A standalone CLI that subscribes to a com.atproto.sync.subscribeRepos endpoint and pretty-prints events,
with emphasis on Sync 1.1 compliance fields (per-op prev CIDs, commit prevData) and a --validate CI mode.
Fits with the recent "more sync1.1 compliant" commit.
2026-06-05 20:57:25 -05:00
Evan Jarrett 902fba4553 convert alert to modal. go fix the codebase 2026-05-16 11:59:47 -05:00
Evan Jarrett 3271ac6dcc fix labeler issues 2026-05-09 21:21:20 -05:00
Evan Jarrett 966e391a91 fix some connection issues with jetstream causing a crashloop 2026-05-09 15:47:19 -05:00
Evan Jarrett 3533f07ecb minor bug fixes, add ability see starred repos 2026-05-06 21:55:47 -05:00
Evan Jarrett 2d0872790d delete manifests correct when deleted from the PDS fixes https://tangled.org/did:plc:e3kzdezk5gsirzh7eoqplc64/issues/18 2026-05-06 21:13:00 -05:00
Evan Jarrett b2d6842bb7 clean up old migration code. minor bug fixes with appview ui 2026-05-04 21:52:28 -05:00
Evan Jarrett 410183f13b first pass at implementing a label service 2026-05-02 11:14:44 -05:00
Evan Jarrett 35aa19e2b2 fix captain record check ttl 2026-05-02 11:11:01 -05:00
Evan Jarrett 7a6775d4fa missed new files for jetstream improvements 2026-04-19 18:04:57 -05:00
Evan Jarrett 7c6b8945ed improve backfill and jetstream db connections 2026-04-19 18:01:17 -05:00
Evan Jarrett 564019d1c3 general appview bugfixes 2026-04-09 10:31:19 -05:00
Evan Jarrett fd5bfc3c50 ui fixes for repo page, fix scanner priority, cleanup goreleaser scripts 2026-04-03 16:48:21 -05:00
Evan Jarrett 22cf108a5f fix quirks on repo and digest pages. fix ips not showing in server logs. add basic spam blocking to LB. add setting to configure your oci (docker) client. 2026-03-24 22:24:51 -05:00
Evan Jarrett 23db9be665 add repo page editor. fix deleting all untagged actually deleting all untagged 2026-03-23 21:16:13 -05:00
Evan Jarrett 385f8987fe overhaul repo pages, add tab for 'artifacts' (tags, manifests, helm charts). implement digest page with layer commands and vuln reports 2026-03-22 21:10:47 -05:00
Evan Jarrett e886192aeb update seamark theme, add 'delete all untagged' option on record page. add garbage collection flag for untagged 2026-03-16 20:26:56 -05:00
Evan Jarrett 136c0a0ecc billing refactor, move billing to appview, move webhooks to appview 2026-02-26 22:28:09 -06:00
Evan Jarrett f90a46e0a4 begin implement supporter badges, clean up lexicons, various other changes 2026-02-20 22:12:18 -06:00
Evan Jarrett 2df5377541 more did:plc fixes, more vulnerability scanner fixes 2026-02-15 22:28:36 -06:00
Evan Jarrett abefcfd1ed let appview work with did:plc based storage servers 2026-02-15 14:20:02 -06:00
Evan Jarrett e3843db9d8 Implement did:plc support for holds with the ability to import/export CARs.
did:plc Identity Support (pkg/hold/pds/did.go, pkg/hold/config.go, pkg/hold/server.go)

  The big feature — holds can now use did:plc identities instead of only did:web. This adds:
  - LoadOrCreateDID() — resolves hold DID by priority: config DID > did.txt on disk > create new
  - CreatePLCIdentity() — builds a genesis operation, signs with rotation key, submits to PLC directory
  - EnsurePLCCurrent() — on boot, compares local signing key + URL against PLC directory and auto-updates if they've drifted (requires rotation key)
  - New config fields: did_method (web/plc), did, plc_directory_url, rotation_key_path
  - GenerateDIDDocument() now uses the stored DID instead of always deriving did:web from URL
  - NewHoldServer wired up to call LoadOrCreateDID instead of GenerateDIDFromURL

  CAR Export/Import (pkg/hold/pds/export.go, pkg/hold/pds/import.go, cmd/hold/repo.go)

  New CLI subcommands for repo backup/restore:
  - atcr-hold repo export — streams the hold's repo as a CAR file to stdout
  - atcr-hold repo import <file>... — reads CAR files, upserts all records in a single atomic commit. Uses a bulkImportRecords method that opens a delta session, checks each record for
  create vs update, commits once, and fires repo events.
  - openHoldPDS() helper to spin up a HoldPDS from config for offline CLI operations

  Admin UI Fixes (pkg/hold/admin/)

  - Logout changed from GET to POST — nav template now uses a <form method=POST> instead of an <a> link (prevents CSRF on logout)
  - Removed return_to parameter from login flow — simplified redirect logic, auth middleware now redirects to /admin/auth/login without query params

  Config/Deploy

  - config-hold.example.yaml and deploy/upcloud/configs/hold.yaml.tmpl updated with the four new did:plc config fields
  - go.mod / go.sum — added github.com/did-method-plc/go-didplc dependency
2026-02-14 15:17:53 -06:00
Evan Jarrett 8048921f5e show attestation details 2026-02-13 19:40:05 -06:00
Evan Jarrett 92c31835e2 implement the ability to promote a hold as a successor as a way to migrate users to a new storage server 2026-02-12 20:14:19 -06:00
Evan Jarrett 8e45b2eee5 remove unused function 2026-02-10 22:24:00 -06:00
Evan Jarrett b251c8857f change to transactions for database 2026-02-10 20:58:24 -06:00
Evan Jarrett 7ba42080c5 more admin panel fixes, allow for fallback relays and jetstreams, improve star lexicon to allow for repo_page backlinks 2026-02-09 21:53:02 -06:00
Evan Jarrett 4d9452bb75 update configs, fix foreign key issues 2026-02-07 23:28:42 -06:00
Evan Jarrett 834bb8d36c libsql instead of sqlite for turso/bunnydb replicated sqlite 2026-02-05 20:43:04 -06:00
Evan Jarrett d6114cf549 implementation of syft/grype scanner as a separate binary 2026-02-04 09:53:04 -06:00
Evan Jarrett 055b34af71 varies fixes for indigo xrpc calls, avatars broken on bsku profile change, opengraph card fixes, other ui improvements 2026-01-14 23:14:43 -06:00
Evan Jarrett b1767cfb6b publish xrpc endpoint lexicons. fix backfill and jetstream to actually validate records match schema 2026-01-12 21:11:55 -06:00
Evan Jarrett 3409af6c67 implement hold discovery dropdown in settings. implement a data privacy export feature 2026-01-07 22:41:14 -06:00
Evan Jarrett 56dd522218 more lint cleanup 2026-01-06 23:08:37 -06:00
Evan Jarrett efef46b15a various linting fixes 2026-01-04 22:02:01 -06:00
Evan Jarrett 506d8b002b fix unit tests 2026-01-02 15:25:09 -06:00
Evan Jarrett 347db5c391 begin support for helm-charts 2026-01-02 13:09:04 -06:00
Evan Jarrett 045aeb2de5 add index table to mst so listRecords is more efficient 2026-01-01 21:19:38 -06:00
Evan Jarrett 1df1bb57a4 clean up logs, delete cached data when atproto account is deleted 2025-12-31 12:21:17 -06:00
Evan Jarrett f19dfa2716 move download stats to the hold account so it can persist across different appviews 2025-12-31 11:04:15 -06:00
Evan Jarrett d11356cd18 more improvements on repo page rendering. allow for repo avatar image uploads (requires new scopes) 2025-12-21 21:51:44 -06:00
Evan Jarrett b18e4c3996 implement io.atcr.repo.page. try and fetch from github,gitlab,tangled README.md files if source exists. 2025-12-20 16:32:41 -06:00
Evan Jarrett 044d408cf8 deployment fixes. add open graph 2025-12-18 11:19:49 -06:00
Evan Jarrett 4063544cdf cleanup view around attestations. credential helper self upgrades. better oauth support 2025-12-18 09:33:31 -06:00
Evan Jarrett 4d5182e2b2 fix jetstream using wrong manifest key 2025-11-07 11:06:51 -06:00
Evan Jarrett 5a41f876ff fix unit tests 2025-10-30 22:33:07 -05:00
Evan Jarrett 9d773d484a dark mode! fixes #1 2025-10-28 22:05:37 -05:00
Evan Jarrett b0799cd94d unit tests 2025-10-28 17:40:11 -05:00