This commit is contained in:
+78
@@ -0,0 +1,78 @@
|
||||
# Contract Design — Questions
|
||||
|
||||
> Fill in each `[Answer]:` tag. Options A-E plus X (Other). The file is the
|
||||
> authoritative record of your contract decisions.
|
||||
|
||||
## Q1: Contract representation
|
||||
|
||||
How should the inter-unit contracts (Generator ↔ AiDraft, Generator ↔ Funnies, and the DraftBundle handoff) be specified?
|
||||
|
||||
A) Shared-schema contracts in Python (data classes / pydantic-style models) that live with the producer unit and are referenced by consumers — ideal for a local library-backed tool (recommended)
|
||||
B) A standalone JSON-schema file per contract, imported by all units
|
||||
C) Formal OpenAPI/AsyncAPI specs — heavier than a local tool needs
|
||||
X) Other (please specify)
|
||||
|
||||
[Answer]: A
|
||||
|
||||
## Q2: Integration mechanism
|
||||
|
||||
How do the units call each other at the library boundary?
|
||||
|
||||
A) Direct module/function calls (the `newspaper` CLI imports the ai-draft and funnies libraries and calls their entry functions in-process) (recommended)
|
||||
B) Each unit is a separate process, communicating via JSON over a pipe/CLI
|
||||
C) A message/event bus between units
|
||||
X) Other (please specify)
|
||||
|
||||
[Answer]: A
|
||||
|
||||
## Q3: Contract ownership
|
||||
|
||||
Who owns each contract spec?
|
||||
|
||||
A) Each producer unit owns the contract for data it emits — AiDraft owns the DraftBundle shape, Funnies owns the puzzle/cartoon output shape, Generator owns content/config input shapes (recommended)
|
||||
B) A shared "contracts" home owned jointly
|
||||
C) The CLI (Generator) owns all contracts centrally
|
||||
X) Other (please specify)
|
||||
|
||||
[Answer]: A
|
||||
|
||||
## Q4: Error / timeout / fallback behaviour
|
||||
|
||||
How should the boundaries handle failure (e.g. Ollama unavailable, Funnies finds no cartoon, missing content)?
|
||||
|
||||
A) Fail gracefully: each unit returns a clear error/fallback (Ollama down → skip drafting with a message; no cartoon → tasteful placeholder; missing content → guided empty state), never a crash or blank page (recommended)
|
||||
B) Hard-fail loudly with a clear message and non-zero exit
|
||||
C) Best-effort only — ignore failures silently
|
||||
X) Other (please specify)
|
||||
|
||||
[Answer]: A
|
||||
|
||||
## Q5: Versioning policy
|
||||
|
||||
How should internal contract changes be handled (this is a one-shot local tool)?
|
||||
|
||||
A) No formal versioning — additive, backward-compatible changes only within/after a build; breaking changes are re-generated in one go (recommended)
|
||||
B) Semantic versioning of the library API, since content/consumers could persist between runs
|
||||
C) Locked/immutable — contracts cannot change after review
|
||||
X) Other (please specify)
|
||||
|
||||
[Answer]: A
|
||||
|
||||
## Consolidated Summary Confirmation
|
||||
|
||||
> Summary of your five contract answers before the contract summary is generated:
|
||||
>
|
||||
> - Contract representation: **shared-schema contracts in Python** (data classes / pydantic-style), referenced by consumers (Q1=A)
|
||||
> - Integration mechanism: **in-process module/function calls** — the `newspaper` CLI imports the ai-draft and funnies libraries (Q2=A)
|
||||
> - Contract ownership: **each producer owns its contract** — AiDraft owns the DraftBundle shape, Funnies the puzzle/cartoon output, Generator the content/config input shapes (Q3=A)
|
||||
> - Error/fallback: **fail gracefully** — Ollama down → skip draft with a message, no cartoon → tasteful placeholder, missing content → guided empty state; never crash or blank (Q4=A)
|
||||
> - Versioning: **no formal versioning** — additive backward-compatible changes; breaking changes regenerated in one go (Q5=A)
|
||||
>
|
||||
> Human pre-approved this summary (answers recorded via the file, self-guided mode).
|
||||
|
||||
Does this all look correct before I generate the contract summary?
|
||||
|
||||
- Looks correct
|
||||
- Request changes
|
||||
|
||||
[Answer]: Looks correct
|
||||
+197
@@ -0,0 +1,197 @@
|
||||
# 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 | — | — |
|
||||
|
||||
<!-- contract-summary-after-confirm -->
|
||||
@@ -0,0 +1,17 @@
|
||||
<!-- INVARIANT: examples are single-line HTML comments so a fresh template parses to total=0 (MEMORY_EMPTY). Do NOT un-comment or split across lines. t100 guards this. -->
|
||||
> This file is kept up to date automatically while the stage runs. Add observations at the review step, not by editing here directly.
|
||||
|
||||
## Interpretations
|
||||
<!-- example: 2026-05-29T10:14:32Z — chose REST over GraphQL; the consuming team only needs CRUD, revisit if subscriptions land -->
|
||||
|
||||
## Deviations
|
||||
<!-- example: 2026-05-29T10:14:32Z — skipped the optional caching layer the stage prose suggested; the dataset is small enough that it adds risk -->
|
||||
|
||||
## Tradeoffs
|
||||
<!-- example: 2026-05-29T10:14:32Z — picked TDD over BDD this run; the team is unit-first and the domain is well-understood -->
|
||||
|
||||
## Open questions
|
||||
<!-- example: 2026-05-29T10:14:32Z — confirm the retention window with compliance before the next stage hardens the schema -->
|
||||
2026-09-13T12:20:00Z — Interpretation — Confirmed contract design (all A, self-guided + user auto-approved summary): shared-schema Python contracts, in-process calls, producer-owned specs, graceful failure, no formal versioning
|
||||
2026-09-13T12:20:00Z — Interpretation — All boundaries are internal library interfaces for a one-shot local tool; no public/external API boundaries
|
||||
2026-09-13T12:30:00Z — Interpretation — contract-design review R-01 settled by human: deepseek-v4-flash:cloud is a CLOUD model explicitly sanctioned (like the internet-pull exception, faster than local). It is the one approved off-machine call at generation time; the emitted newspaper.html stays zero-network. Contract 3 annotated accordingly.
|
||||
Reference in New Issue
Block a user