Skip to content

provider-usage: state how a window's quota comes back - #15

Merged
ualtinok merged 1 commit into
masterfrom
regeneration-field
Aug 17, 2026
Merged

provider-usage: state how a window's quota comes back#15
ualtinok merged 1 commit into
masterfrom
regeneration-field

Conversation

@ualtinok

@ualtinok ualtinok commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Adds an optional regeneration object to RateWindow, present only where an upstream states a replenishment mechanic. Shape settled with @subc across two prior rejections; the third input is a live capture.

Why an object, not a rate

A rate alone cannot separate the two mechanics that produce it. 1,000,000 units per 720h describes both a lump landing on one instant and a steady hourly accrual, and they give opposite answers about headroom on day 14 — a consumer modelling a cliff as gradual believes it has partial capacity when it has exactly none.

That distinction is not hypothetical. It came from a credentialed JetBrains capture posted on cortexkit/insula#1, the first observed rate-bearing payload anyone has produced:

{ "type": "Known", "next": "", "tariff": { "amount": "1000000", "duration": "PT720H" } }

The reporter flagged the wrinkle rather than letting the arithmetic stand for the mechanic. Two earlier shapes were rejected for reasons worth keeping: rate-only cannot express "regenerates, rate unknown", and a kind enum on RateWindow itself would have made absence read as fixed_reset across 32 untagged providers.

Why mechanic is a required string

Required, because an optional discriminator invites exactly the inference the field exists to prevent: with no value present a consumer picks one, and the picker has less evidence than the producer.

String rather than enum, because this is an observability wire — a variant added later must not delete the record reporting a state no consumer has seen. Proved by substitution rather than asserted:

substituting an enum → an_unrecognised_mechanic_decodes_rather_than_dropping_the_window FAILED
  Error("unknown variant `stepped_thaw`, expected one of `cliff`, `drip`, `unstated`")

The whole window fails, so the state a consumer most needs to see is precisely the one that vanishes.

Why an unstated arm

An upstream can state that quota replenishes without anything establishing which mechanic. The honest arm keeps a producer from guessing to satisfy the type, and carries its own contract — display the rate, never derive headroom from it. Same shape and reason as PoolFunding::Unknown and PoolBasis::Unstated.

The pacing consequence of each arm is written at the field, not in a runbook, so the next consumer reads it where they would otherwise re-derive it — and one of the three derivations is silently wrong.

Additive

Three properties pinned rather than claimed: existing producers omit the field, pre-0.7.0 payloads decode into the new shape, and a window without a mechanic serializes byte-identically.

0.6.00.7.0. 23 tests, clippy clean.

Not in this PR

No producer populates it yet. The classifier that decides cliff vs drip vs unstated is producer-side — where the capture evidence lives — and I have one observed payload of each kind, which is enough to seed a classifier that emits unstated when unsure and too little to pin a permanent structural rule. A consumer must never sniff the mechanic from which fields are present.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


Summary by cubic

States how a window’s quota regenerates by adding an optional regeneration object to RateWindow in cortexkit-provider-usage. This prevents consumers from modelling a stated rate as the wrong mechanic (cliff vs drip) and miscomputing headroom.

  • Schema: RateWindow.regeneration appears only when the upstream states replenishment; includes required mechanic (string; accepts cliff, drip, unstated, or unknowns) and optional rate { amount: f64, perMinutes: i64 }.
  • Compatibility: Additive; pre-0.7.0 payloads decode unchanged and windows without regeneration serialize byte-identically. Version 0.7.0.
  • Consumer guidance: Do not infer a mechanic from absence. Treat unknown mechanic values as unstated; display the rate but do not derive headroom.
  • Producers: No producer emits regeneration yet; emit unstated when unsure.

Written for commit eb77573. Summary will update on new commits.

Review in cubic

@ualtinok

Copy link
Copy Markdown
Contributor Author

The wire half is exactly as ruled and better-argued than the ruling — approving on substance; one mechanical revision before merge:

Drop the six .cortexkit/alfonso/context/* files. They're ~2,850 of this PR's 3,022 lines and they're context packages about the store/lease crates — gather artifacts from a different investigation that got staged along with the real change. Beyond the noise, committing them would plant exactly the drifted-evidence class we swept out of subconscious last month: files that look like source-adjacent documentation answering searches with another repo's details. git rm them, force-push the branch, and this merges same-day as committed.

On the substance, three things worth naming as right:

  • The unrecognised-mechanic test proving the string choice by substituting the enum and quoting its failure is the reversion-proof form — the argument lives in the test name the next "improver" will redden.
  • per_minutes doc distinguishing the refill period from window_minutes with the two-pool rationale inline closes the wrong-derivation you caught before it shipped.
  • "Absence licenses nothing" leading the field doc is the right first sentence — it's the sentence a consumer needs before any other.

One nit, take or leave: mechanic's doc says "Treat an unrecognised value as unstated" — consider promoting that consumer rule into the Regeneration struct doc too, since a consumer reading only the struct-level doc would miss it.

Adds an optional `regeneration` object to RateWindow, present only where an
upstream STATES a replenishment mechanic. Absence licenses nothing -- it
means the upstream said nothing, never "fixed window".

WHY AN OBJECT AND NOT A RATE. A rate alone cannot separate the two mechanics
that produce it. "1,000,000 units per 720h" describes both a lump landing on
one instant and a steady hourly accrual, and they give opposite answers about
headroom on day 14: a consumer modelling a cliff as gradual believes it has
partial capacity when it has exactly none. The distinction came from a
credentialed JetBrains capture posted on insula#1 -- the first observed
rate-bearing payload -- whose reporter flagged the wrinkle rather than
letting the arithmetic stand for the mechanic.

WHY `mechanic` IS A REQUIRED STRING. Required, because an optional
discriminator invites exactly the inference this field exists to prevent: with
nothing present a consumer picks, and the picker has less evidence than the
producer. String rather than an enum, because this is an observability wire
and a variant added later must not delete the record reporting a state no
consumer has seen -- proved by substituting an enum, which fails the whole
window with `unknown variant`.

WHY AN `unstated` ARM. An upstream can state that quota replenishes without
anything establishing which mechanic. The honest arm keeps a producer from
guessing to satisfy the type, and carries its own contract: display the rate,
never derive headroom from it. Same shape and same reason as
PoolFunding::Unknown and PoolBasis::Unstated.

The pacing consequence of each arm is stated at the field rather than in a
runbook, so the next consumer reads it where they would otherwise re-derive
it -- and one of the three derivations is silently wrong.

Additive: existing producers omit the field, pre-0.7.0 payloads decode, and
a window without a mechanic serializes byte-identically. All three pinned.
@ualtinok
ualtinok force-pushed the regeneration-field branch from fe27967 to eb77573 Compare August 17, 2026 07:03

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found and verified against the latest diff

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="crates/cortexkit-provider-usage/src/lib.rs">

<violation number="1" location="crates/cortexkit-provider-usage/src/lib.rs:89">
P3: The crate-level "Serialization contract consumers depend on" doc enumerates every camelCase wire key, but the new `regeneration`/`mechanic`/`rate`/`amount`/`perMinutes` keys added in this PR are not listed there. Since the contract list is what consumers compile against, extend it with the new keys so the wire surface is documented in one place.</violation>

<violation number="2" location="crates/cortexkit-provider-usage/src/lib.rs:152">
P3: `per_minutes` is a bare `i64` with no documented positivity invariant. A producer that emits `perMinutes: 0` or a negative value makes a downstream rate computation (amount / per_minutes) divide by zero or go negative, silently corrupting the pacing claim this whole object exists to make. Document (or validate) that `per_minutes` must be > 0.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

/// replenishment — never "this is a fixed window". Most providers state
/// nothing, so absence is the common case and carries no information.
#[serde(skip_serializing_if = "Option::is_none", default)]
pub regeneration: Option<Regeneration>,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The crate-level "Serialization contract consumers depend on" doc enumerates every camelCase wire key, but the new regeneration/mechanic/rate/amount/perMinutes keys added in this PR are not listed there. Since the contract list is what consumers compile against, extend it with the new keys so the wire surface is documented in one place.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At crates/cortexkit-provider-usage/src/lib.rs, line 89:

<comment>The crate-level "Serialization contract consumers depend on" doc enumerates every camelCase wire key, but the new `regeneration`/`mechanic`/`rate`/`amount`/`perMinutes` keys added in this PR are not listed there. Since the contract list is what consumers compile against, extend it with the new keys so the wire surface is documented in one place.</comment>

<file context>
@@ -80,6 +80,76 @@ pub struct RateWindow {
+    /// replenishment — never "this is a fixed window". Most providers state
+    /// nothing, so absence is the common case and carries no information.
+    #[serde(skip_serializing_if = "Option::is_none", default)]
+    pub regeneration: Option<Regeneration>,
+}
+
</file context>

/// match: an observed payload states a 720h refill period on a balance whose
/// percentage is measured against a larger total that includes a purchased
/// pool that never refills.
pub per_minutes: i64,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: per_minutes is a bare i64 with no documented positivity invariant. A producer that emits perMinutes: 0 or a negative value makes a downstream rate computation (amount / per_minutes) divide by zero or go negative, silently corrupting the pacing claim this whole object exists to make. Document (or validate) that per_minutes must be > 0.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At crates/cortexkit-provider-usage/src/lib.rs, line 152:

<comment>`per_minutes` is a bare `i64` with no documented positivity invariant. A producer that emits `perMinutes: 0` or a negative value makes a downstream rate computation (amount / per_minutes) divide by zero or go negative, silently corrupting the pacing claim this whole object exists to make. Document (or validate) that `per_minutes` must be > 0.</comment>

<file context>
@@ -80,6 +80,76 @@ pub struct RateWindow {
+    /// match: an observed payload states a 720h refill period on a balance whose
+    /// percentage is measured against a larger total that includes a purchased
+    /// pool that never refills.
+    pub per_minutes: i64,
 }
 
</file context>
Suggested change
pub per_minutes: i64,
/// Length of the replenishment period in minutes; must be > 0.
///
/// Distinct from the window's own `window_minutes`, which they need not
/// match: an observed payload states a 720h refill period on a balance whose
/// percentage is measured against a larger total that includes a purchased
/// pool that never refills.
pub per_minutes: i64,

@ualtinok
ualtinok merged commit 6344890 into master Aug 17, 2026
8 checks passed
ualtinok added a commit that referenced this pull request Aug 17, 2026
QTA enumerated all 26 CortexKit repos after their gather artifacts rode
into commons PR #15: 24 ignored .cortexkit, two did not (commons,
entorhinal) -- the fixed-the-instances-never-enumerated-the-population
shape in a hygiene pass. A committed context package is the
drifted-evidence class: it wears a source-adjacent path and answers
searches with a different repo's facts.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant