Repository navigation
Merged
Conversation
…tion grants, and API keys created with a role (EN-6) Until now the only per-graph write role was editor, which may also register, edit and drop rules, drop relations and install ontologies, and `.apikey create` could only mint keys that act as their (admin) owner. An agent that needed to record a claim therefore held a credential that could switch its own policy off. - Two new KG roles. `writer` writes facts in every relation, or only in its granted relations. `decider` writes facts only in its granted relations (claims and decision records). Neither may register, edit or drop a rule, declare a schema, drop a relation, clear a prefix, load a file, change an ontology, drop the KG or manage its access list. Every statement of a program is checked before anything commits, including both sides of an update (a guarded claim), and the refusal names the missing grant. - Per-relation grants: `.kg acl grant <kg> <user> writer|decider relations a, b`. The relation list is stored in `_internal.kg_acl_relations`, replaced with the grant in one commit; `.kg acl list` shows it; revoke, KG drop and user drop remove it. - `.apikey create <label> [<ttl>] role <role> on <kg> [relations ...]` creates a key limited to one KG and one role (editor, writer, decider or viewer). A scoped key never acts as an admin, cannot create a KG, uses no other KG, and its access is the meet of its scope and its owner's own access. Scoped keys are stored in `scoped_api_keys` with an `api_key_scopes` row, in one commit, so a server from before this change never reads one as an unscoped key; a key whose stored scope does not decode is not loaded. Dropping a KG revokes the keys scoped to it. `.apikey list` gains a `scope` column. No new locks: the role check runs on the statements already parsed for authorization, reading the ACL snapshot it already read; a scoped key's scope travels on its credential.
This was referenced Oct 4, 2026
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.
Intent
please continue developing the input layer engine so we make sure that the symbolic engine is ready to go live and completely production ready while we develop the GoTM uh, market.
(Context: approved plan, milestone 'Engine production readiness' (GitHub milestone 7). This task is item EN-6: scoped write roles: fact-writer, decider (claims and decision relations only), per-relation grants (E-11) — why: API roles map to engine identities; a
writerkey cannot change policy; the decider cannot write facts)The approved plan's substance for this item: each API role maps to an engine identity per cell. The decider's identity may write only the claim and decision relations, and a
writerkey may write only facts. Engine-level enforcement of this role split is a beta gate (today the only per-graph write role is editor, which may also register, edit and drop rules, delete any fact, drop relations and install ontologies, and.apikey createonly issues keys acting as their admin owner). E-11 asks for a fact-writer role plus per-relation write grants (agents: claim relations such asattemptonly; operators: policy facts such askill_switch,tool_policy; deploy credential: rules and schema), so that an agent key's bypasses (adding a second rule clause, deleting the kill-switch fact,.rel dropof the kill-switch relation) are refused.What Changed
writeranddecider(src/auth/access.rs). Awriterwrites facts in every relation, or only in its granted relations. Adeciderwrites facts only in its granted relations. Neither role can register, edit or drop rules, declare schema, drop relations, clear a prefix, load files, change ontologies, drop the KG or manage its ACL. Every statement in a program is checked before anything commits, including both sides of an update such as a guarded claim. A refusal names the missing grant or role..kg acl grant <kg> <user> writer|decider relations a, b. The relation list is stored in_internal.kg_acl_relationsand replaced together with the grant in one commit..kg acl listshows the list, and with no KG argument it now authorizes and lists the current KG. Revoking the grant, dropping the KG or dropping the user removes the stored relation list..apikey create <label> [<ttl>] role <role> on <kg> [relations ...]. Such a key is limited to one KG and one role (editor, writer, decider or viewer), never acts as admin, cannot create a KG and cannot use any other KG. Its access is the meet of its scope and its owner's access. These keys are stored inscoped_api_keyswith anapi_key_scopesrow, so older servers don't read them as unscoped keys. A key whose stored scope fails to decode isn't loaded. Dropping a KG revokes the keys scoped to it, and.apikey listgains ascopecolumn. The authentication guide and the command and syntax references document the new roles, grants and key options.Risk Assessment
✅ Low: The round-1 fix makes
.kg acl listauthorize and list the same KG (the program's exec KG), and its new test fails without the fix. Across the full change, I traced the scoped-key and writer/decider enforcement through every entry point (authorize_program, session creation, the WS read-access cache, admin-only commands, KG create/drop, persistence and the compatibility of stored rows) and found no bypass or wrong result.Testing
I built the inputlayer-server and ran it on a disposable data dir. Over WebSocket I created writer and decider API keys with roles, plus per-relation ACL grants for password users, and then tried every policy bypass named in the intent. Each one was refused with a message naming what was missing, and the kill switch and the empty
eligibleresult held. A mixed program committed nothing. Scoped keys could not reach other KGs, create one, or run admin commands..kg acl listwith no argument listed only the key's own KG. Scopes and grants survived a restart, and dropping the KG revoked its keys. The 8 targetedscoped_roles_testsalso pass. Everything passed. The UI was not changed, so there are no screenshots. The server and its temp data were torn down afterwards..kg acl listwith no argument from a key scoped to shop lists shop, and listing other KGs is refused.kg acl listreturns shop's entries;.kg acl list defaultand.kg acl list otherare refused; cargo test scoped_key_lists_only_its_own_kgs_acl passesEvidence: Setup: scoped key creation, decider-without-relations rejection, .apikey list scope column
Evidence: Decider agent key: allowed writes, refused bypasses, KG confinement, .kg acl list, admin view after
Evidence: Writer key and per-relation ACL grants for password users
Evidence: Restart persistence and KG drop revoking scoped keys
Evidence: WebSocket driver used for all scenarios
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 1 issue found → auto-fixed ✅
src/protocol/handler.rs:4626-.kg acl listwith no KG argument skips the per-KG check. Inauthorize_program,KgAclList(None)produces an empty target list, sokg_access/authorize_kg_operationnever runs for it. The executor then listskg_filter.or(knowledge_graph).unwrap_or("default")(handler.rs:4394-4398), which is not the KG that authorization resolved (exec_kg, the session's KG). Concrete path: a key created withrole decider on shopconnects over WS bound toshopand sends.kg acl list. The program authorizes, and the reply lists every user and their new per-relation grants ondefault. Over REST, passingknowledge_graph=<other>lists that KG's ACL too. This breaks the new guarantee that a scoped key "may use no other knowledge graph" (authentication.mdx). It also discloses the newkg_acl_relationsgrant lists to identities with no access to that KG. The gap was already there for non-admin users, but this change adds scoped keys and relation grants that make it matter. Fix: inauthorize_program, checkKgAclList(None)againstkgs(the current KG), as other KG-relative commands do. In the executor, listexec_kg/current_kginstead ofknowledge_graph.or("default"), so the KG that was authorized is the one that gets listed.🔧 Fix applied.
✅ Re-checked - no issues remain.
✅ **Test** - passed
✅ No issues found.
.kg acl listwith no argument from a key scoped to shop lists shop, and listing other KGs is refused.kg acl listreturns shop's entries;.kg acl list defaultand.kg acl list otherare refused; cargo test scoped_key_lists_only_its_own_kgs_acl passescargo build --bin inputlayer-server, then started the server with an isolated config, data dir and credentials file on 127.0.0.1:18766Node WebSocket driver (ilws.mjs) sending login/authenticate/execute frames to/ws?kg=<kg>Admin setup:.kg create shop/other, a kill-switchedeligiblerule,.apikey create agent role decider on shop relations attempt, decision,.apikey create ingest role writer on shop,.apikey create bad role decider on shop(rejected),.apikey list(scope column)Decider key: wrote and deletedattempt/decisionfacts; tried a rule,-kill_switch("o1"),+kill_switch,.rel drop,-kill_switch,.rule drop, a schema,+order, a mixed allowed+forbidden program, ACL grant,.kg drop,.user list,.apikey create,.kg create,.kg use other,.kg acl list [default|other], and connecting bound toother/defaultWriter key: fact insert and delete in any relation; rule,.rel dropand ACL grant all refusedACL grants with relations for password users ops (writer kill_switch, tool_policy) and bot (decider attempt); bad grants (decider without relations, viewer with relations) rejected; viewer user cannot create keys or writeRestarted the server, then re-checked the decider key, the ops grant,.apikey listand.kg acl list; dropped and recreatedshop, then retried the old decider keycargo test --lib scoped_roles_tests(8 targeted tests, including the review-fix ACL-list test)✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.