Repository navigation
Quotas on readers' vectors (M10 step 7) - #59
Merged
Merged
Conversation
graphs.reader_vector_quota, warren.vector_quotas (dumped with the extension, revoked from PUBLIC) and an index on vectors (graph_id, created_by); warren.set_vector_quota and warren.vector_usage; info reports reader_vector_quota. Every persisted write (Vectors::save) is admitted by vector::quota::admit: members of the owner role are exempt; where a quota applies, it takes a transaction-scoped advisory-type lock on (graph, role) with pg_warren's own objsubid, sums the role's usage under the latest snapshot and refuses with 53400 only a write that takes usage past the quota and above what it was (DESIGN.md §5.7, §14 items 46 and 47). The upgrade script from 0.5.0 adds the schema and functions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012Rxfe2L3iVjSwSBPdUu1Ke
clean_up_after (drop_owned and the DROP OWNED trigger) deletes the role's rows of warren.vector_quotas, orphans() lists rows naming dropped roles as kind 'quota', and drop_orphans removes them in a fourth locked pass, at the seams step 6 left (DESIGN.md §5.7). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012Rxfe2L3iVjSwSBPdUu1Ke
vector_quota_race: a second session of one role waits on the (graph, role) lock and is refused, or admitted when both fit; a REPEATABLE READ writer counts a write committed after its snapshot. dump.py dumps and restores a default and a per-role quota; upgrade.py checks the update leaves no quota and that one set afterwards refuses a reader's write. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012Rxfe2L3iVjSwSBPdUu1Ke
DESIGN.md §5.7 records what step 7 built and the decisions it made (removing a role's quota, the order of a counted write, the lock, vector_usage's rows), the catalog in §5.1, and the function reference and parallel labels in §9.3; docs/semantics.md, CHANGELOG.md and README.md describe the new functions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012Rxfe2L3iVjSwSBPdUu1Ke
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012Rxfe2L3iVjSwSBPdUu1Ke
PR #59 security review: - reassign_owned counted no vectors it moved, so a reader could reset its usage by moving its vectors to a group it is a member of. The move now takes the (graph, new role) lock and is refused (53400) past the new role's quota, unless it or the caller is exempt. - drop_owned and the DROP OWNED trigger deleted all of a role's quota rows, so a role could shed its own quota. They now remove them only on graphs whose owner the caller is a member of (every one for a caller that may drop orphans); orphans() lists the rest once the role is gone. - Every non-exempt persisted write takes the (graph, role) lock before it reads the quota, so a quota set while a write under none is uncommitted counts that write. - set_vector_quota refuses '-' (22023) and a role oid that names no role (42704) instead of an internal error. - The ledger (lock, quota, usage, decision) moves to catalog::quota, which vector::quota and reassign_owned share. - DESIGN.md §5.7 and §14 items 44, 46, 47, docs/semantics.md and the CHANGELOG: the move counted, drop_owned's quotas, the 53400 a racing replacement may get, and the quota's scope (not a bound on total storage; a quotient inherits no quota). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012Rxfe2L3iVjSwSBPdUu1Ke
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012Rxfe2L3iVjSwSBPdUu1Ke
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012Rxfe2L3iVjSwSBPdUu1Ke
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
M10 step 7, implementing DESIGN.md §5.7 "Quotas on readers' vectors" (§14 items 46, 47).
Schema
graphs.reader_vector_quota, with aCHECK (>= 0)constraint.warren.vector_quotas:pg_extension_config_dump;warren.vectors (graph_id, created_by).Functions
warren.set_vector_quota(name, bytes, role DEFAULT NULL)'-'is refused with 22023. Setting a quota for an oid that names no role is refused with 42704; removing a dropped role's quota is allowed.warren.vector_usage(name): the owner sees every role's usage; a reader sees only its own row.infonow includesreader_vector_quota.Enforcement
Every persisted vector write goes through
Vectors::save, which callsquota::admitfirst. That covers every vector-writing function and the library's results. The lock, the usage read and the decision live incatalog::quota.objsubid.reassign_ownedcharges the vectors it moves to the new role's quota, on every graph where neither the new role nor the caller is exempt, and refuses with 53400 past it.Scope
A quota bounds a role's vectors on another owner's graph. It does not bound total catalog storage:
create_graph, and the quotientcontractcreates for its caller, both give the caller storage it is exempt on. Per-role storage overall is option 46c, which is deferred. This is documented in DESIGN §5.7, §14 item 46 and docs/semantics.md.Dropped roles
drop_ownedand theDROP OWNEDevent trigger delete a role's quota rows only:orphans()and removed bydrop_orphans, or by the graph's owner.Tests
reassign_ownedpast the new role's quota is refused, and a move to the owner is not counted;drop_ownedandDROP OWNEDof oneself keep the role's quota row.vector_quota_race(dblink) covers:reassign_ownedqueued behind a writer.dump.pyandupgrade.py.reassign_ownedanddrop_ownedbypasses are now refused.Run locally
run.py: 52 of 52 pass, plain and--trusted.dump.pyandupgrade.pypass.-D warningsand rustdoc are clean.CLAUDE.md: the module list now includes
vector::quotaandcatalog::quota, and the parallel-labels rule follows DESIGN §9.3.🤖 Generated with Claude Code
https://claude.ai/code/session_012Rxfe2L3iVjSwSBPdUu1Ke