Contents

Access

How PremAgentic decides who may read a document: the four gates every read passes, the ordered access list and the folder rules, agents and where their models run, the reserved hosted-model agents group, the section fetch, and the second line of access control in the database. The other three gates are on Lifecycle, trust and freshness.

Four hard SQL conditions applied at every read, so they define the candidate set and no relevance signal can outweigh them. Behind them, the database enforces access a second time: search and section reads connect as a role that PostgreSQL row-level security limits to what the caller may see, so a bug in application code, or a hand-written query with that role's credentials, still returns nothing the caller was not permitted (see Two lines of access control below). Each is written over the document alone. The text leg runs under them; the vector leg scores only the chunks of documents the database returns under them; and the passages that leave are read back under them once more.

Access. Who may read a document is an ordered access list, evaluated the way the source system evaluates its own: the first entry that names the caller decides, and no entry means no. It is not "deny wins": an allow placed ahead of a deny grants, exactly as an explicit allow beats an inherited deny on a Windows file. Lists come from the connector when it can read the source's permissions, and otherwise from folder rules an administrator sets (prem rules set --prefix hr --entry "deny group:Contractors" --entry "allow group:Staff"); a folder no rule covers is readable by nobody. Each search computes, from the caller's groups as they are at that moment, which lists the caller may read, and the database returns only documents under those lists, so a removed membership, a disabled account, a revoked token or a changed rule applies to the very next query. Changing a rule moves the documents under it at once, with no re-embedding. Rules name groups by id, so renaming a group never changes a rule, and a group that a live rule names cannot be removed without --force, because removing it would disarm a deny. A connector that cannot read the source's permissions returns DocumentAccess.NoOne, and the document indexes but stays unreachable, so the gap is countable instead of invisible. AccessScope.UnrestrictedAudited(reason) bypasses this gate for operator tooling and the evaluation harness, and stamps its reason on every retrieval event it produces.

PremAgentic keeps its own users, groups and agents (prem users, groups, agents, tokens). An agent either acts for one user, holding exactly that user's access and never more (an entry naming the agent can hold it back further), or is a service agent holding only the groups granted to it, and either kind stops when its owner is disabled. Agent tokens are shown once and stored only as a hash.

Every agent says where its model runs: local for a model inside your network, hosted for one outside it, with the vendor named. Register it with prem agents add <name> --owner <user> --mode <mode> --model local|hosted [--vendor "Name"], and move it later with prem agents set <name> --model .... There is no default; a registration that does not say is refused, because the answer decides what the agent may be served.

PremAgentic keeps one group of its own, hosted-model agents, holding every agent whose model runs outside your network. You never edit it: it follows what the agents are set to. A folder rule that DENIES that group is how you say "this folder never leaves the network". You cannot use it the other way round: allowing the group gives nobody anything.

Every answer is recorded with where its model ran, so the audit trail is also the record of what left the network.

SectionFetcher applies all four. A caller who cannot find a document in search cannot fetch it by citing its path, and a path the gates hold back is reported as absent rather than forbidden, so the tool cannot be used to enumerate what exists.

01Two lines of access control

PremAgentic enforces access twice: in each query, and again in the database, which refuses every row a caller may not read. Search and section reads connect as a third role, premagentic_search, bound for the length of one read to a caller session that holds the caller's permitted access lists, recomputed for every read, so a revoked membership applies to the next query. The permitted lists never travel on the connection; the policy looks them up through a function owned by the schema's owner. The application role, which ingests, reads the whole index by design and is the one role listed as a writer; the API refuses to start with a search role the policy would not bind. With the application's own gate removed on purpose in a test, the database still returns nothing unauthorized. The full-text index stays in use under the policy because the text match runs inside a database function generated from the same gate code the application uses; a change to a gate's SQL shows up as a diff in a checked-in rendering. Measured at 100,000 chunks, a search through the search role is no slower than an exempt one. It was 2.68 times slower until the text match function applied the caller's and the session's access lists as one condition, which kept PostgreSQL's parallel plan.