Skip to content

feat(web): receipts of press kit views, with booking details behind press kit access - #329

Merged
botandbang merged 9 commits into
mainfrom
bnb/epk-receipts
Nov 1, 2026
Merged

botandbang merged 9 commits into
mainfrom
bnb/epk-receipts

Conversation

@botandbang

@botandbang botandbang commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

What and why

Artists couldn't see who opened the private part of their press kits, because no view was ever audited: EpkService records saves, deletes, requests, declines, revokes and grants, and the press kit page recorded nothing. Booking details couldn't get a receipt at all. They live in the industry space, whose read policy was publicPolicy, so any signed-in atproto account could read them from the protocol, and the app showed them to every signed-in viewer on the project page and the press kit list.

Added:

  • A receipt each time someone let into a press kit opens it on the site, at most one per person and press kit every 30 minutes. Managers' own views aren't recorded, and a failure to record is logged and never fails the page. The audit log records it as epk.view, with the root as actor and an HMAC of the viewer's DID under a random per-receipt salt, never the DID itself, so nothing goes to the viewer's PDS. The DID and the salt live only in a new epk_view table (migration 008_epk_views), which is never written to atproto.
  • A "Views" section on /dashboard/projects/<slug>/press-kits (EpkViewList.svelte), streamed so the page never waits on it, listing who viewed which press kit and when, with a Delete button (the forgetView action). Deleting is audited as epk.forget, naming the manager and the view's entry, never the viewer.
  • Expiry: receipts are kept for EPK_VIEW_RETENTION_DAYS (90 by default, 1 to 3650), then deleted by an hourly epk-view-expiry task. The dashboard and the privacy page show the configured retention.
  • epk.view and epk.forget in the audit entry lexicon's known values, and Storybook stories for the list's loading, empty, error and full states and the three booking details states.

Changed:

  • The industry space, which holds booking details, is readable by staff only (memberListPolicy). It used to be readable by any signed-in account. ensureRootSpaces corrects the policy on the next start and audits it as system.space.
  • Booking details show to the project's managers and staff on its pages. Everyone else sees them only inside a press kit they've been let into, and that load is where the receipt is written. The project page and the press kit list say "Contacts, rates and the tech rider come with a press kit."
  • The README, the privacy, about and for-DJs pages, ADR 0001's example and the industry space lexicon's description now describe this.

Why deleting a receipt keeps the audit chain valid: each chain entry hashes all its fields together with the previous hash, so no entry's content can change. A view entry never holds the viewer's identity, only an HMAC under a salt the receipt keeps. Deleting or expiring a receipt destroys the DID and the salt together. The entry stays as it was and verifyChain still passes, but it no longer says who viewed. Blanking an entry's content and keeping its hash was ruled out, since verifyChain would need an exception an attacker could use too, and the root's copy on atproto would need rewriting.

Decisions:

  • Receipts can be deleted by the project's managers and expire after 90 days (EPK_VIEW_RETENTION_DAYS). You chose this for LOC-92's decision (ii).
  • Yours to confirm: decision (i) said to move booking details into the gated press kit space. Instead I closed the industry space to staff and show the details only inside a press kit with access. People let into a press kit are members of its space, so details stored there could be read from any atproto client without a receipt, and a project without a press kit would have nowhere to keep them. Undoing it is one line in apps/web/src/lib/server/bootstrap.ts.

Not covered: people let into a press kit can still read its private part (not the booking details) straight from its space with another atproto client, and that leaves no receipt. Viewers can't delete their own receipts; only managers and expiry can.

Fixes LOC-92
Fixes #194

How it was tested

  • New services/epk-views.test.ts (7 tests): newest first across press kits, the chain entry and the root's atproto copy never hold the viewer's DID and the stored HMAC matches the salt and DID, two views 30 minutes apart count as one, a manager's delete leaves verifyChain ok and records epk.forget, non-managers can't delete and managers can't delete another project's receipt, expiry at 90 days keeps the chain verifying, and an audit failure doesn't throw.
  • bootstrap.test.ts: an industry space with the old public policy is closed to member-list reads on the next bootstrap.
  • tests/integration/press-kits.test.ts on a real spaces network: a granted viewer's look leaves exactly one receipt and a manager's none, the dashboard lists it, and forgetView deletes it with the chain still verifying.
  • Broken on purpose, and failed: the DID put in the details and the 30-minute gap removed (2 failures), the project filter, retention cutoff and not-found check dropped (3), and bootstrap.ts back on publicPolicy.
  • profile.svelte.test.ts: the BookingDetails tests check the press kit link for null instead of the sign-in prompt, which was removed on purpose.
  • apps/web pnpm verify under Node 24 (format:check, lint, check, knip, test with 807 tests, test:storybook with 176 tests and accessibility checks as errors, test:build). At the root, tools/lexicons.sh lint and pnpm lexicons:format --check.

Screenshots

The new Storybook stories at 375 and 1280 px, in light and dark, none with page errors or horizontal overflow (the site is dark-only on screen; the light shots use the .light print class): attach the press-kits-views--* PNGs, at least views-375-dark, views-1280-dark, no-views-yet-375-dark and the three booking-details-*-375-dark shots.

Checklist

  • Tests cover the change
  • For the website: Storybook stories cover any new UI states
  • The READMEs, docs/ and --help still say what the code does

…ress kit access

Planned as these commits, folded into one because they share files:

- feat(web): record press kit views and show receipts to the project's managers
- feat(web): show booking details only to managers, staff and people let into a press kit
@botandbang
botandbang requested a review from bnb as a code owner October 30, 2026 16:20
@linear-code

linear-code Bot commented Oct 30, 2026

Copy link
Copy Markdown

LOC-92

bnb
bnb previously approved these changes Oct 30, 2026
@github-actions github-actions Bot added documentation Improvements or additions to documentation web The Local Network website (apps/web) lexicons The computer.localnetwork.* lexicons web: projects Project profiles, claims, and the DJ stats and sets on them web: epk Press kits and who can see them web: bookings Booking inquiries and their threads web: staff Admin, moderation and the audit log web: server Spaces, sync, HappyView, the database and the server around them web: ui Shared components, design tokens, layout and the static pages tests Tests, fixtures and test harnesses labels Oct 30, 2026
# Conflicts:
#	apps/web/README.md
#	apps/web/src/lib/server/app.ts
#	apps/web/src/lib/server/bootstrap.test.ts
#	apps/web/src/lib/server/config.ts
#	apps/web/src/lib/server/db/migrations.ts
#	apps/web/src/lib/server/db/schema.ts
bnb
bnb previously approved these changes Oct 30, 2026
@github-actions

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Coverage Report for apps/web

Status Category Percentage Covered / Total
🟢 Lines 95.54% (🎯 93%)
⬆️ +0.11 pp
7976 / 8348
🟢 Statements 94.24% (🎯 91%)
⬆️ +0.15 pp
12158 / 12900
🟢 Functions 94.28% (🎯 92%)
⬆️ +0.07 pp
3149 / 3340
🟢 Branches 84.81% (🎯 80%)
⬆️ +0.10 pp
5645 / 6656
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
apps/web/src/lib/epk.ts 100%
🟰 ±0 pp
100%
🟰 ±0 pp
100%
🟰 ±0 pp
100%
🟰 ±0 pp
apps/web/src/lib/components/BookingDetails.svelte 76.92%
⬆️ +11.54 pp
75%
⬆️ +5.00 pp
71.42%
⬆️ +14.28 pp
100%
🟰 ±0 pp
19, 19, 22, 22
apps/web/src/lib/components/epk/EpkViewList.svelte 100% 75% 100% 100%
apps/web/src/lib/server/app.ts 66.66%
⬇️ -0.52 pp
46.37%
🟰 ±0 pp
47.27%
⬇️ -0.87 pp
73.93%
⬇️ -0.30 pp
93, 134, 176, 202, 204, 207, 230, 301-314, 383-388, 398, 405, 408-409, 421, 443, 463-503, 508, 519-522, 532-537
apps/web/src/lib/server/bootstrap.ts 94.28%
⬆️ +8.57 pp
92.3%
⬆️ +7.69 pp
100%
🟰 ±0 pp
96.87%
⬆️ +9.37 pp
48, 97
apps/web/src/lib/server/config.ts 85.45%
🟰 ±0 pp
87.93%
🟰 ±0 pp
91.66%
🟰 ±0 pp
87.75%
🟰 ±0 pp
213-223
apps/web/src/lib/server/project-page.ts 83.33%
⬆️ +1.52 pp
76.19%
⬆️ +9.53 pp
100%
🟰 ±0 pp
100%
🟰 ±0 pp
15, 18
apps/web/src/lib/server/audit/log.ts 95.23%
🟰 ±0 pp
86.36%
🟰 ±0 pp
96.29%
🟰 ±0 pp
100%
🟰 ±0 pp
197, 318, 324, 369, 371, 398, 502, 550, 591
apps/web/src/lib/server/db/migrations.ts 96.66%
⬆️ +0.37 pp
50%
🟰 ±0 pp
100%
🟰 ±0 pp
95.45%
⬆️ +0.45 pp
407-409
apps/web/src/lib/server/db/schema.ts 0%
🟰 ±0 pp
0%
🟰 ±0 pp
0%
🟰 ±0 pp
0%
🟰 ±0 pp
apps/web/src/lib/server/services/epk-views.ts 100% 81.81% 100% 100%
apps/web/src/lib/server/testing/core.ts 95.23%
⬆️ +0.07 pp
80%
🟰 ±0 pp
100%
🟰 ±0 pp
100%
🟰 ±0 pp
79, 88, 95
Generated in workflow #45 for commit 05d15c7 by the Vitest Coverage Report Action

Size

Compared with main. KiB are 1,024 bytes.

This pull request main Change
Client JavaScript 683.6 KiB 680.7 KiB +2.9 KiB (+0.4%)
Client JavaScript, brotli 227.3 KiB 226.8 KiB +520 B (+0.2%)
Client CSS 89.6 KiB 89.6 KiB 0 B (0%)
Client CSS, brotli 12.9 KiB 12.9 KiB 0 B (0%)

@botandbang

Copy link
Copy Markdown
Contributor Author

Closing and reopening so CI reruns on a merge ref that includes #344's destinations test fix. Branch unchanged.

@botandbang botandbang closed this Oct 31, 2026
auto-merge was automatically disabled October 31, 2026 01:11

Pull request was closed

@botandbang botandbang reopened this Oct 31, 2026
@botandbang
botandbang enabled auto-merge October 31, 2026 01:22
@botandbang botandbang closed this Oct 31, 2026
auto-merge was automatically disabled October 31, 2026 01:22

Pull request was closed

@botandbang botandbang reopened this Oct 31, 2026
@botandbang
botandbang enabled auto-merge October 31, 2026 01:22
botandbang added a commit that referenced this pull request Oct 31, 2026
Conflicts:
- forms/project.ts: main moved strings, feeRows and formValues to
  #lib/project-form.ts for the browser's drafts. They stay there, and
  the profile editor's booking-city rows (bookCity, bookRegion,
  bookCountry) move into that formValues. The profile editor and its
  test import formValues from there now.
- ProjectForm.svelte: this branch split the fields into BasicsFields and
  PerformerFields; main added drafts. The form keeps the split and gets
  main's draft: FormDraft, DraftNotice, `shown` passed to the field
  components, `children(shown)`, and the fields under
  `{#key formDraft.revision}`, which remounts the components and so
  resets their rows without main's redraw callback. BasicsFields keeps
  a restored draft's "Remove the current image".
- admin/requests: main moved the table into RequestTable. The page
  takes main's version plus this branch's Listings filter, and
  RequestTable names each request with REQUEST_KIND_LABEL and
  requestSubject, so a listing request reads as its DJ. Its story gains
  a listing request.
- app.ts: main's PlyrClient and RecordingService alongside profiles,
  djListings and performers. RequestService stays after them, since it
  takes profiles and djListings.
- README: both sides' notes on blobs and security.

main's new dashboard page tests get the layout's `sections`.

Migrations: main ends at 007_inbox and #329 takes 008, so
008_listing_requests becomes 009_listing_requests. It has only run on
local and test databases.
bnb
bnb previously approved these changes Oct 31, 2026
@github-actions

Copy link
Copy Markdown

Rust coverage

From run 663. Both are held to the thresholds in .cargo/config.toml.

Tests Lines Functions Regions
All 97.4% (83617/85851) 96.5% (13334/13817) 95.66% (183885/192230)
Hegel property tests alone 95.41% (81909/85851) 94.09% (13001/13817) 93.5% (179735/192230)

bnb
bnb previously approved these changes Oct 31, 2026
@botandbang
botandbang added this pull request to the merge queue Nov 1, 2026
Merged via the queue into main with commit 199ac97 Nov 1, 2026
25 checks passed
@botandbang
botandbang deleted the bnb/epk-receipts branch November 1, 2026 18:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation lexicons The computer.localnetwork.* lexicons tests Tests, fixtures and test harnesses web: bookings Booking inquiries and their threads web: epk Press kits and who can see them web: projects Project profiles, claims, and the DJ stats and sets on them web: server Spaces, sync, HappyView, the database and the server around them web: staff Admin, moderation and the audit log web: ui Shared components, design tokens, layout and the static pages web The Local Network website (apps/web)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

web: record press kit views in the audit chain and show receipts to the artist

2 participants