READ-ONLY MODE NO FILES CHANGED

Why does the refresh-token check reject tokens issued before the last deploy?

A worked question from a fictional codebase — answered by reading only. No file was opened for editing, no command was run to change anything, and the proposed fix below exists only as text.

Repository: harborline-web (fictional)  ·  Tools used: Read, Grep, Glob only  ·  Mode: /question — ask, don't touch
01 — Short answer

The answer, in one paragraph

The check compares each token's issued-at time against a deployment timestamp recorded at deploy time. Any token issued before that timestamp is rejected outright — there is no grace window, and the timestamp is never rotated afterwards. Tokens from before the last deploy are therefore rejected by design of the comparison, not by a bug in the clock.

What follows is the trace that earned that paragraph: every hop the code actually makes, the evidence for each claim, the explanations that were ruled out, and the fix — written out, never applied.

02 — The trace

How the code actually gets there

Read in execution order, not in file order. Each step names the file that proves it.

1

A refresh request arrives

auth/refresh.ts:18 — the handler takes the presented token and passes it, unchanged, to the verifier. Nothing is rewritten or re-issued at this stage.

2

The verifier loads the deployment timestamp

auth/tokens.ts:74 — the verifier reads the stored deployment timestamp, written once per deploy. It does not ask when the token was last used, only when the deploy happened.

3

Issued-at is compared, not expiry

auth/tokens.ts:87 — the comparison is token.issuedAt < deployment.timestamp. Expiry is checked later and is not what rejects these tokens.

4

Rejection is immediate, with no grace window

auth/tokens.ts:89–91 — a token older than the timestamp is rejected outright. No fallback, no refresh-and-retry, no grandfathering of still-valid tokens.

5

The timestamp is never rotated afterwards

deploy/record.ts:22 — the timestamp is written at deploy time and left alone. So every pre-deploy token stays rejected until it is re-issued, however valid it still is.

03 — Evidence

Every claim, with its citation

ClaimEvidence
The check compares issued-at against the deployment timestampauth/tokens.ts:87
Pre-deploy tokens are rejected outright, with no grace windowauth/tokens.ts:89–91
The deployment timestamp is written at deploy time and never rotateddeploy/record.ts:22
Ruled out: clock skew — the comparison never touches wall-clock timeauth/tokens.ts:87
Ruled out: a missing migration — the timestamp column exists and is readdb/schema.ts:41
04 — Proposed change

A fix, written out — not applied

The smallest change that answers the question: give pre-deploy tokens a 7-day grace window instead of instant rejection. This is diff text only. It was not run, not saved, and not applied to any file.

PROPOSED CHANGE — NOT APPLIED  ·  auth/tokens.ts
--- a/auth/tokens.ts
+++ b/auth/tokens.ts
@@ -84,8 +84,8 @@
 function verifyRefresh(token) {
   const deployment = loadDeploymentTimestamp();
-  if (token.issuedAt < deployment.timestamp) return reject("stale");
+  if (token.issuedAt < deployment.timestamp - GRACE_WINDOW_DAYS * 86400) return reject("stale");
   return verifySignature(token);
 }

Behaviour change: tokens issued up to 7 days before the deploy would be accepted again; older tokens would still be rejected. Test that would prove it: present a token issued 3 days before the last deploy and confirm it verifies, and a token issued 30 days before and confirm it still rejects.

This is a sample output of the readonly-question skill — a fictional codebase, a fictional question, and a real contract. The full method (contract, evidence rules, escape hatch) is inside the paid skill.