ACL API
ACL is queried and managed through the dedicated /api/v1/acl endpoints for shared resources under viking://resources/.... See resource access control for inheritance rules.
Endpoints
| Method | Path | Behavior |
|---|---|---|
| GET | /api/v1/acl?uri={uri} | Read direct, inherited and effective permissions |
| PUT | /api/v1/acl | Set direct entries and/or inheritance mode |
| POST | /api/v1/acl/grant | Set one principal's direct level |
| POST | /api/v1/acl/revoke | Remove one principal's direct grant |
| DELETE | /api/v1/acl?uri={uri} | Clear direct entries and restore inherit mode |
HTTP
GET /api/v1/acl?uri={uri}
PUT /api/v1/acl
DELETE /api/v1/acl?uri={uri}
POST /api/v1/acl/grant
POST /api/v1/acl/revokeAll ACL operations require manage and return 403 without it; account ADMIN has implicit manage.
GET returns the ACL report directly in result. The report contains uri, acl_mode, direct_entries, inherited_entries, and effective_entries. Mutation endpoints return the report directly.
Example set_acl body:
{
"uri": "viking://resources/project-a",
"acl_mode": "restricted",
"entries": [{"principal": "user:bob", "level": "read"}]
}entriesreplaces direct grants; omission preserves them;[]clears them.acl_mode:inheritcombines direct and inherited grants;restricteduses direct grants only. Omission preserves the mode.- Supply at least one field. Inherited and effective entries are read-only.
- Principals are
user:{id},group:{id}, oruser:*; levels areread,write, ormanage. Duplicate principals retain the highest level. grant_acltakes{uri, principal, level},revoke_acltakes{uri, principal}, and resetting usesDELETE /api/v1/acl?uri={uri}. Incremental changes run inside the server's existing lock.
Creation and content writes
POST /api/v1/resources, POST /api/v1/fs/mkdir, and POST /api/v1/content/write accept a top-level acl field, alongside tags and tag_mode where supported:
{
"acl": {
"acl_mode": "restricted",
"entries": [{"principal": "user:bob", "level": "read"}]
}
}Omitting ACL gives new nodes empty direct grants and inherited parent permissions; existing nodes retain their ACL. Explicit ACL requires inherited manage for a new node, or the existing node's own manage. Authorization and validation precede content mutation. Write access alone cannot change ACL. Creators receive no extra grants.
Imports apply direct ACL only to the final import root. Descendants inherit it; automatically created parent directories receive no direct grants. Re-importing unchanged content still applies an explicit ACL update.
The account's acl.enabled defaults to false: namespace rules apply and ACL is ignored for access decisions. When enabled, the shared root permanently grants user:* = manage. Supplying ACL neither enables the account switch nor implicitly selects restricted mode.
ACL stays in the context index and follows existing asynchronous processing and wait semantics. Temporary inconsistency is accepted. Standalone ACL mutations require an existing context record. Vectorless records and empty-file support are outside this change.
Case Aliases and ACL
When localfs runs on a case-insensitive filesystem, ACL is resolved from the requested URI while the underlying filesystem may resolve a URI that differs only by letter case to the same real file or directory. If the alias URI is authorized, later operations such as read, stat, ls, write, rm, cp, and mv may act on the real object.
Example:
# Bob's directory grants access only to Bob
ov acl set viking://resources/bob-private --acl-mode restricted --entry user:bob=manage
# Alice reading the exact path should get 403
ov read viking://resources/bob-private/private.md
# On case-insensitive localfs, the alias directory may hit the same real directory
ov read viking://resources/BOB-PRIVATE/private.md
ov ls viking://resources/BOB-PRIVATE
ov write viking://resources/BOB-PRIVATE/private.md --content changed --mode replaceThe same risk applies to system-generated files that are reachable through public URIs in the shared resource tree, such as .abstract.md and .overview.md. For example, if the real file is viking://resources/bob-private/.overview.md, viking://resources/BOB-PRIVATE/.OVERVIEW.md may hit it. Internal databases, queues, and lock files that are not exposed through public URIs are not directly in scope for this URI alias issue.
On case-sensitive systems, BOB-PRIVATE and bob-private are different paths; a missing alias target returns NOT_FOUND and does not trigger this issue.
CLI
ov acl get viking://resources/project-a
ov acl set viking://resources/project-a --acl-mode restricted --entry user:bob=read
ov acl grant viking://resources/project-a --principal user:bob --level write
ov acl revoke viking://resources/project-a --principal user:bob
ov acl rm viking://resources/project-a
ov mkdir viking://resources/project-a --acl '{"acl_mode":"restricted","entries":[{"principal":"user:bob","level":"read"}]}'
ov add-resource ./docs --to viking://resources/docs --acl '{"acl_mode":"restricted","entries":[]}'
ov write viking://resources/project-a/a.md --content hello --mode create --acl '{"acl_mode":"inherit"}'SDK
acl = {"acl_mode": "restricted", "entries": [{"principal": "user:bob", "level": "read"}]}
client.mkdir(uri, acl=acl)
client.add_resource("./docs", to=uri, options={"acl": acl})
client.write(file_uri, "hello", options={"acl": acl})
report = client.acl_get(uri)
client.acl_set(uri, [], acl_mode="restricted")
client.acl_grant(uri, "user:bob", "read")
client.acl_revoke(uri, "user:bob")
client.acl_delete(uri)Async Python uses the same method names. TypeScript provides aclGet(uri), aclSet, aclGrant, aclRevoke, and aclDelete; creation options accept acl, while mkdir accepts it as its third argument. Go provides ACL(ctx, uri), SetACL, SetACLMode, GrantACL, RevokeACL, and DeleteACL, with ACLSpec values for creation.