Extends the IR migration to bring across IR's PostMetadata (title, description, source URL, publish date, attachment count) as FC Source + Post + ImageProvenance, so post-level data survives IR shutdown. Also adds a modal-side artist fallback so IR-imported images without ImageProvenance still show a "by " line via image_record.artist_id.
Track B — IR Post/Provenance restore
IR export script (78df92c on the imagerepo repo) bumped to schema v2; new _export_image_posts() dumps every PostMetadata row plus its image-sha256 list, joined through image_tags via the post-kind tag.
ir_ingest.py accepts schema 1 or 2; carries the new image_posts array into /images/_migration_state/ir_tag_manifest.json.
tag_apply.py new phase 4: for each image_posts entry, find-or-creates Source (artist + platform) + Post (title/description/post_url/post_date/attachment_count, with raw_metadata.migrated_from=imagerepo) + per-image ImageProvenance row. Idempotent on rerun. Dry-run safe.
Track A — Modal artist fallback
gallery_service.get_image_with_tags now returns artist: {id, name, slug} when image_record.artist_id is set.
ProvenancePanel.vue shows a minimal "by " card when ImageProvenance is empty but the image has a direct artist FK — so the modal stops being blank for IR-imported images that aren't in a tracked post.
Testing
5 new integration tests in tests/test_tag_apply.py cover phase 4: creates Source+Post+ImageProvenance, idempotent on rerun, unknown sha → unmatched, unknown platform → skipped, dry-run no writes.
Operator workflow
Pull this image; force-update services.
Deploy a one-shot ir-export service inside the IR stack (snippet in chat) that runs scripts/export_for_fabledcurator.py --output /staging/ir-export-v2.json.
Settings → Maintenance → Legacy migration → upload the new JSON to the IR slot.
Click "3. Ingest IR" (the FormData fix from v26.05.24.1 makes uploads actually send now).
Open any image modal: ImageProvenance entries display for images-in-posts; artist fallback displays for images-without-posts.
Commits
c9a3f12 fix(lint): tag_apply.py — one blank line between imports and module-level comment
538c159 fc-3g-ext: IR Post/Provenance restore (tag_apply phase 4) + modal artist fallback
Extends the IR migration to bring across IR's PostMetadata (title, description, source URL, publish date, attachment count) as FC Source + Post + ImageProvenance, so post-level data survives IR shutdown. Also adds a modal-side artist fallback so IR-imported images without ImageProvenance still show a "by <artist>" line via image_record.artist_id.
## Track B — IR Post/Provenance restore
- **IR export script** (`78df92c` on the imagerepo repo) bumped to schema v2; new `_export_image_posts()` dumps every PostMetadata row plus its image-sha256 list, joined through image_tags via the post-kind tag.
- **`ir_ingest.py`** accepts schema 1 or 2; carries the new `image_posts` array into `/images/_migration_state/ir_tag_manifest.json`.
- **`tag_apply.py`** new phase 4: for each `image_posts` entry, find-or-creates `Source` (artist + platform) + `Post` (title/description/post_url/post_date/attachment_count, with `raw_metadata.migrated_from=imagerepo`) + per-image `ImageProvenance` row. Idempotent on rerun. Dry-run safe.
## Track A — Modal artist fallback
- **`gallery_service.get_image_with_tags`** now returns `artist: {id, name, slug}` when `image_record.artist_id` is set.
- **`ProvenancePanel.vue`** shows a minimal "by <artist>" card when ImageProvenance is empty but the image has a direct artist FK — so the modal stops being blank for IR-imported images that aren't in a tracked post.
## Testing
5 new integration tests in `tests/test_tag_apply.py` cover phase 4: creates Source+Post+ImageProvenance, idempotent on rerun, unknown sha → unmatched, unknown platform → skipped, dry-run no writes.
## Operator workflow
1. Pull this image; force-update services.
2. Deploy a one-shot `ir-export` service inside the IR stack (snippet in chat) that runs `scripts/export_for_fabledcurator.py --output /staging/ir-export-v2.json`.
3. Settings → Maintenance → Legacy migration → upload the new JSON to the IR slot.
4. Click "3. Ingest IR" (the FormData fix from v26.05.24.1 makes uploads actually send now).
5. Click "5. Apply IR tags" — phase 4 fires alongside phases 1-3.
6. Open any image modal: ImageProvenance entries display for images-in-posts; artist fallback displays for images-without-posts.
## Commits
```
c9a3f12 fix(lint): tag_apply.py — one blank line between imports and module-level comment
538c159 fc-3g-ext: IR Post/Provenance restore (tag_apply phase 4) + modal artist fallback
```
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Extends the IR migration to bring across IR's PostMetadata (title, description, source URL, publish date, attachment count) as FC Source + Post + ImageProvenance, so post-level data survives IR shutdown. Also adds a modal-side artist fallback so IR-imported images without ImageProvenance still show a "by " line via image_record.artist_id.
Track B — IR Post/Provenance restore
78df92con the imagerepo repo) bumped to schema v2; new_export_image_posts()dumps every PostMetadata row plus its image-sha256 list, joined through image_tags via the post-kind tag.ir_ingest.pyaccepts schema 1 or 2; carries the newimage_postsarray into/images/_migration_state/ir_tag_manifest.json.tag_apply.pynew phase 4: for eachimage_postsentry, find-or-createsSource(artist + platform) +Post(title/description/post_url/post_date/attachment_count, withraw_metadata.migrated_from=imagerepo) + per-imageImageProvenancerow. Idempotent on rerun. Dry-run safe.Track A — Modal artist fallback
gallery_service.get_image_with_tagsnow returnsartist: {id, name, slug}whenimage_record.artist_idis set.ProvenancePanel.vueshows a minimal "by " card when ImageProvenance is empty but the image has a direct artist FK — so the modal stops being blank for IR-imported images that aren't in a tracked post.Testing
5 new integration tests in
tests/test_tag_apply.pycover phase 4: creates Source+Post+ImageProvenance, idempotent on rerun, unknown sha → unmatched, unknown platform → skipped, dry-run no writes.Operator workflow
ir-exportservice inside the IR stack (snippet in chat) that runsscripts/export_for_fabledcurator.py --output /staging/ir-export-v2.json.Commits