fix(docs): make docs writes transactional and the group quota a bound
Audit #6 and #8, applied to the docs store — the same two bugs KV had, in the same two places. Per-app docs (#6): create/update/delete wrote the row, then emitted best-effort. An outbox failure left a committed doc whose trigger never fired. They now go through `atomic_write::DocsWriter`, whose Postgres impl writes and fans out on one connection in one transaction. Group docs (#8): the service read `count_rows`/`projected_total_bytes` on pooled connections and wrote on another, so concurrent creators each saw the same pre-write count and together overshot the ceiling. `PostgresGroupDocsWriter` takes a per-group advisory lock — on its OWN `docs`-namespaced key, so it serializes against other docs writers to that group but not against KV writers, which draw on a separate ceiling. The bespoke `check_total_bytes` (with its own copy of the upper-bound fast path) is gone; group docs now shares `group_quota::check_group_write` with group KV, so the row ceiling, the projected-bytes ceiling, and the fast path that skips the O(n) SUM scan have exactly one implementation between them. tests/atomic_write.rs gains the docs row-quota race (16 concurrent creators against a ceiling of 4). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -209,14 +209,18 @@ pub async fn build_app(
|
||||
authz.clone(),
|
||||
picloud_manager_core::docs_service::docs_max_value_bytes_from_env(),
|
||||
)
|
||||
.with_events(events.clone()),
|
||||
// Quota reads + write + shared fan-out in one advisory-locked tx.
|
||||
.with_atomic_writes(pool.clone()),
|
||||
);
|
||||
let docs: Arc<dyn DocsService> = Arc::new(
|
||||
DocsServiceImpl::with_max_value_bytes(
|
||||
docs_repo,
|
||||
authz.clone(),
|
||||
events.clone(),
|
||||
picloud_manager_core::docs_service::docs_max_value_bytes_from_env(),
|
||||
)
|
||||
.with_atomic_writes(pool.clone()),
|
||||
);
|
||||
let docs: Arc<dyn DocsService> = Arc::new(DocsServiceImpl::with_max_value_bytes(
|
||||
docs_repo,
|
||||
authz.clone(),
|
||||
events.clone(),
|
||||
picloud_manager_core::docs_service::docs_max_value_bytes_from_env(),
|
||||
));
|
||||
let dl_service: Arc<dyn DeadLetterService> = Arc::new(PostgresDeadLetterService::new(
|
||||
dl_repo.clone(),
|
||||
outbox_repo.clone(),
|
||||
|
||||
Reference in New Issue
Block a user