provider-usage: state how a window's quota comes back - #15
Conversation
|
The wire half is exactly as ruled and better-argued than the ruling — approving on substance; one mechanical revision before merge: Drop the six On the substance, three things worth naming as right:
One nit, take or leave: |
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.
fe27967 to
eb77573
Compare
There was a problem hiding this comment.
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>, |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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>
| 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, |
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.
Adds an optional
regenerationobject toRateWindow, 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 720hdescribes 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
RateWindowitself would have made absence read asfixed_resetacross 32 untagged providers.Why
mechanicis a required stringRequired, 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:
The whole window fails, so the state a consumer most needs to see is precisely the one that vanishes.
Why an
unstatedarmAn 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::UnknownandPoolBasis::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.0→0.7.0. 23 tests, clippy clean.Not in this PR
No producer populates it yet. The classifier that decides
cliffvsdripvsunstatedis producer-side — where the capture evidence lives — and I have one observed payload of each kind, which is enough to seed a classifier that emitsunstatedwhen unsure and too little to pin a permanent structural rule. A consumer must never sniff the mechanic from which fields are present.Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.Summary by cubic
States how a window’s quota regenerates by adding an optional
regenerationobject toRateWindowincortexkit-provider-usage. This prevents consumers from modelling a stated rate as the wrong mechanic (cliff vs drip) and miscomputing headroom.RateWindow.regenerationappears only when the upstream states replenishment; includes requiredmechanic(string; acceptscliff,drip,unstated, or unknowns) and optionalrate { amount: f64, perMinutes: i64 }.regenerationserialize byte-identically. Version0.7.0.mechanicvalues asunstated; display the rate but do not derive headroom.regenerationyet; emitunstatedwhen unsure.Written for commit eb77573. Summary will update on new commits.