{"record":{"id":"b7572c3811551bb8","repo":"666ghj/MiroFish","slug":"generated-svg-must-contain-both-reviewed-images","errorCode":null,"errorMessage":"generated SVG must contain both reviewed images","messagePattern":"generated SVG must contain both reviewed images","errorType":"exception","errorClass":"StarHistoryError","httpStatus":null,"severity":"error","filePath":"scripts/star_history.py","lineNumber":1249,"sourceCode":"            exact_values = exact_attribute_values.get(raw_name)\n            if exact_values is not None and value not in exact_values:\n                raise StarHistoryError(\"generated SVG contains an unsafe attribute value\")\n            if raw_name == \"href\":\n                if local_name != \"image\" or value not in {\n                    OWNER_AVATAR_DATA_URI,\n                    WATERMARK_LOGO_DATA_URI,\n                }:\n                    raise StarHistoryError(\"generated SVG contains an external resource\")\n            lowered = value.lower().replace(\" \", \"\")\n            if \"url(\" in lowered and lowered not in {\n                \"url(#xkcdify)\",\n                \"url(#clip-circle-title)\",\n            }:\n                raise StarHistoryError(\"generated SVG contains an external resource\")\n            if lowered.startswith((\"http:\", \"https:\", \"//\")):\n                raise StarHistoryError(\"generated SVG contains an external resource\")\n    if avatar_count != 1 or watermark_count != 1:\n        raise StarHistoryError(\"generated SVG must contain both reviewed images\")\n    try:\n        avatar = base64.b64decode(OWNER_AVATAR_BASE64, validate=True)\n    except ValueError as exc:\n        raise StarHistoryError(\"reviewed avatar data is invalid\") from exc\n    if (\n        len(avatar) > MAX_INLINE_AVATAR_BYTES\n        or hashlib.sha256(avatar).hexdigest() != OWNER_AVATAR_SHA256\n        or _reviewed_avatar_dimensions(avatar) != OWNER_AVATAR_DIMENSIONS\n    ):\n        raise StarHistoryError(\"reviewed avatar data is invalid\")\n    try:\n        watermark = base64.b64decode(WATERMARK_LOGO_BASE64, validate=True)\n    except ValueError as exc:\n        raise StarHistoryError(\"reviewed watermark data is invalid\") from exc\n    png_header = (\n        watermark.startswith(b\"\\x89PNG\\r\\n\\x1a\\n\")\n        and watermark[8:12] == (13).to_bytes(4, \"big\")\n        and watermark[12:16] == b\"IHDR\"","sourceCodeStart":1231,"sourceCodeEnd":1267,"githubUrl":"https://github.com/666ghj/MiroFish/blob/b5b53acc57189a4a42e44a23e149dc655c98fe82/scripts/star_history.py#L1231-L1267","documentation":"Raised by _validate_svg after iterating the whole document: the exact-match counters for the reviewed avatar <image> and watermark <image> must each equal 1. Any deviation — zero occurrences (template dropped one), duplicates, or images whose attribute dicts no longer byte-match the pinned sets at scripts/star_history.py:1199-1213 — fails here or earlier with 'unreviewed image'.","triggerScenarios":"Deleting the watermark image from the template; moving/duplicating the avatar; changing any of its pinned attributes (x/y/width/height/clip-path) so element.attrib != avatar_attributes/watermark_attributes exactly (those cases raise 'unreviewed image' at line 1219, then this fires if the count is 0); rendering a variant (e.g. a banner without watermark).","commonSituations":"Someone removes the 'star-history.com' watermark for aesthetics; the avatar image attributes are reordered or an attribute added (attrib dict comparison fails); a new chart variant is added to _output_payloads that was never wired to include both images.","solutions":["Ensure every rendered SVG variant contains exactly one <image> matching avatar_attributes and one matching watermark_attributes (scripts/star_history.py:1199-1213), attribute-for-attribute.","If you intentionally moved the avatar/watermark (new coordinates), update both the pinned attribute dicts in the validator and the template in the same change.","If you intentionally removed the watermark, that violates the shipped design contract — restore it rather than patching the check.","Run _validate_svg over both light and dark outputs after any template edit (they are validated in _write_outputs at lines 1326-1327)."],"exampleFix":"# before: watermark image deleted from the dark template\n# (watermark_count == 0 at the end of _validate_svg)\n\n# after: keep exactly one pinned watermark <image> per SVG\n<image x=\"635\" y=\"508.333\" width=\"20\" height=\"20\"\n       href=\"{WATERMARK_LOGO_DATA_URI}\"/>","handlingStrategy":"validation","validationCode":"def has_both_reviewed_images(root) -> bool:\n    avatar = watermark = 0\n    for el in root.iter():\n        if el.tag.endswith(\"}image\"):\n            attrs = dict(el.attrib)\n            if attrs == AVATAR_ATTRIBUTES:      # pinned dicts from the validator\n                avatar += 1\n            elif attrs == WATERMARK_ATTRIBUTES:\n                watermark += 1\n    return avatar == 1 and watermark == 1","typeGuard":null,"tryCatchPattern":"try:\n    _validate_svg(svg_bytes)\nexcept StarHistoryError as exc:\n    if \"reviewed images\" in str(exc):\n        # count <image> elements; restore or re-pin the avatar/watermark blocks\n        ...","preventionTips":["Every rendered variant must include exactly one pinned avatar and one pinned watermark <image>.","When repositioning the images, update the pinned attribute dicts and the template together.","Add a render test per variant asserting _validate_svg passes."],"tags":["svg","assets","watermark","validation"],"backgroundTag":null,"analyzedSha":"b5b53acc57189a4a42e44a23e149dc655c98fe82","analyzedAt":"2026-08-14T22:29:33.146Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}