Repository navigation
baukit 0.5.0
baukit v0.5.0
One release train for the Rust crates, TypeScript packages, and templates.
Summary
baukit 0.5.0 carries the cross-product feature plan (20 items) and its follow-up wave. It has many breaking changes, because no product is live yet and nothing kept a compatibility shim. Read the breaking list before upgrading a product.
New
baukit-egress: a guarded outbound HTTP client that blocks private and special-purpose addresses, with a 300-secondRetry-Aftercap by default.baukit-push: a push device registry with token rotation, eviction, dead-token invalidation, daily delivery claims, and deferred Expo receipt polling backed by PostgreSQL.baukit-auth: a PostgreSQL API token store with grants.baukit-core: signed media grants behind themedia-grantsfeature, plus an njs edge verifier indeploy/media-grantsand shared vectors infixtures/media-grants.@baukit/notifications-coreand@baukit/notifications-expo: owned local notification planning and replacement that never cancels other features' notifications.@baukit/localization-core:resolveZonedLocalTimewith explicit gap and fold policies.@baukit/data-contracts-expo-sqlite:ExpoSqliteConnection, a queued raw-SQL connection with foreign keys enforced inside transactions, plus a Node test driver and a migration conformance suite.- Replay-safe mutations, ETag revision preconditions, server purge horizons, and OpenAPI error responses.
- Templates: auth products get a Redis service in Compose, stricter coverage floors, and Keycloak stack e2e helpers.
Breaking
- Wire names are camelCase: the error envelope's
requestId,nextCursor, event envelope fields,ledgerEntryId,ConnectionStatusfields, rate-limitretryAfter, andacceptLanguage. baukit-httpresponses default toCache-Control: private, no-store, and the CORS defaults now expose and accept more headers.classify_http_statustreats425 Too Earlyas retryable.baukit-jobs:JobStore::claimtakes job types.baukit-push: tokens areDeviceToken, andAcceptedcarries a ticket ID.baukit-auth: bearer challenges useerror_description, andscopeis mapped by default.- Several error enums gained variants.
The sections below list every change per crate and package.
Verification
Every step of the local CI mirror passed on the release candidate: make ci, all Rust tests including Docker-gated ones (707), cargo deny, MSRV 1.95, all seven generated fixture flavors with coverage floors, the MCP fixture gate, the Android compile gate, Expo SQLite on an Android emulator (30 cases), and the Hermes vector and notifications checks. iOS and physical devices were not tested.
Rust crates
baukit-auth
Added
- Add the
sqlx-postgresfeature withPostgresApiTokenStore, an
ApiTokenStoreover theapi_tokenstable in
POSTGRES_API_TOKENS_MIGRATION_SQL. The constant is available without the
feature. Grants go into the sameINSERTas the digest.
with_active_token_limitcounts and inserts under a per-owner advisory lock.
touch_last_usednever moveslast_used_atbackwards, andrevokematches
the owner. Products copy the migration and add their own owner foreign key. - Add
erase_owner_api_tokensfor owner erasure without a cascading foreign
key, andpurge_inactive_api_tokensfor batched retention of revoked and
expired tokens. Both need thesqlx-postgresfeature. - Add opaque API token grants.
NewApiToken::with_grantssets them,
ApiTokenRecord::grantsandApiToken::grantscarry them, and
Principal::grantsreturns them for API-token principals. Each grant is an
RFC 6749 scope token of at mostMAX_API_TOKEN_GRANT_LENGTH(128) bytes, and
a token carries at mostMAX_API_TOKEN_GRANTS(64). Anything else fails with
the newApiTokenError::InvalidGrants. - Add
Principal::scopes, read from the verifiedscopeclaim by default.
PrincipalClaimMapping::scope_claimreads another claim. A space-delimited
string and an array of strings both work. - Add
PrincipalClaimMapping::profile_claims,Principal::profile_claims,
Principal::profile_claim, andProfileClaimfor caller-selected verified
string and boolean claims such asemailandemail_verified. - Add
ClerkVerifier::with_audiencesandWorkOsVerifier::with_audiencesfor
products that need anaudcheck. Both adapters still skip the check by
default. Addwith_profile_claimsandissuerto both, and
OidcVerifier::issuer. - Add
IssuerVerifierandMultiIssuerVerifier::from_verifiers, so one
multi-issuer verifier routes OIDC, Clerk, and WorkOS tokens.
Changed
- Break:
ApiToken,ApiTokenRecord, andNewApiTokengain a public
grants: BTreeSet<String>field. Struct literals must set it. Store adapters
must persistApiTokenRecord::grantsin the same write as the digest and
return them onApiToken. - Break:
ApiTokenErrorgainsInvalidGrants. Exhaustive matches must handle
it. - Break:
PrincipalClaimMapping::new()anddefault()now mapscope, so
OIDC, Clerk, and WorkOS verification reads it. A token whosescopeis
neither a string, an array of non-empty strings, nor null fails with
InvalidPrincipalContext, where it used to verify. - Break:
AuthRejectionchallenges use the RFC 6750error_description
parameter instead of the nonstandardhint. Expired tokens now get
Bearer error="invalid_token", error_description="expired"and every other
rejected token getserror_description="invalid". Clients that parsed
hint=must readerror_description=. - Break:
ApiTokenPolicyRejection::with_detailaccepts camelCase detail names only, such as
activeCount, because they become errordetailskeys. A snake_case name such as
active_countnow returnsInvalidDetailName. Codes stay snake_case.
baukit-config
- No component-specific changes in this train.
baukit-core
Added
- Add the
exportmodule withencode_csv,CsvCell,CsvOptions, andCsvEncodeError. It
writes RFC 4180 CSV with CRLF separators and prefixes an apostrophe to text cells that start like
a spreadsheet formula;CsvOptions::without_formula_neutralizationturns that off and
CsvOptions::with_byte_order_markadds U+FEFF.CsvCell::Numericholds a JSON-grammar number
that is written unchanged, so-5stays a number. The module uses onlystdandthiserror, so
default dependencies are unchanged. It passes the same vectors as@baukit/data-contracts/export. - Add the optional
paginationfeature and thepaginationmodule withCursor,Page,
PageKey,PageParams,PaginationError,DEFAULT_PAGE_LIMIT, andMAX_PAGE_LIMIT, moved
frombaukit-httpso domain crates can use them without Axum. The feature addsbase64,
ring, anduuid; the default build keeps its three dependencies. - Add
pagination::MAX_CURSOR_BYTES(4096). Breaking:Cursor::decoderejects longer input with
PaginationError::InvalidCursorbefore decoding, andCursor::encodereturns the same error
instead of issuing a longer cursor. - Add the optional
media-grantsfeature and themedia_grantmodule withMediaGrantKey,
MediaGrantKeyRing,MediaGrant,MediaGrantRequest,VerifiedMediaGrant,
MediaGrantKeyError,MediaGrantError,signing_input, andvalid_media_path. It signs and
verifies HMAC-SHA256 playback grants with a current and an optional previous key, compares
signatures in constant time, and redacts key material fromDebug. The feature addsbase64,
ring, andzeroize. It passesfixtures/media-grants/vectors-v1.jsontogether with the njs
verifier indeploy/media-grants.
Changed
- Break:
pagination::Pageserializesnext_cursorasnextCursor.
baukit-credential-vault
- No component-specific changes in this train.
baukit-egress
Added
- First release of
baukit-egress, a guarded outbound HTTP client for
destinations that users supply. GuardedClientresolves through theResolverport, rejects the lookup when
any answer fails theAddressPolicy, connects only to the checked answers,
never follows redirects, ignores proxy settings, and bounds the lookup, the
connect, the request, and the response body.EgressRequestand
EgressResponsecarry the request and the read body.AddressPolicywithPublicOnlyandAllowLoopback, and
is_public_address, following the IANA special-purpose registries. IPv4-mapped
addresses and64:ff9b::/96are judged by their embedded IPv4 address.Resolver,ResolveFuture,ResolveError,SystemResolver,
StaticResolver, andresolve_destination.validate_destinationchecks scheme, user info, fragment, host, and address
literals without a lookup.EgressErrorwithcode(),retry_class(), andis_retryable().
Non-2xxstatuses are classified bybaukit_http::classify_http_status.
DestinationRejectionnames each refused URL shape. No error carries the URL.EgressOptionswithEgressOptionsError: 3 s lookup, 5 s connect, 10 s
request, and 1 MiB body by default.EgressOptions::with_max_retry_afterandmax_retry_aftercap the
Retry-Afterdelay inEgressError::Statusat 300 s by default. A zero cap
returnsEgressOptionsError::ZeroRetryAfterCap.- Shared vectors in
fixtures/egress/address-policy-v1.json.
Changed
- Break:
425 Too Earlyis nowRetryClass::Unavailable, following
baukit_http::classify_http_status. - Break: a
Retry-Afterdelay above the cap is clamped instead of reported
as sent.
baukit-events
Changed
- Break:
EventEnvelopeserializes camelCase field names while staying schema version 1:
event_id,user_id,occurred_at,source_app, andschema_versionare noweventId,
userId,occurredAt,sourceApp, andschemaVersion.typeandpayloadare
unchanged. Payload keys in the shared fixtures are camelCase. - Break:
IngestOutcomeserializesledger_entry_idasledgerEntryId.
baukit-http
Added
HttpOptions::with_additional_exposed_headersadds response headers to the CORS exposed set,
next towith_additional_allowed_headers. Invalid names return
HttpOptionsError::InvalidHeaderNameand duplicates are ignored.ResponseCachePolicyandHttpOptions::with_response_cache_policycontrol the default
Cache-Controlheader.ResponseCachePolicy::HandlerOwnedturns the default off.- Strong-ETag revision preconditions.
RevisionEtagformats"<prefix><revision>"ETags and
parses exactly one strongIf-Matchvalidator throughrequired_if_matchor
optional_if_match.Revisionholds a revision in0..=i64::MAX.InvalidIfMatchnames each
rejected form: repeated header, empty, non-ASCII,*, weak, list, malformed, prefix mismatch,
non-canonical revision, and out-of-range revision.PreconditionErrorconverts intoApiError
as 428precondition_required, 400invalid_if_matchwithdetails.reason, or 412
precondition_failedwithdetails.currentRevisionwhen the stored revision is known.
ensure_current_revisionbuilds the 412 from an expected and a current revision.
RevisionOutOfRangeconverts into a 500. Shared vectors live in
fixtures/etag-preconditions/vectors-v1.json. Idempotency-Keyparsing.IdempotencyKeyRule::new(min, max)andtry_newset per-route
bounds up toMAX_IDEMPOTENCY_KEY_BYTES(255) over visible ASCII, andrequiredoroptional
read the header from aHeaderMap.IdempotencyKeyhides its value fromDebug.
InvalidIdempotencyKeynames each rejected form: repeated header, empty, invalid character, too
short, and too long.IdempotencyErrorconverts intoApiErroras 400
idempotency_key_required, 400invalid_idempotency_keywithdetails.reason, 409
idempotency_key_reused, or 409idempotency_key_in_progress.IDEMPOTENCY_KEYand the four
code constants are exported.RetryHeaderOptions::with_max_retry_afterclamps parsedRetry-Afterand vendor delays to a
maximum. The default stays uncapped, soclassify_http_statusstill reports what the upstream
sent.
Changed
- Breaking: the default CORS exposed set now also contains
Retry-After,RateLimit-Limit,
RateLimit-Remaining, andRateLimit-Reset, the headersbaukit-ratelimitemits. Browsers can
read them on responses that pass throughfinalizeorlayers. - Breaking: every response from
finalizeorlayersgetsCache-Control: private, no-store
unless the handler or an inner layer already setCache-Control. This includes error, timeout,
panic, 404, and preflight responses. SetResponseCachePolicy::HandlerOwnedto keep the previous
behavior. - Breaking: the default CORS exposed set now also contains
ETagandLocation, and the default
allowed request headers now containIf-Match. - Breaking: the default allowed request headers now contain
Idempotency-Key. - The
HttpOptionsError::InvalidHeaderNamemessage now readsinvalid CORS header name, because
it covers exposed headers too. baukit-httpdepends onbaukit-corewith thepaginationfeature and no longer depends on
base64orringdirectly.- Break: the error envelope field
request_idis nowrequestId, through the re-exported
ErrorBody. This covers everyApiError, extractor rejection, timeout, panic, body-limit,
404, and 405 response. - Break: the
RequestLocalerejection detail keyaccept_languageis nowacceptLanguage. - Break:
Pageresponses carrynextCursorinstead ofnext_cursor, throughbaukit-core. - Break:
classify_http_statusmaps425 Too EarlytoRetryClass::Unavailableinstead of
Permanent, so it is retryable. RFC 8470 section 5.2 lets a client retry once the request is no
longer sent as early data.
Removed
- Breaking: the
paginationmodule and the root re-exports ofCursor,Page,PageKey,
PageParams,PaginationError,DEFAULT_PAGE_LIMIT, andMAX_PAGE_LIMIT. They moved to
baukit_core::paginationwith no re-export at the old path.From<PaginationError> for ApiError
stays in this crate.
Migration
- Add
baukit-corewithfeatures = ["pagination"]to every crate that imports the pagination
types, and replacebaukit_http::{Cursor, Page, ...}andbaukit_http::pagination::...with
baukit_core::pagination::.... - Delete product middleware that overwrites
Access-Control-Expose-Headersto addRetry-After
orRateLimit-*. Pass any other product headers towith_additional_exposed_headers. - Delete product middleware that sets
Cache-Control: private, no-storeorno-storeon every
response. Routes that need a different policy setCache-Controlin the handler, which wins. - Apply
finalizeafter authentication and rate-limit layers so their rejections carry CORS
headers. - Remove
etag,location, andif-matchfrom productwith_additional_exposed_headersand
with_additional_allowed_headerscalls; duplicates are ignored, so leaving them is harmless. - Replace product
If-Matchparsers and ETag formatters withRevisionEtag, keeping the product's
prefix. Clients see 400invalid_if_match, 412precondition_failed, and 428
precondition_requiredin place of product-specific codes. - Remove
idempotency-keyfrom productwith_additional_allowed_headerscalls. Replace product
Idempotency-Keyparsers with anIdempotencyKeyRulewhose bounds keep every key clients send
today, and map key reuse toIdempotencyError::Reused. Clients see 400
idempotency_key_required, 400invalid_idempotency_key, and 409idempotency_key_reusedin
place of product-specific codes. - Clients that stop on
RetryClass::Permanentnow retry a425. Clients that schedule their own
retries fromRetryClass::RetryAftershould setwith_max_retry_after.
baukit-integrations
Changed
- Break:
ConnectionStatusserializes camelCase field names:last_success_at,
last_attempt_at,last_error_code,next_retry_at, andfailed_attemptsare now
lastSuccessAt,lastAttemptAt,lastErrorCode,nextRetryAt, andfailedAttempts.
ConnectionHealthvalues stay snake_case. Stored JSON written by an older release no
longer deserializes.
baukit-jobs
Changed
- Breaking:
JobStore::claimtakes ajob_types: &[&str]argument after
worker_idand claims only pending rows or expired leases whosejob_typeis
in that set. An empty set, or a blank or overlong entry, returns
StoreError::InvalidInput. Custom stores must add the parameter and apply the
filter; direct callers ofPostgresJobStore::claimmust pass the types they
handle. WorkerRunnerpasses its handler'sjob_types()to every claim. Runners with
disjoint handlers can share one outbox. A type that no running handler
declares stays pending and shows inworker_queue_oldest_age_seconds.JobStore::readyonPostgresJobStoreprobes the filtered claim shape.
Added
- Added
migrations/0003_baukit_jobs_claim_by_type.sqland
POSTGRES_MIGRATION_0003_SQL, which rebuildjob_outbox_claim_idxon
(job_type, run_after, created_at, id)for pending rows.
Migration
- Add the 0003 migration after the existing baukit-jobs migrations. Without it,
claims stay correct but scan pending rows of other types. - A worker whose handler did not declare every type it expected to run now
leaves the undeclared types pending. Add them tojob_types()or start a
runner whose handler declares them.
baukit-openapi
Added
ErrorResponseRulesdocuments error responses and response headers from product rules. A
status rule pairs anOperationCondition(Always,Secured,HasRequestBody,
HasPathParameter,UnsafeMethod,HasIdempotencyKey) with a status and a description, and
adds a response with the shared error envelope where the operation lacks that status. A header
rule adds a header to the responses aResponseSelectorpicks.standard_headers()adds
X-Request-Id,Retry-Afteron 429, andWWW-Authenticateon 401.applycovers every
operation andapply_wheretakes a path and method filter.REQUEST_ID_HEADER,
RETRY_AFTER_HEADER,WWW_AUTHENTICATE_HEADER,request_id_header,retry_after_header, and
www_authenticate_headerare public.document_if_matchdocuments a revision precondition on an operation: a required or optional
If-Matchheader parameter, plus 400, 412, and, when required, 428 error responses with the
shared envelope.document_etagadds theETagheader to inline 2xx responses.
if_match_parameter,etag_header,IfMatchRequirement,IF_MATCH_HEADER,ETAG_HEADER,
PRECONDITION_REQUIRED_CODE,PRECONDITION_FAILED_CODE, andINVALID_IF_MATCH_CODEare public.- Add a camelCase naming check.
check_camel_case_names(&openapi, exemptions)returns
SchemaErrorwith oneNamingViolation(JSON pointer, name,NameKind) per property or
path or query parameter name that is not lower camelCase, andassert_camel_case_names
panics with the list.find_naming_violationsruns the same walk on aserde_json::Value,
andis_camel_caseexposes the rule. The walk skips enum and const values, defaults,
examples, discriminator mappings,x-extensions, and header and cookie parameters. The
exemption list is for standard-defined names such as OAuth 2.0access_token.
Changed
- Break:
ErrorBodyserializesrequest_idasrequestId. The documented error envelope is
now{ "error": { "code", "message", "requestId", "details" } }. - Break:
SchemaErrorKindgains theNamingvariant. Exhaustive matches must handle it.
baukit-ops
- No component-specific changes in this train.
baukit-push
Added
- Add the
DeviceRegistryport withregister,rotate,unregister,
list_for_owner,invalidate, anderase_owner, plus the provided
invalidate_dead_tokens, which removes everyDeviceNotRegisteredtoken
from a send's outcomes in one call. A token registered again after the send
survives. Registering a token another owner holds moves it. Each owner keeps
at mostDEFAULT_DEVICES_PER_OWNER(10) devices; a registration over the cap
evicts the least recently registered, tie-broken bycreated_atand then the
token, and never the device being registered.rotateremoves the
predecessor first, so it never evicts another device. - Add
DeviceToken, whoseDebugoutput is redacted,DeviceTimeZone,
DevicePlatform,DeviceRegistration,RegisteredDevice,
RegistrationOutcome,dead_tokens,PushValidationError,
PushStoreError,PushStoreFuture,MAX_DEVICE_TOKEN_LENGTH(512), and
MAX_DEVICE_TIME_ZONE_LENGTH(64). No error text carries a token. - Add the
DeliveryClaimStoreport withclaimandrelease, keyed by
DeliveryClaim(owner, local date,DeliveryKind), for scheduled senders.
It is separate from the registry, so event-driven senders ignore it. - Add the
sqlx-postgresfeature withPostgresDeviceRegistryover
POSTGRES_PUSH_DEVICES_MIGRATION_SQLandPostgresDeliveryClaimStoreover
POSTGRES_PUSH_DELIVERY_CLAIMS_MIGRATION_SQL. Both constants are available
without the feature. Registration runs under a per-owner advisory lock, so
concurrent registrations cannot overshoot the cap. Add
erase_owner_push_devices,erase_owner_delivery_claims, and the batched
purge_delivery_claims. - Add
MemoryDeviceRegistryandMemoryDeliveryClaimStorebehind
test-support. baukit-pushnow depends onchronoanduuid.- Add deferred receipt polling.
PushTicketId(1 to
MAX_PUSH_TICKET_ID_LENGTH(128) bytes of visible ASCII) names an accepted
notification. ThePushReceiptSourceport returns settledPushReceipt
values per ticket, andExpoPushSenderimplements it, splitting requests at
MAX_RECEIPT_BATCH_SIZE(1000, Expo's limit). ThePendingReceiptStoreport
recordsPendingReceiptvalues (ticket, token,sent_at), takes a bounded
due batch and moves it back byRECEIPT_POLL_DELAY(15 minutes), deletes
settled tickets, and purges tickets older thanRECEIPT_RETENTION(24
hours). Its providedrecord_acceptedrecords every accepted outcome of a
send.poll_pending_receiptstakes one due batch, fetches receipts,
invalidatesDeviceNotRegisteredtokens with each send's ownsent_at,
deletes the settled tickets, and returns aReceiptPoll; failures are a
ReceiptPollError.accepted_receiptsis the filter behind
record_accepted. There is no scheduler; products drive the poll from their
job runner. - Add
PostgresPendingReceiptStorebehindsqlx-postgresover
POSTGRES_PUSH_PENDING_RECEIPTS_MIGRATION_SQL, andMemoryPendingReceiptStore
behindtest-support.FakePushSenderimplementsPushReceiptSourceand
gainssettle_receiptandreceipt_requests.
Changed
- Break:
PushMessage::tokenandPushOutcome::tokenareDeviceToken
instead ofString, so theirDebugoutput redacts the token.
PushMessage::newtakes aDeviceToken.FakePushSender::rejectand
accept_without_receipttake aDeviceToken, and
FakePushSender::dead_tokensreturnsVec<DeviceToken>.dead_tokensno
longer skips invalid tokens, since an outcome can no longer hold one. - Break:
PushDeliveryStatus::Acceptedcarries the provider's
PushTicketId. - Break:
PushValidationErrorgains theTicketIdvariant. - Break:
FakePushSender::fail_withalso fails receipt requests.
baukit-ratelimit
Changed
- Break: the rejection detail
retry_afteris nowretryAfter. TheRetry-Afterheader is
unchanged.
baukit-runtime
- No component-specific changes in this train.
baukit-sync
Added
- Add the
sqlx-postgresfeature. It carries every SQLx helper, so a crate that only needs the
hybrid logical clock no longer compiles SQLx,chrono, oruuid. - Add
horizon::check_pull_cursor,PullCursorError, and theresync_requiredwire constants
(RESYNC_REQUIRED_STATUS,RESYNC_REQUIRED_CODE,HORIZON_REVISION_DETAIL,
FULL_RESYNC_CURSOR). These need no feature.HORIZON_REVISION_DETAILis the camelCase
horizonRevision, so the stale-cursor detail isdetails.horizonRevisionon the wire. - Add
purge::TombstoneTable,purge_tombstone_batch,purge_tombstones,purge_horizon, and
guard_pull_cursor. A batch deletes at most the caller's limit of expired tombstones and raises
each affected owner's horizon in the same transaction, never lowering it. It skips owners whose
revision counter row is locked instead of waiting. The pull guard locks the counter row with
FOR KEY SHARE, so a purge for that owner cannot commit while the pull transaction is open. - Add the reference migration
migrations/0002_baukit_sync_purge_horizons.sqland
POSTGRES_PURGE_HORIZONS_MIGRATION_SQLfor thesync_purge_horizonstable.
Breaking
next_revision,ensure_owner,current_revision, andcurrent_revision_for_updatenow need
features = ["sqlx-postgres"].chrono,sqlx, anduuidare optional dependencies.- The purge helpers need
sync_purge_horizons. Its foreign key referencessync_revisions, so a
migration that dropssync_revisionsmust dropsync_purge_horizonsfirst.
baukit-telemetry
- No component-specific changes in this train.
baukit-test
Added
- Publish
fixtures/webhooks/signature-v1.json, signing and verification vectors for the
baukit-webhook-v1HMAC-SHA256 signature. A test runs them against
webhook_signing_input,sign_webhook_hmac_sha256, andverify_webhook_hmac_sha256. - Add
PostgresTestOptionsto start the PostgreSQL fixture with another image
and tag, a login app role without superuser orBYPASSRLS, and migrations.
start_postgresandstart_postgres_with_migrationskeep their signatures
and now delegate to it. - Add
PostgresTestContainer::create_databaseandPostgresTestDatabasesfor
one database per test on a shared container or on an external server such as
DATABASE_URL. EachPostgresTestDatabasedrops its database when it drops. - Add
MockOidcServer::jwks_urlfor verifiers built withfrom_jwks_uri, and
MockOidcServer::mint_with_key_idto sign with the active key under an
unpublishedkid. Document the existingjwks_request_countand
set_jwks_delayfor JWKS cache tests. - Add
check_purge_horizon_conformanceandassert_purge_horizon_conformancewith the
PurgeHorizonAdaptertrait,PurgeHorizonPull, andPullPause. The check covers horizon
monotonicity, batch limits, a purge racing an open pull, cursors at and below the horizon, owner
isolation, and erasure. - Re-export
assert_openapi_camel_caseandcheck_openapi_camel_casefrombaukit-openapi. - Add
check_replay_safe_mutation_conformanceandassert_replay_safe_mutation_conformancewith
theReplaySafeMutationAdaptertrait,ReplayRequest,ReplayOperation,ReplayOutcome,
ReplaySnapshot,CommitCheckpoint,InjectedRollback,ReplayConformanceInputs, and
ReplayConformanceError. The check covers a lost response, equivalent and changed input,
simultaneous requests with one key, owner and operation isolation, rollback before commit, a
crash after commit, expiry, bounded cleanup, and erasure. - Add
assert_response_matches_openapiandcheck_response_matches_openapiwith
ObservedResponseandOpenApiResponseError. They check a real response's status, media type,
and JSON body against its operation in a serialized OpenAPI 3.1 document, using JSON Schema
2020-12 with format validation. The crate now depends onjsonschema0.58.1 without default
features.
Changed
-
InMemoryApiTokenStorestoresApiTokenRecord::grantson the token and
keeps the latestlast_used_atwhen touches arrive out of order. -
Break:
PostgresTestErrorgainsInvalidAppRole,InvalidConnectionUrl,
andSetupvariants. Exhaustive matches on it must handle them. -
Break:
check_auth_router_conformancenow expects the
error_description="expired"anderror_description="invalid"bearer
challenges thatbaukit-authsends. -
With
sqlx-postgres, the container fixture now waits until PostgreSQL
accepts a connection before it returns. -
Break:
check_auth_router_conformanceexpectsrequestIdin the unauthenticated error
envelope instead ofrequest_id.
TypeScript packages
a11y-core
Minor Changes
-
868b67e: Add headless menu navigation.
useRovingMenu({ active, options })gives an action menu one tab stop on an enabled item, arrow-key movement that skips disabled items and wraps, and Home and End. It returnsitemProps(index),activeIndex, and a stableinitialFocusRefto pass touseOverlayA11y, which keeps Escape, Tab containment, and focus restoration. With no enabled item there is no item tab stop and the overlay focuses its first focusable control. When the active item is removed or disabled while open, the tab stop moves to the item now at that position, or back to the initial item; focus follows if it fell to the document body. Keys from hosts nested inside an item are ignored. The purenextEnabledMenuIndex(key, currentIndex, options)is exported for container-level key handlers. Both are available from the package root and from@baukit/a11y-core/web.useOverlayA11ynow accepts anyHostRefasinitialFocusRef, matchinguseFocusTrap. ExistingRefObject<View | null>values still type-check.No breaking changes.
-
8d268e1: Add a
defaultexport condition next toimporton every export except the ESM-only./vitestsubpaths. Jest and other CommonJS-condition resolvers now find@baukit/*without amoduleNameMapper. Each package'stestscript packs the package and resolves every export underrequireconditions from the archive. -
f249d7c: Add
focusAccessibilityElement(ref)to the package root. On native it resolves the view tag withfindNodeHandleand callsAccessibilityInfo.setAccessibilityFocus. On web it focuses the element withpreventScroll. It returns false when there is nothing to focus.useOverlayA11ynow shares its view tag lookup.The mobile template's
useRouteHeadingFocusnow moves native screen-reader focus to the route heading one frame after the route gains focus. It previously did nothing on native.No breaking changes.
-
Release the coordinated baukit 0.5.0 train.
analytics-core
Minor Changes
-
4f4a873: Add exact-key scrubbing and
scrubErrorEventfor crash reports.scrubPropertiesandAnalyticsClientacceptexactBlockedKeys, which redact a key only when the normalized key is equal.scrubErrorEventredacts request headers, bodies, query strings, breadcrumb data, and frame variables, and keeps event and trace IDs and stack frame locations. Break:ip,ip_address,remote_addr,x_forwarded_for, andx_real_ipare now redacted by default throughDEFAULT_EXACT_BLOCKED_KEYS. -
bc05828: Add
HydratedAnalyticsStorageto the new@baukit/analytics-posthog-native/storagesubpath. It loads a list of keys from an asynchronous store such as AsyncStorage once, then serves the synchronousAnalyticsStorageport from memory and writes those keys through. A key that fails to read starts absent without dropping the others. The subpath does not importposthog-react-native.Add
analyticsStorageKeys(prefix)to@baukit/analytics-core. It returns the consent, anonymous ID, user ID, and alias guard keys thatAnalyticsClientuses.posthog-react-nativeis now an optional peer of@baukit/analytics-posthog-native. Apps that use the transport must install it themselves, which the setup instructions already said.The mobile template now calls
HydratedAnalyticsStorageinstead of generating its own class. Products that copied the template class can delete it. -
8d268e1: Add a
defaultexport condition next toimporton every export except the ESM-only./vitestsubpaths. Jest and other CommonJS-condition resolvers now find@baukit/*without amoduleNameMapper. Each package'stestscript packs the package and resolves every export underrequireconditions from the archive. -
Release the coordinated baukit 0.5.0 train.
analytics-posthog-native
Minor Changes
-
bc05828: Add
HydratedAnalyticsStorageto the new@baukit/analytics-posthog-native/storagesubpath. It loads a list of keys from an asynchronous store such as AsyncStorage once, then serves the synchronousAnalyticsStorageport from memory and writes those keys through. A key that fails to read starts absent without dropping the others. The subpath does not importposthog-react-native.Add
analyticsStorageKeys(prefix)to@baukit/analytics-core. It returns the consent, anonymous ID, user ID, and alias guard keys thatAnalyticsClientuses.posthog-react-nativeis now an optional peer of@baukit/analytics-posthog-native. Apps that use the transport must install it themselves, which the setup instructions already said.The mobile template now calls
HydratedAnalyticsStorageinstead of generating its own class. Products that copied the template class can delete it. -
8d268e1: Add a
defaultexport condition next toimporton every export except the ESM-only./vitestsubpaths. Jest and other CommonJS-condition resolvers now find@baukit/*without amoduleNameMapper. Each package'stestscript packs the package and resolves every export underrequireconditions from the archive. -
Release the coordinated baukit 0.5.0 train.
Patch Changes
- Updated dependencies [4f4a873]
- Updated dependencies [bc05828]
- Updated dependencies [8d268e1]
- Updated dependencies
- @baukit/analytics-core@0.5.0
analytics-posthog-web
Minor Changes
- 8d268e1: Add a
defaultexport condition next toimporton every export except the ESM-only./vitestsubpaths. Jest and other CommonJS-condition resolvers now find@baukit/*without amoduleNameMapper. Each package'stestscript packs the package and resolves every export underrequireconditions from the archive. - Release the coordinated baukit 0.5.0 train.
Patch Changes
- Updated dependencies [4f4a873]
- Updated dependencies [bc05828]
- Updated dependencies [8d268e1]
- Updated dependencies
- @baukit/analytics-core@0.5.0
api-runtime
Minor Changes
-
4793121: Move wire names to camelCase to match the Baukit HTTP APIs.
Breaking changes:
@baukit/api-runtime:parseApiErrorEnvelopereads onlyerror.requestId. An envelope that carriesrequest_idand norequestIdis no longer anApiError.ApiErrorEnvelope.error.request_idis nowrequestId.@baukit/events:EventEnvelopeSchemaandEventEnvelopeuseeventId,userId,occurredAt,sourceApp, andschemaVersioninstead ofevent_id,user_id,occurred_at,source_app, andschema_version, and still describe schema version 1.IngestOutcomeSchemausesledgerEntryIdinstead ofledger_entry_id.EventPayloadSchemaaccepts camelCase keys (/^[a-z][a-zA-Z0-9]{0,63}$/) and rejects snake_case keys.@baukit/sync-client:toSnakeCaseSnapshot,toSnakeCaseFailure,SnakeCaseSyncStatusSnapshot, andSnakeCaseSyncFailureare removed. Read the camelCaseSyncStatusSnapshotdirectly. The documentedresync_requireddetail isdetails.horizonRevisionand the pull page flag ishasMore.
-
bb0121b: Add keyed mutation support.
/idempotencyis a new entry point, not re-exported from the package root.createIdempotencyKeyStorekeeps oneIdempotency-Keyper account, operation, and body, compared by canonical JSON, until the outcome is definite orttlMspasses. Keys default tocrypto.randomUUID()and in-memory storage;storagetakes anIdempotencyKeyStorageport to keep them across reloads.classifyMutationStatusandclassifyMutationErrorreturncommitted,not-committed, orpossibly-committed. Network errors, aborts, 408, 429, 5xx, 409idempotency_key_in_progress, and unknown throws are possibly committed and keep the key.sendIdempotentMutationgets the key, sends, and settles it.canonicalJsonandIDEMPOTENCY_KEY_IN_PROGRESS_CODEare exported too.The package root exports
IDEMPOTENCY_KEY_HEADER.RetryableMethodnow includesPOSTandPATCH, andcreateApiFetchretries them only whenretry.methodsnames them and the request carriesIdempotency-Key. UnkeyedPOSTandPATCHstill never retry.Break: before,
retry.methodssilently droppedPOSTandPATCH. A caller that already listed them now gets retries for keyed requests. Remove them frommethodsto keep the old behavior. -
8d268e1: Add a
defaultexport condition next toimporton every export except the ESM-only./vitestsubpaths. Jest and other CommonJS-condition resolvers now find@baukit/*without amoduleNameMapper. Each package'stestscript packs the package and resolves every export underrequireconditions from the archive. -
Release the coordinated baukit 0.5.0 train.
auth-native
Minor Changes
-
8d268e1: Add a
defaultexport condition next toimporton every export except the ESM-only./vitestsubpaths. Jest and other CommonJS-condition resolvers now find@baukit/*without amoduleNameMapper. Each package'stestscript packs the package and resolves every export underrequireconditions from the archive. -
Release the coordinated baukit 0.5.0 train.
-
118e055: Add a state decoration for themed login pages.
NativeOidcClient.signIn({ stateDecoration })forwards leading state segments to the browser flow through the new optionalAuthorizationRequest.stateDecoration.appearanceStateDecoration({ mode, primaryColor?, secondaryColor? })buildsap1.<d|l|s>[.<PRIMARY>.<SECONDARY>]segments anddecoratedAuthorizationState(segments, entropy)joins them with a hex nonce of at least 32 random bytes.@baukit/auth-native/expoexportscreateExpoBrowserFlow({ randomBytes }), the AuthSession browser flow thatcreateExpoOidcEnvironmentalready used. WithrandomBytesit puts the decoration in front of a random nonce. WithoutrandomBytes, or when the entropy source or a segment fails, it keeps AuthSession's own state.ExpoOidcEnvironmentOptionsnow acceptsrandomBytes.No breaking changes.
auth-node
Minor Changes
-
98f44a8: Add
parseApiOrigin(value, { allowLoopbackHttp?, label? })andApiOriginErrorto the package root. The parser trims the value, accepts one trailing slash, and returns the URL's origin. It rejects credentials, a path, a query, or a fragment, and it allows plain HTTP only on a loopback host whenallowLoopbackHttpis set. Errors carry areasonofinvalid_url,insecure_scheme, ornot_an_originand never include the value. The device flow now uses the same scheme and loopback check.The generated MCP template now always depends on
@baukit/auth-nodeand validates its API URL withparseApiOrigin.No breaking changes to the package. The CLI doctor now requires
@baukit/auth-nodein every generatedmcp/package.json. -
8d268e1: Add a
defaultexport condition next toimporton every export except the ESM-only./vitestsubpaths. Jest and other CommonJS-condition resolvers now find@baukit/*without amoduleNameMapper. Each package'stestscript packs the package and resolves every export underrequireconditions from the archive. -
Release the coordinated baukit 0.5.0 train.
auth-web
Minor Changes
- 8d268e1: Add a
defaultexport condition next toimporton every export except the ESM-only./vitestsubpaths. Jest and other CommonJS-condition resolvers now find@baukit/*without amoduleNameMapper. Each package'stestscript packs the package and resolves every export underrequireconditions from the archive. - Release the coordinated baukit 0.5.0 train.
data-contracts-dexie
Minor Changes
- 8d268e1: Add a
defaultexport condition next toimporton every export except the ESM-only./vitestsubpaths. Jest and other CommonJS-condition resolvers now find@baukit/*without amoduleNameMapper. Each package'stestscript packs the package and resolves every export underrequireconditions from the archive. - Release the coordinated baukit 0.5.0 train.
Patch Changes
- Updated dependencies [8d268e1]
- Updated dependencies [acaab1b]
- Updated dependencies
- Updated dependencies [d9225d5]
- Updated dependencies [a9fa22e]
- @baukit/data-contracts@0.5.0
data-contracts-expo-sqlite
Minor Changes
-
f57396c: Add the
@baukit/data-contracts-expo-sqlite/testingentry point for Node unit tests on the built-innode:sqlite(Node 24 or later, no new dependency).NodeSqliteDatabaseimplements the Expo statement methods this adapter calls, on a file path it owns or one you pass in. Exclusive transactions open a second connection to the same file, as on the device, so an overlapping root write fails withdatabase is locked, and foreign keys stay off on every connection.createSqliteMigrationConformanceTests(adapter)returns framework-neutral cases that check a product migration runner for fresh installs, upgrades, restarts, complete rollback of a failed step, and refusal of a database migrated by a newer build.Export
ExpoSqliteDatabase, the part of an ExpoSQLiteDatabasethe adapter calls. TheExpoSqliteStoreconstructor now accepts that type instead ofSQLiteDatabase, so an Expo database still fits.No breaking changes.
-
c795f17: Serialize Expo SQLite operations per database file. Root record, key/value, and schema-metadata calls,
initialize(),withTransaction(), andclose()now run one at a time in call order on one queue shared by every adapter store on the samedatabasePath, so a root statement can no longer overlap an exclusive transaction and fail withdatabase is locked. Transaction-context work stays on the transaction connection. Lock errors still reach the caller unchanged; nothing is retried.Breaking changes:
SqliteRecordStore,SqliteKeyValueStore, andSqliteSchemaMetadataStoreconstructors take(database, namespace)only. The thirdassertAvailableargument is removed, anddatabasemust exposedatabasePath(an ExpoSQLiteDatabasedoes).ExpoSqliteStore.records,.keyValues, and.schemaMetadataare typed as theRecordStore,KeyValueStore, andSchemaMetadataStorecontracts. Their per-storeinitialize()is no longer part of the public type; callExpoSqliteStore.initialize().- A transaction accepted before
close()now runs before the adapter closes instead of failing withstorage_closed. close()returns the same promise to every caller. A failed close keeps rejecting with the same error instead of resolving on the next call.- A root call made from inside a
withTransactioncallback on a store for the same file now waits forever instead of failing withdatabase is locked. Use the transaction context inside the callback. - A product wrapper that queued root operations and transactions around the adapter becomes redundant; remove it after pinning this release.
-
d4aca9d: Add
ExpoSqliteConnection, a raw SQL connection for a product's own schema and migration runner.exec,run,get, andallbind parameters asexpo-sqlitedoes. Root statements,transaction(work), andclose()share the per-file queue withExpoSqliteStoreand the root stores, so a root statement issued during another caller's transaction runs after it instead of joining it.transaction(work)runsBEGIN IMMEDIATEon the supplied handle, commits whenworkresolves, and rolls back whenworkor the commit rejects. The transaction context'stransaction()always rejects with aTypeError, and its statements reject withstorage_closedonce it settles. The connection runsPRAGMA foreign_keys = ONbefore its first statement, so foreign keys stay enforced inside transactions.close()closes the handle only with{ closeDatabase: true }. New types:ExpoSqliteConnectionOptions,SqliteStatements, andSqliteTransaction.ExpoSqliteStorenow shares its queueing, close, and transaction-context code with the connection. Its behavior is unchanged.Behavior change for code that shares a handle: the first statement of an
ExpoSqliteConnectionturns foreign keys on for that handle, soExpoSqliteStoreand root stores on the same handle run with foreign keys on from then on. The adapter's own tables have no foreign keys.No breaking changes.
-
8d268e1: Add a
defaultexport condition next toimporton every export except the ESM-only./vitestsubpaths. Jest and other CommonJS-condition resolvers now find@baukit/*without amoduleNameMapper. Each package'stestscript packs the package and resolves every export underrequireconditions from the archive. -
Release the coordinated baukit 0.5.0 train.
Patch Changes
- Updated dependencies [8d268e1]
- Updated dependencies [acaab1b]
- Updated dependencies
- Updated dependencies [d9225d5]
- Updated dependencies [a9fa22e]
- @baukit/data-contracts@0.5.0
data-contracts
Minor Changes
-
8d268e1: Add a
defaultexport condition next toimporton every export except the ESM-only./vitestsubpaths. Jest and other CommonJS-condition resolvers now find@baukit/*without amoduleNameMapper. Each package'stestscript packs the package and resolves every export underrequireconditions from the archive. -
acaab1b: Add
parseLimitsPolicy,LimitsPolicyError,LimitError, andenforceLimitto/limits.parseLimitsPolicy(value, schema)validates a limits policy file against a schema that names the version, the keys of each section, and the keys allowed to be zero. It returns a result typed from the schema.enforceLimit(field, reason, check)turnsLimitExceededErrorintoLimitErrorwithreason,field,measured, andallowed.The web and mobile templates now call these instead of generating their own parser. Products that copied the template's
parseLimitsPolicy,LimitsPolicyError, andLimitErrorcan delete them and declare a schema.LimitErrorgainsmeasuredandallowed, and invalid counts now throw the package'sRangeErrormessage (measured must be a non-negative safe integer) instead of the generated<field> count must be a non-negative integer.No breaking changes to existing package exports.
-
Release the coordinated baukit 0.5.0 train.
-
d9225d5: Add two opt-in entry points, neither re-exported from the package root and neither depending on React.
/revisioned-writesaddscreateRevisionedWriteQueue. It sends one write at a time with the last acknowledged revision, keeps edits made during a write as a separate unsent value, and combines queued edits with a callercoalescefunction. The write callback returnsaccepted,conflict, orrejected. A conflict pauses the queue untilreset. A throw means the outcome is unknown: the queue keeps the exact value and revision apart from newer edits, andretrysends them again withafterUnknownOutcome: true.resetfences account and document switches and aborts the in-flight write through the injectedAbortSignal. The snapshot works withuseSyncExternalStore./durable-draftaddscreateDurableDraft, which keeps a form value in anyKeyValueStoreas{ version, value }through a versioned codec that validates unchecked JSON. The snapshot reportsrecovery(none,restored,corrupt,unsupported-version,unavailable),persistence(loading,idle,saving,clearing,failed),dirty, andlocalRevision.cleartakessubmittedwith the confirmed local revision, which keeps newer edits, ordiscarded. A failed deletion stays in the snapshot. Storage failures throwDraftPersistenceErrorwithout keys or draft content.No breaking changes.
-
a9fa22e: Add the
/exportentry point, also re-exported from the package root.encodeCsvwrites RFC 4180 CSV with CRLF record separators and neutralizes spreadsheet formulas by default: a text cell that starts with=,+,-,@, tab, or carriage return, or with=,+,-, or@after leading spaces, tabs, CR, or LF, gets a leading apostrophe. PassneutralizeFormulas: falseto turn this off, andbyteOrderMark: trueto start the output with U+FEFF. Finitenumbercells and{ numeric }cells (built withcsvNumeric) must match the JSON number grammar and are written without neutralization, so-5stays a number. Invalid numeric cells, unpaired surrogates, and unsupported runtime values throwCsvEncodeErrorwith a code, row index, and column index, never the cell value.ShareOutcomeandSHARE_OUTCOMESname the share or save resultsshared,saved,cancelled,unavailable, andfailed; no Expo implementation ships yet.baukit_core::export::encode_csvpasses the same vectors infixtures/export-csv/csv-encoding-v1.json.No breaking changes.
events
Minor Changes
-
4793121: Move wire names to camelCase to match the Baukit HTTP APIs.
Breaking changes:
@baukit/api-runtime:parseApiErrorEnvelopereads onlyerror.requestId. An envelope that carriesrequest_idand norequestIdis no longer anApiError.ApiErrorEnvelope.error.request_idis nowrequestId.@baukit/events:EventEnvelopeSchemaandEventEnvelopeuseeventId,userId,occurredAt,sourceApp, andschemaVersioninstead ofevent_id,user_id,occurred_at,source_app, andschema_version, and still describe schema version 1.IngestOutcomeSchemausesledgerEntryIdinstead ofledger_entry_id.EventPayloadSchemaaccepts camelCase keys (/^[a-z][a-zA-Z0-9]{0,63}$/) and rejects snake_case keys.@baukit/sync-client:toSnakeCaseSnapshot,toSnakeCaseFailure,SnakeCaseSyncStatusSnapshot, andSnakeCaseSyncFailureare removed. Read the camelCaseSyncStatusSnapshotdirectly. The documentedresync_requireddetail isdetails.horizonRevisionand the pull page flag ishasMore.
-
8d268e1: Add a
defaultexport condition next toimporton every export except the ESM-only./vitestsubpaths. Jest and other CommonJS-condition resolvers now find@baukit/*without amoduleNameMapper. Each package'stestscript packs the package and resolves every export underrequireconditions from the archive. -
Release the coordinated baukit 0.5.0 train.
integrations-client
Minor Changes
- 8d268e1: Add a
defaultexport condition next toimporton every export except the ESM-only./vitestsubpaths. Jest and other CommonJS-condition resolvers now find@baukit/*without amoduleNameMapper. Each package'stestscript packs the package and resolves every export underrequireconditions from the archive. - Release the coordinated baukit 0.5.0 train.
localization-core
Minor Changes
-
8d268e1: Add a
defaultexport condition next toimporton every export except the ESM-only./vitestsubpaths. Jest and other CommonJS-condition resolvers now find@baukit/*without amoduleNameMapper. Each package'stestscript packs the package and resolves every export underrequireconditions from the archive. -
Release the coordinated baukit 0.5.0 train.
-
8a31c75: Export the shared-fixture vector checks so the vectors run outside Vitest.
@baukit/localization-core/vectorsexportszonedTimeVectorChecks(fixture)forfixtures/zoned-time/vectors-v1.json, and@baukit/notifications-core/vectorsexportsnotificationPlanVectorChecks(fixture)forfixtures/notifications/plan-vectors-v1.json, with the fixture types. Each check has alabel, anexpectedvalue, and anactual()call whose result must deep-equal it. The packages' own Vitest suites now run these checks, andexamples/expo-notifications-conformanceruns them inside Hermes on an Android emulator.No breaking changes. The root exports are unchanged.
-
cf3a85b: Add
resolveZonedLocalTime({ civilDate, civilTime, timeZone, gap, fold }), which turns a civil date and local time in an IANA zone into an instant. The gap policy (rejectorshiftForward) and the fold policy (earlierorlater) are required and have no default. It returns{ ok: true, epochMilliseconds, transition }or{ ok: false, code }withinvalid_civil_date,invalid_civil_time,invalid_time_zone, ornonexistent_local_time, and throwsRangeErroronly for an unknown policy value. It reads zone rules throughIntland adds no dependency. New exports:GapPolicy,FoldPolicy,LocalTimeTransition,ZonedLocalTime,ZonedLocalTimeCode,ZonedLocalTimeResult,INVALID_CIVIL_TIME_CODE,INVALID_TIME_ZONE_CODE, andNONEXISTENT_LOCAL_TIME_CODE. Shared vectors for this function and for Rust resolvers live infixtures/zoned-time/vectors-v1.json.No breaking changes. A product with its own
localDateTimeToInstantcan replace it withresolveZonedLocalTime({ ..., gap: 'reject', fold: 'earlier' }), which matches the old behavior for valid input, and must then handle the returned error code instead of a thrownError.
notifications-core
Minor Changes
-
ea5c1cf: Add
@baukit/notifications-core, which plans local notifications without a notification library.resolveNotificationOccurrences({ occurrences, timeZone, gap, fold, clock, horizonDays })resolves each{ logicalId, civilDate, civilTime, contentDigest }throughresolveZonedLocalTime, keeps the ones inside the horizon, and reports the rest aspast,outside_horizon, ornonexistent_local_time.planNotificationReplacement(current, desired, { replaceAll })returns deterministickeep,cancel, andschedulesets by logical ID, instant, and content digest.createOwnedNotificationScheduler(platform, { pendingLimit })runs that plan against aNotificationPlatformport for one namespace at a time, touches only requests that carry the namespace's marker and thebaukit:<namespace>:<logicalId>identifier, and returns{ status, kept, cancelled, scheduled, failures }with content-free failure codes. Invalid input throwsNotificationPlanError.InMemoryNotificationPlatform,NotificationPlatformFaultState, and the@baukit/notifications-core/vitestconformance suite let products test their own platform code. Shared vectors live infixtures/notifications/plan-vectors-v1.json.Add
@baukit/notifications-expo, the optionalexpo-notificationsadapter.createExpoOwnedNotificationScheduler(Notifications, { pendingLimit })schedules DATE triggers with the marker undercontent.data.baukitNotification, never callscancelAllScheduledNotificationsAsync, and maps iOS provisional and ephemeral authorization to granted.expo-notifications^57.0.13is a peer dependency and the package imports only its types.No breaking changes. Both packages are new.
-
Release the coordinated baukit 0.5.0 train.
-
8a31c75: Export the shared-fixture vector checks so the vectors run outside Vitest.
@baukit/localization-core/vectorsexportszonedTimeVectorChecks(fixture)forfixtures/zoned-time/vectors-v1.json, and@baukit/notifications-core/vectorsexportsnotificationPlanVectorChecks(fixture)forfixtures/notifications/plan-vectors-v1.json, with the fixture types. Each check has alabel, anexpectedvalue, and anactual()call whose result must deep-equal it. The packages' own Vitest suites now run these checks, andexamples/expo-notifications-conformanceruns them inside Hermes on an Android emulator.No breaking changes. The root exports are unchanged.
Patch Changes
- Updated dependencies [8d268e1]
- Updated dependencies
- Updated dependencies [8a31c75]
- Updated dependencies [cf3a85b]
- @baukit/localization-core@0.5.0
notifications-expo
Minor Changes
-
ea5c1cf: Add
@baukit/notifications-core, which plans local notifications without a notification library.resolveNotificationOccurrences({ occurrences, timeZone, gap, fold, clock, horizonDays })resolves each{ logicalId, civilDate, civilTime, contentDigest }throughresolveZonedLocalTime, keeps the ones inside the horizon, and reports the rest aspast,outside_horizon, ornonexistent_local_time.planNotificationReplacement(current, desired, { replaceAll })returns deterministickeep,cancel, andschedulesets by logical ID, instant, and content digest.createOwnedNotificationScheduler(platform, { pendingLimit })runs that plan against aNotificationPlatformport for one namespace at a time, touches only requests that carry the namespace's marker and thebaukit:<namespace>:<logicalId>identifier, and returns{ status, kept, cancelled, scheduled, failures }with content-free failure codes. Invalid input throwsNotificationPlanError.InMemoryNotificationPlatform,NotificationPlatformFaultState, and the@baukit/notifications-core/vitestconformance suite let products test their own platform code. Shared vectors live infixtures/notifications/plan-vectors-v1.json.Add
@baukit/notifications-expo, the optionalexpo-notificationsadapter.createExpoOwnedNotificationScheduler(Notifications, { pendingLimit })schedules DATE triggers with the marker undercontent.data.baukitNotification, never callscancelAllScheduledNotificationsAsync, and maps iOS provisional and ephemeral authorization to granted.expo-notifications^57.0.13is a peer dependency and the package imports only its types.No breaking changes. Both packages are new.
-
Release the coordinated baukit 0.5.0 train.
Patch Changes
- Updated dependencies [ea5c1cf]
- Updated dependencies
- Updated dependencies [8a31c75]
- @baukit/notifications-core@0.5.0
preferences-core
Minor Changes
- 8d268e1: Add a
defaultexport condition next toimporton every export except the ESM-only./vitestsubpaths. Jest and other CommonJS-condition resolvers now find@baukit/*without amoduleNameMapper. Each package'stestscript packs the package and resolves every export underrequireconditions from the archive. - Release the coordinated baukit 0.5.0 train.
pwa-web
Minor Changes
- Release the coordinated baukit 0.5.0 train.
sync-client
Minor Changes
-
4793121: Move wire names to camelCase to match the Baukit HTTP APIs.
Breaking changes:
@baukit/api-runtime:parseApiErrorEnvelopereads onlyerror.requestId. An envelope that carriesrequest_idand norequestIdis no longer anApiError.ApiErrorEnvelope.error.request_idis nowrequestId.@baukit/events:EventEnvelopeSchemaandEventEnvelopeuseeventId,userId,occurredAt,sourceApp, andschemaVersioninstead ofevent_id,user_id,occurred_at,source_app, andschema_version, and still describe schema version 1.IngestOutcomeSchemausesledgerEntryIdinstead ofledger_entry_id.EventPayloadSchemaaccepts camelCase keys (/^[a-z][a-zA-Z0-9]{0,63}$/) and rejects snake_case keys.@baukit/sync-client:toSnakeCaseSnapshot,toSnakeCaseFailure,SnakeCaseSyncStatusSnapshot, andSnakeCaseSyncFailureare removed. Read the camelCaseSyncStatusSnapshotdirectly. The documentedresync_requireddetail isdetails.horizonRevisionand the pull page flag ishasMore.
-
8d268e1: Add a
defaultexport condition next toimporton every export except the ESM-only./vitestsubpaths. Jest and other CommonJS-condition resolvers now find@baukit/*without amoduleNameMapper. Each package'stestscript packs the package and resolves every export underrequireconditions from the archive. -
Release the coordinated baukit 0.5.0 train.
ui-tokens
Minor Changes
- 8d268e1: Add a
defaultexport condition next toimporton every export except the ESM-only./vitestsubpaths. Jest and other CommonJS-condition resolvers now find@baukit/*without amoduleNameMapper. Each package'stestscript packs the package and resolves every export underrequireconditions from the archive. - Release the coordinated baukit 0.5.0 train.
Commits
- chore: prepare baukit v0.5.0 (
7f617b4) - docs(releasing): count seventeen crates and twenty packages (
ebe3236) - docs(plan): record the follow-up wave's final gate (
16dabdc) - docs(plan): record F7 and start F6 (
7311a91) - merge: F7, auth web coverage, Compose Redis, fixture coverage in CI (
7b33561) - docs(evidence): record the F7 follow-up (
938e0b0) - ci: run test:coverage in the generated fixture job (
45301f9) - feat(templates): cover auth web floors and compose Redis for auth products (
13a1375) - docs(plan): record F3 and add F7 (
ba6f74f) - merge: F3, test type gate, mobile auth coverage, stack e2e port (
adfcc34) - docs(evidence): record the item 19 follow-up (
1bb26df) - feat(templates): enforce mobile auth coverage and parameterize the stack e2e port (
2b76f9c) - test(typescript): type-check every package's tests in its test script (
538edc5) - docs(plan): record F5 and correct the item 15 adoption lines (
bc0c153) - merge: F5, queued raw-SQL ExpoSqliteConnection (
bec2a31) - docs(evidence): record the queued raw-SQL connection follow-up (
d7cedcd) - feat(data-contracts-expo-sqlite): add queued raw-SQL ExpoSqliteConnection (
d4aca9d) - docs(plan): record F4 (
b1eacd4) - merge: F4, Hermes vectors and notifications adapter on the emulator (
2eb0ca9) - test(notifications): run the vectors and the Expo adapter on an Android emulator (
8a31c75) - docs(plan): record F1 (
0c2e6b0) - merge: F1, push token redaction and deferred receipt polling (
00c9cc0) - docs(evidence): record the push follow-up for token redaction and receipt polling (
19dfdff) - feat(push): redact tokens in messages and outcomes, poll deferred receipts (
5db83b7) - docs(plan): start F5 (
97de48e) - docs(plan): record F2 (
5f11625) - docs(evidence): mark the Retry-After cap and 425 decisions in the webhook study (
8edc3f7) - merge: F2, 425 retry, Retry-After cap, release-plz coverage (
d48b2cb) - docs(core): build the media grant key ring once and rotate by restart (
d2f1848) - build(release): list every crate in release-plz and enforce it (
78e7ed2) - fix(http): retry 425 and cap Retry-After in the egress client (
939d1e5) - docs(plan): add the follow-up wave with recommended decisions (
c44a52a) - docs(plan): record the final gate (
8cf5c4b) - docs(plan): record item 19 (
5321f10) - docs: build analytics-posthog-native and auth-node before fixture checks (
e70ea6a) - merge: item 19, template code into packages (
a85adf6) - docs(evidence): record item 19 template code moves (
b35dd58) - feat(auth-node): add parseApiOrigin and validate the MCP API URL with it (
98f44a8) - feat(templates): add Keycloak stack e2e helpers to the web auth template (
8b2156e) - feat(auth-native): add a themed Expo browser flow with a state decoration (
118e055) - docs(plan): record the integrated gate on 525a55b (
3a6e5f2) - feat(a11y-core): add focusAccessibilityElement for native route focus (
f249d7c) - feat(analytics-posthog-native): add HydratedAnalyticsStorage (
bc05828) - docs: refresh CLAUDE.md layout and make ci coverage (
f78e1a2) - docs(plan): record item 18 and the live-row cap fix (
c2c1760) - merge: item 18, signed media grants (
27cacb8) - test(test): make the live-row cap race overlap deterministic with a read barrier (
3f7921c) - docs(evidence): record signed media grants as evidence 54 (
76e79be) - feat(deploy): add njs media grant verifier (
5d99155) - feat(core): add signed media grants (
acfe19e) - feat(data-contracts): add limits policy parser and LimitError (
acaab1b) - docs(plan): record item 16 (
d3016ea) - merge: item 16, guarded outbound HTTP client and webhook study (
8a06044) - docs(evidence): record guarded egress and the webhook delivery study as evidence 52 (
9d1f083) - test(test): publish baukit-webhook-v1 signature vectors (
c3e3196) - feat(egress): add guarded outbound HTTP client with published address vectors (
19385d5) - docs(plan): record item 8 (
525a55b) - merge: item 8, OpenAPI error responses and a compatibility check (
1e54427) - docs(evidence): record OpenAPI error responses as evidence 44 (
8ac884c) - feat(templates): document error responses and opt-in compatibility gate (
cce8ebe) - feat(test): add assert_response_matches_openapi (
0f84f37) - feat(scripts): add OpenAPI compatibility check (
6072315) - feat(openapi): add rule-driven error-response decorator (
1a1615b) - docs(plan): record item 7, start item 19 (
2c8fefb) - merge: item 7, replay-safe HTTP mutations (
ea0bdf6) - docs(evidence): record replay-safe mutations as evidence 43 (
5305a44) - feat(api-runtime): add idempotency key store and keyed POST retries (
bb0121b) - feat(http): add Idempotency-Key parser with caller bounds (
465f977) - feat(test): add replay-safe mutation conformance check (
208e649) - docs(plan): record item 17, start item 18 (
523a233) - merge: item 17, push device registry (
5e73a83) - docs(evidence): record push device registry as evidence 53 (
919aece) - feat(push): add device registry and daily delivery claims (
13602b2) - docs(plan): start item 16 (
dc5792a) - docs(plan): record item 15 and the integrated check (
b25875c) - merge: item 15, SQLite migration conformance and Node test driver (
aead6ca) - docs(evidence): record SQLite migration conformance and Node test driver as evidence 51 (
620696f) - feat(data-contracts-expo-sqlite): add node:sqlite test driver and migration conformance cases (
f57396c) - test(test): allow extra serialization retries in the live-row cap comparison (
65c899f) - docs(plan): record item 10, start item 17 (
a5227a7) - merge: item 10, PostgreSQL API token store with grants (
78e0d7c) - docs(evidence): record API token store with grants as evidence 46 (
2112608) - feat(auth): add PostgreSQL API token store with grants and verified scope claims (
bcaa9f4) - docs(plan): record item 5, start item 8 (
240bd13) - merge: item 5, camelCase HTTP APIs (
0f72840) - docs(plan): start item 7 (
aea2a56) - docs(plan): record item 6 (
9bc9adb) - merge: item 6, strong-ETag revision preconditions (
b0570ae) - docs(evidence): add strong-ETag revision precondition evidence (
703515f) - feat(http): add strong-ETag revision preconditions (
257bc85) - feat(openapi): document If-Match revision preconditions and ETag headers (
2cde22a) - test(fixtures): add strong-ETag revision precondition vectors (
92b074e) - docs(plan): start item 15 (
9555432) - docs(plan): record item 12 (
2f33e05) - fix(notifications): add default export conditions and the packed-export check (
6191e7f) - docs(evidence): record the horizonRevision reconciliation in note 41 (
f12ad8d) - docs(evidence): record camelCase HTTP APIs as note 41 (
546c5e4) - feat!: move HTTP and event wire names to camelCase (
4793121) - merge: item 12, local notification planning and owned Expo replacement (
c8fcdf1) - docs(evidence): add local notification planning evidence (
638bef9) - feat(notifications): plan local notifications and replace only owned requests (
ea5c1cf) - test(fixtures): add local notification plan vectors (
c7852df) - docs(plan): start item 10 (
c2c29e9) - docs(plan): record item 9 (
9cbdf55) - merge: item 9, server-side tombstone purge horizons (
61391d1) - docs(evidence): record server purge horizons as evidence 45 (
76c909b) - feat(sync)!: add tombstone purge horizons behind the sqlx-postgres feature (
d922948) - feat(test): add tombstone purge-horizon conformance check (
46b0bb4) - fix(deps): update rustls to 0.23.45 for RUSTSEC-2026-0285 (
e617a56) - docs(plan): start item 6 (
7ee710e) - docs(plan): record item 14 (
845e5f7) - fix(data-contracts): add default conditions to the write queue and draft exports (
8a47f35) - merge: item 14, revisioned writes and durable drafts (
76b4fb0) - docs(evidence): add revisioned writes and drafts evidence (
d260611) - feat(data-contracts): add revisioned write queue and durable draft entry points (
d9225d5) - docs(plan): record wave 1 verification (
6bb19da) - fix(deps): bump rustls to 0.23.45 for RUSTSEC-2026-0285 (
26ff262) - docs(plan): start item 5 (
569b376) - docs(plan): record item 4, wave 1 complete (
b430d27) - docs(evidence): renumber consumer friction evidence to 40 (
d7f1a49) - merge: item 4, consumer friction fixes (
964ac54) - docs(plan): start item 12 (
c13d76c) - docs(plan): record item 11 (
b9014d3) - merge: item 11, zoned local times and calendar recipe (
c7c616f) - docs(plan): start item 14 (
f4fa786) - docs(platform): publish the calendar export recipe and item 11 evidence (
c27621b) - feat(localization-core): resolve zoned local times with required DST policies (
cf3a85b) - docs(plan): record item 20 (
7c527bb) - merge: item 20, safe export encoder (
671dae2) - docs(evidence): add safe export encoder evidence (
83f275f) - feat(core): add CSV export encoder (
6057274) - feat(data-contracts): add export entry point with CSV encoder and share outcome (
a9fa22e) - test(fixtures): add CSV export vectors (
8c59754) - docs(plan): record item 13 and start item 9 (
45a1e32) - merge: item 13, headless menu navigation (
b110d52) - docs(evidence): record headless menu navigation as evidence 49 (
22f97ec) - feat(a11y-core): add useRovingMenu and nextEnabledMenuIndex (
868b67e) - feat(auth): send RFC 6750 error_description in bearer challenges (
9e3325e) - feat(test): expose MockOidcServer JWKS URL and unpublished-kid minting (
252799b) - docs(plan): record wave 1 progress (
8b2d11f) - feat(typescript): publish default export conditions so Jest resolves @baukit packages (
8d268e1) - docs(evidence): renumber Expo SQLite evidence to 37 (
90a9a44) - merge: item 1, serialize Expo SQLite operations (
ddc55fa) - test(examples): cover root and transaction overlap on real Expo SQLite (
1587397) - fix(data-contracts-expo-sqlite): serialize operations per database file (
c795f17) - fix(examples): make the Expo SQLite gate rerunnable without a TTY (
14fee99) - feat(analytics): add exact-key scrubbing and crash event scrubber (
4f4a873) - feat(test): add PostgreSQL image, app role, and per-test database options (
1c1b512) - docs(evidence): renumber HTTP boundary evidence to 38 (
81cdd63) - merge: item 2, close HTTP boundary gaps (
0f43e3e) - docs(evidence): record HTTP boundary gap decisions (
d0a0834) - feat(http)!: close HTTP boundary gaps (
9bba14a) - docs(evidence): renumber job claim evidence to 39 (
22aabd4) - merge: item 3, claim only jobs the worker can handle (
a27fb92) - docs(evidence): record job claim by type evidence (
c68951c) - feat(jobs)!: claim only job types the handler declares (
5e81185) - feat(observability): take product root and metric allowlist in the metric-name linter (
e9c36a6) - docs(plan): add cross-product feature plan with execution tracker (
ef6ac1a)