Skip to content

T-DPO2-ensure-ready-stamps-quoted-iso-z

Status: closed/done · Impact: medium · Complexity: small

Auto-generated from a /sdlc:task-work post-mortem. Review and promote to ready before picking up.

The readiness_verified_at schema pattern currently requires a literal Z suffix. PyYAML’s default loader auto-parses unquoted ISO 8601 strings into Python datetime objects and re-emits them with the explicit +00:00 offset — semantically identical to Z but rejected by the pattern. Loosening the schema to accept both RFC 3339 forms eliminates the round-trip drift class without forcing ensure-ready to quote-on-write or strip timezone info. Existing Z stamps remain valid; PyYAML’s natural +00:00 output now also validates. See T-T5RB-consolidate-task-status-enum for the originating incident.

“PyYAML auto-parses unquoted ISO 8601 datetime strings into Python datetime objects, which then round-trip as 2026-05-19T21:02:01+00:00 and fail the schema’s Z-only pattern — task-ensure-ready hit this on first stamp attempt and the bad value landed in a commit before validation caught up.”

plugin/entities/task/schema.json declares:

"pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?Z$"

The strict Z requirement is the constraint that makes PyYAML’s natural output invalid. Both Z and +00:00 mean UTC; the schema treats them as different.

Loosen the readiness_verified_at pattern to accept any RFC 3339 UTC suffix: Z, +00:00, or -00:00. The pattern becomes:

^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]00:00)$

This matches both forms with zero impact on the rest of the contract. Ensure-ready stays simple — it writes whatever PyYAML emits, the schema accepts both.

  1. Update the regex in plugin/entities/task/schema.json (and any sibling entity schemas that carry the same pattern — at minimum audit the field across plugin/entities/*/schema.json).
  2. Run the validator across the full task corpus to confirm both already-stamped Z tasks and any future +00:00 stamps pass.
  3. Add a fixture under plugin/skills/task-ensure-ready/tests/ that stamps a task, runs the YAML round-trip, and asserts the result validates. The fixture should exercise both literal forms so the loosened pattern is pinned against future tightening.
  • plugin/entities/task/schema.json — loosen the readiness_verified_at pattern.
  • plugin/skills/task-ensure-ready/tests/run_evals.py — add the round-trip + both-forms regression case.
  • AC-1: plugin/entities/task/schema.json’s readiness_verified_at pattern accepts both 2026-05-19T21:02:01Z and 2026-05-19T21:02:01+00:00. Sibling entity schemas (epic, milestone, backlog) carrying the same pattern get the same treatment.
  • AC-2: plugin/validators/validate_frontmatter.py docs/planning/tasks/*.md exits 0 against the current corpus (which contains already-stamped Z-form values).
  • AC-3: A case in plugin/skills/task-ensure-ready/tests/run_evals.py asserts both literal forms validate after yaml.safe_load + yaml.safe_dump round-trip.
  • Broader datetime-handling reform across other skills. This task is narrowly the ensure-ready stamp path.
  • none

Spawned by /sdlc:task-work post-mortem of T-T5RB-consolidate-task-status-enum on 2026-05-19.

Captured by /sdlc:task-work on 2026-05-21. PR: pending.

  • AC-1: auto — plugin/skills/task-ensure-ready/tests/run_evals.py’s new stamp-pattern-round-trip case asserts the schema regex accepts both Z and +00:00 literals (plus fractional-second variants). Sibling-schema audit (command grep for the Z$ pattern across plugin/entities/*/schema.json) found only the task schema carries it, so the AC’s “sibling schemas get the same treatment” clause is vacuously satisfied — no other files need editing.
  • AC-2: auto — plugin/validators/validate_frontmatter.py docs/planning/tasks/*.md reports 80/80 file(s) passed against the loosened schema.
  • AC-3: auto — stamp-pattern-round-trip in run_evals.py performs the yaml.safe_load + yaml.safe_dump round-trip and asserts both forms validate. Full eval suite: 8/8 case(s) passed.
  • The change was a one-line regex edit; the regression test pinned both the literal-form check and the PyYAML round-trip check in a single function, keeping the test surface tight.
  • run_quality_checks.py ran the eval suite (declared as a quality check) automatically — no separate invocation needed to confirm the new case.
  • Step 5b’s git rebase main surfaced a frontmatter conflict between the on-main status: open/ready → in-progress flip and the on-feat-branch readiness_verified_at stamp refresh (both touched adjacent lines). The conflict was trivial to resolve by hand but is 100% mechanical (always the same two-field merge). A pre-rebase helper that detects “both sides touch task frontmatter only” and auto-merges the union of frontmatter edits would remove a manual step. Likely covered by existing follow-up T-H0W9-task-work-rebase-frontmatter-conflict — verify before spawning a duplicate. → T-H0W9-task-work-rebase-frontmatter-conflict
  • The task spec said “Sibling entity schemas (epic, milestone, backlog) carrying the same pattern get the same treatment” but no sibling schemas actually carry that pattern. Ensure-ready’s relevance check passed the spec because the cited files exist, but the AC’s universal-quantifier shape wasn’t sanity-checked against the codebase. A relevance-check enhancement that flags ACs-with-implicit-existence-claims (“sibling X also has Y”) and asks the author to confirm before stamping ready would prevent shipping ACs that are vacuous. → T-PCJB-task-ensure-ready-flags-vacuous-universal-acs

← Back to Tasks