Skip to content

E1-11: vehicle outbox payload entityType fix - #45

Merged
davidru85 merged 11 commits into
mainfrom
story/E1-11-outbox-entitytype-fix
Sep 2, 2026
Merged

E1-11: vehicle outbox payload entityType fix#45
davidru85 merged 11 commits into
mainfrom
story/E1-11-outbox-entitytype-fix

Conversation

@davidru85

@davidru85 davidru85 commented Sep 1, 2026

Copy link
Copy Markdown
Owner

Summary

  • Restore docs/CONTRACTS.md §8 compliance of every outbox payload produced by VehicleOutboxMapper in :feature:vehicle: the Vehicle snapshot and the cascade Fuel Entry tombstone now emit entityType.
  • Record D-110 "Outbox entity-type token ownership" (ADR-0111): keep explicit contract tokens in production code; add a bounded test-only anchor pinning EntityType enum names to the outbox wire values.

Story

E1-11 - :feature:vehicle Outbox Payload entityType Fix

Ready Check

  • Backlog story is explicit
  • Acceptance criteria reviewed
  • Dependencies checked
  • Required decisions are not Proposed or Pending
  • Normative sections reviewed
  • Expected verification identified
  • Human review gates identified before work

In-Progress Checkpoint

  • Date: 2026-09-02
  • Branch and base: story/E1-11-outbox-entitytype-fix from main (68842a2)
  • Current phase and latest commit: complete with owner-review hardening, latest commit 62c804a
  • Push and pull-request status: pushed, this PR
  • Completed since the previous checkpoint: RED, GREEN, REFACTOR committed; full verification suite passed; owner reviewed three options and chose Option C plus test-only hardening; D-110 recorded with ADR-0111 and all four mirrors
  • Verification evidence and known failures: the exact non-instrumented command from AGENTS.md passes with 627 actionable tasks; contractCheck and architectureCheck pass
  • Open decisions or blockers: none
  • Exact next step: merge after CI is green

Scope Completed

  • VehicleOutboxMapper.toVehicleOutboxPayloadOrNull now emits "entityType":"VEHICLE" as the first key, covering create, update and vehicle tombstone call sites.
  • VehicleOutboxMapper.toFuelEntryTombstonePayload now emits "entityType":"FUEL_ENTRY" as the first key of the cascade-delete tombstone payload.
  • VehicleOutboxMapperTest added with unit coverage of the Vehicle snapshot and cascade Fuel Entry tombstone payloads, including entityTypeEnumNamesMatchTheOutboxWireValues anchor.
  • VehicleRepositoryCreateTest.permanentOwnerCreateEnqueuesTheFullVehicleSnapshot exact key-set assertion updated to include entityType.
  • VehicleRepositoryUpdateTest.updateCoalescesTheOutboxAtTheOriginalSequence extended to assert entityType.
  • VehicleRepositoryDeleteTest.permanentOwnerDeleteEnqueuesFuelTombstonesBeforeTheVehicleTombstone extended to assert entityType in every coalesced Fuel Entry tombstone and in the vehicle tombstone.
  • VehicleRepositoryDeleteTest.cascadeDeleteCoalescesWithAnExistingFuelEntryOutboxRowKeepingEntityType added: parameterized coalescence test.
  • VehicleFormStateHolderTest.saveEnqueuesTheClosedRemoteVehicleSnapshot in :shared updated to include entityType in the exact key-set assertion.
  • VehicleRepositoryTestScope.seedFuelEntryOutbox helper added.
  • D-110 "Outbox entity-type token ownership" (ADR-0111) recorded with all four mirrors. The :feature:vehicle commonTest files derive their outbox lookup keys and seeds from EntityType.*.name; payload value assertions stay as exact string literals.

Acceptance Evidence

  • VehicleOutboxMapper.toFuelEntryTombstonePayload emits "entityType":"FUEL_ENTRY": VehicleOutboxMapperTest.fuelEntryTombstonePayloadIncludesEntityTypeFuelEntry.
  • VehicleOutboxMapper.toVehicleOutboxPayloadOrNull emits "entityType":"VEHICLE": VehicleOutboxMapperTest.permanentOwnerVehiclePayloadIncludesEntityTypeVehicle.
  • entityType asserted for each write path: Vehicle create, Vehicle update, Vehicle tombstone, cascade Fuel Entry tombstone.
  • Coalesced (FUEL_ENTRY, entityId) outbox row has entityType regardless of prior write source: VehicleRepositoryDeleteTest.cascadeDeleteCoalescesWithAnExistingFuelEntryOutboxRowKeepingEntityType.
  • entityTypeEnumNamesMatchTheOutboxWireValues pins EntityType.VEHICLE.name == "VEHICLE" and EntityType.FUEL_ENTRY.name == "FUEL_ENTRY".
  • LOCAL_OWNER + PENDING + no outbox invariant preserved.
  • No schema or migration change; production fix confined to mapper code.
  • docs/CONTRACTS.md §8 and docs/TECHNICAL_PLAN.md require no edit.

Out of Scope / Not Done

  • DatabaseMutations (core/database/.../DatabaseMutations.kt:53,112,152,176,312,428,475), the :feature:fuel outbox mapper literals, the :shared literals and the .sq CHECK/SQL literals keep their string literals because :core:database cannot depend on :core:sync (docs/TECHNICAL_PLAN.md §4) and unification requires a gated relocation of EntityType. Any centralization is deferred to a separate, explicit, Ready Phase 3 story that must jointly resolve where EntityType lives, module dependencies, SQL representation and contract assertions. It is not automatically assigned to E3-03.

Files Changed

  • feature/vehicle/src/commonMain/kotlin/com/ruizurraca/carapp/feature/vehicle/data/VehicleOutboxMapper.kt
  • feature/vehicle/src/commonTest/kotlin/com/ruizurraca/carapp/feature/vehicle/data/VehicleOutboxMapperTest.kt (new)
  • feature/vehicle/src/commonTest/kotlin/com/ruizurraca/carapp/feature/vehicle/data/VehicleRepositoryCreateTest.kt
  • feature/vehicle/src/commonTest/kotlin/com/ruizurraca/carapp/feature/vehicle/data/VehicleRepositoryDeleteTest.kt
  • feature/vehicle/src/commonTest/kotlin/com/ruizurraca/carapp/feature/vehicle/data/VehicleRepositoryTestScope.kt
  • feature/vehicle/src/commonTest/kotlin/com/ruizurraca/carapp/feature/vehicle/data/VehicleRepositoryUpdateTest.kt
  • shared/src/commonTest/kotlin/com/ruizurraca/carapp/VehicleFormStateHolderTest.kt
  • docs/handoff-E1-11.md (new)
  • docs/PROJECT_LOG.md
  • docs/DECISION_BOARD.md
  • docs/SPECIFICATION.md
  • docs/TECHNICAL_PLAN.md
  • docs/adr/README.md
  • docs/adr/0111-outbox-entity-type-token-ownership.md (new)
  • docs/BACKLOG.md
  • AGENTS.md
  • README.md

Decisions Made

  • D-110 "Outbox entity-type token ownership" (ADR-0111): E1-11 keeps the explicit contract tokens ("VEHICLE", "FUEL_ENTRY") in production code. The rejected alternatives are (A) extracting shared constants now and (B) deriving all values from the :core:sync EntityType — B is impossible for :core:database without a gated module-boundary change, and unification must also resolve SQL representation and independent contract assertions. The :feature:vehicle commonTest files derive their outbox lookup keys and seeds from EntityType.*.name (the enum is already on their classpath at zero new module cost), while the payload value assertions stay as exact string literals — the contract wire-value anchor. One new test, entityTypeEnumNamesMatchTheOutboxWireValues, pins the EntityType enum names to the wire values mandated by docs/CONTRACTS.md §8 and §20; it has no RED phase because it is a characterization pin of an existing invariant. No refactorization is assigned to E3-03.

Verification Run

  • Relevant tests pass
  • Lint passes (ktlint, detekt)
  • Coverage thresholds hold
  • Architecture checks pass
  • Contract check passes
  • Relevant builds pass (Android, iOS simulator, Shared framework from :composition:ios)
  • Documentation updated if behaviour, decisions or models changed

Commands or checks run:

./gradlew :feature:vehicle:testAndroidHostTest
./gradlew ktlintCheck detekt architectureCheck contractCheck :build-logic:convention:test koverVerify :androidApp:assembleDebug :androidApp:testDebugUnitTest testAndroidHostTest iosSimulatorArm64Test -x :integration:firebase-auth:iosSimulatorArm64Test -x :integration:firebase-firestore:iosSimulatorArm64Test -x :wiring:firebase:iosSimulatorArm64Test -x :composition:ios:iosSimulatorArm64Test

Result: BUILD SUCCESSFUL, 627 actionable tasks. git diff --check clean. contractCheck proves D-110 and ADR status parity across all four mirrors.

Contract Impact

  • No contract changes
  • Updated docs/CONTRACTS.md §:

Decision Board Impact

  • No decision changes
  • Updated docs/DECISION_BOARD.md (D-110) and the related ADR: ADR-0111

Shared-Write Modules Touched

  • None
  • core/database — confirm no other story is modifying it

Project Log Entry

  • Entry appended to docs/PROJECT_LOG.md

Human Review Gate

Gates are defined canonically in AGENTS.md.

  • Not applicable
  • E0-00 owner decision closure
  • Phase 0 closure
  • E0-07 walking skeleton
  • E1-05 consumption calculation
  • E2-06 local owner adoption
  • E3-01 Firestore security rules
  • E3-03 synchronization engine
  • Gated path (docs/SPECIFICATION.md, docs/CONTRACTS.md, docs/DECISION_BOARD.md, AGENTS.md, docs/adr/**, docs/identifiers.md, docs/versions-matrix.md, firestore/**, core/sync/**, core/auth/**, core/database/**, money types)
  • Gated topic (scope, stack or versions, backend, auth, sync algorithm, module boundaries, money representation, error taxonomy, logging and privacy, Firestore rules, Swift-facing API surface)

Risks or Follow-ups

  • Closes [Bug]: Vehicle cascade Fuel Entry tombstone omits entityType #36
  • Closes the E1-06 follow-up and the additional Vehicle payload finding folded into this story.
  • DatabaseMutations, the :feature:fuel and :shared literals, and the .sq CHECK/SQL literals remain independent string literals until a future centralization story.

Add VehicleOutboxMapperTest covering the Vehicle snapshot and cascade
Fuel Entry tombstone payloads, and extend the create, update and delete
repository tests to assert entityType presence. Add coalescence tests
proving a cascade delete after (or before) a direct Fuel Entry write
yields a payload that still carries entityType. Extend
VehicleRepositoryTestScope with seedFuelEntryOutbox to seed an existing
FUEL_ENTRY outbox row.
… payloads

Add "entityType":"VEHICLE" to LocalVehicle.toVehicleOutboxPayloadOrNull
and "entityType":"FUEL_ENTRY" to
FuelEntryDatabaseRow.toFuelEntryTombstonePayload so every outbox payload
produced by :feature:vehicle conforms to docs/CONTRACTS.md §8.
Merge the two near-identical coalescence tests into a single test that
loops over the stale payload variants, keeping the same coverage of a
cascade delete preceded by a direct Fuel Entry outbox write.
Apply ktlint formatting to VehicleOutboxMapperTest and add entityType
to the outbox payload key set assertion in the shared
VehicleFormStateHolderTest so it matches the corrected mapper output.
Complete the handoff with acceptance evidence and files changed, and
append the E1-11 story entry to docs/PROJECT_LOG.md.
@davidru85
davidru85 deployed to cloud-runtime-verification September 1, 2026 19:28 — with GitHub Actions Active
…enum

Replace outbox lookup keys and seeds in :feature:vehicle commonTest with
EntityType.*.name (already on the classpath via :core:sync). Keep payload
value assertions as exact string literals — the contract wire-value anchor.
Add entityTypeEnumNamesMatchTheOutboxWireValues as a characterization pin
so an enum rename fails the build instead of silently changing persisted
and pushed payloads.
@davidru85
davidru85 deployed to cloud-runtime-verification September 2, 2026 07:09 — with GitHub Actions Active
…urce of truth

Add E3-13 backlog entry and story index row for the Phase 3 story that
centralizes the entityType token. Create GitHub issue #46. Update the
E1-11 handoff risks section to reference the follow-up.
@davidru85
davidru85 deployed to cloud-runtime-verification September 2, 2026 07:14 — with GitHub Actions Active
Fix five owner-review findings on PR #45 (documentation-only):
1. Restore the accidentally deleted '### 2026-09-01 — E1-10 second-review
   verification and lifecycle corrections' header in docs/PROJECT_LOG.md.
   This is a repair of accidentally deleted content, not a rewrite of history.
2. Correct the Human Review Gate record in docs/handoff-E1-11.md to declare
   the four gated paths touched (AGENTS.md, SPECIFICATION.md,
   DECISION_BOARD.md, docs/adr/**).
3. Update the In-Progress Checkpoint to the post-fix state.
4. Add missing trailing newlines to ADR-0111 and the handoff.
5. Add 'Closes #36' and check the Gated path line in the PR #45 body.

Append a correction entry to docs/PROJECT_LOG.md.
@davidru85
davidru85 deployed to cloud-runtime-verification September 2, 2026 08:18 — with GitHub Actions Active
…xcluded

Add FirebaseRemoteSyncSourceEntityTypeBoundaryTest proving that:
- a valid Vehicle payload with entityType succeeds but the current code
  leaks entityType into Firestore fields;
- a mismatched entityType is not rejected;
- a missing entityType is not rejected;
- the same applies to Fuel Entry payloads.

Make RecordingFirestoreGateway internal so the new test file can reuse it.
…at the boundary

Require the outbox payload entityType to match EntitySnapshot.entityType
and exclude it from FirestoreWrite.fields, so connected Vehicle and Fuel
Entry writes conform to the closed remote schema of docs/CONTRACTS.md §16.
A missing, unknown, or mismatched entityType fails as
RemoteError.InvalidArgument through the existing closed error API.

Update the existing push/pull tests to include entityType in the outbox
JSON and compare pull results against the remote (entityType-free) JSON.

Add an end-to-end regression test in :shared proving an actual Vehicle
outbox payload with entityType reaches RemoteSyncSource as a valid
EntitySnapshot.
…cord

Finding 2: assert the exact canonical Fuel Entry payload key set in the
coalescence test, add cascade-idempotency test, and add a cross-feature
parity test in :shared proving the direct Fuel Entry writer and the
Vehicle cascade-delete writer produce the same canonical key set.

Finding 3: correct ADR-0111 to declare the gated paths touched by E1-11
instead of denying the human review gate.

Update handoff, project log, and PR body with the final state.
@davidru85
davidru85 deployed to cloud-runtime-verification September 2, 2026 09:04 — with GitHub Actions Active
@davidru85
davidru85 merged commit 38f8ef2 into main Sep 2, 2026
10 checks passed
@davidru85
davidru85 deleted the story/E1-11-outbox-entitytype-fix branch September 2, 2026 11:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Vehicle cascade Fuel Entry tombstone omits entityType

1 participant