Feedback from any page, signed; onboarding by QR code; a review queue for the editor
Status: phase 1 being built on branch conductor/collaborators. Brief by Dinis Cruz (editor), a voice memo of 22 October 2026 to the conductor; written up by conductor@php.newsroom. Build brief, the kind an agent executes.
The ask, in the editor's words (through a transcriber, condensed)
"We should have an append lane to provide feedback from the website: anybody, in any part of the website, can open the sidebar and provide feedback about the page, with screenshots, something that somebody can then open and review. Maybe we need a new agent that reviews it. That includes me.
What's cool is that you can now sign it. Anybody who wants to provide feedback for now needs to have a key: they go through the onboarding process. Part of the exercise is to make this onboarding as simple as possible. Take the workflow we have with the PKI and make it super simple: here is the flow, here is a QR code, we just created your thing; even me giving them a QR code to paste, with what they need, my public key, and some instructions. I want to onboard more contributors and more people. If you want to provide feedback and be involved, contact me by email or ping me on WhatsApp, and we create that web of trust.
The other thing is for me. You have my key, and I can identify myself. How can we create UIs unique to me, and to the team? We have lots of new pages and I need to review them: see what is there, provide comments, on an iPad: next, next, next. How can you get feedback from me, and especially you, the conductor, on the pages that are being created and the pages that will be created next?"
What exists to build on
- The pane on the right of every page (
assets/ask/ask.js, dev@php.newsroom's): tabs, and the reader's vault in the same key slot as the phone app. - The collaborators vault (58526ytp) and its public write-only lane, encrypted in the browser to
sha256:e3ff50abd96816ef, drained byadmin/build/collaborators_drain.py: join requests and feedback from /collaborators/ already arrive there. - pki.sgit.ai's key formats: a key pair is RSA-OAEP 4096 (to receive secrets) and ECDSA P-256 (to sign), the public half the JSON bundle
sgit pki exportemits, the fingerprintsha256:and the first 16 hex of the SHA-256 of the key's SPKI bytes;sgit pki verifychecks a signature. The register's web of trust (invitations, two-way links, the editor as root) is where these keys can go later. - The section ledger (
data/sections.json): every page and section with its versions, so "what is new or changed since I last looked" is already counted.
The design
Phase 1: a key, an invitation, and feedback from any page
1. A key per device, made in the browser. On /join/, the browser makes the person's key pair with Web Crypto, in sgit's format. The private halves are kept in this browser as non-extractable keys (IndexedDB): they sign, and nothing on the page, no script and no extension that reads the page's storage, can read them back out. The public bundle and its fingerprint are shown.
2. The invitation is a QR code, made in the browser. On /join/invite/, a device whose key is enrolled with a role that may invite (the editor, for now) types a handle (never a name), picks a role and how long it lasts, and the page signs the invitation with that key and shows a QR code, the link and a share button (WhatsApp, Messages, email). The invitation is https://php.oct.2026.newsroom.sgit.ai/join/#i1.<the signed invitation>: self-verifying, so nothing is written anywhere when it is made. The editor's correction of 22 October: "all of these need to be run from a browser; we cannot have any command to be typed from a Python environment: look at how we do it with pki.sgit.ai." The one exception is the very first key, the editor's own, which the conductor bootstraps with admin/build/invite.py and hands to him sealed to his pki.sgit.ai key.
3. Joining is one tap, and enrolment is automatic. The join page makes the key and sends an enrolment, {kind: "enrol", invite, handle, bundle, device}, signed by the new key and encrypted to the collaborators key, to the collaborators lane. When the conductor's run drains the lane, the key is enrolled if the enrolment's signature verifies and the invitation's signature verifies against an enrolled key that may invite, in date and with devices left. Nobody types anything.
4. Feedback from any page. A Feedback tab in the pane: the page and version are filled in; a kind (something wrong, unclear, missing, an idea, good); the text; a screenshot (pick a file, or capture this screen, scaled down in the browser). It is signed with the person's key over its canonical bytes, encrypted to the collaborators key, and posted to the lane. Without a key, the tab says how to get one: ask the editor by email or WhatsApp, as /collaborators/ says.
5. Draining verifies. collaborators_drain.py checks every signature with sgit pki verify against the bundle the message carries, and marks each row signed by an enrolled key, signed by an unknown key or unsigned. Screenshots are saved next to the row. Nothing reaches the site; the conductor turns feedback into board items, and a feedback desk agent can take this over.
Phase 2: the editor's review queue
6. /review/, for a key with the role editor (and later the team): every page and section that is new or changed since the reviewer last looked, from the section ledger, one at a time, sized for an iPad: the page, then Next, Good, Needs work, a comment. Each decision is signed feedback of kind review, so the conductor reads the editor's reviews from the same lane. Where the reviewer has got to is kept in this browser and in their vault.
7. The conductor's queue, the same view of what the agents are about to publish: the branches waiting for dev@, so the editor reviews before the release, not after.
What it cannot do, said up front
- A key in one browser is that device's key: a second device is a second invitation. Moving a key between devices, and linking it to the person's record on pki.sgit.ai, is later.
- The invitation code is a bearer credential until it is used: whoever sees the QR code can enrol with it. Send it to one person; each code enrols at most three devices and expires in fourteen days.
- Signing proves which key wrote a message, not who holds it: that is what the editor's enrolment adds, and only as far as the person he sent the code to.
- The lane is public, so anyone can still post unsigned rows; they are kept and marked unsigned.
- The site only shows fingerprints and handles, never a name, unless the person agreed.
Deliverables (phase 1)
/join/andassets/feedback/identity.js(keys, signing, the envelope, the lane),assets/feedback/join.js, the Feedback tab (assets/feedback/pane.js, loaded by the pane)./join/invite/andassets/feedback/invite.js(invitations signed in the browser, QR code from pki.sgit.ai's vendored generator);data/contributors.json(enrolled keys, invitations as hashes); automatic enrolment in the drain (admin/build/contributors.py);admin/build/invite.pyfor the bootstrap only.- Tested end to end in Chromium with two separate browser profiles: the editor's first key bootstrapped and enrolled; an invitation signed on his device; a second device joining with it, enrolled automatically, and sending feedback that drains as signed by an enrolled key. Negative controls: a changed word, page, role or expiry fails, and a key that may not invite cannot.