feat(open-core): /api/runtimes catalog + locked-but-visible switcher hook#2209
Draft
vivekchand wants to merge 1 commit into
Draft
feat(open-core): /api/runtimes catalog + locked-but-visible switcher hook#2209vivekchand wants to merge 1 commit into
vivekchand wants to merge 1 commit into
Conversation
…hook
Phase 5 foundation: surface every known runtime — including paid ones with
zero local sessions — so the global runtime switcher can render an upgrade
affordance against runtimes the install isn't entitled to observe. No
behaviour change in grace mode (the default): the catalog reports
locked=False for every row, the frontend's locked set stays empty, and the
dropdown renders exactly as it did before this endpoint existed.
- clawmetry/entitlements.py: add RUNTIME_LABELS (single source of truth
shared with app.js's _CM_RT_LABEL), runtime_label(), and
runtime_catalog() returning [{id,label,free,allowed,locked}] for every
known runtime. Stable ordering: free first, paid after, both alphabetical.
- routes/entitlement.py: register GET /api/runtimes on bp_entitlement,
returning {runtimes, grace, enforced}. Never-raise: any resolution error
falls back to the OpenClaw-only grace shape.
- clawmetry/static/js/app.js: _cmInitGlobalRuntimeSwitcher() now also
fetches /api/runtimes and folds locked runtimes into _cmLockedRuntimes
ONLY when enforced=true, so grace-mode installs see no change. When
enforcement is on, locked runtimes appear in the dropdown as
"🔒 <Label> · Upgrade" alongside the observed ones.
- tests: 4 new entitlements tests (grace-locks-nothing, enforced-locks-paid,
label-coverage, label-fallback) + a 3-test Flask client suite for the new
endpoint covering both grace and enforced shapes plus key stability.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
Visual diffComparing No flagged differences (running with ALWAYS_POST=1).
Folder: pr/2209/473216446254. Full PNGs also attached as a workflow artefact. Generated by visual-diff bot. Pixel diffs >1% flagged; eyeball the table before merging. This check is non-blocking — fail = bot bug, not a code problem. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Phase 5 foundation on top of the open-core entitlement plumbing landed in #2186 / #2189: expose every known runtime — including paid ones with zero local sessions — through a stable
GET /api/runtimescatalog so the global runtime switcher can render an upgrade affordance against the runtimes this install isn't entitled to observe.Headline invariant: no behaviour change in grace mode (the default). The catalog reports
locked=Falsefor every row, the frontend's locked set stays empty, and the dropdown renders exactly as it did before this endpoint existed. The locked-but-visible affordance only activates onceCLAWMETRY_ENFORCE=1flips the resolver out of grace.Changes
clawmetry/entitlements.py: addRUNTIME_LABELS(single source of truth shared with_CM_RT_LABELinapp.js),runtime_label(), andruntime_catalog()returning[{id, label, free, allowed, locked}]for every known runtime. Stable ordering: free first, paid after, both alphabetical. Never raises — any resolution error falls back to a grace catalog.routes/entitlement.py: registerGET /api/runtimesonbp_entitlement, returning{runtimes, grace, enforced}. Never-raise; any error falls back to the OpenClaw-only grace shape so the UI always has something safe to render.clawmetry/static/js/app.js:_cmInitGlobalRuntimeSwitcher()now also fetches/api/runtimesand folds locked runtimes into_cmLockedRuntimesonly whenenforced=true, so grace-mode installs see no change. When enforcement is on, locked runtimes appear in the dropdown as🔒 <Label> · Upgradealongside the observed ones.tests/test_entitlements.py— 4 new tests: grace-locks-nothing, enforced-locks-paid, label coverage for every paid runtime, unknown-runtime label fallback.tests/test_routes_runtimes.py— 3 Flask-test-client tests for the new endpoint: grace shape, enforced shape, key stability (defends against an accidental rename breaking the dropdown).Smoke test against the live endpoint:
Why a draft
The open-core rollout is in grace mode until the announced enforce date — this PR is the next foundational layer (Phase 5 in
entitlements.py:327). Keeping it draft so the local operator handles the daemon-side verification + cloud-snapshot decrypt before this lands.Local operator verification checklist
I cannot reach the live daemon,
~/.openclaw, or the cloud snapshot from this sandbox — please run the steps below before flipping to ready-for-review:__pycache__under the venv andkickstartthe daemon:grace=true,enforced=false, every paid runtime present withlocked=false.CLAWMETRY_ENFORCE=1and re-hit it: expectlocked=trueon the 10 paid runtimes,openclawstill free; then unset.CLAWMETRY_ENFORCE=1set, paid runtimes appear as🔒 <Label> · Upgradeeven when they have zero sessions.Out of scope
license.py).🤖 Generated with Claude Code
Generated by Claude Code