| 1 | # Census approval: edge-asset-cache-2 |
| 2 | |
| 3 | Ruling `census-ruling-edge-asset-cache-2`. The rows carry it from the |
| 4 | start (AGENTS.md, The census), so nothing flips after the approval. |
| 5 | |
| 6 | Status: APPROVED by Paul, as census owner, in the session on 2026-10-24 |
| 7 | (recorded on #280), on branch tip 7ab21e8f. Re-sealed at the merges with |
| 8 | main b612e710 and 6c75ad81, which put wake-latency-2 and |
| 9 | sec-mail-user-boundary in the chain first: the rows and fingerprints are |
| 10 | unchanged, only the seal moved. |
| 11 | |
| 12 | 17 changes need a decision (below), which `make census-propose` cannot |
| 13 | write. They are written by hand, following the overlays in the chain. Each |
| 14 | is the census owner's call, not an approval. There are no approvals |
| 15 | (`body`, `sites`): no existing unit changed. |
| 16 | |
| 17 | ## What changed |
| 18 | |
| 19 | The branch adds one new package, `internal/assetcache`: the edge asset |
| 20 | cache's disk store. It keeps cached asset responses on a box's disk, under |
| 21 | a budget and a free-space floor (spec |
| 22 | `docs/superpowers/specs/2026-10-24-edge-asset-cache-design.md`, §7). |
| 23 | Nothing imports it yet. Plan 3 wires it into the agent |
| 24 | (`/opt/carlos/carlos agent`, `carlos-edge.service`, user carlos) and opens |
| 25 | it at `$CARLOS_DATA_DIR/asset-cache`. |
| 26 | |
| 27 | A `Store` reads only files it wrote itself, in the one directory it is |
| 28 | opened over (`<id>.meta`, `<id>.<v>.body`, `<id>.<v>.gz` and their |
| 29 | temporaries), plus a statfs of that directory. No unit reads a record, a |
| 30 | registry, a unit file or anything another component writes. None makes a |
| 31 | root decision. A `.meta` that does not parse is deleted with its files, |
| 32 | never trusted. |
| 33 | |
| 34 | This card is refreshed for the final-review fix wave: admission order, |
| 35 | the free-space sample race, the budget enforced at `Open`, and the retry |
| 36 | of failed unlinks. Three units' bodies changed. Their reads did not: |
| 37 | |
| 38 | - `Fill.commit` releases its reservation through the helper that ticks |
| 39 | the release counter. |
| 40 | - `Open` evicts down to the budget after the scan. |
| 41 | - `Store.scan` indexes the oldest `StoredAt` first. |
| 42 | |
| 43 | Their digests and `@legacy` fingerprints, the envelope and the seal are |
| 44 | recomputed. No read was added or removed. The 17 rows are otherwise as |
| 45 | before. |
| 46 | |
| 47 | ## Decisions (the census owner's call) |
| 48 | |
| 49 | Every row is class `new`. Proposed disposition for all 17: class `legacy`, |
| 50 | task `accepted-legacy`, basis |
| 51 | `accepted-legacy;rev3=census-ruling-edge-asset-cache-2;asset cache files under $CARLOS_DATA_DIR/asset-cache, written and read only by this package`. |
| 52 | There is no `cache` class. |
| 53 | |
| 54 | The last column says whether each site really touches a file or only matches |
| 55 | one of the scanner's read names. Name matches only, with no real read: `Open`, |
| 56 | `Fill.commit` (`crypto/rand.Read`) and both `var Statfs`. `Fill.open`'s |
| 57 | `Read` is also `crypto/rand.Read`. |
| 58 | |
| 59 | | unit | class | reads | what it reads | real read? | |
| 60 | |---|---|---|---|---| |
| 61 | | `store.go` `Open` | new | +file:Open, +file:Statfs | nothing itself: `Open` is the function's own name, and `Statfs` is the variable it sets as the default `FS`. The listing is `Store.scan` | no, both name matches | |
| 62 | | `store.go` `Store.scan` | new | +file:ReadDir | lists the cache directory at `Open` | yes | |
| 63 | | `store.go` `Store.loadMeta` | new | +file:ReadFile | one `<id>.meta` during the scan | yes | |
| 64 | | `store.go` `Store.bodyMatches` | new | +file:Stat | a body file's size during the scan, checked against its `.meta` | yes | |
| 65 | | `store.go` `Entry.Open` | new | +file:Open ×2 | `os.Open` of a committed body (identity or gzip) to serve it | one real; the other is the method's own name | |
| 66 | | `store.go` `Reader.Read` | new | +file:Read ×2 | reads the file `Entry.Open` opened | one real; the other is the method's own name | |
| 67 | | `store.go` `Reader.ReadAt` | new | +file:ReadAt ×2 | reads the file `Entry.Open` opened | one real; the other is the method's own name | |
| 68 | | `store.go` `Reader.Stat` | new | +file:Stat ×2 | stats the file `Entry.Open` opened | one real; the other is the method's own name | |
| 69 | | `fill.go` `Fill.open` | new | +file:OpenFile, +file:Read | creates the body temporary (O_EXCL, 0600) | `OpenFile` yes; `Read` is `crypto/rand.Read`, a name match | |
| 70 | | `fill.go` `Fill.writeGzip` | new | +file:OpenFile | creates the gzip temporary (O_EXCL, 0600) | yes | |
| 71 | | `fill.go` `Fill.commit` | new | +file:Read | `crypto/rand.Read`, the version drawn per commit | no, name match only | |
| 72 | | `fill.go` `Fill.finish` | new | +file:Lstat | the size of a temporary it is about to remove | yes | |
| 73 | | `fill.go` `writeSynced` | new | +file:OpenFile | creates the `.meta` temporary (O_EXCL, 0600) | yes | |
| 74 | | `fill.go` `syncDir` | new | +file:Open | opens the cache directory to fsync it | yes | |
| 75 | | `statfs_linux.go` `statfsFS.Space` | new | +file:Statfs | `unix.Statfs` of the cache directory, for the free-space floor | yes | |
| 76 | | `statfs_linux.go` `var Statfs` | new | +file:Statfs | the variable's name | no, name match only | |
| 77 | | `statfs_other.go` `var Statfs` | new | +file:Statfs | the variable's name; off Linux `Space` returns `ErrUnsupported` and reads nothing | no, name match only | |
| 78 | |
| 79 | Envelopes pinned: `internal/assetcache` (new, |
| 80 | `93ba49fd3716984a753682f88739cdc3dc3efed459a0892c831a2232f7250ece`). |
| 81 | |
| 82 | Seal: `68ca9635da386f34ef483c790af19e954122c11edc406d64cde8d55d99d12de5` |
| 83 | (sec-mail-user-boundary's) → |
| 84 | `de15b849fcb93b76caca55be924cde41512f8c470f40b4ebdd77ec65ad7e7069`. |
| 85 | Before the merge with 6c75ad81 it was `27b02059…ecd8` → `fb7ba7c4…f8a1`; |
| 86 | before the merge with b612e710, `0ca3baaf…89f2` → `1fa408dd…6827`. |
| 87 | |
| 88 | Overlay: `internal/provision/testdata/plan14-root-reads-edge-asset-cache-2.tsv`, |
| 89 | appended to `internal/provision/testdata/plan14-root-reads-chain.tsv` after |
| 90 | `plan14-root-reads-sec-mail-user-boundary.tsv` (the last overlay on main |
| 91 | at 6c75ad81), whose seal is checked first. |
| 92 | |
| 93 | Record-write census (9B8): unchanged; the package writes no record keys. |
| 94 | Claim routes: unchanged. go.mod: unchanged. |
| 95 | |
| 96 | ## The question |
| 97 | |
| 98 | Approve the 17 new rows as accepted legacy with the basis above, and the |
| 99 | `internal/assetcache` envelope pin, or hold? |
| 100 | |