Class JobKeyLedger
IdempotencyKey bound to
(action, targetId[, principal]), and verifies a submitted key value against that recorded binding.
IdempotencyKey only binds; its consumer records what it minted. This ledger is that record for the
row-job case: a key endpoint calls issue(String, String, String) and answers response(IdempotencyKey),
and the submit endpoint calls verify(String, String, String, String) (usually through
RowJobSubmit.verify(JobKeyLedger, String, String)).
Rules
- One refusal for every miss. Unknown, expired, evicted and mismatched keys all verify empty, and the
caller answers all of them with
REFUSAL_CODE, so the answer is no oracle for "this value exists for another row". - Spent after first use. On the first successful
AsyncJobRegistry.tryCreate(IdempotencyKey)the endpoint callsclaim(IdempotencyKey, String). A later verify returns theJobKeyLedger.Grantwith that job id, and the endpoint replays the job's ref while the registry still has it, or answersSPENT_CODE— never a secondtryCreate, so a replay after the registry's retention cannot start a second job. - Bounded per principal. A principal holds at most
maxPerPrincipalunspent keys per action; one more evicts that principal's oldest.maxEntriesis a global backstop (oldest first). Without auth, every caller is one shared principal. - Same actor on both endpoints. Resolve the principal the same way on the key endpoint and on submit, or verify fails.
In-memory and single-process, like AsyncJobRegistry. Safe for concurrent use.
Example:
See Also:
- Since:
- 10.0.0
-
Nested Class Summary
Nested Classes -
Field Summary
FieldsModifier and TypeFieldDescriptionstatic final StringThe contract version ofresponse(IdempotencyKey).static final intThe global entry cap by default.static final intThe per-principal, per-action cap on unspent keys by default.static final DurationHow long an issued key stays verifiable by default.static final StringThe refusal code for every failed verify; the codeIdempotencyKey's Javadoc already uses.static final StringThe refusal code for a key that already started a job that is no longer in the registry. -
Method Summary
Modifier and TypeMethodDescriptionvoidclaim(IdempotencyKey key, String jobId) Records the job a key started, so a later verify carries it and the key can never start a second one.static JobKeyLedgercreate()Creates a ledger with the default limits.Mints and records a key bound to(action, targetId)with no principal.Mints and records a key bound to(action, targetId)and to the actor.maxEntries(int value) maxPerPrincipal(int value) response(IdempotencyKey key) The key endpoint's response body:{contractVersion, action, targetId, idempotencyKey}.intsize()The number of recorded keys, for tests and diagnostics.toString()Verifies a submitted key against a binding with no principal.Verifies a submitted key against a binding and an actor.
-
Field Details
-
REFUSAL_CODE
The refusal code for every failed verify; the codeIdempotencyKey's Javadoc already uses.- See Also:
-
SPENT_CODE
The refusal code for a key that already started a job that is no longer in the registry.- See Also:
-
DEFAULT_TTL
How long an issued key stays verifiable by default. -
DEFAULT_MAX_ENTRIES
The global entry cap by default.- See Also:
-
DEFAULT_MAX_PER_PRINCIPAL
The per-principal, per-action cap on unspent keys by default.- See Also:
-
CONTRACT_VERSION
The contract version ofresponse(IdempotencyKey).- See Also:
-
-
Method Details
-
create
Creates a ledger with the default limits.- Returns:
- A new ledger.
-
ttl
- Parameters:
value- How long an issued key stays verifiable. Must be positive.- Returns:
- This object.
-
maxEntries
- Parameters:
value- The global entry cap. Must be positive.- Returns:
- This object.
-
maxPerPrincipal
- Parameters:
value- The per-principal, per-action cap on unspent keys. Must be positive.- Returns:
- This object.
-
clock
- Parameters:
value- The clock, for tests.- Returns:
- This object.
-
issue
Mints and records a key bound to(action, targetId)with no principal.- Parameters:
action- The action id. Must not benull or blank.targetId- The row id. Must not benull or blank.- Returns:
- The new key.
- Throws:
IllegalArgumentException- If an argument isnull or blank.
-
issue
Mints and records a key bound to(action, targetId)and to the actor.- Parameters:
action- The action id. Must not benull or blank.targetId- The row id. Must not benull or blank.principal- The actor. Must not benull or blank.- Returns:
- The new key.
- Throws:
IllegalArgumentException- If an argument isnull or blank.
-
verify
Verifies a submitted key against a binding with no principal.- Parameters:
value- The submitted key value. Can benull .action- The action the submit is for. Can benull .targetId- The row the submit is for. Can benull .- Returns:
- The grant, or empty for every kind of miss.
-
verify
public Optional<JobKeyLedger.Grant> verify(String value, String action, String targetId, String principal) Verifies a submitted key against a binding and an actor.- Parameters:
value- The submitted key value. Can benull .action- The action the submit is for. Can benull .targetId- The row the submit is for. Can benull .principal- The actor. Can benull (then only a key issued without a principal verifies).- Returns:
- The grant, or empty for every kind of miss.
-
claim
Records the job a key started, so a later verify carries it and the key can never start a second one.Idempotent for the same job id. A key that is no longer recorded is ignored.
- Parameters:
key- The verified key. Must not benull .jobId- The job it started. Must not benull or blank.- Throws:
IllegalArgumentException- If an argument isnull or blank.IllegalStateException- If the key already started a different job.
-
size
The number of recorded keys, for tests and diagnostics.- Returns:
- The entry count after a sweep.
-
response
The key endpoint's response body:{contractVersion, action, targetId, idempotencyKey}.- Parameters:
key- The issued key. Must not benull .- Returns:
- An ordered map.
-
toString
-