Repository navigation
br-service-engine 0.3.1
0.3.1 - 2026-09-23
A patch over 0.3.0: a mirror accepts the key grammar a published language really
uses, and migrate asserts the owner posture the ops contract already required.
Both are additive for an adopter on 0.3.0 — every /-terminated consumption and
offer keeps its behaviour and its manifest key — and the second only refuses a
deployment that was already outside the contract.
Fixed
-
A mirror consumes a
.-terminated prefix and a single exact key. 0.3.0
refused at registration everyConsumed::PREFIXthat did not end in/
(invalid configuration: mirror … consumes prefix … which must end with '/' so its manifest key sits outside the data prefix), so a service consuming a producer
whose keys use.as the segment separator, or one singleton configuration key,
could not boot. The rule protected the manifest placement:manifest_key(prefix)
strips the trailing separator and appends_manifest, and under the mirror's
starts_withmatching a prefix that ends mid-segment (catalog/items) would take
its own manifest (catalog/items_manifest) and any sibling sharing its
characters as data. 0.3.1 parsesConsumed::PREFIXonce into one of two forms
(mirror/scope.rs, crate-privateKeyScope):- a prefix ends in a segment separator,
/or.(catalog/items/,
catalog.item.); the mirror reads every key that starts with it, and its
manifest key is the sibling{prefix minus the separator}_manifest
(catalog/items_manifest,catalog.item_manifest), which never starts with
the prefix, so a manifest is never read as data and a data key is never judged
as a manifest; - any other string is one exact key (
catalog.settings), read with one GET at
scan and matched by equality at watch — a key beneath it or sharing its
characters is not read. A single key is judged against no manifest: only an
engine offer writes a manifest, and an offer publishes a family under a prefix,
so no engine producer ever writes one beside a single key. A non-engine
producer's single key is versioned throughwire_version(N9), as before.
Mirror::validaterefuses, asEngineError::Confignaming the mirror and the
string, only the forms that are ambiguous or malformed: a string ending in_or
-(a prefix cut mid-segment — it would otherwise silently read as a key nobody
publishes), a single key ending in_manifest(it would read an offer manifest as
data), a leading separator or two separators in a row (an empty segment), and a
character outside[A-Za-z0-9_./-].require_key::<C>(key)accepts the consumed
single key itself; a key beneath a single key isRequiredKeyOutsidePrefix. - a prefix ends in a segment separator,
-
An offer publishes under a
.-terminated prefix too.register_offerapplies
the same grammar throughmanifest_key, so a producer and its consumers compute
one manifest key from one rule; an offer still refuses a single key. Becausex/
andx.share the manifest keyx_manifest, an engine refuses a second offer
whose manifest key another registered offer already claims; across services the
manifest carries itsprefix, so such a collision reads as
ManifestMismatch::Offerand dead-letters instead of passing silently. -
manifest_key(prefix)(public) accepts a.-terminated prefix and refuses a
single key or a malformed prefix with a message that names the form.
Added
migrateasserts the owner posture. Constitution principle 30 and the ops
contract require the owner role to carryBYPASSRLS: a data migration run by a
role subject to row-level security touches no row of aFORCE ROW LEVEL SECURITY
table and still reports success. 0.3.0'smigratenever checked it, so an adopter
had to add its own guard.engine::boot::assert_owner_posture(&PgPool)(public)
passes a superuser or aBYPASSRLSrole and refuses any other with the new
EngineError::OwnerSubjectToRls { role };apply_migration_chaincalls it right
after the pure library validation and before the first migration, somigrate
(the chart's init container) logs the refusal and exits non-zero with nothing
applied. The engine's own black-box and example harnesses now declare their owner
rolesBYPASSRLS, as production does.- Conformance:
s238(a.prefix and a single key read exactly their keys at scan
and at watch, with sibling, child, near-miss and manifest keys published beside
them),s239(a.prefix is judged against its sibling manifest, a single key
against none),s240(the owner posture matrix, and the chain refusing before it
applies anything),bb15(the real binary'smigraterefuses an owner without
BYPASSRLSand exits non-zero). Unit tests pin the grammar, the manifest
placement for every accepted form, the refused forms, and the offer-side claim.
Adopter migration
- mirror (additive): a consumption of a
.-separated producer or of one
configuration key now registers as written — declareConsumed::PREFIXas the
prefix with its trailing.or/, or as the exact key. Nothing changes for a
/-terminated prefix. A string without a trailing separator is now one exact key:
if you meant a prefix, add the separator (0.3.0 refused that string, so no 0.3.0
adopter reads differently). - migrate (fail-loud): declare the owner role
BYPASSRLS(or runmigrateas a
superuser) — the ops contract already required it, and amigratethat now exits
withOwnerSubjectToRlsnames the role to fix in GitOps. A service-owned owner
posture guard inmainis redundant and can be deleted. EngineErrorgainsOwnerSubjectToRls(the enum is#[non_exhaustive]).