Credits

Why a refund can be missing from your credit history

The list is filtered on movement, not on events. A generation writes two rows and only one of them moves your balance; a refund can be real and still move it by nothing at all.

What the list is actually a list of

The credit history is not a log of everything that happened to your account. It is a list of the rows that moved your balance. The query behind the screen restricts itself to rows on your wallet whose amount is not zero, so a record with no net effect on the balance never reaches the list, however real it is in the ledger.

That single condition explains both of the questions sellers bring to this screen. Where is my refund, and why is there only one line for a generation, have the same answer: the entry exists, and it moved the balance by nothing.

One sentence about timing, inherited rather than re-derived here: the movement you see is fixed at the moment credit is set aside for a run, and when exactly a credit is spent is the subject of a separate page. Everything below is about what reaches the screen afterwards.

Why a written row can fail to appear

Each of these is a property of the read surface, not of your account. They hold for every account on the same screen.

The filter is a single comparison.
The history query selects rows on your wallet where the amount is not zero. Nothing else is excluded, and nothing that satisfies it is hidden. A short list means few movements, not a partial record.
The settling row of a generation carries zero.
Credit is set aside, and later the run settles against what was set aside. The settling row is written with a zero amount, so it is removed by the filter before the list is drawn. The code's own comment records the visible consequence: no double charge is shown to the user.
A refund absorbed by a debt has no net effect.
When the whole of a refund goes to closing an open debt, the balance ends where it started (packages/db/src/credit-lots.ts:443-454). The refund happened; the row it produced does not qualify for the list.
A refund of already-expired credit has no net effect either.
If the credit that had been set aside came from a source that expired at period end, returning it does not raise a spendable balance, so that row is filtered out on the same rule.
The normal failed-run refund is not affected by any of this.
A refund that returns credit to a spendable balance moves the balance and is shown. Invisibility here is specific to accounts carrying a debt or spending credit from an expired period.

What is written, and what reaches the screen

Read the middle column as the ledger and the right column as the screen. The distance between them is the whole subject of this page.

EventWritten in the ledgerShown in the history
Credit set aside for a generationA row that moves the balanceYes
That generation settlingA row whose amount is zeroNo
Refund after a failed run, returning credit to a spendable balanceA row that moves the balanceYes
Refund absorbed entirely by an open debtA row with no net effectNo
Refund of credit drawn from a source that expired at period endA row with no net effectNo

Reading a history that looks short

Each row below is a situation sellers actually arrive with, and the reading that matches how the screen is built.

SituationChooseWhy
A run failed, the credit is back, and there is no refund line.Trust the balance and stop looking for the row.A refund absorbed by an open debt, or one returning credit from a source that had already expired, moves the balance by zero and is filtered out of the list.
There is one line for a generation you know completed.Read that line as the moment credit was set aside, not as the moment the work finished.The settling row carries a zero amount and never reaches the screen, so a single generation can only ever show one line.
You want to know when a generation finished.Use the screen that follows a run in progress, not the ledger.Completion writes nothing that moves the balance, so the history has no record of it to show.
The balance looks higher than what the app will let you spend.Take the smaller figure as the real one and re-check later.The balance is read as stored and the read performs no expiry check; the correcting work happens on the server, on a schedule this page cannot promise.
Credit arrived and the balance rose by less than what was granted.Read the balance as the answer and expect no line explaining the difference.The part of an incoming grant that goes to an open debt does not become spendable, and no separate row in the history accounts for it.
You want to know whether reporting a poor result returns the credit.Treat that as a separate question this page does not answer.The history records balance movement. What the report flow does is documented on its own page.

Where this costs you during a catalogue run

The failure mode is not lost credit. It is a wrong conclusion drawn from a correct screen.

  • Counting rows to reconcile a batch of pieces.

    Count balance movements, because that is what the list contains. A missing refund line is a filter result, not evidence that the refund did not happen.

  • Concluding you were charged twice, or not at all, from the number of lines.

    One line per generation is the designed shape: the second row is written with a zero amount and filtered out. The count of lines is not a count of charges in the ledger.

  • Starting a long batch against the last balance you saw.

    The displayed number can briefly include credit that can no longer be spent. Keep a margin instead of planning a run that depends on the balance being exact at that instant.

  • Using the ledger as a timeline for a shoot.

    There is one timestamp per run — the moment credit was set aside — and none for completion. For anything about the wait, use the screen that owns it.

  • Escalating a support question that the screen has already answered correctly.

    Before reporting a missing entry, check whether the entry would have moved the balance at all. If it would not have, the list is complete as defined.

What the history screen returns

Two things come back, and they answer different questions.

The list: rows on your wallet whose amount is not zero, and nothing else. It is a record of balance movement. It does not carry completion times, quality outcomes, or policy.

The balance: a single stored number, read from the wallet record as it stands at that moment, with no expiry check performed by the read itself. If no wallet record exists, the reply is an empty balance rather than an error — a silent fallback rather than a failure.

Neither answer carries an amount you can compare with a price, a package or a plan. That is outside this surface, and outside this page.

What this page does not cover

Each of these is a real question. None of them is answerable from the surface described above.

  • When exactly a credit is spent during a run. That timing is owned by a separate page and is inherited here in a single sentence.
  • Whether reporting a poor result returns the credit. That is a product flow with its own page.
  • How a debt forms and what it blocks. Only its visible consequence — a smaller increase, with no line explaining it — is stated here.
  • The order in which credit is chosen, split or returned inside the ledger. That algorithm lives in a different part of the code and is not documented by the source behind this page.
  • Whether the periodic expiry job is enabled in a production deployment, how often it runs, or how many copies of the service are running. These depend on deployment settings that cannot be read from the code.
  • When the app's screens re-read your credit or debt state. The client side is not authoritatively described by this source.
  • Any credit amount, price, package or plan name, and any refund policy, entitlement or legal statement.

Where these statements come from

The behaviour on this page was read from the platform source rather than from documentation, as of 2026-08-18.

The balance read and the history filter: apps/api/src/wallet/wallet.service.ts. The balance is returned as the stored number, or zero when no wallet record exists, and the history query restricts rows to those whose amount is not zero. The comment beside that query states the visible consequence directly — a double charge is not shown to the user, because the settling rows carry a zero amount.

The zero net effect of a refund absorbed by a debt or drawn from an expired source: packages/db/src/credit-lots.ts:443-454, in the same file that records that the portion of an incoming grant going to a debt does not become spendable.

The relationship between the burn at the moment credit is set aside and the periodic sweep: apps/api/src/wallet/credit-expiry-sweeper.ts, whose comment names the first as the authoritative check and the second as the job that exists for the honesty of the displayed balance.

Questions

Why is there only one line for a generation?

Because only one of the two rows moved your balance. Credit is set aside for the run and that row appears; the row that settles the run is written with a zero amount, and the list shows only rows whose amount is not zero. Both are written — one reaches the screen.

A refund is missing from my history. Is the ledger wrong?

No. The refund can be real and still have no net effect on the balance: that happens when the whole amount closes an open debt, or when the credit that had been set aside came from a source that expired at period end. A refund after an ordinary failed run does move the balance, and it does appear.

My balance shows credit the app will not let me spend. When does it correct itself?

The balance is read as stored, and that read performs no expiry check of its own. The correcting work happens on the server — at the point where credit is next set aside, and, where it is enabled, through a periodic job. No moment is promised here, because whether that job runs in a given deployment is an environment setting that cannot be read from the code.

Can the history tell me when my generation finished?

No. Finishing does not move the balance, so nothing about it is written into a list built from balance movement. The one timestamp you get per run is the moment credit was set aside for it.