Maity: Board

contract + hub + web: profile pictures on public pages (humans with public_profile, agents), resized and cached

tsk-q155psrk6tzz in progress Maity Coder Claude 004 (maity-coder-claude-004)

Oliver decided this in seq 76508, after eyq5's profile inventory (76470): profile pictures are shown on public pages, scaled to their size and cached. Privacy: a person who sets the profile to private (public_profile off) shows no picture to the public.

Today this is excluded on purpose, in four places, and all four change in this card (inventory §3):

  1. Contract: docs/api.md ~9903 says "A public page grants no image (§16.9)". This becomes the new rule: a public person page with public_profile on, and every public agent page, grant their picture.
  2. Read rule: avatars.may_read (hub/src/maity_hub/avatars.py:251-305) and GET /avatars/{id}, which currently requires a session and a shared project. It becomes public for the cases in (1), with Cache-Control: public and a long max-age plus ETag or a versioned URL, so a change is still seen. Outside those cases it stays as today (private).
  3. Size: serve fitting sizes (for example 32/64/128/256 px), scaled once at upload or on first request and kept, not scaled on every request. Re-encoding to PNG stays.
  4. Public pages: public_people.py and public_agents.py draw the picture. The CSP in public_pages.py:37 gets img-src 'self'. Draw a placeholder when there is none.
  5. KTW: context/members.md:345-350 rejected "a public page is enough to read the picture". That entry becomes superseded by a new one with Oliver's decision and the reason. Also resolve the contradiction with context/projects.md:547-548.

Not in this card: how agent pictures without an upload look (req-ms9y03z7a5g5 is still open with Oliver). This card shows whatever exists today: the upload, otherwise the placeholder.

Tests:

The contract goes first as its own PR. Before the merge, check any new §1.3 codes against web/js/errors.js.

Notes

  1. Maity Coder Claude 004 (maity-coder-claude-004) ·

    Step 1 of 3 (contract): #1408, merge-ready at bf3657287fd6707419a6af71346197b6dc527f5a. Step 2 is the hub PR, step 3 the web PR. The card is reported once, with the last PR (lotse 77799).