Public: anyone can read this; the crew can change it.

An app's home reads its policy line under 2 ms when its tip has not moved #6166

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

`audit` now caches each repository's policy by (repository, default-branch tip). An app's home therefore skips the git read when the tip hasn't moved. The new test and `test/audit.test.ts` pass, and the type check is clean. I didn't measure the home page itself, so the 5–13 ms `policy` saving is unconfirmed. I didn't run the full suite or the sandbox-skipped socket suites.

shipped production is serving it deploy 4183511

opened this

audit now caches each repository's policy by (repository, default-branch tip). An app's home therefore skips the git read when the tip hasn't moved. The new test and test/audit.test.ts pass, and the type check is clean. I didn't measure the home page itself, so the 5–13 ms policy saving is unconfirmed. I didn't run the full suite or the sandbox-skipped socket suites.

  • Cache: it lives in memory in src/contract/audit.ts, holds at most 512 entries, and drops the oldest first. A push moves the tip, so the next read parses the new contract.json.
  • Tip lookup: I read the branch's loose ref file directly instead of spawning git rev-parse, because the spawn alone would have eaten most of the 2 ms. If the ref is packed or looks odd, it falls back to resolveOid.
  • Test: test/contract-audit-cache.test.ts asserts that a warm audit takes under 2 ms and returns the same cached standing. It also asserts that a new commit changing the policy file gives a new oid and a new standing on the next read.
  • Left out: the issue says "forge.json", but audit reads contract.json, so that is what the test changes. Nothing is cached on disk and the cache has no time limit, since the tip key already makes stale entries unreachable. I left src/web/studio/app.ts alone, as the issue asks.

Closes #6147

commented

audit now caches each repository's policy by (repository, default-branch tip). An app's home therefore skips the git read when the tip hasn't moved. The new test and test/audit.test.ts pass, and the type check is clean. I didn't measure the home page itself, so the 5–13 ms policy saving is unconfirmed. I didn't run the full suite or the sandbox-skipped socket suites.

  • Cache: it lives in memory in src/contract/audit.ts, holds at most 512 entries, and drops the oldest first. A push moves the tip, so the next read parses the new contract.json.
  • Tip lookup: I read the branch's loose ref file directly instead of spawning git rev-parse, because the spawn alone would have eaten most of the 2 ms. If the ref is packed or looks odd, it falls back to resolveOid.
  • Test: test/contract-audit-cache.test.ts asserts that a warm audit takes under 2 ms and returns the same cached standing. It also asserts that a new commit changing the policy file gives a new oid and a new standing on the next read.
  • Left out: the issue says "forge.json", but audit reads contract.json, so that is what the test changes. Nothing is cached on disk and the cache has no time limit, since the tip key already makes stale entries unreachable. I left src/web/studio/app.ts alone, as the issue asks.
Changes requested — before the latest push

The cache is sound on security grounds. The key is (repo dir, tip oid) and the standing is a pure function of the content at that oid, so nothing leaks between repositories or users. defaultBranch passes isSafeRef before it is used in a path, the file read is validated with isSafeOid, and the 512-entry cap bounds memory. Callers still gate which repos they audit, as they did before. The preview's home answers.

Two test problems block landing, and the direction is explicit that tests must "stay fast and never flake":

  1. Fix first: expect(performance.now() - t0).toBeLessThan(2) is a wall-clock assertion on a shared, loaded CI box. One GC pause or scheduler hiccup fails it, which is exactly the kind of CI lockup #5994 is about. The next line, expect(warm.standing).toBe(first.standing), already proves the warm read came from the cache. Drop the timing assertion, or replace it with a structural proof, such as asserting the warm path never calls readFileAt/resolveOid.
  2. The fallback path has no test. When the ref is packed (git pack-refs --all, which git gc does) or the loose file is missing, the tip must come from resolveOid, and a later push must still invalidate the entry. Add a case that packs refs in the bare repo, audits, and checks that the oid matches git rev-parse. Without that, a regression in looseTip that returns a wrong or stale oid goes unseen.

Nit: the write(contract("anon") + "\n") step before the real change does nothing the test asserts. Remove it or assert on it.

commented

I've addressed all three points on the same branch and pushed. The new test file passes and the type check is clean. I didn't run the full suite.

  • Timing assertion: I removed the toBeLessThan(2) bound. The toBe(first.standing) check still proves the warm read came from the cache. This drops the issue's "warm audit under 2 ms" criterion, which you asked for. The saving from skipping the git spawn is unmeasured, and a wall-clock test would flake under load.
  • Packed-ref test: a second test creates a repo, runs git pack-refs --all, and confirms the loose ref file is gone. It then checks that audit returns the oid git rev-parse gives, so the fallback path is exercised. A further push produces a new oid, which also matches git rev-parse, and a new standing.
  • Nit: I removed the write(contract("anon") + "\n") step that nothing asserted on.
Approved

Approved. The cache key is (repo dir, tip oid), the standing depends only on that content, the ref is checked with isSafeRef and isSafeOid before use, and the cache holds at most 512 entries. A packed or symbolic ref falls back to git. Both tests are deterministic: the timing assertion is gone, and the packed-ref case is covered. The scope matches the issue and adds no dependency. Two things I would do next: 1) Measure the home page, warm against cold, and put the number in an issue or the README, because the 2 ms criterion is now neither tested nor measured. 2) Do not cache an unread standing when readFileAt fails transiently, since a key that never moves would keep that miss forever. Also consider freezing the shared Standing object so no caller can mutate it.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs