Skip to content

Adopt explicit-UTC YYYY-MM-DDTHH:MM:SSZ timestamps for assertion date fields #766

Description

@williamthorsen

Problem

Assertion date fields (created, updated, last-verified) are stored as date-only YYYY-MM-DD with no timezone marker. Two failures follow:

  1. Unmarked timezone, so agents get it wrong, repeatedly. Nothing in the stored value says the date is UTC. Agent harnesses inject a local "today" into the agent's context, while the write helper derives created/updated from the real UTC clock. Near the UTC-midnight boundary these are different calendar days, so an agent supplying --last-verified from its local clock disagrees with the helper's UTC created.
  2. Day-truncation hides the disagreement. A real incident: a note was written with created: 2026-06-09 (helper, UTC) and last-verified: 2026-06-08 (agent, local), i.e. verified before it was created, produced silently. With sub-day precision the two values would have been visibly adjacent across midnight; truncating to the day erased which side each fell on.

Context

  • A single parse chokepoint absorbs the format change. Every staleness and freshness decision funnels through one helper, computeAgeDays, which returns whole days. Nothing else in the codebase parses a stored date value, sorts notes lexically by date, or depends on sub-day precision. The stored format can gain precision without rippling into consumers.
  • captured-at is the in-repo precedent, at a different precision. The event record family already stores captured-at as a full ISO-8601 instant, but at millisecond precision. Aligning both families on one second-precision format is the consistency win, not a new convention.
  • Legacy notes can't be migrated cleanly, so they aren't. The established coding vault holds ~275 assertion notes already on date-only values, and their created dates are hand/template-authored semantic dates not recoverable from git history. Rewriting them would be large and would fabricate sub-day times the records never had. Because computeAgeDays reads both forms identically at day resolution, leaving legacy values in place is lossless for every consumer.
  • Coherence is cheaper to guarantee at the writer than the validator. With one clock owning all three dates at creation, last-verified < created becomes structurally impossible to produce, so no cross-field validation rule (and the edit-time friction it would add to legacy notes) is needed.

Proposed solution

Store created, updated, last-verified, and event captured-at as explicit-UTC second-precision timestamps, YYYY-MM-DDTHH:MM:SSZ, sourced from a single authoritative clock.

  • One clock at creation. kb-add derives last-verified from the same instant it stamps created (a freshly authored note is verified as of creation), so the three dates start equal and diverge only as the note is edited or re-verified. The agent-supplied --last-verified flag, the entry point for a foreign-clock date, is removed.
  • Permanent dual-accept validation. Frontmatter validation accepts both the legacy bare-date form and the new timestamp form, and rejects genuinely malformed values, including a timestamp with no Z marker. Writers emit only the timestamp form. This is a lasting compatibility policy, not a transition window; legacy notes keep their day-resolution values.
  • Consumers unchanged in granularity. The single age helper parses both forms and continues to return whole-day ages, so staleness and freshness ranking behave as before.
  • Coherence guaranteed at the writer. Because every writer orders last-verified at or after created, an incoherent last-verified < created record cannot be produced; no cross-field validator is introduced.

Acceptance criteria

Must have

  • created, updated, and last-verified are written as explicit-UTC second-precision timestamps in the form YYYY-MM-DDTHH:MM:SSZ.
  • Event captured-at is written in the same second-precision form, so timestamp representation is consistent across the assertion and event record families.
  • For any note kb-add writes, created, updated, and last-verified all originate from one creation instant; the --last-verified flag is removed so no agent-supplied date from a foreign clock can enter.
  • No writer can produce a record where last-verified precedes created (kb-add writes them equal; kb-edit --verify sets last-verified to an instant at or after created).
  • Frontmatter validation accepts both the bare YYYY-MM-DD form (legacy notes) and the full YYYY-MM-DDTHH:MM:SSZ form, and rejects malformed values, including a timestamp lacking the explicit Z marker or carrying an unreal calendar date or time. The permanent dual-accept policy (legacy notes are not migrated) is documented.
  • kb-curate staleness detection and kb-retrieve freshness ranking compute correct whole-day ages from both date forms.
  • Documentation, help text, and usage examples are updated to remove the --last-verified flag (including the kb-add skill doc), leaving no dangling references.
  • New and modified behavior in this change is covered by tests.

Nice to have

  • The kb create schema seed gains a one-line note documenting the timestamp date convention, so newly provisioned stores explain it.

Metadata

Metadata

Labels

featureAdded or improved external functionalityscope:kb

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions