Skip to content

fix(gtd): preserve RFC 3339 due fidelity in agent-mode presentation#956

Merged
ohdearquant merged 1 commit into
mainfrom
fix-912-due-fidelity
Jul 13, 2026
Merged

fix(gtd): preserve RFC 3339 due fidelity in agent-mode presentation#956
ohdearquant merged 1 commit into
mainfrom
fix-912-due-fidelity

Conversation

@ohdearquant

Copy link
Copy Markdown
Owner

Summary

Fixes #912. gtd.assign returns the normalized properties.due as a top-level due convenience field. Agent-mode presentation (crates/khive-runtime/src/presentation.rs) compacts every top-level ISO-like string except field names in PAYLOAD_TIMESTAMP_FIELDS, and that allowlist held only trigger_at (added for the same defect class in a prior fix). A future task's due was therefore minute-truncated (YYYY-MM-DDTHH:MM) on the way out, even though parse_due already guarantees a full RFC 3339 value with seconds and offset. The nested properties.due was unaffected since the inside_properties guard protects it separately.

Root cause: presentation layer only — not the parser (parse_due already normalizes correctly) and not storage (properties.due round-trips fine). The loss happens exclusively in present()'s Agent-mode timestamp-compaction pass, which treats an unlisted top-level ISO-like field the same as a generic timestamp.

Fix: add "due" to PAYLOAD_TIMESTAMP_FIELDS in crates/khive-runtime/src/presentation.rs, mirroring the existing trigger_at entry. Minimal, single allowlist addition; no change to parsing, storage, or the render shape for tasks without due.

Test plan

  • cargo fmt --all -- --check
  • cargo clippy -p khive-pack-gtd -p khive-runtime --all-targets -- -D warnings
  • cargo test -p khive-pack-gtd (includes new assign_due_survives_agent_mode_presentation_round_trip: assigns a task with an offset-bearing RFC 3339 due, reads it back via gtd.tasks and gtd.next, re-applies Agent-mode presentation, asserts verbatim round-trip)
  • cargo test -p khive-runtime presentation:: (includes new agent_does_not_compact_top_level_due unit test)
  • cargo check --workspace

🤖 Generated with Claude Code

gtd.assign returns properties.due as a top-level convenience field.
Agent-mode presentation compacts every top-level ISO-like string
unless its field name is in PAYLOAD_TIMESTAMP_FIELDS, and that
allowlist held only trigger_at, so a future due date was
minute-truncated on the way out even though parse_due already
normalized it to full RFC 3339 with seconds and offset. The nested
properties.due survived correctly since the inside_properties guard
protects it separately.

Add due to PAYLOAD_TIMESTAMP_FIELDS alongside trigger_at, mirroring
the existing fix for the same defect class on trigger_at.

Regression tests: a presentation-layer unit test for a bare top-level
due field, and a gtd-pack integration test that assigns a task with
an offset-bearing RFC 3339 due, then reads it back through
gtd.tasks/gtd.next and re-applies Agent-mode presentation, asserting
the value round-trips unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@ohdearquant

Copy link
Copy Markdown
Owner Author

APPROVE

Findings: 0 Blocker, 0 High, 0 Medium, 0 Low

Reviewed PR #956 (68f21b8a) against main and issue #912.

Looks Right

  • The PR's root-cause claim is supported by source: render_task copies properties.due to the top-level response at crates/khive-pack-gtd/src/handlers.rs:392-412, while parse_due already returns canonical RFC 3339 at crates/khive-pack-gtd/src/handlers.rs:421-440. Before this patch, the Agent transformer compacted every ISO-like top-level string that was neither under properties nor a named payload timestamp (crates/khive-runtime/src/presentation.rs:553-559). The MCP response seam calls that exact transformer at crates/khive-mcp/src/server.rs:1420-1433.
  • Adding due to the narrowly scoped payload-timestamp set at crates/khive-runtime/src/presentation.rs:389-392 is the correct presentation-layer-only fix. It does not alter parsing, storage, handler shapes, or ADR-078's output-format seam.
  • Tasks without a due date keep their previous Agent response: the existing null branch drops non-lifecycle null fields before the payload-timestamp string check (crates/khive-runtime/src/presentation.rs:528-558). Other timestamp compaction remains scoped and covered by agent_still_compacts_other_top_level_timestamps_alongside_trigger_at at crates/khive-runtime/src/presentation.rs:880-891.
  • The new regression uses an RFC 3339 input with a -04:00 offset and exercises gtd.assign, gtd.tasks, and gtd.next, applying PresentationMode::Agent to each result and asserting the canonical due string survives verbatim (crates/khive-pack-gtd/tests/assign.rs:165-211). The dedicated presentation test also covers the bare top-level field (crates/khive-runtime/src/presentation.rs:894-903).
  • The added Rust comments contain no issue-tracker marker; git diff --check is clean.

Commands Run

  • CARGO_TARGET_DIR="$HOME/.cache/shared-cargo-target/khive" cargo test -p khive-pack-gtd (from crates/): passed, including assign_due_survives_agent_mode_presentation_round_trip.
  • CARGO_TARGET_DIR="$HOME/.cache/shared-cargo-target/khive" cargo clippy --workspace --all-targets -- -D warnings (from crates/): passed.
  • CARGO_TARGET_DIR="$HOME/.cache/shared-cargo-target/khive" cargo fmt --all -- --check (from crates/): passed.
  • git diff --check origin/main...HEAD: passed.

What I Did Not Check

  • No full workspace test suite or live MCP-daemon smoke run; the changed GTD package tests and required workspace static gates were run.

Re-Review Guidance

  • No re-review needed.

Domain utility: SKIPPED — no separate domain corpus was available or needed for this narrowly scoped presentation-layer review.

@ohdearquant ohdearquant marked this pull request as ready for review July 13, 2026 16:21
@ohdearquant ohdearquant enabled auto-merge (squash) July 13, 2026 16:21
@ohdearquant ohdearquant merged commit eac673b into main Jul 13, 2026
23 checks passed
@ohdearquant ohdearquant deleted the fix-912-due-fidelity branch July 13, 2026 19:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

gtd.assign top-level 'due' convenience field loses RFC 3339 fidelity in agent-mode presentation

2 participants