Skip to content

feat(pr-03): extend LicenseCache to match filaops-pro reader contract#600

Merged
Blb3D merged 3 commits into
mainfrom
feature/pr03-license-cache-schema
May 11, 2026
Merged

feat(pr-03): extend LicenseCache to match filaops-pro reader contract#600
Blb3D merged 3 commits into
mainfrom
feature/pr03-license-cache-schema

Conversation

@Blb3D
Copy link
Copy Markdown
Owner

@Blb3D Blb3D commented May 11, 2026

Summary

Closes the cross-repo schema drift documented in Aeonyx memory #148.

filaops-pro PR-03b (2026-05-02) extended LicenseCache with four required fields (status, last_verified_at, last_server_timestamp, grace_until) on the assumption Core's matching PR-03 would land in parallel. Core's PR-03 was never opened, leaving the on-disk license.json contract broken on main for 9 days. The break surfaced when a license-server image rebuild (Quoter SPA tarball work) re-baked filaops-pro main into the served wheel; a subsequent --force-recreate on the dogfood VM wiped license.json, re-activation wrote Core's 6-field PR-02 shape, PRO's LicenseCache(**filtered) raised TypeError, load_cache returned None, license_gate returned 403 no_license on every /pro/* route, and /admin/access-requests + /admin/catalogs broke (with a secondary toast-storm amplifier).

This PR extends Core to write the full 10-field shape PRO expects. The four new fields come from data already in the license-server's signed /validate response (server returns status + server_timestamp on every call per PR-03a) plus a locally computed grace_until (activation_moment + GRACE_DAYS, matching PRO's compute_grace_until).

Aeonyx-grounded reconciliation

What changed

app/core/license_cache.py

  • Add 4 PR-03 fields to LicenseCache dataclass with safe defaults:
    • status: str = \"active\"
    • last_verified_at: str = \"\" (derived from activated_at in from_dict if missing)
    • last_server_timestamp: str = EPOCH_ZERO_ISO (sentinel guaranteed older than any real heartbeat — protects the first heartbeat from being rejected as 'rollback' under clock skew)
    • grace_until: str = \"\" (derived from activated_at + GRACE_DAYS in from_dict if missing)
  • Add GRACE_DAYS = 14 and EPOCH_ZERO_ISO module constants
  • Add is_pr02_shape(raw) helper for the auto-upgrade detection
  • from_dict now derives sensible defaults for legacy 6-field caches

app/api/v1/endpoints/system_license.py

  • /system/license/activate reads status + server_timestamp from the validate response, computes grace_until = now + 14d, writes the full 10-field cache
  • /system/license/info calls _upgrade_pr02_file_if_present after load — if the on-disk JSON is missing any PR-03 key, re-save (cache is already PR-03-shaped in memory via from_dict defaults). One-shot, idempotent.
  • LicenseInfoResponse wire schema unchanged

tests/endpoints/test_system_license.py

  • Extend _ok_validate_response to include server_timestamp, nonce, signature (matching the real license-server response shape)
  • test_activate_persists_full_pr03_shape — verifies all four fields land
  • test_activate_falls_back_to_epoch_zero_when_server_omits_timestamp
  • test_activate_carries_server_status_through (e.g. grace_period)
  • test_load_pr02_file_derives_pr03_defaults_from_activated_at
  • test_is_pr02_shape_detects_missing_pr03_fields
  • test_info_auto_upgrades_pr02_file_to_pr03_shape
  • test_info_does_not_resave_pr03_file (idempotency, via mtime)

Test plan

  • pytest tests/endpoints/test_system_license.py39 passed (34 original + 5 new), 0 failed
  • pytest tests/core/test_crypto.py tests/endpoints/test_pro_install.py46 passed (the other Core test files that import from license_cache)
  • ruff check clean on all three changed files
  • Post-merge dogfood: rebuild the BLB3D customer stack with the new Core; the existing in-place PR-02 license.json (now persisted by ecosystem PR Bulk update item type 'Filament' option silently fails #62) should auto-upgrade on first /info call, after which /admin/access-requests and /admin/catalogs should load cleanly
  • CodeRabbit + Copilot reviews

Out of scope (cortex_observe #71 captures these as latent follow-ups)

These three latent issues live on the PRO reader side and need a filaops-pro PR, not a Core PR (Sacred Rule):

  1. filaops_pro.licensing.cache.LicenseCache.expires_at: str has no default — perpetual licenses (Core writes expires_at = None) would crash construction. Today's BLB3D license has an expiry so this is dormant, but it'll surface for any perpetual customer.
  2. PRO's tier: Literal[\"community\",\"pro\",\"enterprise\"] doesn't include \"professional\", which is what license-server emits. Python doesn't enforce Literal at runtime so no crash, but the value semantics are off.
  3. PRO's status: Literal[\"active\",\"grace\",\"expired\"] doesn't include \"grace_period\" or \"cancelled\", both of which the server can return.

Also out of scope: the AdminAccessRequests useEffect toast-storm. Filed as a separate Core PR on fix/toast-storm-usememo (one-line useMemo wrap on the toast factory).

🤖 Generated with Claude Code
Agent-Session: a06c11ad-3e32-40f1-9264-a288b7393ea3

Summary by CodeRabbit

  • New Features

    • License system now includes a 14-day grace period post-activation for improved flexibility.
    • Automatic upgrading of existing license configurations to the latest format, ensuring seamless compatibility.
  • Improvements

    • Enhanced license status tracking with better timestamp management.
    • Improved error resilience—license operations continue functioning even when server data is incomplete.

Review Change Stack

filaops-pro PR-03b (commit 62ced54, 2026-05-02) added four required
fields to LicenseCache — status, last_verified_at,
last_server_timestamp, grace_until — on the assumption Core's matching
PR-03 would land in parallel. Core's PR-03 was never opened, so the
contract has been broken on main for 9 days. The break surfaced when a
license-server image rebuild (filaops-ecosystem PR #61, Quoter SPA
tarball work) re-baked filaops-pro main into the served wheel for the
first time since PR-03b. Subsequent force-recreate on the dogfood VM
wiped license.json, re-activation wrote Core's 6-field PR-02 shape,
PRO's LicenseCache(**filtered) raised TypeError, load_cache returned
None, license_gate returned 403 no_license on every /pro/* route, and
admin pages /admin/access-requests + /admin/catalogs broke with a
secondary toast-storm amplifier.

This PR extends Core to write the full 10-field shape PRO expects.
The four new fields come from data already in the license-server's
signed /validate response (server returns status + server_timestamp on
every call per PR-03a) plus a locally computed grace_until
(activation_moment + GRACE_DAYS, matching filaops_pro's
compute_grace_until). GRACE_DAYS = 14 is duplicated as a module
constant in app.core.license_cache so Core does not import
filaops_pro (Sacred Rule); the comment notes the contract must stay
in sync with filaops_pro.licensing.cache.GRACE_DAYS.

Pre-existing PR-02 cache files on customer instances auto-upgrade on
the first /info call after deploy: the endpoint detects missing PR-03
keys via is_pr02_shape() and re-saves with the full shape, deriving
sensible defaults from activated_at. No manual deactivate+re-activate
required for in-place upgrades.

Tests: 39 passed (34 original + 5 new). Covers the upgrade-on-read
path, the activation cache shape, server_timestamp fallback to epoch
zero, and is_pr02_shape detection. Also verified 46/46 pass in the
two other Core test files that import from license_cache (crypto +
pro_install).

Closes the schema drift documented in Aeonyx memory #148.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 11, 2026 13:59
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 11, 2026

Review Council Results

0 tests   0 ✅  0s ⏱️
0 suites  0 💤
0 files    0 ❌

Results for commit 774524c.

♻️ This comment has been updated with latest results.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 11, 2026

Warning

Rate limit exceeded

@Blb3D has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 43 minutes and 46 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cc17640f-f5f2-4de7-8e37-971c8aaf86fd

📥 Commits

Reviewing files that changed from the base of the PR and between 4288eef and 774524c.

📒 Files selected for processing (3)
  • backend/app/api/v1/endpoints/system_license.py
  • backend/app/core/license_cache.py
  • backend/tests/endpoints/test_system_license.py

Walkthrough

Core adds PR-03 license cache schema with four heartbeat fields, implements idempotent on-read upgrade from PR-02 shape, enriches activation persistence, and provides comprehensive tests validating backward-compatible loading, shape detection, grace-window computation, and endpoint behaviors across upgrade and activation flows.

Changes

PR-03 License Cache Migration and Enriched Persistence

Layer / File(s) Summary
PR-03 Data Schema and Constants
backend/app/core/license_cache.py
Introduces GRACE_DAYS (14) and EPOCH_ZERO_ISO sentinel constant; adds PR-03 LicenseCache dataclass fields (status, last_verified_at, last_server_timestamp, grace_until) with sensible defaults.
License Cache Upgrade Helpers
backend/app/core/license_cache.py
Expands LicenseCache.from_dict to derive missing PR-03 fields from activated_at when loading older files; adds _compute_grace_until(activated_at_iso) to calculate expiration plus grace window; adds is_pr02_shape(raw) detector for one-shot upgrade logic.
System License Endpoint: /info Handler and Upgrade
backend/app/api/v1/endpoints/system_license.py
Imports PR-03 helpers; extends get_license_info to call _upgrade_pr02_file_if_present for on-disk cache files; implements upgrade helper that reads raw JSON, detects PR-02 shape, re-saves as PR-03, and logs/swallows errors to keep endpoint resilient.
System License Endpoint: Activation with PR-03 Persistence
backend/app/api/v1/endpoints/system_license.py
Updates activate_license to persist full PR-03 LicenseCache shape: sets status, last_verified_at, last_server_timestamp (with EPOCH_ZERO_ISO fallback), and computed grace_until based on UTC activation time.
Test Module Setup and Helpers
backend/tests/endpoints/test_system_license.py
Updates test docs and imports to include datetime and is_pr02_shape; modifies _ok_validate_response mock helper to include deterministic server_timestamp field in /validate response.
License Cache Unit Tests for PR-03
backend/tests/endpoints/test_system_license.py
Extends forward-compat test data with additional PR-03 fields; adds test validating PR-02-shaped cache loads with derived defaults; adds test for is_pr02_shape detection across missing field combinations.
Integration Tests: /system/license/info Upgrade and Idempotency
backend/tests/endpoints/test_system_license.py
Adds tests validating first /info call upgrades PR-02 file to PR-03 shape with correct field derivation; confirms idempotency via unchanged mtime on subsequent calls.
Integration Tests: /system/license/activate with PR-03 Persistence
backend/tests/endpoints/test_system_license.py
Adds tests asserting all PR-03 fields are populated and grace_until computation is correct; validates fallback to EPOCH_ZERO_ISO when server omits timestamp; confirms persisted status reflects server-reported value (e.g., grace_period).

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant GetInfo
  participant Upgrade
  participant Disk
  participant Cache
  
  Client->>GetInfo: GET /system/license/info
  GetInfo->>Disk: Check if cache file exists
  alt Cache file exists
    GetInfo->>Upgrade: Perform upgrade check
    Upgrade->>Disk: Read raw JSON
    Upgrade->>Upgrade: is_pr02_shape check
    alt PR-02 detected
      Upgrade->>Cache: from_dict with derivation
      Cache-->>Upgrade: Upgraded LicenseCache
      Upgrade->>Disk: save_license_cache
    end
  end
  GetInfo->>Client: Return LicenseInfoResponse
Loading
sequenceDiagram
  participant Client
  participant ActivateEndpoint
  participant LicenseServer
  participant Disk
  participant Cache
  
  Client->>ActivateEndpoint: POST with license key
  ActivateEndpoint->>LicenseServer: Validate license
  LicenseServer-->>ActivateEndpoint: Response with tier/features/status
  ActivateEndpoint->>Cache: Build PR-03 shape
  Note over Cache: grace_until = activated_at + GRACE_DAYS<br/>last_server_timestamp = server_timestamp or EPOCH_ZERO_ISO
  ActivateEndpoint->>Disk: save_license_cache
  ActivateEndpoint->>Client: Return LicenseInfoResponse
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

  • Blb3D/filaops#579: Establishes base license cache and system_license endpoint; PR #600 extends with PR-03 upgrade and enriched persistence logic.

Poem

🏷️ From PR-02 dust, a new shape rises,
Four heartbeat fields, no more surprises!
On-disk upgrade, a one-shot affair—
Grace windows computed with love and care. 📅✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main feature—extending LicenseCache to match the filaops-pro contract—and uses the PR-03 version marker as context.
Description check ✅ Passed The description provides comprehensive context: Aeonyx-grounded reconciliation, detailed change summaries by file, test results, and acknowledgment of out-of-scope items.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/pr03-license-cache-schema

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR updates Core’s on-disk license.json contract to match the extended LicenseCache shape expected by filaops-pro, preventing PRO from failing to read a freshly written/legacy PR-02 cache and incorrectly gating all /pro/* routes.

Changes:

  • Extend LicenseCache to include PR-03-required fields (status, last_verified_at, last_server_timestamp, grace_until) with legacy-safe defaults and PR-02 shape detection.
  • Update /system/license/activate to persist the full PR-03 cache shape and /system/license/info to one-shot upgrade legacy PR-02 files on read.
  • Expand endpoint/unit tests to cover PR-03 field persistence, timestamp fallback behavior, PR-02→PR-03 upgrade, and idempotency.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
backend/app/core/license_cache.py Adds PR-03 cache fields, upgrade helpers, and default-derivation logic for legacy PR-02 files.
backend/app/api/v1/endpoints/system_license.py Persists the full PR-03 cache shape on activation and auto-upgrades PR-02 files during /info.
backend/tests/endpoints/test_system_license.py Adds coverage for PR-03 field persistence, fallback behavior, and PR-02→PR-03 upgrade/idempotency.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread backend/app/core/license_cache.py Outdated
# Malformed activated_at — fall back to "no grace window".
# PRO's evaluate_license will treat an unparseable grace_until as
# equivalent to expired; better than crashing with a TypeError.
anchor = datetime.now(timezone.utc)
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Good catch — fixed in 91e... (pushing now). _compute_grace_until now returns EPOCH_ZERO_ISO on ValueError. PRO's evaluate_license checks now < grace_until, so an epoch-zero grace_until deterministically denies with grace_period_expired — the safe, loud failure mode you wanted. The docstring is rewritten to match the actual behavior and explain why fail-closed is intentional (granting a fresh window on garbage input would mask corruption/tampering by silently extending access).

Added test_compute_grace_until_fails_closed_on_malformed_anchor covering empty string, non-timestamp garbage, and wrong format. 40/40 pass.

coderabbitai[bot]
coderabbitai Bot previously requested changes May 11, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
backend/app/api/v1/endpoints/system_license.py (1)

170-196: 💤 Low value

Double read of license.json on every /info call.

load_license_cache already parsed the file once; _upgrade_pr02_file_if_present reads it again to inspect the raw dict. Not a correctness problem, just a small bit of wasted I/O on the hot read path. If you want to keep from_dict opaque about what it filled in, an easy alternative is to have load_license_cache return (cache, raw) (or a was_pr02 flag) and pass that into the upgrade helper.

Happy to leave it as-is if the I/O cost is in the noise for this endpoint — the current implementation is at least correctly fenced behind OSError/ValueError.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/app/api/v1/endpoints/system_license.py` around lines 170 - 196,
_load_license_cache currently parses license.json then
_upgrade_pr02_file_if_present reads the file again causing redundant I/O; change
load_license_cache to return the parsed raw dict (or a was_pr02 flag) along with
the LicenseCache and update _upgrade_pr02_file_if_present signature to accept
that raw dict/flag so it can call is_pr02_shape on the already-parsed data
instead of re-reading the file; update every call site that invokes
_upgrade_pr02_file_if_present (and any tests) and keep save_license_cache and
the existing logging/exception handling unchanged.
backend/tests/endpoints/test_system_license.py (1)

398-423: 💤 Low value

Idempotency check relies on filesystem mtime resolution.

On Linux + ext4 + recent Python this is fine because st_mtime_ns gives nanosecond resolution and the two /info calls won't land in the same nanosecond. On filesystems with coarser timestamps (FAT, some macOS configs, certain WSL/Docker bind mounts) two writes inside the same coarse tick could compare equal even if the file was rewritten — which would mask a regression, not produce a false failure. Probably acceptable for CI on Linux, but if you want a stronger signal you could compare file contents (or a hash) instead of mtime, since the assertion you actually care about is "the bytes on disk did not change":

-    client.get(INFO_URL)  # may or may not touch
-    mtime_after_first = path.stat().st_mtime_ns
-    client.get(INFO_URL)
-    mtime_after_second = path.stat().st_mtime_ns
-
-    assert mtime_after_first == mtime_after_second
+    client.get(INFO_URL)
+    contents_after_first = path.read_bytes()
+    client.get(INFO_URL)
+    contents_after_second = path.read_bytes()
+
+    assert contents_after_first == contents_after_second
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/tests/endpoints/test_system_license.py` around lines 398 - 423, The
test test_info_does_not_resave_pr03_file relies on filesystem mtime
(path.stat().st_mtime_ns) which can be coarse on some filesystems; instead read
the file bytes (the LICENSE_CACHE_FILENAME at path created by
save_license_cache) and compare contents (or compute a hash) before and after
the second client.get(INFO_URL) to assert the file bytes did not change; keep
using the same path variable and INFO_URL and replace the mtime comparisons with
a bytes/hash equality check.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/app/core/license_cache.py`:
- Around line 104-118: The loader is setting last_verified_at = activated_at and
computing grace_until from that, which for upgraded PR-02 caches can yield a
past grace window; update the code path that builds the LicenseCache instance
(the constructor call returning cls(...) in license_cache.py) so that
last_verified_at is anchored to the current time when the stored
last_verified_at/activated_at would make grace_until already in the past:
compute last_verified_at = max(parsed_last_verified_or_activated, now_iso) (or
clamp grace_until = max(_compute_grace_until(activated_at), now_iso)) and then
compute grace_until via _compute_grace_until using the adjusted anchor; keep
symbol references: activated_at, last_verified_at, grace_until,
_compute_grace_until and ensure evaluate_license will then see a non-expired
grace window immediately after upgrade.

---

Nitpick comments:
In `@backend/app/api/v1/endpoints/system_license.py`:
- Around line 170-196: _load_license_cache currently parses license.json then
_upgrade_pr02_file_if_present reads the file again causing redundant I/O; change
load_license_cache to return the parsed raw dict (or a was_pr02 flag) along with
the LicenseCache and update _upgrade_pr02_file_if_present signature to accept
that raw dict/flag so it can call is_pr02_shape on the already-parsed data
instead of re-reading the file; update every call site that invokes
_upgrade_pr02_file_if_present (and any tests) and keep save_license_cache and
the existing logging/exception handling unchanged.

In `@backend/tests/endpoints/test_system_license.py`:
- Around line 398-423: The test test_info_does_not_resave_pr03_file relies on
filesystem mtime (path.stat().st_mtime_ns) which can be coarse on some
filesystems; instead read the file bytes (the LICENSE_CACHE_FILENAME at path
created by save_license_cache) and compare contents (or compute a hash) before
and after the second client.get(INFO_URL) to assert the file bytes did not
change; keep using the same path variable and INFO_URL and replace the mtime
comparisons with a bytes/hash equality check.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ce316fdf-7382-4748-b118-79521d3650a2

📥 Commits

Reviewing files that changed from the base of the PR and between d7326ac and 4288eef.

📒 Files selected for processing (3)
  • backend/app/api/v1/endpoints/system_license.py
  • backend/app/core/license_cache.py
  • backend/tests/endpoints/test_system_license.py

Comment thread backend/app/core/license_cache.py
Blb3D and others added 2 commits May 11, 2026 10:07
Two changes responding to PR #600 review:

1. (CodeRabbit) Anchor pre-PR-03 cache's last_verified_at to load time,
   not stale activated_at. The previous derivation could yield a
   grace_until already in the past for a Core that had been installed
   for weeks before upgrading, putting PRO immediately outside the
   grace window — exactly the failure mode the upgrade is meant to
   prevent. The next PRO heartbeat overwrites both fields within ~6h
   so the upgrade-time anchor only matters until then.

2. (Copilot) _compute_grace_until fails closed on a malformed anchor:
   returns EPOCH_ZERO_ISO so PRO's evaluate_license denies with
   grace_period_expired, rather than silently granting a fresh
   GRACE_DAYS window on garbage input. The old behavior masked
   potential corruption or tampering by extending access.

Tests: 40 passed (was 39 + 1 new fail-closed coverage test).
Existing test_load_pr02_file test renamed and updated to assert the
new upgrade-time-anchor semantic. test_info_auto_upgrades likewise
updated to verify the on-disk last_verified_at is newer than the
stale activated_at, not equal to it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…idempotency

Two CodeRabbit nitpicks from the round-1 fix review:

1. /info was reading license.json twice — once via load_license_cache,
   then again inside _upgrade_pr02_file_if_present to inspect the raw
   dict for is_pr02_shape. Factored a public load_license_cache_with_raw
   helper that returns (cache, raw_dict) atomically; load_license_cache
   stays as the thin Optional[LicenseCache] facade so existing callers
   (crypto.py, pro_install.py, tests) don't change. /info now calls the
   with_raw variant and reuses the parsed dict for shape detection.

2. test_info_does_not_resave_pr03_file relied on st_mtime_ns equality.
   On coarse-resolution filesystems (FAT, some Docker bind-mount setups)
   two writes inside the same tick could share an mtime and falsely
   compare equal — masking a regression. Switched to byte equality
   (path.read_bytes()) which is robust to mtime resolution.

Also removed the now-unused json import in system_license.py.

Tests: 86 passed (40 system_license + 24 pro_install + 22 crypto). Ruff
clean. No public API change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Blb3D
Copy link
Copy Markdown
Owner Author

Blb3D commented May 11, 2026

Round-2 review fixes pushed in 774524c.

Addressed both CodeRabbit nitpicks from the round-1 review:

1. Double-read of license.json in /info (system_license.py:170-196 nitpick)

Factored a public load_license_cache_with_raw() helper in app/core/license_cache.py that returns (cache, raw_dict) from a single file read. load_license_cache() is now a thin facade that just unpacks and returns the cache — existing call sites in crypto.py, pro_install.py, and the test suite are unchanged (public API preserved).

/info now calls the _with_raw variant and passes the already-parsed dict into the upgrade check, eliminating the redundant json.loads on the hot read path. The unused json import in system_license.py is gone too.

2. mtime-based idempotency assertion (test_system_license.py:398-423 nitpick)

Switched test_info_does_not_resave_pr03_file to byte equality (path.read_bytes()). The assertion we actually care about is "the bytes on disk did not change", and bytes are robust against coarse-mtime filesystems (FAT, some Docker bind mounts) where two writes inside the same tick could falsely compare equal.

Verification: 86/86 pass (40 system_license + 24 pro_install + 22 crypto). Ruff clean. No public API change.

@Blb3D Blb3D dismissed coderabbitai[bot]’s stale review May 11, 2026 14:57

All three review points addressed across 4288eef (actionable: from_dict anchors last_verified_at to load-time, plus _compute_grace_until fails closed on malformed input) and 774524c (nitpicks: factored load_license_cache_with_raw to drop the double-read in /info; switched the idempotency test from mtime to byte equality). 86/86 tests pass, ruff clean, public API unchanged. CodeRabbit's own follow-up at discussion_r3219550370 confirmed the round-1 approach. Dismissing the stale CHANGES_REQUESTED stamp from before the fix commits.

@Blb3D Blb3D merged commit 901a908 into main May 11, 2026
18 checks passed
@Blb3D Blb3D deleted the feature/pr03-license-cache-schema branch May 11, 2026 14:58
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.

2 participants