pub struct State {
pub store: Mutex<Store>,
pub root: PathBuf,
pub sessions: Mutex<HashMap<MergeSessionId, ApiMergeSession>>,
pub policy_ceiling: Option<Policy>,
pub blob_limits: Option<BlobLimits>,
pub reserved_producers: Vec<String>,
pub reserved_kinds: Vec<String>,
/* private fields */
}Fields§
§store: Mutex<Store>§root: PathBufFilesystem root of the store. Held alongside the Store
itself so handlers that need to read store-level files
(e.g. users.json for actor auth) don’t have to round-
trip through the lock.
sessions: Mutex<HashMap<MergeSessionId, ApiMergeSession>>In-memory merge sessions, keyed by MergeSessionId. Sessions
are ephemeral by design (#134 foundation): they live for the
lifetime of the server process and are GC’d on commit. A
future slice can persist them to disk so a session survives
process restarts. For now an agent that gets unlucky with a
restart re-runs merge/start and gets a fresh session.
policy_ceiling: Option<Policy>Optional server-imposed ceiling on the effect policy honored
by /v1/run and /v1/replay. None (the default, used by
single-tenant lex serve) runs the caller’s request policy
as-is — the operator is the caller there, so that’s
intended. When Some, the request policy is clamped via
[clamp_policy] so it can only narrow the ceiling, never
widen it.
Any embedder that exposes this API to untrusted callers — a
hosted, multi-tenant gateway like lex-hub — MUST set this.
Without it the request body can grant itself [proc]
(arbitrary subprocess spawn), [fs_*] over /, and
unrestricted [net]: arbitrary code execution as the server
process. See lex-hub#6.
NOTE: an empty scope list means “any path/host” in the
runtime, so a ceiling that puts fs_read/fs_write/net in
allow_effects MUST also populate the matching scope list
(allow_fs_read, …) or it re-opens the wildcard. Granting
none of those kinds is the safe default.
blob_limits: Option<BlobLimits>Optional server-imposed limits on the files-beside-the-op-log blob
space (#1007): /v1/blobs/batch refuses an oversize blob (413) or
one that would take the store past its quota (507), and
/v1/ops/batch refuses a SetFiles whose manifest has too many
entries. None (the default, single-tenant lex serve) is
unlimited. A hosted, multi-tenant embedder such as lex-hub should
set it — the same shape as policy_ceiling.
reserved_producers: Vec<String>produced_by.tool names that clients may NOT claim. Defaults to
empty (single-tenant lex serve and existing embedders behave
exactly as before). When non-empty, POST /v1/attestations/batch
refuses — 403 ReservedProducer, whole batch, nothing written —
any attestation whose produced_by.tool matches an entry. An entry
is an exact name, or — when it ends in * — a prefix
("lex-store::review:*" reserves every lex-store::review:<who>
producer, whose suffix is variable). Matching trims and ASCII
case-folds both sides; blank entries are ignored.
The point is to keep a name that only the server writes (the hub’s
own lex-hub-ci, see lex_store::HUB_CI_PRODUCER_TOOL, and the
lex-store::review:* family, see lex_store::REVIEW_PRODUCER_RESERVATION)
from being minted by a tenant key holder. Server-internal writers
(Store::verify_head_and_attest, record_review, …) call the
store directly and are unaffected. A hosted embedder such as
lex-hub should set it; there is deliberately no default name here.
reserved_kinds: Vec<String>Attestation KINDS that clients may NOT file (#1066). Defaults to
empty (single-tenant lex serve and existing embedders behave
exactly as before). When non-empty, POST /v1/attestations/batch
refuses — 403 ReservedKind, whole batch, nothing written — any
attestation whose kind matches an entry.
This is the counterpart of reserved_producers
for readers that key on the KIND rather than on who produced it:
Store::latest_review_verdict (the review inbox, promote’s
“standing Reject”) takes the latest Review on a stage whatever its
produced_by.tool, so reserving the lex-store::review:* producer
family alone does not protect verdicts — a client files a Review
under evil-tool. An embedder that stamps reviewer identity
server-side reserves REVIEW_KIND as well.
An entry is the serde tag of lex_vcs::AttestationKind
("review", "type_check", …; see the *_KIND consts). Matching
is against the kind of the PARSED attestation — exactly what the
store would persist — so no body shape can store a reserved kind
without being judged as it (a body with duplicate "kind" keys does
not parse at all: 400).
Matching trims and ASCII case-folds both sides, and additionally
ignores _, so "Review", " REVIEW ", "TypeCheck" and
"type_check" all mean what they say; an entry ending in * is a
prefix (as for producers); blank entries are ignored.
Server-internal writers (Store::verify_head_and_attest,
record_review, POST /v1/review/verdict, …) call the store
directly and are unaffected. Reserving a kind is not authenticity
(a signature is): reserving TypeCheck should wait for
signature-checked gates.
Implementations§
Source§impl State
impl State
pub fn open(root: PathBuf) -> Result<Self>
Sourcepub fn open_with_ceiling(
root: PathBuf,
policy_ceiling: Option<Policy>,
) -> Result<Self>
pub fn open_with_ceiling( root: PathBuf, policy_ceiling: Option<Policy>, ) -> Result<Self>
Like State::open but installs a policy_ceiling
that /v1/run and /v1/replay clamp the caller’s request
policy against. Embedders exposing this API to untrusted
callers must use this constructor (or set the field directly).
Sourcepub fn with_blob_limits(self, limits: Option<BlobLimits>) -> Self
pub fn with_blob_limits(self, limits: Option<BlobLimits>) -> Self
Install blob_limits (#1007).
Sourcepub fn with_reserved_producers(self, tools: Vec<String>) -> Self
pub fn with_reserved_producers(self, tools: Vec<String>) -> Self
Install reserved_producers: the
produced_by.tool names clients may not claim through the
attestation-writing HTTP endpoints.
Sourcepub fn with_reserved_kinds(self, kinds: Vec<String>) -> Self
pub fn with_reserved_kinds(self, kinds: Vec<String>) -> Self
Install reserved_kinds: the attestation
kinds clients may not file through POST /v1/attestations/batch.
Sourcepub fn new_with_tenant(tenant_id: &str, store_root: PathBuf) -> Result<Self>
pub fn new_with_tenant(tenant_id: &str, store_root: PathBuf) -> Result<Self>
Construct a per-tenant State by prefixing store_root with the
tenant id. Single-tenant lex serve is unaffected — it calls
State::open directly.
tenant_id is restricted to [A-Za-z0-9_-]{1,64}: anything else
(path separators, .., NUL, absolute paths, dotfiles, empty
string) is rejected before touching the filesystem. Without this
PathBuf::join("/etc") would silently replace store_root, and
PathBuf::join("../foo") would escape the tenant root.
Sourcepub fn new_with_tenant_and_ceiling(
tenant_id: &str,
store_root: PathBuf,
policy_ceiling: Option<Policy>,
) -> Result<Self>
pub fn new_with_tenant_and_ceiling( tenant_id: &str, store_root: PathBuf, policy_ceiling: Option<Policy>, ) -> Result<Self>
Multi-tenant constructor that also installs a policy ceiling
for /v1/run / /v1/replay. The path-traversal guard from
new_with_tenant and the effect
ceiling are the two halves a hosted gateway needs.