skilldrop-cli 0.16.35 → 0.16.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/agents/README.md +4 -1
- package/agents/docs-reviewer.md +47 -0
- package/agents/finding-adjudicator.md +49 -0
- package/agents/test-reviewer.md +52 -0
- package/catalogue.json +17 -7
- package/model-routing.json +40 -0
- package/package.json +1 -1
- package/packs/ai-engineering/skills/agent-config-review/SKILL.md +116 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/eval_queries.json +12 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/evals.json +75 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/1/.claude/agents/db-migrator.md +8 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/1/.claude/agents/reviewer.md +9 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/1/.claude/settings.json +48 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/1/.claude/settings.local.json +5 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/1/.mcp.json +22 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/1/package.json +12 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/2/.cursor/mcp.json +17 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/2/config.toml +21 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/4/my-user-settings.json +11 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/4/quillstack/.claude/agents/release-notes.md +13 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/4/quillstack/.claude/settings.json +39 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/4/quillstack/.mcp.json +18 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/4/quillstack/package.json +14 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/5/.github/workflows/codex-triage.yml +29 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/5/AGENTS.md +16 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/5/codex/config.toml +34 -0
- package/packs/ai-engineering/skills/agent-config-review/examples/inputs/tidewater-ingest/.claude/settings.json +22 -0
- package/packs/ai-engineering/skills/agent-config-review/examples/inputs/tidewater-ingest/.mcp.json +9 -0
- package/packs/ai-engineering/skills/agent-config-review/examples/python-repo-tighten.md +81 -0
- package/packs/ai-engineering/skills/agent-config-review/manifest.json +60 -0
- package/packs/ai-engineering/skills/agent-config-review/reference.md +205 -0
- package/packs/ai-engineering/skills/agent-config-review/scripts/agent_config_scan.py +989 -0
- package/packs/ai-engineering/skills/agent-incident-response/SKILL.md +175 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/eval_queries.json +14 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/evals.json +107 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/1/agent-session-excerpt.txt +11 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/1/ci-build-log.txt +24 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/1/incident-notes.md +19 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/2/mcp-config.json +20 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/2/session-transcript.txt +8 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/4/agent-session.log +70 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/4/infra/staging-db.tf +25 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/5/jobs/nightly-triage/job.yaml +19 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/5/jobs/nightly-triage/run.log +13 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/5/usage-export-2026-10-20.csv +13 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/6/.mcp.json +20 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/6/git-log.txt +12 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/6/notesync-excerpt.txt +30 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/6/notesync.log +15 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/6/ssh-config.txt +16 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/7/.github/workflows/ci.yml +28 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/7/SECURITY.md +8 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/7/issue-479.md +35 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/7/package.json +15 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/7/pr-482.diff +20 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/7/run-log.txt +30 -0
- package/packs/ai-engineering/skills/agent-incident-response/examples/injected-issue-comment.md +57 -0
- package/packs/ai-engineering/skills/agent-incident-response/manifest.json +55 -0
- package/packs/ai-engineering/skills/agent-incident-response/reference.md +152 -0
- package/packs/ai-engineering/skills/agent-incident-response/scripts/find_secret_exposure.py +239 -0
- package/packs/ai-engineering/skills/agent-incident-response/scripts/usage_split.py +194 -0
- package/packs/converters/skills/file-to-markdown/SKILL.md +2 -1
- package/packs/converters/skills/file-to-markdown/evals/eval_queries.json +4 -0
- package/packs/converters/skills/file-to-markdown/manifest.json +2 -2
- package/packs/converters/skills/file-to-markdown/scripts/to_markdown.py +253 -3
- package/packs/converters/skills/md-to-confluence/SKILL.md +139 -0
- package/packs/converters/skills/md-to-confluence/evals/eval_queries.json +42 -0
- package/packs/converters/skills/md-to-confluence/evals/evals.json +89 -0
- package/packs/converters/skills/md-to-confluence/evals/files/1/img/latency-dashboard.png +0 -0
- package/packs/converters/skills/md-to-confluence/evals/files/1/payments-runbook.md +60 -0
- package/packs/converters/skills/md-to-confluence/evals/files/2/deploy-checklist.md +33 -0
- package/packs/converters/skills/md-to-confluence/evals/files/3/img/after/screen.png +0 -0
- package/packs/converters/skills/md-to-confluence/evals/files/3/img/before/screen.png +0 -0
- package/packs/converters/skills/md-to-confluence/evals/files/3/release-notes-4.2.md +49 -0
- package/packs/converters/skills/md-to-confluence/evals/files/4/docs/img/topology.png +0 -0
- package/packs/converters/skills/md-to-confluence/evals/files/4/docs/ingest-pipeline-design.md +65 -0
- package/packs/converters/skills/md-to-confluence/evals/files/5/support/troubleshooting-sso.md +57 -0
- package/packs/converters/skills/md-to-confluence/examples/inputs/img/queue-depth.png +0 -0
- package/packs/converters/skills/md-to-confluence/examples/inputs/queue-runbook.fixed.md +21 -0
- package/packs/converters/skills/md-to-confluence/examples/inputs/queue-runbook.md +21 -0
- package/packs/converters/skills/md-to-confluence/examples/queue-runbook.md +57 -0
- package/packs/converters/skills/md-to-confluence/manifest.json +52 -0
- package/packs/converters/skills/md-to-confluence/reference.md +92 -0
- package/packs/converters/skills/md-to-confluence/scripts/md_to_confluence.py +821 -0
- package/packs/converters/skills/md-to-pptx/SKILL.md +131 -0
- package/packs/converters/skills/md-to-pptx/evals/eval_queries.json +12 -0
- package/packs/converters/skills/md-to-pptx/evals/evals.json +84 -0
- package/packs/converters/skills/md-to-pptx/evals/files/1/img/defect-burndown.png +0 -0
- package/packs/converters/skills/md-to-pptx/evals/files/1/status-report.md +75 -0
- package/packs/converters/skills/md-to-pptx/evals/files/3/brand.json +12 -0
- package/packs/converters/skills/md-to-pptx/evals/files/3/night-shift-induction.md +53 -0
- package/packs/converters/skills/md-to-pptx/evals/files/4/charts/ontime.png +0 -0
- package/packs/converters/skills/md-to-pptx/evals/files/4/qbr-q3.md +85 -0
- package/packs/converters/skills/md-to-pptx/evals/files/5/vendor-sync-notes.md +28 -0
- package/packs/converters/skills/md-to-pptx/examples/support-weekly-input.md +48 -0
- package/packs/converters/skills/md-to-pptx/examples/support-weekly.md +75 -0
- package/packs/converters/skills/md-to-pptx/examples/tickets-by-week.png +0 -0
- package/packs/converters/skills/md-to-pptx/manifest.json +49 -0
- package/packs/converters/skills/md-to-pptx/reference.md +139 -0
- package/packs/converters/skills/md-to-pptx/scripts/md_to_pptx.py +1841 -0
- package/packs/dev-team/loops/modernize/LOOP.md +124 -0
- package/packs/dev-team/loops/modernize/evals/eval_queries.json +9 -0
- package/packs/dev-team/loops/modernize/evals/evals.json +27 -0
- package/packs/dev-team/loops/modernize/loop.json +60 -0
- package/packs/dev-team/skills/behaviour-equivalence/SKILL.md +155 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/eval_queries.json +11 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/evals.json +76 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/1/cases.jsonl +12 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/1/new.jsonl +12 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/1/notes.md +15 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/1/old.jsonl +12 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/1/old_rerun.jsonl +12 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/1/pricing_v1.py +45 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/1/pricing_v2.py +65 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/1/rules.json +10 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/3/diff-summary.txt +16 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/4/diff.txt +13 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/4/fee_calc_py2.py +16 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/4/fee_calc_py3.py +24 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/4/input.csv +13 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/4/new_output.csv +13 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/4/old_output.csv +13 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/5/TEAM_NOTES.md +19 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/5/normalisation.yaml +12 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/5/recordings/new_run.jsonl +8 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/5/recordings/old_run1.jsonl +8 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/5/recordings/old_run2.jsonl +8 -0
- package/packs/dev-team/skills/behaviour-equivalence/examples/delivery-date.md +78 -0
- package/packs/dev-team/skills/behaviour-equivalence/manifest.json +33 -0
- package/packs/dev-team/skills/behaviour-equivalence/reference.md +97 -0
- package/packs/dev-team/skills/behaviour-equivalence/scripts/golden_diff.py +524 -0
- package/packs/dev-team/skills/behaviour-equivalence/templates/accepted.json +10 -0
- package/packs/dev-team/skills/behaviour-equivalence/templates/rules.json +7 -0
- package/packs/frontend/pack.json +55 -0
- package/packs/frontend/skills/rendering-strategy/SKILL.md +149 -0
- package/packs/frontend/skills/rendering-strategy/evals/eval_queries.json +38 -0
- package/packs/frontend/skills/rendering-strategy/evals/evals.json +77 -0
- package/packs/frontend/skills/rendering-strategy/evals/files/1/fernhill-app.md +40 -0
- package/packs/frontend/skills/rendering-strategy/evals/files/3/routes.md +33 -0
- package/packs/frontend/skills/rendering-strategy/evals/files/4/build-notes.txt +5 -0
- package/packs/frontend/skills/rendering-strategy/evals/files/4/nuxt.config.ts +19 -0
- package/packs/frontend/skills/rendering-strategy/evals/files/4/pages/recipes/[slug].vue +37 -0
- package/packs/frontend/skills/rendering-strategy/evals/files/4/routes.md +15 -0
- package/packs/frontend/skills/rendering-strategy/evals/files/5/routes.txt +18 -0
- package/packs/frontend/skills/rendering-strategy/evals/files/5/src/hooks.server.ts +14 -0
- package/packs/frontend/skills/rendering-strategy/evals/files/5/src/routes/(marketing)/pricing/+page.server.ts +10 -0
- package/packs/frontend/skills/rendering-strategy/evals/files/5/src/routes/+layout.server.ts +10 -0
- package/packs/frontend/skills/rendering-strategy/evals/files/5/svelte.config.js +10 -0
- package/packs/frontend/skills/rendering-strategy/evals/files/5/traffic.md +18 -0
- package/packs/frontend/skills/rendering-strategy/examples/saas-site.md +42 -0
- package/packs/frontend/skills/rendering-strategy/manifest.json +50 -0
- package/packs/frontend/skills/rendering-strategy/reference.md +116 -0
- package/packs/frontend/skills/rendering-strategy/scripts/render_load.py +181 -0
- package/packs/frontend/skills/web-performance-review/SKILL.md +106 -0
- package/packs/frontend/skills/web-performance-review/evals/eval_queries.json +38 -0
- package/packs/frontend/skills/web-performance-review/evals/evals.json +76 -0
- package/packs/frontend/skills/web-performance-review/evals/files/1/psi.json +1065 -0
- package/packs/frontend/skills/web-performance-review/evals/files/2/after.json +359 -0
- package/packs/frontend/skills/web-performance-review/evals/files/2/before.json +375 -0
- package/packs/frontend/skills/web-performance-review/evals/files/4/psi-pdp-mobile-2026-10-19.json +171 -0
- package/packs/frontend/skills/web-performance-review/evals/files/4/rum-inp-attribution-oct14-19.csv +6 -0
- package/packs/frontend/skills/web-performance-review/evals/files/4/rum-inp-daily.csv +15 -0
- package/packs/frontend/skills/web-performance-review/evals/files/5/crux-home-phone.json +35 -0
- package/packs/frontend/skills/web-performance-review/evals/files/5/lighthouse-home-mobile.json +173 -0
- package/packs/frontend/skills/web-performance-review/examples/quayside-article-lhr.json +480 -0
- package/packs/frontend/skills/web-performance-review/examples/quayside-article.md +103 -0
- package/packs/frontend/skills/web-performance-review/manifest.json +48 -0
- package/packs/frontend/skills/web-performance-review/reference.md +105 -0
- package/packs/frontend/skills/web-performance-review/scripts/lh_summary.py +743 -0
- package/packs/product-manager/skills/lean-canvas/SKILL.md +162 -0
- package/packs/product-manager/skills/lean-canvas/evals/eval_queries.json +42 -0
- package/packs/product-manager/skills/lean-canvas/evals/evals.json +82 -0
- package/packs/product-manager/skills/lean-canvas/evals/files/1/pawpass-notes.md +54 -0
- package/packs/product-manager/skills/lean-canvas/evals/files/3/lunchloop-pitch.md +30 -0
- package/packs/product-manager/skills/lean-canvas/evals/files/4/clubkit-notes.md +44 -0
- package/packs/product-manager/skills/lean-canvas/evals/files/5/cost-model.csv +5 -0
- package/packs/product-manager/skills/lean-canvas/evals/files/5/proposal.md +40 -0
- package/packs/product-manager/skills/lean-canvas/evals/files/5/stakeholder-emails.txt +18 -0
- package/packs/product-manager/skills/lean-canvas/examples/bike-repair.md +72 -0
- package/packs/product-manager/skills/lean-canvas/manifest.json +34 -0
- package/packs/product-manager/skills/lean-canvas/reference.md +102 -0
- package/packs/product-manager/skills/lean-canvas/scripts/unit_economics.py +211 -0
- package/packs/solution-architect/skills/api-contract-draft/SKILL.md +2 -2
- package/packs/solution-architect/skills/api-contract-draft/manifest.json +28 -6
- package/packs/solution-architect/skills/event-contract-draft/SKILL.md +167 -0
- package/packs/solution-architect/skills/event-contract-draft/evals/eval_queries.json +38 -0
- package/packs/solution-architect/skills/event-contract-draft/evals/evals.json +76 -0
- package/packs/solution-architect/skills/event-contract-draft/evals/files/1/order-events-brief.md +45 -0
- package/packs/solution-architect/skills/event-contract-draft/evals/files/4/NOTES.md +23 -0
- package/packs/solution-architect/skills/event-contract-draft/evals/files/4/schemas/shipment-update.json +40 -0
- package/packs/solution-architect/skills/event-contract-draft/evals/files/5/invoice.example.json +25 -0
- package/packs/solution-architect/skills/event-contract-draft/examples/order-service.asyncapi.json +246 -0
- package/packs/solution-architect/skills/event-contract-draft/examples/order-service.md +92 -0
- package/packs/solution-architect/skills/event-contract-draft/manifest.json +25 -0
- package/packs/solution-architect/skills/event-contract-draft/reference.md +113 -0
- package/packs/solution-architect/skills/event-contract-draft/scripts/check_asyncapi.py +502 -0
- package/packs/solution-architect/skills/event-contract-draft/templates/asyncapi-skeleton.json +144 -0
- package/packs/sre-oncall/skills/slo-spec/SKILL.md +161 -0
- package/packs/sre-oncall/skills/slo-spec/evals/eval_queries.json +38 -0
- package/packs/sre-oncall/skills/slo-spec/evals/evals.json +75 -0
- package/packs/sre-oncall/skills/slo-spec/evals/files/1/order-service.md +51 -0
- package/packs/sre-oncall/skills/slo-spec/evals/files/3/search-slo-status.md +30 -0
- package/packs/sre-oncall/skills/slo-spec/evals/files/4/alerts/payments-api.rules.yaml +16 -0
- package/packs/sre-oncall/skills/slo-spec/evals/files/4/docs/dependencies.md +8 -0
- package/packs/sre-oncall/skills/slo-spec/evals/files/4/metrics/summary-90d.md +51 -0
- package/packs/sre-oncall/skills/slo-spec/evals/files/5/ops-notes.md +34 -0
- package/packs/sre-oncall/skills/slo-spec/examples/ticket-booking.md +88 -0
- package/packs/sre-oncall/skills/slo-spec/manifest.json +34 -0
- package/packs/sre-oncall/skills/slo-spec/reference.md +113 -0
- package/packs/sre-oncall/skills/slo-spec/scripts/slo_math.py +449 -0
- package/packs/sre-oncall/skills/slo-spec/templates/slo-spec.md +35 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"skill_name": "event-contract-draft",
|
|
3
|
+
"evals": [
|
|
4
|
+
{
|
|
5
|
+
"id": 1,
|
|
6
|
+
"prompt": "We're moving fulfilment, loyalty and analytics off polling our order API and onto Kafka events. Our brief is in order-events-brief.md. Can you draft the event contract so the three teams can start building?",
|
|
7
|
+
"assertions": [
|
|
8
|
+
"Does not publish an event named CreateShipment; renames it to a past-tense fact in the order domain such as OrderPaid, and explains that the warehouse decides to pick when it sees that fact",
|
|
9
|
+
"Declines the analytics request for one global order across all orders, keys the order events by order id, and says what ordering that gives (per order) and what it does not (across orders)",
|
|
10
|
+
"Puts the order events whose relative order matters (paid, cancelled) on the same channel with the same key",
|
|
11
|
+
"Drops customerEmail because no listed consumer needs it, and keeps the delivery address for fulfilment marked as personal data",
|
|
12
|
+
"Does not rename total to totalAmount in place on the live OrderPlaced event; ships the change as a new field beside the old one or a new version published alongside the old, with a deprecation date that is agreed with fulfilment rather than invented",
|
|
13
|
+
"Every message carries a required envelope: event id, event type, version, occurred-at in UTC RFC 3339, producer and correlation id; money is integer minor units, not 42.50",
|
|
14
|
+
"States at-least-once delivery and says how each consumer stays idempotent (for example dedupe on event id, or loyalty points keyed on order id)",
|
|
15
|
+
"States retention (7 days, from the brief), whether consumers can replay, and what goes to a dead-letter queue, and lists who watches it as open rather than inventing a team",
|
|
16
|
+
"Does not invent volumes, dates, team names or consumers not in the brief; anything assumed is marked as an assumption"
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"id": 2,
|
|
21
|
+
"prompt": "Quick one: we publish PaymentCaptured and PaymentRefunded to Kafka, keyed by paymentId. The ledger team says they need to see the refund after the capture for the same order, and an order can have several payments. Are we covered?",
|
|
22
|
+
"assertions": [
|
|
23
|
+
"Answers in the first lines: no, not for the order as a whole",
|
|
24
|
+
"Explains that Kafka orders only within a partition, so keying by paymentId orders one payment's events but gives no order across payments of the same order",
|
|
25
|
+
"Recommends keying by order id (or giving a per-order sequence the ledger can sort on) if the ledger needs order-level ordering, and notes that changing the key on a live topic is a breaking change",
|
|
26
|
+
"Says delivery is at-least-once and the ledger must deduplicate on an event id",
|
|
27
|
+
"Is a short answer, not a full AsyncAPI document",
|
|
28
|
+
"Does not invent partition counts, volumes or team details not in the question"
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"id": 3,
|
|
33
|
+
"prompt": "We're a small invoicing SaaS. Customers want a webhook when an invoice is paid so their accounting system can mark it settled. Product wants us to include the payer's full card number and email in the payload so they can reconcile. Draft the webhook contract.",
|
|
34
|
+
"assertions": [
|
|
35
|
+
"Refuses to put the card number on the webhook and says so in the first lines, offering a card reference or last four digits instead",
|
|
36
|
+
"Leaves out the payer's email or justifies it with a named consumer need and marks it as personal data",
|
|
37
|
+
"Names the event as a past-tense fact such as InvoicePaid, with an event id, type, version, occurred-at in UTC and producer in every delivery",
|
|
38
|
+
"States that delivery is at-least-once with retries and that receivers must deduplicate on the event id",
|
|
39
|
+
"States that webhooks have no ordering guarantee and gives receivers a way to handle out-of-order or stale deliveries",
|
|
40
|
+
"Specifies a signature over the payload with a timestamp so receivers can verify the sender and reject replays",
|
|
41
|
+
"States the retry schedule or its cap and what happens to deliveries that keep failing, without inventing a customer or vendor name",
|
|
42
|
+
"Additive-only versioning is stated, and receivers are told to ignore unknown fields"
|
|
43
|
+
]
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"id": 4,
|
|
47
|
+
"held_out": "2026-10-20",
|
|
48
|
+
"prompt": "We're the tracking-core team at Parcelway. Our draft message schema for the `shipments` Kafka topic is in schemas/shipment-update.json and our working notes (consumers, incidents, plans) are in NOTES.md. Three teams consume it: notify-svc, analytics and billing. Before we lock it down for peak season, can you turn this into a proper contract the consumers can sign off on — an AsyncAPI doc plus whatever short write-up of the decisions they'll need — and tell us what's wrong with the current draft?",
|
|
49
|
+
"assertions": [
|
|
50
|
+
"Produces an AsyncAPI 3.0 document (asyncapi: 3.0.0, with channels and operations for the `shipments` topic and message schemas) plus a short decisions page, and also lists what is wrong with the current draft as asked.",
|
|
51
|
+
"Replaces the single imperative `UpdateShipment` / `action` enum with separate past-tense facts about the shipment, and specifically flags SEND_DELIVERY_SMS as a command to notify-svc rather than something that happened, removing it from the producer's contract.",
|
|
52
|
+
"Adds a required envelope with a unique event id and an unambiguous UTC (ISO 8601) occurrence time replacing the depot-local `dd/MM/yyyy HH:mm` timestamp, and ties the ~3,000 duplicate 'out for delivery' SMS after the rebalance to at-least-once redelivery, telling notify-svc and billing to deduplicate on the event id (billing must not charge or remit codAmount twice).",
|
|
53
|
+
"Changes the partition key from `carrierId` to `shipmentId`, explains that this gives ordering per shipment only (none across shipments or carriers), connects it to 'delivered' arriving before 'out_for_delivery', and warns that the planned 12 -> 48 partition increase in November remaps keys to partitions so per-shipment ordering can break around the change.",
|
|
54
|
+
"Applies data minimisation: drops `recipient.dateOfBirth` (and questions sending phone/email/address to every consumer, including analytics which keeps data forever), and marks whatever recipient PII remains as PII in the schema.",
|
|
55
|
+
"Rejects the Q1 plan to rename `status` -> `state` and switch `weight` from kg to grams as a v2 on the same topic, because both are breaking; proposes additive-only changes (e.g. a new field with an explicit unit) or a new topic/major version run in parallel with a dated deprecation for the old one.",
|
|
56
|
+
"Addresses retention, replay and dead letters: points out the 7-day default retention cannot support analytics replaying events from last year (needs longer retention, compaction or replay from the lake), and replaces billing's crash-restart loop on unparseable messages with a dead-letter topic and a defined redrive process; also flags codAmount as a float without currency.",
|
|
57
|
+
"Invents nothing that isn't in the input (no extra consumers, partition counts, incidents, field values or tooling claimed to exist beyond the two files), and answers the actual request directly — a contract plus the list of draft problems — without padding."
|
|
58
|
+
]
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"id": 5,
|
|
62
|
+
"held_out": "2026-10-20",
|
|
63
|
+
"prompt": "hey, we're finally doing outgoing webhooks at Ledgerly (invoicing SaaS). customers want to hear when an invoice gets paid, goes overdue or gets voided. PM says it HAS to be exactly once and in order because customers reconcile their books against it. plan rn: POST the whole invoice object to the customer's URL (example in invoice.example.json), add a header X-Ledgerly-Version: 1 and change the payload when we need to, our http client times out at 10s, retry 3x then give up. a couple of customers already told us their endpoint does ERP sync work before it responds and takes ~40s. so: how should we version these and how do we actually guarantee delivery? write up the spec thing too i guess",
|
|
64
|
+
"assertions": [
|
|
65
|
+
"Answers the two questions (versioning, guaranteed delivery) directly and early, and tells the user plainly that exactly-once delivery over HTTP webhooks is not achievable: the honest guarantee is at-least-once, with a unique event id on every delivery that receivers store to deduplicate.",
|
|
66
|
+
"States that ordering cannot be guaranteed across retries and parallel deliveries, and gives receivers a way to cope (e.g. use `updated_at` or a per-invoice sequence number to discard stale events, or re-fetch the invoice from the API) rather than promising in-order delivery.",
|
|
67
|
+
"Spots that the ~40s ERP-sync endpoints will always exceed the 10s client timeout, so every delivery to them would be retried and duplicated; advises receivers to acknowledge with a 2xx quickly and process asynchronously.",
|
|
68
|
+
"Replaces 'retry 3x then give up' with retries on a backoff schedule over a longer window, and after retries are exhausted keeps the event (dead-letter / failed-delivery log) with a way for customers to see and replay or redeliver it, instead of silently dropping it.",
|
|
69
|
+
"On versioning, rejects 'change the payload when we need to' under an unchanging header: proposes a version pinned per endpoint/account, additive-only changes within a version, and breaking changes only in a new version with a dated deprecation window for the old one.",
|
|
70
|
+
"Applies data minimisation to the payload: does not send `internal_notes`, and drops or justifies `customer.tax_id`, `customer.billing_address`, `customer.contact_email` and `payment.card_last4` (e.g. send a thin event with the invoice id and key fields, and let the receiver fetch details), marking any PII kept; also flags `amount`/`unit_amount` 1299.9 as a float and recommends integer minor units with `currency`.",
|
|
71
|
+
"Provides a compact AsyncAPI 3.0 document describing the webhook messages (a common envelope with event id, type and occurrence time, and messages for the paid/overdue/voided cases with past-tense names) plus a short decisions summary, and recommends signing deliveries (e.g. an HMAC signature header with a timestamp) so receivers can verify the sender and reject replays.",
|
|
72
|
+
"Invents nothing not in the input (no made-up existing Ledgerly endpoints, customer names, SLAs or numbers beyond 10s, 3 retries, ~40s and the example invoice), and keeps the answer sized to a chatty question — direct answers first, then the spec — rather than a sprawling design document."
|
|
73
|
+
]
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
}
|
package/packs/solution-architect/skills/event-contract-draft/evals/files/1/order-events-brief.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Order service: events we need to publish
|
|
2
|
+
|
|
3
|
+
From the order service team at Brightcart (fictional), for review with the platform team.
|
|
4
|
+
|
|
5
|
+
We want fulfilment and loyalty to stop polling our REST API every minute and react to events
|
|
6
|
+
instead. The platform team runs Kafka for us; topics default to 7 days' retention.
|
|
7
|
+
|
|
8
|
+
## Events we think we need
|
|
9
|
+
|
|
10
|
+
1. **CreateShipment** — we send this when an order has been paid, so the warehouse starts
|
|
11
|
+
picking.
|
|
12
|
+
2. **OrderCancelled** — a customer or support agent cancels an order before it is dispatched.
|
|
13
|
+
3. **OrderPlaced** — we already publish this today on the topic `orders` (payload below), and
|
|
14
|
+
fulfilment consumes it in production. In the next release we want to rename `total` to
|
|
15
|
+
`totalAmount` and make it an integer in pence, to match the payments team's naming. We'd
|
|
16
|
+
like to just change it in place so there's only one version to maintain.
|
|
17
|
+
|
|
18
|
+
## Who consumes what
|
|
19
|
+
|
|
20
|
+
- **Fulfilment** (warehouse system): needs the order id, the lines (SKU and quantity) and the
|
|
21
|
+
delivery address. Starts picking only once the order is paid. Stops picking if it is
|
|
22
|
+
cancelled.
|
|
23
|
+
- **Loyalty**: needs the customer id and the order total to award points, and takes the points
|
|
24
|
+
back when an order is cancelled.
|
|
25
|
+
- **Analytics**: wants every order event from every order in one single global order, so their
|
|
26
|
+
dashboards replay exactly as things happened across the whole business.
|
|
27
|
+
|
|
28
|
+
We'll put `customerEmail` on every event as well, just in case someone needs it later.
|
|
29
|
+
|
|
30
|
+
## What OrderPlaced looks like today
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"orderId": "ord_7Q2K9",
|
|
35
|
+
"customerId": "cus_31F8",
|
|
36
|
+
"customerEmail": "jo.example@example.com",
|
|
37
|
+
"total": 42.50,
|
|
38
|
+
"currency": "GBP",
|
|
39
|
+
"lines": [{ "sku": "TEA-EB-250", "quantity": 2 }],
|
|
40
|
+
"deliveryAddress": { "line1": "1 Example Street", "town": "Exampleton", "postcode": "EX1 1AA" },
|
|
41
|
+
"placedAt": "2026-10-19 14:03:11"
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Nobody has decided yet who looks after failed messages.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# shipments topic - working notes (tracking-core team)
|
|
2
|
+
|
|
3
|
+
Producer: tracking-core. Kafka topic `shipments`, 12 partitions, key = `carrierId`
|
|
4
|
+
(so each carrier's events stay together). Retention: cluster default, 7 days.
|
|
5
|
+
|
|
6
|
+
## Consumers
|
|
7
|
+
|
|
8
|
+
- **notify-svc** - texts/emails the recipient on status changes. Last month a consumer
|
|
9
|
+
group rebalance made it re-read a batch and ~3,000 recipients got the
|
|
10
|
+
"out for delivery" SMS twice. Also: sometimes "delivered" arrives before
|
|
11
|
+
"out_for_delivery" for the same parcel and customers get confused.
|
|
12
|
+
- **analytics** - copies everything into the data lake, keeps it forever.
|
|
13
|
+
They want to be able to replay events from last year after they fixed a bug
|
|
14
|
+
in their pipeline.
|
|
15
|
+
- **billing** - charges the merchant per delivered parcel and remits `codAmount`.
|
|
16
|
+
When it hits a message it can't parse, it crashes, restarts, reads the same
|
|
17
|
+
message, crashes again. Happened twice in September.
|
|
18
|
+
|
|
19
|
+
## Plans
|
|
20
|
+
|
|
21
|
+
- Peak season: bump `shipments` from 12 to 48 partitions in November.
|
|
22
|
+
- Q1: rename `status` -> `state` and send `weight` in grams instead of kg.
|
|
23
|
+
Plan is to just bump the version to v2 on the same topic and tell consumers.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"title": "UpdateShipment",
|
|
4
|
+
"description": "Draft v0.3 - one message type for everything that happens to a shipment. Published by tracking-core to the 'shipments' topic.",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"action": {
|
|
8
|
+
"type": "string",
|
|
9
|
+
"enum": ["CREATE_SHIPMENT", "UPDATE_STATUS", "SEND_DELIVERY_SMS", "CANCEL"],
|
|
10
|
+
"description": "What to do with this shipment."
|
|
11
|
+
},
|
|
12
|
+
"shipmentId": { "type": "string", "description": "e.g. PW-48213377" },
|
|
13
|
+
"carrierId": { "type": "string", "description": "e.g. CARR-017" },
|
|
14
|
+
"status": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"enum": ["booked", "picked_up", "in_transit", "out_for_delivery", "delivered", "failed_attempt", "returned"]
|
|
17
|
+
},
|
|
18
|
+
"recipient": {
|
|
19
|
+
"type": "object",
|
|
20
|
+
"properties": {
|
|
21
|
+
"name": { "type": "string" },
|
|
22
|
+
"phone": { "type": "string" },
|
|
23
|
+
"email": { "type": "string" },
|
|
24
|
+
"dateOfBirth": { "type": "string", "description": "Only needed for age-restricted parcels, but we always send it." },
|
|
25
|
+
"address": {
|
|
26
|
+
"type": "object",
|
|
27
|
+
"properties": {
|
|
28
|
+
"line1": { "type": "string" },
|
|
29
|
+
"city": { "type": "string" },
|
|
30
|
+
"postcode": { "type": "string" }
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"weight": { "type": "number", "description": "Parcel weight (kg)." },
|
|
36
|
+
"codAmount": { "type": "number", "description": "Cash-on-delivery amount collected, e.g. 24.99" },
|
|
37
|
+
"timestamp": { "type": "string", "description": "When it happened, depot local time, format dd/MM/yyyy HH:mm" }
|
|
38
|
+
},
|
|
39
|
+
"required": ["action", "shipmentId"]
|
|
40
|
+
}
|
package/packs/solution-architect/skills/event-contract-draft/evals/files/5/invoice.example.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "inv_7Qd2KxP9",
|
|
3
|
+
"number": "INV-2026-00418",
|
|
4
|
+
"account_id": "acct_3mZt81",
|
|
5
|
+
"status": "paid",
|
|
6
|
+
"amount": 1299.9,
|
|
7
|
+
"currency": "EUR",
|
|
8
|
+
"due_date": "2026-10-31",
|
|
9
|
+
"updated_at": "2026-10-20T09:14:52Z",
|
|
10
|
+
"customer": {
|
|
11
|
+
"name": "Hollis & Varga GmbH",
|
|
12
|
+
"contact_email": "ap@hollis-varga.example",
|
|
13
|
+
"tax_id": "DE000000000",
|
|
14
|
+
"billing_address": "Musterweg 12, 10115 Berlin"
|
|
15
|
+
},
|
|
16
|
+
"payment": {
|
|
17
|
+
"method": "card",
|
|
18
|
+
"card_last4": "4242",
|
|
19
|
+
"paid_at": "2026-10-20T09:14:50Z"
|
|
20
|
+
},
|
|
21
|
+
"line_items": [
|
|
22
|
+
{ "description": "Pro plan, October", "quantity": 1, "unit_amount": 1299.9 }
|
|
23
|
+
],
|
|
24
|
+
"internal_notes": "Customer asked for net-30, approved by finance"
|
|
25
|
+
}
|
package/packs/solution-architect/skills/event-contract-draft/examples/order-service.asyncapi.json
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
{
|
|
2
|
+
"asyncapi": "3.0.0",
|
|
3
|
+
"info": {
|
|
4
|
+
"title": "Order service events",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"description": "Facts the order service publishes about orders. Consumers: fulfilment, loyalty, analytics.\n\nConsumers must: (1) deduplicate on eventId, because delivery is at-least-once; (2) apply an event only when its orderRevision is higher than the last one applied for that order, and drop it otherwise; (3) ignore fields and event types they do not recognise; (4) never rely on order across different orders; (5) own the dead-letter queue for their consumer group.\n\nVersioning: within a version only optional fields are added. A breaking change ships as a new event version, published alongside the old one until the old one's sunset date, agreed with its consumers."
|
|
7
|
+
},
|
|
8
|
+
"defaultContentType": "application/json",
|
|
9
|
+
"servers": {
|
|
10
|
+
"production": {
|
|
11
|
+
"host": "kafka.example.internal:9092",
|
|
12
|
+
"protocol": "kafka",
|
|
13
|
+
"description": "Placeholder; the platform team supplies the real bootstrap servers."
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"channels": {
|
|
17
|
+
"orderLifecycle": {
|
|
18
|
+
"address": "orders.order-lifecycle.v1",
|
|
19
|
+
"description": "Every state change of an order, in the order it happened for that order.",
|
|
20
|
+
"x-partition-key": "orderId",
|
|
21
|
+
"x-ordering": "Ordered per orderId: OrderPlaced, OrderPaid and OrderCancelled for one order arrive in the order the order service committed them. No order across orders and no global order. A retry can redeliver an earlier event; consumers compare orderRevision.",
|
|
22
|
+
"x-delivery": "at-least-once; duplicates come from producer retries, consumer rebalances and replays",
|
|
23
|
+
"x-retention": "7 days (platform default)",
|
|
24
|
+
"x-replay": "A consumer group can reset its offset to any point in the last 7 days.",
|
|
25
|
+
"x-dead-letter": "After 5 failed attempts [assumption] a consumer parks the message on <consumer-group>.orders.order-lifecycle.v1.dlq. Owner per consumer group: to confirm.",
|
|
26
|
+
"messages": {
|
|
27
|
+
"OrderPlacedV2": { "$ref": "#/components/messages/OrderPlacedV2" },
|
|
28
|
+
"OrderPaid": { "$ref": "#/components/messages/OrderPaid" },
|
|
29
|
+
"OrderCancelled": { "$ref": "#/components/messages/OrderCancelled" }
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"ordersLegacy": {
|
|
33
|
+
"address": "orders",
|
|
34
|
+
"description": "Today's topic, carrying OrderPlaced v1 unchanged until its sunset date. Nothing new is added here.",
|
|
35
|
+
"x-partition-key": "to confirm: the current key on this topic is not stated",
|
|
36
|
+
"x-ordering": "Unchanged from today; no ordering promise is added. No global order.",
|
|
37
|
+
"x-delivery": "at-least-once",
|
|
38
|
+
"x-retention": "7 days (platform default)",
|
|
39
|
+
"x-replay": "A consumer group can reset its offset to any point in the last 7 days.",
|
|
40
|
+
"x-dead-letter": "As today; owner to confirm.",
|
|
41
|
+
"messages": {
|
|
42
|
+
"OrderPlacedV1": { "$ref": "#/components/messages/OrderPlacedV1" }
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"operations": {
|
|
47
|
+
"publishOrderLifecycle": {
|
|
48
|
+
"action": "send",
|
|
49
|
+
"channel": { "$ref": "#/channels/orderLifecycle" },
|
|
50
|
+
"messages": [
|
|
51
|
+
{ "$ref": "#/channels/orderLifecycle/messages/OrderPlacedV2" },
|
|
52
|
+
{ "$ref": "#/channels/orderLifecycle/messages/OrderPaid" },
|
|
53
|
+
{ "$ref": "#/channels/orderLifecycle/messages/OrderCancelled" }
|
|
54
|
+
]
|
|
55
|
+
},
|
|
56
|
+
"publishOrdersLegacy": {
|
|
57
|
+
"action": "send",
|
|
58
|
+
"channel": { "$ref": "#/channels/ordersLegacy" },
|
|
59
|
+
"messages": [ { "$ref": "#/channels/ordersLegacy/messages/OrderPlacedV1" } ]
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
"components": {
|
|
63
|
+
"messages": {
|
|
64
|
+
"OrderPlacedV2": {
|
|
65
|
+
"name": "OrderPlacedV2",
|
|
66
|
+
"title": "Order placed (v2)",
|
|
67
|
+
"summary": "A customer placed an order. Not yet paid.",
|
|
68
|
+
"description": "Event-carried state: what the order contains. No delivery address: nobody acts on an unpaid order's address.",
|
|
69
|
+
"x-event-style": "event-carried-state",
|
|
70
|
+
"bindings": { "kafka": { "key": { "type": "string", "description": "orderId" } } },
|
|
71
|
+
"payload": {
|
|
72
|
+
"allOf": [
|
|
73
|
+
{ "$ref": "#/components/schemas/Envelope" },
|
|
74
|
+
{
|
|
75
|
+
"type": "object",
|
|
76
|
+
"required": ["data"],
|
|
77
|
+
"properties": {
|
|
78
|
+
"eventType": { "type": "string", "const": "OrderPlaced" },
|
|
79
|
+
"eventVersion": { "type": "integer", "const": 2 },
|
|
80
|
+
"data": {
|
|
81
|
+
"type": "object",
|
|
82
|
+
"required": ["orderId", "orderRevision", "customerId", "lines", "totalAmount", "currency"],
|
|
83
|
+
"properties": {
|
|
84
|
+
"orderId": { "$ref": "#/components/schemas/OrderId" },
|
|
85
|
+
"orderRevision": { "$ref": "#/components/schemas/OrderRevision" },
|
|
86
|
+
"customerId": { "type": "string", "description": "Opaque id. Loyalty." },
|
|
87
|
+
"lines": { "$ref": "#/components/schemas/Lines" },
|
|
88
|
+
"totalAmount": { "$ref": "#/components/schemas/TotalAmount" },
|
|
89
|
+
"currency": { "$ref": "#/components/schemas/Currency" }
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
]
|
|
95
|
+
},
|
|
96
|
+
"examples": [
|
|
97
|
+
{
|
|
98
|
+
"name": "placed",
|
|
99
|
+
"payload": {
|
|
100
|
+
"eventId": "8a1f3c52-4d6e-4b7a-9c0d-2e3f4a5b6c7d",
|
|
101
|
+
"eventType": "OrderPlaced",
|
|
102
|
+
"eventVersion": 2,
|
|
103
|
+
"occurredAt": "2026-10-19T14:03:11Z",
|
|
104
|
+
"producer": "order-service",
|
|
105
|
+
"correlationId": "1c9e7a40-55b2-4f0e-8d31-6a7b8c9d0e1f",
|
|
106
|
+
"data": {
|
|
107
|
+
"orderId": "ord_7Q2K9",
|
|
108
|
+
"orderRevision": 1,
|
|
109
|
+
"customerId": "cus_31F8",
|
|
110
|
+
"lines": [ { "sku": "TEA-EB-250", "quantity": 2 } ],
|
|
111
|
+
"totalAmount": 4250,
|
|
112
|
+
"currency": "GBP"
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
]
|
|
117
|
+
},
|
|
118
|
+
"OrderPaid": {
|
|
119
|
+
"name": "OrderPaid",
|
|
120
|
+
"title": "Order paid",
|
|
121
|
+
"summary": "Payment for an order succeeded. Replaces the requested CreateShipment.",
|
|
122
|
+
"description": "Event-carried state: everything fulfilment needs to start picking and loyalty needs to award points, so neither calls the order API.",
|
|
123
|
+
"x-event-style": "event-carried-state",
|
|
124
|
+
"bindings": { "kafka": { "key": { "type": "string", "description": "orderId" } } },
|
|
125
|
+
"payload": {
|
|
126
|
+
"allOf": [
|
|
127
|
+
{ "$ref": "#/components/schemas/Envelope" },
|
|
128
|
+
{
|
|
129
|
+
"type": "object",
|
|
130
|
+
"required": ["data"],
|
|
131
|
+
"properties": {
|
|
132
|
+
"eventType": { "type": "string", "const": "OrderPaid" },
|
|
133
|
+
"eventVersion": { "type": "integer", "const": 1 },
|
|
134
|
+
"data": {
|
|
135
|
+
"type": "object",
|
|
136
|
+
"required": ["orderId", "orderRevision", "customerId", "lines", "totalAmount", "currency", "deliveryAddress"],
|
|
137
|
+
"properties": {
|
|
138
|
+
"orderId": { "$ref": "#/components/schemas/OrderId" },
|
|
139
|
+
"orderRevision": { "$ref": "#/components/schemas/OrderRevision" },
|
|
140
|
+
"customerId": { "type": "string", "description": "Opaque id. Loyalty." },
|
|
141
|
+
"lines": { "$ref": "#/components/schemas/Lines" },
|
|
142
|
+
"totalAmount": { "$ref": "#/components/schemas/TotalAmount" },
|
|
143
|
+
"currency": { "$ref": "#/components/schemas/Currency" },
|
|
144
|
+
"deliveryAddress": {
|
|
145
|
+
"type": "object",
|
|
146
|
+
"x-pii": "fulfilment: where to ship",
|
|
147
|
+
"required": ["line1", "town", "postcode"],
|
|
148
|
+
"properties": {
|
|
149
|
+
"line1": { "type": "string", "maxLength": 200 },
|
|
150
|
+
"town": { "type": "string", "maxLength": 100 },
|
|
151
|
+
"postcode": { "type": "string", "maxLength": 16 }
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
]
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
"OrderCancelled": {
|
|
162
|
+
"name": "OrderCancelled",
|
|
163
|
+
"title": "Order cancelled",
|
|
164
|
+
"summary": "A customer or support agent cancelled an order before dispatch.",
|
|
165
|
+
"description": "Notification: ids and who cancelled. Fulfilment stops picking; loyalty reverses the points it awarded for this orderId.",
|
|
166
|
+
"x-event-style": "notification",
|
|
167
|
+
"bindings": { "kafka": { "key": { "type": "string", "description": "orderId" } } },
|
|
168
|
+
"payload": {
|
|
169
|
+
"allOf": [
|
|
170
|
+
{ "$ref": "#/components/schemas/Envelope" },
|
|
171
|
+
{
|
|
172
|
+
"type": "object",
|
|
173
|
+
"required": ["data"],
|
|
174
|
+
"properties": {
|
|
175
|
+
"eventType": { "type": "string", "const": "OrderCancelled" },
|
|
176
|
+
"eventVersion": { "type": "integer", "const": 1 },
|
|
177
|
+
"data": {
|
|
178
|
+
"type": "object",
|
|
179
|
+
"required": ["orderId", "orderRevision", "customerId", "cancelledBy"],
|
|
180
|
+
"properties": {
|
|
181
|
+
"orderId": { "$ref": "#/components/schemas/OrderId" },
|
|
182
|
+
"orderRevision": { "$ref": "#/components/schemas/OrderRevision" },
|
|
183
|
+
"customerId": { "type": "string", "description": "Opaque id. Loyalty." },
|
|
184
|
+
"cancelledBy": { "type": "string", "enum": ["customer", "support"], "description": "Closed enum." }
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
]
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
"OrderPlacedV1": {
|
|
193
|
+
"name": "OrderPlacedV1",
|
|
194
|
+
"title": "Order placed (v1, deprecated)",
|
|
195
|
+
"summary": "Today's event, documented as-is. Unchanged until sunset.",
|
|
196
|
+
"x-deprecated": true,
|
|
197
|
+
"x-sunset": "to agree with fulfilment",
|
|
198
|
+
"x-replaced-by": "OrderPlacedV2",
|
|
199
|
+
"payload": {
|
|
200
|
+
"type": "object",
|
|
201
|
+
"properties": {
|
|
202
|
+
"orderId": { "type": "string" },
|
|
203
|
+
"customerId": { "type": "string" },
|
|
204
|
+
"customerEmail": { "type": "string", "x-pii": "no consumer named; not carried in v2" },
|
|
205
|
+
"total": { "type": "number", "description": "Major units as a decimal (42.50). Replaced by totalAmount in v2." },
|
|
206
|
+
"currency": { "type": "string" },
|
|
207
|
+
"lines": { "$ref": "#/components/schemas/Lines" },
|
|
208
|
+
"deliveryAddress": { "type": "object", "x-pii": "fulfilment" },
|
|
209
|
+
"placedAt": { "type": "string", "description": "Local time with no zone, as published today." }
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
"schemas": {
|
|
215
|
+
"Envelope": {
|
|
216
|
+
"type": "object",
|
|
217
|
+
"required": ["eventId", "eventType", "eventVersion", "occurredAt", "producer", "correlationId"],
|
|
218
|
+
"properties": {
|
|
219
|
+
"eventId": { "type": "string", "format": "uuid", "description": "Unique per event; identical on every redelivery and replay." },
|
|
220
|
+
"eventType": { "type": "string" },
|
|
221
|
+
"eventVersion": { "type": "integer", "minimum": 1 },
|
|
222
|
+
"occurredAt": { "type": "string", "format": "date-time", "description": "When it happened, UTC, RFC 3339 with Z." },
|
|
223
|
+
"producer": { "type": "string", "const": "order-service" },
|
|
224
|
+
"correlationId": { "type": "string" },
|
|
225
|
+
"causationId": { "type": "string", "description": "eventId or request id that caused this event." }
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
"OrderId": { "type": "string", "pattern": "^ord_[A-Za-z0-9]+$" },
|
|
229
|
+
"OrderRevision": { "type": "integer", "minimum": 1, "description": "Increases by 1 on every change to this order." },
|
|
230
|
+
"TotalAmount": { "type": "integer", "minimum": 0, "description": "Order total in minor units (pence): 42.50 GBP is 4250." },
|
|
231
|
+
"Currency": { "type": "string", "pattern": "^[A-Z]{3}$" },
|
|
232
|
+
"Lines": {
|
|
233
|
+
"type": "array",
|
|
234
|
+
"minItems": 1,
|
|
235
|
+
"items": {
|
|
236
|
+
"type": "object",
|
|
237
|
+
"required": ["sku", "quantity"],
|
|
238
|
+
"properties": {
|
|
239
|
+
"sku": { "type": "string" },
|
|
240
|
+
"quantity": { "type": "integer", "minimum": 1 }
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Worked example: order service events with four traps
|
|
2
|
+
|
|
3
|
+
## Input given to the skill
|
|
4
|
+
|
|
5
|
+
> We're moving fulfilment, loyalty and analytics off polling our order API and onto Kafka
|
|
6
|
+
> events. Our brief is in order-events-brief.md. Can you draft the event contract so the three
|
|
7
|
+
> teams can start building?
|
|
8
|
+
|
|
9
|
+
The brief (in [`../evals/files/1/order-events-brief.md`](../evals/files/1/order-events-brief.md))
|
|
10
|
+
asks for a `CreateShipment` event, an analytics feed "in one single global order", a
|
|
11
|
+
`customerEmail` on every event "just in case", and an in-place rename of `total` to an integer
|
|
12
|
+
`totalAmount` on the live `OrderPlaced`, which fulfilment consumes in production. Retention is
|
|
13
|
+
7 days; nobody owns failed messages yet.
|
|
14
|
+
|
|
15
|
+
## Output
|
|
16
|
+
|
|
17
|
+
**What the contract decides**
|
|
18
|
+
|
|
19
|
+
- `CreateShipment` becomes **`OrderPaid`**. The order service publishes the fact; the warehouse
|
|
20
|
+
decides to pick when it sees it. A command on a shared topic would make the order service own
|
|
21
|
+
the warehouse's workflow.
|
|
22
|
+
- **No single global order across all orders.** Kafka orders messages only within a partition;
|
|
23
|
+
one partition for everything means one consumer at a time, and retries and dead-letter
|
|
24
|
+
redrives reorder it anyway. Instead, every event for one order arrives in order (the topic is
|
|
25
|
+
keyed by order id), and each event carries `occurredAt` and `orderRevision` so analytics can
|
|
26
|
+
sort across orders when it reads.
|
|
27
|
+
- **`customerEmail` is dropped.** None of the three consumers uses it, and on Kafka it would
|
|
28
|
+
sit in every consumer's copy for the whole retention window. The delivery address stays, on
|
|
29
|
+
`OrderPaid` only, marked as personal data that fulfilment needs.
|
|
30
|
+
- **`total` is not changed in place.** Fulfilment reads `OrderPlaced` in production and would
|
|
31
|
+
break on deploy. The new events go on a new topic, `orders.order-lifecycle.v1`: `OrderPlaced`
|
|
32
|
+
version 2 (with `totalAmount` in pence), `OrderPaid` and `OrderCancelled`. The existing
|
|
33
|
+
`orders` topic keeps publishing today's `OrderPlaced` unchanged until a date agreed with
|
|
34
|
+
fulfilment.
|
|
35
|
+
|
|
36
|
+
**Decisions**
|
|
37
|
+
|
|
38
|
+
| # | Decision | Choice | Why |
|
|
39
|
+
|---|---|---|---|
|
|
40
|
+
| 1 | Event names | `OrderPlaced`, `OrderPaid`, `OrderCancelled` | Facts in the order domain; replaces `CreateShipment` |
|
|
41
|
+
| 2 | Topic and key | one topic, `orders.order-lifecycle.v1`, keyed by `orderId` | Paid and cancelled for one order must not race; one key means one partition, in order |
|
|
42
|
+
| 3 | Ordering | per order only; no order across orders, no global order | Kafka's guarantee is per partition |
|
|
43
|
+
| 4 | Producer publishing | each order's events published in commit order, from an outbox written in the same transaction | Without it the per-order promise in 2 is false |
|
|
44
|
+
| 5 | Payload style | `OrderPlaced` and `OrderPaid` carry the order; `OrderCancelled` carries ids and who cancelled | Fulfilment picks and loyalty awards from `OrderPaid` alone; cancelling needs only the id |
|
|
45
|
+
| 6 | Envelope | `eventId`, `eventType`, `eventVersion`, `occurredAt` (UTC, RFC 3339), `producer`, `correlationId`, `causationId` | Dedupe, routing, tracing; today's `placedAt` has no time zone |
|
|
46
|
+
| 7 | Money | `totalAmount` as integer pence plus `currency` | Today's `42.50` is a decimal float |
|
|
47
|
+
| 8 | Personal data | `customerEmail` dropped; `deliveryAddress` on `OrderPaid` only, marked | No consumer for the email; the address only once there is something to ship |
|
|
48
|
+
| 9 | The `total` change | `OrderPlaced` v2 on the new topic; v1 on `orders` unchanged until sunset | A rename plus a type change is breaking; both publish during the window |
|
|
49
|
+
| 10 | Delivery | at-least-once | Kafka redelivers on producer retries, rebalances and replays |
|
|
50
|
+
| 11 | Retention and replay | 7 days; consumer groups can reset offsets within it | Platform default from the brief |
|
|
51
|
+
| 12 | Dead letter | each consumer group parks a message after 5 failed attempts [assumption] on its own DLQ | Owner open; see below |
|
|
52
|
+
|
|
53
|
+
**What consumers must do**
|
|
54
|
+
|
|
55
|
+
1. Deduplicate on `eventId`. Fulfilment: a second `OrderPaid` for one order never starts a
|
|
56
|
+
second pick. Loyalty: award points keyed on the order id, so a repeat is a no-op.
|
|
57
|
+
2. Apply an event only if its `orderRevision` is higher than the last one applied for that
|
|
58
|
+
order; drop it otherwise.
|
|
59
|
+
3. Ignore fields and event types you don't recognise.
|
|
60
|
+
4. Never rely on order across orders. Analytics sorts by `occurredAt`, then `orderRevision`.
|
|
61
|
+
5. Park a message that fails 5 times on your DLQ, and watch it.
|
|
62
|
+
6. Fulfilment: move from `orders` to `orders.order-lifecycle.v1` before the agreed date.
|
|
63
|
+
|
|
64
|
+
**The AsyncAPI document** is saved as `asyncapi.json`
|
|
65
|
+
(here: [`order-service.asyncapi.json`](order-service.asyncapi.json)). It passes a structural
|
|
66
|
+
check: references resolve, every new message has the full envelope and a past-tense name, and
|
|
67
|
+
each topic states its key, ordering, delivery, retention, replay and dead-letter handling. The
|
|
68
|
+
only warnings are on the old `OrderPlaced` v1, documented as it runs today.
|
|
69
|
+
|
|
70
|
+
**To confirm**
|
|
71
|
+
|
|
72
|
+
- The date the old `OrderPlaced` stops on `orders`: order service team with fulfilment.
|
|
73
|
+
- Who watches each dead-letter queue: order service team with each consumer team.
|
|
74
|
+
- How the `orders` topic is keyed today: order service team.
|
|
75
|
+
- Whether analytics needs more than 7 days of history to replay; if so, longer retention or an
|
|
76
|
+
archive: analytics with the platform team.
|
|
77
|
+
- Whether loyalty awards points on `OrderPaid` (assumed) or `OrderPlaced`: loyalty.
|
|
78
|
+
- Whether fulfilment reads `customerEmail` from today's event; if not, it can come off v1 now.
|
|
79
|
+
|
|
80
|
+
## Checker output on the example
|
|
81
|
+
|
|
82
|
+
```text
|
|
83
|
+
AsyncAPI check: examples/order-service.asyncapi.json
|
|
84
|
+
2 channels, 4 messages, 2 operations
|
|
85
|
+
WARN (deprecated) message OrderPlacedV1: envelope fields missing from headers and payload: event id (eventId), event type (eventType), event version (eventVersion), occurred-at (occurredAt), producer (producer), correlation id (correlationId)
|
|
86
|
+
WARN message OrderPlacedV1: no causation id (causationId); consumers cannot tell which event caused this one
|
|
87
|
+
WARN message OrderPlacedV1: x-sunset is 'to agree with fulfilment', not a date; agree the date and list it as open until then
|
|
88
|
+
PII (marked): message OrderPaid.payload.data.deliveryAddress
|
|
89
|
+
PII (marked): message OrderPlacedV1.payload.customerEmail
|
|
90
|
+
PII (marked): message OrderPlacedV1.payload.deliveryAddress
|
|
91
|
+
Result: 0 failures, 3 warnings
|
|
92
|
+
```
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "event-contract-draft",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Draft the contract for an event or message interface (Kafka topics, SNS/SQS, EventBridge, Pub/Sub, RabbitMQ, webhooks) as an AsyncAPI 3.0 document plus a short decisions page — past-tense event names, a required envelope with event id for deduplication, the partition key and the ordering it does and doesn't give, at-least-once delivery and how consumers stay idempotent, data minimisation with PII marked, additive-only versioning with dated deprecation, and retention, replay and dead-letter handling. Use when the user wants to define events, design a Kafka topic or message schema, write an AsyncAPI spec, agree what a producer publishes and what consumers must do, or version an event safely.",
|
|
5
|
+
"entrypoint": "SKILL.md",
|
|
6
|
+
"deps": { "npm": [], "pip": [] },
|
|
7
|
+
"env": { "required": [], "optional": [] },
|
|
8
|
+
"related": ["api-contract-draft", "data-contract", "design-doc"],
|
|
9
|
+
"handoff": [
|
|
10
|
+
{
|
|
11
|
+
"to": "data-contract",
|
|
12
|
+
"when": "an analytics team or a trained model reads the topic as a dataset",
|
|
13
|
+
"purpose": "Add the freshness, completeness and field-semantics guarantees a dataset reader needs on top of the event contract.",
|
|
14
|
+
"fallback": "If data-contract is absent, add each analytics field's unit and meaning to the payload schema and list the freshness expectation under To confirm."
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
"tags": ["asyncapi", "event-driven", "kafka", "messaging", "contract-first"],
|
|
18
|
+
"permissions": {
|
|
19
|
+
"network": [],
|
|
20
|
+
"commands": [],
|
|
21
|
+
"files": "named-paths",
|
|
22
|
+
"notes": "check_asyncapi.py reads the one JSON file it is given (or stdin) and writes a report only to the -o path the user names."
|
|
23
|
+
},
|
|
24
|
+
"model": { "tier": "standard", "rationale": "Contract drafting against the AsyncAPI shape with fixed rules on ordering, delivery and versioning; mid tier is enough." }
|
|
25
|
+
}
|