本文へ移動
cccskills
無料GitHub で公開

canonical-associations

The one association system (platform.associations) and schema-qualified table refs. Use when touching an M2M junction table, an associate_*/attach_*/tag_* RPC, FK columns used as tags, a 'things attached to X' list or card, or a PGRST205 / 42703 table-not-found on a bare .from().

インストール方法を見る

含まれるファイル(6)

  • SKILL.md13.9 KB
  • recipe-a-replace-m2m.md5.9 KB
  • recipe-b-container-cards.md2.4 KB
  • recipe-c-table-refs.md1.4 KB
  • STATUS.md7.4 KB
  • WORK-QUEUE.md13.4 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

Canonical Associations — the campaign playbook

W5 SWAP NOTICE (2026-08-29): the association/category UI + hooks + service implementations now ship in @ai-matrx/associations (/react for faces + hooks, /core for the headless services/store). Paths in this document that point at features/scopes/components/associations/**, features/scopes/components/Category*, or features/scopes/redux/** association/category fragments refer to DELETED files — import from @ai-matrx/associations/react (hooks also re-exported under features/scopes/hooks/), and see features/scopes/host/ for the host binding. The rules and contracts described remain in force.

There is ONE way to relate two entities (content↔content, content↔container) in this app: an edge in platform.associations, written ONLY through associationsService (features/scopes/service/associationsService.ts), which is the sole caller of the assoc_* RPCs. Every bespoke M2M junction table, associate_*/get_*_associations RPC, and FK-tag column used as a relationship is debt to migrate onto this edge. This skill is the exact recipe a subagent follows, one file at a time.

Which recipe — each run uses one

The rules below (field realities, tombstones, the boundary, the token rule, edge direction) apply to every run. Then read ONLY the recipe your task needs:

  • Replacing a bespoke M2M junction, an associate_*/get_*_associations RPC, or an FK-tag column → read recipe-a-replace-m2m.md (Recipe A FE repoint + Recipe A-DB junction collapse — they ship in ONE change).
  • Putting association cards, a picker, or a name dropdown on a container page ("resources for this org/scope/project", "things related to X"), or adding a new card kind → read recipe-b-container-cards.md.
  • A bare .from("<moved-table>"), PGRST205, or 42703 → read recipe-c-table-refs.md.
  • Running a whole flip (DB collapse + FE repoint) → the Campaign workflow below, plus the recipe files it names.

⚡ Field realities — "make this table associable everywhere" (verified 2026-07-05)

When a newly-canonicalized entity must be "added to all the places we have associations (orgs, war rooms, etc.)", it is almost entirely ONE edit — do NOT hunt for per-surface lists:

  1. The card/picker gate is a titleColumn in ENTITY_OVERLAY (features/scopes/registry/entityRegistry.ts) — NOT is_listed. listableTokens()/canListCandidates = titleColumn != null || listCandidates. Add ONE overlay line (token: { Icon, labelPlural, titleColumn: "name", contentRole }) and the entity lights up as an attachable card on the org page, war rooms, projects, scopes, and every container at once. War-room has been open-vocabulary since 2026-07-03 (all whitelists deleted → AssociationList/UniversalAssociationPicker read the same registry), so there is NO separate war-room list to edit.
  2. Register the ENTITY tokens, never the component token. A *_version/component token (is_component=true) is not an attachable card — omit it. Owner/org are the created_by/organization_id conventions; only override in the overlay if the table truly diverges.
  3. Tokens come from the installed @ai-matrx/associations vocabulary (EntityTypeToken, ENTITY_TYPE_METADATA), generated inside the package from platform.entity_types — after the DB registers the token, regenerate + patch-release the package and pnpm up @ai-matrx/associations here (pnpm check:entity-types shows the drift) so the token exists before you reference it in the overlay. There is no local copy.
  4. Matrx Directives (aidream) is a SEPARATE, BIDIRECTIONAL registry — entity_types registration does NOT put a table there. The catalog (aidream/services/directive_catalog/catalog.py) is driven by the references registry (aidream/services/references/resources.py → drift.py): every registered reference shape MUST appear in the catalog with reference=Y AND every catalog reference=Y MUST have a registered reference shape. To add a table to Matrx Directives, register it in resources.py first; adding a catalog row for an unregistered token fails drift, and vice-versa. Not every entity is referenceable (there is no generic folder reference type — so code_folder is intentionally absent while code_file/code_repository are present).
  5. A raw (schema, table) resolves to its canonical display via tryGetEntityInfoByTable(schema, table) (added to entityRegistry.ts). Any surface keyed by a live table name (FK-reference panels, drift reports) MUST resolve icon/label/grouping through it — never a hand-maintained TABLE_META map (that duplication drifts; ProjectReferencesPanel was migrated off exactly this).
  6. Structural FKs stay FKs — do NOT migrate them into associations. A canonicalized entity's own folder_id/repository_id/parent_folder_id/project_id/task_id are real hierarchy/ownership columns. Registering the token makes OTHER things associate to the entity; it does not turn the entity's own FKs into edges.

🚨 Edges are TOMBSTONED, never purged, when an endpoint is trashed (2026-08-14, D135)

platform.associations carries deleted_at + a deleted_via_type/deleted_via_id stamp. Soft-deleting an entity tombstones its live edges; restoring it revives exactly those; only a hard DELETE purges. Every READ is live-only — SQL reads platform.associations_live, a direct PostgREST read passes .is("deleted_at", null), aidream's ORM engine filters it automatically. A reader that sees a tombstone is an access leak (edges convey access via platform.containment_edges → platform.reachability). Re-attaching over a tombstone revives it (BEFORE INSERT trigger) — never "fix" a unique violation by deleting the tombstone. Full contract: common-docs/systems/platform/access/FEATURE.md §2.4c.

🚨 An edge that copies an endpoint's content is gated by BOTH ends (RC-A5, 2026-09-25)

A payload that quotes or copies an endpoint (a text_anchor passage, a relation_snapshot, the variables of a render_binding) is readable only by someone who can open both ends: the restrictive policy assoc_payload_follows_endpoints reads platform.edge_payload_kind.payload_follows_endpoints, which defaults to TRUE for every new kind. Set it false ONLY with a payload_follows_endpoints_reason saying the payload describes the edge itself. Never copy an endpoint's title or text into label or metadata — those columns are read by every member of the edge's organization; read the title at display time instead. Design: common-docs/projects/rich-content-unification/PLAN.md.

The one load-bearing boundary — DO NOT cross it

platform.associations is the M2M association edge. It does NOT absorb two adjacent single-home domains. Leave them alone:

  • iam.permissions = access control / RLS visibility / sharing (share_resource_with_user — a share names a person; share_resource_with_org refuses — plus grant_org_availability for organization configuration, has_permission, the ONE resolver iam.has_access/has_access_for, the shareable_resource_registry). Deleting/repointing it tears out who-can-see-what. KEEP. (check_resource_access was retired 2026-07-15 — never reference or preserve it.)
  • iam.memberships = org/project membership (mbr_*). KEEP.

If a relationship answers "who is allowed to see/do this?" → it's iam.*, not associations. If it answers "what content/containers is this attached to?" → it's platform.associations. When unsure, ask; do not guess and migrate a permissions grant into an association edge.

Where the two meet — contextual access. An association edge can convey access: association_types.conveys_max + container_side feed platform.reachability, so a thing attached to a container the user can reach becomes readable through that container (iam.has_access_for) — but it must NEVER become discoverable in that thing's own lists/search (iam.is_discoverable, which skips conveyance). Before registering a pair with a non-null conveys_max, or building any list/search over an entity that can be attached, read ../aidream/docs/access/CONTEXTUAL_ACCESS.md (the reads-use-has_access / lists-use-is_discoverable rule + the per-surface leak gotcha).

Non-negotiable token rule (this caused the original disaster)

Every sourceType/targetType MUST be a canonical EntityTypeToken — generated 1:1 from platform.entity_types into @ai-matrx/associations (import it from there). ZERO legacy/guessed names. associationGuards rejects non-canonical tokens and non-UUID ids at the call site, so a wrong token throws in code, not at Postgres.

  • A token missing for a real entity → register the entity in platform.entity_types (DB), regenerate (pnpm tsx scripts/generate-entity-types.ts), then use it. Never alias.
  • A token that's just a wrong name (agent_app→app, user_file→file, notes→note) → repoint the callsite to the canonical token. Never add a compatibility map.
  • Ids are row UUIDs, never display strings. (The original bug: an agent passed a cute string as an id.)

Edge direction and one canonical path — every edge write

Direction is canonical and fixed: little points to big — the smaller thing is the source, the bigger thing it points to is the target. task → organization, file → scope, note → project, project → war_room (a war room is bigger than a project — many threads make a war room). (Same direction as scope-tagging; a container's attached resources are its INCOMING edges.) The registry platform.association_types is the single truth of direction per pair; trg_associations_auto_orient REJECTS a wrong-way write of a registered pair with an error naming the canonical direction, and /administration/relationships flags reversed edges. The size hierarchy is a product fact — if a pair's direction seems wrong, ASK; never flip the registry or edges on your own judgment. Registering a NEW pair: insert it in /administration/relationships (or admin_upsert_relationship_rule) with container_side='none' — whether it conveys access is a human decision made there.

A relationship has exactly one canonical path. If two surfaces reach the same edge two ways (one via associations, one via a junction), that's the bug — collapse to associations.

Create-then-associate contract: a surface that CREATES an item and attaches it (upload→attach, "+ New X") creates the durable row FIRST, writes the idempotent edge SECOND, and makes every terminal outcome loud — a created-but-unlinked item is reported WITH its location, never silently orphaned. Full contract + reference implementations: the association-entity-select skill.


Recipe A — Replace a bespoke M2M / association RPC with associationsService

Replacing a junction / association RPC → read recipe-a-replace-m2m.md — the write/read/hook swaps and retiring the old path.

Recipe A-DB — collapse the junction in the DB (2026 downtime SOP)

Collapsing the junction in the DB → read recipe-a-replace-m2m.md — the audit toolkit and the atomic, count-verified migration.

Recipe B — Put an association surface on a container (the card system)

Container card / picker / name-dropdown work → read recipe-b-container-cards.md — provider mount, the one overlay line, getEntityInfo, the third face.

Recipe C — Canonicalize a table reference (kills PGRST205 / 42703)

PGRST205 / 42703 / bare moved-table ref → read recipe-c-table-refs.md — find the canonical home, qualify, register in dead-relations.json, verify.


Campaign workflow (per flip)

  1. Pick a target: a genuine junction from audit.m2m_candidates (now factual) or a file from WORK-QUEUE.md.
  2. DB collapse (Recipe A-DB) + FE repoint (Recipe A / B / C) in ONE change. One canonical path only.
  3. pnpm check:schema + pnpm check:dead-relations green; touched files type-check; audit.refresh() → junction gone from candidates, no new broken_functions.
  4. Adversarial sweep (mandatory before "done"). Spawn a fresh agent (Sonnet 5) in EACH repo — matrx-frontend AND aidream — whose sole job is to REFUTE that the migration is complete: grep for any surviving old-shape usage (.from("<junction>"), the retired RPC, a dropped column, a PostgREST embed of the old relationship, Python ORM models/managers of the moved table). Anything it finds is unfinished work, not noise. A confirmed-empty sweep is the sign-off.
  5. Update the feature's FEATURE.md + Change Log if behavior changed; log the flip to platform.deprecated_relations + the worklog Done log; tick WORK-QUEUE.md.

Guardrails to lean on: scripts/schema-check/ (live diff + dead-relations), eslint.config.mjs (direct-schema ban — extend it to fail-fast in-editor when a whole class is done), pnpm check:doctrine. Loud recovery: any fallback you add (RLS-only candidate read, etc.) must console.error when it fires — a recovery firing means a real ref is still wrong.

Backlog

The prioritized, file-anchored campaign backlog lives in WORK-QUEUE.md next to this skill. Start there; keep it current as items land.

Working tool.bundle_member or a research.* junction, or re-checking a triage verdict → read STATUS.md (the 2026-06-29 facts-only migration snapshot: triage verdicts, the bundle_member finding).

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Admin debug system wiring for the floating AdminIndicator's live debug data and Copy Full Context. Use when adding debug visibility to a route or feature, wiring useDebugContext or the debug panel, capturing console errors, or enabling the copy-context workflow for a page.

日本語の概要は準備中です。原文の説明を表示しています。

armanisadeghi/ai-matrx32026年10月13日 更新

Compact two-icon Copy / Copy-for-AI controls (components/agent-copy). Use when adding copy buttons to a row, card, list, or record; merging duplicate Copy/JSON/AI controls; continuing the copy rollout; or writing a Copy-for-AI payload. NOT for markdown content actions (use rich-document-actions).

日本語の概要は準備中です。原文の説明を表示しています。

armanisadeghi/ai-matrx32026年10月13日 更新

Disclosing a surface's existing fixed AI jobs in the shell's top Agents menu. Use when a page, panel, overlay, or window already runs a mandate behind a button, assist, automatic action, or mode; when the agent-disclosure guard names a file; or during a surface check.

日本語の概要は準備中です。原文の説明を表示しています。

armanisadeghi/ai-matrx32026年10月13日 更新

The watch-fix-rerun method for making an existing platform agent or automated process efficient and correct: baseline its ledger, run one unit yourself, fix the class behind every wasted call, rerun, record. Use when asked to improve, optimize, watch, or 'make efficient' an agent, a sandbox session, a sync job, or any recurring automated process.

日本語の概要は準備中です。原文の説明を表示しています。

armanisadeghi/ai-matrx32026年10月13日 更新

Redux state for agent execution and firing agent shortcuts. Use when editing features/agents/redux/, building agent UI, creating a conversation, touching assembleRequest or NDJSON stream state, wiring agent-state selectors, adding a per-conversation capability, or triggering a shortcut from a button, menu, or code (useShortcutTrigger, launchAgentExecution).

日本語の概要は準備中です。原文の説明を表示しています。

armanisadeghi/ai-matrx32026年10月13日 更新

The provision for a call site: the exhaustive menu of values that place in the code can realistically produce. Use before creating or fixing a mandate or agent that reasons about more than its own input, when deciding whether an agent can answer at all with what it is sent, or when its output 'looks right' but nobody checked it could be. NOT for building the agent (use create-agent).

日本語の概要は準備中です。原文の説明を表示しています。

armanisadeghi/ai-matrx32026年10月13日 更新

armanisadeghi のスキルをすべて見る

このスキルの問題を報告する