TERSL/DB — the system

Keys, grants and visibility

How API keys identify a caller, how a namespace's grants document gives each key rights at paths, and what a key sees and is told where it holds no right.

Overview#

Every request to TERSL/DB presents an API key, and every namespace has a grants document that says what each key may do there. This page describes keys, the grammar of a grant, the rights and what each permits, and how the service answers a key in the parts of a namespace it has no right to. After reading it you can write grants for an agent and predict what that agent will see.

Keys#

A key is the principal: the service decides everything about a request from the key it presents. A key is a 64-character string, tsk_ followed by a 16-character key id, an underscore and a 43-character secret. The key id uses digits and capital letters without I, L, O and U; it is the part that appears in grants, in the record and in the portal. The full key is shown once, when it is issued on the portal's Keys page. The service stores only a hash of it, so a lost key cannot be recovered; issue a new one and revoke the old.

Send the key as Authorization: Bearer <key> or as Terse-Key: <key>. If both are present, Authorization is used. A browser page served from another origin can send neither; see Headers. A key belongs to one account and works only on that account's namespaces. A key can be revoked or deleted, and can carry an expiry date; a revoked, deleted or expired key is refused exactly like an unknown one. The number of keys an account may hold is the plan's key limit.

The grants document#

Each namespace {ns} has a companion document named {ns}_meta. It is a TERSL document of its own, with its own history, and it cannot be addressed on the data-plane routes by anyone: a request to a name ending in _meta is answered with an empty 404. You read and edit it on the portal's Grants page. It has these sections:

Section Holds
# Access the grants; the only section that confers any right
# Meta ## Name (the display name), ## Description, and the readOnly flag on the header
# Verification the namespace's record settings, kept by the service; the Grants page does not show it and refuses a save that includes it

A new namespace's # Access section is empty, so no key is admitted until you grant one. The portal checks the document when you save it and refuses a document with any finding (the findings are shown on the page with codes such as GRANTS003) or that names a key id outside your account. Comments do not survive a save.

Grant lines#

The grammar of # Access:

Where What it means
# Access the section; nothing outside it is read
## <key id> one key; matched against the presenting key's id
### A.B under a key a path in the governed namespace; a dotted header anchors at its last segment
grant(…) rights anchored at the path it stands under; directly under the key header it anchors at the namespace root
flags on grant the closed set read add modify remove append reorder rename history all
max depth: n on grant a non-negative integer that limits add only: new nodes may appear at most n+1 levels below the anchor, and max depth: 0 allows direct children only

This document gives one key read access to the whole namespace and lets it file records one level under Guild.Members, and gives a second key every right under Places:

tersl
# (schema: meta v1)
# Access
## 7Q3M9XBK2ADFHNPR
grant(read)
### Guild.Members
grant(add; append; max depth: 1)
## 4H8R2WQZ6TNC0JKD
### Places
grant(all)

Rights#

Structural changes are checked at the parent of the node they affect; changes to a node's content are checked at the node.

Right Permits
read reading the subtree at and under the anchor; also required at the source of a ? command inside a write, such as a move
add creating a node whose parent is at or under the anchor, within any max depth
modify changing a standing node's attributes, and setting, replacing or removing its TEXT
remove removing a standing node ([REMOVED], -name)
append TEXT [APPEND] onto a standing node
reorder placing a standing node with [FIRST], [LAST], [BEFORE] or [AFTER]
rename renaming a node with [WAS old]
history reading the record: the history route and point-in-time reads; held at the root, it also admits the seal route
all all eight rights above

A node created by a write can be given its attributes, TEXT and position in the same write without modify, append or reorder. A removal of something that does not exist asks for no right. The namespace root's own attributes (#(…) and # ()) are covered only by a grant anchored at the root. A wipe of the whole namespace (# [REMOVED]) needs remove at the root.

How grants combine#

Grants only add rights. A key's rights at a path are everything granted to it at that path or any path above it, and there are no deny rules: a grant deeper in the tree cannot take away a right granted higher up. A grant that carries an unrecognised flag, or any attribute other than max depth, confers nothing at all, so grant(read; add; delegate) grants neither read nor add. history is not implied by read. Two max depth limits on one path take the more generous; an add with no limit above a limited one makes the limit ineffective.

A grant applies to whatever node stands at its path. Renaming or removing P.X leaves every grant at or under P.X dormant, and a grant at a path where nothing stands applies as soon as a node arrives under that name, by creation or by a rename into it. rename keeps a node and its history and changes its address; remove followed by add replaces the node with a new one.

A grants change takes effect within a few seconds.

The visible world#

The parts of a namespace a key can read are its visible world. The service answers in one of three ways, depending on how much of the namespace a key's grants touch.

A key admitted to nothing in a namespace receives an empty 404 with no headers, identical to the answer for a namespace that does not exist. The same answer is given for no key, an unknown key, a wrong secret, and a revoked, expired or other-account key. The namespace is also missing from that key's GET /v1/ns listing. Such a key never sees a rate-limit header, a 429 or an account status.

A key admitted to something receives answers per path. A write that needs a right the key lacks is refused as a whole with status 200, one line naming the first refused right and the path, and Terse-Csn set to the standing CSN:

text
error: declarations: TERSE-SEC-001: Add at 'Places.Harbour' is not permitted

The message gives no reason and does not list what the key could do instead. A read outside the visible world is not an error: the answer is empty, or contains only the visible parts. A visible subtree is returned exactly as an unrestricted read would return it. Containers above it appear as headers with their names only, without attributes, TEXT or objects, so that the visible parts have an address. Query operators such as *, WHERE, FIRST and CONTAINERS run over the visible part only, so counts and selections reveal nothing about hidden nodes. A key that holds add or append but not read sees nothing.

History pages, exports and point-in-time reads are pruned to the same world. History and point-in-time reads use the key's history right under its current grants, so a key removed from a subtree today cannot read that subtree as it was last week. A key with read and no history receives an empty 200 from both.

Administrators and explain#

A key that holds all at the namespace root is an administrator of that namespace. Some routes are open only to administrators: copying the namespace, the scrub, playback sessions and /explain. Any other key receives the empty 404 from them, even for malformed parameters.

GET /v1/ns/{ns}/explain?key=&csn=&path= answers what a given key was permitted at a given CSN. It finds the version of the grants document that governed that CSN and prints the key's grant lines from it, followed by the key's visible world for reading and for history:

text
// terse-explain key=7Q3M9XBK2ADFHNPR csn=4 grants=2
// resolved=authz
# Access
## 7Q3M9XBK2ADFHNPR
### Places
grant(read)
// world read at <root>: partial
# Places
// world history at <root>: nothing

Use it to answer questions such as why an agent could write somewhere last Tuesday. Enforcement always uses the current grants; /explain reports past ones.

Acting through the portal#

The portal reads and writes namespaces for its users with the keys assigned to them on the Users page. Each request the portal makes is checked as if that key had been presented directly: the key must be active, unexpired and in the same account, and its grants and visible world apply. The portal adds no rights of its own. The log entry records the key as principal= and the portal user as user=.

A key issued as portal-only has its secret discarded when it is issued, so it can be used only through the portal.

Read-only namespaces#

The readOnly flag on # Meta makes a namespace read-only: queries, history and exports are served, and a write is refused with 409. Only a key that would be admitted to write somewhere in the namespace receives the 409. A key admitted to nothing receives the empty 404, and a key with only read or history receives the same per-path refusal it would receive on a writable namespace. A copy of a namespace is created read-only; its administrator lifts the flag by removing it on the Grants page.