sgit newsroom v0.1.29 · snapshot 2026-09-24
On this page

maps · 2026-09-25

Feedback over an append lane, drawn

Every moving part of the proposal in issue 048. The reader's feedback leaves the browser encrypted and signed, goes through a write-only lane on a vault the newsroom owns, and comes back into the newsroom's inbox on the next run. The map also shows who holds which key, and what each key cannot do.

The pages this piece is about

This is a proposal, not a shipped feature. Issue 048 holds the checks made before any build. Both SG/Send hosts accept a browser's request from this site. A batch encrypted and signed with Web Crypto was decrypted and its signature verified by sgit pki decrypt. The vault apps framed on this site cannot read its local storage. The transport is sgit.ai's append lanes↗, composed with PKI↗ as vault messaging↗ describes.

The moving parts

Thick arrows are a batch's journey, numbered in order. Dotted arrows are the one-time setup by the editor of record, the blind answer, and the path for anything that fails the signature check. Orange is set up by hand, blue runs in the reader's browser, grey is the SG/Send server, green belongs to the newsroom.

%%{init: {"flowchart": {"wrappingWidth": 460, "nodeSpacing": 40, "rankSpacing": 55}}}%%
flowchart TB
  SETUP["<b>Once: the editor of record</b><br/>1. sgit create newsroom-feedback<br/>2. sgit pki keygen: the key pair that decrypts<br/>3. one append token per device: 64 hex<br/>4. configure: sha256 of each token, sha256 of the enum key"]
  BROWSER["<b>The reader's browser</b>, on sgit.newsroom.sgit.ai<br/>feedback log in local storage: read, star, vote, note, memo<br/>badge: N unsent, and a Send button<br/>encrypt to the public key: RSA-OAEP wraps AES-256-GCM<br/>sign with the device key: ECDSA P-256, not exportable<br/>outbox: waits while offline, marks sent only on ok"]
  SERVER["<b>SG/Send, the append API</b><br/>POST append/write: the token in the body, no account<br/>checks sha256 of the token against the anchors<br/>answers ok true, and nothing else"]
  VAULT["<b>The vault newsroom-feedback</b><br/>one lane per device, outside the commit tree<br/>pending, then processed<br/>ciphertext only: the server cannot read it"]
  RUN["<b>The newsroom's daily run</b><br/>list the lanes with the enum key: no content read<br/>fetch, decrypt with the private key, in memory<br/>verify the signature against approved devices<br/>mark processed"]
  REPO["<b>The newsroom's repository and site</b><br/>admin/inbox: one file per batch, as a paste would be<br/>For the site agent lines: relayed to briefings<br/>the desks act, the build runs, the reader's view updates"]
  BROWSER == "1. the batch, a base64 envelope" ==> SERVER
  SERVER == "2. into the device's lane" ==> VAULT
  VAULT == "3. on the next run" ==> RUN
  RUN == "4. verified batches" ==> REPO
  SERVER -. "blind answer: ok true" .-> BROWSER
  SETUP -. "public key bundle, published on the site" .-> BROWSER
  SETUP -. "a link with the token in the fragment" .-> BROWSER
  SETUP -. "anchors registered" .-> SERVER
  SETUP -. "enum key and private key, as environment variables" .-> RUN
  RUN -. "unsigned or unknown device: kept as data, never as instructions" .-> REPO
  classDef setup fill:#fdf0e6,stroke:#9a3412,color:#1c1d21,text-align:left
  classDef browser fill:#e8eefc,stroke:#1f4fd1,color:#1c1d21,text-align:left
  classDef server fill:#f3f4f6,stroke:#6b7280,color:#1c1d21,text-align:left
  classDef ours fill:#ecfdf5,stroke:#166534,color:#1c1d21,text-align:left
  class SETUP setup
  class BROWSER browser
  class SERVER server
  class VAULT,RUN,REPO ours

One batch, step by step

sequenceDiagram
  autonumber
  participant R as Reader
  participant B as Browser: feedback.js
  participant S as SG/Send append API
  participant V as Vault newsroom-feedback
  participant N as Newsroom daily run
  participant I as admin/inbox
  R->>B: read, star, vote, note (each an event in the log)
  B-->>R: badge: 7 unsent
  R->>B: Send (or the auto-send timer)
  B->>B: batch = the Copy for Claude content since the last send
  B->>B: encrypt to the published public key, sign with the device key
  B->>S: write, with the append token in the body
  S->>V: ciphertext into the device's lane, pending
  S-->>B: ok true, and nothing else
  B->>B: move the sent marker, clear the outbox
  N->>S: list, with the enum key and no content
  N->>S: fetch the new files
  N->>N: decrypt in memory, verify the signature
  N->>I: one file per batch, as a paste would be
  N->>S: mark processed

Who holds what, and what it cannot do

CredentialHeld byCanCannotIf it leaks
Public key bundleeveryone: it is publishedencrypt to the newsroomdecrypt anythingnothing: it is meant to be public
Append token, one per devicethat device's local storagewrite into its own lanelist, fetch or read anything, even what it wrotejunk in one lane, ignored without a valid signature; revoke by removing one anchor
Device signing keythat browser's IndexedDB, not exportablesign that device's batchesleave the browsercannot be copied out; a script on the page could use it while the page is open
Enum keythe newsroom's run, as an environment variablelist, fetch, mark processedwrite, purge, decryptsomeone can see that batches exist, and their sizes; still ciphertext
Private keythe newsroom's run, as an environment variabledecryptreach the server: it is never sentthe batches can be read: rotate the key pair
Vault keythe editor of recordconfigure, purge, everythingthe whole vault: kept out of every browser and every run

What the server still sees

The server sees when a lane receives something, and how big it is. It cannot see the content. That is one reason the design sends batches rather than one message per click. It keeps the reader's rhythm off the server. It also keeps under the lane's limit of 1000 pending files. And each message carries about 1 KB of wrapped key, so tiny messages are wasteful.