Repository navigation
feat(web): receipts of press kit views, with booking details behind press kit access - #329
Merged
Merged
Conversation
…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
bnb
previously approved these changes
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
botandbang
enabled auto-merge
October 30, 2026 23:44
bnb
approved these changes
Oct 30, 2026
bnb
previously approved these changes
Oct 30, 2026
Coverage Report for apps/web
File Coverage
SizeCompared with main. KiB are 1,024 bytes.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Contributor
Author
|
Closing and reopening so CI reruns on a merge ref that includes #344's destinations test fix. Branch unchanged. |
auto-merge was automatically disabled
October 31, 2026 01:11
Pull request was closed
botandbang
enabled auto-merge
October 31, 2026 01:22
auto-merge was automatically disabled
October 31, 2026 01:22
Pull request was closed
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
previously approved these changes
Oct 31, 2026
Rust coverageFrom run 663. Both are held to the thresholds in .cargo/config.toml.
|
bnb
previously approved these changes
Oct 31, 2026
2 tasks done
# Conflicts: # apps/web/README.md
bnb
approved these changes
Nov 1, 2026
2 of 3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What and why
Artists couldn't see who opened the private part of their press kits, because no view was ever audited:
EpkServicerecords 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 waspublicPolicy, 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:
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 newepk_viewtable (migration008_epk_views), which is never written to atproto./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 (theforgetViewaction). Deleting is audited asepk.forget, naming the manager and the view's entry, never the viewer.EPK_VIEW_RETENTION_DAYS(90 by default, 1 to 3650), then deleted by an hourlyepk-view-expirytask. The dashboard and the privacy page show the configured retention.epk.viewandepk.forgetin 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:
memberListPolicy). It used to be readable by any signed-in account.ensureRootSpacescorrects the policy on the next start and audits it assystem.space.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
verifyChainstill passes, but it no longer says who viewed. Blanking an entry's content and keeping its hash was ruled out, sinceverifyChainwould need an exception an attacker could use too, and the root's copy on atproto would need rewriting.Decisions:
EPK_VIEW_RETENTION_DAYS). You chose this for LOC-92's decision (ii).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
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 leavesverifyChainok and recordsepk.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.tson a real spaces network: a granted viewer's look leaves exactly one receipt and a manager's none, the dashboard lists it, andforgetViewdeletes it with the chain still verifying.bootstrap.tsback onpublicPolicy.profile.svelte.test.ts: the BookingDetails tests check the press kit link fornullinstead of the sign-in prompt, which was removed on purpose.pnpm verifyunder 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 lintandpnpm 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
.lightprint class): attach thepress-kits-views--*PNGs, at leastviews-375-dark,views-1280-dark,no-views-yet-375-darkand the threebooking-details-*-375-darkshots.Checklist
docs/and--helpstill say what the code does