Documentation navigation

Wayfarer

Conditional Place structure (BD2-08/09/10)

The supported Docker backend/Web and rewritten mobile client use the contracts below. They share the Place lifecycle owner and locked Segment reconciliation. These operations are transient compare/apply actions, without durable receipts, automatic rebasing, provider refresh or unknown-outcome recovery.

Routes and authority

Let B = /api/trips/{tripId}/conditional and W = /api/trips/{tripId}/editor. All IDs are GUIDs.

Operation Bearer apply Web apply Prepare
Coordinates POST B/places/{id}/coordinates Combined Web PUT below POST at the apply URL plus /prepare
Same-Trip move POST B/places/{id}/move Combined Web PUT below POST at the apply URL plus /prepare
Place order PUT B/regions/{id}/places/order PUT W/regions/{id}/places/order POST at the apply URL plus /prepare
Combined Web intent No bearer authoring parity PUT W/places/{id} POST W/places/{id}/prepare

Bearer actions require the actual incoming active API credential and current ownership; cookies do not supply bearer authority. Web actions require the User cookie, ownership and antiforgery, including prepare. Authority is checked again at apply’s final lock boundary. Missing/foreign targets are concealed equally. The existing unconditional bearer Place PUT returns 409 mutation-originals-required; a complete unconditional Web draft is invalid. Scalar Name/Notes/icon/color edits continue to use their existing conditional PATCH with selected originals, without prepare or any structural fallback. Scalar edits omit location intent and preserve absent or historical non-finite stored locations. Editor reads, structural originals, prepare previews and mutation responses project a historical non-finite Place location as null. This public projection never repairs the stored point or clears enrichment; private conditions retain raw geometry, distinguishing invalid geography from absence and detecting drift even when both publicly project as null. Unrelated Address edits remain permitted under the existing metadata policy. Explicit provider authoring requires a finite effective location; invalid new coordinates continue to reject with controlled validation errors.

Originals, preparation and explicit review

Prepare accepts the operation’s client originals and exact desired intent. It reads a coherent snapshot, compares public originals and returns:

{
  "contract": "bd2-1",
  "operation": "coordinates",
  "tripId": "<trip-guid>",
  "targetId": "<place-guid>",
  "requiresReview": true,
  "place": "<complete public material original, or null for order>",
  "placesByRegion": {},
  "affectedSegmentIds": [],
  "conditionToken": "<opaque protected conditions>"
}

place above denotes the object defined below, not a literal string. The response contains no raw private enrichment/provenance snapshots. Conditions bind the server’s protection keys, owner, Trip, typed target, operation, exact desired intent and complete material fingerprint. Coordinate, move and order purposes are separate. Nondestructive conditions have no wall-clock expiry; current material comparison remains decisive. They authorize neither access nor replay.

Prepare never replaces submitted originals. It rejects stale public evidence. Because the current client view does not contain original private dependencies, the prepared private base requires explicit review. Web presents that review before apply and preserves the authored draft on cancellation. A mobile client may acquire/review conditions before authoring the operation or explicitly review current consequences later; it must preserve an offline intent that lacks adequate original evidence until that review occurs. Do not silently treat a later prepare as the base against which the user originally edited.

Apply repeats the same request with conditionToken and reviewed: true added. It discovers locks, takes the existing profile → Segment → Place → Region → Trip order, rediscoveres the scope after waits and compares the final material state. New dependencies, changed profiles/anchors, membership/order drift or stale target content reject without applying any partial operation. No automatic retry changes expected values or exchanges a stale token for a fresh one.

BD2-08: coordinates

Prepare and apply use this body, adding the reviewed conditions only for apply:

{
  "expected": {
    "regionId": "<original-region-guid>",
    "location": { "latitude": 37.98, "longitude": 23.73 }
  },
  "location": { "latitude": 38.0, "longitude": 23.75 }
}

Original location may be null. Desired bearer coordinates must be a finite, inclusive WGS84 pair; zero is valid. Bearer coordinate clearing is not selected. The fingerprint binds original parent/location, the entire five-field enrichment tuple and affected routes, waypoint associations/positions/vertex indices, anchors, profiles, distance/duration/source and provenance. Unrelated scalar Notes are not coordinate dependencies. Coordinate-only intent preserves the current Address, including null, and does not contact providers.

Changed coordinates clear ResolvedFeatureName, ResolvedFeatureType, AddressEnrichmentProvider, AddressEnrichmentStorageMode and AddressEnrichedAt atomically. Unchanged coordinates retain all five. Existing reconciliation owns route vertex/fallback changes, waypoint effects, Wayfarer measurements, Manual duration preservation and provenance invalidation. Location, derived changes and current-safe Trip freshness commit together.

BD2-09: same-Trip move

A material original has every key below, preserving nullable stored values:

{
  "id": "<place-guid>", "regionId": "<source-guid>",
  "name": "Museum", "notes": null, "address": null,
  "location": { "latitude": 38.0, "longitude": 23.75 },
  "iconName": null, "markerColor": null, "displayOrder": null,
  "resolvedFeatureName": null, "resolvedFeatureType": null
}

Notes use the published normalized authored representation; null and empty are distinct originals. Web exposes exact nullable Address and stored order in originalValues; do not substitute display defaults. Bearer reads expose these values in their existing nested Place DTOs. The prepared fingerprint additionally binds the complete persisted moved-Place content, including private enrichment.

{
  "expected": {
    "place": "<material original object>",
    "placesByRegion": {
      "<source-guid>": [{ "id": "<place-guid>", "displayOrder": null }],
      "<destination-guid>": []
    }
  },
  "destinationRegionId": "<destination-guid>",
  "placeIdsByRegion": {
    "<source-guid>": [],
    "<destination-guid>": ["<place-guid>"]
  }
}

Both Regions must already exist in the same owned Trip. Original sets include all members and their exact nullable orders; array order is immaterial. Desired vectors contain the complete union exactly once, moving only the selected Place. Foreign, duplicated, omitted or additionally transferred members reject. Only Region membership, both affected Place vectors and Trip freshness change. IDs, content, visits, enrichment, routes, measurements and provenance survive. Sparse, tied and null original orders remain evidence; normalization to 1..N affects only the two submitted scopes. Canonical Region identity/stored order stays untouched.

BD2-10: Place order

{
  "expected": { "places": ["<every material original object in this Region>"] },
  "placeIds": ["<every desired Place ID exactly once>"]
}

The complete original membership, nullable stored orders and every reordered Place’s material content are checked. Duplicate, foreign, omitted and stale vectors reject atomically. Only this Region’s Place orders and Trip freshness change; all unrelated fields/collections remain untouched. Bearer ordering may target owned Unassigned Places; Web ordering retains its canonical restriction (403). Neither action repairs or changes the canonical Region’s own stored order.

Combined Web intent

The Web PUT sends {expected, changes, placeIdsByRegion?, conditionToken, reviewed}. Select only dirty name, notesHtml, iconName, markerColor, address, location, regionId and explicit reverseGeocode intentions. Without a move, expected.regionId and each intended field’s exact original are required; coordinate intent includes original nullable location. With a move, use the move’s complete expected.place, expected.placesByRegion and vectors; put the desired destination in changes.regionId. It compares all applicable originals and commits once, without PATCH followed by PUT. placeIdsByRegion is forbidden without a move, including an empty map. Move vectors must prove exactly the original source/destination membership within the owned Trip; unrelated Regions or members reject before writes or affected membership projection. This evidence is checked again under the apply locks.

Web retains explicit Address and metadata choices. Manual Address is trimmed, null means empty, and the established RetainCompatible policy clears the tuple when normalized Address or coordinates change. Explicit successful reverse geocoding replaces Address and the complete trusted tuple; it requires original Address/location evidence, freezes one provider result before domain locking and never repeats provider work on transaction contention. An unavailable requested provider uses the existing manual fallback/warning. Provider admission remains consumed if persistence fails. Ordinary coordinate/move/order actions make zero provider calls. No bearer Address/provider authoring capability is added.

Acknowledgements and holds

200 follows commit and returns contract: 'bd2-1', success, data, affected, deletedIds and warnings; bearer additionally returns applied: true. Place data is the complete editor Place DTO; order data is {regionId, placeOrder}. Membership/order effects include every changed Place and exact scoped vectors. Coordinate effects include complete affected Segment DTOs with fresh route/aggregate conditions, Segment order and visit progress. Acknowledgements are built inside the transaction before commit and adopted only after validating the complete target/effect envelope. They are mutation effects, not a full Trip baseline or a durable receipt.

Malformed/lost replies, disconnects, cancellation at commit and 5xx leave the attempt unknown. Newer drafts and corrective reversals survive acknowledgement; retired sessions cannot adopt late replies. Structural attempt facts and holds survive draft reset within the mounted workspace. A refresh, matching state, missing target or reset cannot establish noncommit or permit blind replay. There is no cross-reload recovery promise.

400 mutation-request-invalid rejects malformed originals/intent/vectors; 401 rejects invalid authority; concealed 404 does not prove deletion; 403 retains protected-operation restrictions. Known-unapplied 409 categories are mutation-review-required, mutation-dependency-stale, mutation-contention and the rejected unconditional route’s mutation-originals-required. Keep the draft and explicitly review an appropriate new base. Never infer noncommit from an arbitrary network/HTTP failure or automatically retry structural intent.

Back to top