본문으로 건너뛰기
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).

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

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-matrx42026년 10월 23일 업데이트

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-matrx42026년 10월 23일 업데이트

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-matrx42026년 10월 23일 업데이트

The method for getting a cheaper model to match an agent's big-model answers by fixing instructions, inputs and setup. Use when asked to move an agent to a smaller or cheaper model, prove one model beats another, or compare models on an agent's test cases.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

armanisadeghi/ai-matrx42026년 10월 23일 업데이트

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-matrx42026년 10월 23일 업데이트

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-matrx42026년 10월 23일 업데이트

armanisadeghi의 스킬 모두 보기

이 스킬의 문제 신고