Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions docs/explanation/validation-repair-patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,78 @@ would change behavior, verification, and limits. Link to existing schema
or fixture definitions when useful, and distinguish historical results
from current validation. Amend an existing pattern when the new case
refines its applicability rather than copying the same procedure.

## Example manifests held to the validator

### Findings on unmodified main

The manifests under `examples/manifests/` were only checked against the
manifest schema, so they drifted from the validator and taught patterns it
rejects. `example_manifests_validate` in `trogon-atlas-server` now applies all
three namespaces to an ephemeral store and requires every model to validate
with no warning or error.

On unmodified main the three models reported these findings.

| Model | Errors | Warnings |
| --- | --- | --- |
| `acme-storefront` | 12 | 2 |
| `acme-reviews` | 7 | 2 |
| `acme-customer-data-deletion` | 0 | 1 |

### Decisions

- **System-recorded timestamp.** `EVENT_FIELD_UNSOURCED` on
`order.placed.placed_at` and `review.submitted.submitted_at` is a real gap.
The command now declares the field with a `DerivedFieldAnnotation`, the
way `capture-payment.paid_at` already did.
- **Fields a downstream view needs travel with the events.**
`READ_MODEL_FIELD_UNSOURCED` on `fulfillment-queue.buyer_id` and
`seller_id` was repaired by carrying both from `place-order` through
`order.placed`, `pending-payments`, `capture-payment` and `order.paid`.
Dropping the fields would have removed what the dispatch automation hands
to `mark-fulfilled`.
- **A view must carry every field of each source event.**
`RM_SOURCE_EVENT_FIELD_NOT_PROJECTED` is repaired by adding the missing
fields, including swimlane state such as `status`.
- **One projection per emitter.** A read model fed by several events
(`order-status`) cannot sit in one slice: `PROJECTION_BEFORE_EMITTER` and
`PROJECTION_NOT_ADJACENT` both fire. It is now one read-model slice per
source event, each placed right after the command slice that emits it.
- **A queue filled in an earlier storyboard.** `pending-payments-rms` moved
into `checkout-flow` after `place-order-slice`. `fulfillment-flow` still
enters on that queue. This is the same-context sibling of the
[upstream subscription pattern](#upstream-subscription-at-a-local-storyboard-entry):
there the emitter lives in another bounded context, here in another
storyboard of the same model, so the projection can follow its emitter
instead of leaving the model.
- **Seed catches up with the recorded acme-reviews repair.** The upstream
fulfillment projection left the storyboard and the model membership, and
the storyboard doc says why. The projection slice itself is deleted from
the seed because no storyboard, model, scenario, or test references it.
- **Each example declares its Project.** `DOMAIN_MISSING_PROJECT` only shows
in project-scoped validation. Each namespace now has a Project of the same
name that its Domain points to, and the test validates every Project.
- **Contexts touch through subscriptions only.**
`CROSS_MODEL_BOUNDARY_VIOLATION` on the grant command slice scenario was
repaired by dropping the `given` that named an `acme-storefront` event. The
automation slice scenario already starts from the subscribed queue.
- **Entry read model.** `STORYBOARD_MISSING_ENTRY` is repaired by naming the
read model the entry observer sees: `cart-summary`, `eligible-reviews` and
`pending-deletions`.
- **Declared intent.** `ORPHAN_NOT_FLAGGED` on `support-agent` and
`UI_USED_AS_INPUT_AND_OUTPUT` on `review-form` are kept and annotated
(`OrphanAnnotation`, `MixedSurfaceAnnotation`) because the persona is
reserved for a flow not modeled yet and the form is prefilled from the row
it submits against.

### Rule observations, not changed here

- `RM_SOURCE_EVENT_FIELD_NOT_PROJECTED` cannot express a view that keeps a
count and ignores the event payload: `cart-summary` now also holds the last
`product_id` and `quantity`.
- `STORYBOARD_MISSING_ENTRY` asks for an observed read model even when the
flow starts at a UI that has nothing to display yet, as in `checkout-flow`.
- `trogon-atlas fmt --check` already reports
`acme-customer-data-deletion/read-model.yaml` as unformatted on main. That
file is left alone.
7 changes: 7 additions & 0 deletions docs/how-to/repair-validation-findings.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,13 @@ manifest replay can overwrite unrelated live changes. Preview the selected
seed manifests with `trogon-atlas apply --dry-run` in the same branch and endpoint.
That check verifies manifest admissibility, not full scoped validation.

When the seed itself is the thing to repair, edit the YAML, run
`trogon-atlas fmt -w` on the touched files, and run
`cargo test -p trogon-atlas-server --test example_manifests_validate`. That
test applies every example namespace to an ephemeral store and lists each
warning or error by code and subject, so it doubles as the before and after
record. Finish with `mise run manifests:verify -- bootstrap`.

## Keep the result available to the next session

Before cleanup or handoff, save a descriptive run record in a directory
Expand Down
5 changes: 5 additions & 0 deletions examples/manifests/acme-customer-data-deletion/domain.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,9 @@ metadata:
namespace: acme-customer-data-deletion
spec:
doc: Honoring an account owner's request to delete the personal data Acme holds about them.
project:
id:
namespace: acme-customer-data-deletion
slug: acme-customer-data-deletion
version: '1'
title: Customer Privacy
8 changes: 8 additions & 0 deletions examples/manifests/acme-customer-data-deletion/project.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
apiVersion: eventmodel.atlas.trogonstack.com/v1alpha1
kind: Project
metadata:
name: acme-customer-data-deletion
namespace: acme-customer-data-deletion
spec:
doc: Customer data deletion across every downstream vendor.
title: Acme Customer Data Deletion
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ spec:
namespace: acme-customer-data-deletion
slug: deletion-request-form
version: '1'
readModel:
readModel:
id:
namespace: acme-customer-data-deletion
slug: pending-deletions
version: '1'
outcome:
events:
- event:
Expand Down
6 changes: 0 additions & 6 deletions examples/manifests/acme-reviews/command-slice.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,6 @@ spec:
namespace: acme-reviews
slug: review-eligibility.granted
version: '1'
given:
- event:
id:
namespace: acme-storefront
slug: order.fulfilled
version: '1'
id: delivered
title: Eligibility granted
when:
Expand Down
7 changes: 7 additions & 0 deletions examples/manifests/acme-reviews/command.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@ spec:
- name: comment
type:
string: {}
- metadata:
- '@type': type.googleapis.com/trogonatlas.eventmodel.v1alpha1.DerivedFieldAnnotation
doc: now() when the review is recorded
kind: KIND_TIMESTAMP
name: submitted_at
type:
timestamp: {}
swimlane:
id:
namespace: acme-reviews
Expand Down
5 changes: 5 additions & 0 deletions examples/manifests/acme-reviews/domain.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,9 @@ metadata:
namespace: acme-reviews
spec:
doc: Keeping buyer confidence high after a sale closes, including seller reputation.
project:
id:
namespace: acme-reviews
slug: acme-reviews
version: '1'
title: Customer Trust
5 changes: 0 additions & 5 deletions examples/manifests/acme-reviews/event-model.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,6 @@ spec:
slug: submit-review-slice
version: '1'
kind: ENTITY_KIND_COMMAND_SLICE
- id:
namespace: acme-reviews
slug: orders-delivered-unreviewed-upsert-rms
version: '1'
kind: ENTITY_KIND_READ_MODEL_SLICE
- id:
namespace: acme-reviews
slug: orders-delivered-unreviewed-delete-rms
Expand Down
8 changes: 8 additions & 0 deletions examples/manifests/acme-reviews/project.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
apiVersion: eventmodel.atlas.trogonstack.com/v1alpha1
kind: Project
metadata:
name: acme-reviews
namespace: acme-reviews
spec:
doc: Review eligibility and submission for delivered storefront orders.
title: Acme Reviews
22 changes: 0 additions & 22 deletions examples/manifests/acme-reviews/read-model-slice.yaml
Original file line number Diff line number Diff line change
@@ -1,27 +1,5 @@
apiVersion: eventmodel.atlas.trogonstack.com/v1alpha1
kind: ReadModelSlice
metadata:
name: orders-delivered-unreviewed-upsert-rms
namespace: acme-reviews
spec:
doc: A delivered order appears on the queue.
projectionRole: PROJECTION_ROLE_UPSERT
readModel:
readModel:
id:
namespace: acme-reviews
slug: orders.delivered-unreviewed
version: '1'
sourceEvents:
- event:
id:
namespace: acme-storefront
slug: order.fulfilled
version: '1'
title: 'Queue: delivered unreviewed'
---
apiVersion: eventmodel.atlas.trogonstack.com/v1alpha1
kind: ReadModelSlice
metadata:
name: orders-delivered-unreviewed-delete-rms
namespace: acme-reviews
Expand Down
29 changes: 25 additions & 4 deletions examples/manifests/acme-reviews/read-model.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ metadata:
namespace: acme-reviews
spec:
doc: 'Worker feed: delivered orders without a review-eligibility grant yet.'
key:
- order_id
schema:
'@type': type.googleapis.com/trogonatlas.eventmodel.v1alpha1.Schema
fields:
Expand All @@ -17,8 +19,15 @@ spec:
- name: seller_id
type:
uuid: {}
key:
- order_id
- name: fulfilled_at
type:
timestamp: {}
- name: granted_at
type:
timestamp: {}
- name: status
type:
string: {}
sourceEvents:
- id:
namespace: acme-storefront
Expand All @@ -37,6 +46,8 @@ metadata:
namespace: acme-reviews
spec:
doc: Orders the reviewer-facing form can still collect a review for.
key:
- order_id
schema:
'@type': type.googleapis.com/trogonatlas.eventmodel.v1alpha1.Schema
fields:
Expand All @@ -52,8 +63,18 @@ spec:
- name: granted_at
type:
timestamp: {}
key:
- order_id
- name: status
type:
string: {}
- name: rating
type:
int: {}
- name: comment
type:
string: {}
- name: submitted_at
type:
timestamp: {}
sourceEvents:
- id:
namespace: acme-reviews
Expand Down
12 changes: 7 additions & 5 deletions examples/manifests/acme-reviews/storyboard.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ metadata:
name: review-eligibility-grant
namespace: acme-reviews
spec:
doc: A delivered order clears the no-review window and becomes reviewable.
doc: A delivered order clears the no-review window and becomes reviewable. It starts from the delivered-unreviewed queue that the storefront fulfillment event populates, so that projection is upstream preparation and not a step here.
entry:
automation:
processor:
Expand All @@ -27,10 +27,6 @@ spec:
slug: review-eligibility.granted
version: '1'
slices:
- id:
namespace: acme-reviews
slug: orders-delivered-unreviewed-upsert-rms
version: '1'
- id:
namespace: acme-reviews
slug: grant-eligibility-auto
Expand Down Expand Up @@ -70,6 +66,12 @@ spec:
namespace: acme-reviews
slug: review-form
version: '1'
readModel:
readModel:
id:
namespace: acme-reviews
slug: eligible-reviews
version: '1'
outcome:
events:
- event:
Expand Down
3 changes: 3 additions & 0 deletions examples/manifests/acme-reviews/ui.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,7 @@ metadata:
namespace: acme-reviews
spec:
doc: 'Reviewer-facing: rate and comment on a delivered order.'
metadata:
- '@type': type.googleapis.com/trogonatlas.eventmodel.v1alpha1.MixedSurfaceAnnotation
doc: The form is prefilled from the eligible-reviews row it submits against, so one surface both displays the order and takes the review.
title: Review form
19 changes: 19 additions & 0 deletions examples/manifests/acme-storefront/command.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,19 @@ spec:
- name: cart_id
type:
uuid: {}
- name: buyer_id
type:
uuid: {}
- name: seller_id
type:
uuid: {}
- metadata:
- '@type': type.googleapis.com/trogonatlas.eventmodel.v1alpha1.DerivedFieldAnnotation
doc: now() when the order is recorded
kind: KIND_TIMESTAMP
name: placed_at
type:
timestamp: {}
swimlane:
id:
namespace: acme-storefront
Expand All @@ -60,6 +73,12 @@ spec:
- name: order_id
type:
uuid: {}
- name: buyer_id
type:
uuid: {}
- name: seller_id
type:
uuid: {}
- metadata:
- '@type': type.googleapis.com/trogonatlas.eventmodel.v1alpha1.DerivedFieldAnnotation
doc: now() when the worker records the outcome
Expand Down
5 changes: 5 additions & 0 deletions examples/manifests/acme-storefront/domain.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,9 @@ metadata:
namespace: acme-storefront
spec:
doc: Buying, paying for, and fulfilling physical goods sold through the Acme storefront.
project:
id:
namespace: acme-storefront
slug: acme-storefront
version: '1'
title: Retail Commerce
20 changes: 15 additions & 5 deletions examples/manifests/acme-storefront/event-model.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -186,11 +186,6 @@ spec:
slug: fulfillment-queue-rms
version: '1'
kind: ENTITY_KIND_READ_MODEL_SLICE
- id:
namespace: acme-storefront
slug: order-status-rms
version: '1'
kind: ENTITY_KIND_READ_MODEL_SLICE
- id:
namespace: acme-storefront
slug: capture-payment-auto
Expand All @@ -216,4 +211,19 @@ spec:
slug: fulfillment-flow
version: '1'
kind: ENTITY_KIND_STORYBOARD
- id:
namespace: acme-storefront
slug: order-status-placed-rms
version: '1'
kind: ENTITY_KIND_READ_MODEL_SLICE
- id:
namespace: acme-storefront
slug: order-status-paid-rms
version: '1'
kind: ENTITY_KIND_READ_MODEL_SLICE
- id:
namespace: acme-storefront
slug: order-status-fulfilled-rms
version: '1'
kind: ENTITY_KIND_READ_MODEL_SLICE
title: Acme Storefront
Loading
Loading