Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 4 additions & 13 deletions docs/backlogs/000000-template.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,7 @@ Briefly describe what outcome would indicate this item is done.

Extra context that helps future task creation.

## Close Reason (Added When Closed)

When a backlog item is moved to `docs/backlogs/closed/`, append:

```md
## Close Reason

- Type: <implemented|stale|replaced|duplicate|wontfix|already-implemented|other>
- Detail: <reason detail>
- Closed By: <backlog close>
- Reference: <task/issue/pr reference>
- Closed At: <YYYY-MM-DD>
```
Closure metadata is added only by `tools/backlog.rs close-doc`. It records the
resolution type, explanatory detail, closing actor, task/issue/PR reference,
and closure date. Do not add a Close Reason section to an open backlog; the
close command appends the populated section when archiving it.
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,10 @@ A future task selects and implements a consistent carrier strategy; poison remai

## Notes (Optional)


## Close Reason (Added When Closed)

When a backlog item is moved to `docs/backlogs/closed/`, append:

```md
## Close Reason

- Type: <implemented|stale|replaced|duplicate|wontfix|already-implemented|other>
- Detail: <reason detail>
- Closed By: <backlog close>
- Reference: <task/issue/pr reference>
- Closed At: <YYYY-MM-DD>
```
- Type: implemented
- Detail: Implemented via docs/tasks/000263-introduce-quad-error-and-narrow-audited-error-convergence.md
- Closed By: backlog close
- Reference: User decision
- Closed At: 2026-08-08
65 changes: 53 additions & 12 deletions docs/error-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,48 +144,89 @@ Disclosure is approved only at one of these boundaries:

- a public Doradb method returning the public `Result` alias;
- an external trait whose signature is fixed to the public result;
- a genuine orchestration owner whose producer set spans multiple independent
domains and cannot be represented by an existing constrained carrier.
- a constrained carrier's disclosure implementation; or
- the three callback-mutation helpers that must forward an arbitrary public
`Error` returned by `Statement::table_mutate_mvcc`'s caller.

Reusable private helpers do not return public `Result` merely to make `?`
compile. Test helpers follow the same rule: test a typed producer as typed, and
use a public adapter only when asserting public classification.

Configuration convergence is owned by public `Engine::bootstrap`. Startup
validates and normalizes transaction configuration there, including resolving
the redo-file prefix, before passing a `ValidatedTrxSysConfig` into the
Runtime-typed transaction-system component.

## Constrained carriers

Three carriers encode closed multi-domain contracts without adding a synthetic
error-stack frame:
Select the narrowest stable contract in this order:

1. one native typed domain;
2. an exact two-domain carrier; then
3. `QuadResult` when three or four common integration domains are reachable.

Four pairwise carriers encode exact two-domain contracts without adding a
synthetic error-stack frame:

- `OperationOrRuntimeError` contains either an Operation report or a Runtime
report;
- `OperationOrFatalError` contains either an Operation report or a Fatal
report;
- `RuntimeOrFatalError` contains either a Runtime report or a Fatal report.
- `RuntimeOrFatalError` contains either a Runtime report or a Fatal report; and
- `LifecycleOrFatalError` contains either a Lifecycle report or a Fatal report.

Structural `From` implementations into these carriers are allowed because the
native report is preserved and the destination explicitly represents that
domain. These are not public convergence conversions.

`QuadError` is the closed final-integration carrier for exactly Operation,
Runtime, Lifecycle, and Fatal. It flattens the pairwise carriers by moving their
native report directly into the matching arm. It deliberately has no Config,
Resource, IO, DataIntegrity, Internal, public `Error`, or completion-bridge
arm. A fifth arm changes the integration design and requires a new design
review rather than a routine extension.

Resource, IO, and DataIntegrity can enter `QuadError` only after a semantic
owner stacks a specific Runtime context such as `TableAccess`, `IndexAccess`,
`CatalogAccess`, `Recovery`, `RedoLogAccess`, or `TransactionCommit`. There is
no generic physical-domain conversion into Quad. Config remains owned by
public bootstrap.

Carrier extensions add attachments to either arm and can replace only the
non-Fatal Runtime context where that operation is owned. Fatal always bypasses
ordinary reinterpretation.
Runtime and Lifecycle reinterpretation. Poison-aware admission therefore
returns Fatal without a Lifecycle frame, while shutdown, closed-session, and
discarded-transaction rejection remain Lifecycle.

Do not introduce a general sum-error framework. Add a carrier only when a
small, stable producer set is repeatedly shared and no existing carrier fits.

## Completion and Fatal transport

`CompletionErrorBridge` transports one canonical typed report across an async
completion or multiple waiters. Its accepted roots are closed and audited: IO,
Resource, DataIntegrity, Lifecycle, Runtime, and Fatal. The bridge itself must
never appear as a frame in the reconstructed or public report.
completion or multiple waiters. Its accepted roots are closed and audited:
Operation, IO, Resource, DataIntegrity, Lifecycle, Runtime, and Fatal. The
bridge itself must never appear as a frame in the reconstructed or public
report.

Cloning a bridge shares its immutable canonical state. Each consumer rebuilds
an independent physical report, retains the registered source frames and
attachments, and installs the consumer-owned outer context. A Runtime report
may contain a private Internal frame beneath it; that frame is diagnostic only
and does not become a completion root or public kind.

Mandatory completion observers return the typed bridge. Their semantic owner
uses a named replay policy: `into_runtime_or_fatal` for an exact pairwise
contract or `into_quad` for the common integration set. `into_quad` preserves
Operation, Runtime, Lifecycle, and Fatal roots; it stacks raw Resource, IO, or
DataIntegrity roots beneath the caller-supplied Runtime context.

Immediately after replay, the semantic owner attaches one combined diagnostic
with the public operation, completion-wait phase, and available request
identifiers. The attachment is added to whichever native carrier arm was
reconstructed, including Operation, Lifecycle, and Fatal arms that do not use
the fallback Runtime context. `QuadError` remains a frame-less carrier.

`SharedFatalError` provides equivalent fan-out for a canonical Fatal report.
Poison publication and every waiter retain the initiating source and Fatal
reason.
Expand Down Expand Up @@ -250,13 +291,13 @@ The principal convergence owners are:
| Area | Boundary |
| --- | --- |
| value and rows | public decode/access adapters and fixed external traits |
| engine | build orchestration, new-session admission, and shutdown facades |
| engine | public bootstrap, new-session admission, and shutdown facades |
| session | public table, checkpoint, retention, and transaction operations |
| transaction | public lock, statement execution, commit, and rollback |
| statement/stream | public DML and stream iteration methods |
| log configuration | fixed `FromStr` adapter over typed validation |
| catalog/table | public semantic facades and genuine Runtime-or-Fatal policy owners |
| recovery/startup | transaction-system bootstrap over typed recovery helpers |
| catalog/table | public semantic facades plus callback mutation error transport |
| recovery/startup | typed recovery helpers beneath public Engine bootstrap |

Lower buffer, file, log internals, index, table, purge, retention, recovery, and
component suppliers stay typed or use one of the constrained carriers. A new
Expand Down
6 changes: 5 additions & 1 deletion docs/process/coding-guidance.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,17 @@ We rely on tooling to enforce style.

### Error Handling
* **Typed Domain Reports**: Internal domain-specific functions should return the matching report alias, such as `ConfigResult`, `OperationResult`, `ResourceResult`, `DataIntegrityResult`, `LifecycleResult`, `FatalResult`, or `InternalResult`. Use the crate-wide `crate::error::Result` only at public API boundaries or in functions that intentionally combine several unrelated domains.
* **Multi-Domain Selection**: Prefer one native domain, then an exact pairwise carrier, then `QuadResult` only when three or four of Operation, Runtime, Lifecycle, and Fatal are reachable. `QuadError` has fixed arity and membership; adding a fifth arm requires a new design review.
* **Physical Integration Ownership**: Resource, IO, and DataIntegrity enter a common integration carrier only after the semantic owner stacks a specific Runtime context. Config convergence belongs to public Engine bootstrap. Do not add blanket lower-domain conversions merely to satisfy `?`.
* **Crate-Owned Trait Errors**: When implementations of a crate-owned trait have different failure domains, use an associated error type and let each implementation expose its narrowest result. Keep generic dispatch typed and convert only in a caller that actually combines unrelated implementations. Use crate `Result` directly only for an externally fixed signature or an implementation that is itself mixed-domain.
* **Fieldless Error Variants**: Define stable error classifications as fieldless `thiserror` variants. Put request-specific details, identifiers, values, and explanatory text in `error-stack` attachments instead of variant fields.
* **Context at the Caller**: Attach operation names, table or block identifiers, configuration field names, and other caller-owned context with `attach` or `attach_with` where that context becomes known. Do not pass parameters down the call stack solely so a leaf function can format an error message.
* **Poison Helper Depth**: Keep Fatal report construction, poison logging, and publication at the owning policy boundary. A shared production poison-publication helper should have at least three callers; otherwise inline it. Do not stack thin domain wrappers: a policy owner may call at most one shared domain helper before `EnginePoisoner`. Substantive state-machine or wait algorithms and the core `EnginePoisoner` API are not publication-wrapper layers.
* **Attachment Granularity**: Combine printable diagnostic facts owned by one semantic boundary into one attachment. Keep typed attachments separate so callers can inspect them, and keep attachments on opposite sides of `change_context` separate because they describe different error frames.
* **Cross-Domain Conversion**: Use `change_context` at the boundary where one domain consumes another domain's failure, then attach the consuming operation's context. Preserve the original report frames; do not convert to `crate::error::Error`, downcast it, and rebuild a new report.
* **Completion Transport**: Finish the owned typed report and capture it once with `CompletionErrorBridge::capture` at the failed handoff. Intermediate completion forwarders clone the bridge unchanged; only the typed or public policy owner materializes it and adds caller-owned context. Never capture a public, Runtime, or already materialized bridge report.
* **Fatal Bypass**: Never replace a Fatal report with Runtime or Lifecycle. Poison-aware admission returns Fatal directly; ordinary shutdown and unavailable session or transaction state remain Lifecycle.
* **Completion Transport**: Finish the owned typed report and capture it once with `CompletionErrorBridge::capture` at the failed handoff. Intermediate completion forwarders clone the bridge unchanged; only the typed or public policy owner materializes it. Immediately after named replay into a constrained carrier, that owner attaches one combined operation/phase diagnostic so every reconstructed native arm receives the boundary context. Never capture a public, Runtime, or already materialized bridge report.
* **Public Error Ownership**: Public `Error` is limited to public Doradb APIs, externally fixed trait adapters, constrained-carrier disclosure, and the three full-table mutation helpers that transport an arbitrary caller callback error. Reusable internal orchestration remains typed.
* **Validation Pattern**: Use `crate::error::Validation<T>` for optimistic logic checks (Valid/Invalid) where failure is a normal control flow, distinct from `Result` (exceptional failures).
* **Runtime Failures vs. Contracts**: Incidental `unwrap()` / `expect()` in runtime paths remains prohibited, and external or otherwise valid runtime failures must remain typed results. A proven internal contract may use a release assertion at the narrowest owning site when its constructor, exact allocation, ownership boundary, or fixed lifecycle establishes the precondition. Document the invariant locally and include the component, edge, type/length, column, or other identifying detail in the assertion diagnostic. Do not use `debug_assert!` as the only guard for a correctness contract or treat this rule as general permission to panic.

Expand Down
57 changes: 22 additions & 35 deletions docs/public-error-audit.csv
Original file line number Diff line number Diff line change
@@ -1,68 +1,55 @@
file,function_or_method,disclose_calls
doradb-storage/src/catalog/index.rs,CreateIndexPlan::new,2
doradb-storage/src/catalog/index.rs,DropIndexPlan::new,2
doradb-storage/src/engine.rs,Engine::bootstrap,25
doradb-storage/src/engine.rs,Engine::new_session,1
doradb-storage/src/engine.rs,Engine::try_shutdown,1
doradb-storage/src/engine.rs,bootstrap_inner,23
doradb-storage/src/error.rs,LifecycleOrFatalError::disclose,2
doradb-storage/src/error.rs,OperationOrFatalError::disclose,2
doradb-storage/src/error.rs,OperationOrRuntimeError::disclose,2
doradb-storage/src/error.rs,QuadError::disclose,4
doradb-storage/src/error.rs,RuntimeOrFatalError::disclose,2
doradb-storage/src/error.rs,SharedFatalError::disclose,1
doradb-storage/src/log/mod.rs,LogSync::from_str,1
doradb-storage/src/session.rs,Session::begin_trx,5
doradb-storage/src/session.rs,Session::buffer_pool_stats,1
doradb-storage/src/session.rs,Session::checkpoint_catalog,3
doradb-storage/src/session.rs,Session::checkpoint_catalog_and_truncate_redo_log,3
doradb-storage/src/session.rs,Session::checkpoint_table,5
doradb-storage/src/session.rs,Session::cleanup_secondary_mem_indexes,5
doradb-storage/src/session.rs,Session::checkpoint_catalog,4
doradb-storage/src/session.rs,Session::checkpoint_catalog_and_truncate_redo_log,4
doradb-storage/src/session.rs,Session::checkpoint_table,6
doradb-storage/src/session.rs,Session::cleanup_secondary_mem_indexes,6
doradb-storage/src/session.rs,Session::close,4
doradb-storage/src/session.rs,Session::create_index,9
doradb-storage/src/session.rs,Session::create_table,4
doradb-storage/src/session.rs,Session::drop_index,8
doradb-storage/src/session.rs,Session::drop_table,4
doradb-storage/src/session.rs,Session::freeze_table,5
doradb-storage/src/session.rs,Session::create_index,11
doradb-storage/src/session.rs,Session::create_table,5
doradb-storage/src/session.rs,Session::drop_index,10
doradb-storage/src/session.rs,Session::drop_table,5
doradb-storage/src/session.rs,Session::freeze_table,6
doradb-storage/src/session.rs,Session::list_table_ids,1
doradb-storage/src/session.rs,Session::lock_table,2
doradb-storage/src/session.rs,Session::logical_lock_stats,1
doradb-storage/src/session.rs,Session::mandatory_runtime_stats,1
doradb-storage/src/session.rs,Session::storage_io_stats,1
doradb-storage/src/session.rs,Session::total_row_pages,3
doradb-storage/src/session.rs,Session::transaction_system_stats,1
doradb-storage/src/session.rs,Session::truncate_redo_log,3
doradb-storage/src/session.rs,Session::truncate_redo_log,4
doradb-storage/src/session.rs,Session::unlock_table,2
doradb-storage/src/session.rs,Session::wait_for_checkpoint_retry,2
doradb-storage/src/session.rs,Session::wait_for_gc_horizon_after,1
doradb-storage/src/session.rs,Session::wait_for_purge_completion_after,1
doradb-storage/src/session.rs,wait_for_maintenance_boundary,4
doradb-storage/src/session.rs,Session::wait_for_gc_horizon_after,2
doradb-storage/src/session.rs,Session::wait_for_purge_completion_after,2
doradb-storage/src/table/access.rs,LazyRow::val,2
doradb-storage/src/table/access.rs,UserTableAccessor::delete_known_cold_row,2
doradb-storage/src/table/access.rs,UserTableAccessor::delete_known_hot_row,3
doradb-storage/src/table/access.rs,UserTableAccessor::delete_unique_mvcc,12
doradb-storage/src/table/access.rs,UserTableAccessor::insert_mvcc,2
doradb-storage/src/table/access.rs,UserTableAccessor::mutate_cold_rows_mvcc,9
doradb-storage/src/table/access.rs,UserTableAccessor::mutate_hot_rows_mvcc,4
doradb-storage/src/table/access.rs,UserTableAccessor::mutate_cold_rows_mvcc,12
doradb-storage/src/table/access.rs,UserTableAccessor::mutate_hot_rows_mvcc,7
doradb-storage/src/table/access.rs,UserTableAccessor::table_mutate_mvcc,1
doradb-storage/src/table/access.rs,UserTableAccessor::update_known_cold_row,4
doradb-storage/src/table/access.rs,UserTableAccessor::update_known_hot_row,5
doradb-storage/src/table/access.rs,UserTableAccessor::update_unique_mvcc_input,18
doradb-storage/src/table/access.rs,UserTableAccessor::validate_table_mutation_update,1
doradb-storage/src/trx/mod.rs,Transaction::commit,1
doradb-storage/src/trx/mod.rs,Transaction::commit,2
doradb-storage/src/trx/mod.rs,Transaction::exec,2
doradb-storage/src/trx/mod.rs,Transaction::lock_table,2
doradb-storage/src/trx/mod.rs,Transaction::rollback,2
doradb-storage/src/trx/stmt.rs,Statement::table_delete_unique_mvcc,3
doradb-storage/src/trx/stmt.rs,Statement::table_delete_unique_mvcc,4
doradb-storage/src/trx/stmt.rs,Statement::table_index_lookup_mvcc,2
doradb-storage/src/trx/stmt.rs,Statement::table_index_scan_mvcc,3
doradb-storage/src/trx/stmt.rs,Statement::table_insert_mvcc,3
doradb-storage/src/trx/stmt.rs,Statement::table_insert_mvcc,4
doradb-storage/src/trx/stmt.rs,Statement::table_lookup_unique_mvcc,2
doradb-storage/src/trx/stmt.rs,Statement::table_mutate_mvcc,2
doradb-storage/src/trx/stmt.rs,Statement::table_scan_mvcc,2
doradb-storage/src/trx/stmt.rs,Statement::table_update_unique_mvcc,4
doradb-storage/src/trx/stmt.rs,Statement::table_upsert_unique_mvcc,4
doradb-storage/src/trx/stmt.rs,Statement::table_update_unique_mvcc,5
doradb-storage/src/trx/stmt.rs,Statement::table_upsert_unique_mvcc,5
doradb-storage/src/trx/stream_stmt.rs,IndexScanMvccStream::next,2
doradb-storage/src/trx/stream_stmt.rs,StreamStmt::table_index_scan_mvcc,5
doradb-storage/src/trx/sys.rs,TransactionSystem::bootstrap,5
doradb-storage/src/trx/sys.rs,TransactionSystem::build,1
doradb-storage/src/trx/sys.rs,TransactionSystem::commit_prepared,1
doradb-storage/src/trx/sys.rs,TransactionSystem::commit_transaction,2
doradb-storage/src/value.rs,ValKind::try_from,1
Loading
Loading