{"record":{"id":"d23ae71244204e7e","repo":"affaan-m/ECC","slug":"label-must-be-an-iso-8601-timestamp","errorCode":null,"errorMessage":"${label} must be an ISO-8601 timestamp.","messagePattern":"(.+?) must be an ISO-8601 timestamp\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/lib/memory-vault-format.js","lineNumber":139,"sourceCode":"  }\n  return values.reduce((result, value) => {\n    const normalized = validator(value);\n    if (result.includes(normalized)) {\n      throw new Error(`${label} must not contain duplicate values.`);\n    }\n    return [...result, normalized];\n  }, []);\n}\n\nfunction validateTimestamp(value, label) {\n  const normalized = asNonEmptyString(value, label, 64);\n  const parsed = new Date(normalized);\n  if (\n    !ISO_TIMESTAMP_PATTERN.test(normalized)\n    || Number.isNaN(parsed.getTime())\n    || parsed.toISOString() !== normalized\n  ) {\n    throw new Error(`${label} must be an ISO-8601 timestamp.`);\n  }\n  return normalized;\n}\n\nfunction normalizeBody(value) {\n  if (typeof value !== 'string') {\n    throw new Error('memory body must be a string.');\n  }\n  if (hasUnsafeControlCharacters(value, true)) {\n    throw new Error('memory body must not contain unsafe control or bidirectional formatting characters.');\n  }\n  const normalized = value.trim();\n  if (normalized.length === 0) {\n    throw new Error('memory body must contain non-whitespace context.');\n  }\n  if (Buffer.byteLength(normalized, 'utf8') > MAX_BODY_BYTES) {\n    throw new Error(`memory body is too large (maximum ${MAX_BODY_BYTES} bytes).`);\n  }","sourceCodeStart":121,"sourceCodeEnd":157,"githubUrl":"https://github.com/affaan-m/ECC/blob/01e15490f04e29cfefe3896951f43db46994d8ee/scripts/lib/memory-vault-format.js#L121-L157","documentation":"Thrown by validateTimestamp() when a timestamp field (createdAt or updatedAt) does not match the strict ISO-8601 pattern ^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z$ AND round-trip exactly through new Date(x).toISOString(). The library requires millisecond precision and a trailing 'Z' (UTC) so timestamps are unambiguous and sort lexically. Any deviation is rejected rather than silently coerced.","triggerScenarios":"saveMemory({createdAt: '2024-01-01T00:00:00Z'}) (no milliseconds), saveMemory({createdAt: '2024-01-01T00:00:00.000+00:00'}) (offset instead of Z), saveMemory({createdAt: Date.now()}) (number not string), saveMemory({createdAt: '2024-13-45T99:99:99.999Z'}) (invalid date that still matches regex but fails Date parse), or saveMemory({updatedAt: someDate.toISOString().replace('.000Z','Z')}) which strips the round-trip requirement.","commonSituations":"Migrating from a system that stored epoch millis or SQL DATETIME strings. Using moment/date-fns default format() output. Copy-pasting a timestamp from a log file that uses second precision. Manually constructing timestamps via string concatenation. Frontend code that runs toISOString().replace(...) to trim what looked like redundant precision.","solutions":["Pass new Date().toISOString() verbatim — it always emits the exact format required.","If you have an epoch number, wrap it: new Date(epochMs).toISOString().","If you have an offset timestamp, normalize first: new Date('2024-01-01T00:00:00.000+00:00').toISOString().","Stop stripping the .SSS portion; the round-trip check fails without it.","When backfilling, run each candidate through new Date(s).toISOString() === s before passing it in."],"exampleFix":"// before\nsaveMemory({\n  title: 'handoff',\n  createdAt: '2024-08-12T14:30:00Z',          // missing .SSS\n  updatedAt: Date.now(),                        // number, not string\n  body: '...'\n});\n\n// after\nconst now = new Date().toISOString();\nsaveMemory({\n  title: 'handoff',\n  createdAt: now,                               // 2024-08-12T14:30:00.123Z\n  updatedAt: now,\n  body: '...'\n});","handlingStrategy":"validation","validationCode":"const ISO = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z$/;\nfunction isValidVaultTimestamp(value) {\n  return typeof value === 'string'\n    && ISO.test(value)\n    && !Number.isNaN(new Date(value).getTime())\n    && new Date(value).toISOString() === value;\n}\nif (!isValidVaultTimestamp(input.createdAt)) {\n  input.createdAt = new Date().toISOString();\n}","typeGuard":"function isIsoTimestamp(value): value is string {\n  return typeof value === 'string'\n    && /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z$/.test(value)\n    && new Date(value).toISOString() === value;\n}","tryCatchPattern":"try {\n  saveMemory(input);\n} catch (err) {\n  if (/must be an ISO-8601 timestamp/.test(err.message)) {\n    input.createdAt = new Date().toISOString();\n    input.updatedAt = new Date().toISOString();\n    saveMemory(input);\n  } else throw err;\n}","preventionTips":["Always derive timestamps from new Date().toISOString() at the call site, never from string concatenation.","Type the input field as { createdAt: string; updatedAt: string } and document the exact format in a comment.","Add a unit test that asserts saveMemory() works with new Date().toISOString() output."],"tags":["timestamp","iso-8601","validation","memory-vault"],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}