Commit Graph
2 Commits
Author SHA1 Message Date
Paul NothaftandPaul Nothaft 5c1e38d921 feat(usage): distinguish real edits and template delivery with v5 consent (#1339)
* feat(usage): distinguish real edits and template delivery with v5 consent

* fix(usage): exclude queued test messages and count reorders as edits

- queueEmail carries usageEligible: false into email_data and the queue
  processor passes it on, so the dev tools' send-test-email no longer
  records email_template_delivery once the worker sends it.
- event-types/reorder and categories/reorder-global compare the persisted
  order before and after and record the v5 edit markers only when it
  changed, matching the display_order edit already counted on PUT.
- normalized() builds arrays with Array.from so a row array from the sqlite
  binding compares equal under Jest's separate realm.

* fix(usage): cover per-gallery category order and workflow test runs

- categories/reorder records category_editing when an event's override
  changes; reorder/:eventId records it when an override was actually
  removed.
- send_email and the collections handoff pass usageEligible: false for a
  workflow test run (engine.testRun sets __test), so a non-dry test send is
  not counted as template delivery.

---------

Co-authored-by: Paul Nothaft <[email protected]>
2026-09-07 20:03:15 +02:00
Paul Nothaft c741dc22c5 docs(usage): state in the consent dialog that the connection only runs outwards
The dialog described what is sent and where it goes, but never said which way
the connection runs. That is the part an operator is actually being asked to
accept: opening an outbound path to someone else's service.

PicPeak sends and never pulls. One place in the service reaches the network,
it is a POST, and it requests exactly two paths — /api/envelopes, and
/api/participant/lookup only when an operator asks for their own export. No
scheduled job contacts the collector; the daily rollup is driven solely by an
authenticated admin hitting /activity. There is no route the collector could
call, and redirect: 'error' means it cannot even point a request somewhere
else. From a reply only the acknowledgement for the packet just sent is read,
with every field compared against that packet before it is accepted; the
stored copy drops the session token and no read path hands it back to the UI.
A requested export is streamed to the operator as a file and never
interpreted.

The consequence is why it belongs in the consent text and not only in the
docs: this channel cannot deliver code, configuration or content into an
installation, not even from a collector that has been taken over. It is a
security property by design rather than by convention.

usageOutboundOnly.test.js guards it by source inspection rather than
behaviour, because a behavioural test only proves that today's calls behave.
It fails the moment someone adds a second fetch, a poll for messages, a
scheduled pull, or a public route touching the usage service — verified by
injecting each of those.
2026-09-06 17:58:19 +02:00