[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6N0T0AR97HC1R00759DNKVJ":3,"skill-same:01M6N0T0AR97HC1R00759DNKVJ":96},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":23,"publishedAt":45,"linkOnly":46,"markdownContent":47,"markdownBody":48,"frontMatter":49,"refName":73,"commitSha":74,"skillDirectory":75,"forksCount":76,"repositoryHtmlUrl":77,"repositoryHomepageUrl":6,"ownerHtmlUrl":78,"ownerBlogUrl":79,"ownerDescription":80,"updatedAt":81,"install":82,"resources":95},"01M6N0T0AR97HC1R00759DNKVJ","schema-unify",null,"Migrate a brain from gbrain-base (or any pack) to gbrain-base-v2's 14-canonical-type taxonomy via gbrain onboard --check + the unify-types Minion handler. Collapses 94 noisy types to 15 canonical with subtypes, alias rows, and link rows. Triggers when an agent notices pack_upgrade_available, type_proliferation, or asks \"what is the canonical taxonomy \u002F how do I clean up my page types\".","garrytan\u002Fgbrain","garrytan","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F19957?v=4","01M6DJCCDDG53MTYYRTXAXFG17","Garry Tan","01M6MZQZCEN3RRMD33E1X9ZZR9",32847,"MIT","https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgbrain\u002Fblob\u002Fmaster\u002Fskills\u002Fschema-unify\u002FSKILL.md",[18],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发","SCENARIO","使用场景",[24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44],"schema unify","schema","unify","consolidate","when","handler","my","canonical","types","submit","brain","get","MCP","active","upgrade","do","clean","up","migrate","how","run","2026-11-10 10:12:30",false,"---\nname: schema-unify\ndescription: Migrate a brain from gbrain-base (or any pack) to gbrain-base-v2's 14-canonical-type taxonomy via gbrain onboard --check + the unify-types Minion handler. Collapses 94 noisy types to 15 canonical with subtypes, alias rows, and link rows. Triggers when an agent notices pack_upgrade_available, type_proliferation, or asks \"what is the canonical taxonomy \u002F how do I clean up my page types\".\nbrain_first: exempt\ntools:\n  - gbrain onboard --check\n  - gbrain onboard --check --explain\n  - gbrain onboard --check --json\n  - gbrain jobs submit unify-types\n  - gbrain jobs get\n  - gbrain schema active\n  - gbrain schema use\n  - gbrain schema stats\n  - gbrain restore\n  - mcp:run_onboard\ntriggers:\n  - \"unify my types\"\n  - \"migrate to gbrain-base-v2\"\n  - \"94 types to 14\"\n  - \"apply canonical taxonomy\"\n  - \"clean up my page types\"\n  - \"pack upgrade\"\n  - \"shrink type proliferation\"\n  - \"what does the canonical taxonomy look like\"\n  - \"consolidate page types\"\n  - \"retype pages to canonical\"\n---\n\n# Schema Unification (gbrain-base → gbrain-base-v2)\n\nv0.41.22 ships **gbrain-base-v2** — a 15-type DRY\u002FMECE taxonomy (14 canonical + `note` catch-all) — as the install default for new brains. Existing brains on `gbrain-base` can opt in via the `pack_upgrade_available` onboard finding + the `unify-types` PROTECTED Minion handler.\n\nThis skill is the playbook for that migration.\n\n## brain_first: exempt\n\nThis skill is ABOUT the brain's shape — it can't depend on the brain it's reshaping. No `gbrain search` lookup first; jump straight to onboard.\n\n## When this skill fires\n\n- Agent runs `gbrain onboard --check` and sees `pack_upgrade_available` or `type_proliferation` warnings\n- User asks \"what is the canonical taxonomy \u002F how do I clean up my page types \u002F migrate to v2\"\n- A `dangling_aliases` finding surfaces (post-unify GC)\n- An agent ingesting from a custom pack wants to consult the v2 taxonomy as a reference\n\n## Mental model (one paragraph)\n\nA production gbrain brain accreted **94 distinct `pages.type` values** over years of ingestion: tweet \u002F tweet-thread \u002F tweet-bundle \u002F tweet-single \u002F media\u002Fx-tweet\u002Fbundle \u002F tweet-stub all coexisting; 5.5K concept-redirect pages; atom-partner-link pages that should be links; civic \u002F framework \u002F insight \u002F memo \u002F anecdote one-offs. The cure: collapse to **15 canonical types** (person, company, media, tweet, social-digest, analysis, atom, concept, source, deal, email, slack, writing, project, note) with subtypes\u002Fformat\u002Forigin pushed to frontmatter, alias-rows for redirects, real link-rows for edge-shaped pages, and a catch-all that bins long-tail unknowns to `note` with `frontmatter.legacy_type = \u003Coriginal>` for rollback.\n\n## Workflow\n\n### Phase 1: Discovery\n\nConfirm the brain is actually on `gbrain-base` (not already on v2).\n\n```bash\ngbrain schema active --json | jq -r '.identity'\n```\n\nExpected: `gbrain-base@1.0.0+\u003Csha>`. If you see `gbrain-base-v2@...`, the brain is already on v2 — skip the migration.\n\nThen run onboard to see what would change:\n\n```bash\ngbrain onboard --check\n```\n\nLook for the `pack_upgrade_available` finding. If it's `ok`, there's no successor declared for the active pack — done.\n\n### Phase 2: Preview\n\nRun the per-cluster narrative:\n\n```bash\ngbrain onboard --check --explain\n```\n\nThis invokes the `unify-types` handler in dry-run mode and prints:\n- How many pages would retype per cluster (tweets, articles, companies, etc.)\n- How many concept-redirect pages would become alias rows\n- How many edge-shaped pages would convert to real links\n- The synthesized catch-all rules for unknown types\n\nReview the output. If the proposed changes look wrong, **don't** proceed — file an issue or write a custom pack with adjusted mapping_rules.\n\n### Phase 3: Apply\n\n**Managed brains: stop after the preview.** Applying the migration is not\nsupported on a managed brain (managed persistence on) yet: the retype writes\npages directly instead of through the coordinated writer, so the apply job is\nrefused (`writer_coordinator_required`) before it changes anything. The\n`pack_upgrade_available` finding says so on those brains. Keep using\n`gbrain onboard --check --explain` to preview, tell the user the pack upgrade\nwaits for coordinated retype support, and don't submit the apply job below.\nOne exception: when the preview shows nothing to retype, link or alias, the\napply job only switches the active pack, so it may be submitted with the\nuser's agreement.\n\nThe handler is PROTECTED (manual_only) — autopilot will never auto-fire it. Submit explicitly:\n\n```bash\ngbrain jobs submit unify-types \\\n  --params '{\"target_pack\":\"gbrain-base-v2\",\"apply\":true}'\n```\n\nOn PGLite (the install default), or on any setup without a running `gbrain jobs work` worker or supervisor daemon, add `--follow` so the job executes inline:\n\n```bash\ngbrain jobs submit unify-types \\\n  --follow \\\n  --params '{\"target_pack\":\"gbrain-base-v2\",\"apply\":true}'\n```\n\nThe persistent worker daemon is Postgres-only. Without `--follow` on PGLite, the job sits queued forever and the migration never runs.\n\n`apply` defaults to **false** (dry-run) per the handler contract, so\n`\"apply\":true` is required here or the job reports success having retyped\nnothing and left the active pack unflipped. Omit it to preview.\n\nWatch progress per phase (worker-daemon runs; with `--follow` the same progress streams inline):\n\n```bash\ngbrain jobs get \u003Cjob_id>      # one job: status, progress, result\ngbrain jobs watch --follow    # live dashboard of the whole queue\n```\n\nA job that stays `queued` here means no worker is running; resubmit with `--follow` to execute it inline.\n\nOn a 186K-page brain expect ~10 minutes. The handler runs:\n1. Preflight (validate target pack has `mapping_rules:`)\n2. Stats snapshot (pre-state for celebration summary)\n3. Acquire `gbrain-unify` db-lock (60min TTL)\n4. Apply phases:\n   - Explicit retype rules (tweets, articles, companies, etc.)\n   - Catch-all retype (unknown types → note with legacy_type)\n   - Page-to-link rules (atom-partner-link, symlink)\n   - Page-to-alias rules (concept-redirect)\n5. Final sync (untyped rows by path-prefix)\n6. **Flip active pack** to gbrain-base-v2\n7. Verify + celebration summary\n\n### Phase 4: Verify\n\n```bash\ngbrain onboard --check\ngbrain schema stats\n```\n\nExpected:\n- `pack_upgrade_available` → `ok` (active pack is now v2)\n- `type_proliferation` → `ok` (≤16 distinct typed values)\n- `dangling_aliases` → `ok` (slug_aliases all point at active canonicals)\n- `gbrain schema stats` shows ≤16 distinct types\n\n### Phase 5: Post-migration\n\nSearch and query `--type article` keep returning those pages post-unify: `media` declares `article` as an alias, so the type filter expands through the active pack's alias closure (the results also include other `media` pages). Direct SQL against `pages.type` needs updating to the canonical types.\n\nSearch queries get a small ranking signal: pages reached via `slug_aliases` (canonicals of one or more aliases) get a 1.05x boost. Visible via `gbrain search --explain`.\n\n## Rollback\n\nEvery retyped page preserves `frontmatter.legacy_type = \u003Coriginal>`.\n\nRestore types in bulk (Postgres\u002FSupabase deployments only; requires direct DB access):\n\n```sql\nUPDATE pages SET type = frontmatter->>'legacy_type'\nWHERE source_id = 'default' AND frontmatter->>'legacy_type' IS NOT NULL;\n```\n\nOn PGLite there is no SQL shell, so use the CLI surface instead: `frontmatter.legacy_type` persists per page, so individual retypes can be reverted through the normal `put_page`\u002FCLI surface, and the soft-delete restore and pack-flip revert below work on every engine.\n\nPage-to-alias and page-to-link source pages soft-delete with 72h TTL. Restore within that window:\n\n```bash\ngbrain restore \u003Cslug>\n```\n\nRevert the active pack flip:\n\n```bash\ngbrain schema use gbrain-base\n```\n\n## Anti-patterns\n\n- **Don't run unify-types under autopilot.** It's manual_only by design. Autopilot remediation should never silently change your taxonomy.\n- **Don't expect mapping_rules to cover every legacy type explicitly.** Use the catch-all (`*unknown*`) for the long tail. Pages get retyped to `note` with `legacy_type` preserved.\n- **Don't rewrite body-text wikilinks.** The slug_aliases table IS the resolver. `[[old-redirect-slug]]` keeps working via `engine.resolveSlugWithAlias` short-circuit.\n- **Don't bypass the dry-run.** Always run `--explain` before applying. The trust delta is real.\n- **Don't run two unify jobs concurrently.** The `gbrain-unify` db-lock serializes them; the second submission rejects with \"already in progress.\"\n\n## Decision tree\n\n```\nActive pack already gbrain-base-v2?\n  → Skip migration.\n\nCustom pack with own mapping_rules?\n  → Run --check --explain to see if your pack declares migration_from\n    for the active pack. If yes, target_pack = your pack name.\n\nBrain has many custom types not covered by gbrain-base-v2 mapping_rules?\n  → The catch-all retype binds them to `note` with legacy_type preserved.\n    Review by inspecting frontmatter.legacy_type after the migration.\n\nFederated brain (multiple sources)?\n  → Add --params source_id to scope the migration per-source. Each\n    source can be migrated independently.\n\nWorried about a specific cluster's mapping?\n  → Fork gbrain-base-v2 (`gbrain schema fork gbrain-base-v2 my-pack`),\n    edit mapping_rules in your fork, then target the fork.\n```\n\n## Contract\n\nInputs:\n- A brain on `gbrain-base` (or any pack with `migration_from: gbrain-base-v2`).\n- Trusted local CLI access on the brain host: `gbrain jobs submit` grants the PROTECTED-handler opt-in itself for protected names; the remote MCP `submit_job` op cannot.\n- ~10 min wallclock on a 186K-page brain.\n\nOutputs:\n- Pages retyped to canonical types with `frontmatter.legacy_type` preserved (per-page rollback signal).\n- `slug_aliases` rows for concept-redirect pages (alias table IS the resolver — no link rewrite).\n- Real `links` rows for edge-shaped pages (`atom-partner-link`, `symlink`, etc.).\n- Active pack flipped to `gbrain-base-v2` atomically at end of successful run.\n\nSide effects:\n- Source pages soft-deleted with 72h restore TTL (`gbrain restore \u003Cslug>`).\n- One-time cache invalidation on KNOBS_HASH_VERSION bump (5→6); self-healing in `cache.ttl_seconds`.\n- Search\u002Fquery `--type X` expands through the active pack's alias closure (back-compat).\n\nFailure modes:\n- Concurrent submission rejected by the `gbrain-unify` db-lock; second call exits gracefully.\n- Catch-all retype excludes `page_to_link` + `page_to_alias` source types (caught in E2E pre-merge).\n- Phase failures abort the run before `active_pack_flipped`; partial state restorable via op_checkpoint resume.\n\n## When it fails\n\nFollow the [agent operator protocol](..\u002Fconventions\u002Fagent-operator-protocol.md) for any gbrain error `code`, exit code, `[AGENT]` block or notice block. Specific to this skill:\n\n- A second unify submission is rejected because the `gbrain-unify` lock is held (\"already in progress\"): wait for the running job (`gbrain jobs get \u003Cid>`); do not resubmit.\n- A phase fails before `active_pack_flipped`: the pack did not change; resume from the checkpoint rather than restarting from scratch.\n- The run reports a cost line: retyping can call a model, so confirm the budget with the user before submitting on a large brain.\n\n## Anti-Patterns\n\nDON'T:\n- Submit `unify-types` via the remote MCP `submit_job` op. PROTECTED handlers require trusted local callers (`gbrain jobs submit` on the brain host); remote MCP rejection is the intentional trust boundary.\n- Edit `mapping_rules` in `gbrain-base-v2.yaml` to skip clusters you don't trust. Fork the pack instead (`gbrain schema fork`) so the source-of-truth migration stays consistent across brains.\n- Run `unify-types` from inside an autopilot tick. The check is `manual_only` — autopilot deliberately never auto-fires it because pack upgrades are one-time consenting taxonomy decisions.\n- Hard-delete soft-deleted source pages before the 72h restore window. Use `gbrain restore \u003Cslug>` first if rollback is needed.\n- Assume `frontmatter.legacy_type` survives every roundtrip. The marker is canonical for the immediate post-migration window; downstream re-imports may overwrite it.\n\n## Output Format\n\nPer phase, the handler emits to stderr:\n```\n[unify-types] phase=retype-explicit applied=N skipped=M  cost=USD  ttl=Ns\n[unify-types] phase=retype-catch-all applied=N\n[unify-types] phase=page-to-link converted=N pages soft-deleted\n[unify-types] phase=page-to-alias aliased=N pages soft-deleted\n[unify-types] phase=sync residual=N\n[unify-types] active_pack flipped from gbrain-base to gbrain-base-v2\n```\n\nFinal celebration summary to stderr:\n```\n═══════════════════════════════════════════════════════════\n  gbrain-base-v2 migration complete\n═══════════════════════════════════════════════════════════\n  Before: 94 distinct page types\n  After:  15 canonical types\n  Retyped:      25,632 pages\n  Aliased:       5,521 redirects → slug_aliases table\n  Linkified:        65 ghost pages → real link rows\n  Soft-deleted:  5,586 pages (restorable for 72h)\n═══════════════════════════════════════════════════════════\n```\n\nFor structured JSON, `gbrain call get_job '{\"id\": \u003Cid>}'` returns the job row; its `result` field carries the `UnifyTypesResult` shape with `per_phase`, `pack_identity_after`, `active_pack_flipped` (`gbrain jobs get \u003Cid>` prints the same result inline).\n\n## Reference\n\n- Architecture: https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgbrain\u002Fblob\u002Fmaster\u002Fdocs\u002Farchitecture\u002Ftype-taxonomy.md\n- Pack-upgrade mechanism: https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgbrain\u002Fblob\u002Fmaster\u002Fdocs\u002Farchitecture\u002Fpack-upgrade-mechanism.md\n- Issue: https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgbrain\u002Fissues\u002F1479\n","# Schema Unification (gbrain-base → gbrain-base-v2)\n\nv0.41.22 ships **gbrain-base-v2** — a 15-type DRY\u002FMECE taxonomy (14 canonical + `note` catch-all) — as the install default for new brains. Existing brains on `gbrain-base` can opt in via the `pack_upgrade_available` onboard finding + the `unify-types` PROTECTED Minion handler.\n\nThis skill is the playbook for that migration.\n\n## brain_first: exempt\n\nThis skill is ABOUT the brain's shape — it can't depend on the brain it's reshaping. No `gbrain search` lookup first; jump straight to onboard.\n\n## When this skill fires\n\n- Agent runs `gbrain onboard --check` and sees `pack_upgrade_available` or `type_proliferation` warnings\n- User asks \"what is the canonical taxonomy \u002F how do I clean up my page types \u002F migrate to v2\"\n- A `dangling_aliases` finding surfaces (post-unify GC)\n- An agent ingesting from a custom pack wants to consult the v2 taxonomy as a reference\n\n## Mental model (one paragraph)\n\nA production gbrain brain accreted **94 distinct `pages.type` values** over years of ingestion: tweet \u002F tweet-thread \u002F tweet-bundle \u002F tweet-single \u002F media\u002Fx-tweet\u002Fbundle \u002F tweet-stub all coexisting; 5.5K concept-redirect pages; atom-partner-link pages that should be links; civic \u002F framework \u002F insight \u002F memo \u002F anecdote one-offs. The cure: collapse to **15 canonical types** (person, company, media, tweet, social-digest, analysis, atom, concept, source, deal, email, slack, writing, project, note) with subtypes\u002Fformat\u002Forigin pushed to frontmatter, alias-rows for redirects, real link-rows for edge-shaped pages, and a catch-all that bins long-tail unknowns to `note` with `frontmatter.legacy_type = \u003Coriginal>` for rollback.\n\n## Workflow\n\n### Phase 1: Discovery\n\nConfirm the brain is actually on `gbrain-base` (not already on v2).\n\n```bash\ngbrain schema active --json | jq -r '.identity'\n```\n\nExpected: `gbrain-base@1.0.0+\u003Csha>`. If you see `gbrain-base-v2@...`, the brain is already on v2 — skip the migration.\n\nThen run onboard to see what would change:\n\n```bash\ngbrain onboard --check\n```\n\nLook for the `pack_upgrade_available` finding. If it's `ok`, there's no successor declared for the active pack — done.\n\n### Phase 2: Preview\n\nRun the per-cluster narrative:\n\n```bash\ngbrain onboard --check --explain\n```\n\nThis invokes the `unify-types` handler in dry-run mode and prints:\n- How many pages would retype per cluster (tweets, articles, companies, etc.)\n- How many concept-redirect pages would become alias rows\n- How many edge-shaped pages would convert to real links\n- The synthesized catch-all rules for unknown types\n\nReview the output. If the proposed changes look wrong, **don't** proceed — file an issue or write a custom pack with adjusted mapping_rules.\n\n### Phase 3: Apply\n\n**Managed brains: stop after the preview.** Applying the migration is not\nsupported on a managed brain (managed persistence on) yet: the retype writes\npages directly instead of through the coordinated writer, so the apply job is\nrefused (`writer_coordinator_required`) before it changes anything. The\n`pack_upgrade_available` finding says so on those brains. Keep using\n`gbrain onboard --check --explain` to preview, tell the user the pack upgrade\nwaits for coordinated retype support, and don't submit the apply job below.\nOne exception: when the preview shows nothing to retype, link or alias, the\napply job only switches the active pack, so it may be submitted with the\nuser's agreement.\n\nThe handler is PROTECTED (manual_only) — autopilot will never auto-fire it. Submit explicitly:\n\n```bash\ngbrain jobs submit unify-types \\\n  --params '{\"target_pack\":\"gbrain-base-v2\",\"apply\":true}'\n```\n\nOn PGLite (the install default), or on any setup without a running `gbrain jobs work` worker or supervisor daemon, add `--follow` so the job executes inline:\n\n```bash\ngbrain jobs submit unify-types \\\n  --follow \\\n  --params '{\"target_pack\":\"gbrain-base-v2\",\"apply\":true}'\n```\n\nThe persistent worker daemon is Postgres-only. Without `--follow` on PGLite, the job sits queued forever and the migration never runs.\n\n`apply` defaults to **false** (dry-run) per the handler contract, so\n`\"apply\":true` is required here or the job reports success having retyped\nnothing and left the active pack unflipped. Omit it to preview.\n\nWatch progress per phase (worker-daemon runs; with `--follow` the same progress streams inline):\n\n```bash\ngbrain jobs get \u003Cjob_id>      # one job: status, progress, result\ngbrain jobs watch --follow    # live dashboard of the whole queue\n```\n\nA job that stays `queued` here means no worker is running; resubmit with `--follow` to execute it inline.\n\nOn a 186K-page brain expect ~10 minutes. The handler runs:\n1. Preflight (validate target pack has `mapping_rules:`)\n2. Stats snapshot (pre-state for celebration summary)\n3. Acquire `gbrain-unify` db-lock (60min TTL)\n4. Apply phases:\n   - Explicit retype rules (tweets, articles, companies, etc.)\n   - Catch-all retype (unknown types → note with legacy_type)\n   - Page-to-link rules (atom-partner-link, symlink)\n   - Page-to-alias rules (concept-redirect)\n5. Final sync (untyped rows by path-prefix)\n6. **Flip active pack** to gbrain-base-v2\n7. Verify + celebration summary\n\n### Phase 4: Verify\n\n```bash\ngbrain onboard --check\ngbrain schema stats\n```\n\nExpected:\n- `pack_upgrade_available` → `ok` (active pack is now v2)\n- `type_proliferation` → `ok` (≤16 distinct typed values)\n- `dangling_aliases` → `ok` (slug_aliases all point at active canonicals)\n- `gbrain schema stats` shows ≤16 distinct types\n\n### Phase 5: Post-migration\n\nSearch and query `--type article` keep returning those pages post-unify: `media` declares `article` as an alias, so the type filter expands through the active pack's alias closure (the results also include other `media` pages). Direct SQL against `pages.type` needs updating to the canonical types.\n\nSearch queries get a small ranking signal: pages reached via `slug_aliases` (canonicals of one or more aliases) get a 1.05x boost. Visible via `gbrain search --explain`.\n\n## Rollback\n\nEvery retyped page preserves `frontmatter.legacy_type = \u003Coriginal>`.\n\nRestore types in bulk (Postgres\u002FSupabase deployments only; requires direct DB access):\n\n```sql\nUPDATE pages SET type = frontmatter->>'legacy_type'\nWHERE source_id = 'default' AND frontmatter->>'legacy_type' IS NOT NULL;\n```\n\nOn PGLite there is no SQL shell, so use the CLI surface instead: `frontmatter.legacy_type` persists per page, so individual retypes can be reverted through the normal `put_page`\u002FCLI surface, and the soft-delete restore and pack-flip revert below work on every engine.\n\nPage-to-alias and page-to-link source pages soft-delete with 72h TTL. Restore within that window:\n\n```bash\ngbrain restore \u003Cslug>\n```\n\nRevert the active pack flip:\n\n```bash\ngbrain schema use gbrain-base\n```\n\n## Anti-patterns\n\n- **Don't run unify-types under autopilot.** It's manual_only by design. Autopilot remediation should never silently change your taxonomy.\n- **Don't expect mapping_rules to cover every legacy type explicitly.** Use the catch-all (`*unknown*`) for the long tail. Pages get retyped to `note` with `legacy_type` preserved.\n- **Don't rewrite body-text wikilinks.** The slug_aliases table IS the resolver. `[[old-redirect-slug]]` keeps working via `engine.resolveSlugWithAlias` short-circuit.\n- **Don't bypass the dry-run.** Always run `--explain` before applying. The trust delta is real.\n- **Don't run two unify jobs concurrently.** The `gbrain-unify` db-lock serializes them; the second submission rejects with \"already in progress.\"\n\n## Decision tree\n\n```\nActive pack already gbrain-base-v2?\n  → Skip migration.\n\nCustom pack with own mapping_rules?\n  → Run --check --explain to see if your pack declares migration_from\n    for the active pack. If yes, target_pack = your pack name.\n\nBrain has many custom types not covered by gbrain-base-v2 mapping_rules?\n  → The catch-all retype binds them to `note` with legacy_type preserved.\n    Review by inspecting frontmatter.legacy_type after the migration.\n\nFederated brain (multiple sources)?\n  → Add --params source_id to scope the migration per-source. Each\n    source can be migrated independently.\n\nWorried about a specific cluster's mapping?\n  → Fork gbrain-base-v2 (`gbrain schema fork gbrain-base-v2 my-pack`),\n    edit mapping_rules in your fork, then target the fork.\n```\n\n## Contract\n\nInputs:\n- A brain on `gbrain-base` (or any pack with `migration_from: gbrain-base-v2`).\n- Trusted local CLI access on the brain host: `gbrain jobs submit` grants the PROTECTED-handler opt-in itself for protected names; the remote MCP `submit_job` op cannot.\n- ~10 min wallclock on a 186K-page brain.\n\nOutputs:\n- Pages retyped to canonical types with `frontmatter.legacy_type` preserved (per-page rollback signal).\n- `slug_aliases` rows for concept-redirect pages (alias table IS the resolver — no link rewrite).\n- Real `links` rows for edge-shaped pages (`atom-partner-link`, `symlink`, etc.).\n- Active pack flipped to `gbrain-base-v2` atomically at end of successful run.\n\nSide effects:\n- Source pages soft-deleted with 72h restore TTL (`gbrain restore \u003Cslug>`).\n- One-time cache invalidation on KNOBS_HASH_VERSION bump (5→6); self-healing in `cache.ttl_seconds`.\n- Search\u002Fquery `--type X` expands through the active pack's alias closure (back-compat).\n\nFailure modes:\n- Concurrent submission rejected by the `gbrain-unify` db-lock; second call exits gracefully.\n- Catch-all retype excludes `page_to_link` + `page_to_alias` source types (caught in E2E pre-merge).\n- Phase failures abort the run before `active_pack_flipped`; partial state restorable via op_checkpoint resume.\n\n## When it fails\n\nFollow the [agent operator protocol](..\u002Fconventions\u002Fagent-operator-protocol.md) for any gbrain error `code`, exit code, `[AGENT]` block or notice block. Specific to this skill:\n\n- A second unify submission is rejected because the `gbrain-unify` lock is held (\"already in progress\"): wait for the running job (`gbrain jobs get \u003Cid>`); do not resubmit.\n- A phase fails before `active_pack_flipped`: the pack did not change; resume from the checkpoint rather than restarting from scratch.\n- The run reports a cost line: retyping can call a model, so confirm the budget with the user before submitting on a large brain.\n\n## Anti-Patterns\n\nDON'T:\n- Submit `unify-types` via the remote MCP `submit_job` op. PROTECTED handlers require trusted local callers (`gbrain jobs submit` on the brain host); remote MCP rejection is the intentional trust boundary.\n- Edit `mapping_rules` in `gbrain-base-v2.yaml` to skip clusters you don't trust. Fork the pack instead (`gbrain schema fork`) so the source-of-truth migration stays consistent across brains.\n- Run `unify-types` from inside an autopilot tick. The check is `manual_only` — autopilot deliberately never auto-fires it because pack upgrades are one-time consenting taxonomy decisions.\n- Hard-delete soft-deleted source pages before the 72h restore window. Use `gbrain restore \u003Cslug>` first if rollback is needed.\n- Assume `frontmatter.legacy_type` survives every roundtrip. The marker is canonical for the immediate post-migration window; downstream re-imports may overwrite it.\n\n## Output Format\n\nPer phase, the handler emits to stderr:\n```\n[unify-types] phase=retype-explicit applied=N skipped=M  cost=USD  ttl=Ns\n[unify-types] phase=retype-catch-all applied=N\n[unify-types] phase=page-to-link converted=N pages soft-deleted\n[unify-types] phase=page-to-alias aliased=N pages soft-deleted\n[unify-types] phase=sync residual=N\n[unify-types] active_pack flipped from gbrain-base to gbrain-base-v2\n```\n\nFinal celebration summary to stderr:\n```\n═══════════════════════════════════════════════════════════\n  gbrain-base-v2 migration complete\n═══════════════════════════════════════════════════════════\n  Before: 94 distinct page types\n  After:  15 canonical types\n  Retyped:      25,632 pages\n  Aliased:       5,521 redirects → slug_aliases table\n  Linkified:        65 ghost pages → real link rows\n  Soft-deleted:  5,586 pages (restorable for 72h)\n═══════════════════════════════════════════════════════════\n```\n\nFor structured JSON, `gbrain call get_job '{\"id\": \u003Cid>}'` returns the job row; its `result` field carries the `UnifyTypesResult` shape with `per_phase`, `pack_identity_after`, `active_pack_flipped` (`gbrain jobs get \u003Cid>` prints the same result inline).\n\n## Reference\n\n- Architecture: https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgbrain\u002Fblob\u002Fmaster\u002Fdocs\u002Farchitecture\u002Ftype-taxonomy.md\n- Pack-upgrade mechanism: https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgbrain\u002Fblob\u002Fmaster\u002Fdocs\u002Farchitecture\u002Fpack-upgrade-mechanism.md\n- Issue: https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgbrain\u002Fissues\u002F1479\n",{"name":5,"tools":50,"triggers":61,"brain_first":72,"description":7},[51,52,53,54,55,56,57,58,59,60],"gbrain onboard --check","gbrain onboard --check --explain","gbrain onboard --check --json","gbrain jobs submit unify-types","gbrain jobs get","gbrain schema active","gbrain schema use","gbrain schema stats","gbrain restore","mcp:run_onboard",[62,63,64,65,66,67,68,69,70,71],"unify my types","migrate to gbrain-base-v2","94 types to 14","apply canonical taxonomy","clean up my page types","pack upgrade","shrink type proliferation","what does the canonical taxonomy look like","consolidate page types","retype pages to canonical","exempt","master","074ec301549ded4930f04d2c3b22d709a6c095cc","skills\u002Fschema-unify",4902,"https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgbrain","https:\u002F\u002Fgithub.com\u002Fgarrytan","https:\u002F\u002Fblog.garrytan.com","Writes software, dreams. ","2026-11-14 04:11:30",{"source":8,"skillName":5,"commands":83,"prompt":93,"zipAvailable":94},[84,87,90],{"tool":85,"command":86},"npx","npx skills add garrytan\u002Fgbrain --skill schema-unify",{"tool":88,"command":89},"bunx","bunx skills add garrytan\u002Fgbrain --skill schema-unify",{"tool":91,"command":92},"pnpm","pnpm dlx skills add garrytan\u002Fgbrain --skill schema-unify","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgbrain\u002Fblob\u002Fmaster\u002Fskills\u002Fschema-unify\u002FSKILL.md",true,[],[97,110,116,137,164,177],{"skillNo":98,"name":99,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":100,"categories":101,"keywords":102,"publishedAt":45},"01M6N109FNGH1PK49T734PZXGB","query-helper","https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgbrain\u002Fblob\u002Fmaster\u002Ftest\u002Ffixtures\u002Fskill-catalog\u002Fskills\u002Fquery-helper\u002FSKILL.md",[],[103,104,105,106,107,108,109],"query helper","helper","query","search","page","find","false",{"skillNo":111,"name":112,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":113,"categories":114,"keywords":115,"publishedAt":45},"01M6N108A23GW6NGQEQ0E7GV0A","broken","https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgbrain\u002Fblob\u002Fmaster\u002Ftest\u002Ffixtures\u002Fskill-catalog\u002Fskills\u002Fbroken\u002FSKILL.md",[],[112],{"skillNo":117,"name":118,"nameZh":6,"description":119,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":120,"categories":121,"keywords":122,"publishedAt":45},"01M6N105E88C5K2H03H0CC79G1","brain-ops","Read, enrich, and write brain pages with source attribution.","https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgbrain\u002Fblob\u002Fmaster\u002Ftest\u002Ffixtures\u002Fskill-catalog\u002Fskills\u002Fbrain-ops\u002FSKILL.md",[],[123,34,124,125,126,107,127,128,41,129,105,130,131,132,133,134,135,136,106],"brain ops","ops","true","attribution","read","write","look","pages","people","with","enrich","web","path","source",{"skillNo":138,"name":139,"nameZh":6,"description":140,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":141,"categories":142,"keywords":148,"publishedAt":45},"01M6N102GRMF22TVCNT6RK5MRD","meeting-prep","Build a briefing before you meet someone, from their recent threads, open loops and what they care about.","https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgbrain\u002Fblob\u002Fmaster\u002Ftest\u002Ffixtures\u002Fskill-catalog-query\u002Fskills\u002Fmeeting-prep\u002FSKILL.md",[143],{"categoryNo":144,"categoryName":145,"taxonomyType":146,"taxonomyTypeDesc":147},"01JBSEEDCAT000000000000034","行政与办公人员","PROFESSION","适用职业",[149,150,151,152,153,106,154,155,156,105,109,157,158,159,160,161,162,163],"meeting","prep","meeting prep","me","prepare","brief","open","build","loops","about","you","briefing","threads","for","Meet",{"skillNo":165,"name":166,"nameZh":6,"description":167,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":168,"categories":169,"keywords":170,"publishedAt":45},"01M6N1005QK7REHX5KGMNR5PF0","inbox-zero","Triage the inbox into replies, delegations and archives.","https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgbrain\u002Fblob\u002Fmaster\u002Ftest\u002Ffixtures\u002Fskill-catalog-query\u002Fskills\u002Finbox-zero\u002FSKILL.md",[],[171,172,173,106,174,109,175,30,176],"inbox zero","ZeRO","inbox","clear","replies","triage",{"skillNo":178,"name":179,"nameZh":6,"description":180,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":181,"categories":182,"keywords":183,"publishedAt":45},"01M6N0ZXQ8RRRW71PNV61T57GP","deal-memo","Draft an investment memo from the deal pages and the founder history already in the brain.","https:\u002F\u002Fgithub.com\u002Fgarrytan\u002Fgbrain\u002Fblob\u002Fmaster\u002Ftest\u002Ffixtures\u002Fskill-catalog-query\u002Fskills\u002Fdeal-memo\u002FSKILL.md",[],[184,185,186,130,34,187,188,128,189,125,106,190],"deal memo","deal","memo","draft","investment","history","founder"]