Summary
classify() in src/rpc/ttl.ts owns the rule for what counts as archived and what counts as low:
remaining <= 0 is archived, remaining < threshold is low. decideMaintenance() in
src/keeper/policy.ts then re-implements the same two comparisons to sort readings into
archivedKeys and lowKeys.
One rule, encoded twice. Every TtlReading already carries the status that classify produced,
so the second copy is reading data it could have trusted. If the boundary ever moves, it moves in
one place and stays wrong in the other — and the boundary is load-bearing, because Soroban's
extend_ttl only writes when remaining life is strictly below the threshold.
Acceptance Criteria
Tech Stack
TypeScript 5.7 on Node 22+ (CI runs 22 and 24), ESM with moduleResolution: "NodeNext" — relative
imports need the .js extension. @stellar/stellar-sdk is pinned to an exact version; verify RPC
API shape against the installed copy, not against documentation. Tests are Vitest under test/ and
run without a network. npm run format:check, npm run lint, npm run build and npm test must
all be clean.
Summary
classify()insrc/rpc/ttl.tsowns the rule for what counts as archived and what counts as low:remaining <= 0is archived,remaining < thresholdis low.decideMaintenance()insrc/keeper/policy.tsthen re-implements the same two comparisons to sort readings intoarchivedKeysandlowKeys.One rule, encoded twice. Every
TtlReadingalready carries thestatusthatclassifyproduced,so the second copy is reading data it could have trusted. If the boundary ever moves, it moves in
one place and stays wrong in the other — and the boundary is load-bearing, because Soroban's
extend_ttlonly writes when remaining life is strictly below the threshold.Acceptance Criteria
decideMaintenancesorts onreading.statusrather than re-deriving it fromremaining.thresholdstays in the decision'sreasonstring, which is what makes a log lineself-explanatory.
classifyand confirm the policy tests fail —proving there is now one place to change rather than two.
Tech Stack
TypeScript 5.7 on Node 22+ (CI runs 22 and 24), ESM with
moduleResolution: "NodeNext"— relativeimports need the
.jsextension.@stellar/stellar-sdkis pinned to an exact version; verify RPCAPI shape against the installed copy, not against documentation. Tests are Vitest under
test/andrun without a network.
npm run format:check,npm run lint,npm run buildandnpm testmustall be clean.