matrx-runtime 0.0.1__py3-none-any.whl
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.
- matrx_runtime/__init__.py +146 -0
- matrx_runtime/_config.py +52 -0
- matrx_runtime/db/__init__.py +217 -0
- matrx_runtime/db/bootstrap/000_substrate.sql +82 -0
- matrx_runtime/db/bootstrap/010_identity.sql +84 -0
- matrx_runtime/db/bootstrap/020_runtime.sql +206 -0
- matrx_runtime/db/models_runtime.py +143 -0
- matrx_runtime/engine.py +865 -0
- matrx_runtime/lifecycle.py +142 -0
- matrx_runtime/models.py +353 -0
- matrx_runtime/origins.py +79 -0
- matrx_runtime/orm_store.py +388 -0
- matrx_runtime/store.py +364 -0
- matrx_runtime-0.0.1.dist-info/METADATA +48 -0
- matrx_runtime-0.0.1.dist-info/RECORD +16 -0
- matrx_runtime-0.0.1.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""matrx-runtime — the Request Management Layer.
|
|
2
|
+
|
|
3
|
+
You hand us a request; we own its entire life and its whole nested tree —
|
|
4
|
+
identity, nesting, status, every failure and success, accumulated cost and any
|
|
5
|
+
metered limit, context propagation, cancellation, and resume — WITHOUT knowing or
|
|
6
|
+
caring what the work is. Payload-blind: never chat, agents, providers, tokens, or
|
|
7
|
+
messages. Feature data stays in the consumer's tables, reached via an opaque
|
|
8
|
+
`link_kind`/`link_id`.
|
|
9
|
+
|
|
10
|
+
Design: docs/runtime/REQUEST_MANAGEMENT_LAYER.md. Public API = exactly __all__.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from matrx_runtime.lifecycle import (
|
|
16
|
+
ALLOWED_TRANSITIONS,
|
|
17
|
+
InvalidExecutionTransition,
|
|
18
|
+
TransitionResult,
|
|
19
|
+
can_transition,
|
|
20
|
+
event_for_transition,
|
|
21
|
+
is_terminal,
|
|
22
|
+
transition,
|
|
23
|
+
)
|
|
24
|
+
from matrx_runtime.engine import (
|
|
25
|
+
DEFAULT_LEASE_SECONDS,
|
|
26
|
+
BudgetExceeded,
|
|
27
|
+
ConcurrentTransition,
|
|
28
|
+
DeadlineExceeded,
|
|
29
|
+
ExecutionCancelled,
|
|
30
|
+
ExecutionEngine,
|
|
31
|
+
ExecutionNotFound,
|
|
32
|
+
ExecutionScope,
|
|
33
|
+
InvalidLeaseState,
|
|
34
|
+
LimitExceeded,
|
|
35
|
+
)
|
|
36
|
+
from matrx_runtime.models import (
|
|
37
|
+
TERMINAL_STATUSES,
|
|
38
|
+
ContextMode,
|
|
39
|
+
ExecutionCheckpoint,
|
|
40
|
+
ExecutionContext,
|
|
41
|
+
ExecutionControl,
|
|
42
|
+
ExecutionError,
|
|
43
|
+
ExecutionEvent,
|
|
44
|
+
ExecutionEventKind,
|
|
45
|
+
ExecutionOutcome,
|
|
46
|
+
ExecutionStatus,
|
|
47
|
+
ExecutionTree,
|
|
48
|
+
ExecutionTreeNode,
|
|
49
|
+
GlobalExecution,
|
|
50
|
+
MeterEntry,
|
|
51
|
+
MeterEvent,
|
|
52
|
+
Origin,
|
|
53
|
+
Request,
|
|
54
|
+
)
|
|
55
|
+
from matrx_runtime.store import ExecutionStore, InMemoryExecutionStore
|
|
56
|
+
from matrx_runtime.orm_store import OrmExecutionStore
|
|
57
|
+
from matrx_runtime.origins import (
|
|
58
|
+
OriginSpec,
|
|
59
|
+
OriginSyncResult,
|
|
60
|
+
is_caller_allowed,
|
|
61
|
+
sync_origins,
|
|
62
|
+
)
|
|
63
|
+
from matrx_runtime.db import PACKAGE_DB_NAME, apply_schema, bind_to_host, bootstrap_db
|
|
64
|
+
|
|
65
|
+
__version__ = "0.0.1"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def configure(*, db_config_name: str | None = None) -> None:
|
|
69
|
+
"""Host-injection entry point (called once at startup; mirrors matrx-rag).
|
|
70
|
+
`db_config_name` is the matrx-orm pool the host already registered — we alias
|
|
71
|
+
the package's `"matrx_runtime"` name onto it and register the `runtime.*`
|
|
72
|
+
Models the `OrmExecutionStore` needs. Standalone installs call `bootstrap_db()`."""
|
|
73
|
+
from matrx_runtime import _config
|
|
74
|
+
|
|
75
|
+
if db_config_name is not None:
|
|
76
|
+
_config.set_db_config_name(db_config_name)
|
|
77
|
+
from matrx_orm import is_database_registered
|
|
78
|
+
|
|
79
|
+
from matrx_runtime.db import bind_to_host as _bind
|
|
80
|
+
|
|
81
|
+
if is_database_registered(db_config_name):
|
|
82
|
+
_bind(db_config_name)
|
|
83
|
+
_config.mark_configured()
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def is_configured() -> bool:
|
|
87
|
+
from matrx_runtime import _config
|
|
88
|
+
|
|
89
|
+
return _config.is_configured()
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
__all__ = [
|
|
93
|
+
# lifecycle
|
|
94
|
+
"transition",
|
|
95
|
+
"can_transition",
|
|
96
|
+
"is_terminal",
|
|
97
|
+
"event_for_transition",
|
|
98
|
+
"TransitionResult",
|
|
99
|
+
"InvalidExecutionTransition",
|
|
100
|
+
"ALLOWED_TRANSITIONS",
|
|
101
|
+
# engine
|
|
102
|
+
"ExecutionEngine",
|
|
103
|
+
"ExecutionScope",
|
|
104
|
+
"ExecutionNotFound",
|
|
105
|
+
"ConcurrentTransition",
|
|
106
|
+
"BudgetExceeded",
|
|
107
|
+
"LimitExceeded",
|
|
108
|
+
"DeadlineExceeded",
|
|
109
|
+
"ExecutionCancelled",
|
|
110
|
+
"InvalidLeaseState",
|
|
111
|
+
"DEFAULT_LEASE_SECONDS",
|
|
112
|
+
# store
|
|
113
|
+
"ExecutionStore",
|
|
114
|
+
"InMemoryExecutionStore",
|
|
115
|
+
"OrmExecutionStore",
|
|
116
|
+
# origin registry
|
|
117
|
+
"OriginSpec",
|
|
118
|
+
"OriginSyncResult",
|
|
119
|
+
"sync_origins",
|
|
120
|
+
"is_caller_allowed",
|
|
121
|
+
# host wiring / db binding
|
|
122
|
+
"configure",
|
|
123
|
+
"is_configured",
|
|
124
|
+
"bind_to_host",
|
|
125
|
+
"bootstrap_db",
|
|
126
|
+
"apply_schema",
|
|
127
|
+
"PACKAGE_DB_NAME",
|
|
128
|
+
# models / enums
|
|
129
|
+
"GlobalExecution",
|
|
130
|
+
"ExecutionStatus",
|
|
131
|
+
"ExecutionEventKind",
|
|
132
|
+
"ContextMode",
|
|
133
|
+
"ExecutionContext",
|
|
134
|
+
"ExecutionError",
|
|
135
|
+
"ExecutionOutcome",
|
|
136
|
+
"ExecutionControl",
|
|
137
|
+
"ExecutionEvent",
|
|
138
|
+
"ExecutionCheckpoint",
|
|
139
|
+
"ExecutionTree",
|
|
140
|
+
"ExecutionTreeNode",
|
|
141
|
+
"MeterEvent",
|
|
142
|
+
"MeterEntry",
|
|
143
|
+
"Origin",
|
|
144
|
+
"Request",
|
|
145
|
+
"TERMINAL_STATUSES",
|
|
146
|
+
]
|
matrx_runtime/_config.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Host-injection registry — the seam between matrx-runtime and its host.
|
|
2
|
+
|
|
3
|
+
The host calls :func:`matrx_runtime.configure` once at startup; the package reads
|
|
4
|
+
back what it needs here. Kept tiny and dependency-free (the boundary gate stays
|
|
5
|
+
green). Today the only seam is the matrx-orm pool name the spine persists to; the
|
|
6
|
+
Coordinator/context drops will add their seams here the same way.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
|
|
13
|
+
_registry: dict[str, object] = {
|
|
14
|
+
# matrx-orm pool identifier the host registered via
|
|
15
|
+
# register_database_from_env. matrx_runtime.configure(db_config_name=...)
|
|
16
|
+
# aliases PACKAGE_DB_NAME onto it.
|
|
17
|
+
"db_config_name": None,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
_configured = False
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def set_db_config_name(name: str) -> None:
|
|
24
|
+
_registry["db_config_name"] = name
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def is_configured() -> bool:
|
|
28
|
+
return _configured
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def mark_configured() -> None:
|
|
32
|
+
global _configured
|
|
33
|
+
_configured = True
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_db_config_name() -> str:
|
|
37
|
+
"""Resolve the host's matrx-orm pool name.
|
|
38
|
+
|
|
39
|
+
Order: the name injected via ``configure(db_config_name=...)`` → the
|
|
40
|
+
``MATRX_RUNTIME_DB_NAME`` env var (name of an already-registered config).
|
|
41
|
+
Raises if neither is set — running the ORM-bound store with no configured
|
|
42
|
+
pool is a wiring bug, surfaced loudly rather than silently no-op'd.
|
|
43
|
+
"""
|
|
44
|
+
name = _registry.get("db_config_name") or os.environ.get("MATRX_RUNTIME_DB_NAME")
|
|
45
|
+
if not name:
|
|
46
|
+
raise RuntimeError(
|
|
47
|
+
"matrx-runtime: no database configured. Call "
|
|
48
|
+
"matrx_runtime.configure(db_config_name=<registered matrx-orm pool>) "
|
|
49
|
+
"at host startup, set MATRX_RUNTIME_DB_NAME, or use "
|
|
50
|
+
"matrx_runtime.bootstrap_db() for a standalone pool."
|
|
51
|
+
)
|
|
52
|
+
return str(name)
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"""Database binding for the execution spine.
|
|
2
|
+
|
|
3
|
+
matrx-runtime ships ONE canonical copy of its `runtime.*` model classes
|
|
4
|
+
(``matrx_runtime.db.models_runtime``) — generated from the live database by the
|
|
5
|
+
host's ``db/generate.py`` run (the ``output:`` entry in aidream's
|
|
6
|
+
``db/matrx_orm.yaml``). Every model bakes ``_database = "matrx_runtime"``
|
|
7
|
+
(:data:`PACKAGE_DB_NAME`). What that name points at is decided exactly once:
|
|
8
|
+
|
|
9
|
+
* **Hosted** — the host registered its own pool and calls
|
|
10
|
+
``matrx_runtime.configure(db_config_name=...)``, which calls
|
|
11
|
+
:func:`bind_to_host`: a matrx-orm *name alias* maps ``"matrx_runtime"`` onto
|
|
12
|
+
the host's config. One registration, one physical pool — the package NEVER
|
|
13
|
+
opens a second connection.
|
|
14
|
+
* **Standalone** — :func:`bootstrap_db` registers the pool from
|
|
15
|
+
``MATRX_RUNTIME_POSTGRES_*`` env vars under :data:`PACKAGE_DB_NAME`.
|
|
16
|
+
|
|
17
|
+
This mirrors matrx-rag's binding exactly (the proven pattern). There is
|
|
18
|
+
deliberately NO ``SET search_path``: every model fully-qualifies its schema
|
|
19
|
+
(``runtime.global_execution``), so the transaction pooler resetting backend
|
|
20
|
+
state between transactions is a non-issue.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import os
|
|
26
|
+
from typing import TYPE_CHECKING, Any
|
|
27
|
+
from urllib.parse import quote
|
|
28
|
+
|
|
29
|
+
if TYPE_CHECKING:
|
|
30
|
+
from asyncpg import Pool
|
|
31
|
+
|
|
32
|
+
# The config name every matrx-runtime model class is bound to. Hosts alias it to
|
|
33
|
+
# their registered pool (bind_to_host); standalone registers it as a real config
|
|
34
|
+
# (bootstrap_db).
|
|
35
|
+
PACKAGE_DB_NAME = "matrx_runtime"
|
|
36
|
+
|
|
37
|
+
# Env prefix for TRUE standalone credentials. Deliberately NOT "MATRX_RUNTIME_DB":
|
|
38
|
+
# a MATRX_RUNTIME_DB_NAME env var would mean "name of an already-registered
|
|
39
|
+
# matrx-orm config", which register_database_from_env would misread as the
|
|
40
|
+
# database name (the same trap matrx-rag documents).
|
|
41
|
+
STANDALONE_ENV_PREFIX = "MATRX_RUNTIME_POSTGRES"
|
|
42
|
+
|
|
43
|
+
_models_registered = False
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def connection_url() -> str:
|
|
47
|
+
"""Resolve the postgres connection URL for ad-hoc / migration use.
|
|
48
|
+
|
|
49
|
+
Priority:
|
|
50
|
+
1. ``RUNTIME_DATABASE_URL`` — explicit override.
|
|
51
|
+
2. ``DATABASE_URL`` — generic shared URL.
|
|
52
|
+
3. ``MATRX_RUNTIME_POSTGRES_*`` — the package's standalone credential set.
|
|
53
|
+
4. ``SUPABASE_MATRIX_*`` — legacy aidream-flavored fields.
|
|
54
|
+
|
|
55
|
+
Raises ``RuntimeError`` if none resolve to a usable URL.
|
|
56
|
+
"""
|
|
57
|
+
explicit = os.environ.get("RUNTIME_DATABASE_URL") or os.environ.get("DATABASE_URL")
|
|
58
|
+
if explicit:
|
|
59
|
+
return explicit
|
|
60
|
+
|
|
61
|
+
for prefix in (STANDALONE_ENV_PREFIX, "SUPABASE_MATRIX"):
|
|
62
|
+
host = os.environ.get(f"{prefix}_HOST")
|
|
63
|
+
user = os.environ.get(f"{prefix}_USER")
|
|
64
|
+
password = os.environ.get(f"{prefix}_PASSWORD")
|
|
65
|
+
if prefix == "SUPABASE_MATRIX":
|
|
66
|
+
database = os.environ.get("SUPABASE_MATRIX_DATABASE_NAME") or "postgres"
|
|
67
|
+
else:
|
|
68
|
+
database = os.environ.get(f"{prefix}_NAME") or "postgres"
|
|
69
|
+
port = os.environ.get(f"{prefix}_PORT") or "5432"
|
|
70
|
+
if host and user and password:
|
|
71
|
+
return (
|
|
72
|
+
f"postgresql://{quote(user, safe='')}:{quote(password, safe='')}"
|
|
73
|
+
f"@{host}:{port}/{database}"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
raise RuntimeError(
|
|
77
|
+
"matrx-runtime: cannot build database URL — set RUNTIME_DATABASE_URL, "
|
|
78
|
+
f"DATABASE_URL, the {STANDALONE_ENV_PREFIX}_HOST/USER/PASSWORD trio, or "
|
|
79
|
+
"the SUPABASE_MATRIX_HOST/USER/PASSWORD trio."
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _register_models() -> None:
|
|
84
|
+
"""Import the generated model module so its classes register.
|
|
85
|
+
|
|
86
|
+
The module ends with ``model_registry.register_all(..., skip_existing=True)``
|
|
87
|
+
so a host's earlier same-name registration always wins.
|
|
88
|
+
"""
|
|
89
|
+
global _models_registered
|
|
90
|
+
if _models_registered:
|
|
91
|
+
return
|
|
92
|
+
from matrx_runtime.db import models_runtime # noqa: F401 (registers on import)
|
|
93
|
+
|
|
94
|
+
_models_registered = True
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def bind_to_host(db_config_name: str) -> None:
|
|
98
|
+
"""Hosted entry — point :data:`PACKAGE_DB_NAME` at the host's pool.
|
|
99
|
+
|
|
100
|
+
Registers a matrx-orm name alias (idempotent for the same target) and
|
|
101
|
+
registers the package's ``runtime.*`` model classes. Called automatically by
|
|
102
|
+
``matrx_runtime.configure(db_config_name=...)``.
|
|
103
|
+
"""
|
|
104
|
+
from matrx_orm import is_database_registered, register_database_alias
|
|
105
|
+
|
|
106
|
+
if not is_database_registered(PACKAGE_DB_NAME):
|
|
107
|
+
register_database_alias(PACKAGE_DB_NAME, db_config_name)
|
|
108
|
+
_register_models()
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def bootstrap_db(
|
|
112
|
+
*,
|
|
113
|
+
env_prefix: str = STANDALONE_ENV_PREFIX,
|
|
114
|
+
additional_schemas: tuple[str, ...] = ("runtime",),
|
|
115
|
+
env_var_overrides: dict[str, str] | None = None,
|
|
116
|
+
) -> str:
|
|
117
|
+
"""Standalone entry — register the package's own pool from env vars.
|
|
118
|
+
|
|
119
|
+
Reads ``{env_prefix}_HOST/_PORT/_NAME/_USER/_PASSWORD`` and registers the
|
|
120
|
+
database under :data:`PACKAGE_DB_NAME`, then registers the model set.
|
|
121
|
+
Idempotent: a host that already registered/aliased the name turns this into
|
|
122
|
+
a no-op model-registration call. Returns :data:`PACKAGE_DB_NAME`.
|
|
123
|
+
"""
|
|
124
|
+
from matrx_orm import is_database_registered, register_database_from_env
|
|
125
|
+
|
|
126
|
+
if not is_database_registered(PACKAGE_DB_NAME):
|
|
127
|
+
ok = register_database_from_env(
|
|
128
|
+
name=PACKAGE_DB_NAME,
|
|
129
|
+
env_prefix=env_prefix,
|
|
130
|
+
additional_schemas=list(additional_schemas),
|
|
131
|
+
env_var_overrides=env_var_overrides,
|
|
132
|
+
)
|
|
133
|
+
if not ok:
|
|
134
|
+
raise RuntimeError(
|
|
135
|
+
f"matrx-runtime standalone bootstrap failed: missing {env_prefix}_* "
|
|
136
|
+
"env vars. Either set them, or run hosted via "
|
|
137
|
+
"matrx_runtime.configure(db_config_name=<registered pool name>)."
|
|
138
|
+
)
|
|
139
|
+
_register_models()
|
|
140
|
+
return PACKAGE_DB_NAME
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def ensure_bound() -> str:
|
|
144
|
+
"""Resolve the pool name matrx-runtime should use, binding lazily if needed.
|
|
145
|
+
|
|
146
|
+
Order: an already-registered/aliased :data:`PACKAGE_DB_NAME` → the
|
|
147
|
+
host-injected ``db_config_name`` (bound on first use) → raise. The
|
|
148
|
+
ExecutionStore adapter calls this so the models resolve to a live pool even
|
|
149
|
+
if ``configure`` was passed a name but ``bind_to_host`` hasn't run yet.
|
|
150
|
+
"""
|
|
151
|
+
from matrx_orm import is_database_registered
|
|
152
|
+
|
|
153
|
+
if is_database_registered(PACKAGE_DB_NAME):
|
|
154
|
+
_register_models()
|
|
155
|
+
return PACKAGE_DB_NAME
|
|
156
|
+
|
|
157
|
+
from matrx_runtime._config import get_db_config_name
|
|
158
|
+
|
|
159
|
+
name = get_db_config_name() # raises if neither injected name nor env is set
|
|
160
|
+
if is_database_registered(name):
|
|
161
|
+
bind_to_host(name)
|
|
162
|
+
return PACKAGE_DB_NAME
|
|
163
|
+
# Unregistered name: hand it to matrx-orm unchanged so its own
|
|
164
|
+
# "Configuration not found" error surfaces.
|
|
165
|
+
return name
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def bootstrap_sql_files() -> list[Any]:
|
|
169
|
+
"""The bundled `bootstrap/*.sql` files in apply order (`NNN_*.sql`)."""
|
|
170
|
+
from pathlib import Path
|
|
171
|
+
|
|
172
|
+
return sorted((Path(__file__).parent / "bootstrap").glob("*.sql"))
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
async def apply_schema(*, pool: Pool | None = None) -> list[str]:
|
|
176
|
+
"""Create the standalone substrate on a fresh database — schemas, enums, the
|
|
177
|
+
actor/timestamp functions, the minimal identity shim, and the `runtime.*` tables +
|
|
178
|
+
indexes + triggers + RLS. Applies the bundled `bootstrap/*.sql` in order; every
|
|
179
|
+
statement is idempotent + NON-DESTRUCTIVE, so this is a pure no-op against a host
|
|
180
|
+
(aidream) that already owns the schema. Standalone flow: `bootstrap_db()` then
|
|
181
|
+
`await apply_schema()`. Returns the file names applied.
|
|
182
|
+
|
|
183
|
+
Design + the non-destructive invariant: docs/runtime/STANDALONE_SUBSTRATE.md.
|
|
184
|
+
"""
|
|
185
|
+
from matrx_orm.operations.dynamic_admin import execute_sql_script
|
|
186
|
+
|
|
187
|
+
applied: list[str] = []
|
|
188
|
+
for path in bootstrap_sql_files():
|
|
189
|
+
# execute_sql_script rides asyncpg's simple-query protocol (no bound
|
|
190
|
+
# params), which is what makes a multi-statement idempotent DDL
|
|
191
|
+
# script runnable in one call — matrx_orm.operations.dynamic_admin
|
|
192
|
+
# is the ORM's sanctioned surface for this shape.
|
|
193
|
+
if pool is not None:
|
|
194
|
+
await execute_sql_script(path.read_text(), pool=pool)
|
|
195
|
+
else:
|
|
196
|
+
await execute_sql_script(path.read_text(), config_name=ensure_bound())
|
|
197
|
+
applied.append(path.name)
|
|
198
|
+
return applied
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def get_models() -> dict[str, Any]:
|
|
202
|
+
"""Return name → Model class for everything the package ships."""
|
|
203
|
+
_register_models()
|
|
204
|
+
from matrx_runtime.db import models_runtime
|
|
205
|
+
|
|
206
|
+
out: dict[str, Any] = {}
|
|
207
|
+
for attr in vars(models_runtime).values():
|
|
208
|
+
if isinstance(attr, type) and getattr(attr, "_table_name", None):
|
|
209
|
+
out[attr.__name__] = attr
|
|
210
|
+
return out
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
async def get_pool() -> Pool:
|
|
214
|
+
"""Return the shared matrx-orm asyncpg pool (the host's pool when hosted)."""
|
|
215
|
+
from matrx_orm.core.async_db_manager import AsyncDatabaseManager
|
|
216
|
+
|
|
217
|
+
return await AsyncDatabaseManager.get_pool(ensure_bound())
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
-- matrx-runtime standalone substrate — 000: schemas + enums + actor/timestamp triggers.
|
|
2
|
+
-- Idempotent + NON-DESTRUCTIVE: every statement is a no-op where the object already
|
|
3
|
+
-- exists (e.g. inside aidream). Portable: no auth.uid(), no Supabase-only objects.
|
|
4
|
+
-- See docs/runtime/STANDALONE_SUBSTRATE.md.
|
|
5
|
+
|
|
6
|
+
create schema if not exists platform;
|
|
7
|
+
create schema if not exists iam;
|
|
8
|
+
create schema if not exists runtime;
|
|
9
|
+
|
|
10
|
+
-- Visibility tiers (platform.visibility). Create only if absent — never alter a host's.
|
|
11
|
+
do $do$
|
|
12
|
+
begin
|
|
13
|
+
if not exists (
|
|
14
|
+
select 1 from pg_type t join pg_namespace n on n.oid = t.typnamespace
|
|
15
|
+
where t.typname = 'visibility' and n.nspname = 'platform'
|
|
16
|
+
) then
|
|
17
|
+
create type platform.visibility as enum ('private', 'internal', 'link', 'public');
|
|
18
|
+
end if;
|
|
19
|
+
end
|
|
20
|
+
$do$;
|
|
21
|
+
|
|
22
|
+
-- Permission levels (public.permission_level). Create only if absent.
|
|
23
|
+
do $do$
|
|
24
|
+
begin
|
|
25
|
+
if not exists (
|
|
26
|
+
select 1 from pg_type t join pg_namespace n on n.oid = t.typnamespace
|
|
27
|
+
where t.typname = 'permission_level' and n.nspname = 'public'
|
|
28
|
+
) then
|
|
29
|
+
create type public.permission_level as enum ('viewer', 'editor', 'admin');
|
|
30
|
+
end if;
|
|
31
|
+
end
|
|
32
|
+
$do$;
|
|
33
|
+
|
|
34
|
+
-- updated_at / version stamping. CREATE-IF-ABSENT (never CREATE OR REPLACE): a host's
|
|
35
|
+
-- own version is NEVER overwritten — the only truly non-destructive rule, now that the
|
|
36
|
+
-- host's functions evolve independently (e.g. aidream's _stamp_actor gained an auth.uid
|
|
37
|
+
-- fallback). The standalone body below is the portable baseline.
|
|
38
|
+
do $do$
|
|
39
|
+
begin
|
|
40
|
+
if not exists (
|
|
41
|
+
select 1 from pg_proc p join pg_namespace n on n.oid = p.pronamespace
|
|
42
|
+
where p.proname = '_touch_row' and n.nspname = 'platform'
|
|
43
|
+
) then
|
|
44
|
+
execute $exec$
|
|
45
|
+
create function platform._touch_row()
|
|
46
|
+
returns trigger language plpgsql as $fn$
|
|
47
|
+
begin
|
|
48
|
+
new.updated_at := now();
|
|
49
|
+
if tg_op = 'UPDATE' then new.version := old.version + 1; end if;
|
|
50
|
+
return new;
|
|
51
|
+
end
|
|
52
|
+
$fn$;
|
|
53
|
+
$exec$;
|
|
54
|
+
end if;
|
|
55
|
+
end
|
|
56
|
+
$do$;
|
|
57
|
+
|
|
58
|
+
-- created_by / updated_by stamping. PORTABLE baseline: resolves the actor from the
|
|
59
|
+
-- app.user_id GUC ONLY (no auth.uid() — so it CREATEs on vanilla Postgres). A host on
|
|
60
|
+
-- Supabase keeps its own richer version (e.g. COALESCE(app.user_id, auth.uid())) — this
|
|
61
|
+
-- is create-if-absent, so that version is never touched. The runtime engine sets
|
|
62
|
+
-- created_by explicitly regardless, so attribution is correct on either.
|
|
63
|
+
do $do$
|
|
64
|
+
begin
|
|
65
|
+
if not exists (
|
|
66
|
+
select 1 from pg_proc p join pg_namespace n on n.oid = p.pronamespace
|
|
67
|
+
where p.proname = '_stamp_actor' and n.nspname = 'platform'
|
|
68
|
+
) then
|
|
69
|
+
execute $exec$
|
|
70
|
+
create function platform._stamp_actor()
|
|
71
|
+
returns trigger language plpgsql as $fn$
|
|
72
|
+
declare uid uuid := nullif(current_setting('app.user_id', true), '')::uuid;
|
|
73
|
+
begin
|
|
74
|
+
if tg_op = 'INSERT' then new.created_by := coalesce(new.created_by, uid); end if;
|
|
75
|
+
new.updated_by := coalesce(uid, new.updated_by);
|
|
76
|
+
return new;
|
|
77
|
+
end
|
|
78
|
+
$fn$;
|
|
79
|
+
$exec$;
|
|
80
|
+
end if;
|
|
81
|
+
end
|
|
82
|
+
$do$;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
-- matrx-runtime standalone substrate — 010: MINIMAL portable identity.
|
|
2
|
+
-- The smallest thing that satisfies runtime's organization FK + registry-driven RLS.
|
|
3
|
+
-- Every object is create-if-absent: in aidream the real (richer) organizations +
|
|
4
|
+
-- iam.has_access + registries already exist and are kept UNTOUCHED. This file only
|
|
5
|
+
-- materializes a minimal shim on a bare DB. See docs/runtime/STANDALONE_SUBSTRATE.md.
|
|
6
|
+
|
|
7
|
+
-- The organization the governed private root is scoped to (global_request.organization_id
|
|
8
|
+
-- FK target). aidream's is 13 cols → auth.users; standalone gets an id-only shim.
|
|
9
|
+
create table if not exists public.organizations (
|
|
10
|
+
id uuid primary key default gen_random_uuid(),
|
|
11
|
+
name text,
|
|
12
|
+
created_at timestamptz not null default now()
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
-- The registries has_access reads (token → table, and the composition/containment edges).
|
|
16
|
+
create table if not exists platform.entity_types (
|
|
17
|
+
token text primary key,
|
|
18
|
+
schema_name text not null,
|
|
19
|
+
table_name text not null,
|
|
20
|
+
is_component boolean not null default false
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
create table if not exists platform.entity_relationships (
|
|
24
|
+
child_type text not null,
|
|
25
|
+
parent_type text not null,
|
|
26
|
+
fk_column text not null,
|
|
27
|
+
kind text not null,
|
|
28
|
+
primary key (child_type, parent_type, kind)
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
-- Portable, fail-closed access resolver. Mirrors aidream's iam.has_access but uses the
|
|
32
|
+
-- app.user_id GUC (not auth.uid()) and resolves only owner + composition + public — the
|
|
33
|
+
-- minimal safe set (grants / org-admin / containment are the host's richer layer). Create
|
|
34
|
+
-- only if absent: aidream's full version is NEVER overwritten.
|
|
35
|
+
do $do$
|
|
36
|
+
begin
|
|
37
|
+
if not exists (
|
|
38
|
+
select 1 from pg_proc p join pg_namespace n on n.oid = p.pronamespace
|
|
39
|
+
where p.proname = 'has_access' and n.nspname = 'iam'
|
|
40
|
+
) then
|
|
41
|
+
execute $exec$
|
|
42
|
+
create function iam.has_access(p_type text, p_id uuid, p_required permission_level default 'viewer')
|
|
43
|
+
returns boolean language plpgsql stable security definer
|
|
44
|
+
set search_path to 'public', 'platform', 'iam'
|
|
45
|
+
as $fn$
|
|
46
|
+
declare
|
|
47
|
+
v_schema text; v_table text; v_is_component boolean;
|
|
48
|
+
v_uid uuid := nullif(current_setting('app.user_id', true), '')::uuid;
|
|
49
|
+
v_vis platform.visibility; v_owner uuid; v_org uuid;
|
|
50
|
+
v_parent_type text; v_parent_col text; v_parent_id uuid;
|
|
51
|
+
begin
|
|
52
|
+
if v_uid is null then return false; end if;
|
|
53
|
+
|
|
54
|
+
select schema_name, table_name, coalesce(is_component, false)
|
|
55
|
+
into v_schema, v_table, v_is_component
|
|
56
|
+
from platform.entity_types where token = p_type;
|
|
57
|
+
if v_schema is null then return false; end if;
|
|
58
|
+
|
|
59
|
+
-- component: access is exactly the parent's, full depth
|
|
60
|
+
if v_is_component then
|
|
61
|
+
select parent_type, fk_column into v_parent_type, v_parent_col
|
|
62
|
+
from platform.entity_relationships where child_type = p_type and kind = 'composition' limit 1;
|
|
63
|
+
if v_parent_type is null then return false; end if;
|
|
64
|
+
execute format('select %I from %I.%I where id=$1', v_parent_col, v_schema, v_table)
|
|
65
|
+
into v_parent_id using p_id;
|
|
66
|
+
if v_parent_id is null then return false; end if;
|
|
67
|
+
return iam.has_access(v_parent_type, v_parent_id, p_required);
|
|
68
|
+
end if;
|
|
69
|
+
|
|
70
|
+
-- standard entity: owner or public-read
|
|
71
|
+
begin
|
|
72
|
+
execute format('select visibility, created_by, organization_id from %I.%I where id=$1', v_schema, v_table)
|
|
73
|
+
into v_vis, v_owner, v_org using p_id;
|
|
74
|
+
exception when others then return false; end;
|
|
75
|
+
if v_owner is null and v_vis is null then return false; end if;
|
|
76
|
+
if v_owner = v_uid then return true; end if;
|
|
77
|
+
if v_vis = 'public' and p_required = 'viewer' then return true; end if;
|
|
78
|
+
return false;
|
|
79
|
+
end
|
|
80
|
+
$fn$;
|
|
81
|
+
$exec$;
|
|
82
|
+
end if;
|
|
83
|
+
end
|
|
84
|
+
$do$;
|