Skip to content

Commit 1ebf4ca

Browse files
PerishCodeLooper
andauthored
docs(tools-pack): add build cache contract (#6207)
* docs(tools-pack): add build cache contract Document the tools-pack build-graph cache as a contract: the exact-match acceptance model, determinant rules, materialization-time parameters, the signing boundary, and fail-closed confidence grading. The rules come from an audit of all 11 cache nodes. Two are written down because nothing currently enforces them: a node key must carry the key of every upstream node it consumes, and a node key must not restate a list that already exists as a constant. Known low-confidence points are declared explicitly so fail-closed grading stays checkable rather than becoming a verbal convention. * docs(tools-pack): clarify cache materialization exceptions Generated-By: looper 0.11.2 (runner=fixer, agent=codex) * docs(tools-pack): document NSIS base version scope Generated-By: looper 0.11.2 (runner=fixer, agent=codex) --------- Co-authored-by: Looper <looper@noreply.github.com>
1 parent 30501a2 commit 1ebf4ca

3 files changed

Lines changed: 189 additions & 0 deletions

File tree

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ This file is the single source of truth for agents entering this repository. Rea
1111
- References and current plans: `docs/references.md`, `docs/code-review-guidelines.md`, `specs/current/maintainability-roadmap.md`, `specs/current/ci.md` (CI scope confidence methodology — required before changing confidence or guard fields in `scripts/scopes.ts`).
1212
- Directory-level agent guidance: `.github/AGENTS.md`, `apps/AGENTS.md`, `packages/AGENTS.md`, `tools/AGENTS.md`, `e2e/AGENTS.md`.
1313
- Packaged auto-update architecture and high-confidence local harness: read `tools/pack/AGENTS.md` section "Packaged auto-update architecture and harness" before touching packaged updater code, release-channel identity, installer behavior, or updater UI.
14+
- Packaged build cache contract: `tools/pack/CACHE.md` (determinant rules, materialization-time parameters, confidence grading — required before changing any build-cache node key).
1415

1516
## Workspace directories
1617

tools/pack/AGENTS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
Follow the root `AGENTS.md` and `tools/AGENTS.md` first. This tool owns the repo-external packaged build/start/stop/logs command surface.
44

5+
Read `tools/pack/CACHE.md` before changing any build-cache node key, adding a cache node, or changing what a cached node reads or writes. It is the source of truth for the build-graph cache under `--cache-dir`: determinant rules, materialization-time parameters, the signing boundary, and confidence grading.
6+
57
## Owns
68

79
- Local packaging orchestration for packaged Open Design artifacts.

tools/pack/CACHE.md

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
# tools-pack build cache contract
2+
3+
This file is the source of truth for the `tools-pack` build-graph cache: the
4+
cache under `--cache-dir` that stores packaged build artifacts as
5+
content-addressed nodes. Read it before changing any node key, adding a node,
6+
or changing what a node writes.
7+
8+
It records **current state only**, not change history — same convention as
9+
`specs/current/ci.md`.
10+
11+
Out of scope: the GitHub Actions cache that wraps this store. That layer is
12+
owned by `.github/`; it restores and saves the store as an opaque directory and
13+
carries no correctness obligation (see **Why coarse restore is safe** below).
14+
15+
## Cache model
16+
17+
A node is `{ id, key, outputs, build, invalidate }`, acquired through
18+
`ToolPackCache` (`src/cache.ts`).
19+
20+
- `keyHash = hash(node.id + "\n" + node.key)`; the entry lives at
21+
`entries/<node.id>/<keyHash>/` with a `manifest.json`.
22+
- An entry is accepted only when `manifest.key === node.key` exactly. A
23+
mismatch is reported as `key mismatch` and the node rebuilds
24+
(`cache.ts:270-282`). There is no fuzzy or prefix matching inside the store.
25+
- `invalidate` is an additional per-node veto applied to an otherwise-valid
26+
entry (for example `win.packaged-app` re-validates its native rebuild
27+
output).
28+
- `materialize` copies entry outputs to their workspace locations. Steps that
29+
run on the materialization path execute on **both** the hit and the miss
30+
path.
31+
32+
### Why coarse restore is safe
33+
34+
Because acceptance is an exact `node.key` comparison, a restored store is a
35+
*pool of candidates*, not an authority. Entries that do not match are ignored.
36+
An outer layer may therefore restore a broader or older store than strictly
37+
requested without risking a wrong build — it only affects how much work is
38+
skipped, never what is produced.
39+
40+
This property is what the surrounding CI caching depends on. Do not weaken it
41+
by introducing prefix or best-effort matching inside the store.
42+
43+
## Node inventory
44+
45+
The build-graph cache is almost entirely Windows-specific.
46+
47+
| Node | Platform |
48+
| --- | --- |
49+
| `<platform>.workspace-build` | all |
50+
| `win.resource-tree` | win |
51+
| `win.workspace-tarballs` | win |
52+
| `win.packaged-app` | win |
53+
| `win.electron-builder-dir` | win |
54+
| `win.nsis-payload-base` | win |
55+
| `win.nsis-payload-overlay` | win |
56+
| `win.nsis-installer` | win |
57+
| `win.portable-zip` | win |
58+
| `win.launcher-payload-base` | win |
59+
| `win.launcher-payload` | win |
60+
61+
`mac` and `linux` have `<platform>.workspace-build` only.
62+
63+
## Determinant rules
64+
65+
**R1 — A node key must cover every input that determines the node's output.**
66+
Inputs include file content, configuration values, tool versions, and process
67+
environment. An input that is read by `build` but absent from `key` is a
68+
defect, not an optimization.
69+
70+
**R2 — A node key must carry the key of every upstream node it consumes.**
71+
Re-deriving an upstream node's own inputs is not a substitute: upstream keys
72+
carry inputs that are not file content (see R3), so re-derivation silently
73+
drops them.
74+
75+
Existing links: `win.packaged-app` carries `tarballsKey`;
76+
`win.electron-builder-dir` carries `packagedAppKey` and `resourceTreeKey`;
77+
`win.nsis-installer` carries `basePayloadKey` and `overlayPayloadKey`;
78+
`win.launcher-payload` carries `sourceKey`.
79+
80+
**R3 — Build outputs are never direct key inputs.** `hashPackageSourcePath`
81+
excludes `dist`, `.next`, `out`, `node_modules`, and `.od`. A node that
82+
consumes another node's build output must obtain that output's identity
83+
through R2, not by hashing the output tree.
84+
85+
**R4 — A node key must not restate a list that already exists as a
86+
constant.** Derive key inputs from the constant instead. Two independent
87+
restatements of the same list drift silently and produce stale entries with no
88+
signal.
89+
90+
**R5 — Every declared input needs a witness.** See **Changing a cache node**.
91+
92+
## Materialization-time parameters
93+
94+
Some values deliberately do **not** enter node keys. They are stamped onto the
95+
output every time the node is materialized, so cached content stays
96+
parameter-agnostic and is specialized on the way out.
97+
98+
Current materialization-time parameters:
99+
100+
- **App version.** `win.packaged-app` omits it. It is applied through
101+
electron-builder `extraMetadata.version`, then rewritten on materialization
102+
by `rewriteUnpackedAppPackageVersion` and `rewriteWinExecutableVersion`, then
103+
verified by `assertMaterializedUnpackedVersionConsistency` — a fail-closed
104+
check over the app `package.json` version, the `open-design-config.json`
105+
`appVersion`, and the Windows executable fixed file version.
106+
- **Namespace / channel and runtime endpoints.**
107+
`win.electron-builder-dir` omits them. `open-design-config.json` — which
108+
carries `namespace`, `amrProfile`, `telemetryRelayUrl`, `updateMetadataUrl`,
109+
`posthogKey`/`posthogHost`, `webOutputMode`, and `namespaceBaseRoot` — is
110+
regenerated on the materialization path by `writePackagedConfig`.
111+
112+
The downstream `win.nsis-payload-overlay`, `win.nsis-installer`,
113+
`win.portable-zip`, and `win.launcher-payload` nodes carry `namespace` and the
114+
full `packagedVersion` in their keys, because their content includes the
115+
already-stamped payload. `win.nsis-payload-base` instead carries only
116+
`versionCore`: its content excludes `Open Design.exe`,
117+
`resources/app/package.json`, and `resources/open-design-config.json`, which
118+
are assigned to the version-bearing overlay.
119+
`win.launcher-payload-base` is the exception: its key carries `namespace`, but
120+
version identity reaches it only indirectly through the upstream `sourceKey`;
121+
the final `win.launcher-payload` archive explicitly carries the
122+
version-bearing `manifest` and `configBody`.
123+
124+
**Requirement.** A value may be a materialization-time parameter only when
125+
both hold:
126+
127+
1. it is re-applied unconditionally on the materialization path, so the hit
128+
and miss paths converge; and
129+
2. the applied value is verified by a fail-closed assertion.
130+
131+
Adding a materialization-time parameter without (2) is not permitted.
132+
133+
> Known asymmetry: app version satisfies (2). The other regenerated config
134+
> fields — `namespace`, `amrProfile`, `telemetryRelayUrl`,
135+
> `updateMetadataUrl`, `posthogKey`, `posthogHost`, `webOutputMode`,
136+
> `namespaceBaseRoot`, and the packaged entrypoint fields — currently satisfy
137+
> only (1): they are rewritten but not asserted.
138+
139+
## Signing boundary
140+
141+
Signing material never enters cache content. `resolveWinSigningCacheKey`
142+
(`src/win/sign.ts`) contributes only the certificate SHA-1, digest algorithm,
143+
timestamp algorithm, and timestamp URL, and appears in the keys of
144+
`win.nsis-payload-overlay`, `win.nsis-installer`, and `win.portable-zip`.
145+
146+
`win.nsis-payload-base` correctly omits signing: it is built before
147+
`ensureSignedUnpacked()`, while the overlay is built after. Keep that ordering
148+
when changing the payload split.
149+
150+
## Confidence tiers
151+
152+
Borrowed from `specs/current/ci.md`.
153+
154+
- **`certain`** — the key provably covers the node's full input closure, and a
155+
witness test demonstrates it (see below). Only `certain` nodes may
156+
participate in cross-run cache reuse policies built on top of this store.
157+
- **low confidence** — anything else.
158+
159+
**Grading is fail-closed: a node is low confidence until a witness proves
160+
otherwise.** A newly added node is low confidence by default.
161+
162+
## Declared low-confidence points
163+
164+
Recorded so that grading stays checkable. These are known and accepted; do not
165+
extend them.
166+
167+
- `<platform>.workspace-build``pnpm-workspace.yaml` is not a key input;
168+
file mode (executable bit) is not hashed by `hashPackageSourcePath`.
169+
- `win.launcher-payload` — the `seed: "nsis-base"` branch takes content from
170+
the NSIS base payload but carries only the literal `"nsis-base"`, not
171+
`WIN_ARCHIVE_CACHE_VERSION`. Bumping that constant without bumping the
172+
launcher payload cache versions mismatches.
173+
174+
## Changing a cache node
175+
176+
1. Read this file and `AGENTS.md` in this directory.
177+
2. If the change alters what `build` reads or writes, update the key in the
178+
same commit.
179+
3. Bump the node's `schemaVersion` / cache-version constant whenever key
180+
semantics change.
181+
4. Add or update the node's witness test in `tests/`. A witness proves both
182+
halves:
183+
- mutating **each declared input** changes the key;
184+
- mutating a **known non-input** (for example a package's `dist` tree)
185+
leaves the key unchanged.
186+
5. Never introduce prefix or best-effort matching inside the store.

0 commit comments

Comments
 (0)