← 1.0.0 backlogs ISS-8

Two write handlers in one file roll back differently

Type
Bug — inconsistent error path
Found while
SAPE-19
Triage
Defer
Status
Open
Severity
Medium — the window is narrow, and what it leaves behind is sticky

Description

Two handlers in internal/server write to one database through one store, under the same write lock, and they treat a failed write differently.

declare rolls back (internal/server/declare.go:99-110):

stored, err := db.store.DeclareOperation(caller, asked.Operation)
if err != nil {
	if back := db.store.Rollback(); back != nil {
		return nil, fmt.Errorf("%w (and rolling it back failed: %v)", err, back)
	}
	return nil, err
}

invoke does not (internal/server/server.go:757-762):

db.mutex.Lock()
defer db.mutex.Unlock()

result, err := db.store.Invoke(caller, asked.Command, asked.Version, asked.Arguments)
if err != nil {
	return nil, err
}

Evidence

Triage

Does it lose data, break a promise on a public surface, or stop somebody installing and running? Not demonstrably. Committed data is never at risk — the concern is uncommitted bytes left pending after a refusal, and the next successful writer commits or rolls them as part of its own transaction. No wire shape changes either way, and the fix is invisible to every client.

Defer, with a caveat stated plainly: this was deferred because nobody has produced the failing case, not because the reasoning proves there is none. The honest status is "the window is narrow and unmeasured". If somebody does produce a case where a refused invoke leaves Pending() true, the triage answer changes to yes — that is the data-loss branch, and it would be a bug, not debt.

If deferred, what it costs to wait