Summary
Two ledger actions cannot be dispatched successfully by any out-of-process client, for different reasons. Both are reachable only as refusals. Recording them so the workflow-coverage work (morph#359's second axis) allowlists them with a stated reason rather than chasing them, and so a client author does not assume they are usable.
1. RunReportJob — service principal only
LedgerModel::execute(const RunReportJob&) refuses any principal but kReportRunnerPrincipal (src/models/ledger_model.cpp:1205-1206):
if (ctx == nullptr || ctx->principal != kReportRunnerPrincipal) {
throw Forbidden{"RunReportJob: only the report runner may run a report job"};
}
This is deliberate and well documented — dto/report_dto.hpp explains the submit→run→poll split and why the run is an action rather than a lambda. No complaint about the design.
The consequence for a client is that the submit → run → poll triple cannot be completed from outside: a scenario submits a report, sees Pending, and cannot advance it. The server's own runner ticks on a timer, and waiting on a timer is precisely what the scenario runner forbids by design (its README: "No sleep, and no wall-clock waits").
Verified against a live server: SubmitReport returns a job id, GetReportStatus returns status == 0 (Pending), and RunReportJob from a user client returns exactly the message above.
2. UndoTransaction — needs a journalId nothing hands out
UndoTransaction{ledgerId, journalId} reverses a journal entry. No action in the rung's wire surface returns a journalId:
StoreTransaction returns GetLedgerResult — the accounts list, verified on the wire as {"accounts":[{"id":…,"name":…,"kind":…,"currency":…,"balance":{…}}]}. No journal id, no receipt.
GetLedger returns the same shape.
- No
GetJournal/ListTransactions action exists.
So a client can only ever call UndoTransaction with an id it guessed. Verified: journalId=999999 returns a not-found error naming the journal, which is the only assertable outcome.
This one looks less like a decision and more like a gap — undo is in the rung's "What to implement" (step 6) and its journal-derived-undo story is one of the subsystems it exists to exercise, but the client-side half of it is unreachable. The in-process tests get the id from the DB.
Verification status
Both reproduced on master against a live ladder_ledger_server, inside a 22-step workflow scenario that otherwise passes 48 assertions with zero surviving mutants. Neither is inferred from reading.
Not verified: whether UndoTransaction's missing id-return is deliberate. Unlike RunReportJob, whose restriction is documented at length in its own DTO comment, I found nothing recording this one.
What would close this
- For
RunReportJob: nothing to change in the rung. A note in the README that the run half is service-only, so client authors do not plan around it, would help — and the coverage allowlist entry cites this issue.
- For
UndoTransaction: either an action that returns journal ids (a GetJournal, or StoreTransaction answering with the created journalId alongside the ledger state), or a recorded decision that undo is a server-side/administrative path with no client story.
Until then both are allowlisted on the workflow-coverage axis with these reasons, which means the gate will not chase them but a reader can see exactly why.
Summary
Two
ledgeractions cannot be dispatched successfully by any out-of-process client, for different reasons. Both are reachable only as refusals. Recording them so the workflow-coverage work (morph#359's second axis) allowlists them with a stated reason rather than chasing them, and so a client author does not assume they are usable.1.
RunReportJob— service principal onlyLedgerModel::execute(const RunReportJob&)refuses any principal butkReportRunnerPrincipal(src/models/ledger_model.cpp:1205-1206):This is deliberate and well documented —
dto/report_dto.hppexplains the submit→run→poll split and why the run is an action rather than a lambda. No complaint about the design.The consequence for a client is that the submit → run → poll triple cannot be completed from outside: a scenario submits a report, sees
Pending, and cannot advance it. The server's own runner ticks on a timer, and waiting on a timer is precisely what the scenario runner forbids by design (its README: "Nosleep, and no wall-clock waits").Verified against a live server:
SubmitReportreturns a job id,GetReportStatusreturnsstatus == 0(Pending), andRunReportJobfrom a user client returns exactly the message above.2.
UndoTransaction— needs ajournalIdnothing hands outUndoTransaction{ledgerId, journalId}reverses a journal entry. No action in the rung's wire surface returns ajournalId:StoreTransactionreturnsGetLedgerResult— the accounts list, verified on the wire as{"accounts":[{"id":…,"name":…,"kind":…,"currency":…,"balance":{…}}]}. No journal id, no receipt.GetLedgerreturns the same shape.GetJournal/ListTransactionsaction exists.So a client can only ever call
UndoTransactionwith an id it guessed. Verified:journalId=999999returns a not-found error naming the journal, which is the only assertable outcome.This one looks less like a decision and more like a gap — undo is in the rung's "What to implement" (step 6) and its journal-derived-undo story is one of the subsystems it exists to exercise, but the client-side half of it is unreachable. The in-process tests get the id from the DB.
Verification status
Both reproduced on master against a live
ladder_ledger_server, inside a 22-step workflow scenario that otherwise passes 48 assertions with zero surviving mutants. Neither is inferred from reading.Not verified: whether
UndoTransaction's missing id-return is deliberate. UnlikeRunReportJob, whose restriction is documented at length in its own DTO comment, I found nothing recording this one.What would close this
RunReportJob: nothing to change in the rung. A note in the README that the run half is service-only, so client authors do not plan around it, would help — and the coverage allowlist entry cites this issue.UndoTransaction: either an action that returns journal ids (aGetJournal, orStoreTransactionanswering with the createdjournalIdalongside the ledger state), or a recorded decision that undo is a server-side/administrative path with no client story.Until then both are allowlisted on the workflow-coverage axis with these reasons, which means the gate will not chase them but a reader can see exactly why.