From 12e8b1a4e8428236347c6b9599a939ded80004ec Mon Sep 17 00:00:00 2001
From: thrr87 <193831865+thrr87@users.noreply.github.com>
Date: Wed, 16 Sep 2026 17:49:54 +0200
Subject: [PATCH 1/2] fix: decode omitted Grok credit percentage as proto3 zero
---
Resources/Info.plist | 4 +-
Sources/CodexLimits/GrokBillingClient.swift | 7 ++-
Sources/CodexLimits/GrokIntegration.swift | 2 +-
.../GrokBillingClientTests.swift | 26 +++++----
.../GrokIntegrationTests.swift | 58 ++++++++++++-------
docs/MEASUREMENT-CONTRACT.md | 2 +-
docs/research/grok-zero-usage-2026-09-16.md | 31 ++++++++++
7 files changed, 91 insertions(+), 39 deletions(-)
create mode 100644 docs/research/grok-zero-usage-2026-09-16.md
diff --git a/Resources/Info.plist b/Resources/Info.plist
index 2ea1917..560c87e 100644
--- a/Resources/Info.plist
+++ b/Resources/Info.plist
@@ -13,9 +13,9 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 0.3.1
+ 0.3.2
CFBundleVersion
- 11
+ 12
LSApplicationCategoryType
public.app-category.developer-tools
LSMinimumSystemVersion
diff --git a/Sources/CodexLimits/GrokBillingClient.swift b/Sources/CodexLimits/GrokBillingClient.swift
index ff3db2a..a94590d 100644
--- a/Sources/CodexLimits/GrokBillingClient.swift
+++ b/Sources/CodexLimits/GrokBillingClient.swift
@@ -103,9 +103,10 @@ struct GrokAllowanceSnapshot: Codable, Equatable, Sendable {
let source: String
if config.keys.contains("creditUsagePercent")
|| config.keys.contains("currentPeriod") {
- // A period-only reply still carries current facts; absence is not a zero reading.
- used = number(config["creditUsagePercent"])
- guard !config.keys.contains("creditUsagePercent") || used != nil else {
+ // GrokCreditsConfig uses an implicit-presence proto3 float: omitted means zero.
+ // Validate the current period below before accepting that default.
+ used = config.keys.contains("creditUsagePercent") ? number(config["creditUsagePercent"]) : 0
+ guard used != nil else {
throw GrokBillingError.missingAllowance
}
guard let current = config["currentPeriod"] as? [String: Any] else {
diff --git a/Sources/CodexLimits/GrokIntegration.swift b/Sources/CodexLimits/GrokIntegration.swift
index 8739604..f15b8b8 100644
--- a/Sources/CodexLimits/GrokIntegration.swift
+++ b/Sources/CodexLimits/GrokIntegration.swift
@@ -295,7 +295,7 @@ final class GrokIntegrationStore: ObservableObject {
guard self.isCurrent(expected) else { return }
self.snapshot = cached
self.hasStoredData = cached != nil
- if let cached {
+ if let cached, cached.remainingPercent != nil {
self.nextRefreshAt = min(cached.observedAt.addingTimeInterval(600), cached.resetsAt)
}
} catch {
diff --git a/Tests/CodexLimitsTests/GrokBillingClientTests.swift b/Tests/CodexLimitsTests/GrokBillingClientTests.swift
index b72c0b5..23ddbc0 100644
--- a/Tests/CodexLimitsTests/GrokBillingClientTests.swift
+++ b/Tests/CodexLimitsTests/GrokBillingClientTests.swift
@@ -6,18 +6,20 @@ final class GrokBillingClientTests: XCTestCase {
private let observedAt = Date(timeIntervalSince1970: 1_700_000_000)
private let current = #"{"config":{"creditUsagePercent":25.5,"currentPeriod":{"type":"USAGE_PERIOD_TYPE_WEEKLY","start":"2030-01-01T00:00:00+00:00","end":"2030-01-08T00:00:00.123456+00:00"},"prepaidBalance":{},"onDemandUsed":{"val":125},"onDemandCap":{"val":2500},"isUnifiedBillingUser":true},"subscription_tier":"Super\u0007Grok","private":"do-not-retain"}"#
- func testPeriodWithoutPercentageRetainsFactsWithoutInventingUsage() throws {
- let partial = try decode(current.replacingOccurrences(of: #""creditUsagePercent":25.5,"#, with: ""))
- XCTAssertNil(partial.reportedUsedPercent)
- XCTAssertNil(partial.remainingPercent)
- XCTAssertNil(partial.historyObservation)
- XCTAssertTrue(partial.isValid)
- XCTAssertEqual(partial.period, .weekly)
- XCTAssertEqual(partial.resetsAt.timeIntervalSince1970, 1_894_060_800.123456, accuracy: 0.001)
- XCTAssertEqual(partial.subscriptionTier, "SuperGrok")
- XCTAssertEqual(partial.prepaidBalanceUSD, 0)
- XCTAssertEqual(try JSONDecoder().decode(GrokAllowanceSnapshot.self, from: JSONEncoder().encode(partial)), partial)
- XCTAssertEqual(try decode(current.replacingOccurrences(of: "25.5", with: "0")).remainingPercent, 100)
+ func testOmittedCurrentPercentageUsesProtoZeroDefault() throws {
+ let omitted = current.replacingOccurrences(of: #""creditUsagePercent":25.5,"#, with: "")
+ for reply in [omitted, omitted.replacingOccurrences(of: "TYPE_WEEKLY", with: "TYPE_MONTHLY")] {
+ let zero = try decode(reply)
+ XCTAssertEqual(zero.reportedUsedPercent, 0)
+ XCTAssertEqual(zero.remainingPercent, 100)
+ XCTAssertEqual(zero.historyObservation?.remainingPercent, 100)
+ XCTAssertTrue(zero.isValid)
+ XCTAssertEqual(zero.resetsAt.timeIntervalSince1970, 1_894_060_800.123456, accuracy: 0.001)
+ XCTAssertEqual(zero.subscriptionTier, "SuperGrok")
+ XCTAssertEqual(zero.prepaidBalanceUSD, 0)
+ XCTAssertEqual(try JSONDecoder().decode(GrokAllowanceSnapshot.self, from: JSONEncoder().encode(zero)), zero)
+ XCTAssertEqual(try decode(reply.replacingOccurrences(of: #""config":{"#, with: #""config":{"creditUsagePercent":0,"#)), zero)
+ }
}
func testCurrentAndLegacyAllowancesKeepOnlyValidatedFacts() throws {
diff --git a/Tests/CodexLimitsTests/GrokIntegrationTests.swift b/Tests/CodexLimitsTests/GrokIntegrationTests.swift
index d8d7346..b97a7df 100644
--- a/Tests/CodexLimitsTests/GrokIntegrationTests.swift
+++ b/Tests/CodexLimitsTests/GrokIntegrationTests.swift
@@ -4,7 +4,7 @@ import ClaudeIntegrationCore
@MainActor
final class GrokIntegrationTests: XCTestCase {
- func testPeriodOnlyRefreshAfterResetSurvivesRelaunchAndRecoversWithoutInventedHistory() async throws {
+ func testOmittedZeroAfterResetSurvivesRelaunchAndTracksSubsequentUsage() async throws {
let clock = GrokTestClock()
let first = fixture(at: clock.now())
let source = GrokFetchProbe(snapshot: first)
@@ -20,42 +20,60 @@ final class GrokIntegrationTests: XCTestCase {
"start":"\(formatter.string(from: clock.now()))","end":"\(formatter.string(from: reset))"},\
"prepaidBalance":{}},"subscription_tier":"Example plan"}
"""
- let partial = try GrokAllowanceSnapshot.decode(Data(reply.utf8), observedAt: clock.now(), sourceVersion: "1.2.3")
- await source.setSnapshot(partial)
+ let zero = try GrokAllowanceSnapshot.decode(Data(reply.utf8), observedAt: clock.now(), sourceVersion: "1.2.3")
+ await source.setSnapshot(zero)
await store.refresh()
XCTAssertNil(store.error)
XCTAssertEqual(store.currentSnapshot?.resetsAt, reset)
XCTAssertEqual(store.snapshot?.observedAt, clock.now())
XCTAssertEqual(store.snapshot?.subscriptionTier, "Example plan")
- XCTAssertEqual(store.statusText, "Usage percentage unavailable")
- XCTAssertEqual(store.menuBarText, "—")
- XCTAssertEqual(store.history, originalHistory)
+ XCTAssertEqual(store.menuBarText, "100%")
+ let zeroHistory = originalHistory + [try XCTUnwrap(zero.historyObservation)]
+ XCTAssertEqual(store.history, zeroHistory)
+ let chart = try XCTUnwrap(IntegrationAllowanceChart(
+ metric: zero.historyMetric, observations: store.history, current: zero.historyObservation,
+ now: clock.now(), isStale: store.isStale, safetyBuffer: 3
+ ))
+ XCTAssertTrue(chart.chart.currentProjection.isEmpty, "An estimate cannot cross the reset")
await store.setVisible(true, includeHistory: false)
- XCTAssertNil(store.overview, "An old period must not remain the current thumbnail")
+ XCTAssertEqual(store.overview?.latest?.remaining, 100)
await store.setEnabled(false)
let restored = makeStore(clock: clock, source: source, cache: cache)
await restored.setVisible(true)
- XCTAssertEqual(restored.snapshot, partial)
- XCTAssertEqual(restored.history, originalHistory)
- XCTAssertNil(restored.currentSnapshot?.remainingPercent)
+ XCTAssertEqual(restored.snapshot, zero)
+ XCTAssertEqual(restored.history, zeroHistory)
+ XCTAssertEqual(restored.currentSnapshot?.remainingPercent, 100)
clock.advance(600)
- let zeroReply = reply.replacingOccurrences(of: #""config":{"#, with: #""config":{"creditUsagePercent":0,"#)
- let zero = try GrokAllowanceSnapshot.decode(Data(zeroReply.utf8), observedAt: clock.now(), sourceVersion: "1.2.3")
- await source.setSnapshot(zero)
+ let usedReply = reply.replacingOccurrences(of: #""config":{"#, with: #""config":{"creditUsagePercent":1,"#)
+ let used = try GrokAllowanceSnapshot.decode(Data(usedReply.utf8), observedAt: clock.now(), sourceVersion: "1.2.3")
+ await source.setSnapshot(used)
await restored.refresh()
XCTAssertNil(restored.error)
- XCTAssertEqual(restored.menuBarText, "100%")
- XCTAssertEqual(restored.history, originalHistory + [try XCTUnwrap(zero.historyObservation)])
- let chart = try XCTUnwrap(IntegrationAllowanceChart(
- metric: zero.historyMetric, observations: restored.history, current: zero.historyObservation,
- now: clock.now(), isStale: restored.isStale, safetyBuffer: 3
- ))
- XCTAssertTrue(chart.chart.currentProjection.isEmpty, "An estimate cannot cross the reset or missing reading")
+ XCTAssertEqual(restored.menuBarText, "99%")
+ XCTAssertEqual(restored.history, zeroHistory + [try XCTUnwrap(used.historyObservation)])
await restored.deleteData()
}
+ func testFreshCacheWithoutPercentageFetchesBeforeShowingCurrentUsage() async throws {
+ let clock = GrokTestClock()
+ let current = fixture(at: clock.now())
+ let source = GrokFetchProbe(snapshot: current)
+ let cache = temporaryDirectory().appendingPathComponent("snapshot.json")
+ var saved = try XCTUnwrap(JSONSerialization.jsonObject(with: JSONEncoder().encode(current)) as? [String: Any])
+ saved.removeValue(forKey: "reportedUsedPercent")
+ try FileManager.default.createDirectory(at: cache.deletingLastPathComponent(), withIntermediateDirectories: true)
+ try JSONSerialization.data(withJSONObject: saved).write(to: cache)
+ let store = makeStore(clock: clock, source: source, cache: cache)
+ await store.setVisible(true)
+ let calls = await source.calls
+ XCTAssertEqual(calls, 1, "A partial 0.3.1 cache must not delay a current measurement")
+ XCTAssertEqual(store.menuBarText, "75%")
+ XCTAssertEqual(store.history, [try XCTUnwrap(current.historyObservation)])
+ await store.deleteData()
+ }
+
func testOverviewReadsCurrentPeriodWithoutRetainingDetailAndFallsBackToLatest() async throws {
let clock = GrokTestClock()
let current = fixture(at: clock.now())
diff --git a/docs/MEASUREMENT-CONTRACT.md b/docs/MEASUREMENT-CONTRACT.md
index abd944c..41e4010 100644
--- a/docs/MEASUREMENT-CONTRACT.md
+++ b/docs/MEASUREMENT-CONTRACT.md
@@ -33,7 +33,7 @@ An Integration may publish Account Allowance only when the source identifies the
- A percentage must be finite and source-valid. Missing, null, malformed, or semantically unknown values are unavailable, never zero.
- Claude Code `used_percentage` is valid only inside `0...100`; convert it to `remaining = 100 - used`. Reject an out-of-range value.
- Grok `creditUsagePercent` is a used percentage. Preserve the finite original as `reportedUsedPercent`, apply the official `0...100` clamp, and convert it to remaining. Record `measurementSource` as `creditUsagePercent` or `legacyCredits`, with the CLI version when available. Use legacy `100 * used.val / monthlyLimit.val` only when both current fields are absent; a zero or missing legacy limit is unavailable. A present empty Cent object means zero, while a missing monetary field remains unavailable. The collector sends `_x.ai/billing` on the ACP wire; the internal handler name omits the underscore.
-- A valid Grok `currentPeriod` without `creditUsagePercent` is a successful partial observation: retain its period, reset, observation time, and available account facts, but leave the percentage unavailable. It replaces the latest snapshot without adding an allowance-history point or forecast. Never substitute zero, a previous period’s percentage, or legacy credits for the omitted value. A present null or malformed percentage remains an invalid response. Historic charts identify their range as the last recorded window when no current allowance observation exists; `Last received` dates the latest accepted response rather than the latest refresh attempt.
+- In the current Grok credits schema, `credit_usage_percent` is an implicit-presence proto3 float: an omitted `creditUsagePercent` with a validated `currentPeriod` means 0% used / 100% remaining. Preserve reset, observation time and available account facts, and record the numeric observation normally. This does not authorize zero for absent configuration, unknown periods, invalid dates or unusable legacy limits. A present null or malformed percentage remains invalid at the ACP boundary. Old 0.3.1 caches without a percentage are refreshed on demand, not rewritten into historical zeros. See [provider schema evidence](research/grok-zero-usage-2026-09-16.md). Historic charts identify their range as the last recorded window when no current allowance observation exists; `Last received` dates the latest accepted response rather than the latest refresh attempt.
- A direct remaining percentage is validated without changing its orientation.
- Every allowance window carries its provider window ID or period type, observed time, reset time, and duration when known.
- Weekly and monthly Grok periods remain distinct. Retain a supplied `currentPeriod.start` or legacy `billingPeriodStart` only after supported finite-date and start-before-reset validation. A missing start remains absent; never invent a monthly start from a fixed duration. An unknown period type is unavailable rather than relabeled as weekly.
diff --git a/docs/research/grok-zero-usage-2026-09-16.md b/docs/research/grok-zero-usage-2026-09-16.md
new file mode 100644
index 0000000..f756798
--- /dev/null
+++ b/docs/research/grok-zero-usage-2026-09-16.md
@@ -0,0 +1,31 @@
+# Grok zero usage after a period reset
+
+The 0.3.1 fix accepted a current period with an omitted `creditUsagePercent` but treated the percentage as unavailable. That interpretation was wrong for the current Grok credits schema.
+
+## Provider evidence
+
+The public grok.com application served [this JavaScript asset](https://cdn.grok.com/_next/static/chunks/3ezlkvn7-td91.js) on 2026-09-16. Its generated `fileDesc` contains `prod/grok/backend/proto/grok_build_billing.proto`:
+
+- Syntax: `proto3`.
+- Message: `grok_api_v2.GrokCreditsConfig`.
+- `credit_usage_percent`: field 1, `TYPE_FLOAT`, singular, no `proto3_optional` flag and no oneof.
+- `current_period`: field 8, `grok_api_v2.UsagePeriod`.
+- `GetGrokCreditsConfigResponse.config` refers to that message.
+
+The asset SHA-256 is `39f707417258763b6c3cb49bb31a61131a2705f6d4f16ce5be8fb1b4009652bc`; the decoded 9,266-byte descriptor SHA-256 is `37b0510d706ae2b9a8e582a11cfa2aa614110727c6e16fade942d2bd3c8e28f7`.
+
+This is an implicit-presence numeric scalar. Its absent wire value is zero; [ProtoJSON omits default values for fields without presence](https://protobuf.dev/programming-guides/json/#presence-and-default-values). It is not an optional measurement whose absence means unknown.
+
+The [official Grok CLI billing handler](https://github.com/xai-org/grok-build/blob/482711333c7195dc16a272777f86086d615e2afb/crates/codegen/xai-grok-shell/src/extensions/billing.rs) requests this credits configuration, decodes the percentage into a Rust `Option`, and preserves omission in ACP. Its [usage display](https://github.com/xai-org/grok-build/blob/482711333c7195dc16a272777f86086d615e2afb/crates/codegen/xai-grok-pager/src/app/effects/helpers.rs#L1461-L1471) falls back to zero. The schema evidence establishes why that zero is appropriate for a valid current-period response; the UI fallback alone was insufficient evidence.
+
+## Decoder boundary
+
+For a validated current weekly/monthly period, an omitted percentage decodes to 0% used / 100% remaining. Explicit values keep their existing finite-number validation and display clamp. Explicit null, boolean or malformed values remain rejected at the ACP boundary. Missing configuration, unknown period types, invalid dates and unusable legacy limits do not become zero usage.
+
+Old 0.3.1 caches lacking a percentage remain readable, but are refreshed on demand instead of being considered a fresh numeric observation. Do not retrofit a zero into historical cache data: record the new successful response at its actual observation time.
+
+## Reproduction
+
+The installed Grok Build 1.0.30 returned a current weekly period without `creditUsagePercent`, `used` or `monthlyLimit`. A harness compiled from the production 0.3.1 collector reproduced `FAIL: current Grok usage percentage is unavailable`. The read used the existing ACP path, made no model request and retained no raw account response.
+
+The same live check compiled with the corrected decoder returned 100% remaining and passed the snapshot cache round trip. The focused decoder regression first failed on the released implementation, then passed with the schema default applied.
From 4f85569fdb7db5b80f57e308f28ae21aabd5d30b Mon Sep 17 00:00:00 2001
From: thrr87 <193831865+thrr87@users.noreply.github.com>
Date: Wed, 16 Sep 2026 17:52:10 +0200
Subject: [PATCH 2/2] docs: record approved 0.3.2 release exceptions
---
docs/adr/0015-ship-claude-as-experimental.md | 4 +++-
docs/prd/multi-integration-workspace.md | 8 ++++----
docs/releasing.md | 2 +-
3 files changed, 8 insertions(+), 6 deletions(-)
diff --git a/docs/adr/0015-ship-claude-as-experimental.md b/docs/adr/0015-ship-claude-as-experimental.md
index d56ed3a..7c23c11 100644
--- a/docs/adr/0015-ship-claude-as-experimental.md
+++ b/docs/adr/0015-ship-claude-as-experimental.md
@@ -14,7 +14,9 @@ The eligible-account check is recorded as `Waived`, not `Passed`, and no longer
The product owner also explicitly waived the expanded all-enabled idle comparison and eight-hour mixed lifecycle soak for release 0.3.0 on 2026-09-10. Neither test was performed. The validator accepts only an exception for the exact release version. CI, deterministic lifecycle tests, native QA, universal packaging, and update-signature validation remain required.
-On 2026-09-16, the product owner explicitly renewed both performance exceptions for release 0.3.1. The current PRD rows are `Waived for 0.3.1`; neither check was run or passed, and this renewal does not cover future versions. The owner also authorized an administrative merge of PR #74 without a second/code-owner approval after its tests and CodeQL pass.
+On 2026-09-16, the product owner explicitly renewed both performance exceptions for release 0.3.1. The PRD rows were recorded as `Waived for 0.3.1`; neither check was run or passed, and this renewal does not cover future versions. The owner also authorized an administrative merge of PR #74 without a second/code-owner approval after its tests and CodeQL pass.
+
+On 2026-09-16, after reviewing the corrected zero-usage behavior and PR #75, the product owner explicitly renewed both performance exceptions for 0.3.2 and authorized its publication after green tests and CodeQL. The owner also authorized an administrative merge of PR #75 without a second/code-owner approval. The current PRD rows are `Waived for 0.3.2`; neither performance check was run or passed, and this approval does not cover later versions.
## Consequence
diff --git a/docs/prd/multi-integration-workspace.md b/docs/prd/multi-integration-workspace.md
index c92c4b3..c5a7211 100644
--- a/docs/prd/multi-integration-workspace.md
+++ b/docs/prd/multi-integration-workspace.md
@@ -2,7 +2,7 @@
Status: Accepted for v1 implementation
-Release 0.3.1 is accepted with the explicit owner exceptions recorded in [ADR-0015](../adr/0015-ship-claude-as-experimental.md). Unperformed checks remain marked as waived rather than passed.
+Release 0.3.2 is accepted with the explicit owner exceptions recorded in [ADR-0015](../adr/0015-ship-claude-as-experimental.md). Unperformed checks remain marked as waived rather than passed.
## Destination
@@ -329,16 +329,16 @@ Disabling Codex pauses its account timer, local collection, history exchange, an
## v1 release boundary and gates
-Claude Code is opt-in `Experimental` and Grok is opt-in `Beta`; release 0.3.1 is accepted with the recorded owner exceptions below. OpenCode remains deferred. The maturity label appears in Settings and the Integration detail header, not beside every value. Grok also displays CLI-version provenance because its custom ACP billing extension is not a versioned public billing API.
+Claude Code is opt-in `Experimental` and Grok is opt-in `Beta`; release 0.3.2 is accepted with the recorded owner exceptions below. OpenCode remains deferred. The maturity label appears in Settings and the Integration detail header, not beside every value. Grok also displays CLI-version provenance because its custom ACP billing extension is not a versioned public billing API.
| Release gate | Current evidence | Status |
|---|---|---|
| Grok and OpenCode source decision | Grok 1.0.25 accepts correctly prefixed ACP billing with CLI-owned authentication; OpenCode remains excluded by RSS/write budgets | Passed |
| Bounded Codex history and serialized demand-driven collection | 3,650-day fixture, 32-candidate reconciliation, idle process release, deterministic lifecycle tests | Passed |
| Claude relay, setup, privacy, deletion, executable selection, and boundary behavior | Packaged helper checks and deterministic Release tests | Passed |
-| All-enabled idle comparison | Expanded comparison not run; product owner renewed the 0.3.0 exception on 2026-09-16 specifically for release 0.3.1; ADR-0015 | Waived for 0.3.1 |
+| All-enabled idle comparison | Expanded comparison not run; product owner renewed the prior exceptions on 2026-09-16 specifically for release 0.3.2; ADR-0015 | Waived for 0.3.2 |
| Eligible Claude account observation | Product owner waived this check on 2026-09-10 for the experimental Claude Code release; ADR-0015 | Waived |
-| Eight-hour mixed lifecycle soak | Not run; product owner renewed the 0.3.0 exception on 2026-09-16 specifically for release 0.3.1; ADR-0015 | Waived for 0.3.1 |
+| Eight-hour mixed lifecycle soak | Not run; product owner renewed the prior exceptions on 2026-09-16 specifically for release 0.3.2; ADR-0015 | Waived for 0.3.2 |
`Scripts/validate-release.sh` requires this document to be exactly `Accepted for v1 implementation`. Each required performance row must be `Passed` or explicitly `Waived for VERSION` for the version being released. A version-specific exception does not satisfy later releases; deterministic tests and short comparisons do not count as evidence for an unperformed gate.
diff --git a/docs/releasing.md b/docs/releasing.md
index 9853dbc..f88163a 100644
--- a/docs/releasing.md
+++ b/docs/releasing.md
@@ -14,7 +14,7 @@ Losing the EdDSA private key prevents ad-hoc-signed installations from trusting
## Release flow
1. Ask Codex to prepare a release and provide the stable version number.
-2. Codex confirms that the multi-integration PRD is `Accepted for v1 implementation` and that `All-enabled idle comparison` and `Eight-hour mixed lifecycle soak` are recorded as `Passed` or explicitly `Waived for VERSION` for that release, runs the Release tests and QA, then updates `CFBundleShortVersionString` and increments `CFBundleVersion`. The eligible Claude account observation is waived for the experimental release; [ADR-0015](adr/0015-ship-claude-as-experimental.md) records separate owner approvals of both performance exceptions for 0.3.0 and 0.3.1. No unperformed check is recorded as passed. The validator checks the status and both required rows; it does not block development while they remain pending. The historical Codex-plus-Claude idle comparison does not cover the restored Grok scope or the new provider-local history readers. QA includes history persistence, old-cache seeding, reset and forecast boundaries, and separate Claude/Grok data deletion.
+2. Codex confirms that the multi-integration PRD is `Accepted for v1 implementation` and that `All-enabled idle comparison` and `Eight-hour mixed lifecycle soak` are recorded as `Passed` or explicitly `Waived for VERSION` for that release, runs the Release tests and QA, then updates `CFBundleShortVersionString` and increments `CFBundleVersion`. The eligible Claude account observation is waived for the experimental release; [ADR-0015](adr/0015-ship-claude-as-experimental.md) records separate owner approvals of both performance exceptions for 0.3.0, 0.3.1 and 0.3.2. No unperformed check is recorded as passed. The validator checks the status and both required rows; it does not block development while they remain pending. The historical Codex-plus-Claude idle comparison does not cover the restored Grok scope or the new provider-local history readers. QA includes history persistence, old-cache seeding, reset and forecast boundaries, and separate Claude/Grok data deletion.
3. Run `Scripts/validate-release.sh VERSION` and the `Release` workflow with `dry_run` enabled.
4. Inspect the universal app archive, both universal app/helper executables, signed `appcast.xml`, generated notes, and workflow result.
5. Run the workflow with `dry_run` disabled. It creates a Draft Release only.