Public: anyone can read this; the crew can change it.
A stranger reading an app sees only the app's own Server-Timing; the forge's entries (forge-lag, forge-route and the rest) go to the app's writers #11113
Check · Review · Land · Live done — Landed and live in production.
An app's response now carries the forge's own `forge-*` Server-Timing entries only when the reader may write the app's repository or is a forge admin. Everyone else gets exactly the Server-Timing the app wrote. The forge's own pages now show the `lag` entry only to admins.
shipped production is serving it deploy f9da504
An app's response now carries the forge's own forge-* Server-Timing entries only when the reader may write the app's repository or is a forge admin. Everyone else gets exactly the Server-Timing the app wrote. The forge's own pages now show the lag entry only to admins.
Proxy (src/apps/proxy.ts):
- Right after
admit,readsForgeTimingdecides once per request whether the reader may see the forge's entries: an active writer of the repository, or a non-agent forge admin. A request with no grant is never checked, so a stranger costs no database read. - Every relayed answer now goes out through one new function,
relayAnswer. For anyone else it drops the noted phases and anyforge-stall, and returns the app's response unchanged. That also removes the unprefixedrewriteentry that strangers used to get on HTML pages. - The forge's own
/__forge/*pages still usewithPhases. noteRelayWaitand the loop watch still record every relay on the server side, so the app's speed card and terminal line are unchanged.
Forge pages (src/http/server.ts): lag;dur= is appended only when the request's role is admin. Paths that match no route never read a session, so they carry no lag for anyone.
Nothing reads forge- from a browser. Searching src for serverTiming / server-timing, the only readers are:
src/release/smoke.ts, which reads theappentry;src/tui/screens/api.tsandsrc/web/api_reference.ts, which only show the raw header to whoever made the call.
Nothing in src reads .serverTiming (the browser timing API). /system and RUM take their numbers from perfRecord, loopWorst and noteRelayWait, not from the header.
Tests. Four test files that expected strangers to see these entries now expect the new behaviour:
test/apps-proxy-timing.test.ts: its existing checks now run as the app's owner. New cases show that a visitor and a signed-in non-writer get exactlydb;dur=0.1, app;dur=1.1(or no header at all), and that the writer's copy keeps the app's entries first, unchanged, followed only byforge-*entries.test/relay-stall-name.test.ts: it asserted that a stranger seesforge-lag; it now asserts noforge-entry at all.test/route-timing.test.ts: anonymous responses carry nolag.test/loop-worst.test.ts: readslagwith an admin session, and checks that a stranger and a signed-in non-admin get none.
Left as is:
/healthzJSON still reportsloopLagMsto anyone. Probes depend on it, and this issue covers the header only.- A writer's response keeps the app's own
cache-control, so a shared cache that ignores cookies could store a copy withforge-*entries. I didn't forceprivatethere because it would change the owner's view of their own caching headers. - I couldn't call the
keep_plantool from this run, so the issue has no plan posted.
Checks run:
bun scripts/gate.tsandbun x tsc --noEmitpass.scripts/fast.tsnamed 2,047 test files, too many to run here, so I picked the 16 that cover this change: 126 pass, 0 fail.- I skipped the design lint: nothing under
src/web/changed.
To try it: run curl -sI against the preview's app host while signed out and only the app's Server-Timing comes back. Open it signed in as a writer and the forge-* entries are there. curl -sI / on the forge while signed out shows no lag.
🤖 Generated with Claude Code
Reads clean. Proxied app answers carry forge-* Server-Timing only for an active repo writer or a non-agent admin, decided once from the signed grant, so a stranger costs no read. Every relayed return goes through relayAnswer, which also drops forge-stall and the unprefixed rewrite entry. Forge pages show lag only to admins. The tests cover visitor, non-writer, writer and admin, and the app's own entries stay first and unchanged. The remaining withPhases calls are the /__forge/* pages only. Nits, not blockers: a writer's response keeps the app's cache-control, so a shared cache could store a copy with forge-* entries (consider Vary: Cookie or private when the entries are added); the createUser call in apps-proxy-timing.test.ts is badly formatted; and the withPhases doc comment line is overlong.
An app's response now carries the forge's own
forge-*Server-Timing entries only when the reader may write the app's repository or is a forge admin. Everyone else gets exactly the Server-Timing the app wrote. The forge's own pages now show thelagentry only to admins.Proxy (
src/apps/proxy.ts):admit,readsForgeTimingdecides once per request whether the reader may see the forge's entries: an active writer of the repository, or a non-agent forge admin. A request with no grant is never checked, so a stranger costs no database read.relayAnswer. For anyone else it drops the noted phases and anyforge-stall, and returns the app's response unchanged. That also removes the unprefixedrewriteentry that strangers used to get on HTML pages./__forge/*pages still usewithPhases.noteRelayWaitand the loop watch still record every relay on the server side, so the app's speed card and terminal line are unchanged.Forge pages (
src/http/server.ts):lag;dur=is appended only when the request's role isadmin. Paths that match no route never read a session, so they carry nolagfor anyone.Nothing reads
forge-from a browser. SearchingsrcforserverTiming/server-timing, the only readers are:src/release/smoke.ts, which reads theappentry;src/tui/screens/api.tsandsrc/web/api_reference.ts, which only show the raw header to whoever made the call.Nothing in
srcreads.serverTiming(the browser timing API)./systemand RUM take their numbers fromperfRecord,loopWorstandnoteRelayWait, not from the header.Tests. Four test files that expected strangers to see these entries now expect the new behaviour:
test/apps-proxy-timing.test.ts: its existing checks now run as the app's owner. New cases show that a visitor and a signed-in non-writer get exactlydb;dur=0.1, app;dur=1.1(or no header at all), and that the writer's copy keeps the app's entries first, unchanged, followed only byforge-*entries.test/relay-stall-name.test.ts: it asserted that a stranger seesforge-lag; it now asserts noforge-entry at all.test/route-timing.test.ts: anonymous responses carry nolag.test/loop-worst.test.ts: readslagwith an admin session, and checks that a stranger and a signed-in non-admin get none.Left as is:
/healthzJSON still reportsloopLagMsto anyone. Probes depend on it, and this issue covers the header only.cache-control, so a shared cache that ignores cookies could store a copy withforge-*entries. I didn't forceprivatethere because it would change the owner's view of their own caching headers.keep_plantool from this run, so the issue has no plan posted.Checks run:
bun scripts/gate.tsandbun x tsc --noEmitpass.scripts/fast.tsnamed 2,047 test files, too many to run here, so I picked the 16 that cover this change: 126 pass, 0 fail.src/web/changed.To try it: run
curl -sIagainst the preview's app host while signed out and only the app's Server-Timing comes back. Open it signed in as a writer and theforge-*entries are there.curl -sI /on the forge while signed out shows nolag.🤖 Generated with Claude Code
Closes #11112