Skip to content

feat: Universal Licensing Core — LicenseTerm, restrictions, quotas, and obligations #5

Description

@KonstantinMirin

Status: implemented in PR #6. This issue captures the rationale; the PR is the authoritative description of the final wire shapes. The design evolved during implementation (Pricing redesign, proto-native vocabulary, enum sentinels) — this body has been refreshed to match what shipped.

Problem

Offer.AccessRestrictions modeled access rules as a flat, ad-hoc structure: permitted/prohibited lists for function, geography, and user category. Sufficient for binary allow/deny, but it could not express a complete commercial licensing arrangement in a machine-readable, cross-domain-portable form. Specifically there was no standard way to represent:

  • Multi-arrangement resources — a news article free for academic RAG but paid for commercial use: two terms for one resource, not two resources.
  • Usage quotas — a stock photo capped at 500,000 impressions, a design file at 1,000 manufactured units. Quotas gate license validity, not billing.
  • Post-use obligations — attribution, share-alike, contribution, notice, network-copyleft. Behavioral requirements that exist regardless of whether a fee is charged.
  • Reference-only terms — complex licenses (MCPS/PRS, patent portfolios) that can't be enumerated; the governing document is at a URI.
  • Offline metering — physical manufacturing events the Exchange can't observe; the agent self-reports.
  • Pricing per use caseFREE for academic, priced for commercial, on the same resource.

Symptom: publishers were abusing PRICING_MODEL_ATTRIBUTION/CONTRIBUTION to carry behavioral obligations, because there was no obligation concept. Attribution is not a payment model.

Model — one resource, many LicenseTerm

Each LicenseTerm is one complete access arrangement, the same shape at ingest (ResourceEntry.terms) and discovery (Offer.terms):

  • TermSemanticsENUMERATED (machine fields authoritative; Exchange enforces) or REFERENCE_ONLY (License.uri is authoritative; machine enforcement deferred).
  • Licenseuri (canonical identity, never URL-validated), id (SPDX short-id / TollBit cuid), name, immutable.
  • Restriction — one axis (FUNCTION / GEOGRAPHY / USER_TYPE) with permitted[], prohibited[], critical.
  • Quota — usage cap gating validity: metric, limit, window (HOURLY/DAILY/MONTHLY/TOTAL).
  • Obligationkind (ATTRIBUTION, CONTRIBUTION, SHARE_ALIKE, NETWORK_COPYLEFT, NOTICE, OTHER), trigger, scope_license (required for SHARE_ALIKE), detail.
  • Pricing — required on every term; model = FREE must be explicit (absent ≠ free).

scopes (Biscuit scope-gating) and part_label (sub-parts) round out LicenseTerm. Every required enum carries _UNSPECIFIED = 0 and is rejected if unset.

Pricing — structure + metering basis

PricingModel is the closed charging structure: FREE, PER_UNIT, FLAT (+ UNSPECIFIED). The open metering basis ("per what") is Pricing.unit, a vocabulary (below). Pricing.metering = ONLINE / NONE / OFFLINE_SELF_REPORTED. There is no revenue-share model — settlement is off-protocol. PRICING_MODEL_ATTRIBUTION/CONTRIBUTION are removed (they are Obligations).

Vocabulary — proto-native, not JSON

The open string axes (Pricing.unit, Quota.metric, and the function/geography/user-type restriction axes) are defined in the proto via custom options (ramp.v1.vocab) / (ramp.v1.vocab_enum) and tooled by buf:

  • Validationprotovalidate CEL: a bare token must be registered, OR vendor:namespaced (unbounded), OR empty. Structural rules + cross-field rules run at the RPC boundary via the Connect validate interceptor — no per-language validation code.
  • Constants — a buf plugin emits a typed-constant package per axis (pricingunits, quotametrics, functiontokens, geographytokens, usertypes) with IsRegistered(), so application code branches on generated constants. Membership is enforced at ingest from this single source.
  • Geography registers only the non-ISO specials (*, EU, EEA); ISO-3166 alpha-2 codes are structural. Growing a registered list is an additive version bump; vendor extensions need no registry change.

This replaces the earlier vocab/*.json registry files (deleted).

Cross-domain examples (final shapes)

  • News article — Term 1 {FUNCTION: ai-input (academic), pricing: FREE}; Term 2 {FUNCTION: ai-input+ai-index (commercial_entity), pricing: PER_UNIT unit=accesses $0.05}; both ATTRIBUTION / ON_USE.
  • Stock photo{FUNCTION: display, GEOGRAPHY: *, pricing: FLAT $1.20, quota: impressions ≤ 500000 / TOTAL, ATTRIBUTION}.
  • Patent/CAD{FUNCTION: manufacture, pricing: PER_UNIT unit=units-manufactured $0.50 metering=OFFLINE_SELF_REPORTED, quota: units-manufactured ≤ 1000 / TOTAL, CONTRIBUTION}.
  • Reference-only{semantics: REFERENCE_ONLY, license.uri: <MCPS/PRS terms>, pricing: <stated>}; machine fields deferred to the document.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions