feat(entitlement): has_capacity_batch + has_capacity_batch_at + endpoints - #5114
feat(entitlement): has_capacity_batch + has_capacity_batch_at + endpoints#5114vivekchand wants to merge 45 commits into
Conversation
Visual diffComparing 49 of 70 comparison(s) flagged (>1% pixel diff).
Folder: c5c5e3e9d189. 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. |
|
Automated maintainer check-in. CI is 100% green on this draft. The PR body explicitly leaves merge, When you're ready:
Nothing else is blocking this; the 77 new tests are passing and the AC ratchet holds. Generated by Claude Code |
|
Autonomous maintainer check-in (2026-08-23) The The run was killed mid-screenshot capture. The 100% diffs on mobile views are an artifact of the aborted run (incomplete captures compare against complete baselines). This is not a code problem. The PR is left as DRAFT per the author's explicit intent ("leaving merge, Generated by Claude Code |
|
| ) | ||
| return None | ||
|
|
||
|
|
There was a problem hiding this comment.
The PR implements has_capacity_batch and has_capacity_batch_at helper functions for batch-querying entitlement capacity grants across three axes, but these functions and their intended contract are not documented in any blueprint or requirement. These are implemented features with no corresponding documentation.
| ) | ||
|
|
||
|
|
||
| @bp_entitlement.route("/api/entitlement/has-capacity-batch") |
There was a problem hiding this comment.
The PR implements two new API endpoints (/api/entitlement/has-capacity-batch and /api/entitlement/has-capacity-batch-at) for batch-querying entitlement capacity across channels, retention_days, and nodes, but these endpoints are not documented in any blueprint or requirement.
|
Drift Bot: acknowledged, no code change on my side. Both findings are the same shape — the two new helpers ( For the diff itself: additive only, no contract change to existing entitlement surface, ships in GRACE, and byte-parity-tied to the singular Leaving it to the operator to mirror the pair into the blueprint when they land the corresponding pair on the closed-source side. Generated by Claude Code |
|
| "entitlements: tiers_for_node_count_at_batch failed: %s", exc | ||
| ) | ||
| return None | ||
|
|
There was a problem hiding this comment.
The PR implements has_capacity_batch and has_capacity_batch_at helper functions for batch-querying entitlement capacity grants across three axes (channels, retention_days, nodes), but these functions and their intended contract are not documented in the blueprint or requirements.
| ) | ||
|
|
||
|
|
||
| @bp_entitlement.route("/api/entitlement/has-capacity-batch") |
There was a problem hiding this comment.
The PR implements two new API endpoints (/api/entitlement/has-capacity-batch and /api/entitlement/has-capacity-batch-at) for batch-querying entitlement capacity across channels, retention_days, and nodes, but these endpoints are not documented in the blueprint or requirements.
|
CI block (transient): E2E Gate (required) was cancelled after ~18 min (run 32658722359) — the Action needed: manually rerun the cancelled E2E Gate job on that run to unblock the PR. Generated by Claude Code |
|
✨ auto-fixed: branch was behind main (base Generated by Claude Code |
|
E2E Gate now red on Same underlying finding as my previous reply above: the two new helpers + endpoints aren't in the "Hosted Agent Billing and Developer Access" blueprint on the factory side, which I can't touch from this repo. This wake gives me nothing new to push locally — appeasing Deferring to the operator: run Generated by Claude Code |
|
✨ auto-fixed: merged main (3 commits ahead) into branch; this re-triggers CI including a fresh E2E Gate + Drift Bot run to clear the stale failure. Generated by Claude Code |
|
|
|
||
|
|
||
| def has_capacity_batch( | ||
| *, | ||
| channels: int | None = None, | ||
| retention_days: int | None = None, | ||
| nodes: int | None = None, | ||
| ) -> dict: | ||
| """Per-axis boolean grants for every supplied capacity axis in one pass. | ||
|
|
||
| Boolean-gate twin of :func:`tiers_for_capacity_batch` on the three | ||
| capacity axes. Closes the capacity-axis symmetry gap in the ``has_*`` | ||
| family: :func:`has_batch` collapses to the two grant axes (features + | ||
| runtimes) and does not accept capacity args at all -- so a caller that | ||
| wants a live "does this install admit N channels / K retention days / | ||
| M nodes?" answer for a ``(channels, retention_days, nodes)`` bundle | ||
| had to fan out three ``has_channel_count`` / ``has_retention_window`` | ||
| / ``has_node_count`` calls. This helper delivers the same per-axis | ||
| boolean those three singulars return, on all three axes, in one call. | ||
|
|
||
| Envelope shape mirrors :func:`tiers_for_capacity_batch` on the three | ||
| capacity axes exactly (same per-axis ``None`` "not supplied" | ||
| sentinel, same never-raise contract):: | ||
|
|
||
| { | ||
| "channels": <bool> | None, | ||
| "retention_days": <bool> | None, | ||
| "nodes": <bool> | None, | ||
| } | ||
|
|
||
| Each boolean matches the singular ``has_<axis>`` helper byte-for-byte | ||
| (delegates directly to :func:`has_channel_count` / | ||
| :func:`has_retention_window` / :func:`has_node_count`) so grace | ||
| semantics carry through unchanged: while ``ent.grace`` is ``True`` | ||
| (the current rollout state) every axis returns ``True`` for every | ||
| finite request, same as the scalars. | ||
|
|
||
| Critically, ``retention_days=None`` here means *unset* -- NOT | ||
| *unlimited* (matches :func:`tiers_for_capacity_batch` on the same | ||
| axis). Asking the "does this install admit unlimited retention?" | ||
| question is the singular :func:`has_retention_window` call's job -- | ||
| it would delegate to the resolved entitlement here otherwise and a | ||
| caller supplying every other axis but leaving retention off would | ||
| get a mis-routed live-grant answer instead of an omitted axis. | ||
|
|
||
| A blank or non-int value on any axis short-circuits that axis to | ||
| ``None`` (matches :func:`tiers_for_capacity_batch`'s per-axis | ||
| ``None``-on-bad-input posture -- the caller opts in per-axis by | ||
| supplying a value). Distinguishes "not supplied" from ``False`` | ||
| (which is what the singular ``has_*`` helpers return on non-int | ||
| input) so a UI can tell a typo from a real deny. | ||
|
|
||
| Never raises: any resolver blowup collapses the whole envelope to | ||
| all-``None`` so a caller can bind this into a boolean AND-chain | ||
| without a try/except. | ||
| """ | ||
| try: | ||
| return { | ||
| "channels": ( | ||
| has_channel_count(channels) | ||
| if channels is not None | ||
| else None | ||
| ), | ||
| "retention_days": ( | ||
| has_retention_window(retention_days) | ||
| if retention_days is not None | ||
| else None | ||
| ), | ||
| "nodes": ( | ||
| has_node_count(nodes) | ||
| if nodes is not None | ||
| else None | ||
| ), | ||
| } | ||
| except Exception as exc: | ||
| logger.warning( | ||
| "entitlements: has_capacity_batch failed: %s", exc | ||
| ) | ||
| return { | ||
| "channels": None, | ||
| "retention_days": None, | ||
| "nodes": None, | ||
| } | ||
|
|
||
|
|
||
| def has_capacity_batch_at( |
There was a problem hiding this comment.
The PR implements has_capacity_batch() helper function for batch-querying entitlement capacity grants across three axes (channels, retention_days, nodes) in a single call, but this function and its contract are not documented in the blueprint or requirements.
There was a problem hiding this comment.
The PR implements has_capacity_batch_at() helper function for what-if perspective queries on entitlement capacity across three axes, but this function and its contract are not documented in the blueprint or requirements.
| &retention_days=K&nodes=M`` -- per-axis boolean grants for every | ||
| supplied capacity axis in one pass. | ||
|
|
||
| Boolean-gate twin of ``/api/entitlement/tiers-for-capacity-batch`` | ||
| on the three capacity axes. Closes the capacity-axis symmetry gap | ||
| in the ``/has-*`` family: ``/has-batch`` covers only features + | ||
| runtimes (the grant axes) and does not accept capacity args at all | ||
| -- so a caller that wants a live "does this install admit N | ||
| channels / K retention days / M nodes?" answer for a | ||
| ``(channels, retention_days, nodes)`` bundle either had to fan out | ||
| three ``/has-<axis>`` calls or hydrate the full | ||
| ``/capacity-headroom`` payload. This endpoint delivers the same | ||
| per-axis boolean those three singulars return, on all three axes, | ||
| off ONE round-trip. | ||
|
|
||
| At least one of ``channels=`` / ``retention_days=`` / ``nodes=`` | ||
| must be supplied (non-empty / parseable after normalisation). A | ||
| blank or non-int value on an individual axis is treated as "not | ||
| supplied" for that axis (matches | ||
| ``/api/entitlement/tiers-for-capacity-batch``'s never-mis-route | ||
| posture rather than silently reporting a typo as ``false``); the | ||
| endpoint 400s only when *no* axis parsed successfully. Never | ||
| 5xxs: the grace-shape envelope is returned on any resolver | ||
| failure. | ||
|
|
||
| Response shape:: | ||
|
|
||
| { | ||
| "channels": <bool> | None, | ||
| "retention_days": <bool> | None, | ||
| "nodes": <bool> | None, | ||
| "current_tier": "...", | ||
| "current_tier_rank": <int>, | ||
| "grace": <bool>, | ||
| "enforced": <bool>, | ||
| } | ||
|
|
||
| Each boolean matches the singular | ||
| ``/has-channel-count?count=`` / ``/has-retention-window?days=`` / | ||
| ``/has-node-count?count=`` endpoint byte-for-byte -- grace | ||
| semantics carry through unchanged from the singular helpers. | ||
|
|
||
| Critically, ``retention_days`` here treats ``None`` (parameter | ||
| omitted / unparseable) as *unset* -- NOT *unlimited* (matches | ||
| ``/tiers-for-capacity-batch``'s posture on the same axis). | ||
| Asking the "does this install admit unlimited retention?" |
There was a problem hiding this comment.
The PR implements GET /api/entitlement/has-capacity-batch endpoint for batch-querying entitlement capacity grants across channels, retention_days, and nodes, but this endpoint is not documented in the blueprint or requirements.
|
Mergeability janitor sweep (2026-08-24) Blocking check: Drift Bot flags places where the diff contradicts a Blueprint or an acceptance criterion in No other check is failing; everything else is green. Generated by Claude Code |
|
Auto-mergeability sweep — blocked on author decision — skipping The E2E Gate is failing because
Why this can't be fixed from the repo: The drift-bot check is set by an external GitHub App ( Action required: An operator with access to Generated by Claude Code |
|
Autonomous maintainer status check — 2026-08-24 All GitHub Actions CI checks on this PR pass (Syntax & Lint, API Tests across 3 OS, pip install matrix, Store invariants, MOAT checks, E2E Browser Tests, Entitlement API tests, Wheel install & asset presence). However the PR is blocked by Drift Bot, which is preventing the E2E Gate from passing. What is failing:
What Drift Bot checks: What needs human action:
This is outside the scope of what I can fix from here (no access to the 8090 Software Factory configuration). Flagging for your attention. Generated by Claude Code |
|
Auto-janitor note (2026-08-24): This PR is blocked by a Drift Bot failure inside the E2E Gate (required) check — all other CI checks pass. Root cause: Fix in queue: PR #5147 patches the stale list in FLYWHEEL.md. Merging #5147 should clear Drift Bot on this PR and allow the E2E Gate to pass. No rebase or code change needed here. Generated by Claude Code |
Previous commit accidentally changed the generated summary from \"exec-approval audit\" to \"exec-approval policy\". This restores the exact text gen_module_map.py produces from the source docstring. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GS4DCYUuj31UsEqTRUSWV8
|
| retention_days: int | None = None, | ||
| nodes: int | None = None, | ||
| ) -> dict: | ||
| """Per-axis boolean grants for every supplied capacity axis in one pass. |
There was a problem hiding this comment.
The has_capacity_batch() helper function for batch-querying entitlement capacity grants across three axes (channels, retention_days, nodes) is implemented in the codebase but not documented in the blueprint.
| def has_capacity_batch_at( | ||
| perspective_tier: str, | ||
| *, | ||
| channels: int | None = None, |
There was a problem hiding this comment.
The has_capacity_batch_at() helper function for perspective-scoped what-if queries on entitlement capacity across three axes is implemented in the codebase but not documented in the blueprint.
| from . import _shared | ||
|
|
||
|
|
||
| @_shared.bp_entitlement.route("/api/entitlement/has-capacity-batch") |
There was a problem hiding this comment.
The GET /api/entitlement/has-capacity-batch endpoint for batch-querying per-axis boolean capacity entitlements across three axes is implemented in the codebase but not documented in the blueprint.
…rift Bot run MODULE_MAP.md was already corrected to 252 modules with the correct routes/policy.py and new-file entries. This commit unsticks a stale Drift Bot result from the prior commit SHA. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GS4DCYUuj31UsEqTRUSWV8
|
| channels: int | None = None, | ||
| retention_days: int | None = None, | ||
| nodes: int | None = None, | ||
| ) -> dict: |
There was a problem hiding this comment.
The has_capacity_batch() helper function for batch-querying entitlement capacity grants across three axes (channels, retention_days, nodes) is implemented in the codebase but not documented in the blueprint.
|
|
||
| def has_capacity_batch_at( | ||
| perspective_tier: str, | ||
| *, |
There was a problem hiding this comment.
The has_capacity_batch_at() helper function for perspective-scoped what-if queries on entitlement capacity across three axes is implemented in the codebase but not documented in the blueprint.
| from . import _shared | ||
|
|
||
|
|
||
| @_shared.bp_entitlement.route("/api/entitlement/has-capacity-batch") |
There was a problem hiding this comment.
The GET /api/entitlement/has-capacity-batch endpoint for batch-querying per-axis boolean capacity entitlements across three axes is implemented in the codebase but not documented in the blueprint.
|
Branch updated against Generated by Claude Code |
|
| channels: int | None = None, | ||
| retention_days: int | None = None, | ||
| nodes: int | None = None, | ||
| ) -> dict: |
There was a problem hiding this comment.
The has_capacity_batch() helper function for batch-querying entitlement capacity grants across three axes (channels, retention_days, nodes) is implemented in the codebase but not documented in the blueprint.
|
|
||
| def has_capacity_batch_at( | ||
| perspective_tier: str, | ||
| *, |
There was a problem hiding this comment.
The has_capacity_batch_at() helper function for perspective-scoped what-if queries on entitlement capacity across three axes is implemented in the codebase but not documented in the blueprint.
|
PR sweep standing down: Generated by Claude Code |
|
Checked during this scheduled sweep (2026-09-12). The PR is Once the Drift Bot status resolves and CI finishes (currently running — most checks are queued), this will be mergeable. Generated by Claude Code |
|
Maintenance bot: merged Generated by Claude Code |
|
| channels: int | None = None, | ||
| retention_days: int | None = None, | ||
| nodes: int | None = None, | ||
| ) -> dict: |
There was a problem hiding this comment.
The has_capacity_batch() helper function for batch-querying entitlement capacity grants across three axes (channels, retention_days, nodes) is implemented in the codebase but not documented in the blueprint.
|
|
||
| def has_capacity_batch_at( | ||
| perspective_tier: str, | ||
| *, |
There was a problem hiding this comment.
The has_capacity_batch_at() helper function for perspective-scoped what-if queries on entitlement capacity across three axes is implemented in the codebase but not documented in the blueprint.
| from . import _shared | ||
|
|
||
|
|
||
| @_shared.bp_entitlement.route("/api/entitlement/has-capacity-batch") |
There was a problem hiding this comment.
The GET /api/entitlement/has-capacity-batch endpoint for batch-querying per-axis boolean capacity entitlements across three axes is implemented in the codebase but not documented in the blueprint.
|
| channels: int | None = None, | ||
| retention_days: int | None = None, | ||
| nodes: int | None = None, | ||
| ) -> dict: |
There was a problem hiding this comment.
The has_capacity_batch() helper function for batch-querying entitlement capacity grants across three axes (channels, retention_days, nodes) is implemented in the codebase but not documented in the blueprint.
|
|
||
| def has_capacity_batch_at( | ||
| perspective_tier: str, | ||
| *, |
There was a problem hiding this comment.
The has_capacity_batch_at() helper function for perspective-scoped what-if queries on entitlement capacity across three axes is implemented in the codebase but not documented in the blueprint.
| from . import _shared | ||
|
|
||
|
|
||
| @_shared.bp_entitlement.route("/api/entitlement/has-capacity-batch") |
There was a problem hiding this comment.
The GET /api/entitlement/has-capacity-batch endpoint for batch-querying per-axis boolean capacity entitlements across three axes is implemented in the codebase but not documented in the blueprint.
|
✨ auto-fixed: merged origin/main (2 commits ahead) into branch — now up to date with main. Generated by Claude Code |
No-PRD: Byte-parity boolean-gate twin of the already-shipped
tiers_for_capacity_batch{,_at}pair on the same three capacity axes (channels / retention_days / nodes); ships in GRACE, no behaviour change; the private 8090 factory that hosts the tiering blueprint is unreachable from this autonomous public-repo fire, so a proper product record is on the local operator to author if this warrants one.Summary
Adds the boolean-gate twin of
tiers_for_capacity_batchon the three capacity axes. Closes the capacity-axis symmetry gap in thehas_*family:has_batchcovers only features + runtimes (the grant axes) and does not accept capacity args at all, so a caller wanting a live "does this install admit N channels / K retention_days / M nodes?" bundle answer had to fan out threehas_<axis>calls (or threehas_<axis>_atcalls on the what-if side).What lands
Two helpers in
clawmetry/entitlements.py, mirroring the existingtiers_for_capacity_batchpair:Live-grant: delegates per axis to
has_channel_count/has_retention_window/has_node_count. Grace semantics carry through unchanged.What-if: delegates per axis to
has_channel_count_at/has_retention_window_at/has_node_count_at. Grace-independent (walks the static per-tier caps), sohas_capacity_batch_at("oss", channels=100)returnschannels=Falseeven in grace.Per-axis
Nonemeans "not supplied" (matchestiers_for_capacity_batch);retention_days=Noneis UNSET, not unlimited — callers wanting the unlimited-retention live grant use the singularhas_retention_windowscalar (same splittiers_for_capacity_batchcarries). Bad input on an axis short-circuits toFalse(matches the singularhas_<axis>helpers' strict callsite-typo posture), distinguishable from theNonenot-supplied sentinel so a UI can tell a typo from a real deny.Endpoints in
routes/entitlement.py:GET /api/entitlement/has-capacity-batch?channels=N&retention_days=K&nodes=MGET /api/entitlement/has-capacity-batch-at?tier=<perspective>&channels=N&retention_days=K&nodes=MBoth mirror
/tiers-for-capacity-batch{,-at}'s 400/404 posture: 400 when no axis parsed; the_atendpoint 400s on missing/blank tier and 404s on unknown tier; per-axis blank/non-int is treated as unsupplied (never mis-routes a typo). Never 5xxs — resolver failure returns the grace envelope on the live URL and the perspective envelope on the_atURL.Grace / open-core posture
Ships in GRACE like every other entitlement helper. Zero current behaviour change: the live gate grants everything today, and the
_atwhat-if scalar is the surface a pricing matrix binds to before enforcement flips on. No version bump, no release tag.Tests
New
tests/test_entitlement_has_capacity_batch.py(77 tests):Nonesentinel)has_<axis>andhas_<axis>_athelpersretention_days=Nonemeans unset, not unlimited_athelperLocal operator verification checklist
The autonomous fire can't touch the local daemon, the browser, or the live cloud snapshot. Once CI is green, please verify locally:
launchctl kickstart -k gui/$(id -u)/com.clawmetry.syncchannels/retention_days/nodes: truewithgrace: true, enforced: false, current_tier: "oss"; the_atURL withtier=oss&channels=100000returnschannels: false(grace-independent what-if); the_atURL withtier=cloud_proand the three-axis bundle returnstrue/true/trueplus the perspective envelope./api/entitlement//api/entitlement/tiers-for-capacity-batch{,-at}(these were not touched).Draft only — leaving merge,
[RELEASE]PR, tag, and cloud rollout to the operator.