Mirrored from GitHub github.com/roostorg/coop

Fix for Manual Review Tool: If createdAt datetime is in bad format, store it as null in decision log (#913) master

* fix(mrt): store null for unparseable item createdAt in decision log Submitting a decision failed with a 500 ("Job submission failed. Please try again.") for any job whose item carries a truthy but unparseable createdAt value. `#logDecision` passed `new Date(itemCreatedAtField)` straight into the `item_created_at` timestamptz column; an unparseable value yields an Invalid Date, which the pg driver serializes to a NaN string that Postgres rejects (22007), failing the whole decision insert. The decision is never recorded and the job is never removed, so the task is stuck in the queue. Normal item submissions can't reach this state because the DATETIME field handler validates dates at intake. A bad value only arrives via a path that skips that validation (e.g. system-generated reports, or a createdAt role mapped to a non-DATETIME field). Normalize at the write boundary: parse the value and store null when it is not a valid date. The column is already nullable, so the decision records and the task clears. Adds a unit regression test for the normalization helper. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CEU6YHFiBYfTqjgM5Vyt9n * docs(changelog): note the unparseable createdAt decision fix Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CEU6YHFiBYfTqjgM5Vyt9n * fix(mrt): preserve epoch 0 in parseItemCreatedAt Address review: `!value` treated a numeric 0 (a valid 1970-01-01 epoch) as empty. Guard only null/undefined/empty-string instead, and cover epoch 0 in the test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CEU6YHFiBYfTqjgM5Vyt9n * fix(mrt): record unparseable item createdAt values Address review: surface invalid createdAt values instead of silently nulling them. When a present createdAt can't be parsed, emit a tracer span (job id, org id, the raw value) so the bad data is diagnosable and can be backfilled. The decision still saves with a null item_created_at. new Date() returns an Invalid Date rather than throwing, so this detects the invalid parse rather than wrapping in try/catch. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CEU6YHFiBYfTqjgM5Vyt9n --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>


+94 -3
3 changed files