@ai-matrx/associations 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,122 @@
1
+ # Changelog — @ai-matrx/associations
2
+
3
+ ## 0.2.0 — 2026-08-29 (unreleased; W2 of the extraction)
4
+
5
+ Wave 2: the headless owns-persistence `/core` subpath — the service
6
+ chokepoint, guards, the loud result funnel, the registry merge engine, the
7
+ subscribable cache store, titles/favorites/categories/candidates/entity-rows,
8
+ and the falsifiable `assertDemandedSchema` probe. Behavior ported VERBATIM
9
+ from the matrx-frontend originals (census appendix, re-read in full
10
+ 2026-08-29); only the measured seams inverted onto the W1 ports.
11
+
12
+ - **`createAssociationsStore(cfg)`** (`src/core/store.ts`) — the one runtime
13
+ object a host constructs. Required ports (dataSource/identity/errorSink)
14
+ throw at construction, each with a named message. Replaces matrx-frontend's
15
+ ~530 lines of Redux fragments with verbatim cache behavior: per-key
16
+ in-flight dedup, ready short-circuit unless `force`, pending retains prior
17
+ edges AND the visible error, add/remove reload BOTH endpoints, setTargets
18
+ reloads the source only, category echo-insert then authoritative reload,
19
+ stable idle defaults (`useSyncExternalStore` contract), `subscribe`/
20
+ `subscribeCategories`/`invalidate`/`invalidateAll`. The "never dispatch
21
+ appContextSlice" law is now STRUCTURAL (no host-store import path exists)
22
+ and proven by test: a write notifies only the two endpoint keys.
23
+ - **Services** as sink-bound factories over the `dataSource` port, each
24
+ method returning the never-throw envelope: `associationsService` (incl. the
25
+ stricter file→conversation `conversation_file_*` routing and the
26
+ `agent_resource_*` pair), `categoriesService`, `favoritesService`,
27
+ `candidates` (generic `reference_search_candidates` read + overlay
28
+ override + universal cross-token search), `titles` (chunked batch resolve,
29
+ prime, fallback), `entityRows` (registry-convention create/rename),
30
+ `helpers` (linkEdges/linkOneToMany/linkManyToOne/replaceTargets/
31
+ linkCreated/unlinkEdges), and the pure edge classifier
32
+ (`isContentSourceEdge` exclusion-list law).
33
+ - **`rpcResult`**: `createRpcResultHelpers(errorSink)` — the original mapper
34
+ verbatim PLUS the new PGRST202 → `demanded_schema_violation` branch that
35
+ ALWAYS reaches the errorSink and names the demanded-schema contract.
36
+ - **`assertDemandedSchema(dataSource, { selfTest })`**: probes all 23
37
+ demanded RPCs with ZERO side effects (every uuid arg is an unparseable
38
+ sentinel, so PostgREST resolves the function but Postgres fails the cast
39
+ before any body executes — verified against the live SECURITY DEFINER
40
+ sources 2026-08-29). `selfTest: true` probes a fabricated name that MUST
41
+ come back PGRST202, so a dataSource that cannot fail is refused
42
+ (falsifiability rule).
43
+ - **Registry merge engine** (`src/core/registry.ts`): generated metadata +
44
+ host `entityOverlay` port, convention owner/org columns, reads-tolerant
45
+ alias resolution (`cld_file` → `file`), curated/listable token sets with
46
+ the pickable-but-unlistable scream.
47
+ - **Seam inversions (deliberate, per the packaging standard)**: supabase
48
+ singleton → `dataSource`; `requireUserId` store read → `identity`; every
49
+ `console.warn`/`console.error` scream → `errorSink` events (plain
50
+ PostgREST result errors still do NOT double-report — hosts capture those
51
+ at the dataSource layer — except PGRST202, which always screams);
52
+ `isTransportFailure` re-implemented in-package (the `@ai-matrx/data/net`
53
+ original's NetError-class branches cannot occur here; the plain-object/
54
+ TypeError/navigator branches ported verbatim) to keep `/core` at zero
55
+ runtime dependencies; lucide `DEFAULT_ICON` → `Icon: null` (C19 — the
56
+ consumer renders its default); the module-level entity-title Map →
57
+ store-instance state (no module-level mutable state exists anywhere in
58
+ `/core`, so there is nothing for dual-loaded graphs to split and no
59
+ globalThis slot is needed); `favoritesService` + `favoritesCore` (two host
60
+ files split only by the singleton coupling) collapse back into ONE
61
+ implementation.
62
+ - **Port addition (additive)**: `AssociationsDataSource` gains OPTIONAL
63
+ structural `from`/`schema` members — entity-row create/rename write TABLES
64
+ (verbatim original behavior), not RPCs. An rpc-only dataSource still
65
+ satisfies everything else; entity-row calls against it return a screamed
66
+ error naming the missing capability.
67
+ - **Defects found in the originals**: none requiring a behavior change; two
68
+ latent strictness holes hardened in the port (unchecked `failures[0]` in
69
+ the helper combiner and unchecked `list[cursor]` in the search worker —
70
+ both unreachable in practice, both now guarded under
71
+ `noUncheckedIndexedAccess`).
72
+ - Tests: 97 (from 26) — every rpcResult mapping branch incl. PGRST202,
73
+ guards + alias recovery, every service branch (file→conversation boundary,
74
+ id-echo checks, dedupe/empty short-circuits), registry merge/degrade,
75
+ candidates/titles/entity-rows incl. chunking and capability screams, the
76
+ full store behavior suite incl. the never-touches-context invariant, and
77
+ the probe proven falsifiable both ways.
78
+ - Gate: `pnpm typecheck && pnpm test && pnpm check:package` green; the
79
+ tarball canary now imports AND requires `/core`, constructs a store
80
+ against fake ports, proves required-port throw, and bans "use client" in
81
+ all four dist files.
82
+
83
+ ## 0.1.0 — 2026-08-29 (unreleased; W1 of the extraction)
84
+
85
+ Wave 1 of the associations extraction
86
+ (`common-docs/projects/npm-package-extraction/ASSOCIATIONS-PACKAGE-DESIGN.md`,
87
+ ratified C18): the types root.
88
+
89
+ - **Entity-type vocabulary in-package**: `src/entity-types.generated.ts`
90
+ (651 tokens, generated live from `platform.entity_types` on 2026-08-29) plus
91
+ the generator (`scripts/generate-entity-types.ts`, ported from matrx-frontend
92
+ `scripts/generate-entity-types.ts`). The app-side ENTITY_OVERLAY duplication
93
+ guard stays in matrx-frontend's `check:entity-types` until the W5 elimination
94
+ pass (the overlay is host code). Skew policy per C18: writes strict, reads
95
+ tolerant; registry changes ship as patch releases.
96
+ - **Demanded RPC contract**: `src/demanded-rpcs.generated.ts` — the 23-function
97
+ surface (`DemandedRpcSignatures` / `DemandedRpcName` / `DEMANDED_RPC_NAMES`),
98
+ sliced from the live-generated platform DB contract by
99
+ `scripts/extract-demanded-rpcs.ts`. The legacy `assoc_link`/`assoc_list`/
100
+ `assoc_unlink` trio is deliberately not demanded (zero client callers;
101
+ flagged for server-side retirement).
102
+ - **Edge/result types** ported verbatim from matrx-frontend
103
+ `features/scopes/types.ts` (`AssociationEdge`, `AssociationTargetEdge`,
104
+ `AssociationSourceEdge`, `AssociationsEntry`, `UserEntityState`,
105
+ `PlatformCategory`, `CategoriesEntry`, `ASSOCIATION_TARGET_TYPES`) with two
106
+ deliberate seam inversions, per the packaging standard:
107
+ - the host contract's `Json` type became `unknown` (JSON-boundary rule);
108
+ - `ScopesRpcError`/`ScopesRpcResult` became `AssociationsRpcError`/
109
+ `AssociationsRpcResult`, and the error-code union gained
110
+ `demanded_schema_violation` (the PGRST202 → demanded-schema scream that
111
+ `/core`'s `rpcResult` will map in W2; `template_missing`, a scopes-only
112
+ code, was not carried over).
113
+ - **The seven ports** (`src/ports.ts`): `AssociationsDataSource`,
114
+ `AssociationsIdentity`, `ErrorSink` (required) + `EntityOverlayEntry`,
115
+ `AssociationsNotifier`, `WindowShellPort`, `CapturePort`,
116
+ `PickerOverrideMap`, `EntityDoorsPort` (optional, defined degradations),
117
+ combined in `AssociationsConfig`. Shapes distilled from the measured
118
+ matrx-frontend call sites (census 2026-08-29), nothing speculative.
119
+ - Gate: `pnpm typecheck && pnpm test && pnpm check:package` (build, publint,
120
+ attw on the packed tarball, empty-project ESM import + CJS require canary,
121
+ pure-root "use client" ban) — all green; `check:entity-types` drift gate
122
+ proven falsifiable against the live registry.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 AI Matrix Engine
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,115 @@
1
+ # @ai-matrx/associations
2
+
3
+ THE one way to relate two entities on the AI Matrx platform: association edges,
4
+ categories, favorites/recents, and the generated entity-type vocabulary — shipped
5
+ as one package a Matrx client or enterprise app installs.
6
+
7
+ **v0.2 ships the types root and the headless `/core`** — the service
8
+ chokepoint, guards, the never-throw result funnel, the registry merge engine,
9
+ and the subscribable cache store. The React faces (`/react`: hooks, cards,
10
+ list, pickers, capture toolbar) arrive in the next waves. Everything below
11
+ about the schema is binding — it is the package's product.
12
+
13
+ ```ts
14
+ import {
15
+ type EntityTypeToken, // 651-token union generated from platform.entity_types
16
+ ENTITY_TYPE_METADATA, // per-token schema/table/label/titleColumn/contentRole
17
+ isEntityTypeToken, // runtime guard, narrows to the union
18
+ type AssociationEdge, // one assoc_for_entity row (both directions)
19
+ type AssociationsRpcResult, // the never-throw result envelope
20
+ type DemandedRpcName, // the 23-function demanded RPC surface
21
+ type AssociationsConfig, // the seven ports a host binds
22
+ } from "@ai-matrx/associations";
23
+ ```
24
+
25
+ ```ts
26
+ import {
27
+ createAssociationsStore, // the ONE runtime object a host constructs
28
+ assertDemandedSchema, // boot probe: every demanded RPC must answer
29
+ } from "@ai-matrx/associations/core";
30
+
31
+ const store = createAssociationsStore({
32
+ dataSource: supabase, // structural subset of SupabaseClient
33
+ identity: { requireUserId }, // throws when unauthenticated
34
+ errorSink: (e) => report(e), // recovery layers scream here
35
+ entityOverlay: { file: { hrefFor: (id) => `/files/f/${id}` } }, // optional
36
+ });
37
+
38
+ await store.load("task", taskId); // lazy, deduped, subscribable
39
+ store.getEdges("task", taskId); // sync, stable idle default
40
+ await store.add({ sourceType: "note", sourceId, targetType: "task", targetId });
41
+ store.titles.prime("task", taskId, "Renamed");
42
+ await store.favorites.setFavorite("note", noteId, true);
43
+
44
+ // Dev/CI boot check for any new environment (zero side effects; selfTest
45
+ // proves the probe can fail before you trust its green):
46
+ await assertDemandedSchema(supabase, { selfTest: true });
47
+ ```
48
+
49
+ ## The demanded schema — read this first
50
+
51
+ This package is **opinionated like the Python `matrx-*` packages**: it does not
52
+ adapt to your database — it DEMANDS ours. A consumer who won't run our schema is
53
+ not this package's consumer. It ships no DDL (the platform DB owns that; managed
54
+ consumers run on our infrastructure); it ships the demand three ways —
55
+ documented (here), typed (`DemandedRpcSignatures`, generated from the live DB),
56
+ and probed (`assertDemandedSchema` in `/core`). Every violation
57
+ **screams, never degrades**.
58
+
59
+ 1. **The edge.** `platform.associations(source_type, source_id, target_type,
60
+ target_id, organization_id, label, metadata, role, position, created_by,
61
+ created_at, deleted_at, …)` — one polymorphic table, unique on the 5-tuple
62
+ `(source_type, source_id, target_type, target_id, role)` `NULLS NOT DISTINCT`.
63
+ Type columns are FK-validated against `platform.entity_types.token`.
64
+ 2. **RPC-only.** The client holds **no grant** on the table. Every operation goes
65
+ through the 23 SECURITY DEFINER functions in `DEMANDED_RPC_NAMES` (`assoc_*`
66
+ ×8, `conversation_file*` ×3, `agent_resource_*` ×2, `cat_*` ×5, `ues_*` ×4,
67
+ `reference_search_candidates`). Those readers consume the live-only
68
+ `associations_live` view, so tombstone semantics (a trashed entity
69
+ soft-removes its edges; restore revives them) hold by construction — this
70
+ package never writes a `.is("deleted_at", null)` filter. The DB's legacy
71
+ `assoc_link`/`assoc_list`/`assoc_unlink` trio is deliberately NOT demanded.
72
+ 3. **Direction is registry-owned: little→big.** The resource is the SOURCE, the
73
+ container the TARGET (`task → organization`, `file → scope`). The DB trigger
74
+ `trg_associations_auto_orient` rejects reversed writes — this package
75
+ surfaces that error verbatim and never flips an edge for you.
76
+ 4. **A link is never a grant.** Access lives in `iam.permissions` /
77
+ `iam.memberships`. The ONLY sanctioned association→access bridge is DB-side
78
+ conveyance (`platform.association_types.conveys_max` → reachability). This
79
+ package makes **zero access decisions**.
80
+ 5. **A missing function screams.** A PostgREST PGRST202 on any demanded RPC maps
81
+ to the `demanded_schema_violation` result code and always reaches your
82
+ errorSink: a database without our schema produces one unambiguous scream, not
83
+ a mystery toast.
84
+
85
+ ## The token vocabulary — writes strict, reads tolerant
86
+
87
+ `EntityTypeToken` mirrors `platform.entity_types` 1:1 (651 active tokens),
88
+ regenerated by `pnpm gen:entity-types` inside this package. Skew policy:
89
+
90
+ - **Writes are STRICT** against the installed vocabulary: a token this version
91
+ doesn't know fails at the guard, in code, before Postgres — exactly like a
92
+ missing DB type. The fix is `pnpm up @ai-matrx/associations` (registry changes
93
+ ship as **patch releases**), never a raw-string widening.
94
+ - **Reads are TOLERANT**: an edge arriving with a token this version doesn't
95
+ know renders through the fallback (default icon, raw label, no door) instead
96
+ of crashing.
97
+
98
+ ## The seven ports
99
+
100
+ One `AssociationsConfig`, bound once per host. `dataSource` (a structural subset
101
+ of `SupabaseClient` answering the demanded RPCs), `identity`, and `errorSink`
102
+ are **required** — construction throws without them. `entityOverlay` (icons /
103
+ routes / candidate overrides per token), `notifier`, `windowShell`, `capture`,
104
+ `pickerOverrides`, and `entityDoors` are optional, each with a defined
105
+ degradation — never a dead button, never a silent failure.
106
+
107
+ ## Versioning
108
+
109
+ Consumed by Matrx repos as `latest` (`workspace:*` inside the aidream monorepo)
110
+ per THE LATEST LAW — never pinned. Registry token changes → regenerate → patch.
111
+ Contract changes → minor/major per semver.
112
+
113
+ ## License
114
+
115
+ MIT