fix(antigravity-cli): read the per-generation timestamp from the agy 1.1.18 gen_metadata layout

Merged
JU
3mo ago
+675-16

Refs #1184

What broke

crates/tokscale-core/src/sessions/antigravity_cli.rs dates each generation from chatModel.#9.#4, a {#1: seconds, #2: nanos} Timestamp. agy 1.1.18 removed that field. The reporter's decode of a live 1.1.18 gen_metadata row shows chatModel.#9 now carrying #2 = 18446744073709551615 (u64::MAX — an int64 -1, i.e. an "unset" sentinel) and a new #10 holding 8 length-delimited bytes. With #4 gone, every row fell through to the session-created stamp from trajectory_metadata_blob.#2, so on a session that has been open since Aug 22 every turn is bucketed to Aug 22 and tokscale models -c antigravity-cli --today reports 0.

What this does

#9.#4 is still read first, unchanged, on its original ms > 0 filter — pre-1.1.18 databases and older installs keep their exact behaviour, and a row carrying both fields still prefers the explicitly typed #4 Timestamp.

When #4 is absent, #9.#10 is decoded, most-structured shape first: a nested {#1: seconds, #2: nanos} Timestamp; a nested message holding the epoch scalar in field 1 as a varint or a fixed64; or the payload itself as 8 raw fixed64-style bytes, little-endian (protobuf's own byte order) then big-endian. Each candidate integer is unit-detected by magnitude — seconds, milliseconds, microseconds, nanoseconds — which is sound only because those four windows are disjoint over any believable date, so at most one unit can produce an in-range result.

Every inferred reading is then range-checked against 2020-01-01 through five years from now. Anything outside that window is discarded and the next candidate is tried; when they all fail, the existing session-created fallback takes over. That direction is deliberate: a wrong per-turn date silently corrupts day buckets and the server-side monotonic ratchet, which is worse than the known-conservative bug this replaces, so the failure mode is always "degrade to today's behaviour", never "produce a plausible-looking wrong date".

#9.#2 is never consulted as a time, and u64::MAX is rejected by name in the scalar decoder so no candidate path can promote the sentinel into a date.

A raw IEEE-754 f64 reading of the same 8 bytes is deliberately not attempted. It is the one candidate with a non-trivial false-positive rate against a non-timestamp payload — any double in the 2^30-ish exponent range decodes to a plausible epoch-second count — and nothing in the field dump points at it.

Please verify before this is treated as closed

I have no agy CLI install and no 1.1.18 gen_metadata database to decode against, so the #10 layout here is inferred from the field dump in the issue, not observed. What is certain from the dump is the field number, the wire type (2, length-delimited) and the length (8 bytes); what is inferred is which of the shapes above those 8 bytes actually are. @yourlovelyscent-beep — if you can run a build of this branch against your machine and confirm tokscale hourly -c antigravity-cli now spreads turns across the days you actually used them (rather than all sitting under the session-start hour), that closes the loop. If it still buckets everything at session start, the 8 bytes are something else entirely and the raw bytes of chatModel.#9.#10 from one row would pin it down.

Cache

ClientId::AntigravityCli has no parser_version() arm in message_cache.rs and the lane does not go through parse_cached_lane — both call sites in lib.rs parse gen_metadata directly on every run. There are no cached session-start-dated messages to invalidate, so nothing is bumped here.

Tests

cargo test -p tokscale-core antigravity — 45 passed, 0 failed. cargo clippy -p tokscale-core --all-targets -- -D warnings and cargo fmt --all -- --check both clean. Full cargo test -p tokscale-core green (1850 passed).

New coverage in antigravity_cli.rs: every accepted #9.#10 shape (nested Timestamp, nested varint/fixed64 scalar, raw 8 bytes in both byte orders, across all four units) dates the turn; the u64::MAX sentinel is never read as a time, whether it sits in #9.#2 or arrives inside the #10 payload; an opaque 8-byte payload and in-shape-but-out-of-window values (1990, ten years out) all fall back to the session stamp rather than producing a date; the explicit #9.#4 Timestamp keeps priority over #9.#10; and an end-to-end database whose rows all use the 1.1.18 layout dates each row to its own turn instead of collapsing onto the session-created date.

Verified by falsification, twice. Removing the #9.#10 branch fails exactly the two new-layout tests and nothing else. Separately, neutering the plausibility window so it accepts everything fails four tests including the fallback guard — so the range check is load-bearing, not decoration.


Summary by cubic

Fixes per-generation timestamps for Antigravity CLI after agy 1.1.18 removed chatModel.#9.#4. Previously, every turn fell back to the session start; now we parse the new #9.#10 payload so turns are dated when they happened, while pre-1.1.17 stays unchanged.

Bug Fixes

  • Interpret #9.#10 as a nested Timestamp, a nested scalar in field 1 (varint or fixed64), or 8 raw fixed64 bytes (LE/BE).
  • Keep #9.#4 as the first choice when present and >0.
  • Detect seconds/millis/micros/nanos by magnitude and accept only plausible times (>= 2020-01-01 and <= now + 5y).
  • Ignore #9.#2 and explicitly reject u64::MAX so the unset sentinel never becomes a date.
  • Fall back to the session-created timestamp when nothing decodes in-range.
  • No cache invalidation or migrations; older databases behave the same.
  • Add tests covering all accepted shapes and the fallback guards.