# §17.5–§17.7 — Module Canonical Home + Mirror Model + Revibe-as-Fork **v8.2 NEW (2026-07-22)**: This section is new in v8.2. It establishes: 1. The **canonical-home rule** — every module has a canonical home; the gallery is never the default (§17.5) 2. The **mirror model** — gallery mirrors for cross-app discoverability, with cleanup + auto-promotion rules (§17.6) 3. The **revibe-as-fork rule** — a revibe (§18) is a fork; the canonical is the app's repo, the gallery has a fork entry (§17.7) **Audience**: AI sessions doing FVW v8.2 work, operators building modules/apps, anyone touching the gallery. --- ## 17.5 — Module Canonical Home + Mirror Model ### 17.5.1 — The Rule **Every module has a canonical home.** The canonical home is wherever the operator decides the work happens. The gallery is one possible home, but never the default. The AI must ask the operator where the canonical home is. The `_module_kind` field in `module-meta.json` declares the kind. There are 5 kinds. ### 17.5.2 — The 5 Module Kinds | `_module_kind` | Canonical home | When | |---|---|---| | `original` | the app repo | App does the work | | `borrowed` | another app repo (via `_canonical_source`) | App uses module made elsewhere | | `gallery-original` | the gallery itself | Operator chose gallery as long-term canonical (only when nowhere else fits) | | `gallery-mirror` | an external repo (mirrored to gallery) | Cross-app, mirrored for discovery | | `app-fork` | the app's repo (with `_forked_from` of the original) | App revibed a module | ### 17.5.3 — The 4 Questions When told "publish X to the gallery", the AI must ask the operator: 1. **New or revibe?** — Is X a brand-new module, or a fork/customisation of an existing one? 2. **Where's the canonical home?** — Which repo, which folder? The AI never assumes the gallery. 3. **Publish what?** — Recipes only, recipes+code, or full module+tests+bundle? 4. **Fork of anything?** — If yes, point at the original via `_forked_from`. The AI never assumes. Each answer becomes a field in the module's metadata. The operator decides everything. ### 17.5.4 — The Annotation in `module-meta.json` ```json // App's own work { "_module_kind": "original", "_canonical_source": null, ... } // App uses module made elsewhere { "_module_kind": "borrowed", "_canonical_source": "avidtech6/ai-shell/ai-toast", ... } // Gallery owns this (only when no other home) { "_module_kind": "gallery-original", "_canonical_source": null, ... } // Gallery has a copy of something from elsewhere { "_module_kind": "gallery-mirror", "_canonical_source": "avidtech6/ai-shell/ai-toast", ... } // App revibed a module { "_module_kind": "app-fork", "_canonical_source": "avidtech6/vibecoder-standalone", "_forked_from": "avidtech6/ai-shell/ai-toast", ... } ``` ### 17.5.5 — The "What the AI Does" Table | Answers to the 4 questions | AI does | |---|---| | New + canonical in app repo + recipes+code + not a fork | Create at app, push mirror to gallery as `gallery-mirror` | | New + canonical in app repo + recipes only + not a fork | Create at app (recipe book only), push recipe-book mirror to gallery as `gallery-mirror` | | New + canonical in gallery + recipes+code + not a fork | Create at gallery as `gallery-original` (gallery IS canonical) | | Revibe + canonical in app repo + recipes+code + fork of X | Create at app's `modules/revibe/X/` per §18.3, push fork entry to gallery as `app-fork` | | New + canonical in new repo + recipes only + not a fork | Create new repo, set as canonical, gallery gets `gallery-mirror` | ### 17.5.6 — Defaults the AI Must NOT Apply - ❌ "New → gallery canonical" (wrong — canonical is the operator's call) - ❌ "Publish with code" (wrong — might be recipes-only) - ❌ "This is its own thing" (wrong — might be a fork) - ❌ "Mirror it" (wrong — might be a one-off publish) **The only default:** if the operator says "wherever you think", the AI asks again. This is a decision the operator must make. --- ## 17.6 — Mirror Cleanup + Auto-Promotion ### 17.6.1 — The Mirror States A `gallery-mirror` can be in one of 4 states: | `_mirror_status` | Meaning | |---|---| | `healthy` | Source is alive, mirror is current | | `orphaned` | Source is dead, mirror is now stranded | | `reattached` | Source came back, mirror resumed | | `promoted` | Mirror became its own thing (gallery-original) | ### 17.6.2 — The Cleanup State Machine ``` source alive │ ▼ ┌──────────────────────┐ │ healthy (mirror) │ └──────────────────────┘ │ │ source │ │ source comes dies │ │ back ▼ ▼ ┌──────────────┐ ┌──────────────┐ │ orphaned │ │ reattached │ └──────────────┘ └──────────────┘ │ │ 30+ days pass (default) ▼ ┌──────────────────────┐ │ promoted │ ← _module_kind changes to "gallery-original" │ (gallery-original) │ _canonical_source: null └──────────────────────┘ _promoted_from: ``` ### 17.6.3 — The Cleanup Workflow The gallery runs `cleanup-mirrors.yml` daily. For every `gallery-mirror`: 1. **Check if source is alive** — GitHub API: does the repo exist? Does the path exist? Do checksums match? 2. **If source is dead**: - Set `_mirror_status: "orphaned"` - Set `_orphaned_at: ` - Keep the mirror where it is (don't delete) 3. **If source has been orphaned for 30+ days** (default, configurable per entry): - Promote: `_module_kind: "gallery-original"`, `_canonical_source: null` - Add `_promoted_from: `, `_promoted_at: ` 4. **If source comes back as the same module**: - Set `_mirror_status: "reattached"` - Resume mirror 5. **If source comes back as a different module**: - Keep the orphan as `gallery-original` (legacy) - Create a new entry for the new module - Both coexist ### 17.6.4 — The 30-Day Threshold (Configurable) Default: 30 days. Configurable per entry via `_cleanup_threshold_days`: ```json { "_module_kind": "gallery-mirror", "_canonical_source": "avidtech6/ai-shell/ai-toast", "_cleanup_threshold_days": 90, // wait 90 days before auto-promoting ... } ``` Set to `null` to disable auto-promotion (operator must explicitly promote). ### 17.6.5 — Why This Matters Without cleanup, the gallery becomes a graveyard of dead pointers. The state machine forces decisions: - Either the source is alive (mirror is current) - Or it's dead (mirror is now its own thing, marked as such) The 30-day window gives sources time to come back (e.g. a temporarily archived repo), but not so long that the gallery has stale entries. --- ## 17.7 — Revibe = Fork ### 17.7.1 — The Rule A revibe (§18) is a fork. The moment an app revibes a module: - The revibe is a fork with `_module_kind: "app-fork"` - The canonical home of the fork is the **app's repo** (not the gallery) - The gallery has a fork entry for discoverability, but the canonical is at the app - The fork can diverge from the original over time (this is the point of a fork) ### 17.7.2 — The Revibe Flow ``` 1. Operator says: "I want to revibe module X in app A" 2. AI asks the 4 questions (most answers are implicit): - Revibe (operator said so) - Canonical at A (the app doing the revibe) - Recipes + code (default for revibes) - Fork of X (the module being revibed) 3. AI: a. Creates the revibe at A/modules/revibe/X/ per §18.3 - module.json with _module_kind: "app-fork" - _canonical_source: "avidtech6/A" - _forked_from: - recipe-book/, src/, tests/ b. Sets up A's CI to push fork entry to gallery c. Fork entry in gallery: gallery/forks/A/X/ d. Original X stays canonical at its original home e. The fork can diverge from the original over time ``` ### 17.7.3 — The Fork Folder Convention Forks live in `gallery/forks///`: ``` gallery/ ├── forks/ │ ├── vibecoder-standalone/ │ │ ├── ai-toast-vibecoder-style/ │ │ │ ├── module.json (kind: app-fork, forked_from: ai-toast) │ │ │ ├── recipe-book/ │ │ │ ├── src/ │ │ │ └── tests/ │ │ └── ... │ ├── freshcards-standalone/ │ │ └── ... │ └── fvs/ │ └── ... ``` This groups all of an app's forks together, making the fork landscape scannable. ### 17.7.4 — Why the Fork Lives at the App (Not the Gallery) - The fork is the app's work. The app maintains it. - The gallery is a publisher, not an author. Forks are authored at the app. - If the app archives, the fork is dead. The gallery can mark the fork as `orphaned` and promote it to `gallery-original` (per §17.6) — same cleanup as mirrors. ### 17.7.5 — The Fork Can Be Borrowed Other apps can borrow a fork the same way they borrow an original: ```json // In another app's modules/ { "_module_kind": "borrowed", "_canonical_source": "avidtech6/vibecoder-standalone/revibe/ai-toast-vibecoder-style", ... } ``` This is a "fork of a fork" if the original is a fork, or a "fork via another app's revibe" if the original is upstream. Either way, the canonical is wherever the work happens. --- ## Summary **§17.5** — Canonical home is wherever the operator decides. 5 kinds. 4 questions. AI never assumes. **§17.6** — Mirrors get cleaned up: 30-day orphan threshold, auto-promote to `gallery-original`. 4 mirror states. **§17.7** — Revibe = fork. Fork lives at the app, gallery has a fork entry. Fork can be borrowed by other apps. **The single sentence:** > **Every module has a canonical home. The gallery is a publisher, not an author. The AI asks 4 questions, never assumes.** --- *This is the v8.2 doctrine. The position doc is at `/workspace/position-gallery-entry-shapes-v4.md`. The AI publishing guide is at `gallery-pact/AI-PUBLISHING-GUIDE.md` (in the gallery repo).*