{"record":{"id":"75bf73f354694047","repo":"JuliusBrussee/caveman","slug":"label-unreviewed-sync-proposal-marker-on-line-s","errorCode":null,"errorMessage":"{label}: unreviewed sync proposal marker on line(s) {', '.join(str(number) for number in offending)}: the proposal bumped verified_at, so merging with the marker intact attests a price check nobody performed -- confirm each row against its sources, then delete the {REVIEW_MARKER!r} line","messagePattern":"(.+?): unreviewed sync proposal marker on line\\(s\\) (.+?): the proposal bumped verified_at, so merging with the marker intact attests a price check nobody performed -- confirm each row against its sources, then delete the (.+?) line","errorType":"validation","errorClass":"CatalogError","httpStatus":null,"severity":"error","filePath":"shared/provider-catalog/validate_catalog.py","lineNumber":183,"sourceCode":"    if verified_at < now - timedelta(days=120):\n        raise CatalogError(f\"{label}: verified_at is older than 120 days\")\n    return identity[0], identity[1], identity[2]\n\n\ndef check_review_markers(text: str, label: str) -> None:\n    \"\"\"Refuse a catalog that still carries an unreviewed sync proposal.\n\n    YAML comments are invisible to the parsed rows, so this reads the raw file:\n    a rubber-stamped merge must not be able to advance price provenance while\n    the line saying \"nobody has confirmed this yet\" is still in the file.\n    \"\"\"\n    offending = [\n        index + 1\n        for index, line in enumerate(text.splitlines())\n        if line.lstrip().startswith(REVIEW_MARKER)\n    ]\n    if offending:\n        raise CatalogError(\n            f\"{label}: unreviewed sync proposal marker on line(s) \"\n            f\"{', '.join(str(number) for number in offending)}: the proposal bumped \"\n            \"verified_at, so merging with the marker intact attests a price check \"\n            \"nobody performed -- confirm each row against its sources, then delete \"\n            f\"the {REVIEW_MARKER!r} line\"\n        )\n\n\ndef pricing_identity(row: dict[str, Any]) -> dict[str, Any]:\n    \"\"\"The part of a row the immutable dated snapshot pins.\n\n    Present-with-a-value and absent are different attestations, so only the\n    price-affecting capability keys the row actually carries are recorded:\n    adding one to a row that had none changes that row's price and must break\n    the pin exactly like editing one that was already there.\n    \"\"\"\n    identity = {key: row.get(key) for key in PRICING_IDENTITY_KEYS}\n    capabilities = row.get(\"capabilities\") or {}","sourceCodeStart":165,"sourceCodeEnd":201,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/shared/provider-catalog/validate_catalog.py#L165-L201","documentation":"validate_catalog.py enforces that automated provider-catalog sync proposals are manually reviewed before being merged. A sync proposal must carry a literal REVIEW_MARKER line; if any line in the file still starts with that marker, the check refuses validation, because the proposal bumped verified_at — merging it as-is would attest a price verification that nobody actually performed. The error exists to force a human to confirm each changed row against its sources and then delete the marker line.","triggerScenarios":"Running `validate_catalog` (via `main`) on a catalog file whose text still contains one or more lines starting with REVIEW_MARKER, i.e. a sync proposal was generated (bumping verified_at) but never signed off; any of the marker lines left in current.yaml or a generated proposal triggers the raise with the 1-based offending line numbers.","commonSituations":"A developer merges an automated price-sync PR without deleting the review placeholder; a script re-generates a proposal and leaves the marker in place; a partial manual review removes some marker lines but misses others on multi-row syncs; CI validates a proposal file that was copied verbatim from the sync tool output.","solutions":["Open the file at each reported line number, confirm the row's pricing against its upstream sources, then delete the REVIEW_MARKER line","If the proposal is stale, re-run the sync to regenerate a fresh proposal before reviewing","If verified_at should not have been bumped, revert the verified_at change and re-apply only intended edits, removing the marker","Commit the marker-free file and re-run validate_catalog to confirm it passes"],"exampleFix":"# before (current.yaml)\nverified_at: 2026-09-20\n# REVIEW_MARKER: auto-generated sync, verify before merge\nmonthly_input: 3.00\n# after (marker reviewed and removed)\nverified_at: 2026-09-20\nmonthly_input: 3.00","handlingStrategy":"validation","validationCode":"import pathlib\n\ndef ensure_review_marker_removed(path: str, marker: str) -> None:\n    text = pathlib.Path(path).read_text()\n    offending = [i + 1 for i, ln in enumerate(text.splitlines()) if ln.lstrip().startswith(marker)]\n    if offending:\n        raise ValueError(f\"{path}: review marker still present on lines {offending}\")","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Make CI run validate_catalog on every catalog PR so marker leftovers fail the build before merge","Add a pre-commit hook that scans catalog files for the REVIEW_MARKER prefix","Delete the marker only after verifying each changed row against its upstream sources","Keep sync-proposal files out of the main catalog directory until reviewed"],"tags":["validation","catalog","review-workflow","ci"],"backgroundTag":"schema-validation-failed","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}