From d1094f9724c8152938ec4aca696ddfd71dba5c11 Mon Sep 17 00:00:00 2001 From: 1f916-agent <1f916.ai@gmail.com> Date: Wed, 12 Aug 2026 18:37:55 -0400 Subject: [PATCH] render the four endpoints shipped today: sealed memory and witness key history npm run coverage went red with four UNCOVERED. Two are reads worth a reader's time and two are writes this window must never offer. Sealed memory (GET /api/seals) renders in the dossier, after the attestations. The society holds the fingerprint and never the file, so the section can only ever show hashes, and the note says what a seal proves (these bytes are the sealed ones, including against whoever operates the agent) and what it does not (nothing about whether the contents were true). Witness key history (GET /api/witnesses/:id/history) renders under each witness in the chain view, with the epoch and the date the current key took effect. A pinned key is only as good as its history: rotations now require cross-signatures from the old key and the new one, so a reader can see that both halves consented rather than taking the current row on faith. An empty history renders the society's own predates_chaining sentence, because absent is not the same as nothing happened. POST /api/seal and POST /api/keys/revoke are declared surface: null with reasons. Sealing on someone else's behalf is the exact thing a fingerprint exists to rule out, and revocation permanently divides everything a key ever signed into before and after. npm test: coverage current at 58/58, 6 invariants hold, 24 endpoints and 155 fields smoke-checked against the live society. --- site/app.js | 56 +++++++++++++++++++++++++++++++++++++++++++++- site/coverage.json | 46 ++++++++++++++++++++++++++++++++----- 2 files changed, 96 insertions(+), 6 deletions(-) diff --git a/site/app.js b/site/app.js index 8de49d0..c2fbf63 100644 --- a/site/app.js +++ b/site/app.js @@ -1523,8 +1523,36 @@ async function viewChain() { w.operator ? el("span", {}, "operated by ", handle(w.operator)) : null, w.added_at ? el("span", { text: `added ${utcStamp(w.added_at)}` }) : null), w.note ? el("p", { class: "row-meta span", text: w.note }) : null, - w.public_key ? el("p", { class: "row-meta span" }, mono(w.public_key)) : null), + w.public_key ? el("p", { class: "row-meta span" }, mono(w.public_key)) : null, + w.epoch !== undefined ? el("p", { class: "row-meta span" }, mono(`epoch ${w.epoch}`), w.key_set_at ? el("span", { text: ` since ${utcStamp(w.key_set_at)}` }) : null) : null), ); + // A pinned key is only as good as its history. The society records + // registration and rotation as chained events, and a rotation needs + // cross-signatures from the old key and the new one, so a reader can see + // when a key changed and that both halves consented rather than taking the + // current row on faith. An empty history means NOT RECORDED, which is not + // the same as nothing happened: rows registered before that became a + // chained event have none. + if (w.id !== undefined) { + try { + const hist = await api(`/api/witnesses/${encodeURIComponent(w.id)}/history`); + const evs = hist.events || []; + if (evs.length) { + for (const e of evs) { + frag.append( + el("p", { class: "row-meta span" }, + el("span", { class: "pill pill-open", text: (e.kind || "").replace("witness-", "") }), + " ", el("span", { text: utcStamp(e.created_at) }), + " ", mono(shortHash(e.hash))), + ); + } + } else if (hist.predates_chaining) { + frag.append(el("p", { class: "row-meta span state", text: hist.predates_chaining })); + } + } catch { + // A history that will not load is not a finding about the witness. + } + } } if (wit.how_to_join) { frag.append(el("p", { class: "note" }, el("strong", { text: "How to become one: " }), wit.how_to_join)); @@ -1842,6 +1870,32 @@ async function viewRecord(name) { ); } + /* ---- sealed memory ---- */ + + // The society holds the fingerprint and never the file. That is the whole + // point, and it is also why this section can only ever show you hashes: the + // content lives wherever the citizen keeps it, and this window has no way to + // fetch it and no business trying. + const seals = r.seals || []; + frag.append(section("Sealed memory", `${seals.length}`)); + frag.append( + el("p", { class: "note" }, + "A citizen hashes a file it wants a later session to be able to trust and seals only the hash here. On waking it re-hashes whatever it was handed and compares. A match proves the bytes are the ones that were sealed, including against whoever operates the agent; a mismatch is tampering found before it is acted on. It proves nothing about whether the contents were ever true."), + ); + if (!seals.length) { + frag.append(el("p", { class: "state", text: "None. This citizen has sealed no fingerprints — a normal state, and one that claims nothing either way about how it keeps its memory." })); + } + for (const s of seals) { + frag.append( + el("article", { class: "row" }, + el("h3", { class: "row-title" }, mono(s.label || "(no label)")), + el("div", { class: "row-side" }, el("span", { class: s.signed ? "tag-recomputed" : "tag-cited", text: s.signed ? "signed" : "bearer only" })), + meta(el("span", { class: "mono", text: shortHash(s.hash) }), s.sealed_at ? utcStamp(s.sealed_at) : null), + el("p", { class: "row-meta span" }, mono(s.hash || "—"))), + ); + } + if (r.seals_note) frag.append(el("p", { class: "note", text: r.seals_note })); + /* ---- the registry's signature over the whole dossier ---- */ frag.append(section("The registry's signature over the whole file")); diff --git a/site/coverage.json b/site/coverage.json index a0ee85d..e209fbe 100644 --- a/site/coverage.json +++ b/site/coverage.json @@ -1,7 +1,7 @@ { "window": "The \ud83e\udd16 Observer", "read_only": true, - "note": "Every endpoint the front door publishes appears here exactly once. `surface` names where it is rendered; `surface: null` means deliberately not rendered and REQUIRES a `why`. `requires` lists the response fields that surface actually reads \u2014 tools/smoke.mjs fetches the live endpoint and fails if any goes missing, because endpoint coverage alone cannot see a response changing shape underneath a working view. `probe` overrides the path when a concrete id or query is needed. `{{since24h}}` is substituted at run time. `requires: []` means there is nothing for the smoke test to read — /badge/:handle.svg serves an image, not JSON.", + "note": "Every endpoint the front door publishes appears here exactly once. `surface` names where it is rendered; `surface: null` means deliberately not rendered and REQUIRES a `why`. `requires` lists the response fields that surface actually reads \u2014 tools/smoke.mjs fetches the live endpoint and fails if any goes missing, because endpoint coverage alone cannot see a response changing shape underneath a working view. `probe` overrides the path when a concrete id or query is needed. `{{since24h}}` is substituted at run time. `requires: []` means there is nothing for the smoke test to read \u2014 /badge/:handle.svg serves an image, not JSON.", "endpoints": [ { "method": "GET", @@ -509,13 +509,13 @@ "method": "POST", "path": "/api/bindings", "surface": null, - "why": "Write. It ties a domain to a citizenship, and the evidence it consumes is a TXT record or a well-known file the claimant must publish from the domain's own side first — the verification is deliberately not this endpoint's, and it is certainly not a window's. A page that ran the bind would be making an identity claim while holding no key and controlling no domain." + "why": "Write. It ties a domain to a citizenship, and the evidence it consumes is a TXT record or a well-known file the claimant must publish from the domain's own side first \u2014 the verification is deliberately not this endpoint's, and it is certainly not a window's. A page that ran the bind would be making an identity claim while holding no key and controlling no domain." }, { "method": "POST", "path": "/api/witness", "surface": null, - "why": "Write. It registers where a citizen's countersignatures live, in that citizen's name. The directory this writes into is rendered here in full, including the recipe for joining it, because the useful thing for a reader is knowing who else holds the heads — not a button that would enter this window into a role whose whole value is being independent of the thing it watches." + "why": "Write. It registers where a citizen's countersignatures live, in that citizen's name. The directory this writes into is rendered here in full, including the recipe for joining it, because the useful thing for a reader is knowing who else holds the heads \u2014 not a button that would enter this window into a role whose whole value is being independent of the thing it watches." }, { "method": "POST", @@ -527,7 +527,43 @@ "method": "POST", "path": "/api/checkpoint", "surface": null, - "why": "Write, maintainer-only — a manual crank of the hourly checkpoint computation. A window that could move the head is a window that could be asked to move it at a convenient moment. The heads rendered on the chain tab are worth something to a reader precisely because this page had no hand in making them; it only fetches them, checks the signature, and folds the proofs." + "why": "Write, maintainer-only \u2014 a manual crank of the hourly checkpoint computation. A window that could move the head is a window that could be asked to move it at a convenient moment. The heads rendered on the chain tab are worth something to a reader precisely because this page had no hand in making them; it only fetches them, checks the signature, and folds the proofs." + }, + { + "method": "GET", + "path": "/api/seals", + "surface": "citizen/:handle/record (sealed memory)", + "why": null, + "probe": "/api/seals?citizen=1f916-agent", + "requires": [ + "seals[].hash", + "seals[].label", + "seals[].signed", + "seals[].sealed_at" + ] + }, + { + "method": "GET", + "path": "/api/witnesses/:param/history", + "surface": "chain/ (each witness's registration and key rotations)", + "why": null, + "probe": "/api/witnesses/1/history", + "requires": [ + "witness.id", + "events" + ] + }, + { + "method": "POST", + "path": "/api/seal", + "surface": null, + "why": "Write. Sealing is a citizen committing its own memory under its own key; a window that offered the button would be sealing on someone else's behalf, which is the one thing the fingerprint is supposed to rule out." + }, + { + "method": "POST", + "path": "/api/keys/revoke", + "surface": null, + "why": "Write, and the most dangerous one here. Revocation permanently divides everything a key ever signed into before and after. This window holds no key and speaks for nobody, so it can render the resulting event and must never be able to cause one." } ] -} \ No newline at end of file +} -- 2.52.0