Skip to content

Commit 1e565b5

Browse files
committed
docs(agents): preserve admitted relation oid through lifecycle I/O
1 parent 0803b4d commit 1e565b5

1 file changed

Lines changed: 30 additions & 23 deletions

File tree

AGENTS.md

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -192,31 +192,38 @@ add CODEOWNERS-based merge gates until multiple independent maintainers exist.
192192
administrative, replication, maintenance, grant-capable, membership-delegating,
193193
executable-privileged, view-mediated-RLS-bypass, materialized-copy, foreign-data,
194194
and owner-capable login sessions are outside the application isolation guarantee.
195-
- The write path must close the admission-to-write DDL race rather than treating live
196-
catalog admission as transferable to a later statement. `enqueue_in_transaction()`
197-
must acquire `LOCK TABLE ONLY public.llm_context_lifecycle_outbox IN ROW EXCLUSIVE
198-
MODE` before live authority admission and retain it through the caller transaction,
199-
including the durable `INSERT`. This deliberately pulls forward the table lock that
200-
PostgreSQL ordinary DML already uses: concurrent `CREATE TRIGGER` and other
201-
conflicting schema DDL must wait instead of changing executable row authority after
202-
admission. Do not substitute a package advisory lock, a second post-hoc catalog
203-
check, or `ACCESS EXCLUSIVE`; the former does not participate in PostgreSQL table-DDL
204-
locking and the latter needlessly serializes compatible application DML. The normal
205-
application role still needs only the existing non-grantable `SELECT` and `INSERT`
206-
privileges, which are sufficient for `ROW EXCLUSIVE`. Lock acquisition and
207-
contention are part of the complete buyer-path latency measurement, not removable
208-
security overhead.
209-
- The read path must close the admission-to-read relation-identity race rather than
210-
relying on a later `SELECT` to acquire relation authority after admission.
211-
`load_in_transaction()` must acquire `LOCK TABLE ONLY
195+
- The write path must close both the admission-to-write DDL race and qualified-name
196+
rebinding. `enqueue_in_transaction()` must acquire `LOCK TABLE ONLY
197+
public.llm_context_lifecycle_outbox IN ROW EXCLUSIVE MODE` before live authority
198+
admission and retain it through the caller transaction, including the durable
199+
`INSERT`. The lock protects the admitted relation object from conflicting table-
200+
program/relation DDL, but it does not by itself authenticate an independently
201+
mutable schema/name binding. `_require_rls_application_role()` must return the exact
202+
validated lifecycle-outbox `pg_class.oid`; the write must carry that admitted OID
203+
into the same data-modifying CTE that can perform the `INSERT`, resolve the live
204+
qualified name with `pg_catalog.to_regclass(...)`, and execute the write only when
205+
the live OID equals the admitted OID. A standalone identity recheck followed by a
206+
separate `INSERT` is another TOCTOU interval. Do not substitute a package advisory
207+
lock, caller `search_path`, schema qualification alone, or `ACCESS EXCLUSIVE`.
208+
The normal application role still needs only the existing non-grantable `SELECT` and
209+
`INSERT` privileges. Lock acquisition/wait and the live OID proof are part of the
210+
complete buyer-path latency measurement, not removable security overhead.
211+
- The read path must close both admission-to-read relation-object and namespace-name
212+
races. `load_in_transaction()` must acquire `LOCK TABLE ONLY
212213
public.llm_context_lifecycle_outbox IN ACCESS SHARE MODE` before live authority
213214
admission and retain it through tenant binding, optional tenant/event advisory
214-
serialization, and the consuming `SELECT`. Concurrent `ACCESS EXCLUSIVE` DDL must
215-
wait rather than rename, replace, or drop the admitted relation between the catalog
216-
proof and data access. Do not widen ordinary reads to `ROW EXCLUSIVE` or
217-
`ACCESS EXCLUSIVE`; `ACCESS SHARE` is the minimal fence and remains compatible with
218-
ordinary reads and writes. Treat its acquisition and wait as part of complete
219-
buyer-path latency evidence, not removable security overhead.
215+
serialization, and the consuming `SELECT`. `ACCESS SHARE` protects the admitted
216+
relation object against conflicting relation DDL, but it does not authenticate the
217+
independently mutable `public` schema/name binding. `_require_rls_application_role()`
218+
must return the exact validated lifecycle-outbox `pg_class.oid`, and the consuming
219+
read itself must resolve the live qualified name with `pg_catalog.to_regclass(...)`,
220+
require that live OID to equal the admitted OID, and accept a durable row only when
221+
its `tableoid` is the admitted OID. Keep the identity comparison in the statement
222+
that can return evidence; a standalone pre-read recheck only creates another TOCTOU
223+
interval. Do not widen ordinary reads to `ROW EXCLUSIVE` or `ACCESS EXCLUSIVE`, and
224+
do not treat schema qualification, caller `search_path`, or the package advisory
225+
lock as object identity. Treat lock acquisition/wait and the live OID proof as part
226+
of complete buyer-path latency evidence.
220227
- Migrations must restore forced RLS within the same atomic SQL statement that
221228
relaxes owner enforcement, preserve legacy rows under `standalone`, remain
222229
idempotent, and keep the packaged and Docker initialization schemas

0 commit comments

Comments
 (0)