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

Appending a cost-ledger entry reads only the file's tail, so the finish path stays fast at month's end #12280

Merged opened this

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

Merged

`appendRun` now reads only the tail of the newest month file, so the finish path no longer grows with the month's entries. `bun test test/cost-ledger.test.ts` passes (13 tests), `bun x tsc --noEmit` is clean, and `bun scripts/gate.ts` printed nothing. I did not run the full suite or `bun scripts/ledger-verify.ts` by hand. The new tests call `verifyDir`, the function that script uses, and it return…

shipped production is serving it deploy 97489a2

opened this

appendRun now reads only the tail of the newest month file, so the finish path no longer grows with the month's entries. bun test test/cost-ledger.test.ts passes (13 tests), bun x tsc --noEmit is clean, and bun scripts/gate.ts printed nothing. I did not run the full suite or bun scripts/ledger-verify.ts by hand. The new tests call verifyDir, the function that script uses, and it returns ok.

What changed, in src/core/ledger.ts

  • Tip lookup: lastHash reads the last 64 KiB (TAIL_BYTES) of the newest month file and drops the partial first line. If that window holds no whole entry, it doubles the window up to the whole file, then falls back to the previous month. A corrupt last line still chains from the last readable entry, with the same one-time log. That log now names the file, not the line number, because the tail read doesn't know it.
  • Torn-line check: isTorn reads one byte instead of the file.
  • Nothing cached: the tip is read from disk on every append, so two processes on one directory still chain from what is on disk.
  • Failure handling: appendRun keeps its try/catch, so it still never throws into the finish path.
  • Test hook: I exported a readStats.bytes counter so tests can assert on bytes read.

Tests

  • The tests are in test/cost-ledger.test.ts, not test/ledger.test.ts, which is the decision-ledger suite. This is the file that already covers src/core/ledger.ts.
  • With a 20 000-entry month file, one append reads at most TAIL_BYTES + 1 bytes. It chains from the right hash, and the chain verifies with 20 001 entries.
  • A corrupt last line longer than the window forces the window to widen. The append still chains from the last readable entry.

Not done: I did not add the "timing under a stated bound" variant. I asserted on bytes read, which is deterministic.

To try: bun test test/cost-ledger.test.ts.

Closes #12272

3 earlier comments · 1 event
commented

appendRun now reads only the tail of the newest month file, so the finish path no longer grows with the month's entries. bun test test/cost-ledger.test.ts passes (13 tests), bun x tsc --noEmit is clean, and bun scripts/gate.ts printed nothing. I did not run the full suite or bun scripts/ledger-verify.ts by hand. The new tests call verifyDir, the function that script uses, and it returns ok.

What changed, in src/core/ledger.ts

  • Tip lookup: lastHash reads the last 64 KiB (TAIL_BYTES) of the newest month file and drops the partial first line. If that window holds no whole entry, it doubles the window up to the whole file, then falls back to the previous month. A corrupt last line still chains from the last readable entry, with the same one-time log. That log now names the file, not the line number, because the tail read doesn't know it.
  • Torn-line check: isTorn reads one byte instead of the file.
  • Nothing cached: the tip is read from disk on every append, so two processes on one directory still chain from what is on disk.
  • Failure handling: appendRun keeps its try/catch, so it still never throws into the finish path.
  • Test hook: I exported a readStats.bytes counter so tests can assert on bytes read.

Tests

  • The tests are in test/cost-ledger.test.ts, not test/ledger.test.ts, which is the decision-ledger suite. This is the file that already covers src/core/ledger.ts.
  • With a 20 000-entry month file, one append reads at most TAIL_BYTES + 1 bytes. It chains from the right hash, and the chain verifies with 20 001 entries.
  • A corrupt last line longer than the window forces the window to widen. The append still chains from the last readable entry.

Not done: I did not add the "timing under a stated bound" variant. I asserted on bytes read, which is deterministic.

To try: bun test test/cost-ledger.test.ts.

commented

No code change this round: the deploy summary you quoted reads as a success, so I found nothing to fix.

  • The summary: "running at … · no tests declared, none run" is the success text from src/apps/runtime.ts. It means the app came up and forge.json's app block has no test command. This pull never changed forge.json, so the preview has always been "no tests declared".
  • The preview: https://lab--12280--forge-trevato-apps.open-platform.sh/healthz returns 200.
  • The log you pasted: it shows only periodic forge-wide SQL and process lines. I saw no error from this pull in it.
  • The tests: bun test test/cost-ledger.test.ts still passes, 13 of 13.

If the deploy is still marked failed, please send the failing line of the log; the excerpt shows none.

The pull itself is unchanged. appendRun reads only the last 64 KiB of the newest month file (src/core/ledger.ts), widening only when that window holds no whole entry. The torn-line check reads one byte. The tip is always read from disk, so two processes still chain correctly. The tests are in test/cost-ledger.test.ts, not test/ledger.test.ts, which is the decision-ledger suite. They cover a 20 000-entry month read within the window, a widened window, and a corrupt last line.

commented

I merged main into the branch, resolved the one conflict, and pushed. The gate and tsc --noEmit are clean, and test/cost-ledger.test.ts passes (14 tests).

  • Conflict: test/cost-ledger.test.ts had two new tests added at the same spot, so I kept both. One is this pull's tail-window test, the other is main's test that an agent run's entry carries the token fields.
  • Gate fix: the gate flagged the TAIL_BYTES comment in src/core/ledger.ts as over 100 columns, so I shortened it.
  • Not run: scripts/fast.ts said the diff is too broad to name tests, because scripts/kept-input.ts changed on main. I ran only the cost-ledger tests; the forge's check runs the full suite on this push.
Approved — before the latest push

The append path now reads a 64 KiB tail window and one byte for the torn check, never caches the tip, and widens only when no whole entry fits. I read the logic: the mid-file fragment is dropped, the window doubles until from===0, and then it falls back to the previous month. The tests cover a 20 000-entry month (bytes <= TAIL_BYTES+1, the chain still verifies), widening past a corrupt oversized last line, and the existing two-writer test still stands. This is internal, so there is no UI to exercise. Two things to do next: (1) readStats is a test hook exported from production code; consider scoping it so it is not a mutable global on the finish path. (2) Add a two-writer test that appends after the tail window has already been exceeded, so the no-cache guarantee is pinned at scale.

commented

I merged main into the branch, resolved the one conflict in src/core/ledger.ts, and pushed.

main had turned the append into a shared appendBody(make, what) helper, with appendRun and the new snapshot entries calling it. I kept that helper and this pull's one-byte isTorn check, and removed the old appendRun header that the merge had left duplicated.

The gate and tsc --noEmit are clean. The 17 tests in the test files that import core/ledger pass, including test/cost-ledger.test.ts and the snapshot tests. The "disk full" line in that output comes from a snapshot test that exercises a failure on purpose.

I did not run the full suite; the forge's check does that on this push.

Approved

Tail-window lookup is correct: partial first line dropped, window doubles to whole file, falls back to the previous month, nothing cached, torn check reads one byte. The test with a 20 000-entry month asserts bytes read <= TAIL_BYTES+1 and that the chain verifies. The widening test covers a corrupt oversized last line. Preview answers 200. Non-blocking follow-ups: readStats is a mutable global exported from production code (consider injecting a counter or spying on the fs read); add a two-writer test that appends after the file exceeds the window. Tests live in test/cost-ledger.test.ts, which is the suite that covers this module.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs