@ai-matrx/records 0.3.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,95 @@
1
+ # Changelog — @ai-matrx/records
2
+
3
+ ## 0.3.0
4
+
5
+ **The anonymous lane, wired to the doors that actually exist.** This package
6
+ named a door `custom.anon_record_write`, which the store never had — so
7
+ `FormRunner` told every person that a public form's link was "pending" for a
8
+ lane that had already shipped. W4-ANON landed as SEVEN doors, and they are all
9
+ here now: `anonTokenIssue` / `anonTokenVerify` / `anonTokenRevoke` (an embed
10
+ token carries ONE decision, read or write, and an exact origin list), `anonWrite`
11
+ (the stranger's write — the TOKEN supplies the organization, and what it makes is
12
+ a quarantined submission, never a record), `anonCapture` (the SIGNED-IN offline
13
+ path, deduplicated on a key the client mints) and `anonPublish`. A screen calling
14
+ a live feature pending is the same silent failure as one calling a missing
15
+ feature live.
16
+
17
+ **Work.** `workStates`, `workTemplateRefusal` / `workTemplateDeclare` /
18
+ `workTemplateInstantiate` / `workTemplateShape` / `workInstantiationShape`,
19
+ `workWhoseTurn`, `workHasAssignment`, `workAssignmentFields`,
20
+ `workTakeAssignment`, `workTransitionRefusal`, and the slots:
21
+ `workSlotsDeclare`, `workSlotHold`, `workSlotRelease`, `workSlotHolds`,
22
+ `workSlotExpire`. One hold per slot is a UNIQUE INDEX in the database, so a
23
+ second hold comes back as `already_exists` carrying the index that refused it.
24
+
25
+ **The trust doors, and they are `iam`'s, not the store's.** `isExternalPrincipal`,
26
+ `externalPrincipalCard`, `externalPrincipalReach`, `publishBindingCreate`,
27
+ `publishBindingRevoke`, `resolvePublishBinding`, `worldPublishGapNotice`,
28
+ `publishToWorld`. They are named by hand in `core/doors.ts` rather than
29
+ generated, because `pnpm records:generate` reads `custom` and only `custom` — a
30
+ census of it says nothing true about `iam`. `RecordsDataSource.rpc` gained an
31
+ optional third argument, `{ schema }`, which a supabase-js host routes to
32
+ `client.schema(schema).rpc(...)`; every existing call is unchanged.
33
+
34
+ New types: `WorkState`, `WorkGraph`, `WorkInstantiation`, `WorkTurn`,
35
+ `WorkSlotHold`, `WorkAssignmentField`, `AnonTokenBinding`,
36
+ `ExternalPrincipalCard`, `PublishBinding`, `ResolvedPublishBinding`.
37
+
38
+ **Two vocabularies this package had wrong, found by writing a Field the store
39
+ refused.** `FIELD_SENSITIVITIES` read `standard · sensitive · secret`; the store
40
+ has always wanted `public · internal · confidential · restricted`, which is also
41
+ what the knob `custom/field_sensitivity_levels` keys off — so the two could
42
+ never have agreed. And `Field.context_policy` was typed as `Delivery`
43
+ (`inline · on_demand · auto`), which is a different question: delivery is how
44
+ much of a merge field's value rides into a turn, while FLD-12's context policy
45
+ is whether an agent may see a Field's values at all, in the store's own words
46
+ `include · summarize · exclude · on_request`. Both are corrected, and
47
+ `ContextPolicy` is now its own exported type. Neither was generated, which is
48
+ why neither was caught by `pnpm check:store-registry`.
49
+
50
+ `src/store.generated.ts` regenerated from the live store.
51
+
52
+ ## 0.2.0
53
+
54
+ **Reads go through the read door.** `recordRead` calls `custom.read_record` and
55
+ `list` calls `custom.read_records` instead of selecting `custom.record`, so the
56
+ store decides which rows a reader may see and which fields of them: a masked
57
+ field comes back present-and-null with the store's own reason in `hidden`
58
+ (`_hidden`, unfolded for you). `query` materialises through the same doors, and
59
+ its as-of reads now call `custom.query_record_as_of` / `custom.query_table_as_of`
60
+ — both clocks — instead of filtering `updated_at` itself and refusing the world
61
+ clock by name.
62
+
63
+ Shapes that changed with it: `list` and `query` answer `ReadRow[]`
64
+ (`{ id, document, level, hidden }`) rather than raw store rows, `RecordRead` is
65
+ `{ record_id, document, hidden, computed }`, and `useRecords` lost
66
+ `includeDeleted` — the read door reads live records only, and there is no door
67
+ that answers otherwise.
68
+
69
+ New refusal class: `stale_read` (`40001`), which is `custom.has_visibility_at`
70
+ refusing to answer from a snapshot older than a write the reader has already
71
+ observed. It is the one refusal worth retrying.
72
+
73
+ **A store defect this found and fixed** (`db/migrations/campaign/vis25_the_owner_arm_on_the_read_door.sql`,
74
+ applied to the main database): `custom.has_visibility` had no VIS-25 owner arm,
75
+ so the record you had just written was yours to `custom.read_records` and not
76
+ yours to `custom.read_record` — the list offered the row and the door slammed.
77
+
78
+ ## 0.1.1
79
+
80
+ First working release: the types root, the headless `/core` client over the
81
+ store's doors, the `/react` binding, the generated store registry
82
+ (`pnpm records:generate` / `check:store-registry`) and the real-door suite that
83
+ runs against the live rehearsal branch inside an always-rolled-back transaction.
84
+
85
+ Two defects the suite found in the mirror and fixed:
86
+
87
+ - The document-size prediction measured `JSON.stringify` bytes; the store
88
+ measures the JSONB text, which puts a space after every `:` and every `,`. On
89
+ an 18-key document the two sentences disagreed by 35 bytes. The mirror now
90
+ renders the way Postgres does.
91
+ - The mirror predicted refusals for `ver`, `actor`, `on_behalf_of` and `at`.
92
+ The store never raises them: `custom.stamp_value_envelopes` and
93
+ `custom.value_versions` overwrite all four with the truth BEFORE
94
+ `custom.value_envelope_refusal` looks. Those four predictions are gone —
95
+ a sentence the store never says is a defect in the mirror.
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,118 @@
1
+ # @ai-matrx/records
2
+
3
+ THE headless client of the AI Matrx record store: the Table / Field / Rule /
4
+ Record / Relation / Home types, the closed vocabularies and refusal codes read
5
+ from the store ITSELF, the typed calls over the store's doors and nothing else,
6
+ the merge-field declaration, a validation mirror that predicts only what the
7
+ store's own triggers refuse, and a result envelope that never throws.
8
+
9
+ The package ships the types root, the headless `/core` and the React binding
10
+ `/react`. `@ai-matrx/records-ui` renders it; nothing else talks to the store.
11
+
12
+ **The one rule this package lives by:** the client mirrors the store's contract
13
+ and never re-implements a rule the store enforces. Where the store refuses, this
14
+ package surfaces the store's own sentence — never a translation of it.
15
+
16
+ ```ts
17
+ import {
18
+ type Table, type Field, type StoredRecord, // the shapes
19
+ type ValueEnvelope, // ver · src · actor · on_behalf_of · at · absent · alternates · dated
20
+ PARITY_FIELD_TYPES, // the 13 field types, read from custom.parity_field_types()
21
+ ACTOR_VOCABULARY, // exactly: user, agent, system
22
+ ABSENCE_REASONS, // exactly: never asked, none, refused, conflicting
23
+ STORE_DOORS, STORE_REFUSAL_CODES, // GENERATED from pg_proc on the live store
24
+ } from "@ai-matrx/records";
25
+ ```
26
+
27
+ ## /core — the typed client over the doors
28
+
29
+ ```ts
30
+ import { createRecordsClient } from "@ai-matrx/records/core";
31
+
32
+ const records = createRecordsClient({
33
+ dataSource: supabase, // structural subset of SupabaseClient
34
+ organizationId: orgId, // the store is keyed (organization_id, id)
35
+ onError: (e) => report(e), // OPTIONAL — the default screams to console
36
+ });
37
+
38
+ const written = await records.recordWrite({ table_id, data: { title: "Acme", owner: personId } });
39
+ const rows = await records.list({ table_id, limit: 50 });
40
+ const one = await records.recordRead({ record_id, withComputed: true });
41
+ const moved = await records.recordUpdate({ record_id, patch: { title: "Acme Inc" }, expectedVersion: 3 });
42
+
43
+ if (!moved.ok && moved.error.code === "stale_write") {
44
+ // The store's own words, plus the conflict field by field.
45
+ moved.error.message; // "Someone else changed this record while you were working on it. …"
46
+ staleWriteDetail(moved.error); // { expected_version, current_version, contested_fields }
47
+ }
48
+ ```
49
+
50
+ Nothing here throws, and nothing here invents an answer. A door the store does
51
+ not have yet is answered `door_absent`, naming the door AND the lane that owes
52
+ it — `metadataSearch`, `fieldPropose`, `tablePropose` and `mergeFieldResolve`
53
+ refuse today rather than returning an empty list that would read like "there is
54
+ nothing".
55
+
56
+ ## The mirror — what the store is about to say, said half a second earlier
57
+
58
+ ```ts
59
+ import { predictWriteRefusals } from "@ai-matrx/records/core";
60
+
61
+ const problems = predictWriteRefusals({ fields, document, recordType });
62
+ // [{ field_key: "amount", sqlstate: "23514", predicted_by: "custom.validate_values",
63
+ // message: "Amount has to be at least 1" }]
64
+ ```
65
+
66
+ This is NOT validation — the store validates, inside the write door, and cannot
67
+ be bypassed. The mirror only says what the store is about to say, in the store's
68
+ exact sentence, and it says NOTHING where it cannot see (option membership,
69
+ relation targets, anything needing a query). Silence means "ask the store".
70
+
71
+ ## /react — bind once, then hooks
72
+
73
+ ```tsx
74
+ import {
75
+ RecordsProvider, useTables, useFields, useRecords, useRecord, useRecordMutation,
76
+ } from "@ai-matrx/records/react";
77
+
78
+ <RecordsProvider client={records}>{children}</RecordsProvider>;
79
+
80
+ const { tables } = useTables();
81
+ const { fields } = useFields(table_id);
82
+ const { rows, total, live, liveOffReason } = useRecords({ table_id, pageSize: 50 });
83
+ const { write, update, remove, conflict } = useRecordMutation();
84
+ ```
85
+
86
+ `live` is never quietly false: when realtime is not bound, `liveOffReason` says
87
+ why in a sentence a screen can show.
88
+
89
+ ## The generated half
90
+
91
+ `src/store.generated.ts` is written by `pnpm records:generate` from the live
92
+ store's own catalogue — the vocabularies from the store's vocabulary functions,
93
+ the field types from `custom.parity_field_types()`, the doors from `pg_proc`,
94
+ the kernel ids from the kernel rows, the tokens from `platform.entity_types`.
95
+ `pnpm check:store-registry` exits non-zero the moment this package and the store
96
+ disagree, which is how a door another lane added shows up as a red check rather
97
+ than as a runtime surprise.
98
+
99
+ ## The suite
100
+
101
+ `pnpm test` runs against the live rehearsal branch. Every case is one
102
+ transaction that always ends in `rollback`: inside it the suite opens the
103
+ product switch, grants only what its own half needs, and sets
104
+ `request.jwt.claims` to the campaign's test identity (`test@test.com`) — so every
105
+ call goes through the real doors, the real triggers and the real guards, as a
106
+ real identity, and the branch is byte-identical afterwards (the last test
107
+ re-reads it from a fresh connection and proves it). The red half is the same
108
+ calls without that standing, asserted by the store's message text.
109
+
110
+ ## Commands
111
+
112
+ | Command | What it does |
113
+ |---|---|
114
+ | `pnpm records:generate` | Rewrite `src/store.generated.ts` from the live store |
115
+ | `pnpm check:store-registry` | Fail when this package and the store disagree |
116
+ | `pnpm test` | The real-door suite |
117
+ | `pnpm build` | tsup → `dist` (esm + cjs + d.ts), then the client boundary stamp |
118
+ | `pnpm check:package` | build · exports agreement · publint · tarball canary |