HOS AIHOS AI
Draft · Observe

Documentation · HOS Events

HOS Events 0.1

Immutable, CloudEvents-compatible records of what happened in hospitality operations: one envelope profile, fifteen event types in five families, and the delivery rules every producer and consumer share.

Draft for review.

Names and fields may change before 0.1 is final. The specification text is published under CC BY 4.0; schemas are Apache-2.0 (see licensing). Every example is synthetic. Comments and counter-proposals are welcome through the technical contributor path.

Principles

Facts before intentions.

Immutable facts

An event records what happened. A correction, a released unit, a reverted check-in or a withdrawn maintenance window is a new event; nothing is edited in place.

Named for meaning

Types are domain.resource.past_tense, singular and vendor-neutral: housekeeping.task.completed, not a vendor's webhook name.

One dimension at a time

A status change states only the changed dimension, the previous value when known, the new value, the authority source and an optional reason.

Explicit snapshots

A snapshot is sent only when deltas cannot recover the state. It is declared in hosdatamode, allowed by the producer's manifest and sensitivity-classified.

Plans are not states

unit.maintenance_scheduled plans when a unit will be out of service or out of sale. The unit's statuses change only through unit.status_changed, when the window begins.

Honest time and actor

time is when the fact occurred. When the source only knows the entity's last modification, hostimebasis says modified; when it knows nothing, recorded. hosactor names who acted, pseudonymously, when the source knows.

Envelope profile

Every event is a CloudEvent with HOS context.

Draft. CloudEvents 1.0 in structured JSON mode with HOS extension attributes. Every HOS event, and every reference situation, uses this envelope. Attribute names follow CloudEvents rules: lower-case letters and digits, at most 20 characters, scalar values only.

HOS event envelope attributes
AttributeMeaning
specversionCloudEvents specification version.
idProducer-scoped, immutable event identity. With source, it is the duplicate key: a redelivered event keeps its id.
sourceThe producer. Must equal the producer declared in its Event Producer manifest.
typeVendor-neutral HOS event type, named domain.resource.past_tense in the singular.
timeWhen the fact occurred, when known. Otherwise the best time the producer has, qualified by hostimebasis.
datacontenttypeHOS 0.1 data is always JSON.
dataschemaoptionalOptional reference to the schema of data.
hosschemaversionHOS version the event conforms to.
hosrecordedatWhen the HOS implementation (producer or adapter) recorded the event. HOS always carries it.
hostimebasisoptionalWhat time represents. Absent means occurred. modified means the source's last modification of the entity, when it does not date the change itself: the fact occurred at or before time. recorded means the occurrence time is unknown and time is when the producer or adapter recorded the fact.
hostenantTenant: the security, policy and data-autonomy boundary.
hospropertyProperty the fact belongs to: the operating context.
hospropertytimezoneIANA time zone of the Property. Local times are derived from it, never assumed.
hosbusinessdateoptionalLocal operating (business) date the fact belongs to, when it matters. It can differ from the calendar date of time.
hossubjectsTyped HOS entity references implicated by the fact, as space-separated type:id pairs, for example stay:stay_1042 unit:unit_204. A string because CloudEvents attributes cannot hold arrays.
hosdatamodeoptionalHow to read data. Absent means delta. A snapshot must be declared here, be allowed for its type and be sensitivity-classified.
hossensitivityoptionalSensitivity class of data. Required for snapshots.
hoscausationsourceoptionalSource of the event that caused this one, when known.
hoscausationidoptionalId of the event that caused this one, when known. Used with hoscausationsource.
hosactoroptionalWho made the change in the producer, when it knows: user (a staff member), guest, system (the producer itself) or integration (another system through its API), with a pseudonymous, producer-scoped reference, for example user:staff_k3. Never a name or contact detail.
dataThe normalized payload. Its shape is defined by type; vendor detail goes in data.extensions under an inverted domain namespace.
unit.status_changed · synthetic
{
  "specversion": "1.0",
  "id": "hk-002231",
  "source": "urn:hos:housekeeping:demo",
  "type": "unit.status_changed",
  "time": "2026-07-30T06:40:00Z",
  "datacontenttype": "application/json",
  "hosschemaversion": "0.1",
  "hosrecordedat": "2026-07-30T06:40:02Z",
  "hostenant": "tenant_demo",
  "hosproperty": "prop_demo",
  "hospropertytimezone": "Europe/Paris",
  "hosbusinessdate": "2026-07-30",
  "hossubjects": "unit:unit_204",
  "data": {
    "unit_id": "unit_204",
    "dimension": "housekeeping",
    "previous": "inspected",
    "current": "dirty",
    "authority_source": "urn:hos:housekeeping:demo",
    "reason": "departure"
  }
}

Event catalogue

Fifteen event types in five families.

Required data is listed per type; every data object also accepts namespaced extensions. Each type has a downloadable example.

HOS Events 0.1 catalogue
TypeFamily · authorityRequired dataMeaningExample
reservation.createdReservationReservation system (PMS)reservation_id, status, planned_arrival_date, planned_departure_dateA reservation was created.reservation.created.json
reservation.updatedReservationReservation system (PMS)reservation_id, changed_fieldsA reservation changed. changed_fields lists what changed; only those fields are carried, with their new values.reservation.updated.json
reservation.cancelledReservationReservation system (PMS)reservation_idA reservation was cancelled.reservation.cancelled.json
stay.expectedStayPMSstay_id, reservation_id, planned_arrival_at, planned_departure_atA stay is expected: the operational visit that fulfils a reservation, one per booked unit. It is published as soon as the stay is committed, however early, and again whenever its planned times change; the latest occurrence wins. hosbusinessdate is the business date of the planned arrival. Unit assignment is a separate fact.stay.expected.json
stay.checked_inStayPMSstay_id, unit_idThe guest checked in. time is the actual arrival.stay.checked_in.json
stay.check_in_revertedStayPMSstay_idA check-in was reverted, for example because it was recorded in error. The stay is expected again; time is when the check-in was reverted.stay.check_in_reverted.json
stay.checked_outStayPMSstay_id, unit_idThe guest checked out. time is the actual departure.stay.checked_out.json
stay.unit_assignedStayPMSstay_id, unit_id, previous_unit_idA unit was assigned to a stay. Successive assignments, and the releases stay.unit_unassigned records, form the stay's assignment history.stay.unit_assigned.json
stay.unit_unassignedStayPMSstay_id, unit_idA unit was released from a stay, which has no unit until the next stay.unit_assigned. unit_id is the released unit; its entry in the assignment history ends at time.stay.unit_unassigned.json
unit.status_changedUnitDeclared per dimension (housekeeping system, PMS, maintenance)unit_id, dimension, current, authority_sourceOne dimension of a unit changed. The event states only that dimension, the previous value when known, the new value, the authority source and an optional reason; it never alters the other dimensions.unit.status_changed.json
unit.status_changed (snapshot)UnitDeclared per dimensionunit_id, statuses, authority_source, reasonSnapshot mode, only with hosdatamode = snapshot and when the producer declares snapshots: the current value of each dimension the producer reports, sent when deltas cannot recover the state, for example after an outage.unit.status_changed.snapshot.json
unit.maintenance_scheduledUnitDeclared: maintenance system or PMSmaintenance_id, unit_id, starts_at, ends_at, statusesA maintenance window was planned for a unit, or its plan changed. It is published again whenever the window changes; the latest occurrence for a maintenance_id wins. It does not change the unit's current statuses: unit.status_changed does, when the window begins.unit.maintenance_scheduled.json
unit.maintenance_cancelledUnitDeclared: maintenance system or PMSmaintenance_id, unit_idA maintenance window was withdrawn. A window that ends as planned needs no event; one cut short is published again with its new end.unit.maintenance_cancelled.json
housekeeping.task.createdHousekeepingHousekeeping systemtask_id, task_type, priorityA housekeeping task was created for a unit or a stay.housekeeping.task.created.json
housekeeping.task.completedHousekeepingHousekeeping systemtask_id, task_typeA housekeeping task was reported complete. Completing a task does not change a unit status by itself.housekeeping.task.completed.json
guest.message.receivedGuest communicationGuest messaging systemmessage_id, channel, sensitivityAn inbound guest message was received. Only metadata and extracted signals travel; the content stays in the messaging system.guest.message.received.json

Delivery, ordering and replay

Six rules every producer and consumer apply the same way.

  1. 01At-least-once delivery

    The same event can arrive more than once. Consumers deduplicate on source + id.

  2. 02No global order

    Ordering holds only when a producer declares it, per subject or in total. Otherwise consumers order by time, then source, then id; delivery order never decides.

  3. 03Declared capability

    A consumer processes only what the producer's manifest declares for the property. Undeclared means denied.

  4. 04Source authority

    Only the declared system of record changes a value. Other facts are recorded and shown as conflicts, never merged into a single truth.

  5. 05Replay

    Producers declare replay support and retention. Replays are JSON Lines, one event per line; a certified Event Producer profile provides at least 30 days.

  6. 06Minimal data

    Data objects are closed. People appear only as pseudonymous references, message content never enters HOS, and vendor detail lives in namespaced extensions.

Event Producer manifest

A system is not simply compatible: it declares exactly what it produces.

A public system publishes its manifest at /.well-known/hos/manifest.json; a private one may use a configured, authenticated URL.

What it declares

The events it emits for which properties, per dimension where relevant, whether it is the system of record for each, whether it may send snapshots, its delivery mechanisms and ordering, replay, retention and known limitations.

One authority

At most one producer is authoritative for a given property, event type and dimension. A PMS can mirror housekeeping status without being its authority.

Signed by its producer

A signature beside the manifest shows who declared these capabilities and that nobody changed them. It expires, and the producer's keys rotate without breaking it.

Example: the synthetic PMS manifest

Signed manifests

A consumer can check who declared the capabilities, and that nobody changed them.

The manifest stays readable JSON. Its producer signs it with a key it publishes, and the signature travels beside it.

  1. 01Signed by its producer

    A producer signs its manifest, so a consumer can check who declared its capabilities and that nobody has changed them since. A consumer verifies a manifest it retrieves before trusting any declaration in it. A manifest that fails counts as no manifest: nothing is declared, so nothing is processed. A manifest an operator installs by hand, as the conformance scenarios do, is trusted as configured.

  2. 02Three files

    A public producer serves, on one HTTPS origin, its manifest at /.well-known/hos/manifest.json, the signature at /.well-known/hos/manifest.jws and its public keys at /.well-known/hos/jwks.json. A private producer gives the three through configured, authenticated URLs.

  3. 03Detached signature

    manifest.jws is a JWS in compact serialization with a detached payload (RFC 7515, appendix F): the header and the signature, with nothing between the two dots. The payload is the manifest in its canonical form (RFC 8785), in UTF-8, so spacing and member order in manifest.json do not matter. The manifest itself carries no signature.

  4. 04Header

    The protected header holds alg, Ed25519 (RFC 9864) or ES256; kid, the signing key's id in the producer's key set; iat, when the manifest was signed; and exp, when the signature expires, both in whole seconds since the epoch. Every other algorithm is refused, none and EdDSA included, and so are crit and b64.

  5. 05Keys

    jwks.json is a JSON Web Key Set (RFC 7517) of public keys only: an OKP key on Ed25519 for Ed25519, an EC key on P-256 for ES256. Each kid is unique in the set. A key that states alg, use or key_ops states its algorithm, sig and verify.

  6. 06Verification

    A consumer canonicalizes the manifest it received, finds the key named by kid, checks that the key fits alg, and verifies the signature. It then checks that iat has come and exp has not, allowing at most 60 seconds of clock skew.

  7. 07Rotation

    A producer signs its manifest again before exp. To change keys, it adds the new key to its key set, signs with it, and keeps the old key until every manifest signed with the old key has expired. A compromised key is removed at once, and whatever it signed then fails verification.

manifest.jws · protected header
{
  "alg": "Ed25519",
  "kid": "Rmvn6iiK5EPEKShAwybtwHfST8XHnptFp5kdveGjsfk",
  "iat": 1790380800,
  "exp": 1821916800
}

A fictional producer, signed

What https://housekeeping.example would serve under /.well-known/hos/. The domain is reserved for examples; the producer is not a real system.

Test vectors in /spec/0.1/conformance/signing/ pair a manifest, its signature and a key set with the verdict a verifier must reach: valid, expired, not yet valid, signed with another key, changed after signing, unknown kid, alg none, or a payload that is not detached.

Reference situation · non-normative

What a consumer can derive: an arrival room-readiness risk.

Situations are not part of the event catalogue. The reference projection emits them with the same envelope so they can be replayed and audited.

arrival.room_readiness_at_risk

Raised when a stay is still expected and its assigned unit may not be ready when the guest comes: the guest is expected before the planned check-in and the unit is not ready, a maintenance window blocks the unit at that time, or another guest in house in the unit is not due to leave before then. A unit is not ready when its authoritative housekeeping status is not clean or inspected (in the scenarios), a window blocks it, or another guest still holds it. It carries the evidence, the conflicts, the latest task, and the window or the stay that holds the unit.

arrival.room_readiness_resolved

Emitted when the risk stops holding, with the reason: the stay was given another unit, the unit is ready, the window went, the guest in house left or is now due to leave first, the arrival is no longer early, the reservation is inactive or the stay has started. Situations are emitted on transitions only, and HOS 0.1 never acts on them.

Reference situation schema (non-normative)

Conformance corpus

Prove an implementation against three scenarios and 42 synthetic deliveries.

Each scenario is a property with its own producers and manifests. An implementation conforms when it reproduces every scenario's expected.json.

13 deliveries · Europe/Paris

Early arrival, unit not ready

  • Nominal early-arrival, dirty-unit scenario
  • Duplicate event (source + id deduplication)
  • Out-of-order event
  • Conflicting source facts
  • Snapshot recovery with explicit classification
  • Missing capability (undeclared means denied)

/spec/0.1/conformance/arrival-readiness/

14 deliveries · Europe/Lisbon

Assigned unit out of order

  • Maintenance window blocking an assigned arrival
  • A plan is not a state: the window and the maintenance status travel separately
  • Mirrored maintenance plan from a non-authoritative producer
  • Latest plan wins: an older revision delivered late is superseded
  • Reassignment resolves the risk
  • Causation across producers

/spec/0.1/conformance/room-out-of-order/

15 deliveries · America/Toronto

Late check-out on a same-day turnover

  • A unit held by another guest in house is not ready
  • Late check-out overlapping a same-day arrival
  • Occupancy observed by a non-authoritative producer
  • Latest plan wins: an older stay plan replayed late is superseded
  • Delivery order differs from occurrence order
  • Check-out resolves the risk

/spec/0.1/conformance/late-checkout/