Skip to content

feat: report Coder Agent runtime hours usage in entitlements - #27796

Open
jaaydenh wants to merge 15 commits into
mainfrom
entitlements-80f1
Open

feat: report Coder Agent runtime hours usage in entitlements#27796
jaaydenh wants to merge 15 commits into
mainfrom
entitlements-80f1

Conversation

@jaaydenh

@jaaydenh jaaydenh commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

closes CODAGT-852

Populates codersdk.Feature.Actual for FeatureAgentRuntimeHours from hb_agent_runtime_v1 usage events, and emits a warning when the deployment reaches its advisory soft limit or its runtime hour allocation. Until now A1 shipped the claims (Limit/SoftLimit/HardLimit) but left Actual nil, so nothing reported how much runtime had actually been consumed.

Adds GetTotalUsageHBAgentRuntimeV1, which sums hb_agent_runtime_v1 runtime_ms over the winning license's usage period with start-inclusive, end-exclusive bounds. Unlike GetTotalUsageDCManagedAgentsV1 it reads usage_events directly rather than the daily rollup: this event type is exactly one row per hourly bucket deployment-wide, and day-truncated bounds would charge up to 24 buckets from outside the license term against it (double-charging renewal boundary days). The result is bucket-granular rather than exact; the query doc records the attribution model and the retention dependency.

Migration 000565_usage_events_agent_runtime_invariants enforces the two invariants that SUM correctness and bucket attribution rest on: a partial unique index on (event_type, created_at) for hb_agent_runtime_v1 (replacing the non-unique index from 000561), and a CHECK that created_at is an exact UTC hour boundary. Both validate existing rows, so a pre-existing violator aborts the upgrade loudly; that is deliberate, since every supported writer has always produced conforming rows and silently deleting usage rows in a migration is worse. InsertUsageEvent keeps its ON CONFLICT (id) DO NOTHING arbiter, so a duplicate bucket under a different id raises instead of being dropped; the generator recognizes the one benign way that can happen (two replicas racing the same deterministic id) and treats it as the other replica winning.

Usage events record milliseconds while the agent_runtime_hours_* claims are in hours, so agentRuntimeMsToHours floors milliseconds to whole hours before Actual is set. Actual therefore shares a unit with Limit, SoftLimit and HardLimit (documented on codersdk.Feature.Actual), otherwise Feature.Compare and every consumer would be comparing mixed units. Flooring also means the rendered value and the warnings can never disagree, and a discarded sub-hour remainder can never be the difference between tripping and not tripping an hour-granularity threshold.

At most one threshold warning is emitted: reaching the allocation supersedes the advisory soft limit, so the banner never stacks both messages. The warning texts are constants in codersdk/licenses.go; TestLicenseAgentRuntimeHoursWarningTexts pins the prefix couplings the dashboard's LicenseBanner classifies by. The advisory soft-limit warning renders muted and without a sales link; allocation-reached renders prominently with one.

Unusable runtime-hour claim combinations (a soft limit at or above the allocation, a threshold without an allocation, a negative value) never invalidate the signed license: decodeAgentRuntimeHours drops the unusable claims and the deployment publishes LicenseAgentRuntimeHoursClaimsIgnoredWarningText, so a mis-issued license is visible without costing the customer their deployment.

Usage measurement failures are logged server-side with their cause; the entitlements payload carries a stable operator-facing text instead of the raw driver error, since GET /api/v2/entitlements is reachable without authentication (the managed-agent sibling got the same treatment). These diagnostics stay in entitlements.Errors, so the documented coderd_license_errors Prometheus gauge keeps counting measurement failures exactly as it did on main; the dashboard recognizes the exact texts and renders them as muted diagnostics rather than license errors. A usage-query failure aborts the refresh only when the refresh's own context is already dead (the caller went away or coderd is shutting down); the abort deliberately never keys on the error looking like a cancel, since Postgres raises SQLSTATE 57014 for statement_timeout kills as well as client cancels, so any failure with a live context degrades into the stable diagnostic instead. A nil usage closure is a developer error that fails LicensesEntitlements outright.

Three notes for reviewers:

  • Query name. The issue specifies GetTotalUsageCoderAgentRuntimeV1, but the event type that actually shipped in B2 is hb_agent_runtime_v1. I named the query after the event type to match the GetTotalUsageDCManagedAgentsV1 / dc_managed_agents_v1 precedent. Happy to rename if you prefer the literal name from the issue.
  • Actual is hours, not milliseconds. CODAGT-855 (F1) currently describes actual as milliseconds needing conversion. It should render actual directly as hours.
  • Warning copy. The warning strings are drafted in this PR, not specified by CODAGT-852, and should be confirmed by whoever owns the license terms before this ships.

Hard limit enforcement (CODAGT-856), structured and audience-scoped warnings (F4), and any UI (F1, F5) are out of scope. Follow-ups filed from review: CODAGT-890 (auth model for the entitlements endpoint's usage data) and CODAGT-891 (docs for runtime hours, allocation, and limits).

Closes https://linear.app/codercom/issue/CODAGT-852/b4-wire-runtime-usage-into-entitlements-actual-threshold-warnings

Populate codersdk.Feature.Actual for FeatureAgentRuntimeHours from the
usage_events_daily rollup, and warn when the deployment reaches its
advisory soft limit or its runtime hour allocation.

Adds GetTotalUsageHBAgentRuntimeV1, which sums hb_agent_runtime_v1
runtime_ms over the winning license's usage period. It mirrors
GetTotalUsageDCManagedAgentsV1, including its UTC-day truncation and
inclusive bounds.

Usage events record milliseconds while the agent_runtime_hours_* claims
are in hours, so agentRuntimeMsToHours floors milliseconds to whole hours
before Actual is set. Actual therefore shares a unit with Limit,
SoftLimit and HardLimit, and the rendered value can never disagree with
the warnings.

At most one warning is emitted: reaching the allocation supersedes the
soft limit, so the banner never stacks both messages. Hard limit
enforcement and audience-scoped warnings are out of scope.
@linear-code

linear-code Bot commented Aug 3, 2026

Copy link
Copy Markdown

CODAGT-852

@jaaydenh

jaaydenh commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

@codex review
/coder-agents-review

@coder-agents-review

coder-agents-review Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Chat: Review posted | View chat
Requested: 2026-08-09 07:04 UTC by @jaaydenh
Spend: $68.01 / $100.00

Review history
  • R1 (2026-08-04): 22 reviewers, 6 Nit, 7 Note, 6 P2, 12 P3, 1 P4, COMMENT. Review
  • R2 (2026-08-05), 6 Nit, 7 Note, 6 P2, 12 P3, 1 P4, COMMENT. Review
  • R3 (2026-08-05): 25 reviewers, 11 Nit, 11 Note, 10 P2, 30 P3, 3 P4, COMMENT. Review
  • R4 (2026-08-06): 27 reviewers, 15 Nit, 12 Note, 1 P0, 20 P2, 43 P3, 4 P4, REQUEST_CHANGES. Review
  • R5 (2026-08-06): 27 reviewers, 20 Nit, 12 Note, 1 P0, 24 P2, 48 P3, 4 P4, COMMENT. Review
  • R6 (2026-08-07), 20 Nit, 12 Note, 1 P0, 24 P2, 48 P3, 4 P4, COMMENT. Review
  • R7 (2026-08-09): 25 reviewers, 23 Nit, 13 Note, 1 P0, 26 P2, 53 P3, 4 P4, COMMENT. Review

deep-review v0.9.0 | Round 7 | b5c9e8e..907c91a

Last posted: Round 7, 120 findings (1 P0, 26 P2, 53 P3, 4 P4, 23 Nit, 13 Note), COMMENT. Review

Finding inventory

Finding inventory: PR #27796

Findings

# Sev Status Location Summary Round Reviewer Posted
CRF-1 P2 Author fixed (41bda6d) enterprise/coderd/license/license.go:801 Advisory soft-limit warning renders at same prominence and CTA as allocation-exceeded warning R1 Netero Yes
CRF-2 P3 Author fixed (e42b139) enterprise/coderd/license/license_internal_test.go:183 TestAgentRuntimeMsToHoursNoOverflow asserts a condition that cannot fail R1 Netero Yes
CRF-3 Note Author fixed (e42b139; clamp kept, doc reframed around the caller-supplied AgentRuntimeMsFn seam) enterprise/coderd/license/license.go:1033 ms <= 0 clamp unreachable in production; tests pin unproducible input R1 Netero Yes
CRF-4 Note Merged into CRF-11 codersdk/licenses.go:24 Soft-limit warning text is a strict prefix of the allocation-exceeded text R1 Netero No
CRF-5 Note Author fixed (e42b139, partial by design; full evaluate/populate/warn split left to the existing license.go TODO) enterprise/coderd/license/license.go:767 Runtime-hours usage block and query are structural copies of the managed-agent pair R1 Netero Yes
CRF-6 Note Author fixed (e42b139) enterprise/coderd/license/license.go:770 No test exercises the AgentRuntimeMsFn == nil dev-error sentinel R1 Netero Yes
CRF-7 P2 Author fixed (e42b139) enterprise/coderd/license/license.go:786 Actual becomes hours for this feature; codersdk.Feature.Actual documents no unit and the F1 ticket already reads it as milliseconds R1 Ryosuke P2, Leorio P2, Pariston P3, Knov P3, Chopper P3, Meruem P3, Razor P3, Mafu-san P3, Luffy P3 Yes
CRF-8 P2 Author fixed (e42b139) enterprise/coderd/license/license.go:762 Generator forfeits buckets older than a 7-day window with no backfill, so Actual can be permanently and unboundedly low while the comment claims only lag R1 Chopper P2, Razor P2, Hisoka P3, Mafuuu P3, Leorio P3, Komugi Note Yes
CRF-9 P2 Author fixed (e42b139; query moved off the rollup to exact half-open bounds on usage_events) coderd/database/queries/usageevents.sql:140 Day-truncated inclusive bounds charge up to 24 hourly buckets from outside the license term, double-counting the renewal day against two allocations; the aggregate-table rationale does not hold for this event type R1 Knuckle P2, Knov P3 Yes
CRF-10 P2 Author fixed (e42b139; validateAgentRuntimeHours now rejects soft <= 0) enterprise/coderd/license/license.go:800 agent_runtime_hours_limit_soft: 0 is a valid claim and makes the soft-limit warning fire at zero usage, permanently R1 Knov P2, Ryosuke P2 Yes
CRF-11 P2 Author fixed (e42b139; both texts now begin "Your deployment has used", pinned by TestLicenseAgentRuntimeHoursWarningTexts) codersdk/licenses.go:24 Both warning texts match the AI Governance near-limit prefix, so the banner renders them in the muted variant and cannot distinguish advisory from billable R1 Melody P2, Zoro P3, Netero (CRF-4) Yes
CRF-12 P2 Deferred (CODAGT-890) enterprise/coderd/license/license.go:797 Consumed hours and licensed allocation are served by the unauthenticated GET /api/v2/entitlements, whose swagger annotation claims a session token is required R1 Kurapika P2, Kite Note Yes
CRF-13 P3 Author fixed (e42b139; both call sites, cause logged server-side) enterprise/coderd/license/license.go:782 Raw driver error string is appended to entitlements.Errors and served unauthenticated; message names no consequence or action R1 Kite P3, Kurapika P3, Leorio P3 Yes
CRF-14 P3 Author fixed (41bda6d) enterprise/coderd/license/license.go:768 Zero-allocation license still runs the query every refresh and publishes actual: 50, limit: 0 for a feature reported as disabled R1 Knov P3, Kite P3, Luffy P3, Mafu-san Note, Mafuuu Note, Zoro Note Yes
CRF-15 P3 Author fixed (e42b139; direct map assignment) enterprise/coderd/license/license.go:787 AddFeature is used as a field patch; the write only lands because Compare's last tiebreaker prefers a non-nil Actual R1 Meruem P3, Knov Note Yes
CRF-16 P3 Author fixed (e42b139; third placeholder names the soft limit) enterprise/coderd/license/license.go:803 Soft-limit warning formats the allocation, never the soft limit, so the operator cannot tell why the banner appeared; no other surface exposes the soft limit either R1 Chopper P3, Leorio P3, Hisoka Note, Gon Note Yes
CRF-17 P3 Author fixed (e42b139) enterprise/coderd/license/license_test.go:1697 Six new cases index Warnings[0] after a non-fatal assert.Len, so a failure panics and takes the package test binary down R1 Komugi P3 Yes
CRF-18 P3 Author fixed (e42b139) coderd/database/querier_test.go:10878 The "other event types are never mixed in" assertion cannot fail; the event_type filter's existence is unproven R1 Bisky P3 Yes
CRF-19 P3 Author fixed (e42b139) enterprise/coderd/license/license.go:794 runtimeHours.Limit != nil can never be false, so it is a branch no test can reach and it implies an impossible state R1 Bisky P3 Yes
CRF-20 P3 Author fixed (e42b139; appendAgentRuntimeHoursWarning added) enterprise/coderd/license/license.go:795 The two-tier warning ladder is inlined when appendAIGovernanceSeatLimitWarning is the established extracted form for exactly this shape R1 Robin P3, Zoro Nit Yes
CRF-21 P3 Author fixed (e42b139) enterprise/coderd/licenses_test.go:160 Negative warning assertion matches a hand-copied substring, so rewording either constant silently disarms it R1 Chopper P3, Robin Nit Yes
CRF-22 P3 Author fixed (e42b139) enterprise/coderd/license/license.go:757 The UTC-truncation caveat and the ms-to-hours rationale are each written three or four times across layers R1 Gon P2, Leorio Nit, Meruem Nit, Zoro Note Yes
CRF-23 P3 Deferred (CODAGT-891) codersdk/licenses.go:30 Nothing in docs/ defines a runtime hour, the allocation, or the soft limit, while the banner tells customers usage "may be billable" R1 Razor P3, Leorio P4 Yes
CRF-24 P4 Author accepted R2 (a third threshold collides with the at-most-one-warning rule and prefigures F4; C1 owns HardLimit and should be revisited if C1 dies) enterprise/coderd/license/license.go:796 HardLimit is decoded and validated but no threshold reads it, so crossing the contractual ceiling produces the same message as reaching the allocation R1 Mafuuu P4, Pariston Note, Kite Note Yes
CRF-25 Nit Author fixed (e42b139) enterprise/coderd/license/license_test.go:938 Comment says "AI Gateway" for an AI Governance seats warning R1 Gon Yes
CRF-26 Nit Author fixed (e42b139) enterprise/coderd/license/license_test.go:1884 GracePeriod case comment describes winner selection the case never exercises R1 Gon, Bisky, Pariston Yes
CRF-27 Nit Author fixed (e42b139; closure returns a poison value) enterprise/coderd/license/license_test.go:1835 t.Error in the case closure captures the parent t, not the subtest's R1 Bisky, Komugi Yes
CRF-28 Nit Author fixed (e42b139; renamed to LicenseAgentRuntimeHoursAllocationReachedWarningText) codersdk/licenses.go:25 AllocationExceeded fires on >=, so it fires when the allocation is reached and nothing is exceeded R1 Gon, Ryosuke Note Yes
CRF-29 Nit Author fixed (e42b139; LicenseOptions.AgentRuntimeHours added) enterprise/coderd/license/license_test.go:1374 Runtime-hour claims are wired ad hoc where LicenseOptions.ManagedAgentLimit/AIGovernanceAddon builders exist R1 Robin Yes
CRF-30 Nit Author contested; panel closed R3 (12/13 accept) codersdk/licenses.go:30 The over-allocation text reuses the under-limit sentence shape instead of stating the overage, unlike its AI Governance sibling R1 Mafuuu Yes
CRF-31 Note Author fixed (e42b139; PR body names the sentence needing license-terms sign-off) codersdk/licenses.go:30 "Additional usage may be billable" is a billing assertion with no cited owner R1 Mafu-san Yes
CRF-32 Note Author fixed (41bda6d) coderd/database/queries/usageevents.sql:136 The query depends on invariants stated nowhere near it (event-type literal, deterministic bucket IDs); a break fails silently as "0 hours used" R1 Meruem, Knov Yes
CRF-33 Note Author contested; panel closed R3 (12/12 accept) coderd/database/queries/usageevents.sql:130 The rollup is written at insert time regardless of publish outcome, so Actual counts buckets the collector permanently rejected R1 Knuckle Yes
CRF-34 Note Deferred (F1, identified in the PR body as CODAGT-855; ticket body not verified, no Linear access) site/src/modules/dashboard/entitlements.ts:19 Populating Actual makes getFeatureVisibility's limitCompliant false once usage passes the allocation, which would hide an F1 panel exactly when it matters R1 Razor Yes
CRF-35 P2 Author fixed (41bda6d) coderd/database/queries/usageevents.sql:147 Half-open created_at bounds assign each whole hourly bucket to the period containing its start, so runtime is misattributed across every license boundary that is not hour-aligned, and the query doc claims the bounds are exact R3 Netero Yes
CRF-36 P3 Author fixed (41bda6d) enterprise/coderd/licenses_test.go:147 Comment says Actual is read from the usage_events_daily rollup; the CRF-9 fix moved this read path onto usage_events R3 Netero Yes
CRF-37 P3 Author fixed (41bda6d) enterprise/coderd/license/license_test.go:928 Sibling stale comment: "the production closure that reads the aggregate table" describes a closure that now reads usage_events directly R3 Netero Yes
CRF-38 Nit Author fixed (41bda6d) enterprise/coderd/license/license_internal_test.go:178 TestAgentRuntimeMsToHoursNoOverflow pins the divisor, not overflow; int64 division by a positive constant cannot overflow R3 Netero Yes
CRF-39 P2 Author fixed (41bda6d) codersdk/licenses.go:25 The constants' doc asserts a LicenseBanner coupling that does not exist, and the two new texts now share their own split-on-placeholder prefix, which is the classifier key the codebase actually uses R3 Gon P2, Chopper P3, Hisoka P3, Meruem P3, Leorio P3, Pariston P3, Ryosuke P3, Razor P3, Zoro P3, Nami P3, Melody Nit Yes
CRF-40 P3 Author fixed (41bda6d) codersdk/licenses_test.go:17 The test's rationale describes exact-match link dispatch as prefix-sensitive; the two mutual-prefix assertions guard a property no consumer has R3 Bisky P3, Nami P3, Gon P3, Leorio P3, Robin P3, Hisoka P3, Meruem P3, Chopper P3, Pariston P3 Yes
CRF-41 P2 Author fixed (41bda6d) enterprise/coderd/license/license.go:1137 The CRF-10 fix rejects the whole signed license for agent_runtime_hours_limit_soft: 0, dropping the deployment to unlicensed and taking every paid feature with it R3 Pariston P2, Mafuuu P2, Kite P2, Kurapika P3 Yes
CRF-42 P3 Author fixed (41bda6d) enterprise/coderd/license/license.go:813 The nil-AgentRuntimeMsFn sentinel is discarded, so the operator is told to read a log nothing wrote, and the message is byte-identical to a real query failure R3 Bisky P3, Chopper P3, Leorio P3, Kite P3 Yes
CRF-43 P3 Author fixed (41bda6d) enterprise/coderd/license/license.go:813 A usage-query failure goes into entitlements.Errors, turning the whole banner red with the heading "License errors require attention" and a contact-sales CTA under a message that says to read the logs R3 Nami P3, Mafuuu P3, Pen Botter P3, Ryosuke P3 Yes
CRF-44 P3 Author fixed (41bda6d) enterprise/coderd/license/license.go:146 Both closure error branches, including the logger.Error the operator message points at, and the managed-agent query-error branch have zero test coverage R3 Chopper P3, Mafu-san P3, Kite P3 Yes
CRF-47 P3 Author fixed (41bda6d) coderd/database/queries/usageevents.sql:146 @start_date/@end_date name instants as dates on the one query whose correctness turns on that distinction, matching the sibling that means day-truncated inclusive bounds R3 Knov P3, Gon P3, Robin Nit Yes
CRF-48 P3 Author fixed (41bda6d) coderd/database/queries/usageevents.sql:133 A bucket holds the full duration of turns whose row landed in that hour, not runtime that elapsed in it, so no choice of bounds makes this query exact and prorating cannot fix it R3 Hisoka P3, Komugi Note, Pariston Note Yes
CRF-49 P4 Author fixed (41bda6d) enterprise/coderd/license/license.go:786 chat_messages.created_at is transaction-start time, so a transaction outliving AgentRuntimeEligibilityLag lands rows in a sealed bucket and its runtime is dropped permanently R3 Komugi P4 Yes
CRF-50 P3 Author fixed (41bda6d) coderd/database/queries/usageevents.sql:129 The load-bearing "created_at is the bucket start" invariant is a comment; the shared heartbeat insert seam stamps wall-clock time and nothing in the schema rejects a misaligned row R3 Meruem P3 Yes
CRF-51 P3 Author fixed (41bda6d) coderd/database/queries/usageevents.sql:133 Nothing enforces one row per bucket, so a duplicate under a different id permanently overstates Actual and the rollup, in the opposite direction from CRF-32's silent zero R3 Knov P3 Yes
CRF-52 P3 Author fixed (41bda6d) site/src/modules/dashboard/LicenseBanner/LicenseBannerView.stories.tsx:269 Two new user-facing warning classes ship with no story, so the rendering they were engineered around is pinned only from Go and cannot fail on a TypeScript change R3 Nami P3, Kite P3 Yes
CRF-54 P3 Deferred (CODAGT-891 and F1/CODAGT-855) enterprise/coderd/license/license.go:973 Both warnings pin a prominent, undismissable, deployment-wide banner for the remainder of the usage period, for states the license permits R3 Pen Botter P3, Chopper (evidence) Yes
CRF-55 P3 Author fixed (41bda6d) enterprise/coderd/license/license.go:781 Three production comments cite C1, a plan-document label with no meaning in this repo, where the convention is the CODAGT ticket ID R3 Gon P2 Yes
CRF-56 P3 Author fixed (41bda6d) enterprise/coderd/license/license.go:143 The fix commits introduced fresh comment duplication: the log-here/message-there coupling is written four times and the zero-allocation rationale three times plus once in tests R3 Gon P3, Zoro Nit, Robin Nit Yes
CRF-58 P3 Author fixed (41bda6d) enterprise/coderd/license/license.go:813 The two new operator-facing error strings are inline literals, so the tests asserting them hand-copy substrings, the class CRF-21 fixed for the warning texts R3 Ryosuke P3 Yes
CRF-59 P3 Author fixed (41bda6d) codersdk/deployment.go:395 The Actual doc comment's bullet list flattens into a run-on cell with stray hyphens and double spaces in the published API reference R3 Leorio P3, Razor Nit, Kite Nit, Melody Nit Yes
CRF-60 P3 Author fixed (41bda6d) codersdk/licenses.go:33 The warnings never say over what period the hours accrued, and a runtime hour is an agent-hour, so "used 150 of the 100 hours" two days into a term is correct and reads as a bug R3 Luffy P3, Knov Note, Pen Botter Note Yes
CRF-61 Note Deferred (CODAGT-890) enterprise/coderd/license/license.go:437 CRF-13's rule holds only at the two call sites the fix touched; the JWT parse error above still puts a raw error on the same unauthenticated payload R3 Kurapika Yes
CRF-62 Nit Author fixed (41bda6d) enterprise/coderd/coderdenttest/coderdenttest.go:242 AgentRuntimeHours(allocation, softLimit, hardLimit) overloads 0 as "omit" for two params and as a real claim value for the third, and silently drops negative claims R3 Knov Nit, Gon Nit, Kite Nit, Zoro Nit Yes
CRF-63 Nit Author contested; dropped by orchestrator R4 (no panel ran; Netero verified the drift is contained to one test file and folding is a larger diff than the defect) coderd/database/querier_test.go:10820 Third local usage-event insert closure in this file, copied from the sibling with the IDs swapped R3 Robin Nit Yes
CRF-64 Note Author fixed (41bda6d) coderd/database/queries/usageevents.sql:125 The query doc justifies usage_events over the rollup but never over chat_messages, which is where genuinely exact bounds live R3 Robin Note, Razor Note Yes
CRF-65 Nit Author fixed (41bda6d) codersdk/licenses_test.go:22 strings.Split(s, sep)[0] where strings.Cut is the stdlib form and its discarded ok is the assertion the test wants R3 ging-go Nit Yes
CRF-66 Nit Author fixed (41bda6d) enterprise/coderd/license/license_test.go:1363 Helper comment says validation requires a hard limit alongside a positive allocation; it only constrains the value when the claim is present R3 Meruem Nit Yes
CRF-67 Note Deferred (F4/CODAGT-854) enterprise/coderd/license/license.go:817 A metered, potentially billable threshold has no alertable metric; the banner is the only signal R3 Ryosuke Note Yes
CRF-68 P3 Author fixed (41bda6d) enterprise/coderd/license/license.go:765 The CRF-15 fix replaced AddFeature with a direct map write in the new block and left the identical field-patch on the managed-agent block three lines above R3 Melody P3, Meruem P4 Yes
CRF-69 P4 Author fixed (41bda6d) coderd/database/queries/usageevents.sql:119 CRF-9's bug class is fixed here and left standing in GetTotalUsageDCManagedAgentsV1, and the new comment names that sibling without noting the defect remains R3 Mafu-san P4 Yes
CRF-70 Note Author fixed (41bda6d) coderd/database/queries/usageevents.sql:137 CRF-33's accepted semantics (locally recorded, not collected, runtime) are documented in the generator but not on the read path a debugger lands on R3 Leorio Note Yes
CRF-71 P2 Author fixed (747e8fd); author contests the finding's race analysis, panel to weigh coderd/database/queries/usageevents.sql:21 The (id) arbiter was removed from the shared usage-event insert to fix a race the generator cannot produce, silently suppressing every future unique violation on usage_events for every event type R4 Netero Yes
CRF-72 P2 Author fixed (747e8fd) coderd/database/migrations/000562_usage_events_agent_runtime_invariants.up.sql:16 The migration says a duplicate bucket "surfaces as an insert error and gets logged"; the same commit made it silently suppressed, in an artifact that cannot be corrected after it ships R4 Netero, Law Yes
CRF-73 P2 Author fixed (747e8fd) enterprise/coderd/license/license.go:1116 Deleting validateAgentRuntimeHours replaced license rejection with claim-dropping that emits no error, warning or log, so a mis-issued license is undetectable from the deployment R4 Netero Yes
CRF-74 P3 Author fixed (747e8fd) enterprise/coderd/license/license.go:136 The query runs under AsSystemRestricted when AsUsagePublisher already carries exactly the permission the query checks R4 Netero Yes
CRF-75 P3 Author fixed (747e8fd) site/src/modules/dashboard/LicenseBanner/LicenseBannerView.stories.tsx:304 The new stories assert text and link only; all 14 pass with the muted-variant classifier disabled, so the CRF-1 fix ships with no regression test R4 Netero Yes
CRF-76 P3 Author fixed (747e8fd) enterprise/coderd/license/license.go:744 LicenseManagedAgentUsageNotConfiguredWarningText is emitted by production code and asserted by no test, and the branch changes a nil closure from an error to a warning R4 Netero Yes
CRF-77 P2 Author fixed (747e8fd) coderd/database/migrations/000562_usage_events_agent_runtime_invariants.up.sql:8 The CHECK constraint, the unique index and the arbiter change that pairs with them ship with no test at all R4 Law P2, Netero P3 Yes
CRF-78 P3 Author fixed (747e8fd) enterprise/coderd/usage/generator.go:52 Four comments still name ON CONFLICT (id) DO NOTHING as what makes concurrent replicas safe; the arbiter no longer exists. Four panel reviewers found a fourth site Netero missed, querier_test.go:10766, which is a test's stated reason for passing R4 Netero, Hisoka, Mafuuu, Komugi, Knuckle Yes
CRF-79 Nit Author fixed (747e8fd) enterprise/coderd/license/license.go:749 The managed-agent context-cancellation branch is the only new statement in either usage block with zero coverage R4 Netero Yes
CRF-80 P0 Author fixed (747e8fd) coderd/database/migrations/000562_usage_events_agent_runtime_invariants.up.sql:1 Migration version 000562 is already taken on main, so the merged tree holds two 000562 migrations and coderd cannot load them R4 Law Yes
CRF-81 Note Author accepted R5 (deliberate abort; reasoning on thread) coderd/database/migrations/000562_usage_events_agent_runtime_invariants.up.sql:20 CREATE UNIQUE INDEX over a populated table aborts the upgrade if any deployment already holds two rows for one bucket R4 Law Yes
CRF-82 P2 Author fixed (747e8fd) enterprise/coderd/license/license.go:759 Demoting usage-query failures from Errors to Warnings removes them from coderd_license_errors and enrolls them in the entitlement warning HTTP header, silently changing two documented gauges R4 Hisoka P2, Razor P3, Kite P3, Melody P3 Yes
CRF-83 P2 Author fixed (747e8fd) site/src/modules/dashboard/LicenseBanner/LicenseBanner.tsx:177 The four usage-measurement diagnostics are excluded from isMutedWarning, so a database blip paints the prominent orange banner headed "Your license limits have been exceeded" R4 Nami P2, Knov P3, Meruem P3, Chopper P3, Ryosuke P3, Pariston P3, Kite P3, Hisoka P3, Mafuuu P3, Melody P3 Yes
CRF-84 P2 Author fixed (747e8fd) enterprise/coderd/license/license.go:1018 The claim constants still document parse-time validation this diff deleted, including a soft-limit range that now contradicts the decoder R4 Mafuuu P2, Razor P2, Gon P2, Meruem P3, Leorio P3 Yes
CRF-85 P2 Author fixed (747e8fd) enterprise/coderd/license/license.go:741 The commit that fixed the comment-duplication finding rewrote the managed-agent block into a line-for-line copy of the runtime block, tripling verbatim rationale R4 Gon P2, Zoro P3, Robin P3, Razor P3, Mafu-san P3, Ryosuke P3 Yes
CRF-86 P2 Author fixed (747e8fd) enterprise/coderd/license/license.go:1104 The PR description still describes the design the head commit replaced, including "No migration is needed" and the opposite behaviour for a zero soft limit; this repo squash-merges R4 Mafu-san P2, Leorio P2, Kite P3 Yes
CRF-87 P2 Author fixed (747e8fd) enterprise/coderd/license/license.go:807 The cancellation bail is dead against a real Postgres cancel (195 of 200 return pq.Error 57014, not a context sentinel), so a dropped HTTP request publishes a false deployment-wide warning and shutdowns log errors R4 Takumi P2, Chopper P3 Yes
CRF-88 P3 Author accepted R5 (deliberate abort; reasoning on thread) coderd/database/migrations/000562_usage_events_agent_runtime_invariants.up.sql:7 The CHECK is added validating with no violator handling, so one pre-existing misaligned row aborts the migration transaction and the upgrade R4 Meruem P3, Ryosuke P3, Knuckle P3, Luffy P3 Yes
CRF-89 P3 Author fixed (747e8fd) coderd/database/queries_internal_test.go:25 The payload-field pin cannot fail: the query's own result alias total_runtime_ms contains the string the loop searches for R4 Zoro P3, Bisky Note, Mafuuu Nit, Gon Nit Yes
CRF-90 P3 Author fixed (747e8fd) site/src/modules/dashboard/LicenseBanner/LicenseBannerView.stories.tsx:344 One of the four diagnosticWarnings entries is storied; the other three have no assertion on any layer R4 Bisky P3, Nami P3, Melody P3 Yes
CRF-91 P3 Author fixed (747e8fd) codersdk/deployment.go:412 CRF-59's fix rewrote two bullet lists and left the third, so the regenerated table still ships one malformed cell one row below the row it fixed R4 Kurapika P3, Leorio P3, Gon P3, Ryosuke Nit, Knov P4 Yes
CRF-92 P4 Deferred (CODAGT-899) enterprise/cli/server.go:148 The only usage.Cron job registration omits EventType, so Register rejects it, the error is discarded, and no hb_ai_seats_v1 event is ever produced in production R4 Kite P4, Melody P4 Yes
CRF-93 P3 Author fixed (747e8fd) enterprise/coderd/license/license.go:798 The nil-closure branch is unreachable in production and costs two published SDK constants, two production branches and a customer-facing banner for a developer-only state R4 Ryosuke P3, Luffy P3 Yes
CRF-94 P3 Author fixed (747e8fd) codersdk/licenses.go:46 All four diagnostic texts say the figure "may be stale or missing"; the code makes stale impossible, it is always missing R4 Leorio P3, Mafuuu Nit Yes
CRF-95 P3 Author fixed (747e8fd) coderd/database/migrations/000562_usage_events_agent_runtime_invariants.up.sql:2 The CHECK is justified by the entitlements read path, which does not depend on hour alignment; the justification does not support the constraint R4 Pariston P3 Yes
CRF-96 P3 Author fixed (747e8fd) enterprise/coderd/license/license.go:130 CRF-35's fix removed the exactness claim from the query doc and left the identical claim in the closure that calls the query R4 Mafu-san P3 Yes
CRF-97 P3 Author fixed (747e8fd) site/src/modules/dashboard/LicenseBanner/LicenseBanner.tsx:141 The advisory soft-limit banner still tells the operator to email sales, the same instruction as allocation-reached, so the CRF-1 tier split stops at the colour R4 Leorio P3 Yes
CRF-98 Nit Author fixed (747e8fd) enterprise/coderd/license/license_test.go:1476 agentRuntimeHoursLicense(allocation, softLimit) reintroduces the sentinel-zero overload CRF-62 just removed from LicenseOptions R4 Gon Yes
CRF-99 Nit Author fixed (747e8fd) site/src/modules/dashboard/LicenseBanner/LicenseBannerView.stories.tsx:194 renderLicenseBannerWithWarnings rebuilds the DashboardValue shell its sibling helper already builds, with a redundant reduce type argument and a deprecated .Provider R4 Robin, Zoro, ging-ts, ging-react Yes
CRF-100 Nit Author fixed (747e8fd) coderd/database/migrations/000562_usage_events_agent_runtime_invariants.down.sql:1 Bare DROP INDEX where the migration one version earlier uses DROP INDEX IF EXISTS on the same index R4 Knuckle Yes
CRF-101 P2 Author fixed (c29d9cd, 907c91a) enterprise/coderd/usage/generator.go:253 The unique-violation fallback discards existsErr and reports an exists-check failure as "bucket already recorded under a different id"; the branch has zero coverage R5 Netero P3, Mafu-san P2, Melody, ging-go, Takumi Yes
CRF-102 P2 Author fixed (c29d9cd, 907c91a) enterprise/coderd/license/license.go:979 The CRF-87 fix matches SQLSTATE 57014, which Postgres also uses for statement_timeout, so a server-side timeout aborts the whole entitlements computation and, at startup, prevents coderd booting R5 Hisoka P2, Komugi P3, Knuckle Note, Kurapika Note Yes
CRF-103 P3 Author fixed (c29d9cd, 907c91a) enterprise/coderd/license/license.go:634 The claims-ignored warning names no license and no claim, nothing is logged server-side, and it fires for every parsed license, so following its instruction does not clear it R5 Hisoka P3, Mafuuu P3, Pariston P3, Ryosuke P3, Kite P3, Leorio P3, Luffy P3, Pen Botter P3, Chopper Note Yes
CRF-104 P2 Author fixed (c29d9cd, 907c91a) enterprise/coderd/license/license_test.go:2736 The noRuntime helper's comment states a warning contract the code does not have; a nil closure is a hard error R5 Gon P2, Mafuuu P3, Zoro P3, Leorio P3, Chopper Nit, Kite Nit, Razor Nit Yes
CRF-105 P3 Author fixed (c29d9cd, 907c91a) enterprise/coderd/license/license.go:122 The usage-failure policy is classified twice in two layers that must agree, and the managed-agent copy of the guard is untested R5 Meruem P3, Ryosuke P3, Robin P3, Bisky P3 Yes
CRF-106 P2 Author fixed (c29d9cd, 907c91a) enterprise/coderd/license/license.go:960 Fourth consecutive round of the same comment-duplication finding: each fix-round rewrite mints fresh verbatim copies, now five copies of the gauge rationale across three languages and six sites for the arbiter story R5 Gon P2 x4 and P3 x4, Zoro P3, Robin P3, Meruem Nit Yes
CRF-107 P3 Author fixed (c29d9cd, 907c91a) enterprise/coderd/license/license.go:561 A license carrying the feature name agent_runtime_hours as a claim is dropped silently, the one runtime-hour mis-issuance the tolerant decoder cannot warn about R5 Knov P3, Kurapika P3 Yes
CRF-108 P3 Author fixed (c29d9cd, 907c91a) codersdk/licenses.go:33 The soft-limit text says usage is "reaching the advisory soft limit of %d hours" while the warning fires at and above it, so for most of its life the sentence is wrong R5 Leorio P3 Yes
CRF-109 P3 Author fixed (c29d9cd, 907c91a) enterprise/coderd/usage/generator.go:241 The race-resolution branch's only positive coverage is a scheduling accident; no deterministic test pins either outcome R5 Bisky P3 Yes
CRF-110 Nit Author fixed (c29d9cd, 907c91a) codersdk/licenses.go:47 The two *UsageUnavailableWarningText constants are named WarningText but are deliberately routed to Errors for the gauge R5 Gon, Knov, Ryosuke, Mafuuu Yes
CRF-111 Nit Author fixed (c29d9cd, 907c91a) site/src/modules/dashboard/LicenseBanner/LicenseBannerView.stories.tsx:191 formatWarning is the third home of the placeholder-substitution knowledge, and it lives where production code cannot reach it R5 Robin, Melody Yes
CRF-112 Nit Author fixed (c29d9cd, 907c91a) coderd/database/querier_test.go:10806 The CHECK-constraint assertion hand-copies the constraint name into ErrorContains where its sibling sixteen lines up uses the generated constant R5 Melody Yes
CRF-113 Nit Author fixed (c29d9cd, 907c91a) codersdk/licenses_test.go:53 One mutual-prefix assertion still guards a classifier that does not exist, and its failure message names it as real R5 Razor Yes
CRF-114 Nit Author fixed (c29d9cd, 907c91a) enterprise/coderd/license/license_test.go:3298 Hand-rolled map copy loop where maps.Copy has existed since Go 1.21 R5 ging-go Yes
CRF-115 P2 Open enterprise/coderd/license/license.go:944 The PR body still describes the SQLSTATE-57014 abort classifier that the CRF-102 fix removed; this repo squash-merges the body into the permanent commit message R7 Mafu-san P2, Pen Botter P2, Pariston P3, Mafuuu P3, Leorio P3 Yes
CRF-116 P2 Open enterprise/coderd/license/license.go:131 Fifth consecutive round of the same comment-duplication finding; the commit that claimed to deduplicate introduced verbatim duplicates twenty lines apart R7 Gon P2 x5 Yes
CRF-117 P3 Open enterprise/coderd/license/license.go:637 The claims-ignored warning's dedup guard has no test: no case feeds two licenses with unusable claims R7 Bisky P3, Meruem Note Yes
CRF-118 P3 Open site/src/modules/dashboard/LicenseBanner/LicenseBanner.tsx:66 The AI Governance near-limit branch of isMutedWarning is pinned by no test R7 Nami P3 Yes
CRF-119 P3 Open enterprise/coderd/license/license.go:123 The usage-failure logging still lives in the Entitlements closures while the abort policy lives in measureUsage, so one policy is still split across two layers R7 Ryosuke P3 Yes
CRF-120 P3 Open coderd/database/migrations/000565_usage_events_agent_runtime_invariants.up.sql:4 The migration assigns SUM protection to uniqueness alone and mislabels what the alignment CHECK buys R7 Pariston P3 Yes
CRF-121 P3 Open codersdk/licenses_test.go:17 The test rationale attributes the over-limit fallback suppression to the near-limit prefix alone, leaving the over-limit prefix unexplained R7 Leorio P3 Yes
CRF-122 Nit Open codersdk/licenses_test.go:25 Local cutPrefix collides with strings.CutPrefix while doing nearly the opposite R7 Gon, Robin Yes
CRF-123 Nit Open enterprise/coderd/license/license_test.go:1019 Four new TestEntitlements subtests each hand-copy the same ~25-line Premium license-options block R7 Robin Yes
CRF-124 Nit Open site/src/modules/dashboard/LicenseBanner/LicenseBanner.tsx:39 formatLicenseMessage's doc claims it renders templates the way the backend's fmt.Sprintf calls do, which is not what it does R7 Razor, Leorio Note Yes
CRF-125 Note Open enterprise/coderd/license/license.go:160 A zero-value FeatureArguments.Logger silently discards the ignored-claims detail the published warning tells the operator to look for R7 Kite Yes

Contested and acknowledged

Law mandatory split (R4) - discharged by panel R5, 16/16

  • Verdict under review: Law returned Split/Mandatory in round 4 on two legs: (a) three independent concerns entered in the head commit and no panel had reviewed them, and (b) they occupy distinct risk domains.
  • Author counter-position: declined the four-PR restructure ("I don't have standing to restructure the PR into four") and reshaped the head commit along Law's slice boundaries instead: self-contained renumbered migration with its own tests, diagnostics centralized in one measureUsage helper plus one frontend set, claim validation isolated to one function.
  • Panel disposition (R5, 16/16): every reviewer who addressed it concluded the three concerns are reviewable in place and accepted the reshape. Netero supplied the mechanical evidence that each concern is independently tested at head; Bisky ran those tests rather than reading them; Knuckle, Takumi, Knov, Razor, Meruem, Ryosuke, Zoro, Leorio, Pariston, Hisoka, Luffy, Killua and Pen Botter each checked the seams from their own domain.
  • Residual, recorded rather than resolved: Mafu-san's split of the two legs is the sharpest statement of what remains. Leg (a) is discharged by process, because the round-4 panel reviewed exactly those concerns and found six P2s in them. Leg (b) is not answered by seam hygiene: a squash merge makes the migration, the diagnostics rework and the claim tolerance one revert unit, and the concern most likely to need an independent revert is the migration, because it validates existing rows and deliberately aborts upgrades on a violator. Reverting it post-merge drags the feature with it. Mafuuu reached the same place from the contract seat: revertibility is one-directional. That residual is a bounded, human call about merge strategy, not a code defect.

CRF-30 (Nit, codersdk/licenses.go:30) - closed by panel vote R3, 12/13 accept

  • Panel closure (R3): Netero, Nami, Hisoka, Meruem, Chopper, Mafu-san, Leorio, Knov, Pariston, Razor, Luffy and Pen Botter each evaluated the defense independently and accepted it. The decisive verification is that the sibling LicenseAIGovernanceOverLimitWarningText can state an overage only because aiGovernanceOverLimitMessage fires on a strict actual > limit (LicenseBanner.tsx:40-53), while this warning fires on actualHours >= allocation and AtAllocation pins that it renders at exactly 100 of 100. The sibling's phrasing is not transferable without the sibling's trigger, and splitting the trigger adds a third rung to a ladder that must stay at one warning. Consequence accepted: an operator at 150 of 100 reads both operands and subtracts. Mafuuu dissented on the sentence shape rather than the overage clause; outvoted.

CRF-33 (Note, coderd/database/queries/usageevents.sql:130) - closed by panel vote R3, 12/12 accept

  • Panel closure (R3): Knuckle, who raised it, verified every factual leg in the tree (enterprise/cli/server.go:157-160 starts the generator unconditionally with a comment saying publish_usage_data gates only Tallyman; InsertHeartbeatUsageEvent writes the row with no publish-state condition; the new query filters on event_type and created_at only). Hisoka, Meruem, Chopper, Mafu-san, Leorio, Knov, Pariston, Razor, Luffy, Pen Botter and Netero concurred. Consequence accepted: the banner can claim billable runtime Tallyman never accepted. The alternative is worse and asymmetric: gating on publish success would let a Tallyman outage silently clear an over-allocation banner, and a permanently rejected bucket is never regenerated. Over-reporting is the safe direction for an advisory warning. Residual disclosure filed as CRF-70.

CRF-24 (P4, enterprise/coderd/license/license.go:796) - HardLimit decoded but read by no threshold

  • Finding: agent_runtime_hours_limit_hard is decoded, validated as >= allocation, and published on the feature, and no code path compares Actual against it. A deployment at 130 hours against a 120-hour hard limit sees the same message it saw at 100.
  • Author defense: A third threshold here would collide with the at-most-one-warning rule in this block and prefigure F4's structured warnings. C1 is the linked ticket that owns HardLimit. If C1 dies, the claim should be revisited rather than left inert.
  • Author accepted (R2): The defense restates the finding's own conclusion, which already said the right move is probably not to add the threshold now but to make sure C1 owns it in writing. C1 is a named linked ticket. Nothing further is required in this PR.

CRF-30 (Nit, codersdk/licenses.go:30) - Over-allocation text reuses the under-limit sentence shape

  • Finding: The allocation-reached text renders "used 150 of the 100 hours" and leaves the reader to subtract the overage, where the established sibling LicenseAIGovernanceOverLimitWarningText states it directly: "using %d of %d ... seats (%d over the limit)".
  • Author defense: The trigger is >=, so the message also fires at exactly 100 of 100, where "(0 over the limit)" reads wrong. Splitting at-allocation from past-allocation grows a warning ladder that C1 will restructure anyway. The copy is flagged in the PR body for the license-terms owner. The text was reworded for CRF-11 and CRF-28 but not for this finding's substance.
  • Status: contested, defense unevaluated. The panel judges it in round 3.

CRF-33 (Note, coderd/database/queries/usageevents.sql:130) - Usage counted regardless of publish outcome

  • Finding: Rows are written and counted at insert time regardless of whether the usage collector accepted them, so Actual and the billable warning can exceed what Coder collected.
  • Author defense: Intentional, and it survives the CRF-9 change in equivalent form (the query now reads usage_events, which the generator writes unconditionally in enterprise builds; publish_usage_data only gates Tallyman). Actual deliberately measures locally recorded usage rather than collector-accepted usage, because tying the warnings to publish success would let a Tallyman outage silently reset the banner. Bucket-for-bucket reconciliation belongs to the billing pipeline (C1/F4), not this read path.
  • Status: contested, defense unevaluated. The panel judges it in round 3.

CRF-63 (Nit, coderd/database/querier_test.go:10820) - contested R4

  • Finding: Third local usage-event insert closure in querier_test.go, copied from the sibling with the IDs swapped; fold the three into one shared helper, ideally in the same edit that builds EventData from Fields().
  • Author defense: The closure was changed for CRF-32 to use Fields() and EventType() but not folded, because "folding the three insert closures into one shared helper would mean refactoring the two pre-existing tests, which I left alone to keep the diff reviewable."
  • Status: contested, defense unevaluated. The panel judges it in round 4.

Law analysis

Round 7 re-run

  • Head SHA 907c91a. Effective LOC +2049 -250 across 21 files (550 production, 1499 test, 236 generated). Growth since the round-4 analysis: +669, over the 500 threshold, with additions still above 1000, so the trigger fired.
  • Verdict: Don't split. Enforcement: Advisory. Nothing blocks.
  • Basis: the re-raise gate. The round-4 mandatory split targeted this same concern set and was closed by panel vote 16/16. New evidence would have to be a new independent concern or a new risk domain entering the diff, and Law verified the opposite: every line added since the discharge is remediation of panel findings inside the already-enumerated concerns, and those additions were themselves reviewed. The write-path invariants, the diagnostics rework and the claim tolerance have each absorbed multiple rounds of targeted findings. "The scrutiny a split would have bought has been delivered by process."
  • Residual, unchanged from round 5 and still a human merge-strategy call: under a squash merge the slices are one revert unit, and the migration is the piece most likely to need an independent revert. Law notes slice A is self-contained and could be cherry-picked out before merge if independent revertibility is wanted.

Round 4 analysis (superseded, kept for the record)

  • Head SHA: 41bda6d. Effective LOC: +1380 -191 across 17 files (363 production, 1017 test, 221 generated). Test density 73.7%.
  • Round 1 effective additions were 653; growth is +727, and Law had never run for this PR, so both the round-1 and the round-2+ triggers fire.
  • Verdict: Split. Enforcement: Mandatory.
  • Proposed order: Slice 1 fix(coderd/database) write-path invariants (migration 000562 plus the InsertUsageEvent arbiter change); then Slice 2 fix(enterprise/coderd/license) usage-measurement diagnostics and Slice 3 fix(enterprise/coderd/license) stop rejecting licenses over unusable runtime-hour claims, in either order; then Slice 4 feat(enterprise/coderd/license) the runtime hours feature itself.
  • Basis: not size. Three independent concerns with distinct risk domains (schema write path, license acceptance, managed-agent diagnostics) all entered in 41bda6d145, the head commit, which no panel has reviewed, and are positioned to merge on the strength of four rounds conducted before they existed.
  • Panel skipped this round per the Law decision gate. Churn guard treats the mandatory split like a P0 next round: author silence is BLOCKED.

Round log

Round 1

Netero first pass: no P0-P2, 2 P3, 4 Notes. Panel of 22 followed. 6 P2, 12 P3, 1 P4, 6 Nits, 5 Notes posted.
CRF-1 dropped: superseded by CRF-11 after direct evaluation of the banner classifier showed the opposite rendering. CRF-4 merged into CRF-11.
Gon's five comment-duplication P2s consolidated into CRF-22 at P3. Reviewed against 9bab7a8..64c9fc7.

Round 2 update

BLOCKED. Churn guard classified 32 open findings across 64c9fc7..cb1b9a3: 26 addressed, 1 acknowledged (CRF-24), 2 contested (CRF-30, CRF-33), 2 deferred with tickets (CRF-12 -> CODAGT-890, CRF-23 -> CODAGT-891), 1 silent (CRF-34).
No panel, no Netero. Status cells left unchanged: nothing was verified this round, so the churn guard's classifications stay in round-2/churn-guard.md rather than being written into the ledger as fact.
CRF-30 and CRF-33 defenses are unevaluated. The panel judges them when the round proceeds.
Base moved 9bab7a8 -> b5c9e8e. Reviewed against b5c9e8e..cb1b9a3.

Round 3 update

PROCEED. No new commits since round 2 (head still cb1b9a3); the only change is the author's comment deferring CRF-34 to F1, which was the sole blocker. Status cells now carry the churn guard's classifications: 26 author-fixed (claims, not verified), 1 accepted, 2 contested, 3 deferred.
Inventory validation reopened CRF-1 at its original P3. It was dropped in round 1 because both warning texts collided with the AI Governance near-limit prefix and therefore rendered muted, which inverted the finding. The CRF-11 fix changed both texts to begin "Your deployment has used", so isAIGovernanceNearLimitWarning is now false for both and they render warningProminent. Verified in the round-3 worktree. The precondition behind the drop is gone, so the original finding is live: an advisory soft-limit warning now gets the same prominent orange banner and the same generic contact-sales CTA as the allocation-reached message.
Reviewed against b5c9e8e..cb1b9a3.

Round 4 update

PROCEED. Panel of 25 plus Netero. Reviewed against b5c9e8e..cb1b9a3 (no new commits since round 2; the author's CRF-34 reply unblocked the round).
Contested findings closed by panel vote: CRF-30 (12/13), CRF-33 (12/12).
Panel re-raised two findings the churn guard had classified as addressed, as replies on their existing threads rather than as new IDs: CRF-14 (fix was comment-only and the comment is false) and CRF-32 (fix covered one of two silent-zero literals).
CRF-1 reopened and raised P3 -> P2 on new evidence.
Two fix-chain regressions found: CRF-41 (the CRF-10 validator fix invalidates the whole license) and CRF-39/CRF-40 (the CRF-11 fix documented a constraint that does not exist and introduced a new self-collision on the classifier key).
New this round: 4 P2, 17 P3, 2 P4, 5 Nits, 4 Notes, plus 2 re-raises. CRF-45 and CRF-46 were withdrawn before posting: both were re-raises of CRF-32 and CRF-14 and are carried on those threads instead, so they are not separate findings. IDs 53 and 57 were folded into CRF-1 and CRF-56 during cross-check.

Round 4 posting note

Three findings had no diff line and were folded into the review body by post, so they have no thread the author can reply to: CRF-52 (stories file absent from the diff), CRF-68 (license.go:765, outside the hunks) and CRF-61 (license.go:437, outside the hunks). This is the failure mode that blocked round 2. If they come back silent, check whether the author had a thread before treating silence as non-response.
The two re-raise replies (CRF-14, CRF-32) posted but could not unresolve their threads: the bot token lacks unresolveReviewThread. The threads still read as resolved on GitHub; the reply text carries the re-raise.

Round 5 update

PROCEED. Churn guard over cb1b9a3..41bda6d (3 commits, +841 -388 across 25 files): 31 addressed, 1 contested (CRF-63), 3 deferred with tickets (CRF-54, CRF-61, CRF-67), 0 silent.
Two changes went beyond what the findings asked for and are flagged for the panel rather than accepted by the orchestrator: Features.validateAgentRuntimeHours and its five sentinel errors were deleted outright in response to CRF-41, replacing license rejection with tolerant decoding; and InsertUsageEvent dropped its (id) arbiter for a bare ON CONFLICT DO NOTHING in response to CRF-51, which changes a shared insert used by every usage event type.
CI is red: gen, lint, offlinedocs and sqlc-vet all failing at 41bda6d. Recorded as a finding rather than as context, because failing checks are defects.
Effective additions crossed the Law threshold (1380 > 1000, up from 653 at round 1, and Law has never run for this PR), so Law was spawned alongside Netero.

Round 4 posting note

Law's mandatory split skipped the panel, so no panel vote was available for CRF-63. Dropped by orchestrator instead, with the reason recorded on the row; that is an orchestrator disposition, not a panel closure.
CRF-80 (P0) was verified independently by the orchestrator: git ls-tree origin/main shows 000562_oauth2_public_client_tokens, git merge-tree --write-tree origin/main HEAD produces a tree containing all four 000562 files, golang-migrate v4.19.0 is in go.mod and its iofs.go:69 returns ErrDuplicateMigration, and fix_migration_numbers.sh exists. main is at 000564, so the renumber target is 000565.
CI red-check attribution: the P0 explains gen and sqlc-vet, which both start Postgres and run migrations on the pull request merge ref. It does not explain lint or offlinedocs; lint/migrations is only a grep for hardcoded schema names. Netero ran all four locally at head and every one passed, which is consistent, because head has only one 000562. His competing lead for the remaining two is that all four failing jobs are the only jobs that install Go tools from source via mise.

Round 4 panel note

The Law decision gate says a mandatory split skips the panel, but deep-review post rejects both REQUEST_CHANGES and COMMENT for a round with no panel reviewers once a panel has reviewed the PR. The tool's enforcement won: the panel ran. That is also the substantively right outcome, since Law's argument is that three of the concerns in this diff have never been reviewed, and the panel found 6 further P2s in exactly those concerns.

Round 4 posting note (2)

Posted as REQUEST_CHANGES with 26 inline comments. Four folded into the review body for want of a diff line and therefore have no thread the author can reply to: CRF-78 (generator.go), CRF-84 (license.go:1018), CRF-91 (codersdk/deployment.go:412) and CRF-92 (enterprise/cli/server.go). Two of those are P2. If any comes back silent next round, check whether a thread existed before treating silence as non-response.

Round 6 update

PROCEED. Churn guard over 41bda6d..8d0e5c8 (2 commits, +684 -373 across 22 files): 27 addressed, 2 acknowledged (CRF-81, CRF-88), 1 deferred (CRF-92 -> CODAGT-899), 0 silent, 0 contested.
The four thread-less round-4 findings (CRF-78, CRF-84, CRF-91, CRF-92) all received itemized responses in the author's top-level comment, so the folded-body failure mode did not recur.
CRF-80 (P0) verified fixed by the orchestrator: migration renumbered to 000565 and git ls-tree on git merge-tree --write-tree origin/main HEAD now shows only 000562_oauth2_public_client_tokens at 562 and the runtime-hours migration at 565.
Law's mandatory split drew a substantive counter-position rather than silence: the author declined the four-PR restructure ("I don't have standing to restructure the PR into four") and reshaped the head commit along Law's slice boundaries instead. Evaluating that counter-position is the panel's central task this round. Law does not re-run: effective additions grew 1380 -> 1751, which is +371, under the 500 threshold.
On CRF-71 the author contests part of the finding's reasoning with a reproducible test (TestGeneratorConcurrentReplicas), claiming the same-id concurrent-replica race is real because the arbiter pre-check only sees committed rows. That correction needs verification, not acceptance.

Round 5 update

PROCEED, panel of 27 plus Netero. Reviewed against b5c9e8e..8d0e5c8.
Law's mandatory split is discharged by unanimous panel vote (16/16 of those who addressed it), with the revert-granularity residue recorded above as a human call. Law did not re-run: effective additions grew 1380 -> 1751, +371, under the 500 threshold.
Round 4's CRF-71 race analysis was wrong and the author's correction is right. Netero verified by mutation: with the race-resolution branch removed, TestGeneratorConcurrentReplicas fails 4 of 4; with it, 8 of 8 pass. ON CONFLICT (id) DO NOTHING absorbs the same-id race only on the arbiter index, so this PR's second unique index changes that. Takumi independently confirmed under the race detector.
CRF-80 (P0) verified fixed by the orchestrator against the merge tree.
New this round: 4 P2, 5 P3, 5 Nits. No comments folded into the body: all 22 candidate lines were checked against the diff hunks before drafting.
Fix-chain pattern, fourth consecutive round: CRF-102 is a defect introduced by the CRF-87 fix that this panel demanded, and CRF-101 is the third consecutive round in which a review fix shipped a new error branch with zero coverage.

Round 6 update

BLOCKED. Churn guard over 8d0e5c8..c29d9cd (1 commit, +514 -197 across 13 files): 11 addressed, 3 silent (CRF-112, CRF-113, CRF-114), 0 acknowledged, 0 contested, 0 deferred out of 14 open findings.
No panel, no Netero. Status cells left unchanged: nothing was verified this round, so the churn guard's classifications stay in round-6/churn-guard.md rather than entering the ledger as fact.
All three silent findings had live inline threads (PRRT_kwDOGkVX1s6XAdm3, PRRT_kwDOGkVX1s6XAdm6, PRRT_kwDOGkVX1s6XAdnA), all still unresolved with no replies and no targeting code change. The folded-body failure mode that caused the round-2 block does not apply here.
CI is red on Flake Check (run 92803291237), which runs the changed tests at -count=35 -shuffle=on -parallel=16 against real Postgres. The orchestrator could not reproduce it locally at those settings for enterprise/coderd/usage (TestGenerator), enterprise/coderd/license (full package), or coderd/database (TestGetTotalUsageHBAgentRuntimeV1, TestUsageEventsTrigger), all with DB=ci. Recorded as an open state of the PR, not as a finding, since no reviewer ran this round.
Bisky's CRF-109 predicted this class in round 5: the race-resolution branch's only positive coverage is a scheduling accident. It is marked addressed by the churn guard and unverified by any panel.

Round 7 update

PROCEED. Churn guard over the cumulative 8d0e5c8..907c91a (2 commits): 14 addressed, 0 silent, 0 contested, 0 deferred out of 14 open findings. The three nits that blocked round 6 are all targeted by 907c91a24d and their threads are now resolved.
Round 6 was BLOCKED and entered nothing in the ledger, so round 5's findings carried through unclassified until now; the statuses above are the first entry for CRF-101 through CRF-114 and are author claims, not panel verification.
Law re-runs this round. Effective additions are 2049 against the 1380 recorded under ## Law analysis at round 4, a growth of +669 over the 500 threshold, with additions still above 1000. Law's round-4 mandatory split was discharged by panel vote in round 5, so this is a fresh assessment of a diff that has grown by roughly half since he last saw it, not a re-litigation.
The round-6 Flake Check failure has a claimed cause and fix: TestEntitlements/FullLicense builds a license from every codersdk.FeatureNames entry, and agent_runtime_hours is not a valid claim name, which is the production behaviour CRF-107 described. The test now special-cases the feature to use its allocation claim. Worth the panel's attention that the CI failure and CRF-107 share a root cause, and that this change fixes the test rather than the behaviour, which CRF-107's own fix addressed separately by adding the feature name to ignoredClaims.

Round 7 Law and Netero

Law re-ran and returned Don't split / Advisory, so the panel proceeds normally and nothing is gated.
Netero returned no findings. He settled the round-6 CI question by mutation: with the TestEntitlements/FullLicense special-case guard disabled the test fails 1 of 1 at head against real Postgres via the claims-ignored warning, and passes with it. The Flake Check red was a deterministic failure surfacing under -count=35, not a schedule-dependent flake, so there is no residual flake to chase in that test.

Round 7 panel

Panel of 25 plus Netero and Law. Law: Don't split, Advisory. Netero: no findings.
2 P2, 5 P3, 3 Nits, 1 Note. No finding this round is about runtime behaviour. Six reviewers returned nothing at all (Hisoka, Zoro, ging-go, ging-ts, ging-react, Kurapika), which has not happened in seven rounds.
Fix verification, independently and by execution rather than by reading: CRF-101 (Komugi, Takumi, Chopper, Knuckle, Meruem, Melody, Razor), CRF-102 (the same seven plus Bisky), CRF-109 (Bisky, Komugi, Takumi, Meruem, Razor: TestGenerateBucketUniqueViolation pins all three unique-violation outcomes deterministically), CRF-103 (Chopper, Meruem), CRF-113 (Bisky, Razor), CRF-75/83/90/97 (Chopper, Bisky, Nami).
Netero settled the round-6 CI question by mutation: the Flake Check red was a deterministic TestEntitlements/FullLicense failure, not a schedule-dependent flake.
CRF-116 stays at P2 for consistency with the round-5 calibration of the same finding, but the ask changes: name an owning location per fact rather than deleting instances, which is what the last four passes did.

About deep-review

CRF = Coder Review Finding (P0-P4, Nit, Note)

Reviewer Focus
Bisky tests
Chopper ops/errors
Churn-guard change verification
Ging language modernization
Gon naming
Hisoka edge cases
Killua perf
Kite change integrity
Knov contracts
Knuckle SQL
Komugi flake/determinism
Kurapika security
Law decomposition
Leorio docs
Luffy product
Mafu-san process
Mafuuu contracts
Melody dispatch/pairing
Meruem structural
Nami frontend
Netero mechanical checks
Pariston premise testing
Pen-botter product gaps
Razor verification
Robin duplication
Ryosuke Go arch
Takumi concurrency
Zoro shape

🤖 Managed by Coder Agents.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. More of your lovely PRs please.

Reviewed commit: 64c9fc7178

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The unit decision is the whole feature and this PR gets it right and writes down why: flooring milliseconds to whole hours at the single point where the two units meet is what keeps Feature.Compare, Feature.Capable, the coder features list columns and the warning thresholds from silently comparing hours against milliseconds. The at-most-one-warning switch is pinned in both directions (AtAllocation asserts the soft message is absent, not just that the allocation one is present). AgentRuntimeHours/NoClaimNoFeature fails from inside the AgentRuntimeMsFn closure, which proves the query is never reached rather than only that nothing warned. And TestGetTotalUsageHBAgentRuntimeV1 gives the day-truncation and timezone behavior query-level coverage that GetTotalUsageDCManagedAgentsV1 never had. The PR description also names two decisions a reviewer could not have seen from the code, including drift in a downstream ticket. That is the standard.

Twenty-three reviewers. 6 P2, 12 P3, 1 P4, 6 nits, 5 notes. No P0 or P1: the code builds, the tests pass here (including the Postgres-backed one), the SQL matches the rollup trigger key for key, and the authz wrapper matches its sibling.

The P2s cluster, and the cluster is worse than its parts. This PR turns a sum into a customer-facing number and attaches "Additional usage may be billable" to it. That number can be permanently and unboundedly low, because the generator forfeits buckets older than a 7-day window and nothing backfills (CRF-8). It can also be too high at period boundaries, because day-truncated inclusive bounds charge the renewal day against both terms (CRF-9). The warning can fire when nothing has happened, because agent_runtime_hours_limit_soft: 0 is a valid claim and 0 >= 0 (CRF-10). When it does fire, both messages collide with the AI Governance near-limit prefix and render in the softest banner variant in the system, indistinguishable from each other (CRF-11). The number itself carries no unit at the public field, and the ticket for the UI that will render it already reads it as milliseconds (CRF-7). And it is all served to unauthenticated callers by an endpoint whose swagger annotation claims otherwise (CRF-12). Individually each is arguable. Together they describe a meter that can be wrong in both directions, can alarm about nothing, renders quietly when it is right, and publishes the result to the internet.

Three of these need a human decision rather than a code change, and none of them should be accepted as permanent by an agent: whether the 7-day forfeit window is disclosed or the generator backfills to nbf (CRF-8, a billing-dedup question, not a code one); whether entitlements are deliberately public, in which case the RBAC check in writeEntitlementWarningsHeader on the same strings is only buying the illusion of a gate (CRF-12); and who owns the "may be billable" copy, which no docs/ page defines (CRF-23, CRF-31). File tickets or state the acceptance explicitly.

On the first-pass findings: Netero's CRF-1 claimed the runtime warnings render prominently. That is wrong, and CRF-11 is why. LicenseAIGovernance90PercentWarningText.split("%d%%")[0] is "You have used ", which both new texts start with. I evaluated it rather than reasoning about it. CRF-1 is dropped and CRF-4 is folded into CRF-11. Netero's other findings stand.

Two things I want to protect from the fix pass. Keep the query name: GetTotalUsageHBAgentRuntimeV1 matches GetTotalUsageDCManagedAgentsV1/dc_managed_agents_v1 and the event type is what someone greps when the number looks wrong; the issue's literal name would break the pattern. Keep the raw hourMs literal in license_internal_test.go and the raw "hb_agent_runtime_v1" literals in querier_test.go: deriving them from the expressions under test would hide exactly the bug they exist to catch.

Luffy, on the negative case: "That's the check I look for and it's usually missing."


site/src/modules/dashboard/entitlements.ts:19

Note [CRF-34] Populating Actual makes agent_runtime_hours fail getFeatureVisibility's limitCompliant check once usage passes the allocation. (Razor)

limitCompliant = limit && actual ? limit >= actual : true, so with limit=100, actual=150 the feature's visibility flips to false. Nothing keys off agent_runtime_hours visibility today (the only site/src occurrences are in typesGenerated.ts), so this is inert in this PR.

It matters for F1: a UI wired through useFeatureVisibility would hide the runtime-hours panel at exactly the moment the customer needs to see it. Razor notes the zero-allocation case is safe by accident, since limit=0 is falsy in JS so limitCompliant stays true. Worth knowing before the UI lands, not worth changing here.

🤖

🤖 This review was automatically generated with Coder Agents.

Comment thread enterprise/coderd/license/license_internal_test.go Outdated
Comment thread enterprise/coderd/license/license.go
Comment thread enterprise/coderd/license/license.go
Comment thread enterprise/coderd/license/license.go Outdated
Comment thread enterprise/coderd/license/license.go
Comment thread enterprise/coderd/license/license_test.go Outdated
Comment thread codersdk/licenses.go Outdated
Comment thread codersdk/licenses.go Outdated
Comment thread coderd/database/queries/usageevents.sql
Comment thread coderd/database/queries/usageevents.sql
…dings

Query accuracy: GetTotalUsageHBAgentRuntimeV1 now reads usage_events
directly with exact start-inclusive, end-exclusive bounds instead of the
UTC-day-truncated usage_events_daily rollup. hb_agent_runtime_v1 is one
row per hourly bucket deployment-wide, served by its dedicated partial
index, so day truncation only bought a 0-24 bucket accounting error at
period boundaries, double-charging renewal days across two terms.

Warning texts: both constants are reworded so neither shares the
'You have used ' prefix that LicenseBanner uses to classify AI
Governance advisories (which rendered runtime warnings in the muted
variant), and neither is a prefix of the other. The soft limit warning
now names the threshold that fired. The 'AllocationExceeded' constant is
renamed to 'AllocationReached' to match its >= trigger. A codersdk test
pins the prefix couplings.

Robustness: warning emission moves into appendAgentRuntimeHoursWarning,
mirroring appendAIGovernanceSeatLimitWarning; a zero soft limit claim
now rejects the license instead of warning at zero usage forever;
Actual is written back directly rather than through AddFeature's merge
policy; raw query errors are logged server-side and replaced with a
stable operator-facing message on the unauthenticated entitlements
payload; and the unreachable Limit != nil guard is dropped.

Docs and tests: codersdk.Feature.Actual documents its per-feature units,
the runtime block comment discloses the generator's forfeited-bucket
window instead of calling everything lag, warning assertions use require
before indexing, the nil AgentRuntimeMsFn dev error gains a test, the
event-type filter test can now actually fail, and the runtime claims get
a coderdenttest.LicenseOptions builder.
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

Docs preview

Check off each page once it's been reviewed. If a page changes in a later push, its checkbox clears automatically so it gets a fresh look. Pages not yet wired into the docs navigation aren't listed here.

…cy-limited mode

Per PR discussion, agent_runtime_hours_allocation of 0 does not disable
the feature; it carries no hour budget and forces the concurrency-limited
mode that C1 will implement. Comments and the test case name now say
that instead of 'disables the feature'. Behavior is unchanged.
@jaaydenh

jaaydenh commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cb1b9a3a1f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread coderd/database/queries/usageevents.sql Outdated
@jaaydenh

jaaydenh commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

/coder-agents-review

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review is blocked this round, on one unaddressed item out of 32. I want to be clear up front that this is a process gate and not a judgment on the work: 26 of the 32 round-1 findings were addressed in e42b139f8b and cb1b9a3a1f, including all six P2s, and several of the fixes are better than what the panel asked for. The query rewrite in particular went further than the finding did: reading usage_events with exact start-inclusive, end-exclusive bounds instead of the day-truncated rollup, plus a doc recording the retention dependency the change creates and a generated-SQL test pinning the event-type literal against usagetypes.UsageEventTypeHBAgentRuntimeV1. That is the right shape.

Unaddressed, and the only thing standing between this and a panel round:

CRF-34 (Note, site/src/modules/dashboard/entitlements.ts:19). Populating Actual makes agent_runtime_hours fail getFeatureVisibility's limitCompliant check (limit && actual ? limit >= actual : true) once usage passes the allocation, so a UI wired through useFeatureVisibility would hide the runtime-hours panel at exactly the moment the customer needs to see it. Inert in this PR, since nothing under site/src reads that visibility flag yet. No code change and no response: git diff b5c9e8e4..cb1b9a3a1f -- site/src touches only typesGenerated.ts, and no comment on the PR engages it.

The honest part of that: CRF-34's line fell outside the diff, so the tooling folded it into the round-1 review body instead of opening a thread. There was nothing to reply to, which makes it the easiest item on the list to miss. Either reply here accepting it ("inert today, F1 owns it"), or carry it into the F1 ticket alongside the actual-is-hours correction already noted there. Either closes it. A one-line acknowledgement is enough; the finding does not ask for code.

On the rest, for the record rather than for action now:

Deferred with tickets, both accepted as deferrals: CRF-12 (entitlements endpoint auth model) to CODAGT-890, filed with both options and the swagger-versus-enforcement mismatch recorded, and CRF-23 (docs for runtime hours, allocation, limits) to CODAGT-891, due before C1. CRF-31 is closed the right way too: you checked Linear, found CODAGT-852 specifies threshold behavior rather than copy, and put that in the PR body naming the sentence that needs license-terms sign-off. That is the answer, not a deflection.

CRF-24 acknowledged with reasoning I am not going to argue with (a third threshold collides with the at-most-one-warning rule and prefigures F4; C1 owns HardLimit; if C1 dies the claim should be revisited rather than left inert).

CRF-30 and CRF-33 are contested, and I am deliberately not resolving them myself. Both defenses are substantive: on CRF-30, that >= means the message also fires at exactly 100 of 100 where "(0 over the limit)" reads wrong; on CRF-33, that Actual should measure locally recorded usage because gating on publish success would let a Tallyman outage reset the banner. Those are arguments about the design, and the panel should evaluate them rather than the orchestrator waving them through. They carry into the next round as contested, not as closed.

CI is green (23 passed, 9 skipped), which it was not at round 1.

Nothing else was reviewed this round. No panel ran, so no fix was verified: every "addressed" above is your claim plus the churn guard reading the diff, not the panel checking the result. Reply to CRF-34 or push a fix and the next round runs the panel against the whole change, including verification of the round-1 fixes.

🤖 This review was automatically generated with Coder Agents.

@jaaydenh

jaaydenh commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

regarding CRF-34, this will be handled in issue F1

@jaaydenh

jaaydenh commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

/coder-agents-review

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Twenty-six reviewers on the fix round. The round-1 fixes are real, and two of them are better than what the panel asked for: the query rewrite moved off the rollup to exact half-open bounds and documented the retention dependency the move creates, and queries_internal_test.go pins a cross-language literal whose breakage would otherwise be silent. Knuckle and Hisoka independently measured the new plan on loaded local Postgres (2.4 ms and 4.0 ms, index scan, ~8,760 rows against 500k and 2M unrelated rows), so the "no rollup needed" argument is now measured rather than argued. Komugi ran the new tests at -race -count=60 across three GOMAXPROCS settings, green. TestGetTotalUsageHBAgentRuntimeV1 is now a real mutation test: the foreign-event-type row carries a runtime_ms key, so dropping the event_type predicate fails it.

4 P2, 17 P3, 2 P4, 5 nits, 4 notes, plus 2 re-raises. No P0 or P1.

Two of the P2s are fix-chain regressions, which is the part I want to lead with because both trace to advice this panel gave.

CRF-41: the CRF-10 fix rejects the entire signed license for agent_runtime_hours_limit_soft: 0. Pariston proved the blast radius with a throwaway test rather than arguing it: HasLicense=false, audit logs, RBAC, HA and every other paid feature drop to not_entitled because one cosmetic threshold claim is zero. Round 1 told you to put that check in the validator. Knov and Ryosuke both said so, and Ryosuke explicitly argued against treating 0 as absent because it would "hide the license defect." That advice was wrong, and three reviewers independently say so this round. Everywhere else in the same claim loop a nonsensical feature value is skipped rather than fatal. The one-line alternative is in decodeAgentRuntimeHours: treat a zero soft limit as absent.

CRF-39 and CRF-40: the CRF-11 fix removed the real prefix collision and documented a constraint that does not exist, then introduced a new collision on the mechanism that does. Thirteen reviewers landed on the doc and the test. The claim "the dashboard's LicenseBanner dispatches on both" is false: the banner imports neither constant, and its only template-level dispatch is === against two placeholder-free strings, which is prefix-insensitive by definition. Meanwhile Chopper checked the mechanism that is real, Text.split("%d")[0], and both new constants now yield exactly "Your deployment has used ". I re-ran it: identical, and Leorio measured an 88-character shared prefix. So the constraint the comment invents is unenforceable, and the constraint every future classifier will actually derive is already violated between the two new texts. That also gives CRF-1 a mechanical fix instead of a frontend-only one.

CRF-1 is reopened and raised to P2. It was dropped in round 1 because both texts collided with the AI Governance prefix and rendered muted, which inverted the finding. The CRF-11 fix removed that collision, so both now take warningProminent. Six reviewers traced the render path independently. An operator at 80 of 100 hours, inside their allocation with nothing owed, gets the identical full-orange banner and the identical "Contact sales@coder.com." as an operator at 100 of 100 whose usage "may be billable". New evidence beyond round 1: the banner has no dismiss control and the usage period is the license term, so crossing a soft limit early in a year-long term means eleven months of a prominent banner nobody can clear, and with any second message the heading reads "Your license limits have been exceeded" above a message saying the deployment is still within its allocation.

CRF-35 is Netero's, and it arrived at the same defect as the unresolved @chatgpt-codex-connector thread. Netero's framing is the sharper one: because the two half-open ranges partition on bucket start, each bucket is counted exactly once, so this is misattribution rather than the double-charge CRF-9 was. One correction to Netero's own writeup, found in cross-check: the query doc does state the bucket-start rule at line 129. What it does not state is the consequence, that a bucket straddling a bound is attributed entirely to one side.

And then the thread nobody was pulling. Hisoka, Komugi and Pariston independently found that a bucket does not contain the runtime that elapsed in that hour (CRF-48). GetTotalChatMessageRuntimeMsInRange filters chat_messages.created_at, which this codebase documents as the transaction start time, and sums each row's full runtime_ms. So a turn running 10:50 to 11:20 puts all thirty minutes into whichever bucket its row landed in. I verified the chain. The consequence is that the doc's "Exact bounds" sentence is unreachable for three independent reasons, and that the remedy the codex thread proposes, prorating boundary buckets, prorates a quantity that was never proportional to the bucket. Komugi went further and showed with a live Postgres probe that now() does not advance inside a transaction, so a chat transaction outliving the eligibility lag lands rows in a sealed bucket and ON CONFLICT (id) DO NOTHING drops that runtime permanently (CRF-49). Pariston's read is the one to take seriously before C1: the event encodes a duration as a point event with only a bucket-start timestamp, so no reader can attribute a boundary correctly and every future reader will re-derive the same approximation. That is an upstream encoding decision, not this query's bug, but this PR is what turns the number into a customer-facing figure.

Two findings the churn guard had recorded as addressed came back, and both are on their original threads rather than as new IDs. CRF-14: the fix was comment-only by its own admission, and Mafu-san found the comment it added is false against the code in the same commit. CRF-32: the pin covers event_type and stops one literal short of runtime_ms, which four reviewers reached separately.

Both contested findings are closed, and neither by me. CRF-30 closed 12 of 13: the decisive check is that the AI Governance sibling can state an overage only because its trigger is strictly actual > limit, while this one fires on >= and AtAllocation pins it at exactly 100 of 100, so the sibling's phrasing is not transferable without the sibling's trigger. Mafuuu dissented on sentence shape and was outvoted. CRF-33 closed 12 of 12, including Knuckle who raised it: over-reporting locally recorded runtime is the safe direction, because gating on publish success would let a Tallyman outage silently clear an over-allocation banner. Residual disclosure filed as CRF-70.

Things worth protecting from the next fix pass. Do not gate the runtime block on Enabled to satisfy CRF-46; the finding is that the comment is wrong, not that the behavior is. Do not delete the hourMs literal or the hand-written literals in querier_test.go for the wrong reason: CRF-32's residual asks for the payload to be built from Fields(), not for the constants to be inlined. And keep appendAgentRuntimeHoursWarning; four reviewers verified it has exactly four reachable outcomes and a case for each.

Hisoka, on the one that moved: "the bucket is not the hour."


site/src/modules/dashboard/LicenseBanner/LicenseBannerView.stories.tsx:269

P3 [CRF-52] Two new user-facing warning classes ship with no story, so the rendering they were engineered around is pinned only from Go. (Nami P3, Kite P3)

AIGovernanceNearLimit, AIGovernanceOverLimitFromFeature and TelemetryRequiredError establish the project pattern: a backend warning class gets a story that renders LicenseBanner against a mock DashboardContext and asserts the text and the link. This PR adds two warning classes and no story. Neither new constant appears anywhere under site/src except typesGenerated.ts; both Nami and Kite grepped.

Nami's point about the asymmetry is the one that matters: "The Go test can only observe Go constants. Anyone editing isAIGovernanceNearLimitWarning, messageLink, or the variant dispatch changes what these two warnings look like, and every Go test still passes."

CRF-11 was exactly that class of break, and it was caught by a reviewer reading TSX, not by a test. CRF-1 is the same class again. Two stories driving LicenseBanner with the formatted soft-limit text and the formatted allocation text, asserting the variant class and the link, would close the gap and would fail today on CRF-1. site/AGENTS.md also makes this the required test form for this behavior rather than a nice-to-have.

🤖

enterprise/coderd/license/license.go:765

P3 [CRF-68] The CRF-15 fix replaced AddFeature with a direct map write in the new block and left the identical field-patch three lines above it. (Melody P3, Meruem P4)

The fix landed with a comment explaining why: routing through AddFeature "would silently drop the write if Feature.Compare ever stopped preferring a non-nil Actual". The managed-agent block, in the same function, still does the thing that comment argues against:

agentLimit.Actual = &managedAgentCount
entitlements.AddFeature(codersdk.FeatureManagedAgentLimit, agentLimit)

Both reviewers traced Compare for the managed-agent call and got the same answer: existing and add differ in exactly one field, every earlier step ties, and the write lands only at step 7, if f.Actual != nil && b.Actual == nil { return 1 }.

Melody's consequence: if that tiebreaker changes, Actual for FeatureManagedAgentLimit is silently dropped while LicenseManagedAgentLimitExceededWarningText still fires, because the warning reads the local managedAgentCount and not the feature. The banner then shows a limit-exceeded warning with no count behind it.

Meruem rated this P4 because nothing is broken today and the line is outside the diff; Melody rated it P3 because the two adjacent blocks now disagree about which mechanism is safe with the correct analysis written next to only one of them. Higher wins. One line, the same shape the PR already applied:

entitlements.Features[codersdk.FeatureManagedAgentLimit] = agentLimit

Raising it rather than merely reporting it because the author already did the analysis and applied it to one of the two instances.

🤖

enterprise/coderd/license/license.go:437

Note [CRF-61] CRF-13's rule holds at the two call sites the fix touched, and the same unauthenticated payload still carries a raw error from the line above. (Kurapika)

fmt.Sprintf("Invalid license (%s) parsing claims: %s", ..., err.Error()) puts a JWT parse error verbatim into entitlements.Errors, which GET /api/v2/entitlements serves without authentication (CRF-12, deferred to CODAGT-890). The fix generalized correctly from the runtime closure to the managed-agent closure; this is the third instance of the same class in the same function, and it predates the PR.

Recording rather than raising: the leak here is license-parse detail rather than schema detail, the line is outside the diff, and CODAGT-890 already owns the question of what that endpoint may expose. Worth having in the ticket, since "no raw errors on the entitlements payload" is a rule that is now true in two of three places and will read as true in all three to the next person who checks two of them.

🤖

🤖 This review was automatically generated with Coder Agents.

Comment thread coderd/database/queries/usageevents.sql Outdated
Comment thread coderd/database/queries/usageevents.sql Outdated
Comment thread enterprise/coderd/license/license.go
Comment thread enterprise/coderd/license/license.go Outdated
Comment thread codersdk/licenses.go Outdated
Comment thread codersdk/licenses_test.go Outdated
Comment thread enterprise/coderd/license/license_test.go Outdated
Comment thread enterprise/coderd/licenses_test.go Outdated
Comment thread enterprise/coderd/license/license_test.go Outdated
Comment thread enterprise/coderd/license/license_internal_test.go Outdated
- Tolerant claim decode: nonsensical agent runtime hour claims are ignored
  instead of invalidating the whole signed license (CRF-41). The validator
  is removed; decodeAgentRuntimeHours skips unusable soft/hard limits and
  treats a negative allocation as absent.
- Usage query failures land in entitlements.Warnings, not Errors, and the
  nil-closure dev error gets its own text that does not point at logs
  nothing wrote (CRF-42, CRF-43). All four texts are exported codersdk
  constants driven by tests (CRF-58), with log-content coverage for both
  closure error branches (CRF-44).
- The two runtime hours warning texts diverge before their first
  placeholder and name the license term; the banner classifies the
  advisory soft-limit text into the muted variant and drops the sales link
  from measurement diagnostics; stories pin all of it (CRF-1, CRF-39,
  CRF-40, CRF-52, CRF-60).
- Migration 000562 enforces the two invariants the query doc asserted:
  hb_agent_runtime_v1 created_at is hour-aligned and unique per bucket
  (CRF-50, CRF-51). InsertUsageEvent drops its (id) arbiter so replicas
  racing the same bucket are suppressed on either constraint.
- Query doc states bucket-granular semantics honestly instead of claiming
  exact bounds, documents the chat_messages tradeoff, publish-outcome
  independence, and the sibling's remaining day-granularity defect
  (CRF-35, CRF-48, CRF-64, CRF-69, CRF-70); bounds renamed to
  @start_time/@end_time (CRF-47).
- Test seams: AgentRuntimeHours builder takes *int64 for optional claims
  (CRF-62), the querier test builds payloads from usagetypes Fields()
  (CRF-32, CRF-63), the internal query test pins runtime_ms, licenses_test
  uses strings.Cut (CRF-65), stale rollup comments name usage_events
  (CRF-36, CRF-37), the divisor test is named for what it pins (CRF-38),
  and comment duplication and C1 references are gone (CRF-55, CRF-56,
  CRF-14).
- Managed agent Actual is written back directly like the runtime sibling
  (CRF-68); the Actual doc comment survives the API reference table
  (CRF-59).

jaaydenh commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

Pushed 41bda6d addressing the round-2 findings. Per-thread replies are on each comment; the three findings raised in the review body are also fixed:

  • CRF-52: three new stories drive LicenseBanner with the formatted soft-limit text (asserting the muted variant), the formatted allocation text (prominent), and the usage-unavailable diagnostic (asserting no sales link).
  • CRF-68: the managed-agent block now writes the patched feature back directly, same as the runtime sibling, with the same rationale comment.
  • CRF-61: recorded; the remaining raw-error site at license.go:437 predates this PR and stays with CODAGT-890.

One deviation worth flagging: CRF-51's unique index exposed that an ON CONFLICT (id) arbiter breaks concurrent replicas racing the same bucket (caught by TestGeneratorConcurrentReplicas), so InsertUsageEvent now uses a plain ON CONFLICT DO NOTHING. A duplicate bucket under any id is suppressed rather than loud, which keeps Actual correct in all cases; details on the CRF-51 thread.

Validation: make gen clean; golangci-lint clean; go test green for codersdk, coderd/database (queries, trigger, migrations, dbauthz suite), enterprise/coderd/license, enterprise/coderd (licenses/entitlements), enterprise/coderd/usage; pnpm test:storybook 14/14 passing for the banner stories; tsc and biome clean.

@codex review
/coder-agents-review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Bravo.

Reviewed commit: 41bda6d145

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Two blockers and a split, then a panel round that found six more P2s in exactly the code Law says has not been reviewed.

The merged tree cannot start coderd. Migration version 000562 is already taken on main by 000562_oauth2_public_client_tokens. Law found it; I verified it before posting. git merge-tree --write-tree origin/main HEAD produces a tree containing all four files, and the collision is silent because the filenames differ. golang-migrate v4.19.0, which go.mod pins, returns source.ErrDuplicateMigration from iofs.go:69 when a second migration lands on an existing version, so the embedded source fails to open and every path that loads migrations fails before running any of them. main is at 000564; ./coderd/database/migrations/fix_migration_numbers.sh renumbers to 000565. Check against origin/main afterwards, not the merge base, which is four migrations stale.

That also reconciles a disagreement between two of my own reviewers about the red CI. Netero ran all four failing checks locally at head and every one passed, including the full make lint with the pinned golangci-lint and a complete offlinedocs export, and concluded the diff was not at fault. He was testing head, which has one 000562. CI checks out the merge ref, which has two. Razor reproduced the merge-ref failure and reports it accounts for all four checks; Netero's local runs bound how much of that is the diff itself. Either way, renumber first and re-read CI before spending a round on the code.

Law returned a mandatory split, and the panel then ran anyway because the tooling requires it once a panel has reviewed a PR. That turned out to matter. Law's argument was never about size: three independent concerns entered in 41bda6d145, the head commit, and no panel had seen them. The panel found six P2s this round and every one of them is in those three concerns, not in the feature. That is the split argument proving itself inside a single round.

Law's slices, in dependency order: (1) the write-path invariants, migration 000562 plus the InsertUsageEvent arbiter change; (2) usage-measurement diagnostics; (3) stop rejecting licenses over unusable runtime-hour claims; (4) the feature. He rejects cutting slice 4 further, by name. The counter-argument that splitting at round 4 discards continuity is real, and checkable: git log -S puts all three carved-out concerns in the head commit, so the continuity that exists covers the feature and the slices have none to lose.

The theme of this round is that both round-3 P2 fixes over-corrected toward silence, and the diagnostics rework traded one wrong signal for another.

Silence. CRF-41 said rejecting a whole signed license over agent_runtime_hours_limit_soft: 0 was disproportionate; the fix deleted validateAgentRuntimeHours and all five sentinel errors, so a license with a soft limit above its allocation now yields a healthy-looking deployment that never fires the warning the customer was sold, and TestAgentRuntimeHoursClaimTolerance pins that silence rather than catching it (CRF-73). CRF-51 asked that a duplicate bucket surface rather than be counted, which is the rationale the panel accepted it on; the fix made it silent and widened silent suppression to the insert every usage event type shares (CRF-71). In both cases the destination is defensible and the mechanism removed a signal nobody asked to remove.

Signals. CRF-43 asked that a failed usage query stop rendering as a license error. It now renders as a license limit warning: the four diagnostics are excluded from isMutedWarning, so a database blip paints the prominent orange banner under the heading "Your license limits have been exceeded" (CRF-83, ten reviewers). And demoting them from Errors to Warnings moved them out of coderd_license_errors, into the warnings gauge, and into the entitlement warning HTTP header, silently changing two documented Prometheus gauges that nothing in the PR body mentions (CRF-82).

Takumi's P2 is the one I would fix first after the P0, because it is load-bearing for three separate things this PR added. The cancellation bail is xerrors.Is(err, context.Canceled), and a real Postgres cancel does not return a context sentinel. Measured against this workspace's Postgres with coder's own lib/pq fork, 200 staggered cancels: 195 came back as pq.Error 57014, 5 as sentinels, and those 5 landed before dispatch. So a dropped browser request during POST /api/v2/licenses publishes a false deployment-wide "usage unavailable" warning that persists until the next refresh, and every shutdown that interrupts a refresh writes the error log the suppressor added in round 3 exists to prevent.

Two contradictions that a split would have caught. The migration says a duplicate bucket "surfaces as an insert error and gets logged"; the query doc in the same commit says it is silently suppressed. Netero settled it against a live database: INSERT 0 0. Three reviewers found the false claim appears in the migration twice, and four found a fourth stale ON CONFLICT (id) reference that Netero missed, in querier_test.go:10766, where it is a test's stated reason for passing. Migration files are the one artifact nobody edits again, and you are already renumbering this one.

One test that cannot fail. Zoro checked the CRF-32 fix rather than accepting it: the loop asserts each HBAgentRuntime.Fields() key appears in the generated SQL, and the SQL contains AS total_runtime_ms, so require.Contains(sql, "runtime_ms") passes even if the event_data->> key is renamed. I reproduced it. The pin added to close a silent-zero class is vacuous for the literal it was added for (CRF-89). Netero separately proved the new stories vacuous the same way: all fourteen pass with isMutedWarning stubbed to false (CRF-75).

What verified clean, so it is not relitigated: the half-open bounds and the @start_time/@end_time rename; the stable operator-facing strings with causes logged and no driver text on the payload; the two warning-text prefix relations the banner actually reads; make -B gen/db reproducing every generated file byte for byte; the CHECK constraint's timezone correctness, which Knuckle measured rather than assumed, along with his finding that it is not a lock problem, so do not reach for NOT VALID. Kurapika confirmed the new authorization check is correctly scoped and that no untrusted input reaches either path.

CRF-63 had no panel vote available when I first drafted this round, and I dropped it as orchestrator; Mafu-san and Pariston have since evaluated the defense and agree it is acceptable, so it is closed either way.

Law, on why this is a split and not a nag: "A contradiction that survives inside a single commit is what an unreviewed concern looks like."


enterprise/coderd/usage/generator.go:52

P3 [CRF-78] Three comments still name ON CONFLICT (id) DO NOTHING as the mechanism that makes concurrent replicas safe; the arbiter no longer exists. (Netero)

The Generator doc comment reads "Deterministic event IDs plus the database's ON CONFLICT (id) DO NOTHING make concurrent replicas safe without locking." Same dead reference at generator.go:160 and at querier_test.go:10766.

The last one matters most: it sits eight lines above TestGetTotalUsageHBAgentRuntimeV1 and states that the re-insert idempotency the test above it relies on comes from the (id) arbiter. So the next person to touch that test reads a false explanation of why it passes.

None of these three files is in the diff, and all three became wrong because of it. That is the signature of a change reaching past its own boundary, and it is a second piece of evidence for treating the write-path change as its own slice: the concern has consumers this PR never looked at.

🤖

enterprise/coderd/license/license.go:1018

P2 [CRF-84] The claim constants still document parse-time validation that this diff deleted, including a soft-limit range that now contradicts the decoder. (Mafuuu P2, Razor P2, Gon P2, Meruem P3, Leorio P3)

Deleting validateAgentRuntimeHours was the CRF-41 fix. The documentation that described it stayed. Five reviewers found the surviving claims, and they are on the constants a license issuer reads to learn what a valid license looks like.

Razor: the block still specifies parse-time validation and states a soft-limit range that the tolerant decoder no longer enforces, so the two now disagree about what a soft limit may be. Meruem counted four comments asserting a validation that no longer exists. Leorio found the one that matters most, at license.go:550: it is the comment a reader lands on when asking what happens to a bad claim, and it still promises rejection.

Gon's is the sharpest and is arguably its own defect: decodeAgentRuntimeHours documents a "concurrency-limited mode" in the present tense, and that mode exists nowhere in the repository. It is C1's future behaviour described as current fact, on the function a future implementer will read as the specification.

Consequence: the deployment now silently drops a claim the docs say will be rejected, and the only written description of the contract is the wrong one. This is the same shape as CRF-72, and it is why Law wants the validation change reviewed as its own slice: the code change was one function, the contract it invalidated is spread across four comments and two files.

🤖

codersdk/deployment.go:412

P3 [CRF-91] CRF-59's fix rewrote two bullet lists in this struct and left the third, so the regenerated table still ships a malformed cell one row below the row it fixed. (Kurapika P3, Leorio P3, Gon P3, Ryosuke Nit, Knov P4)

Five reviewers. docs/reference/api/schemas.md:7799 still renders the usage_period description as a run-on cell with inline hyphens, exactly the defect CRF-59 described, in the same generated table, one row below the row this PR fixed and regenerated.

Knov's framing is the one to act on: the fix rewrote two doc comments instead of the generator that flattens them, and the surviving broken cell is the one the fixed row's own text refers the reader to. So the PR fixed the sentence that points at a cell it left broken.

Two ways to close it. Rewrite the third bullet list as prose the way the other two were, which is the cheap, in-scope fix and makes the whole table consistent. Or teach the doc-comment-to-table generator to render lists, which is out of scope here but is the reason this keeps recurring. Kurapika notes this PR regenerates the table either way, so the marginal cost of the first option is one comment and a make gen you are already running.

🤖

enterprise/cli/server.go:148

P4 [CRF-92] The only usage.Cron job registration omits EventType, so Register rejects it, the error is discarded, and no hb_ai_seats_v1 event is ever produced. (Kite P4, Melody P4)

Two reviewers found this independently while tracing the created_at alignment question for the new CHECK constraint. Register validates that a CronJob carries an EventType and returns an error when it does not; the single production call site omits the field and the returned error is discarded, so the job is never registered and the event type is never generated.

Outside this diff, and reported rather than requested. It is here for two reasons. It is the second writer to usage_events that the new CHECK constraint would apply to, which is how both reviewers found it, so anyone reasoning about that constraint's blast radius needs to know this writer is currently inert. And Kite notes the discarded error is what makes it invisible: the deployment looks configured.

If hb_ai_seats_v1 is expected to be flowing in production today, this is worth checking before it becomes someone's billing discrepancy. A ticket is the right home.

🤖

🤖 This review was automatically generated with Coder Agents.

Comment thread coderd/database/migrations/000562_usage_events_agent_runtime_invariants.up.sql Outdated
Comment thread coderd/database/queries/usageevents.sql Outdated
Comment thread coderd/database/migrations/000562_usage_events_agent_runtime_invariants.up.sql Outdated
Comment thread coderd/database/migrations/000562_usage_events_agent_runtime_invariants.up.sql Outdated
Comment thread enterprise/coderd/license/license.go Outdated
Comment thread enterprise/coderd/license/license.go Outdated
Comment thread site/src/modules/dashboard/LicenseBanner/LicenseBanner.tsx
Comment thread enterprise/coderd/license/license_test.go Outdated
Comment thread site/src/modules/dashboard/LicenseBanner/LicenseBannerView.stories.tsx Outdated
Comment thread coderd/database/migrations/000562_usage_events_agent_runtime_invariants.down.sql Outdated
- Renumber migration 000562 to 000565 (000562 is taken on main by
  oauth2_public_client_tokens; duplicate versions abort migration loading)
- Restore InsertUsageEvent's ON CONFLICT (id) arbiter so a duplicate
  bucket under a different id raises instead of being silently dropped;
  the generator resolves the benign same-id replica race by checking the
  deterministic id after a unique violation
- Keep usage-measurement diagnostics in entitlements.Errors so the
  coderd_license_errors gauge keeps alerting on measurement failures;
  the dashboard classifies the exact texts as muted diagnostics instead
  of license errors, with a non-exceedance heading for all-muted banners
- Treat canceled queries that reached Postgres (SQLSTATE 57014) like the
  context sentinels via database.IsQueryCanceledError, both for the
  entitlements bail and the error-log suppressors
- Make a nil usage closure a hard developer error instead of an
  operator-facing warning; delete both NotConfigured SDK constants
- Surface dropped runtime-hour claims with a new claims-ignored warning
  so an incorrectly issued license is detectable without invalidating it
- Extract the shared usage-measurement policy into measureUsage so the
  managed-agent and runtime blocks cannot drift
- Use AsUsagePublisher instead of AsSystemRestricted for both usage
  queries; the publisher subject carries exactly the needed permission
- Pin the payload key via its extraction expression (event_data->>'...')
  so the alias total_runtime_ms cannot satisfy the assertion
- Add trigger-test cases for the unique index and hour-aligned CHECK,
  storybook variant/link assertions for every diagnostic text, and a
  non-exceedance heading story; test helper takes *int64 soft limit
- Fix stale ON CONFLICT (id), validation-era claim docs, and exactness
  comments; de-list the usage_period doc bullets for the API table

jaaydenh commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

Pushed 747e8fd (+ comment-grammar follow-up 8d0e5c8) addressing the round-4 findings. Per-thread replies are on each comment; the four findings raised in the review body:

  • CRF-80 (P0): renumbered the migration to 000565 via fix_migration_numbers.sh and verified git merge-tree against origin/main shows no version collision.
  • CRF-84: the validation-era claim documentation is gone. The claim-constant block, the feature-loop comment at the old license.go:550, and decodeAgentRuntimeHours's doc now all describe the tolerant decode, including the per-claim keep/ignore conditions. Gon's phantom "concurrency-limited mode" is now explicitly future tense: "CODAGT-856 will make a zero allocation force a concurrency-limited mode; that mode does not exist yet."
  • CRF-78: all three stale ON CONFLICT (id) comments (generator doc, generator body, querier_test.go) were rewritten alongside the CRF-71 resolution, and since the arbiter is restored they now describe the real mechanism: arbiter no-op for committed re-inserts, unique-violation for different-id duplicates, generator-resolved race for concurrent same-id inserts.
  • CRF-91: the third bullet list (UsagePeriod) is rewritten as prose like its two siblings; docs/reference/api/schemas.md regenerated and the usage_period cell now renders as a single clean paragraph.
  • CRF-92: out of scope as you noted; filed as CODAGT-899 with both reviewers' analysis (inert hb_ai_seats_v1 writer, discarded Register error).

Two deliberate decisions to highlight, both taken with their tradeoffs written down:

  1. CRF-71/72/77: ON CONFLICT (id) DO NOTHING is restored, but with one correction to the finding: the same-id concurrent-replica race is real (the arbiter pre-check only sees committed rows) and reproducibly fails TestGeneratorConcurrentReplicas with a bare arbiter restore. The generator now resolves that specific race by confirming the bucket exists under the deterministic id, and propagates everything else. Details on the CRF-71 thread.
  2. CRF-82/83: took the "keep the diagnostic in Errors and fix the banner classification" option. coderd_license_errors keeps counting measurement failures exactly as on main; the dashboard classifies the exact diagnostic texts as muted notices with a non-exceedance heading ("License notices") when the whole banner is muted.

On Law's split: I don't have standing to restructure the PR into four, but this round shrank the disputed surface toward his slice boundaries: the write-path invariants now carry their own tests and a self-contained renumbered migration, the diagnostics rework is centralized in one measureUsage helper plus one frontend set, and the claim-validation change is one function with its warning. If the split is still wanted, those seams are now clean to cut along.

Validation: make gen clean and committed; golangci-lint clean on all touched packages; go test green for codersdk, coderd/database (queries, trigger incl. the two new invariant cases, migrations), coderd/database/dbauthz (method suite, after the AsUsagePublisher change), enterprise/coderd/license, enterprise/coderd (entitlements/licenses), enterprise/coderd/usage (incl. 20x TestGeneratorConcurrentReplicas); storybook 17/17 with the muted-classifier mutation now failing 5 tests (round-3 gap closed); tsc, biome, typos and emdash lints clean. PR body rewritten against the head commit (CRF-86).

@codex review

/coder-agents-review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8d0e5c875a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread site/src/modules/dashboard/LicenseBanner/LicenseBanner.tsx

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The mandatory split is discharged. Sixteen reviewers addressed it independently and all sixteen concluded the three concerns are reviewable in place. Netero supplied the mechanical evidence that each is independently tested at head; Bisky ran those tests rather than reading them; Knuckle, Takumi, Knov, Razor, Meruem, Ryosuke, Zoro, Leorio, Pariston, Hisoka, Luffy, Killua and Pen Botter each checked the seams from their own domain and reached the same answer. "I don't have standing to restructure the PR into four" is an honest statement about authority rather than a dodge, and the reshaping is genuine partial compliance.

One residual, recorded rather than resolved, because it is a human call about merge strategy. Mafu-san split Law's two legs and only one is discharged. Leg (a), three unreviewed concerns positioned to merge, is gone: the round-4 panel reviewed exactly those concerns and found six P2s in them. Leg (b) is about revert granularity and no amount of in-place seam hygiene answers it. A squash merge makes the migration, the diagnostics rework and the claim tolerance one revert unit, and the piece most likely to need an independent revert is the migration, because it validates existing rows and deliberately aborts the upgrade on a violator. Reverting it post-merge drags the feature with it. Mafuuu reached the same place from the contract seat: revertibility is one-directional.

Round 4's CRF-71 was wrong and your correction is right. I asked for verification rather than acceptance and got it twice. Netero removed the race-resolution branch, leaving the arbiter alone, and TestGeneratorConcurrentReplicas failed 4 of 4 runs with pq: duplicate key value violates unique constraint "idx_usage_events_agent_runtime"; with the branch restored, 8 of 8 pass. Takumi confirmed the interleaving under the race detector. The mechanism is exactly as you described: ON CONFLICT (id) DO NOTHING absorbs concurrent same-id inserts only on the arbiter index, so the finding's claim was true before this PR added a second unique index and false after. The branch is load-bearing, not defensive decoration. Correcting a panel with a reproducible test is the right way to handle a review finding you think is wrong, and it worked.

The P0 is fixed and I verified it against the merge tree rather than the branch: git ls-tree over git merge-tree --write-tree origin/main HEAD now shows only 000562_oauth2_public_client_tokens at that version and the runtime-hours migration at 000565.

Four P2s, five P3s, four nits. The one to fix first is Hisoka's, and it is uncomfortable because this panel caused it.

CRF-102: the CRF-87 fix matches SQLSTATE 57014, and Postgres uses 57014 for statement_timeout kills as well as client cancels. I verified the chain: IsQueryCanceledError matches any pq.Error with that code, measureUsage treats it as "the caller went away" and fails the whole LicensesEntitlements call, and enterprise/coderd/coderd.go:791 runs updateEntitlements inside New() and returns the error. So on a deployment with an aggressive statement_timeout and a slow usage query, every refresh aborts with only a Warn log, entitlements freeze at the last good snapshot, none of the usage-unavailable diagnostics this PR built for exactly this case ever render, and coderd crash-loops on restart. On main the same 57014 landed in Errors as a diagnostic and the refresh completed. Round 4 asked you to catch 57014 from a client cancel, which was right; nobody said 57014 is only a client cancel. Hisoka's bound is the cheap fix: treat 57014 as an abort only when ctx.Err() != nil, or distinguish on pq.Error.Message. Not reproduced against a real timeout, and the 57014-for-statement_timeout mapping is standard Postgres, so this is full severity anyway.

That is the fourth consecutive round in which a fix this panel asked for introduced a new defect, and Mafu-san flagged the narrower version of the same pattern: CRF-101 is the third consecutive round in which a review fix shipped a new error branch with zero coverage. I am not asking you to fix the pattern. I am naming it because at this point the shape of our findings is part of the cause: a review that says "catch X" without saying "and here is what X also means" keeps producing this.

Nine reviewers landed on the claims-ignored warning (CRF-103) from nine directions, which is the widest convergence of the round. It names no license and no claim, nothing is logged server-side, and it fires once per parsed license, so a customer who does what it says still sees it. Pariston's version is the one to read: a superseded license keeps the banner up after the replacement lands.

What verified clean, so it is not relitigated: the arbiter restore and all four stale comment sites; the existsErr sites' behaviour under a real race; the migration's two constraints, each exercised at the SQL layer; the CRF-89 pin now asserting event_data->>'field' rather than a bare key; CRF-99 verified by two reviewers reading the head tree; and CRF-75 verified by mutation, with the storybook suite now failing 5 tests when isMutedWarning is stubbed, which is the exact mutation Netero used to prove the gap in round 3. CRF-81 and CRF-88 were accepted with reasoning that four reviewers independently judged sound, and the migration now states the deliberate abort in the artifact.

No comments folded into the review body this round. I checked all twenty-two candidate lines against the diff hunks before drafting, which is the fix for a failure mode that has cost this review two rounds.

Luffy, on the banner: "OI THIS PART IS GREAT. The warnings finally act like what they are."

🤖 This review was automatically generated with Coder Agents.

Comment thread enterprise/coderd/license/license.go Outdated
Comment thread enterprise/coderd/usage/generator.go Outdated
Comment thread enterprise/coderd/license/license.go Outdated
Comment thread enterprise/coderd/license/license_test.go Outdated
Comment thread enterprise/coderd/license/license.go Outdated
Comment thread codersdk/licenses.go Outdated
Comment thread site/src/modules/dashboard/LicenseBanner/LicenseBannerView.stories.tsx Outdated
Comment thread coderd/database/querier_test.go Outdated
Comment thread codersdk/licenses_test.go Outdated
Comment thread enterprise/coderd/license/license_test.go Outdated

jaaydenh commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

Pushed c29d9cd addressing the round-5 findings. Per-thread replies are on each comment. Highlights:

  • CRF-102 (P2): the usage-measurement abort no longer keys on the error looking like a cancel; usageMeasurementAborted aborts only when the computation's own context is dead, so a statement_timeout 57014 degrades into the usage-unavailable diagnostic instead of wedging every refresh and coderd startup. Both layers share the one helper (also closes CRF-105), pinned by TestUsageMeasurementAborted and new StatementTimeout table cases.
  • CRF-101 (P2): the unique-violation fallback now distinguishes "exists check failed" (carrying both errors via errors.Join) from "bucket held under a foreign id", and TestGenerateBucketUniqueViolation pins all three branch outcomes deterministically (also closes CRF-109).
  • CRF-103/107 (P3): ignored runtime-hour claims (including the feature name minted as a claim) are now logged server-side with the license UUID and claim names; the banner text no longer asserts a single license and points at the logs.
  • CRF-104/106 (P2): the noRuntime comment states the real nil-closure contract, and each duplicated rationale now has one owning definition with pointers elsewhere (gauge/muted split: the codersdk constant doc; arbiter race: generateBucket; staleness: the four named constants).
  • CRF-108/110/111 (nits): soft-limit copy reads "at or above the advisory soft limit", the unavailable pair is renamed *UsageUnavailableErrorText, and the story formatter moved into LicenseBanner.tsx as the exported formatLicenseMessage shared with production.

Validation: go build ./..., license/usage/codersdk package tests, TS typecheck, biome, and the 17 LicenseBanner storybook interaction tests all pass locally.

jaaydenh commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

/coder-agents-review

jaaydenh commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c29d9cd60f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread site/src/modules/dashboard/LicenseBanner/LicenseBannerView.stories.tsx Outdated

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Blocked on three unanswered nits. Eleven of the fourteen open findings were addressed, including both P2s, and the two that mattered most were addressed well. This is a process gate, and clearing it costs three replies.

Unaddressed, with no code change and no response on any of their threads:

CRF-112 (coderd/database/querier_test.go:10806). The CHECK-constraint assertion hand-copies the constraint name into ErrorContains where its sibling sixteen lines up uses the generated database.Check* constant.

CRF-113 (codersdk/licenses_test.go:53). One mutual-prefix assertion still guards a classifier that does not exist, and its failure message describes that classifier as real.

CRF-114 (enterprise/coderd/license/license_test.go:3298, now :3425). Hand-rolled map copy loop where maps.Copy applies.

I want to be precise about why these block, because the last time I blocked this review it was on a finding you had no way to reply to. That is not the case here. All three are inline threads, all three are still unresolved, and none has a reply. A one-line "not doing this, and here is why" closes any of them; the gate is a response, not a change. If you disagree with all three, say so and they close as contested.

On the round-5 fixes, recorded rather than reviewed, because no panel ran: the churn guard classifies CRF-101 through CRF-111 as addressed, including CRF-102, where the 57014 match is now bounded, and CRF-101, where the unique-violation fallback distinguishes the exists-check failure. Those are the two I would most want verified, and they have not been. Every "addressed" here is a diff reading, not a panel checking the result, and four rounds running this review has found a fix that was incomplete or that introduced a new defect.

Separately, and independent of the gate: CI is red on Flake Check (run 92803291237). That job runs the changed tests at -count=35 -shuffle=on -parallel=16 against real Postgres, which is exactly the shape of run that finds a test whose coverage depends on a schedule landing. I tried to narrow it and could not reproduce it locally at those settings for enterprise/coderd/usage (TestGenerator), the full enterprise/coderd/license package, or coderd/database (TestGetTotalUsageHBAgentRuntimeV1, TestUsageEventsTrigger), all with DB=ci. So this is an inability to reproduce, not a diagnosis, and the failing test name is in the job log rather than anything I can see from here.

Worth connecting to a finding you already have: Bisky's CRF-109 said in round 5 that TestGeneratorConcurrentReplicas's only positive coverage is a scheduling accident, and asked for two deterministic cases that cannot pass by luck. The churn guard reads that as addressed. If Flake Check is failing on that test, the deterministic cases are both the fix and the proof.

Reply to the three nits, or push a change, and the next round runs the panel against the whole diff including verification of the round-5 fixes.

🤖 This review was automatically generated with Coder Agents.

…lure

- TestEntitlements/FullLicense enabled every FeatureName as a bare claim,
  including agent_runtime_hours, which decodeAgentRuntimeHours drops with
  the claims-ignored warning, failing the test's empty-warnings assertion
  in all four red CI jobs. The test now encodes the feature as its
  allocation claim, like license_test.go's SingleLicenseAll.
- CRF-112: the hour-aligned CHECK assertion uses the generated
  database.CheckUsageEventsAgentRuntimeHourAligned constant.
- CRF-113: dropped the mutual-prefix assertion pair guarding a
  non-existent prefix classifier; the surviving assertion says what it
  really guards (the allocation-reached text must not match the
  soft-limit prefix, which is a real startsWith consumer).
- CRF-114: maps.Copy replaces the hand-rolled map copy loop.
- Codex P1: LicenseBannerView exposes data-variant, and the stories
  assert it instead of Tailwind background classes, per the
  site/AGENTS.md rule against class-name assertions. Verified by
  mutation: stubbing isMutedWarning to false fails 5 story tests.
- Comment pass over license.go: removed comments restating adjacent
  code, deduplicated the measureUsage logging rationale, and tightened
  the decodeAgentRuntimeHours and FeatureArguments docs.

jaaydenh commented Aug 9, 2026

Copy link
Copy Markdown
Contributor Author

Pushed 907c91a addressing the round-6 gate and the red CI.

  • CI (Flake Check, test-go-pg, test-go-pg-17, test-go-race-pg): all four jobs failed on the same assertion: TestEntitlements/FullLicense enables every FeatureName as a bare claim, and since round 4 made the bare agent_runtime_hours claim tolerate-and-warn instead of skip, the test tripped the claims-ignored warning and failed assert.Empty(t, res.Warnings) deterministically (35/35 in Flake Check, so not a flake and not TestGeneratorConcurrentReplicas). The test now encodes the feature as its allocation claim, the same way SingleLicenseAll does. Verified green against Postgres locally.
  • CRF-112, CRF-113, CRF-114: all three nits are fixed rather than contested; per-thread replies on each.
  • Codex P1: the stories no longer assert Tailwind classes; LicenseBannerView exposes data-variant and the stories assert that, keeping the mutation-proven coverage of the muted/prominent classifier (stubbing isMutedWarning to false still fails 5 stories).
  • Also a comment pass over license.go: removed comments restating adjacent code (the duplicated measureUsage logging rationale, the decode early-return narration) and tightened the FeatureArguments and decodeAgentRuntimeHours docs to only what the code cannot say.

Validation: enterprise/coderd/license, TestEntitlements/FullLicense, and the coderd/database usage tests against Postgres; banner stories, biome, and tsc for the frontend; make pre-commit (including lint/go) on commit.

jaaydenh commented Aug 9, 2026

Copy link
Copy Markdown
Contributor Author

/coder-agents-review

jaaydenh commented Aug 9, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Delightful!

Reviewed commit: 907c91a24d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is the first round where nothing the panel found is about how the code behaves. Two P2s, five P3s, three nits and a note, and every one of them is documentation, test coverage, or a comment. Six reviewers returned nothing at all, which has not happened in seven rounds.

The round-5 P2s are both verified fixed, by execution rather than reading, and by reviewers who did not coordinate. CRF-102: seven reviewers independently confirmed the abort now keys on the computation's own ctx.Err() rather than on SQLSTATE 57014, so a statement_timeout can no longer wedge entitlements or stop coderd booting. Meruem's framing is the one worth keeping, that the fix addresses the root cause rather than the symptom, because keying on the caller's context is what the branch always meant. CRF-101: the exists-check failure is carried with errors.Join(err, existsErr) instead of discarded, and the branch that had zero coverage now has three deterministic cases.

CRF-109 deserves its own paragraph because it is the finding that predicted the round-6 CI failure class. Bisky asked for coverage that could not pass by luck, and TestGenerateBucketUniqueViolation pins all three unique-violation outcomes without racing for any of them. Five reviewers verified it by running it.

On the round-6 red: Netero settled it by mutation. With the TestEntitlements/FullLicense special-case guard disabled, the test fails 1 of 1 at head against real Postgres via the claims-ignored warning; with it, it passes. So that was a deterministic failure surfacing under -count=35, not a schedule-dependent flake, and there is nothing left to chase there. Worth noting the failure and CRF-107 shared a root cause: agent_runtime_hours is not a valid claim name, which is exactly the silent-drop the finding described, and the production half was fixed separately by adding the feature name to ignoredClaims.

Law re-ran, because effective additions grew 669 since his last analysis, and returned don't split, advisory. His reasoning is worth quoting because it closes the thread properly rather than by fatigue: the round-4 mandatory split targeted this same concern set and was discharged 16/16, new evidence would have to be a new concern or a new risk domain entering the diff, and he verified the opposite, that every line added since is remediation of panel findings inside the already-enumerated concerns. "The scrutiny a split would have bought has been delivered by process." One residual stands unchanged and is a merge-strategy call for a human, not a code finding: under a squash merge the slices are one revert unit, and the migration is the piece most likely to need an independent revert because it deliberately aborts the upgrade on a violator. Law notes slice A is self-contained and could be cherry-picked out before merge if independent revertibility is wanted.

The two P2s are both recurrences, and I want to be straight about one of them.

CRF-115: the PR body still says a canceled query "(context sentinel or SQLSTATE 57014) aborts the refresh", which is precisely the design the CRF-102 fix removed. Five reviewers caught it. This is the same class as CRF-86, which you fixed in round 4 by rewriting the body against the head commit; the body has gone stale again because the fix changed the behaviour it described. Under squash merge the body is the permanent commit message, so a future bisect lands on a message describing a classifier the commit deleted.

CRF-116: fifth consecutive round of comment duplication, and the commit that claimed to deduplicate introduced two verbatim copies twenty lines apart. I am keeping it at P2 for consistency with how I rated the same finding in round 5, but the severity is clearly not the lever, so the ask changes. The last four passes deleted instances. That is why it keeps coming back: deleting a copy does not decide where the fact lives. Pick the owning location for each fact once, put the rationale there, leave a one-line pointer everywhere else, and stop rewriting rationale during fix passes, which is where every new copy has come from.

Everything else is small and specific: three test gaps (CRF-117, CRF-118, CRF-121), one layering residue from CRF-105 that the measureUsage extraction half-closed (CRF-119), one migration comment that mislabels what the alignment CHECK buys (CRF-120), and three nits.

Kite's note is the one I would not lose: a zero-value FeatureArguments.Logger silently discards the ignored-claims detail that the published warning tells the operator to go look for. Nothing in this PR constructs one, so it is not a defect today.

Bisky, on the stories, which is a sentence I did not expect to be writing about this file three rounds ago: "real tests, not costume jewelry."

🤖 This review was automatically generated with Coder Agents.

Comment thread enterprise/coderd/license/license.go Outdated
Comment thread enterprise/coderd/license/license.go Outdated
Comment thread enterprise/coderd/license/license.go
Comment thread site/src/modules/dashboard/LicenseBanner/LicenseBanner.tsx
Comment thread enterprise/coderd/license/license.go Outdated
Comment thread codersdk/licenses_test.go Outdated
Comment thread codersdk/licenses_test.go Outdated
Comment thread enterprise/coderd/license/license_test.go Outdated
Comment thread site/src/modules/dashboard/LicenseBanner/LicenseBanner.tsx
Comment thread enterprise/coderd/license/license.go
measureUsage now owns the whole usage-failure policy: the cause is logged
there instead of in each Entitlements closure, so the abort rule and the
log-suppression rule can no longer drift apart (CRF-119), and the closures
are reduced to plain queries, removing the duplicated logging rationale
(CRF-116). Comment ownership is consolidated: the classifier story lives on
the codersdk warning-text constants, the tolerate-and-warn rationale on
decodeAgentRuntimeHours, and the unit rationale on agentRuntimeMsToHours,
with pointers elsewhere (CRF-116, CRF-121). The claims-ignored warning dedup
guard is pinned by a two-license test asserting one warning and per-license
log detail (CRF-117), the AI Governance near-limit branch of isMutedWarning
is pinned by its own story (CRF-118, verified by branch-local mutation), the
four usage-failure subtests share a fixture helper (CRF-123), the migration
comment now assigns SUM protection to uniqueness and attribution protection
to the alignment CHECK (CRF-120), the test-local cutPrefix is renamed
templatePrefix to stop colliding with strings.CutPrefix (CRF-122), and
formatLicenseMessage's doc states its %d-only substitution rule (CRF-124).

jaaydenh commented Aug 9, 2026

Copy link
Copy Markdown
Contributor Author

Pushed 6ced6b6 addressing the round-7 findings; per-thread replies are on each comment and all threads are resolved.

  • CRF-115 (P2): the PR body's cancellation sentence now describes the shipped design: aborts key on the refresh's own dead context, never on the error looking like a cancel, since 57014 also fires for statement_timeout.
  • CRF-116 (P2) + CRF-119: fixed together by ownership rather than deletion. measureUsage now takes the logger and owns classification, logging, and publication; the two Entitlements closures are plain queries carrying no policy, which removed the duplicated rationale's last home. Owning locations now: query doc = bounds/bucket semantics, decodeAgentRuntimeHours = tolerate-and-warn, codersdk constants = classifier couplings, agentRuntimeMsToHours = units; everything else points there.
  • CRF-117: WarningDeduplicatedAcrossLicenses pins one warning + per-license log detail for two violating licenses.
  • CRF-118: AIGovernanceNearLimit asserts the muted variant; verified by branch-local mutation (disabling only that branch of isMutedWarning fails exactly that story).
  • CRF-120/121/122/123/124: migration comment credits SUM protection to uniqueness and the CHECK to attribution; the codersdk test names both prefix consumers; cutPrefix renamed templatePrefix; the four usage-failure subtests share premiumRuntimeHoursFixture; formatLicenseMessage's doc states the %d-only substitution rule.
  • CRF-125 (note): acknowledged, kept as-is; the zero-value Logger hazard is documented on the field and nothing constructs one.

Validated: enterprise/coderd/license full package against Postgres, codersdk warning-texts test, LicenseBanner Storybook suite 17/17 plus the branch-local mutation check, tsc/biome clean, pre-commit (gen/fmt/lint) green.

Comments now cover only what is not obvious from the code: cross-file
couplings, failure-policy rationale, and invariants the SQL depends on.
Removed the comment-ownership pointers, the restated mechanics, and the
digressions into alternatives that were not taken.
@coder-tasks

coder-tasks Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Documentation Check

New Documentation Needed

  • docs/ai-coder/ai-governance.md - Document the Coder Agent runtime hours allocation, its advisory soft limit, and hard limit, plus the two new operator-facing License Banner warnings this PR ships (soft-limit approaching and allocation-reached). The banner text references "Coder Agent runtime hours," an "allocation," and an "advisory soft limit," and states additional usage may be billable, but none of these concepts are defined anywhere in docs/. ai-governance.md already documents Agent Workspace Build usage and the Deployment > Licenses view, so it is the natural home. This matches the author-noted follow-up CODAGT-891 and review findings CRF-23 / CRF-54.

Note

docs/reference/api/schemas.md is auto-generated and was correctly regenerated for the codersdk.Feature doc changes. No manual action needed there.


Automated review via Coder Agents

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