refactor: les commentaires disent cap-surface et cap-enforcement
Suite du balayage commencé dans la doc : 20 occurrences de P1a/P1b dans les commentaires, les titres de tests et le README. Les phrases ont été récrites, pas substituées : « the breach P1a opened » devient « the breach that cap-surface opened », et « labelled P1b's » ne survivait pas à un nom plus long. Un lecteur qui n'a jamais entendu ni l'un ni l'autre doit comprendre la phrase. L'avertissement de déploiement du README garde sa force et gagne un nom : « "anonymous" or "private" until cap-enforcement lands per-document encryption. » Reste une occurrence dans e2e/polyfill-entry.ts, qui part avec le lot e2e.
This commit is contained in:
@@ -0,0 +1,7 @@
|
|||||||
|
# Doc-debt — app-contract
|
||||||
|
|
||||||
|
> Presence of a block = doc to update. Processed → delete the block; no blocks left → delete this file.
|
||||||
|
> One block = one "big change": `why` + `files` + `verify` (leaves to review).
|
||||||
|
|
||||||
|
## Raw markers (consolidate into blocks, then delete)
|
||||||
|
- TOUCHED packages/polyfill/README.md @2026-08-11 (session f93872b5-293a-4916-a353-181409a96d42)
|
||||||
@@ -22,7 +22,7 @@ Per-symbol, with the target signature and an epistemic label on every claim:
|
|||||||
> has the shape of the real model — you hold a document's `ReadCap` or you do not read
|
> has the shape of the real model — you hold a document's `ReadCap` or you do not read
|
||||||
> it, and there is no authorization list anywhere — but nothing is encrypted yet and
|
> it, and there is no authorization list anywhere — but nothing is encrypted yet and
|
||||||
> the stand-in key is a constant. Nothing this library does may be described as
|
> the stand-in key is a constant. Nothing this library does may be described as
|
||||||
> "anonymous" or "private" until per-document encryption lands (P1b).
|
> "anonymous" or "private" until cap-enforcement lands per-document encryption.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ export function holdOwnCap(id: string, scope: Scope, doc: Nuri, cap: ReadCap): v
|
|||||||
const caps = getCaps();
|
const caps = getCaps();
|
||||||
// `learn(cap)`, not `open(doc, scope)` — the cap must be the SAME value that was
|
// `learn(cap)`, not `open(doc, scope)` — the cap must be the SAME value that was
|
||||||
// written to the Store branch, not a second one minted from the NURI. They agree
|
// written to the Store branch, not a second one minted from the NURI. They agree
|
||||||
// today only because the stand-in value is a constant; with a real key (P1b) a
|
// today only because the stand-in value is a constant; with a real key (cap-enforcement) a
|
||||||
// second mint would produce a DIFFERENT key and the document would be unreadable
|
// second mint would produce a DIFFERENT key and the document would be unreadable
|
||||||
// by the very session that created it. Mint once, store it, hold that one.
|
// by the very session that created it. Mint once, store it, hold that one.
|
||||||
caps.learn(cap);
|
caps.learn(cap);
|
||||||
|
|||||||
@@ -46,10 +46,11 @@
|
|||||||
* reference. Filed apart (`learnFromPublicStore`) because it grants reading only.
|
* reference. Filed apart (`learnFromPublicStore`) because it grants reading only.
|
||||||
*
|
*
|
||||||
* ── What this module does NOT do ──────────────────────────────────────────
|
* ── What this module does NOT do ──────────────────────────────────────────
|
||||||
* Enforce. The shape is right after P1a; the isolation is still fake. Per-document
|
* Enforce. The shape is right, delivered by cap-surface; the isolation is still
|
||||||
* encryption and closing the read paths that bypass the guard (an ANCHORLESS
|
* fake. Per-document encryption and closing the read paths that bypass the guard
|
||||||
* `docs.sparqlQuery`, the inbox, `store-registry`, `subscribe`, `open-repo`) are P1b.
|
* (an ANCHORLESS `docs.sparqlQuery`, the inbox, `store-registry`, `subscribe`,
|
||||||
* Nothing may be claimed "anonymous" or "private" until then.
|
* `open-repo`) are cap-enforcement's job. Nothing may be claimed "anonymous" or
|
||||||
|
* "private" until then.
|
||||||
*
|
*
|
||||||
* The write caps below (`grantWrite`, `governsWrite`, `canWrite`, `hasWritePolicy`) are
|
* The write caps below (`grantWrite`, `governsWrite`, `canWrite`, `hasWritePolicy`) are
|
||||||
* **inert, not partial** — a distinction the docs got wrong until 2026-08-07, when an
|
* **inert, not partial** — a distinction the docs got wrong until 2026-08-07, when an
|
||||||
@@ -57,7 +58,7 @@
|
|||||||
* `hasWritePolicy()` is permanently false and the `ng-proxy` guard they feed never fires
|
* `hasWritePolicy()` is permanently false and the `ng-proxy` guard they feed never fires
|
||||||
* at all. Writing is governed instead by OWNERSHIP, at the write door (`reach.ts`
|
* at all. Writing is governed instead by OWNERSHIP, at the write door (`reach.ts`
|
||||||
* `assertMayWrite`) — which is what upstream's `verify_permission` actually checks. These
|
* `assertMayWrite`) — which is what upstream's `verify_permission` actually checks. These
|
||||||
* four are dead surface kept for P1b; do not read them as a working policy.
|
* four are dead surface kept for cap-enforcement; do not read them as a working policy.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { CAP_SEGMENT, hasReadCap, targetOf } from "../model/nuri";
|
import { CAP_SEGMENT, hasReadCap, targetOf } from "../model/nuri";
|
||||||
@@ -74,7 +75,7 @@ import type { Nuri, PrincipalId, ReadCap, Scope } from "../model/types";
|
|||||||
* own header, and put the emulation's one invented value in the file that claims to hold
|
* own header, and put the emulation's one invented value in the file that claims to hold
|
||||||
* only verified target vocabulary.
|
* only verified target vocabulary.
|
||||||
*
|
*
|
||||||
* P1b replaces this single constant with a real key; migration deletes both.
|
* cap-enforcement replaces this single constant with a real key; migration deletes both.
|
||||||
*/
|
*/
|
||||||
const STAND_IN_CAP = "OK";
|
const STAND_IN_CAP = "OK";
|
||||||
|
|
||||||
@@ -117,7 +118,7 @@ export class CapRegistry {
|
|||||||
* been, the holder holds it like any other and this set records only how it got there.
|
* been, the holder holds it like any other and this set records only how it got there.
|
||||||
*/
|
*/
|
||||||
private inPublicStore = new Set<Nuri>();
|
private inPublicStore = new Set<Nuri>();
|
||||||
/** doc NURI → principals holding its WRITE cap. Decorative until P1b. */
|
/** doc NURI → principals holding its WRITE cap. Decorative until cap-enforcement. */
|
||||||
private writers = new Map<Nuri, Set<PrincipalId>>();
|
private writers = new Map<Nuri, Set<PrincipalId>>();
|
||||||
/** Fired whenever a holder gains a cap — a cap delivered asynchronously must
|
/** Fired whenever a holder gains a cap — a cap delivered asynchronously must
|
||||||
* re-trigger the reads that were empty for want of it. */
|
* re-trigger the reads that were empty for want of it. */
|
||||||
@@ -246,7 +247,7 @@ export class CapRegistry {
|
|||||||
* It is a READ grant and nothing else. Upstream a public store makes its repos
|
* It is a READ grant and nothing else. Upstream a public store makes its repos
|
||||||
* world-readable, never world-writable — writing needs the write cap, and
|
* world-readable, never world-writable — writing needs the write cap, and
|
||||||
* `verify_permission` fires on WRITE only. Here the write guard still consults the
|
* `verify_permission` fires on WRITE only. Here the write guard still consults the
|
||||||
* read cap (write caps are decorative until P1b, see the module header), so without
|
* read cap (write caps are decorative until cap-enforcement, see the module header), so without
|
||||||
* this distinction a bare reference to a public document would buy a WRITE — a
|
* this distinction a bare reference to a public document would buy a WRITE — a
|
||||||
* consumer would build on it, and have to unlearn it at migration.
|
* consumer would build on it, and have to unlearn it at migration.
|
||||||
*
|
*
|
||||||
@@ -308,7 +309,7 @@ export class CapRegistry {
|
|||||||
* and when listing the holder's own documents back, which is how a holder's caps are
|
* and when listing the holder's own documents back, which is how a holder's caps are
|
||||||
* rebuilt on a fresh session.
|
* rebuilt on a fresh session.
|
||||||
*
|
*
|
||||||
* Deliberately does NOT touch write caps: those are decorative until P1b, and
|
* Deliberately does NOT touch write caps: those are decorative until cap-enforcement, and
|
||||||
* arming their guard here would be enforcement this batch does not do.
|
* arming their guard here would be enforcement this batch does not do.
|
||||||
*/
|
*/
|
||||||
open(nuri: Nuri, scope: Scope): ReadCap {
|
open(nuri: Nuri, scope: Scope): ReadCap {
|
||||||
@@ -367,7 +368,7 @@ export class CapRegistry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- write caps (decorative until P1b) ----------------------------------
|
// --- write caps (decorative until cap-enforcement) ----------------------------------
|
||||||
|
|
||||||
/** Grant `principal` the WRITE cap of document `doc`. */
|
/** Grant `principal` the WRITE cap of document `doc`. */
|
||||||
grantWrite(doc: Nuri, principal: PrincipalId): void {
|
grantWrite(doc: Nuri, principal: PrincipalId): void {
|
||||||
|
|||||||
@@ -106,8 +106,8 @@ export function assertMayReach(nuri: Nuri, op: string): void {
|
|||||||
*
|
*
|
||||||
* - too lax — a cap received in an inbox passed, so an application could write into a
|
* - too lax — a cap received in an inbox passed, so an application could write into a
|
||||||
* document it merely reads. Someone could ship collaborative editing on it and lose
|
* document it merely reads. Someone could ship collaborative editing on it and lose
|
||||||
* it at migration. It was labelled "P1b's", but P1b is key MATERIAL and this is a
|
* it at migration. It was labelled cap-enforcement's responsibility, but
|
||||||
* model relation;
|
* cap-enforcement is key MATERIAL and this is a model relation;
|
||||||
* - too strict — the owner of her own public document was refused, whenever she opened
|
* - too strict — the owner of her own public document was refused, whenever she opened
|
||||||
* it from its reference before her store had been listed (a deep link, a fresh
|
* it from its reference before her store had been listed (a deep link, a fresh
|
||||||
* session). The comment beside the code asserted the opposite.
|
* session). The comment beside the code asserted the opposite.
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
* can answer is **do I hold this document's cap, or not** — so the key value is the
|
* can answer is **do I hold this document's cap, or not** — so the key value is the
|
||||||
* constant `OK`, which says exactly that and pretends nothing more. What identifies
|
* constant `OK`, which says exactly that and pretends nothing more. What identifies
|
||||||
* the document is the NURI the key is attached to; the value carries no information.
|
* the document is the NURI the key is attached to; the value carries no information.
|
||||||
* Real per-document encryption is P1b's job, and it replaces this one constant.
|
* Real per-document encryption is cap-enforcement's job, and it replaces this one constant.
|
||||||
* Until then, possession is a SHAPE, not a protection.
|
* Until then, possession is a SHAPE, not a protection.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
@@ -896,8 +896,8 @@ const inboxCache = new Map<string, Nuri>();
|
|||||||
* recipient's inbox. That makes the inbox the **bootstrap of the whole
|
* recipient's inbox. That makes the inbox the **bootstrap of the whole
|
||||||
* reachability graph** rather than a side feature — and it is why an inbox has to
|
* reachability graph** rather than a side feature — and it is why an inbox has to
|
||||||
* BELONG to someone. Before this existed, an inbox was any NURI a caller passed,
|
* BELONG to someone. Before this existed, an inbox was any NURI a caller passed,
|
||||||
* so "read the inbox" meant "read anyone's inbox", and since P1a routes caps
|
* so "read the inbox" meant "read anyone's inbox", and since cap-surface routes
|
||||||
* through it, reading someone else's collected the caps addressed to them.
|
* caps through it, reading someone else's collected the caps addressed to them.
|
||||||
*
|
*
|
||||||
* Created on first sight and stable thereafter. Recorded in the doc-shim under its
|
* Created on first sight and stable thereafter. Recorded in the doc-shim under its
|
||||||
* own predicate, read by its OWN query rather than added to the account SELECT: an
|
* own predicate, read by its OWN query rather than added to the account SELECT: an
|
||||||
@@ -1144,7 +1144,7 @@ export async function listMyEntityDocs(id: string, scope: Scope): Promise<Nuri[]
|
|||||||
// Recover the caps by READING the Store branch, never by recomputing them from
|
// Recover the caps by READING the Store branch, never by recomputing them from
|
||||||
// the NURIs — that is the whole point of storing them. A fresh session gets back
|
// the NURIs — that is the whole point of storing them. A fresh session gets back
|
||||||
// exactly what was recorded, and the day the stand-in value becomes a real key
|
// exactly what was recorded, and the day the stand-in value becomes a real key
|
||||||
// (P1b) this path needs no change at all.
|
// (cap-enforcement) this path needs no change at all.
|
||||||
//
|
//
|
||||||
// Scoped to the current holder: another user's store caps are not ours to hold.
|
// Scoped to the current holder: another user's store caps are not ours to hold.
|
||||||
const holder = getCurrentUser();
|
const holder = getCurrentUser();
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* caps.test.ts — the cap surface as KEY POSSESSION.
|
* caps.test.ts — the cap surface as KEY POSSESSION.
|
||||||
*
|
*
|
||||||
* What these prove is a SHAPE, not a protection (the library is deliberately
|
* What these prove is a SHAPE, not a protection (the library is deliberately
|
||||||
* insecure until P1b): the only question the registry can answer is "do I hold
|
* insecure until cap-enforcement): the only question the registry can answer is "do I hold
|
||||||
* this document's cap?", there is no principal to look up in a list, and no
|
* this document's cap?", there is no principal to look up in a list, and no
|
||||||
* function turns a bare reference into a cap.
|
* function turns a bare reference into a cap.
|
||||||
*/
|
*/
|
||||||
@@ -170,7 +170,7 @@ test("a cap arriving fires the change signal — an asynchronous delivery must r
|
|||||||
expect(fired).toBe(1); // unsubscribed
|
expect(fired).toBe(1); // unsubscribed
|
||||||
});
|
});
|
||||||
|
|
||||||
test("write is restricted to write-cap holders (decorative until P1b)", () => {
|
test("write is restricted to write-cap holders (decorative until cap-enforcement)", () => {
|
||||||
const { caps } = registry();
|
const { caps } = registry();
|
||||||
expect(caps.hasWritePolicy()).toBe(false);
|
expect(caps.hasWritePolicy()).toBe(false);
|
||||||
caps.grantWrite("did:ng:o:doc", "alice");
|
caps.grantWrite("did:ng:o:doc", "alice");
|
||||||
|
|||||||
@@ -341,7 +341,7 @@ test("one held caps per virtual WALLET, not per spelling of its id", async () =>
|
|||||||
expect(hasCap(doc)).toBe(false);
|
expect(hasCap(doc)).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
// THE BREACH P1a OPENED. Caps travel as inbox deposits, so an unguarded inbox read
|
// THE BREACH THAT cap-surface OPENED. Caps travel as inbox deposits, so an unguarded inbox read
|
||||||
// let anyone who knew an inbox NURI collect the caps addressed to its owner —
|
// let anyone who knew an inbox NURI collect the caps addressed to its owner —
|
||||||
// defeating directed sharing entirely. Depositing stays open (it is the only way a
|
// defeating directed sharing entirely. Depositing stays open (it is the only way a
|
||||||
// link crosses between wallets at all); reading does not.
|
// link crosses between wallets at all); reading does not.
|
||||||
@@ -393,7 +393,7 @@ test("a fresh session rebuilds the held caps from the scope index (the emulated
|
|||||||
// The Store branch exists so a cap is READ back, not recomputed. Without this test
|
// The Store branch exists so a cap is READ back, not recomputed. Without this test
|
||||||
// the two are indistinguishable: with a stand-in value, re-minting happens to give
|
// the two are indistinguishable: with a stand-in value, re-minting happens to give
|
||||||
// the same string. So corrupt the stored cap and check the corruption wins — proof
|
// the same string. So corrupt the stored cap and check the corruption wins — proof
|
||||||
// the value comes from the store, and proof that P1b's real key will too.
|
// the value comes from the store, and proof that cap-enforcement's real key will too.
|
||||||
test("a document's cap is READ from the Store branch, never recomputed", async () => {
|
test("a document's cap is READ from the Store branch, never recomputed", async () => {
|
||||||
const ng = inject();
|
const ng = inject();
|
||||||
setCurrentUser("alice");
|
setCurrentUser("alice");
|
||||||
@@ -426,7 +426,7 @@ test("the listing and the caps are two separate records", async () => {
|
|||||||
expect(subjects.has("urn:ng-eventually:shim:storeBranch")).toBe(true); // Store branch: readCap
|
expect(subjects.has("urn:ng-eventually:shim:storeBranch")).toBe(true); // Store branch: readCap
|
||||||
});
|
});
|
||||||
|
|
||||||
// P1b will make the stand-in value a real, non-derivable key. The moment it does,
|
// cap-enforcement will make the stand-in value a real, non-derivable key. The moment it does,
|
||||||
// any path that mints a SECOND cap instead of using the stored one breaks: the
|
// any path that mints a SECOND cap instead of using the stored one breaks: the
|
||||||
// creator would hold a key that does not open its own document. This pins that the
|
// creator would hold a key that does not open its own document. This pins that the
|
||||||
// creation path mints exactly once.
|
// creation path mints exactly once.
|
||||||
|
|||||||
@@ -7,9 +7,10 @@ import { resetCaps, resetConfig } from "../src/shared-wallet/bootstrap";
|
|||||||
|
|
||||||
// This suite injects a fake `ng` via configure() and declares WRITE caps —
|
// This suite injects a fake `ng` via configure() and declares WRITE caps —
|
||||||
// which stay an authorization list on purpose: only READING is key possession
|
// which stay an authorization list on purpose: only READING is key possession
|
||||||
// (P1a). The write axis is decorative until P1b (every internal writer bypasses
|
// (delivered by cap-surface). The write axis is decorative until
|
||||||
// this proxy). Reset after each test so the docs.test.ts "not configured" guard
|
// cap-enforcement (every internal writer bypasses this proxy). Reset after each
|
||||||
// still holds and no cap leaks into another suite.
|
// test so the docs.test.ts "not configured" guard still holds and no cap leaks
|
||||||
|
// into another suite.
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
resetConfig();
|
resetConfig();
|
||||||
resetCaps();
|
resetCaps();
|
||||||
|
|||||||
Reference in New Issue
Block a user