Metadata-Version: 2.5
Name: matrx-records
Version: 0.1.2
Summary: The records server: the eight agent verbs over the custom record store's doors, the merge-field resolver, and the record workflow nodes
Project-URL: Homepage, https://github.com/AI-Matrix-Engine/aidream-current
Project-URL: Repository, https://github.com/AI-Matrix-Engine/aidream-current
Author-email: Matrx <admin@aimatrx.com>
Maintainer-email: Matrx <admin@aimatrx.com>
License: MIT
Keywords: agents,custom-data,matrx,merge-field,records
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.13
Requires-Python: >=3.13
Requires-Dist: matrx-files>=0.2.301
Requires-Dist: matrx-orm>=3.1.240
Requires-Dist: matrx-utils>=2.0.80
Requires-Dist: pydantic>=2.0.0
Provides-Extra: agent
Requires-Dist: matrx-ai>=0.5.619; extra == 'agent'
Requires-Dist: matrx-connect>=0.1.233; extra == 'agent'
Provides-Extra: graph
Requires-Dist: matrx-graph>=0.2.326; extra == 'graph'
Description-Content-Type: text/markdown

# matrx-records

The **records server**: the record read/write paths, the eight agent verbs and their
generated tool cache, the merge-field resolver and its provenance writer, and the record
workflow nodes.

It is imported by the app and never imports it. It talks to the database only through
`matrx-orm`, and only through the `custom.*` store's own doors, under the operating
person's own principal — there is no agent identity and there are no service roles.

```python
from matrx_records import Principal, RecordStore

store = RecordStore(Principal(user_id=uid, organization_id=org, claims=jwt_claims))
tables = await store.table_list()
```

Host wiring is one call at startup:

```python
from matrx_records import configure_records
configure_records(database="supabase_automation_matrix", session_factory=my_act_as_user)
```

## The scope system, read from the store (SCOPES-READS-SERVER)

Every server reader of scopes reads `matrx_records.store.scopes.ScopeReader` — never the old
`context.*` tables. A scope type is a Table the context system keeps, a scope a Record of it, a
context item a Field; the reader answers in the old column names:

```python
from matrx_records.store.scopes import ScopeReader

reader = ScopeReader.for_ambient(user_id=uid)          # the operating person, or the run's person
types = await reader.types(org_ids)                     # lists: the contract RPCs, one statement
scopes = await reader.scopes(org_ids, type_ids=[tid])
docs = await reader.scope_documents([org], type_ids=[tid])  # every Record WITH its values
one = await reader.scopes_by_id([sid])                  # by id, in whichever org it opens in
items = await reader.items(org, [tid])                  # Fields, with the old value_type word
```

Values kept as files are read whole with `ScopeReader.whole_value(scope, key)`.
The old scope tables are retired (2026-10-05); the store is the only writer, so there is nothing to compare against.

## The server half — routing an existing record path (CUT-N-13)

An `aidream` path that reads or writes records asks the gateway, and the gateway decides
**per caller**: the record store when `custom/system_enabled` resolves on for that
person's organization, the path's own existing body when it does not.

```python
from matrx_records.server import route_read

rows = await route_read(
    source="context.scopes",                       # the legacy relation
    match={"id": scope_id},
    legacy=lambda: Scopes.filter(id=scope_id).values("id", "name"),
)
```

`legacy` is the OFF arm, so today's behavior is unchanged while the knob is false and it
is impossible to route a path without naming what it used to do. **The ramp is per SOURCE:**
the switch says the organization keeps its data in the store, not that every relation has
moved, so a source with no Table of its own slug is *not adopted yet* and keeps reading and
writing today's tables — the `Route` says so, with the remedy. Only a source whose Table
exists resolves through the store (`RecordsGateway.route_for`; feedback f35b12ec). `attrs=True` when the
legacy arm hands back model instances; `RecordsGateway.read_joined(...)` for a path whose
legacy arm is a SQL `JOIN` across two record-bearing relations.

Which paths those are is a **query**, never a list:

```python
from matrx_records.server import the_enumeration

for path in await the_enumeration():
    print(path.relative_path, path.sources, path.routed)
```

It intersects matrx-orm's model registry with `platform.deprecated_relations` (the
platform's own supersession register) and then reads the repository's import graph.
`tests/test_server_paths_against_the_store.py` is parametrized over it, so a path nobody
repointed fails by name and a legacy relation declared tomorrow pulls its paths in with
no code change.

## The outside door's verbs (TABLE-API-1, 2026-09-29)

The table API and the AI Matrx MCP (aidream `services/table_api`) reach the store only through
these, as the person: `table_read` (a Table's words, Fields and choice options), `record_page`
(`custom.read_records_page`: filter, search, sort, page, total), `record_headers_of` (version and
timestamps), `table_of`, `records_holding` (ids per key value, one statement), `record_change_many`
and `record_upsert` (one transaction; advisory lock on the key; a person's verb, or an agent's
only with `change_approval` saying nobody is asked) and `record_archive` (a person's archive /
restore; an agent uses `record_delete`, which asks first).

