carousell/rctp

Extensions

Status: Draft 0.4, pre-release. Sources: Draft 0.4 section 4.5, binding section 10a, AGENTS.md, CONTRIBUTING.md, GOVERNANCE.md.

Partners sometimes need something the current draft does not have. RCTP has two ways to add it without forking the specification and without confusing anyone else:

  • Open values under your own reverse domain. Draft 0.4 makes some values open [4.5(2)]: method types, event types, adjustment types, attestation types and term names. A party may add one under a domain it controls, such as example.carrier:picked_up_sealed. Anyone who does not recognise it treats it as informative, and it never changes a decision.
  • x- experiments for anything else not in Draft 0.4: a verb, a reason or a field. The prefix tells every other party "this is an experiment; if you don't know it, it grants nothing".

Reverse-domain open values in v0.2

v0.2 narrows the open values so that closed schemas can carry them (binding 10a):

Kind v0.2 behaviour
Deal event Accepted: an event may be an RCTP checkpoint, a UCP fulfillment event or adjustment, or <reverse.domain>:<name> (common.schema.json, ExtensionValue). Extension events are written to the deal record as informative and never satisfy or block a money rule.
Fulfillment method type Accepted: a UCP method type such as shipping or meetup, or <reverse.domain>:<name>. The gateway checks an offer's selected_option_id against the listing's fulfillment_options either way.
Term name Not accepted. Only the eleven terms of Draft 0.4 6.2 are allowed; a term set that needs another waits for a registry addition.

The pattern is ^[a-z0-9-]+(\.[a-z0-9-]+)+:[a-z][a-z0-9_]{0,63}$: a domain, a colon and a snake_case name. Use a domain you control.

x- naming

x-<partner>.<name>, where <partner> is a short lowercase label for your organisation and <name> says what it is. Use lowercase letters, digits, - in the partner label, and _ or . in the name.

Kind Example Where it would appear
Verb x-examplepartner.bulk_relist An action in a decision or deal record entry
Reason x-examplepartner.relist_quota_reached The reason of a refusal of an x- verb
Field x-examplepartner.campaign_ref A property in an RCTP object

The schema pattern for verb names is ^x-[a-z0-9-]+\.[a-z0-9_.]+$ (common.schema.json, Action).

Rules for every extension

  1. Your own name. It carries x- and a partner label you control, or a reverse domain you control. Never reuse a registered name or another partner's label.
  2. Tracking issue. An x- extension has a tracking issue in this repository before anything ships. The issue names the owner, the purpose and the end date.
  3. End date. An x- extension lives for at most two drafts or six months, whichever comes first. By then it is promoted into the spec or removed.
  4. No weakened invariant. It must not weaken INV-1 to INV-9. For example, an extension cannot carry a floor or personal data, cannot run below the floor, cannot sign a contract, and cannot be approved by anyone but the seller.
  5. Registered rules keep registered reasons. An x- reason may only refuse an x- verb. A refusal under any registered rule still carries its registered reason [13].
  6. Unknown means nothing. A gateway never lets an unrecognised value change a decision [4.5(2)]. An x- verb the gateway does not support is never run.
  7. Agents fail safe. An agent that receives an x- reason it does not know treats the call as refused and does not retry it unchanged.
  8. Declared. List every extension you use, with its tracking issue, in your partner integration profile.

What v0.2 does with x- today

The reference gateway is stricter than "ignore" in most places. Stricter never weakens an invariant.

Kind v0.2 behaviour
Verb The Action schema accepts x- names, but scopes are only dev.rctp.listing:* and dev.rctp.deal:* and the gateway has no route for any x- verb, so no grant can allow one and it is never run.
Reason Not accepted yet. Reason in common.schema.json is a closed enum generated from the registry, and the SDKs' Reason types are closed too. An x- reason needs a schema change first.
Field Not accepted yet. Every object schema is closed (additionalProperties: false). An x- field in a request body is a 400 invalid_request. An x- field needs a schema change first.

Proposing an extension

  1. Open an issue describing the problem, not just the fix. Give the name, the spec sections it touches, an invariant-by-invariant note on why none is weakened, and the end date.
  2. Change the schema first if the extension adds a reason or a field: edit schemas/, add a valid and an invalid example under schemas/examples/, run pnpm codegen, then write code (CONTRIBUTING.md).
  3. Test it. Add tests for its rules. If an invariant is involved, the test name contains the invariant's ID.
  4. Record it in CHANGELOG.md, with the tracking issue and the end date.
  5. Keep the pull request small. Editors review it under lazy consensus: an editor's approval and no unresolved objection after 5 working days (GOVERNANCE.md).

Promoting an extension into the spec

An extension that proves useful should become part of the next draft, without the prefix.

  1. Open a promotion issue that links the tracking issue and shows evidence: who used it, where, and what broke.
  2. Write one pull request that changes the spec text, the schemas and the conformance vectors together, as every spec change does. A new verb, reason, checkpoint, term or level is a registry addition and needs editor review [16].
  3. Add a conformance vector for any behaviour another implementation must also follow, with its plausible wrong answer [17.3].
  4. Plan the move. Keep the x- name working until its end date so integrators can switch, then remove it and note both in the changelog.

An extension that is not promoted by its end date is removed. Removing an x- name does not need a new major version: it was never registered.

Example tracking record

Field Example
Name x-examplepartner.bulk_relist
Kind Verb
Owner Example Partner Agents (https://agents.examplepartner.example/.well-known/ucp)
Purpose Relist several expired listings in one call, each keeping its approved terms
Intended tier ask
Invariants touched INV-5 (only the seller changes terms), INV-9 (no personal data)
Tracking issue #<number>
Started Draft 0.4
End date Two drafts or six months after start, whichever comes first
Status Proposed

Source: docs/extensions.md