{"record":{"id":"fb82fd31d6e83c6b","repo":"tinyhumansai/openhuman","slug":"provider-chain-rows-must-be-numbered-1-n-in-order","errorCode":null,"errorMessage":"provider-chain rows must be numbered 1..N in order; row ${i + 1} is numbered ${p.order}","messagePattern":"provider-chain rows must be numbered 1\\.\\.N in order; row (.+?) is numbered (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/generate-architecture-docs.mjs","lineNumber":72,"sourceCode":"        '`@generated-source:provider-chain` … `@end-source:provider-chain`'\n    );\n  }\n  const region = appSource.slice(begin, end);\n  const rowRe = /^\\s*\\*?\\s*(\\d+)\\.\\s+(.+?)\\s+—\\s+(.+?)\\s*$/;\n  const providers = [];\n  for (const line of region.split('\\n')) {\n    const m = rowRe.exec(line);\n    if (!m) continue;\n    providers.push({ order: Number(m[1]), name: m[2].trim(), role: m[3].trim() });\n  }\n  if (providers.length === 0) {\n    throw new Error('provider-chain source marker contained no `N. Component — role` rows');\n  }\n  // Guard against drift in the marker itself: orders must be 1..N, in order,\n  // names non-empty, and roles free of the `|` that would break the table.\n  providers.forEach((p, i) => {\n    if (p.order !== i + 1) {\n      throw new Error(\n        `provider-chain rows must be numbered 1..N in order; row ${i + 1} is numbered ${p.order}`\n      );\n    }\n    if (!p.name) throw new Error(`provider-chain row ${p.order} is missing a component name`);\n    if (!p.role) throw new Error(`provider-chain row ${p.order} is missing a role`);\n    if (p.name.includes('|') || p.role.includes('|')) {\n      throw new Error(`provider-chain row ${p.order} must not contain a \"|\" character`);\n    }\n  });\n  return providers;\n}\n\n/**\n * Render the markdown body that lives between the BEGIN/END markers.\n * Returned as an array of lines (no trailing join) so splicing stays\n * deterministic and line-oriented.\n *\n * @param {{ order: number, name: string, role: string }[]} providers","sourceCodeStart":54,"sourceCodeEnd":90,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/a221052e0df5b1f7598fceba7329fd1af95d6699/scripts/generate-architecture-docs.mjs#L54-L90","documentation":"A drift guard in parseProviderChain(): after parsing, each row's parsed order must equal its 1-based array index (orders exactly 1..N, ascending, contiguous). If row i+1 carries any other number — a duplicate, a gap, or a descending sequence — the generator throws instead of rendering a table whose Order column disagrees with reality.","triggerScenarios":"Inserting a new provider mid-chain without renumbering (1, 2, 2, 4); deleting a middle row and leaving a gap (1, 2, 4); reordering rows physically but keeping their old numbers (1, 3, 2). Renumbering only the tail (1, 2, 5) after appending also fails.","commonSituations":"Any hand edit of the provider chain that touches membership or order — exactly the drift the generator exists to catch; merge conflicts resolved by taking rows from both sides without renumbering.","solutions":["Renumber the rows top-to-bottom as 1, 2, … N with no gaps or repeats so the numbering matches the actual chain order","Prefer deleting the whole row (line) when removing a provider, then renumber the remainder","Re-run `pnpm docs:generate`; the order column in gitbooks/developing/architecture/frontend.md now matches the marker"],"exampleFix":"// before — inserted PersistGate twice, numbering drifted:\n * 1. Sentry.ErrorBoundary — crash boundary\n * 2. Redux Provider — app store\n * 2. PersistGate — redux persistence\n * 4. BootCheckGate — boot gate\n\n// after:\n * 1. Sentry.ErrorBoundary — crash boundary\n * 2. Redux Provider — app store\n * 3. PersistGate — redux persistence\n * 4. BootCheckGate — boot gate","handlingStrategy":"validation","validationCode":"const orders = rows.map(l => Number(ROW_RE.exec(l)[1]));\nconst contiguous = orders.every((n, i) => n === i + 1);\nif (!contiguous) {\n  console.error(`Row numbering drifted: got [${orders.join(', ')}], expected 1..${orders.length}`);\n  process.exit(1);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["After any insert/delete/reorder in the marker block, renumber the whole list 1..N top-to-bottom in the same edit","Let the generator be the linter: run `pnpm docs:generate` immediately after touching the chain"],"tags":["docs","codegen","validation","drift"],"backgroundTag":null,"analyzedSha":"a221052e0df5b1f7598fceba7329fd1af95d6699","analyzedAt":"2026-08-16T12:47:06.542Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}