Build the general-purpose usage-cap capability that the Mapper public preview runs on — and that the launch subscription tiers will reuse. Supersedes this ticket's original monthly-reset, Mapper-specific design (approach doc ocl-workspace/mapper/2026-09-15-mapper-public-preview-approach.md, WP2, 2026-09-16).
Any metered thing can be locked to an arbitrary number we assign when ready. The preview's starting values are just the first assignment.
Four independent caps — row count is not the match meter (one AutoMatch run can fire several match requests per row, one per algorithm, at very different compute costs):
| Cap |
Scope |
Preview value |
mapper.projects |
user |
1 |
mapper.rows_per_project |
project |
25 (hard) |
mapper.match_operations |
user |
100 |
ai_assistant.calls |
user |
6 |
ACs
- Named caps with values held in data, each independently assignable; adding a new cap later needs no change to the enforcement path
- One-time allowance: monotonic, no period, no reset. More access comes from an admin raising a cap or granting a role.
- Usage ledger per user, maintained across the whole system — one ledger, so every surface agrees
- Quota not stored in
UserProfile.extras, which is user-writable via PUT /users/<username>/ (core/users/serializers.py:249). Real columns.
- Concurrency-safe increments (
F() updates, not read-modify-write) — the Mapper fires MAX_CONCURRENT_REQUESTS chunks in parallel
- A single check-and-consume API used by every enforcing service, including
ocl-ai-assistant
- Structured
403 + error_code naming which cap was hit, with used/allowed and no reset date. Never 429 — that is already the throttling path (APIService.onThrottle), and a quota wall must not read as a rate limit.
GET /user/ exposes capabilities + caps + usage so clients render without probing
- Admins can raise any cap per user or per role
- Per-action consumption records (action, units, algorithm, project, run) so configuration-based weighting can be added later without a rewrite.
AutomatchRun remains the per-run audit trail.
To decide during implementation
- Where cap values attach: role-level with per-user override, highest wins; unlimited = no cap set
- Whether a match operation meters an API call or a row-algorithm pair (the client batches 10 rows per request). Proposed: row-algorithm pairs.
Split out of this ticket: the permission/capability layer, the Mapper chokepoint wiring, and $invoke enforcement in ocl-ai-assistant are now separate tickets in this milestone.
Epic OpenConceptLab/ocl_online#133
Build the general-purpose usage-cap capability that the Mapper public preview runs on — and that the launch subscription tiers will reuse. Supersedes this ticket's original monthly-reset, Mapper-specific design (approach doc
ocl-workspace/mapper/2026-09-15-mapper-public-preview-approach.md, WP2, 2026-09-16).Any metered thing can be locked to an arbitrary number we assign when ready. The preview's starting values are just the first assignment.
Four independent caps — row count is not the match meter (one AutoMatch run can fire several match requests per row, one per algorithm, at very different compute costs):
mapper.projectsmapper.rows_per_projectmapper.match_operationsai_assistant.callsACs
UserProfile.extras, which is user-writable viaPUT /users/<username>/(core/users/serializers.py:249). Real columns.F()updates, not read-modify-write) — the Mapper firesMAX_CONCURRENT_REQUESTSchunks in parallelocl-ai-assistant403+error_codenaming which cap was hit, with used/allowed and no reset date. Never429— that is already the throttling path (APIService.onThrottle), and a quota wall must not read as a rate limit.GET /user/exposes capabilities + caps + usage so clients render without probingAutomatchRunremains the per-run audit trail.To decide during implementation
Split out of this ticket: the permission/capability layer, the Mapper chokepoint wiring, and
$invokeenforcement in ocl-ai-assistant are now separate tickets in this milestone.Epic OpenConceptLab/ocl_online#133