# C4 review checklist The valuable diagram is not the most detailed one. It is the smallest view that lets its audience inspect a decision. Use this as a review instrument: every comment must point at a driver, a workflow, or a risk. Source: lesson 0044 · Draw the Context, Then Choose Boundaries. --- ## Blank ### Per-view header — every diagram carries these | Field | One-line guidance | | --- | --- | | Title | What it shows, in the audience's language. | | Scope | System, container, or component; and which part. | | Audience | Who is reading it and what they must decide. | | Version / date | Diagrams rot silently. Date them. | | Legend | Say what a box, a dashed line, and a color mean. | | Drivers addressed | Which numbered drivers this view answers. | ### The views | View | Must show | Review question | | --- | --- | --- | | System context | People, the system in scope, external systems, trust boundaries | What is ours, and where does uncontrolled behavior enter? | | Container | Deployables, data stores, interactions, protocols, owners | Which driver justifies each operational boundary? | | Dynamic | Ordered interactions for one critical happy or failure path | Where can work, identity, or truth be lost or duplicated? | | Module / dependency | Model boundaries and allowed arrows inside a deployable | Can the likely change stay local, and is the rule enforced? | ### Checklist **Context** - [ ] Every external system names its owning team or vendor. - [ ] Every trust boundary is drawn, including the one between your system and your own other systems. - [ ] Every actor is a role someone actually performs, including operational roles. - [ ] PII crossings are marked. **Container** - [ ] Every arrow is labeled with the interaction *and* the protocol. Unlabeled arrows conceal assumptions. - [ ] Every data store names its single authoritative writer. - [ ] Every deployable has an owner and an on-call route. - [ ] For each deployable beyond the first, complete this sentence: "We accept network failure, versioning, deployment, observability, security, and on-call overhead because independent `[scaling / isolation / availability / release cadence / technology / ownership]` is required by driver `[n]`." If the blank says "cleanliness" or "maybe later", keep a module seam. **Dynamic** - [ ] The chosen path is the risky one, not the easy one. - [ ] At least one failure path is drawn, not only the happy path. - [ ] Timeouts, retries, and their idempotency keys are visible. - [ ] It is clear which step is the point of no return for external side effects. **Module / dependency** - [ ] Boundaries follow evidence — language, change cadence, invariants, or ownership — not nouns from the brief. - [ ] The dependency direction is stated and enforceable by a test, not only by intent. - [ ] Technology sits at the edges: the domain-facing interface says `create_label(intent)`, not `carrier_sdk.shipments.post(payload)`. - [ ] Every box on this diagram would survive the question "which likely change does this contain?" **Whole packet** - [ ] Every driver has a visible response somewhere in the packet. - [ ] Every box and arrow has a justification; delete the rest. - [ ] No diagram tries to serve two audiences at once. ### Diagram smells | You see | What it hides | The move | | --- | --- | --- | | One diagram with 70 boxes | The audience and the decision | Split by scope and question | | Arrows without labels | Protocol, direction, and meaning | Name every interaction | | A database shared by "services" | State ownership is fictional | Name the writer, or merge the boundaries | | A service per domain noun | Deployment cost with no driver | Go back to the workflow and the change cases | --- ## Completed — returns resolution pilot ### Packet contents | View | Elements | Decision it exposes | Drivers | | --- | --- | --- | --- | | Context | Customer, support agent, returns system, order platform, carrier, payments, warehouse system — each external system labeled with what it is *authoritative* for | Scope, actors, external ownership, and where personal data crosses a trust boundary | D3 | | Container | Existing web app, returns module inside the existing storefront deployable, the storefront database with a new `returns` schema, a label worker process, a daily reconciliation job | One deployable, three processes, one schema. No returns service, no message broker, no second database | D1, D2, D4 | | Dynamic A | Submit → order read → in-process eligibility → one transaction (return + decision + label intent + outbox) → response → worker → carrier → notification → receipt → refund request → settlement | Where work, identity, or truth can be lost or duplicated | D2 | | Dynamic B | The carrier-timeout path, including the disable branch and the four-hour outage fallback | What the customer sees and what on-call does | D1, D5 | | Module | eligibility, return_journey, adapters, ports, api — with the allowed import arrows | Whether a weekly policy change can stay inside one module | D4 | The three "authoritative:" labels on the context view are the most load-bearing text in the packet. They say the returns system holds no independent opinion about order truth, settled money, or whether a parcel arrived — which is what makes the state machine enforceable rather than aspirational. ### The review, run with the driver table on the wall | Finding | Type | Action | | --- | --- | --- | | An unlabeled arrow from the worker to the carrier | Arrow smell | Labeled "create label · HTTPS/JSON · client reference = label intent id · 5 s timeout". The timeout was news to two people in the room | | The first draft drew a separate "Returns Service" deployable | A boundary with no driver | The extraction sentence could not be completed: no driver required independent scaling, availability, release cadence, or ownership. Reverted to modules inside the existing deployable, and the accepted cost — no independent scaling or release of eligibility — was recorded in **ADR 0001** | | The dynamic view showed only the happy path | Missing failure | Added the carrier-timeout view. Drawing it is what made the duplicate-label problem visible on paper rather than in production | | A proposed message broker between the API and the worker | Mechanism without a driver | Removed. One database transaction already gives all-or-nothing across return, decision, and intent; a broker would have added a distributed failure mode to buy nothing. The outbox row in the same schema does the job | | An arrow from `eligibility` to the carrier adapter | Dependency direction | Removed. Eligibility decides and performs no I/O; the journey attaches labels. The build now fails if that import returns | | The privacy driver had no visible response anywhere | Driver with no response | Added the personal-data column to the context view, which is where the team noticed that the carrier payload carried the return reason. It now carries recipient name and address only | ### The service-extraction sentence, completed honestly > "We accept network failure, versioning, deployment, observability, security, and on-call > overhead because independent ___ is required by driver ___." No completion of that sentence was true for the pilot, so the returns capability stayed inside the existing deployable. The one boundary that *was* justified is smaller than a service: the label worker is a **separate process in the same artifact, sharing the same schema**, because D1 requires the carrier call to survive a 30-minute outage without blocking request acceptance. That is the smallest boundary that satisfies the driver, and no network hop was introduced where a transaction would do. ### The module rule, enforced rather than documented | Module | May import | May not import | Why | | --- | --- | --- | --- | | `eligibility` | nothing in the module tree | `return_journey`, any adapter, any vendor SDK | D4: a policy change must not require touching an adapter. A pure decision function is also the cheapest thing to test at the 200-case scale the A1 sample produced | | `return_journey` | `eligibility`, ports | adapter implementations, vendor SDKs | It depends on vocabulary, not on the vendor | | `adapters` | ports, vendor SDKs | `eligibility` | Translation lives at the edge, one direction only | | `api` | `return_journey`, ports | `eligibility` directly | One entry point per capability; the API cannot bypass the journey's guards | The table is a build check, not a README. That is the difference between D4 being a checkable property and being a hope. **Traces to:** the boundaries come from `workflow-and-eventstorming-board.md`; the worker process is ADR 0003 in `option-table-spike-brief-and-adr.md`; the import rule is driver D4's evidence mechanism in `architecture-driver-table.md`.