Changelog¶
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
0.4.1 - 2026-06-17¶
Added¶
- Mergeable quantile sketch (
DDSketch) —forze.base.primitivesaddsDDSketch/WindowedDDSketch: a relative-error sketch answering any quantile and mergeable across streams (fleet-wide / multi-quantile latency). ComplementsP2Quantile. - Hybrid Logical Clock (
HybridLogicalClock) —forze.base.primitivesaddsHybridLogicalClock/HlcTimestamp: a skew-tolerant causal clock (reads the ambientTimeSource) with an optionalmax_driftguard. - Causal outbox ordering — opt-in
hlc_ordering=TrueonPostgresOutboxConfig/MongoOutboxConfigstamps events with an HLC and claims them in causal order across replicas (relay forwards the untrustedHEADER_HLC, drift-guarded). Off by default; Postgres requires adding anhlc BIGINTcolumn first (legacy rows fall back tocreated_at). - Fleet-wide adaptive-bulkhead congestion signal — the AIMD latency-quantile signal flows through a pluggable
LatencyDigestStore(default in-process windowed-P², behavior-preserving);forze_redisaddsRedisLatencyDigestStoreso the limit reacts to the fleet's p95. Opt-in viaResilienceDepsModule(latency_digest_store=…). - Prioritized load shedding — opt-in
prioritized=TrueonBulkheadStrategy/AdaptiveBulkheadStrategymakes the wait queue criticality-aware via the new task-scopedCriticality+bind_criticality. No-op until enabled; requiresmax_queue >= 1. - Delay-based bulkhead (
GradientBulkheadStrategy) — a third bulkhead kind (Gradient2) that tunes concurrency from the latency gradient with nolatency_threshold. Mutually exclusive with the other bulkhead kinds.
Changed¶
- Quantile estimators relocated —
P2Quantile/WindowedP2Quantilemoved fromforze.application.execution.resilience.quantiletoforze.base.primitives(co-located withDDSketch; now publicbase.primitivesexports). The old module path is removed; internal resilience wiring is unaffected.
Fixed¶
- Typing annotations — type-only imports moved under
TYPE_CHECKINGwith forward references (including the runtime-optional OpenTelemetry types), so affected modules import cleanly without those optional dependencies installed and skip needless runtime imports.
0.4.0 - 2026-06-17¶
Added¶
Encryption — envelope encryption, BYOK & at-rest sealing across every plane (opt-in, off by default):
- Envelope-encryption core.
forze.base.cryptoships the self-describingEncryptedEnvelopewire format (pack_envelope/unpack_envelope/is_envelope; rotated keys still decrypt historical data), theAeadprotocol, andAesGcmAead(AES-256-GCM) /ChaCha20Poly1305Aead.contracts.cryptoadds the asyncKeyManagementPort(generate_data_key/unwrap_data_key— the BYOK seam, the KEK never leaves the backend),KeyRef/DataKey,EnvelopeCipher, the sharedFieldEncryptionpolicy (encrypted/searchable/binds_record_id), and a fail-closedrequired_encryptionfloor (none < field < envelope;EncryptionTier,encryption_satisfies,validate_required_encryption). Addscryptographyto core deps. - Per-tenant keyring + wiring.
KeyDirectoryPort(StaticKeyDirectory/TenantTemplateKeyDirectoryfor BYOK) resolves a tenant→KEK;Keyring(forze.application.integrations.crypto, aBytesCipherPort) is the async caching bridge — DEK reuse bounded bymax_dek_messages,warm(tenant), striped fill-locks + bounded LRU.CryptoDepsModule(deterministic_root=…)composes the stack and registers the dep keys;forze_mockships realAesGcmAead+ dev-onlyMockKeyManagement. - At-rest sealing across every persistence & transport plane — each driven by a
…Spec(encryption=…)/encrypt=policy, fail-closed at wiring (core.<plane>.encryption_wiring), tolerant of legacy plaintext (envelope sniff), no-op for plaintext routes: - Object storage —
S3StorageConfig/GCSStorageConfigencrypt=True; AAD binds bucket/key/tenant; presigned URLs refused (bypass the keyring). - Document fields — Postgres/Mongo/Firestore via
DocumentSpec(encryption=FieldEncryption(encrypted={…}))+EncryptingModelCodec(sync codec bridged to async KMS by a warm pre-pass; cold call raisescore.crypto.cipher_not_warm).binds_record_id=Truebinds the recordidinto the AAD (transplant resistance; bulk-update of a bound field refused withcore.crypto.record_id_required);reencrypt_documentsupgrades legacy ciphertext. Typed and raw projections decrypt transparently. - Searchable (deterministic) fields —
FieldEncryption(searchable={…})viaDeterministicFieldCipher(AES-SIV, per-(tenant,field)HKDF, no KMS) so equality/membership filters rewrite to match ciphertext at the shared chokepoint (all document backends; unsupported positions raisecore.crypto.searchable_op_unsupported). Zero-downtime root rotation:deterministic_previous_rootmatches both keys (search_variants),reencrypt_documents, then drop. Trade: leaks equality/frequency within a tenant. - Search reads —
SearchSpec.encryption(the same policy object as the document spec, so they can't drift): Meilisearch seals onupsert, in-place Postgres FTS/vector + Mongo decrypt sealed fields out of results. Decryption once on raw rows (decrypt_search_rows) before decode, across every read path — offset/cursor,select_search, rawproject_search, hub (HubSearchSpec.encryption), federated, and snapshot re-pagination. - Analytics & graph —
AnalyticsSpec/GraphNodeSpec/GraphEdgeSpecencryption; sealed on ingest/write, decrypted out of every read/traversal path (shareddecrypt_rows) on Postgres/ClickHouse/BigQuery/DuckDB and Neo4j get/neighbors/expand/shortest-path/scoped-walk. Encrypted columns/properties are confidential, not analyzable/matchable; analytics rejectsbinds_record_id(no stable id), graph binds the kind'skey_field. - Outbox & direct messaging —
OutboxSpec.encryption(none/at_rest/end_to_end) andQueueSpec/StreamSpec/PubSubSpecencryption(none/end_to_end— a transport has no at-rest store; the outbox owns that tier); AAD binds(tenant, event_id)from forwarded headers; works over SQS/RabbitMQ/Redis streams+pubsub. Backend-agnostic decorators (encrypting_{queue,stream,pubsub}_command) seal direct-published payloads so they are interchangeable with relayed ones;decrypt_consumed_payloadhandles both.QueueCommandPort.enqueue_manygainsmessage_headers(per-message) so an encrypted batch still ships in oneSendMessageBatch. - Durable payloads — Temporal (
TemporalConfig(encrypt_payloads=True)via the nativePayloadCodecseam,encrypting_data_converter(...), runs outside the workflow sandbox) and Inngest (DurableFunctionEventSpec(encrypt=True); the_forzerouting envelope stays plaintext). Per-tenant BYOK; a Temporal worker must be built from the same encrypting client to decode. - Cache, search snapshots & idempotency results — the distributed cache body (bound
(tenant, pk)), ranked-search snapshots ((tenant, run id)), andIdempotencySpec(encrypt_result=True)results (EncryptingIdempotencyPort, AAD(tenant, op:key)) are sealed automatically when the underlying route encrypts, closing plaintext re-exposure in Redis/Postgres. The in-process L1 stays plaintext in memory (process-scoped). - Vault Transit KMS (
forze_vault) —VaultTransitKeyManagementimplementsKeyManagementPorton Transit (transit_generate_data_key/transit_decrypt/transit_rewrap,VaultConfig.transit_mount, opt-inVaultDepsModule(key_management=…));VaultTransitTenantProvisionercreates a tenant's Transit key from the sameKeyDirectoryPort(idempotent; teardown opt-in viaallow_deletion). - BYOK access-token signing + JWKS — pluggable
SignerPort; shipsHs256Signer(default, behavior-preserving),LocalAsymmetricSigner(RS256/ES256),forze_vault.VaultTransitSigner;kid-aware verification across the issuer +AuthnKernelConfig.access_token_verifiers(rotate by overlap);forze_fastapi.attach_jwks_route+jwks_document(*signers)publish/.well-known/jwks.json(symmetric secrets never published). Breaking:AccessTokenService(secret_key=…)→AccessTokenService(signer=Hs256Signer(secret=…));issue_token/verify_tokenare now awaitable;AccessTokenConfig.algorithmremoved. - Crypto & signing observability —
instrument_crypto(...)(CryptoKeyringStats: data-keys generated/unwrapped, cache hits, cold miss) andforze_identity.authn.instrument_signing(...)(SigningStats: tokens signed/verified/verify_failed, bykid/alg); always-on.
Multi-tenancy hardening:
- Declared-minimum tenant isolation, fail-closed at wiring — every deps module accepts
required_tenant_isolationovernone < tagged < namespace < dedicated, enforced per route (a single unscoped sibling fails it); each integration declares itsmax_supported_isolationceiling so an unreachable floor fails as a capability mismatch. One declarativevalidate_module_tenancy(groups=[TenancyRouteGroup(…)]). New exportsTenancyRouteGroup,validate_module_tenancy,isolation_satisfies,validate_required_isolation,derive_tenant_isolation_mode. Additive (Nonedefault unchanged). - Neo4j reaches
namespace/dedicated—Neo4jGraphConfig.databaseaccepts a(tenant_id)->strresolver (per-tenant database →namespace); newRoutedNeo4jClientresolves per-tenant Bolt URI/credentials from secrets (→dedicated; fails closed on partial auth), wired viarouted_neo4j_lifecycle_step. New exportsRoutedNeo4jClient,Neo4jRoutingCredentials,routed_neo4j_lifecycle_step. - Tenant infrastructure provisioning (
TenantProvisionerPort) — idempotentprovision/deprovisiononTenantManagementPort.provision_tenant/deprovision_tenant, wired viaTenancyDepsModule.tenant_provisioner;Noop/Function/Composite+ referenceObjectStorageTenantProvisioner(ensures a bucket) andPostgresSchemaTenantProvisioner(CREATE SCHEMA, opt-indrop_on_deprovision). Opt-in. - Analytics per-tenant namespace routing + advisory binding —
ClickHouseAnalyticsConfig.query_database/BigQueryAnalyticsConfig.query_dataset/PostgresAnalyticsConfig.query_schemaresolve an unqualified table in the tenant's namespace;tenant_awareroutes bind the tenant id as a query param, fail closed if unbound, and reject SQL that never references it. Helpersbind_tenant_param/assert_tenant_param_referenced/TENANT_PARAM. Off by default. - Tenant-safe structured graph walk + raw gating —
GraphQueryPort.scoped_walk(anchor, ScopedWalkParams(…))runs an adapter-owned, fully-structured, full-path tenant-scoped traversal; the whole-query raw hatch is disabled by default (Neo4jGraphConfig.allow_raw_querydefaultsFalse). New exportsGraphPathStep,ScopedWalkParams. (Breaking: deployments usingctx.graph.rawmust setallow_raw_query=True.)
Query DSL:
- Fluent builder
Q—Q.field("age").gt(18) & Q.field("name").like("a%")lowers to the same filter AST (.build()/.to_ast()); covers every value operator,&/|/~, field compares, and array quantifiers. New exportsQ,QueryCondition,FieldRef. Additive (lowers faithfully, does not re-validate). - Hierarchy operators (
$descendant_of/$ancestor_of) on aTreePathfield — inclusive, label-boundary-correct containment; Postgres nativeltreeortextprefix fallback, mock oracle; gated byQueryCapabilities.supports_hierarchy. New exportsTreePath,HierarchyOp,HierarchyValue. - Aggregation —
$count_distinct,$stddev_pop/samp,$var_pop/samp,$percentile, and post-group$havingon Postgres/Mongo (mock oracle). ($first/$lastdeferred.) - Full + array-of-arrays nested quantifiers on every document backend (Postgres nested
EXISTS, Mongo$expr);supports_nested_quantifiersgate dropped. Operator/field-type validation (validate_query_field_types) now runs in the gateway and the mock, rejecting mismatches withquery_operator_type_mismatch. - Mixed-direction keyset pagination + per-key
NULLS FIRST/LAST— coherent null ordering across backends (fixes a Postgres keyset predicate that dropped null-keyed rows); cursor tokens carry null placement (old tokens stay valid); Mongo opt-incomputed_null_ordering. New helpersQuerySortNulls/QuerySortKeySpec,resolve_sort_keys,parse_sort_value,ordered_compare. - Query discovery metadata —
build_query_discoveryprojects a read model's filterable/sortable/aggregatable surface as OpenAPIx-forze-query+ MCP line. New helpersclassify_field_type,field_value_operators,is_quantifiable_field.
Identity & API keys:
- Tenant selector self-service —
GET /tenants(active memberships),POST /tenants/{id}/activate(validates membership viaTenantResolverPort→tenant_mismatch/tenant_inactive, re-mints a tenant-scoped token pair — Pattern B: the signedtidis re-validated against live membership each request),DELETE /tenants/{id}(drops the caller's own membership). New aggregateforze_kits.aggregates.tenancy.build_tenancy_registry(authn_spec)+forze_fastapi.routes.attach_tenancy_routes(allAuthnRequired, tenant-unaware);TenantManagementPort.list_principal_tenants,TenancyDeps.require_manager. - Tenant admin (
forze_kits.aggregates.tenancy_admin) — the privileged inverse:create_tenant/list_members/invite_member/remove_member/deactivate_tenantviaattach_tenancy_admin_routes(POST /tenants,GET /tenants/{id}/members,POST /tenants/{id}/deactivate,POST/DELETE /memberships). Ships unguarded — bindAuthnRequired+AuthzBeforeAuthorizeper op before exposing. NewTenantManagementPort.list_tenant_principals. (Breaking forTenantManagementPortimplementers: newlist_principal_tenants+list_tenant_principals.) - Self-service API-key management —
issue_api_key/list_api_keys/revoke_api_keyasPOST/GET/DELETE /api-keys(secret returned once;hint/label). NewApiKeyInfo. Breaking forApiKeyLifecyclePort. Migration:ALTER TABLE <api_key_accounts> ADD COLUMN hint text, ADD COLUMN label text. - Delegation-aware API keys (user→agent) —
issue_api_key(actor_principal_id=…)binds a delegation actor (RFC 8693actclaim →AuthnIdentity.actor; engine enforces the user×agent grant intersection). NewACT_CLAIM. Breaking forApiKeyLifecyclePort. Migration:ALTER TABLE <api_key_accounts> ADD COLUMN actor_principal_id uuid. - MCP boundary API-key auth —
ForzeApiKeyVerifier+AccessTokenIdentityResolverprotect a FastMCP server with the forze_identity brain (no OAuth flow); reads-only by default. - OpenAPI security from configured authn —
apply_openapi_securityderivessecuritySchemesfrom theAuthnRequirement; principal-requiring ops are flaggedx-requires-authn(newOperationCatalogEntry.requires_authn) with a matching MCP line. - Authn plane —
AuthnOrchestratorinforze.application.integrations.authnwith a full mock identity plane;attach_authn_routes(login/refresh/logout/change-password/deactivate + reset); self-servicePasswordResetPort(single-use, no enumeration);AuthnEventSink+ fixed-window loginlockout.deactivate_principalships unguarded.
Cache:
- In-process L1 document cache (
CacheSpec(l1=L1Spec(…))) ahead of the distributed cache — tenant-scoped, TTL staleness budget, pluggable eviction (LRU+TTL / scan-resistantTinyLfuStore);RedisCacheConfig(invalidation_push=True)shrinks the window to one round-trip (RESP3CLIENT TRACKING);instrument_document_l1;CachePort.exists. Off by default. - Stampede protection & adaptive freshness — singleflight on read-through misses; probabilistic early refresh (
early_refresh_beta, optionalearly_refresh_background); per-entryage_ttl/sliding_ttl+ keywordttl=on every setter.
Resilience & runtime:
- New strategies —
AdaptiveBulkheadStrategy(AIMD concurrency, optionallatency_quantile; CoDel shedding + adaptive LIFO),AdaptiveThrottleStrategy, tail-basedHedgeStrategy.adaptive_delay_quantile, token-bucketRateLimitStrategy+THROTTLED/TIMEOUTexception kinds;ResilienceDepsModule(port_policies=[…]). - Invocation deadlines — per-operation budgets (
registry.bind(op).with_deadline(…),bind_deadline); expiry raisesexc.timeout(504), projected to catalog/routes/MCP. - Distributed limits — pluggable
RateLimitStore(RedisRateLimitStore, fails open) so N replicas share one rate; bulkheads/budgets stay process-local. - App assembly & deployment —
build_runtime+forze_fastapi.runtime_lifespan/forze_mcp.runtime_lifespan(warm scope for the app lifetime); graceful drain (drain_timeout, default 10s);DeploymentProfile.FLEET(singleton-guard,singleton_lifecycle_step, readiness probe, deadline-budget HTTP propagation) andSERVERLESS(rejectsrequires_long_running, zero default drain);instrument_resilience/instrument_tenant_pools.
Messaging & storage:
- Envelope headers + correlation propagation — messages gain
headers/delivery_count; the relay forwards the full envelope andprocess_with_inboxrebinds correlation/causation across broker hops (optional documented-trust tenant rebinding). - Outbox
ordering_key— per-aggregate ordering (SQS FIFOMessageGroupId, stream partition key); dedup keyed on the event-id header. Migration:ALTER TABLE … ADD COLUMN ordering_key TEXT. - Kits queue-consumer runner (
run_consumer/queue_consumer_background_lifecycle_step) — inbox exactly-once, requeue, poison parking, envelope rebinding. - Stream pending-entry recovery —
StreamGroupQueryPort.claim(XAUTOCLAIM) +pending(XPENDING). Breaking for port implementers. - Presigned object-storage URLs —
StorageQueryPort.presign_download/StorageCommandPort.presign_upload(S3 SigV4, GCS V4, mock). Breaking for port implementers (minting an upload URL is a CQRS write). - Object-storage metadata & access ops —
head(completion-seam enabler for presigned uploads),download_range(HTTP Range, 206),download_if_changed(Noneon 304),copy/move(server-side; S3 5 GiB single-copy cap, GCSrewrite; refused on object-encrypting routes),put_object_tags; generated FastAPI download routes honourRange/If-None-Match. Breaking forStorageQueryPort/StorageCommandPort/ObjectStorageClientPortimplementers (per-objectexpires_atomitted — S3/GCS have no per-object TTL). - Resumable multipart uploads —
StorageUploadSessionPort(ctx.storage.uploads(spec), CQRS-write-guarded):begin_upload/presign_part/list_parts/complete_upload/abort_uploadfor large direct-to-storage uploads with the app out of the data path. S3 native multipart; GCS temp-keys +compose; mockdeposit_partseam. Refused on object-encrypting routes. - Storage HTTP edge — kit ops + generated FastAPI routes for presigned download/upload and the full multipart session (
POST /presign/{download,upload},POST /uploads,/uploads/parts/url,/uploads/parts,/uploads/complete,/uploads/abort) so a browser/Uppy client drives direct uploads. The presigned URL rides the response body but never a log line; minting an upload URL is a command op — bind authn/authz. - Server-side encryption at rest (SSE/CMEK) — for direct-upload flows where client-side envelope encryption can't reach.
S3StorageConfig.sse(S3ServerSideEncryption(mode="none|s3|kms", kms_key_id=…)) applies SSE-S3/SSE-KMS to upload/copy/presign/multipart (SSE-KMS signed headers ridePresignedUrl.headers);GCSStorageConfig.kms_key_name(CMEK) covers app-path upload/compose, while presigned/resumable PUTs rely on the bucket default (documented divergence). A separate axis from client-sideencrypt(combinable; does not satisfy a client-siderequired_encryptionfloor). Off by default.
Misc:
- Catalog/registry ergonomics —
OperationCatalogEntrygainssupports_idempotency_key/required_permissions(projected to routes/MCP); duplicatemergekeys raise (override=Truehatch); one-stepregistry.register(…). PlusRecordingNotificationSenders,AnalyticsDeps.command,TenancyDeps.require_resolver(),OperationDescriptor.tags→ FastAPI route tags. - Generated-route mount ergonomics — every
attach_*_routeshelper gainsresource=(a prefix string, mutually exclusive withns=) andpath_overrides=(per-op path replacements;operationIdstays the verbatim catalog key). Additive. - Patch authoring — scoped, materialized, fail-closed reach —
registry.patch(selector, namespace=ns)/commit_patch(…, namespace=ns)match only ops underns(str_key_selector.in_namespacebuilds the scoped selector);registry.materialize_patches(*selectors)folds patches into per-op plans so a laterOperationRegistry.mergecan't reach a sibling.mergenow raises when a patch authored in one registry matches another's ops, naming the selectors/ops. (Breaking only for a registry that merged a broad pre-merge patch onto another's ops — passmerge(…, cross_registry=True).)
Changed¶
- Queue consumer and outbox relay are now configurable classes — attrs classes that validate config once on construction, replacing the function-runners that took
ctx+ a long kwarg list:run_consumer(ctx, …)→QueueConsumer(queue=…, queue_spec=…, handler=…, inbox_spec=…, tx_route=…, …).run(ctx, *, timeout=…);relay_outbox_*/relay_outbox→OutboxRelay(outbox_spec=…, …).to_queue/.to_stream/.to_pubsub/.run. Lifecycle steps keep flat params;relay_outbox_claimsunchanged. Breaking for directrun_consumer/relay_outbox_*callers. - Tenant-isolation tier model made coherent — ladder
none < tagged < namespace < dedicated(relationrung removed); per-tenant collection/index names reachnamespacevia dynamic-resolver detection; each integration owns itsmax_supported_isolationceiling (required, fail-closed); namespace resolution unified intoresolve_scoped_namespaceacross nine adapters (key/path formats unchanged). - Argon2 hashing off the event loop —
PasswordService.hash_password/verify_password/timing_dummy_hashare nowasyncon a bounded pool (PasswordConfig.hashing_concurrency, default 4);*_syncvariants remain. - Performance (measured):
- Engine hot path — hookless op ~2.5→1.2 µs (−52%), QUERY −56%,
bind−50%;resolve_simplememoized per scope (−73%); aggregateloadskips the dump roundtrip (−22% flat / −67% nested). - Data access —
Document.update()copies only changed subtrees (−21%/−44%, OCC history −30%); Postgres root-tx ridesBEGIN(−21%), out-of-tx on autocommit (−37%); Mongocreateskips read-back (−49%), single updates usefind_one_and_update(−30%), outbox claims in 3 round-trips (−90%, needs a sparseclaim_tokenindex);trusteddecode 1.5–2.6×, msgspecforbid_extra3–13×. - Observability / cold start — lazy error-context (~8–13 µs→0.2 µs), batched relay marks (~18.4k rows/s), opt-in
trace(26×), memoized log scrubbing (~53×/27×); s3/sqs type-stubs +opentelemetryconfined toTYPE_CHECKING/lazy import. - FastAPI
style="rpc"uses REST verbs + query params —GET /notes.get?id=,PATCH /notes.update?id=&rev=,DELETE /notes.kill?id=, etc. Breaking: RPC clients must switch fromPOST /<op>; REST and MCP unchanged. singleton_lifecycle_steptakes aDistributedLockSpec, not a live port — Breaking: passspec=DistributedLockSpec(name=...).- Release-coherence sweep — relay logs the at-least-once → fire-and-forget downgrade; Temporal
query/update/resultdeserialize into declared types;ApiKeyConfig.prefixvalidated; sagastep_failedstaysDOMAIN.
Fixed¶
- Tenant-isolation correctness & parity — Postgres outbox/inbox now enforce the declared isolation floor (were excluded); a missing bound tenant fails closed consistently as
authentication/tenant_required(was 500/401 split) viaTenancyMixin._tenant_id_for_resolve; the mock durable/graph/document adapters now tenant-partition their stores, pinned by a mock-tenancy-parity meta-test. - Post-commit work survives task cancellation — the after-commit drain runs as a cancellation-protected critical section (
forze.base.asyncio.run_to_completion), then re-raises; cancellation during the body still rolls back. - PGroonga search honors tenant isolation regardless of plan — a tenant-aware PGroonga search now always uses
filter_first, overridingpgroonga_plan="index_first"/"auto"(which ranked a heap top-K across all tenants and post-filtered, scanning cross-tenant rows and possibly truncating a tenant's results to a slice of the global top-K).
0.3.0 - 2026-06-11¶
Added¶
- Generated FastAPI routes (
attach_document_routes/attach_search_routes/attach_storage_routes): project a frozen registry's operations onto a user-ownedAPIRouter(the HTTP sibling ofregister_tools),operationId= operation key verbatim. Requiredstyle("rest"resource paths /"rpc"operation-named); capability-aware withinclude=narrowing; dispatches throughrun_operationso plans, read-only enforcement, and hooks apply. Merging a soft-deletion registry adds delete/restore. No ETag or route-feature framework — idempotency is now engine-level. forze_mcp(forze[mcp]) — expose operations to AI frameworks as MCP tools (read-only MVP):register_tools(server, registry, ctx_factory, …)adds a frozen registry's operations as tools onto your own FastMCP server (flat arg signature from the input DTO,OperationKind→readOnlyHint/destructiveHint, governedrun_operationdispatch). Read-only by default (include_writes=Trueto expose commands). Alsoregister_dsl_query_prompts(querying-grammar prompts),register_schema_resources(per-spec JSON schema + queryable fields),register_resource_templates(get-by-id asnotes://{id}),LoggingMiddleware, and thebuild_mcp_serverconvenience. Pluggable identity (StaticIdentityResolver/DelegatedIdentityResolver). Built on FastMCP 3.x; test-backed example underexamples/recipes/mcp_server/.forze_duckdb(forze[duckdb]) — in-process DuckDB analytics over object storage (query-only):AnalyticsQueryPortover a Parquet/CSV/Iceberg/Delta lake on S3/GCS/local files without a standing warehouse. Typed source descriptors (ParquetSource/CsvSource/JsonSource/IcebergSource/DeltaSource) auto-derive DuckDB extensions; typed object-store credentials (S3Credentials/GcsCredentials) renderCREATE SECRET, resolved viaSecretsPortor supplied inline. Bridged to asyncio on a bounded executor (cursor-per-query, native timeout interrupt), Arrow-internal. Wire withDuckDbDepsModule+duckdb_lifecycle_step; also serves as a real-engine analytics test double.- Delegated identity (on-behalf-of, RFC 8693):
AuthnIdentity.actorcarries the principal performing the action;AuthzBeforeAuthorizeenforces least-privilege intersection (both subject and actor must be permitted — the confused-deputy defense). Token-derived viaAuthnDepsModule(actor_claim="act")(multi-hop chains); explicit pairwise authority viaDelegationPort.may_act+DelegationGrantPort(AuthzSpec.enforce_delegation_grant, fail-loud). Document-backed adapters inforze_identity, mocks inforze_mock;forze_mcpships aDelegatedIdentityResolver. - Operation-level CQRS (
OperationKindQUERY/COMMAND):registry.bind(op).as_query()runs read-only — a command port cannot be acquired (by construction) and the transaction opensREAD ONLY(enforced at the DB, including the raw-query hatch); covers every state-write accessor (document/outbox/search/graph/dlock/storage/analytics/authz/authn). Untagged defaults to COMMAND (behavior-preserving). Replica routing viaas_query().bind_tx().set_route(...). - Operation catalog descriptors (
OperationDescriptor+FrozenOperationRegistry.catalog()): interface-agnostic request/response-schema + description metadata for projecting operations onto MCP/HTTP without re-deriving schemas;catalog()joins the descriptor with the plan'sOperationKind. All kit builders populated. - Queryable-field policy (
QueryFieldPolicyonDocumentSpec): per-aggregatefilterable/sortable/aggregatableallow-sets (validated against the read model). Powers MCP schema discovery and boundary-only enforcement (QueryFieldGuardin the kit list/search/aggregate handlers); direct port calls stay unrestricted. - OpenTelemetry traces + metrics (
instrument_operations): wraps every operation in an OTel span (kind, ids, tenant, principal) plus aforze.operationscounter andforze.operation.durationhistogram, via the global providers. OpenTelemetry is already a core dependency. Opt-in, additive. @invariant— declarative domain invariants: an always-true(self) -> Nonerule enforced on both create and update — closes the footgun that merge-patch updates (viamodel_copy) bypass Pydantic@model_validators. Positioned against@update_validator(transition rules) and raw@model_validator(escape hatch, documented as not running on updates).- Saga / process orchestration (
SagaDefinition+ in-process executor): declarative multi-step processes across aggregates that can't share a transaction; typedSagaSteps withSagaStepKind(COMPENSATABLE/PIVOT/RETRYABLE) modeling a point of no return, per-steptx_route/retry_policy, reverse compensation before the pivot, forward-only after.run_saga(ctx, definition, initial)viaSagaExecutorPort; must run outside an enclosing transaction. A shared backend-agnosticSagaProgresscoordinator drives both the in-process executor andforze_temporal.TemporalSaga(Temporal owns durability). - DDD domain events + aggregate roots → outbox:
DomainEvent/AggregateRoot(forze.domain.models) buffer events (record_event/collect_events); the declarative@event_emitterraises an event from an(before, after, diff)transition onDocument.update. Persisting an aggregate drains and dispatches its events in the operation's transaction viaDomainEventDispatcherPort(InProcessDomainEventDispatcher, factory-registered handlers); theoutbox_event_handlerbridge stages them in the transactional outbox.forze_kits.aggregates.AggregateRepository(load/add/apply) supports the functional-decider pattern. Wired viaDomainEventsDepsModule. - End-to-end worked example (
examples/recipes/order_fulfillment/): the first runnable, test-backed example — checkout saga → aggregate@event_emitter→ outbox → relay → inbox → downstream, plus the compensation path, all in-process onforze_mock. - Deterministic time & ids (
TimeSourceseam):forze.base.primitives.utcnow()/uuid7()read a context-activeTimeSource, sobind_time_source(FrozenTimeSource(...))makes every read (including domain self-stamping) deterministic with no call-site changes; the Temporal worker binds a replay-deterministic source. - Resilience policy pipeline (
forze.application.contracts.resilience): Polly-style composable strategies (BulkheadStrategy,CircuitBreakerStrategy,RetryStrategywith jittered/decorrelated backoff +RetryBudget,TimeoutStrategy,FallbackStrategy) compose into a validatedResiliencePolicy; named viaResilienceSpec, run throughctx.resilience().run(...)or attached per-op asResilienceWrap(retry re-runs with a fresh transaction per attempt). ShipsInProcessResilienceExecutorwith default"occ"/"transient"policies;forze_mockno-op passthrough. Hedging (HedgeWrap/HedgeStrategy) races a redundant attempt afterdelay, gated by a freeze-time safety check (idempotent/read-only only). Distributed breaker (CircuitBreakerStoreseam +RedisCircuitBreakerStore) so a fleet trips/recovers together (two-tier, server-clock Lua, fails open). - Inbox / consumer-side dedup (
forze.application.contracts.inbox):InboxPort.mark_if_unseen(atomic seen/not-seen);process_with_inboxmarks and runs the handler in one transaction (exactly-once effect for at-least-once delivery).PostgresInboxStore+ mock. Distinct from idempotency (operation-level result replay). - Graph contracts +
forze_neo4j(forze[neo4j]): graph ports resolvable viactx.graph.query/.command/.raw; Neo4j adapter over the async Bolt driver (vertex/edge CRUD, both edge-identity modes,neighbors/expand/shortest_path, tenant isolation, raw Cypher hatch); reusablekernel.cypher. In-memoryMockGraphAdapter. forze_kits— consolidated kit package: domain kits, aggregate registries/facades, mapping, DTOs, outbox/notify integrations, secrets adapters, runtime scopes (DistributedLockScope). Absorbs formerforze_patterns,forze.application.{composition,handlers,mapping,dto,kit}, andforze_secrets(see the Removed migration table). Includes a closed-schema, document-backed stored-file kit (StoredFileKitSpec).forze_http(forze[http]): outbound HTTP integration —HttpServiceSpec/HttpServicePort,HttpClient/RoutedHttpClient(tenant routing),HttpDepsModule, declarativeBaseHttpIntegration+async_http_op;ctx.httpresolves services by name. httpx under the hood.forze_meilisearch(forze[meilisearch]): async Meilisearch — offsetSearchQueryPort,SearchCommandPort, federated search (native federation or weighted RRF).- Transactional outbox + notify + search-command:
forze.application.contracts.outbox(OutboxSpec,IntegrationEvent, command/query ports, request-scopedOutboxStaging) with Postgres/Mongo/Mock stores; relay helpers +outbox_relay_background_lifecycle_step(at-least-once claim/reclaim) inforze_kits.integrations.outbox.forze_kits.integrations.notify— typed notification commands, routing, dispatch, queue-consumer helper. CoreSearchCommandPort(ensure_index/upsert/delete/…) for external index maintenance. - Tenant routing: declarative per-request backend targets (
RelationSpec/NamedResourceSpec+coerce_*/require_static_*,forze.application.contracts.resolution) adopted across all integrations; per-tenantRouted*Clientvariants with*RoutingCredentials,routed_*_lifecycle_step, LRU pool dedup by connection fingerprint, backed byTenantClientRegistryand tenancy/secret helpers inforze.application.contracts.tenancy. - Identity — IdP presets (
forze_identity.builtin.idp): OIDC presets for Google Sign-In, VK ID (server-side introspection), and Telegram Login;oidc_bootstrap_identity_depsfor externalid_tokenJWTs;OidcIdpPreset/ConfigurableOidcIdpVerifier. PKCE helpers (generate_pkce),OidcTokenVerifier.require_nonce. Authn:refresh_api_keyrotation; single-use password invites (HMAC-digest storage); customtoken_verifiersskip access-secret validation. - Execution — freeze/resolve pipeline: authoring
DepsRegistry(freeze()→FrozenDepsRegistry.resolve()→FrozenDeps) separates registration from per-scope resolution; matchingLifecyclePlan→ frozen → resolved withLifecycleModule, topological ordering, androuted_client_lifecycle_step. Per-scope caches (cache_resolved_operations/cache_resolved_ports, default on) with tenant-scoped resolvers staying per-call. - Codecs:
default_model_codec,stored_field_names_for,DocumentCodecs/document_codecs_for_spec/DocumentSpec.resolved_codecs; optionalread_codec/ingest_codecon search/analytics specs; trusted-row read validation. - Postgres / Mongo search: Postgres
read_validationstrict/trusted, PGroonga plan modes + candidate caps, hubper_leg_limit/combo_*/parallel legs +SearchOptionsoverrides; MongoMongoDepsModule.searches(text/Atlas/vector, offset + cursor, optional Redis snapshots, index-validation lifecycle step). - Document adapters:
max_scan_pages/max_stream_pages/max_chunked_command_pages(default 100 000,Noneunlimited) with cursor-stall detection. - Durable workflow:
DurableWorkflowRunStatus/Description+describe()onDurableWorkflowQueryPort(forze_temporal). forze_temporalsecure connections:TemporalConfig.tls/api_key(Temporal Cloud) /rpc_metadata/data_converteroverride; defaults unchanged (plaintext localhost, pydantic converter).- AWS — long-lived clients + credential chain (SQS/S3): one aiobotocore client opened at
initialize()and reused;access_key_id/secret_access_keyandregion_namebecome optional (default credential/region chain — env, profile, IAM role, SSO, IMDS); S3 derivesLocationConstraintfrom the resolved region. Per-tenant routed credentials still require explicit keys and region. - Vault — token renewal, metadata existence, health: opt-in self-renew loop;
kv_existsvia the KV v2 metadata endpoint; standardhealth()for the first time. forze_fastapiupload cap + attach-time validation: chunked upload streaming undermax_upload_size(default 64 MiB,Nonedisables) with early Content-Length rejection; id / id+rev route builders validate DTO shape at attach time.forze_socketioerror translation + identity: handler exceptions become structured ack payloads honoring egress redaction; optional connect-timeidentity_resolverbound per event.- Distributed-lock fencing tokens (breaking for port implementers):
DistributedLockCommandPort.acquirereturnsAcquiredLock | Nonecarrying a monotonic fencing token;DistributedLockScopeyields the handle. Backends that cannot issue tokens returntoken=None. - Object-storage tags end-to-end:
UploadObjectRequestDTO.tags(S3 native tagging / GCS prefixed metadata / mock);include_tagsguarantee flag on head/list (Truemakes S3 payGetObjectTagging); tags on head/listed value objects. IdempotencyPort.fail()(breaking for port implementers): releases a pending claim on handler failure so legitimate retries aren't rejected as duplicates (Redis + mock).AuthnFacade.deactivate_principal: the existing tested handler is now registered intobuild_authn_registry, exposed on the facade, and exported.forze_mockparity: strict transactions (MockDepsModule(strict_tx=True)— snapshots DB-backed stores, savepoint nesting, read-only enforcement; queues/streams/storage deliberately don't roll back, matching production); queue/idempotency parity (idle-timeoutconsume, visibility-timeout redelivery, dead-letter list, TTL'd idempotency); consumer groups (one-consumer-per-group with realack) and true keyset cursor pagination; tenancy helpers and distributed-lock/search/durable/identity adapters.forze.baseprimitives:CacheLane,SimpleLruRegistry/GuardedLruRegistry,InflightLane(singleflight),OnceCell,frozen_mapping, and fingerprint helpers (stable_json_bytes/stable_payload_fingerprint/stable_fingerprint/connection_string_fingerprint).
Changed¶
- Breaking — document write identity is an explicit argument:
CreateDocumentCmdno longer carriesid/created_at; the command write surface becomescreate(payload, *, id=None)/ensure(id, payload)/upsert(id, create, update)withKeyedCreate/UpsertItembulk value objects (the gateway mirrors with parallel sequences). Restore viaforze_kits.dto.ImportTimestamps+ensure. Migration: moveid/created_atinto the new arguments; replace bulk lists with the value objects. - Breaking — storage CQRS split:
StoragePort/StorageDepKeysplit intoStorageQueryPort(download,list) /StorageCommandPort(upload,delete) with separate dep keys; resolve viactx.storage.query(spec)/.command(spec). S3/GCS factory renames (ConfigurableS3Storage→…StorageQuery/…StorageCommand). - Breaking — coordinators → adapters:
DocumentCoordinator→DocumentAdapter,DocumentCacheCoordinator→DocumentCache,SearchResultSnapshotCoordinator→SearchResultSnapshot,OutboxStagingCoordinator→OutboxStaging,DistributedLockCoordinator→DistributedLockScope; helpers moved underforze.application.integrations;forze.application.coordinatorsremoved. - Breaking — codecs unified on
ModelCodec: document/search/analytics paths materialize through spec-owned codecs; document kernel gateways require explicit codecs at construction (build viaread_gw/doc_write_gw).read_validation="trusted"decode on Postgres/Mongo/Firestore; the versioned cache stores compact JSON bytes. - Breaking — frozen
attrsintegration configs: all integration wiring configs are frozenattrsclasses (no dict/TypedDictliterals);tenant_awareinherited fromTenantAwareIntegrationConfig; module-levelvalidate_*_confremoved (validation at construction /.validate()); several timeout fields move totimedelta. - Breaking —
ensure_bucketis create-if-missing on both backends (S3): S3 previously raisednot_found; both now create idempotently and race-safe. Usebucket_exists()for existence assertions. - Breaking —
nack(requeue=...)semantics aligned (SQS):requeue=Falseno longer deletes the SQS message (silent loss) — it leaves it for the redrive policy to dead-letter;requeue=True= immediate redelivery (best-effort). Apps relying on nack-to-drop mustack. - Breaking —
workflow_id_template→workflow_id_base: the schedule field is passed verbatim (Temporal appends the fire timestamp); renamed across contract/adapter/mock, no alias. - Idempotency reshaped to engine-level result idempotency:
IdempotencySnapshot(HTTP-shaped) replaced by interface-agnosticIdempotencyRecord(result: bytes); a newIdempotencyWraphook reads a context-boundidempotency_key, hashes the args, and returns the stored typed result early (skipping the handler and its transaction). The FastAPI middleware reads the canonicalIdempotency-Keyheader. - OCC retry routed through the resilience pipeline: Postgres/Mongo/Firestore write gateways drop their own
tenacitydecorators for the sharedocc_retry("occ"policy, decorrelated backoff, 3 attempts); the executor is resolved per scope with a shared default, so apps keep OCC retries with no wiring change. Attempt counts unchanged; registeringResilienceDepsModulelets an app override"occ". - Write gateways — unified OCC/history validation: Postgres/Mongo share one
HistoryOccMixin; a missing history snapshot now raises retryableexc.precondition(history_not_found_retry) on both (Mongo previously raisednot_found). - Async contract protocols standardized on
def … -> Awaitable[X]: the remainingasync def-declared Protocol ports are converted (type-only; implementations andawaitcall sites unaffected; makes contracts decorator-friendly). Async-generator methods unchanged. - Transaction nesting contract: nested scopes are savepoints; isolation and
read_onlyare honored only at the root; a nested scope requesting a conflictingread_onlyraisestx_nested_read_only_conflict.TransactionHandle.idremoved; gainedread_only. - Unbounded-read protection unified on the implicit cap: Mongo/Firestore gain the Postgres
find_many_implicit_limit(default 10 000,Nonedisables); the hard "filters or limit required" precondition is dropped. - Analytics SQL pagination wraps in a subquery: the shared
apply_limit_offsetwraps Postgres/ClickHouse too (fixes registered queries already ending inLIMIT); negative limit/offset now raise; catch-all driver-error summaries normalized via a sharedfallback_exception_mapper. forze_mockadapters are stricter (potentially breaking for tests): the mock password verifier actually compares;MockAuthzDecisionPort/scope port are deny-by-default;MockTenantResolverPortmirrors real membership/ambiguity/inactive checks;MockDocumentAdapter.createraisesconflicton a duplicate id. Lenient-mock false passes now fail honestly.- Graph contracts (evolving, pre-1.0): dual-addressing
EdgeRef.by_key/by_endpoints(per-kindGraphEdgeSpec.identity);key_fields;shortest_pathsingle path + newk_shortest_paths;validate_graph_module_specraisesconfiguration. - Execution-context lifecycle tripwire + import-linter + kernel consolidation: constructing an
ExecutionContextwhile an operation is in flight now logs a warning (the unsupported per-request pattern); plane layering (forze_kitsconsumers, nothing importsforze_identity) is nowlint-imports-enforced (14 contracts); kernel-client boilerplate consolidated ontoGuardedLifecycle/ContextScopedResource(http/GCS/Temporal/Vault/RabbitMQ; behavior-preserving). - Internal package layout: integration
kernel→kernel.client,execution→lifecycle/+execution.deps.{configs,factories}; the operation registry/planning/facade/run modules move underforze.application.execution.operations. Package-root imports unchanged; direct internal-module imports must update. - Performance: hookless operations skip body-stage scaffolding and fold the middleware chain iteratively (~30%); per-scope caches reuse gateways/adapters/codecs; trusted bulk decode hoists the field set + construct loop; JSON logs render via
orjson. - Misc: Postgres streaming reads use a server-side named cursor (bounded client memory); outbox (Postgres/Mongo) bulk
INSERT … ON CONFLICT DO NOTHING+claim_pending/stale-processingreclaim (reclaim_stale_after, default 5 min) +requeue_failed; Mongo uses a singleindex_name; storage/analytics internals move toforze.application.integrations; search snapshot fingerprints re-baseline once;forze[oidc]now bundleshttpx.
Deprecated¶
forze_identity.oidc:OidcTokenVerifier.enforce_issuer_and_audiencenow defaults toTrue— construction requires bothissuerandaudienceunless explicitly opted out.
Removed¶
- Dead public surface (pre-release cleanup, all verified unreferenced): the orphan
forze[arango]extra;AccessTokenService.try_decode_token; theISSUER_FORZE_JWTconstant;EffectiveGrantsAdapter;HeaderTokenAuthn.scheme/bearer_format;GCSHead/GCSListedObjectaliases; the unusedFORZE_*_LOGGER_NAMEStuples andMIDDLEWARESenum members;PostgresQualifiedName.from_string; theforze_postgres.kernel.client.fingerprintmodule; the never-honoredbatch_sizeofMongoClientPort.delete_many. python-dateutilcore dependency: dropped;datetime_to_uuid7parses ISO-8601 via stdlibdatetime.fromisoformat(trailingZaccepted).forze[casbin]extra: dropped (no integration shipped against it).forze_identity.local(breaking): useforze_identity.builtin.local; local verifiers/factories no longer exported fromforze_identity.authn/.tenancy.forze_identity.builtin.telegram: Telegram Mini AppinitDataHMAC preset, superseded by Telegram Login OIDC underforze_identity.builtin.idp.telegram.- Execution:
forze.application.coordinators;forze.application.execution.{registry,planning,facade,running};OperationRunner;lifecycle_graph_from_sequence(usesteps_graph_from_sequence). - Validation helpers from public APIs: Postgres (
validate_pg_search_conf,validate_postgres_hub_search_conf, …) and integrations (validate_mongo_search_conf,validate_clickhouse_analytics_config, …); validation now lives on the config types / instance validation. Also dict/mapping coercion forConfigurablePostgresDocument/…ReadOnlyDocument. - Codecs:
RecordMappingCodec/Pydantic*/Msgspec*,codec_for_model,pydantic_cache_dump*, and publicpydantic_*/msgspec_*helpers inforze.base.serialization(useModelCodec/default_model_codec);SearchSpec.row_codec/resolved_row_codecandDocumentReadGatewayPort.effective_row_codec(useread_codec). - Relocated to
forze_kits(breaking): the formerforze_patterns,forze.application.{composition,kit,handlers.*,mapping,dto}, andforze_secretsmodules now live underforze_kits.Mapper/MapperFactorystay onforze.application.contracts.mapping.OutboxDestination(queue_route=…, queue=…)replaced by the discriminatedOutboxDestination.queue(route=…, channel=…)(also.stream,.pubsub).
| Old import | New import |
|---|---|
forze_patterns.soft_deletion |
forze_kits.domain.soft_deletion |
forze.application.composition.document |
forze_kits.aggregates.document |
forze.application.composition.outbox |
forze_kits.integrations.outbox |
forze.application.kit.DistributedLockScope |
forze_kits.scopes.DistributedLockScope |
forze_secrets |
forze_kits.adapters.secrets |
forze.application.handlers.document |
forze_kits.aggregates.document.handlers |
forze.application.handlers.search |
forze_kits.aggregates.search.handlers |
forze.application.handlers.storage |
forze_kits.aggregates.storage.handlers |
forze.application.handlers.authn |
forze_kits.aggregates.authn.handlers |
forze.application.mapping |
forze_kits.mapping |
forze.application.dto |
forze_kits.dto |
OutboxDestination(queue_route=..., queue=...) |
OutboxDestination.queue(route=..., channel=...) |
StoragePort |
StorageQueryPort / StorageCommandPort |
StorageDepKey |
StorageQueryDepKey / StorageCommandDepKey |
ctx.storage(spec) |
ctx.storage.query(spec) / ctx.storage.command(spec) |
RecordMappingCodec |
ModelCodec |
SearchSpec.row_codec |
SearchSpec.read_codec |
PostgresReadGateway(...) without codec= |
Pass codec= or build via read_gw |
PostgresWriteGateway(...) without write codecs |
Pass create_codec / update_codec / codec=, or use doc_write_gw |
PostgresHistoryGateway(...) without history_codec |
Pass history_codec and codec=, or use doc_write_gw |
Fixed¶
- Package error mappers were dead code in 12 integrations:
ChainExceptionMappernow flattens nested chains so package mappers are consulted — most critically PostgresSerializationFailure/DeadlockDetected(and Mongo/Neo4j conflicts) now map toCONCURRENCY, so OCC retry fires on real serialization conflicts. Mapped errors carry the interceptionsite. - Firestore transactions:
Aborted(contention) →CONCURRENCY(OCC retry); rollback onBaseException(no leaked server-side tx);count_documentsjoins the ambient tx; a mismatcheddatabaseraises configuration. - ClickHouse
run_query_all_pagesis one streaming execution (consistent snapshot, no growing-OFFSET duplicates, no(attempts+1)²retry blow-up). - Redis pipelines fail loud on reads: value-returning methods inside
pipeline()now raiseredis_read_in_pipelineinstead of returning garbage coerced from the pipeline object. - RabbitMQ robustness:
close()nacks/requeues pending unacked messages; a pending watermark warns on growth; poison messages are dead-lettered (consumer continues); one delay queue per distinct delay value (no head-of-line blocking). Same poison handling on SQS. - Outbox relay failure model (transient retry / poison / drain): codec decode (poison) failures fail immediately; broker publish failures reschedule with exponential backoff + jitter until
max_attempts(default 5). New durableattempts/available_atcolumns (migration:ALTER TABLE … ADD COLUMN attempts INT NOT NULL DEFAULT 0, ADD COLUMN available_at TIMESTAMPTZ),mark_retry(...)(breaking for port implementers),requeue_failedresets the counter; the relay drains the backlog per tick. At-least-once, ordering not preserved across retries — key onevent_id. - Outbox staging is per-route and per-task: fixes a process-global
flushedflag that silently dropped events after the first flush, and shared buffers that handed every route's rows to whichever store flushed first. Newbuffer_for(route)/flushed_for(route)/peek(route?). GuardedLruRegistryuse-after-dispose race: refcount 0→1 transitions and eviction reads now happen under the registry lock (these guard live connection pools); a dispose error during drain deregisters and propagates.- After-commit callbacks run to completion: a failing post-commit callback no longer skips the rest — all run, failures aggregate into one
after_commit_failed(raised after, rolls nothing back). - Lifecycle steps are shut down exactly once: per-scope started-state tracking ends the double-shutdown on failed startup.
finallyhooks observe before-hook denials: before hooks now run inside the try/finally so audit/metricsfinallyhooks see denials;on_failurestays handler-only.- OCC history validation hardened: records re-keyed by
(id, rev)(no positional zip); all comparisons run in canonical python-mode space so no-op datetime/UUID resends don't falsely conflict. Document.update()re-validates the patched state: merges into a python-mode dump andmodel_validates the result — semantic no-ops yield an empty diff (no spuriousrev/history/event), partial nested dicts and ISO-string datetimes are no longer left raw,@computed_fieldkeys are excluded at every depth, and@model_validatornow runs on update (invalid patches raiseValidationError).- Concurrent graph waves report all failures (
ExceptionGroupfor 2+; a single failure still raises directly). - Per-scope port cache works for per-call specs (value equality, identity fast-path first).
kill()/kill_many()verify row counts on every path (all paths raisenot_foundon missing rows).- SQS message identity fixed (was breaking inbox dedup):
QueueMessage.idis now the brokerMessageId(stable across redeliveries); the ReceiptHandle moved toSQSQueueMessage.receipt_handle. - Postgres transaction options no longer leak across pooled connections: read-only/isolation are now emitted as
SET TRANSACTION …inside the root tx (was persistent psycopg attributes causing intermittent read-only write failures). - Mongo write conflicts retry under OCC: WriteConflict (112) and
TransientTransactionError→CONCURRENCY. forze_fastapimiddleware errors return proper status codes:CoreExceptions raised in forze middlewares render the standard JSON error (viabuild_core_exception_response) instead of 500s; a malformed correlation/causation header falls back to a generated id.- RabbitMQ/SQS receive & consume defaults: bounded
receivewindows; uniform idle-timeoutconsume(None= forever, finite = clean stop) — no more indefinite block, 1s death, or zero-wait busy-loop. DistributedLockScopeno longer loses the lock silently: a lost heartbeat is recorded and raised (CONCURRENCY) at scope exit without masking the body's own exception.- Notify consumer dedup: the event id is derived deterministically from the broker message identity (was a random UUID that defeated dedup).
- All integration kernel clients:
initialize()/close()serialize on an internal lock (no double-create/leak under concurrency); partial-failure assignment hardened (BigQuery/Postgres/Redis). - Analytics adapters:
run_chunked/select_run_chunkedreject non-positivefetch_batch_sizeup front (sharedvalidate_fetch_batch_size). - Misc fixes: Postgres
ON CONFLICTfromconflict_target/inferred PKs (composite PKs, extra UNIQUE indexes); PGroongaindex_firstcap andsearch_count=exactcorrections; Mongo bulk-upsert miss →mongo_ensure_bulk_miss; Meilisearch federated snapshot finalization; identity duplicate/ambiguous-login detection;forze_fastapitenant-hint resolution;connection_string_fingerprintincludes sorted query params. forze_temporal+forze[mcp]workflow sandbox:sandboxed_workflow_runner()/default_sandbox_restrictions()passbeartype(fastmcp's transitive import hook) andcoveragethrough the workflow sandbox, fixing circular-import validation failures and a coverage-induced test hang.
Security¶
- Password change revokes existing sessions (breaking by default):
change_passwordrevokes all of the principal's sessions (refresh families +sid-bound access JWTs);revoke_sessions_on_password_change=True, opt-out explicit, missing session ports fail at startup. - Rehash-on-login (opt-in):
Argon2PasswordVerifierpersists parameter-upgraded hashes after login (password_rehash_on_login=True), OCC- and fire-safe. sensitive=Truespec marker keeps credentials off generated surfaces:attach_*_routes/register_tools/register_schema_resources/register_resource_templatesrefuse sensitive specs at attach time; the shipped authn specs are marked (would otherwise have served Argon2 hashes / HMAC digests).- Owner-override permission keys configurable + documented: the
"admin"/"{resource_type}.admin"bypass moves toAuthzKernelConfig.owner_override_permissions(defaults unchanged, empty set disables) — previously hardcoded, so an unrelatedadminpermission silently granted a global bypass. tenancy_mode="global"warns over tenant-partitioned stores (grants shared across tenants — setrequire_invocation_tenantfor isolation).- OIDC nonce value binding:
verify_id_token_nonce(constant-time, single error) +generate_nonce()/generate_state(); VK/Telegram exchange acceptexpected_nonce; callback hardening checklist added. - Secret values masked in reprs framework-wide: credential value objects become
repr=False;ClickHouseConfig.password/routing creds andInngestConfigkeys →SecretStr;LocalIdentityConfig.api_keysout of repr. Direct readers must call.get_secret_value(). - Outbound HTTP does not follow redirects by default (
HttpConfig.follow_redirects=False): httpx only stripsAuthorizationon cross-origin redirects, so custom credential headers would otherwise follow a malicious 30x to an attacker host. AuthnDepsModulerejects a token-verifier override without a resolver override (principal-collision hazard; fails at startup naming the route).- Tenancy adapters enforce the cache/history guard (a cached principal→tenant binding could keep a detached principal resolving after revocation).
- Cursor pagination tokens validated as client input: malformed/stale/tampered tokens raise 4xx (was
INTERNAL500); values restricted to JSON scalars; mixed-type sort-key compares surface as an invalid-cursor error. - Log message text is scrubbed: string scrub rules apply to the rendered message after interpolation, not just structured extras.
- Postgres sort direction whitelisted (
asc/desconly; uppercase now raises). S3 object tags URL-encoded (noTaggingquery-string corruption/injection).OidcClaimMapperrejects emptyiss/sub(empty subjects collapsed onto one principal). - 5xx responses no longer leak internal diagnostics: generic detail for status ≥ 500, sanitized
contextrestricted to < 500, catch-all mapper summaries made static (driver text moved into suppressed-and-scrubbeddetails);CONFIGURATION-kind details no longer sent to clients. - Authz document-scope filters fail closed: a scope port returning row filters with no DTO attribute to carry them now raises
CONFIGURATION(was silently dropped → unscoped query). - Raw-query tenancy hardening:
ctx.graph.raw(spec)(forze_neo4j) fails closed in a tenant-aware module (was unscoped across all tenants) and binds$tenant; newctx.tenancy.current()/require_current_id()for kernel-client ports. - Missing authentication surfaces as
AUTHENTICATION(401), notAUTHORIZATION(403). builtin.localAPI-key verification no longer 500s on non-ASCII input (UTF-8 bytes comparison).asyncio.CancelledErrorpasses through exception interceptors (was converted toCoreException, breaking timeouts, structured concurrency, and graceful shutdown framework-wide).forze_identity.authnsession enforcement (breaking): access JWTs carry asidclaim cross-checked against the session store, so logout / refresh-rotation invalidate access beforeexp. Pre-upgrade tokens withoutsidfail until re-login (or register a stateless verifier override).forze_identity.authnchange_passwordrequires the current password (breaking): re-authenticates first, so a hijacked session can't escalate to account takeover.forze_identity.authnprincipal eligibility (breaking): authn and credential lifecycle gated onis_activeviaPrincipalEligibilityPort;PrincipalDeactivationPortcascades policy/session/credential deactivation; API keys persist and enforceexpires_at; key ownership checks takeidentity.forze_identity.authnlogin hardening: generic 401 for all failures, always runs Argon2 verify (anti-enumeration/timing).forze_identity.authzfail-closed tenant isolation: grant-resolution adapters refuse to construct when a tenant-scoped route has a non-tenant-aware binding/catalog port ("global"routes unaffected).forze_identity.oidcresolves JWKS signing keys in a worker thread (no event-loop block on a cache miss).- Secret-field redaction: JWT signing keys / HMAC peppers become
repr=False;VaultConfig.token,S3RoutingCredentials.secret_access_key,GCSRoutingCredentials.service_account_json→SecretStr;HttpRoutingCredentials.headersredacted and routed through the one-way KDF in fingerprints. forze_fastapi—X-Tenant-Id/X-Forwarded-Hostnot trusted by default (breaking): a rawX-Tenant-Idis ignored unlesstrust_tenant_header=True(verified-credential tenants still honored); forwarded host gated ontrust_forwarded_host=True; Scalar docs defaultpersist_auth=False.- Input/identifier hardening: Meilisearch filter attribute names validated (no filter-expression injection / tenant-filter bypass); Postgres PGroonga terms quoted as literal phrases (operator chars can't alter match scope/cost); SQS rejects absolute-URL queue names on tenant-aware adapters; object-storage keys validated (safe charset, no
../absolute) before forwarding;forze_identity.tenancyrejects invalid hints and inactive tenants. - Misc: BigQuery/GCS routed clients unlink temp service-account JSON files on close;
configure_logging(sanitize_logs=True)scrubserror.message/error.stack, andinclude_exception_stack=Falseomits stacks from JSON logs.
0.2.0 - 2026-05-28¶
Added¶
- Execution:
OperationRegistry,FrozenOperationRegistry,Handler, stage hooks,OperationRegistry.patch()/PlanPatch,make_registry_operation_resolver,run_operation, andfacade_opon document/search/storage/authn facades.ResolvedOperationPlandrives runtime hooks, transaction scopes, and after-commit dispatch. - Execution context: nested resolvers —
ctx.document,ctx.search,ctx.deps,ctx.tx_ctx,ctx.inv_ctx,ctx.authz,ctx.analytics. - Tracing:
ResolutionTracer/RuntimeTracerwithDepsPlan.with_tracing()andDepsResolutionTrace.to_key_dag(); development runtime tracing (forze.application.execution.tracing,FORZE_RUNTIME_TRACE,validate_runtime_trace); optionalTxTraceronTransactionContext. - Composition catalogs:
DOCUMENT_OPERATIONS,SEARCH_OPERATIONS,STORAGE_OPERATIONS,AUTHN_OPERATIONSunderforze_kits.*.catalog; operation-plan hooksforze.application.hooks.{authz,authn,tenancy}. - Query DSL: literal
$values/ field$fieldsfilters,$not, array quantifiers ($any/$all/$none), text patterns ($like/$ilike/$regex), aggregate$computed/$groups/$trunc, configurableQueryFilterLimits, pre-parsedQueryExpron gateways. - Document & search:
DocumentCoordinator/DocumentCacheCoordinator/SearchResultSnapshotCoordinator;update_matching/ensure; method-specific ports (find_page,find_cursor,search_page,project_*,select_*, …); hub and federated search (FTS/PGroonga v2, weighted RRF);RowLockModeonfor_update;select_cursor; stream methods (find_stream/project_stream/select_stream);hydrate_from_write;default_sortwith shared sort helpers. - Durable functions: contracts under
forze.application.contracts.durable.function; optionalDurableFunctionSpec.operation;handler_for_registry_operationandrun_durable_function. forze_inngest(inngestextra): Inngest adapter with registry-backed cron/event runs,inngest_lifecycle_step, and FastAPIserve.- Workflow schedules: schedule contracts and
forze_temporalTemporal Schedules (create/upsert/update/delete/pause/unpause/trigger/describe/list) with declarativeTemporalDepsModule.schedule_bootstraps. - Queue delayed delivery:
enqueue/enqueue_manyacceptdelay/not_before(SQSDelaySeconds, Mockvisible_at, RabbitMQ DLX delay queues whendelayed_delivery=True). forze_identity(+oidcextra): consolidated authn/authz/tenancy/OIDC with verify-then-resolve ports,AuthnOrchestrator,AuthzPolicyService;forze_identity.localdemo file/env API-key identity.- Analytics:
AnalyticsSpec,AnalyticsQueryPort, optionalAnalyticsIngestPort, and Postgres / ClickHouse (clickhouse) / BigQuery (bigquery) adapters. forze_firestore(firestore),forze_gcs(gcs),forze_secrets,forze_vault(vault): document, object-storage, and secrets integrations with routed clients and lifecycle steps.- Postgres startup validation: Pydantic↔column compatibility, bookkeeping triggers, and tenancy-wiring checks on
PostgresDepsModule. - Scrubbing & logging:
forze.base.scrubbing(sanitize(value, context=...), default structlog field scrubbing viaconfigure_logging(sanitize_logs=True));ForzeConsoleRenderer.max_traceback_frames(default 20). - Integrations: Redis distributed locks;
PydanticModelCodec/MsgspecModelCodec;StrKeySelector/StrKeyNamespace; optional domain mixins inforze_kits.
Changed¶
- Breaking — execution & composition:
Usecase/UsecaseRegistryreplaced byHandler+OperationRegistry. Register withset_handler, compose plans via.patch()/.bind()/.bind_outer()/.bind_tx(), then.freeze(); resolve withregistry.resolve(operation, ctx). - Breaking —
ExecutionContext:ctx.doc_query/ctx.doc_command→ctx.document.query/.command;ctx.dep(...)→ctx.deps.provide/ctx.deps.resolve_configurable;ctx.transaction(...)→ctx.tx_ctx.scope(...);CallContext→InvocationMetadataviactx.inv_ctx. - Breaking — document & search ports: result shape and pagination mode are chosen by method name (
find_pagevsfind_cursor, …);find_many_with_cursorremoved. - Breaking — query DSL: filter literals use
"$values"(was"$fields"); field compares use"$fields"(was"$compare"); grouping uses"$groups"/"$trunc"(top-level"$time_bucket"removed). - Breaking — identity: legacy
forze_authnzconsolidated intoforze_identity(authn/authz/tenancy/oidc).AuthnIdentityis principal-only;AuthnPortreturnsAuthnResult; tenant hints validated viaTenantResolverPort. - Breaking — authorization:
AuthzPort.permits(...)removed; useAuthzDecisionPort.authorize(AuthzRequest)withAuthz*types. Import plan helpers fromforze.application.hooks.authz. - Breaking — durable workflows: contracts under
forze.application.contracts.durable.workflowwithDurableWorkflow*types and renamed dep keys. - Breaking — errors:
forze.base.errorsremoved in favor offorze.base.exceptions; HTTPX-Error-Codedefaults tocore.<kind>. - Breaking — tracing: runtime tracing renamed to
forze.application.execution.tracing(RuntimeTrace,trace_runtime,validate_runtime_trace);Deps.merge()no longer propagates tracer flags (useDepsPlan.with_tracing()). - Breaking — FastAPI:
forze_fastapi.endpoints/andtransport.http/removed; the package now ships middleware, exception handlers, OpenAPI helpers, and security resolvers only. - Breaking — Mongo:
MongoClient.db/collectionandMongoGateway.collare async. - Document/search pagination: omitting
sortsno longer emitsORDER BY idwhen the read model has noidfield; configuredefault_sortor pass explicitsorts.@computed_fieldnames excluded from persistence;ensure/upsertskip redundant read round-trips on insert. - Messaging contracts:
QueueMessage/PubSubMessage/StreamMessageare frozen attrs value objects; queue/pubsub/stream specs require aModelCodec. forze_gcs: native asyncgcloud-aio-storageinstead of threadedgoogle-cloud-storage. Postgres PGroonga: match andweightsfollow index declaration order; every indexed column must appear inSearchSpec.fields. Postgres & Redis: safer batched writes, implicit read limits, routed pool locking,get/mget→bytes | None, atomicmsetwithNX/XX, concurrent cache I/O.- Scrubbing/console: log-context scrub uses
**********and Logfire-aligned substring rules; default Rich traceback visibility 8 → 20 frames. Socket.IO:ForzeSocketIOAdapter.bindtakesoperation_resolver.forze_fastapi: unhandled route exceptions return a generic JSON 500 whenregister_exception_handlers(app)is used.
Removed¶
- Execution:
Usecase,UsecaseRegistry,UsecasePlan, thebucketmodule,facade_call,FacadeOpRef,OpKeySpace,GuardSkip, and registry graph introspection types. - FastAPI: the
endpoints/package,transport.http/,ForzeAPIRouter,facade_dependency, and attach-based route helpers. - Authn & identity: monolithic
AuthnAdapter,HeaderAuthnIdentityResolver,OAuth2Tokens,PrincipalContext, and principal codec ports. - Query/search/domain: deprecated predicate aliases (
QueryPredicate, …); legacyPostgresFTSSearchAdapter/PostgresPGroongaSearchAdapterand thehub_pgroongamodule;forze.domain.mixins(useforze_kitsmixins).
Fixed¶
forze_fastapi:register_exception_handlersCRITICAL-logs tracebacks for unhandled exceptions and 5xxCoreExceptionwith a chained cause; deliberate causeless 5xx logs at ERROR with structured fields only.- Errors:
CoreError.detailsand FastAPIcontextresponses no longer expose raw credentials or Pydantic validationinput. - Postgres: batched
UPDATE … FROM (VALUES …)casts nullable cells correctly; no duplicaterevinVALUES;read_onlyset before opening transactions;text[]array coercion. Postgres search: hub/PGroonga empty queries no longer emit invalid rank SQL; offset snapshot pages reuse validated rows. - Redis: script result normalization avoids rare
isinstancefailures on union types. S3: user-metadata decoding on download/list; upload persists optionaldescription; default keys use a fresh UUID v7 per call. Authn: API-key lifecycle unpacks(prefix, secret)in the correct order.
0.1.14 - 2026-04-08¶
Added¶
forze.base.logging: structlog-based logging—structured records, TRACE level, Rich console and JSON renderers, request/context binding, per-namespace levels, optional dual pretty (stderr) + JSON (stdout) output, and global unhandled-exception hooks (register_unhandled_exception_handler). Replaces the previous Loguru stack.forze_fastapi: ANSI-colored HTTP status in access logs (format_status_for_log); optionalforze_unhandled_exception_handler/register_exception_handlersfor non-CoreErrorexceptions (CRITICAL log + 500).forze.application.contracts.workflow: port protocols and specs for workflow engines (start, signal, update, query, cancel, terminate, handle types).forze_temporal: Temporal integration package—TemporalDepsModuleand lifecycle; workflow adapter implementingWorkflowCommandPort; client- and worker-side interceptors to propagateExecutionContext, map headers/metadata, and run payload codecs (workflow/activity inputs and results); platform client wiring for workers.forze_fastapi.middlewares.context: ASGIContextBindingMiddlewareto bind call and principal context and emit call-context headers on responses.
Changed¶
DepsreplacesDepRouter: spec-basedDepRouterandcontracts/deps/router.pyare removed. Route selection lives onDeps:plain_depsvsrouted_deps,provide(key, route=..., fallback_to_plain=...),Deps.plain/Deps.routed/Deps.routed_group, and updated merge /without/without_routesemantics—no separate router objects in the container.DepKey/DepsPortimports: moved toforze.application.contracts.base; the oldforze.application.contracts.depspackage (keys, ports, router) is gone—replacefrom forze.application.contracts.deps import …withfrom forze.application.contracts.base import DepKey, DepsPort(and drop router types).DepsModulewiring: integration packages (forze_postgres,forze_mongo,forze_redis,forze_s3,forze_rabbitmq,forze_sqs,forze_temporal, …) now buildDepsthrough module callables, shared config types, and routed registration aligned with the new container—review each package’sexecution/deps/for factory signatures and keys.- Contracts: ports, specs, and dependency keys updated across domains (document, search, workflow, cache, queue, pubsub, stream, tx, …)—including renames, new overloads (e.g. document command/query), search types/specs reshaped (
internal/parse helpers removed),MapperPortunderforze.application.contracts.mapping, and workflow deps + specs (signals, queries, updates) expanded. forze_fastapi: HTTP integration reorganized underendpoints/(attach_document,attach_search,attach_http, route features for idempotency and ETag);ForzeAPIRouterand theforze_fastapi.routingpackage are removed—compose a standardAPIRouterand use theattach_*helpers.forze.base.logging: new configuration andLoggerAPI (configure,getLogger, messagesubvs extras); layout and rendering options are documented on the module—migrate any code that relied on Loguru-specific helpers.forze.base.logging: OpenTelemetry-aware processors,ExceptionInfoFormatter, optional custom console renderer when bridging foreign loggers, configurable dim keys, and level-aware Rich console styling.forze_fastapi: idempotent routes do not record idempotency when the request body is invalid JSON (422), so the same idempotency key can be reused after fixing the body.forze_fastapi:attach_http_endpointsfor batch HTTP route registration;exclude_noneonattach_document,attach_http, andattach_searchto controlresponse_model_exclude_none.forze.application.execution:UsecaseRegistry.finalizesupportsinplace=Trueto finalize a registry in place without copying.forze.application.contracts.documentand document adapters (forze_postgres,forze_mongo,forze_mock): optionalreturn_newandreturn_diffon create, update, touch, and batch variants—skip repeat reads when the hydrated document is not needed, or return JSON update diffs (and paired results where applicable).
Removed¶
DepRouterand theforze.application.contracts.depspackage (keys/ports/router split); useDepsrouting andforze.application.contracts.baseforDepKey/DepsPort.TenantContextPortandforze.application.contracts.tenant.ActorContextPortandforze.application.contracts.actor(caller identity is modeled viaExecutionContext/AuthIdentityand related codecs—see FastAPIContextBindingMiddleware).- Loguru-based implementation and the
logurudependency; removed helpers such asconfigure(prefixes=...),render_message, andsafe_previewin favor of the structlog pipeline andLogger.
Fixed¶
forze_postgres/forze_mongo: document deps modules register eachrw_documentsroute’s read/query port from that route’sreadconfig (fixes incorrect reuse ofro_documentsand broken or duplicated routing).forze_postgres/forze_mongo: tenant-aware write gateways includetenant_idin UPDATE and hard-delete predicates so writes match read isolation; Postgres still raisesNotFoundErrorwhen no row matches the scoped delete.forze_postgres:PostgresFTSSearchAdapterreads rows from the configured source relation and uses the index only for catalogtsvectormetadata; empty-query FTS uses a validORDER BYwhen no rank is computed.
0.1.13 - 2026-03-15¶
Added¶
hybridmethoddescriptor inforze.base.descriptorsfor class/instance dual method support.PaginationDTO withpageandsizefields for list and search request payloads.DocumentDTOswithlistandraw_listkeys for custom list request DTO types.SearchDTOswithread,typed, andrawkeys for search facade DTO configuration.build_document_list_mapperandbuild_document_raw_list_mapperin document composition.build_search_typed_mapperandbuild_search_raw_mapperin search composition.LoggingMiddlewareinforze_fastapi.middlewaresfor request/response logging with scope.Logger.optfor passing options (depth, exception, etc.) to the underlying logger.UVICORN_LOG_CONFIG_TEMPLATEandInterceptHandlerinforze_fastapi.loggingfor uvicorn log_config integration.- Storage application layer additions:
UploadObject,ListObjects,DownloadObject,DeleteObjectusecases plus storage compositionStorageUsecasesFacade,StorageDTOs, andbuild_storage_registry.
Changed¶
OperationPlan.merge,UsecasePlan.merge, andUsecaseRegistry.mergeare now hybridmethods (callable on class or instance).OverrideDocumentEndpointNamesrenamed toOverrideDocumentEndpointPaths;name_overridesrenamed topath_overridesin document router.OverrideSearchEndpointNamesrenamed toOverrideSearchEndpointPaths;name_overridesrenamed topath_overridesin search router.- Document and search facades now use
dtos: DocumentDTOs/dtos: SearchDTOsinstead ofread_dto;build_document_registryandbuild_search_registryrequiredtos. DTOMappernow requiresin_(source model type) in addition toout; update existing mappers accordingly.MappingStepprotocol is now generic (MappingStep[In: BaseModel]); custom step implementations should specify the source type.CoreModelno longer includesDecimalinjson_encoders; custom serialization for Decimal fields must be handled elsewhere.ListRequestDTOandSearchRequestDTOextendPagination; pagination (page,size) now in request body.- List and search usecases take request DTO directly instead of TypedDict with body/page/size.
- Postgres and Mongo document adapters: write operations now return results via read gateway for consistent read/write source separation.
- Logging: scope-based contextualization across execution modules;
logger.section()for structured spans; usecase scope in log format;safe_previewreplaces_args_safe_for_loggingfor argument preview.
Fixed¶
- Document list endpoints now correctly pass pagination to the usecase.
- Logging format: escape extra dict in output to avoid loguru KeyError; exclude redundant
logger_namefrom displayed extra.
Removed¶
Paginationandpaginationfromforze_fastapi.routing.params; use request body instead.Usecase.log_parametersandUsecase._args_safe_for_logging; usesafe_previewfromforze.base.logginginstead.register_uvicorn_logging_interceptor; useUVICORN_LOG_CONFIG_TEMPLATEin uvicornlog_configinstead.
0.1.12 - 2026-03-11¶
Added¶
- Paginated list documents endpoint in
forze_fastapidocument router with typed (list) and raw (raw-list) variants,ListRequestDTO,RawListRequestDTO, andListDocumentusecase. name_overrideson document and search routers:OverrideDocumentEndpointNamesandOverrideSearchEndpointNamesfor customizing operation IDs and endpoint paths.attach_document_routesandattach_search_routesfor attaching document/search routes to existing routers.
Changed¶
attach_search_routerrenamed toattach_search_routesinforze_fastapi.routers.search. Update imports accordingly.
Fixed¶
- Postgres bulk update: correct table alias in RETURNING clause; use English error messages for consistency errors.
0.1.11 - 2026-03-11¶
Added¶
- Route-level HTTP ETag support in
forze_fastapiwithETagProviderprotocol,ETagRoute, andmake_etag_route_classfor reusable conditional GET handling. RouteETagConfigandRouterETagConfigfor per-route and per-router ETag configuration (enabled, provider, auto_304).DocumentETagProviderthat derives ETag values from documentid:revfor stable version identity without response hashing.- ETag and
If-None-Match/ 304 Not Modified support on the document metadata endpoint. get()override onForzeAPIRouterwithetagandetag_configparameters.RouteFeatureprotocol andcompose_route_classengine inforze_fastapi.routing.routes.featurefor composable route-level behaviors (ETag, idempotency, tracing, etc.) without subclass conflicts.ETagFeatureandIdempotencyFeatureas standaloneRouteFeatureimplementations, decoupled from theirAPIRoutesubclasses.route_featuresparameter onForzeAPIRouter.add_api_route,.get(), and.post()for explicit feature composition on individual routes.- Document update validators now run even when the update produces an empty diff.
pydantic_model_hashnormalizesDecimalvalues for stable hashing;CoreModeladdsDecimaltojson_encodersfor consistent serialization.
Changed¶
ForzeAPIRouternow composes idempotency, ETag, and customRouteFeatureinstances into a single route class viacompose_route_class, replacing the sequentialroute_class_overridepattern that only supported one feature per route.pydantic_validatedefaultforbid_extrachanged fromTruetoFalse; extra keys are now ignored by default.Document.touch()now returns a new instance viamodel_copyinstead of mutating in place.- Postgres document gateway: revision mismatch now raises
ConflictErrorwithcode="revision_mismatch"when history is disabled. - Postgres query renderer: array operators (
$subset,$disjoint,$overlaps) now require array column types viaraise_on_scalar_t.
Fixed¶
- Document metadata endpoint path corrected from
/medatadato/metadata. - Cache operations in Postgres and Mongo document adapters are now non-fatal; failures are suppressed so primary operations succeed when cache is unavailable.
0.1.10 - 2026-03-11¶
Added¶
- Error handler for
forze_mongo(mongo_handled) that maps PyMongo exceptions toCoreErrorsubtypes, bringing Mongo in line with Postgres, Redis, S3, SQS, and RabbitMQ error handling. - Optimistic retry with tenacity on
MongoWriteGatewaywrite operations (create,create_many,_patch,_patch_many), mirroring the existing Postgres retry strategy forConcurrencyError. - Default adaptive retry configuration (3 attempts) for S3 client when no explicit retries config is provided.
Changed¶
- Replaced
DeepDiff-based dict diff with a lightweight recursive implementation, yielding 50–250× speedup oncalculate_dict_differenceand 10–150× speedup onapply_dict_patch. - Removed
deepdiffandmergedeepruntime dependencies from the core package. - Cached middleware chain in
Usecase.__call__to avoid rebuilding closures on every invocation. - Cached
inspect.signaturelookups in error-handling decorators vialru_cache. - Cached
inspect.getmodulelookups in introspection helpers vialru_cache. - Cached
TypeAdapterinstances per payload type inSocketIOEventEmitterto avoid repeated construction. - Pre-computed
MappingStep.produces()results inDTOMapperto avoid redundant calls per mapping pass. Document._apply_updatenow usesmodel_copy(deep=False)for scalar-only diffs.- S3 storage adapter
listnow fetches object metadata concurrently viaasyncio.gatherinstead of sequentialhead_objectcalls. - Used
list.extendover+=for middleware chain construction inUsecasesPlanRegistry. - Added
slots=Trueto_CmWrapperand_AsyncCmWrapperin error utilities. - Eliminated per-call
inspect.signature().bind_partial()overhead from error-handling decorators; operation name is now resolved once at decoration time. - Postgres
fetch_onewith dict row factory uses a dedicated_row_to_dictmethod instead of wrapping in a list. - SQS queue name sanitization uses pre-compiled regex patterns.
- RabbitMQ
ack/nacknow acquire the pending-messages lock once per batch instead of per message. - Cached
pydantic_field_namesvialru_cache; return type narrowed tofrozenset[str]for immutability. - Cached
normalize_pg_typein Postgres introspection utilities vialru_cache. - Pre-computed query operator sets as module-level
frozensetconstants in the filter expression parser, replacing per-callget_args()lookups. - S3
list_objectsnow exits pagination early when the requested limit window has been fully collected.
0.1.9 - 2026-03-10¶
Added¶
- Socket.IO integration package
forze_socketiowith typed command-event routing, usecase dispatch throughExecutionContext, typed server-event emitter, ASGI/server builders, and optionalforze[socketio]extra.
Changed¶
- Contracts refactor: Removed conformity protocols (
DocumentConformity,PubSubConformity,QueueConformity,SearchConformity,StreamConformityand their dep variants). Port protocols remain the source of truth for contract conformance. - Removed
forze.base.typing; type checking now enforced via mypy strict mode.
0.1.8 - 2026-03-10¶
Added¶
strict_content_typeparameter (default True) toForzeAPIRouterand route methods.- Tenant context support in S3 storage adapter (
forze_s3). S3ConfigTypedDict for abstracting botocore configuration inforze_s3.- Socket and connect timeouts to
RedisConfiginforze_redis. - Prefix validation to
S3StorageAdapter. - Mongo document adapter with dependency factories and CRUD/query support in
forze_mongo. - PubSub contracts (
PubSubSpec, conformity protocols, dep keys/ports) in core and Redis pubsub adapter/execution wiring with publish-subscribe support. - RabbitMQ integration package
forze_rabbitmqwith queue contracts wiring, client/adapters, execution module/lifecycle, and unit/integration test coverage. - In-memory integration package
forze_mockwith shared-state adapters/deps for document, search, counter, and additional contracts (cache, idempotency, storage, queue, pubsub, stream, tx manager) for local mock backends without external services. - SQS integration package
forze_sqswith async aioboto3 client/adapters, execution module/lifecycle, optionalforze[sqs]extras, and unit/integration coverage via LocalStack.
Changed¶
- Search router: split building and attachment for flexibility.
- Response body chunk processing in idempotent route (performance).
- Postgres
__patch_manyloop now usesasyncio.gather(performance). - Postgres document write operations avoid redundant reads (performance).
- Mongo integration now mirrors Postgres composition with dedicated read/write/history gateways, configurable rev/history strategies (application-managed), and execution module wiring.
- RabbitMQ batch enqueue now publishes via a single channel scope and queue declaration per batch (performance).
Fixed¶
- Tenant context dep resolution in S3 storage adapter (invoke dep as factory).
- Read gateway fallback on cache failure.
- Deterministic UUID generation now uses SHA-256 instead of MD5 (security).
0.1.7 - 2026-03-08¶
Changed¶
- Package is now published on PyPI instead of OCI (ghcr.io).
register_scalar_docs: parameterversionrenamed toscalar_version; docs page title now usesapp.title.
0.1.6 - 2026-03-04¶
Execution and mapping refactor, middleware-first approach for usecases, split search, cache, and document contracts.
Added¶
forze.application.mappingmodule withDTOMapper,MappingStep,NumberIdStep,CreatorIdStep,MappingPolicyfor composable async DTO mapping.build_document_plan,build_document_create_mapper, andreplace_create_mapperinbuild_document_registryfor document lifecycle and custom create mappers.- Namespaced
DocumentOperationandStorageOperationvalues (document.*,storage.*). CREATOR_ID_FIELDconstant inforze.domain.constants.- Search contract in
forze.application.contracts.search:SearchReadPort,SearchWritePort,SearchSpec,SearchIndexSpec,SearchFieldSpec,parse_search_spec;PostgresSearchAdapterin forze_postgres. - FastAPI search router:
build_search_router,search_facade_dependencyinforze_fastapi.routers.search.
Changed¶
DocumentOperation,DocumentUsecasesFacademoved fromforze.application.facadestoforze_kits.aggregates.document.StorageOperationmoved toforze.application.usecases.storage. Facades package removed.Effect,Guard,Middleware,NextCallmoved fromforze.application.execution.usecasetoforze.application.execution.middleware.Depsconstructor-based API: useDeps(deps={...})instead ofregister/register_many. Builder methods removed.Usecasenow requiresctx: ExecutionContext;with_guards/with_effectsreplaced bywith_middlewares.TxUsecaseremoved; transaction handling viaTxMiddlewarein plan.DocumentUsecasesFacadeProvidernow requiresregandplan(no longer optional).CreateDocumentandUpdateDocumentuse asyncDTOMapperinstead of syncCallablemappers.CreateNumberedDocumentremoved; usebuild_document_create_mapper(spec, numbered=True)withreplace_create_mapperin registry.- Search spec: public TypedDict specs vs internal attrs; per-index
source;SearchGroupsfrom dict to list for ordering. DepRoutersubclasses:dep_keymust be set as class attribute when using@attrs.define(no longer as class-definition kwarg).
Fixed¶
- Postgres history gateway: consistency error messages now in English.
- Postgres search adapter: correct attrs mutable default for gateway cache.
- Postgres index introspection: LATERAL unnest, simplified has_tsvector_col detection.
- Postgres error handler:
GroupingErrorhandling.
0.1.5 - 2026-02-28¶
Added¶
scalar-fastapidependency andregister_scalar_docsinforze_fastapi.openapifor Scalar API reference UI.- Exception handlers module in
forze_fastapi.handlerswithregister_exception_handlers. operation_idon all document router endpoints for stable OpenAPI operation IDs.- Exports in
forze_postgres,forze_redis,forze_s3:PostgresDepsModule,RedisDepsModule,S3DepsModule, client dep keys, and lifecycle steps. IdempotencyDepKeyinforze.application.contracts.idempotencyfor registering idempotency implementation in the execution context.forze_fastapi.routing.routeswithIdempotentRouteandmake_idempotent_route_classfor route-level idempotency (replaces endpoint wrapping).DepsModule,DepsPlaninforze.application.execution.depsfor dependency composition.DepsPlan.from_modulesandLifecyclePlan.from_steps,with_stepsfactory methods.LifecyclePlanandLifecycleStepinforze.application.execution.lifecyclefor startup/shutdown hooks.ExecutionRuntimeinforze.application.execution.runtimecombining deps plan, lifecycle, and context scope.
Changed¶
Depsmoved fromforze.application.contracts.depstoforze.application.execution. Update imports accordingly.- Postgres, Redis, S3 restructure:
dependencies/removed; modules moved toexecution/withPostgresDepsModule,RedisDepsModule,S3DepsModule(attrs-based classes) and lifecycle steps (postgres_lifecycle_step,redis_lifecycle_step,s3_lifecycle_step). Replacepostgres_module(client)withPostgresDepsModule(client=client)()and similarly for redis/s3. DepRouter.from_depsnow acceptsDepsPortand returns optional remainder.- Port resolvers
doc,counter,txmanager,storageconsolidated intoPortResolvernamespace class. Replacedoc(ctx, spec)withPortResolver.doc(ctx, spec)and similarly forcounter,txmanager,storage. DTOSpecrenamed toDocumentDTOSpecinforze_kits.aggregates.document. Update imports accordingly.- Document router: request body params now use
Body(...)withoverride_annotationsfor correct OpenAPI schema generation. ForzeAPIRouterandbuild_document_routerno longer accept idempotency parameters; idempotency is applied via custom route class and resolved fromExecutionContextviaIdempotencyDepKey. Register yourIdempotencyDepPortwith the key.
0.1.4 - 2026-02-27¶
Added¶
- Configurable revision bump strategy in
forze_postgres:PostgresRevBumpStrategyenum (DATABASE vs APPLICATION) andpostgres_document_configurablefactory withrev_bump_strategyparameter. - Middleware protocol and chain composition in
forze.application.execution.usecase.Usecase. forze.application.features.outboxmodule with buffer middleware and flush effect.MiddlewareFactoryand middleware support inUsecasePlan.
Changed¶
TxContextScopedPortrenamed toTxScopedPort(simplified: removedctxrequirement). Update imports fromTxContextScopedPorttoTxScopedPort.require_tx_scope_matchdecorator removed; tx scope validation is now handled byExecutionContextwhen resolving dependencies.PostgresDocumentAdapterno longer requiresctx; usesTxScopedPortinstead.
Fixed¶
- Duplicate guards, middlewares, and effects are now deduplicated by priority when merging
UsecasePlanoperations.
0.1.3 - 2026-02-27¶
Added¶
- Filter query DSL in
forze.application.dsl.query: AST nodes, parser, and value coercion. - Mongo query renderer in
forze_mongo.kernel.queryfor compiling filter expressions to MongoDB queries. forze.base.primitives.bufferfor buffer handling.
Changed¶
- Application layer restructure:
forze.application.kernelsplit intoforze.application.contracts(ports, specs, deps, schemas) andforze.application.execution(context, usecase, plan, registry, resolvers). Update imports accordingly. - Contracts flattening: Top-level re-exports (
contracts.document,contracts.deps, etc.); internal modules moved to_ports,_deps,_schemas,_specs. - Tx contracts rename:
TxManagerPortand related contracts moved fromcontracts.txmanagertocontracts.tx. Update imports fromforze.application.contracts.txmanagertoforze.application.contracts.tx. - Postgres filter builder: Replaced
forze_postgres.kernel.builderwith DSL-basedforze_postgres.kernel.queryrenderer. Old builder (coerce, filters, sorts) removed.
0.1.2 - 2026-02-26¶
Added¶
forze.base.typingwith protocol conformance helpers.- Domain document support in
forze.domainbuilt fromforze.domain.models.Documentwith name/number/soft-deletion mixins and update-validator infrastructure for safer incremental updates. - Document kernel in
forze.application.kernel: pluggable usecase plans,DocumentUsecasesFacadefactory,DocumentPortwith explicitDocumentSearchPortandDocumentReadPort/DocumentWritePort, andDocumentOperationenum for operation keys. - Optional FastAPI integration package
forze_fastapi: routing helpers, idempotent POST support, and prebuilt document router. - Optional provider packages:
forze_postgres,forze_redis,forze_s3,forze_temporal,forze_mongowith platform clients, gateways/adapters, and dependency keys for composition.
Changed¶
- Kernel: Transaction handling and dependency resolution refactored around
ExecutionContextandforze.application.kernel.deps.*;TxManagerPort/AppRuntimePortremoved fromforze.application.kernel.ports. Usecase base now relies on the new context and tx ports. - Postgres filter builder (in
forze_postgres.kernel.builder): filter input accepts only canonical operator names (eq,neq,gt,gte,lt,lte,in,not_in,is_null,or, plus array and ltree ops). Aliases such as==,ge,not in,in_,or_are no longer accepted and raiseValidationError. Useinandorfor membership and disjunction. - Infrastructure previously under
forze.infrahas been moved into optional packages; coreforzeno longer ships Postgres, Redis, S3, or Temporal implementations.
Fixed¶
- Correct UUIDv7 datetime conversion in
forze.base.primitives.uuidso round-trips between datetimes and UUIDs preserve timestamp semantics.
0.1.1 - 2026-02-23¶
Added¶
- Initial DDD/Hex contracts: ports, results, errors.
Fixed¶
- Packaging metadata for PyOCI classifiers.