TRVLSPORT Developers
Only the current contract is available.

Contract v1 · generated reference

RuntimeHandler.catalogProjectOnOutbox

OP-RUNTIME-CATALOG-PROJECT-ON-OUTBOX is a firestore trigger operation owned by Traveler.

Back to the complete API catalogue

Authority and transport

Actor
Firestore document event
Authentication
platform invocation
Authorization
platform-only; authority re-verified from server-written state, never from the delivered payload
Resource boundary
server-derived from verified payload and stored state
Transport
FIRESTORE_TRIGGER
Deployed export
catalogProjectOnOutbox
Trigger
onDocumentCreated; Firestore document outbox/{id}; retry false
App Check
NOT_APPLICABLE
Canonical source
functions/src/properties/traveler/catalog/projection.ts

Consumed document

A Firestore trigger has no client request. The fields below are read from the created document the platform delivers; the handler re-verifies authority from server-written state and never trusts the delivered payload.

Consumed document fields for RuntimeHandler.catalogProjectOnOutbox
FieldRequiredSchema
outbox/{id}Yesdocument id of the created outbox row; the projection's idempotency key
typeYes"listing.published" | "listing.review_required"; any other type is SKIPPED_TYPE without a read
aggregateIdYeslisting id (opaque document id)
payload.slugYesexperience slug the listing was published under
payload.organizationIdYesowning organization id, re-verified against the listing
uidNoacting uid recorded on the audit row; defaults to system

Response schema

Promise<void>; outcome PROJECTED | WITHDRAWN | SKIPPED_DUPLICATE | SKIPPED_TYPE | REFUSED recorded in catalogProjections/{outboxId}

States, consistency and recovery

State path
DOCUMENT_CREATED → TYPE_FILTER → RECEIPT_COMPARE_AND_SET → SERVER_STATE_REVERIFICATION → PROJECTED_OR_WITHDRAWN → REFUSED_WITH_OPERATOR_SIGNAL → EXECUTION_FAILURE_LOGGED
Reads
the outbox row, its catalogProjections receipt, the listing, the organization, the existing experience and at most 366 availability days, all inside one transaction
Writes
experiences/{slug}, searchProjection/{slug}, the catalogProjections/{outboxId} receipt and an audit row; a refusal writes outbox/catalog-refused-{id} instead
Transaction
one Firestore transaction: receipt compare-and-set, re-verification of server-written state, then the catalogue writes
Idempotency
outbox document id through the catalogProjections receipt; a redelivery is SKIPPED_DUPLICATE and rewrites nothing
Concurrency
receipt compare-and-set inside the projection transaction
Retry/replay
at-least-once delivery is safe; retry is disabled (retry: false) and refusals are signalled rather than retried
Rate limits
Platform trigger only; there is no public caller quota.
Audit
traveler_trigger metric with outcome, outbox id and latency plus a catalogProject audit row; never listing copy

Error contract

No public error envelope; a row that does not re-verify is REFUSED with a durable catalog.projection_refused outbox signal and trigger retry is disabled, so nothing is looped or guessed. Never treat an unknown error as success.

No public error code is exposed by this managed trigger.

Producer test references

Evidence tier: SEMANTIC_CORE_REFERENCE.

Reviewer-mapped semantic/core references

  • functions/test/catalog-projection-source-anchors.test.js
    • catalogProjectOnOutbox is a us-west4 Firestore v2 create trigger on outbox/{id}, retry off, no HTTP surface
    • projection.ts never marks an outbox row dispatched and never updates the row it consumed
    • projectOutboxEvent ignores every non-catalogue event before touching the store
    • the projection takes its availability badge from the capacity ledger (WP-03) and carries no copy of the rule
  • functions/test/recovery-catalog-projection.test.js
    • S1.a CORE: listing.published projects experiences/{slug} with exactly the §3.1 keys, remainingCapacity 5, serviceDate, startsAt in UTC
    • S1.b CORE: replaying the same outbox id is SKIPPED_DUPLICATE — nothing is rewritten
    • S1.c CORE: DRAFT listing / slug mismatch / unverified org / payouts disabled are REFUSED with one signal each and no catalogue write
    • S1.c2 CORE: an inconsistent availability day (held+booked > capacity) is refused, never clamped
    • S1.c3 CORE: a listing with no future capacity projects as SOLD_OUT with serviceDate null (honest, not invented)
    • S1.d CORE: listing.review_required withdraws — search doc gone, SOLD_OUT / IN_REVIEW / withdrawnAt, remainingCapacity untouched
    • S1.e CORE: re-publication re-projects the same slug (withdrawnAt cleared, PUBLISHED, new version)
    • S2.e WIRING: an outbox listing.published row written through the Admin SDK is projected by catalogProjectOnOutbox within 15 s
    • S2.4 JOURNEY: listingPublish -> outbox listing.published -> catalogProjectOnOutbox -> experiences/{slug} (polled <= 15 s)
    • S2.6 JOURNEY: listingUpdate on the live listing -> listing.review_required -> trigger withdraws (search gone, SOLD_OUT)

Each cited test name is checked against the referenced file by the documentation suite, so a renamed or removed producer test breaks this page instead of leaving a stale claim.

Protocol or inventory references

  • functions/test/estate-service-inventory.js
  • functions/test/recovery-traveler-journey.test.js

These references prove export, wrapper or protocol behavior only. They are not counted as complete operation semantics.

Example availability

NOT_PUBLIC_TRANSPORT: This handler is provider- or platform-invoked and has no public client invocation.

Common integration contract

Sandbox and Production are isolated

Run examples only in a governed sandbox or local emulator. Sandbox identities, App Check tokens, payment fixtures and data do not grant Production access. The typed ports do not ratify a public numeric quota; enforcement may be stricter. Honor RATE_LIMITED and the server's retry-after value.

Authentication and authorization

Firebase Auth proves identity. App Check proves the approved app where a callable requires it. Roles, organization membership, requester identity and resource ownership are derived and rechecked by the server.

Authentication guide

Errors and recovery

Stable error codes distinguish validation, denial, conflict, expiry, authority pending, rate limiting and temporary unavailability. Never infer success from an HTTP redirect or a browser state.

Error catalogue

Idempotency, concurrency and retry

Reuse an idempotency key only for an identical retry. A new intent needs a new key. Preserve version preconditions and reload after a conflict.

Retry guidance

Changes and escalation

Check compatibility before adopting a new contract version. Review the changelog, service status and support guidance before retrying a persistent failure.

Common failure families
FamilyClient decision
Authentication / authorizationRefresh or step up only when instructed; do not retry a denial unchanged.
Validation / conflict / expiryCorrect or reload state, then obtain confirmation for a new intent.
Rate limited / unavailableHonor retry-after and use bounded backoff; inspect service status.
Unknown / serverFail closed and escalate with a privacy-safe correlation id.

Executable network examples are published only when the exact wire schema is producer-proven. Check the selected operation's example-availability status; an unresolved schema is a release blocker, not permission to guess.

Verified examples and local-only recovery snippets