# Contract Summary — Wedding Newspaper Generator > Contracts across the unit boundaries, per confirmed decisions (Q1–Q5 all = A): > shared-schema contracts in Python (data classes / pydantic-style), in-process > module/function calls, producer-owned specs, graceful failure with clear > fallbacks, no formal versioning. All boundaries are internal library > interfaces for a one-shot local tool — there are no public/external API > boundaries (nothing is served or deployed). ## Contracts table | # | Provider Unit | Consumer | Mechanism | Owner | |---|---|---|---|---| | 1 | AiDraft (U2) | Generator (U1) | in-process call → returns Draft | AiDraft | | 2 | Funnies (U3) | Generator (U1) | in-process call → returns FunniesResult | Funnies | | 3 | Generator (U1) | AiDraft (U2) | in-process call → DraftBrief (input contract) | Generator | | 4 | Generator (U1) | Funnies (U3) | in-process call → FunniesBrief (input contract) | Generator | | 5 | Generator (U1) | Review page (external-consumer of DraftBundle) | file handoff → DraftBundle JSON read via native file picker | Generator | ## Per-contract spec Contracts are shared-schema blocks in Python (pydantic-style dataclasses lived next to the producer). The shapes below document the agreed payloads. ### Contract 1 — Draft (AiDraft → Generator) Provider: AiDraft. Data emitted after a local-Ollama draft, consumed by Generator for the per-article review handoff. ```yaml shared-schema: draft producer: AiDraft version: 1 fields: - name: articles type: list[DraftArticle] required: true - name: generatedAt type: datetime required: true sub-shape: DraftArticle fields: - name: articleId type: string required: true - name: type type: string # lead | article | filler | wellwish | ... required: true - name: headline type: string required: true - name: byline type: string default: "" - name: body type: string required: true - name: section type: string required: false ``` ### Contract 2 — FunniesResult (Funnies → Generator) Provider: Funnies. Emits the content-derived puzzle + cartoon output, consumed by Generator for the funnies section. ```yaml shared-schema: funnies-result producer: Funnies version: 1 fields: - name: crossword type: Crossword | null default: null - name: findAWord type: FindAWord | null default: null - name: comics type: list[Comic] default: [] sub-shape: Crossword fields: - name: grid type: list[list[str]] - name: cluesAcross type: list[str] - name: cluesDown type: list[str] - name: solution type: list[list[str]] sub-shape: FindAWord fields: - name: words type: list[str] - name: grid type: list[list[str]] sub-shape: Comic fields: - name: id type: string - name: art type: string # local/bundled asset URI or data - name: dialogue type: list[str] ``` ### Contract 3 — DraftBrief (Generator → AiDraft) Provider: Generator. The input the CLI passes to AiDraft to request a draft. ```yaml shared-schema: draft-brief producer: Generator version: 1 fields: - name: contentContext type: string # content-derived brief for the lead/fillers - name: model type: string # granted model deepseek-v4-flash:cloud (CLOUD, sanctioned) default: deepseek-v4-flash:cloud - name: requestedArticleTypes type: list[string] default: [lead, filler] ``` > **Model note (human ruling 2026-09-13):** `deepseek-v4-flash:cloud` may be a > **cloud** model and is explicitly sanctioned, treated like the internet-pull > exception for content enrichment. It is faster than a local model and is > approved for AI drafting at generation time. This is the one sanctioned > off-machine call in the pipeline; the EMITTED `newspaper.html` remains > self-contained and zero-network (NFR4). ### Contract 4 — FunniesBrief (Generator → Funnies) Provider: Generator. The content-derived context Funnies uses to build puzzles + select/embed a cartoon. ```yaml shared-schema: funnies-brief producer: Generator version: 1 fields: - name: themeWords type: list[str] # derived from content - name: context type: string # couple/occasion context for cartoon search - name: cartoonSource type: string # internet-enrich allowed (human ruling) — fetch at build time, embed locally default: internet-allow ``` ### Contract 5 — DraftBundle JSON (Generator → Review page) Provider: Generator. The on-disk JSON written by the CLI and read by the per-article review page via the native file picker (NFR5, ADR-004). ```yaml shared-schema: draft-bundle producer: Generator version: 1 format: json-file fields: - name: articles type: list[DraftArticle] required: true - name: generatedAt type: datetime required: true consumed-by: - role: review-page mechanism: native file picker (user-granted read), never fetch() of a sibling file ``` ## Contract ownership rules - **Owner of each spec** is its producer unit (Q3=A): the owning library holds the Python dataclass/schema and version. - **Consumers reference** the producer-owned shape; they never re-declare a competing copy. - **Additive, backward-compatible changes only** within normal builds (Q5=A): consumers ignore unknown fields. Breaking changes to a contract are agreed and regenerated in one pass for this self-contained tool (no external consumers to co-ordinate). - **Failure contract (Q4=A):** grace, never crash. Ollama unreachable → AiDraft returns an empty/no-draft result with a message, Generator proceeds without AI. No cartoon found → Funnies returns a tasteful content-derived placeholder, not a remote fetch. Missing content → Generator surfaces the guided empty state (US10). Every boundary returns a clear, typed result; the CLI never exits on a blank page. ## Open questions | Contract | Question | Blocks | |---|---|---| | 1 (Draft) | Exact per-article type taxonomy (lead/article/filler/wellwish) — confirm at Functional Design | AiDraft, Generator | | 5 (DraftBundle) | Whether the review page expects a pure-JSON vs schema-hint wrapper | Generator (review page) | | None blocking | — | — |