Skip to main content

Requirements Guard Rails — Ghasi Melmastoon

Companion: JIRA_PROJECT_SETUP.md · JIRA_TICKET_TEMPLATE.md · DEFINITION_OF_DONE.md · CODING_STANDARDS.md · TESTING_STANDARDS.md · docs/07-epics-and-user-stories.md

This document is the single, authoritative gate that decides whether a story (or epic, task, bug, spike, ADR) is complete enough to start work on. It is the Definition of Ready (DoR) made operational. Both humans and AI agents apply it. The spec-review and architect stages of the multi-agent workflow refuse to proceed unless every applicable rule passes.


1. Why guard rails

Ambiguous tickets cause:

  • AI agents to hallucinate behavior the user never asked for.
  • Engineers to ship the wrong feature and then re-do it.
  • QA to write tests for the implementer's interpretation, not the user's.
  • Spec drift, because the team starts encoding "what we built" instead of "what was asked".

The guard rails below cost minutes at refinement and save days at delivery.


2. Universal guard rails (every issue type)

Rule
One ticket = one outcome. A reader can articulate the user-visible outcome in a single sentence.
Right-sized. Story complexity is S, M, or L. XL is forbidden — split. Spike is time-boxed ≤ 5 dev-days. ADR has a single decision.
Owned. A primary component (squad) is set. A secondary component is set if multi-service.
Linked. Spec ID resolves to a real artifact (US-MEL-NNNN, EP-MEL-NN, J-NN, W-NN, ADR-NNNN, or a clear technical scope for Task).
Inheritable acceptance. The cross-cutting acceptance bullets in DEFINITION_OF_DONE.md Universal section apply implicitly; the ticket need not restate them. Specific overrides go in the ticket.
Traceability. The ticket can be located in docs/13-traceability-matrix.md (added in Wave 0) or will be added by the same PR that closes the ticket.

3. Story-specific guard rails

A Story passes DoR only if every rule below is satisfied. The spec-review agent prints a checklist and refuses to proceed otherwise.

3.1 The user story sentence

  • Format: As a <persona>, I want <outcome>, so that <value>.
  • Persona is from the canonical list (docs/07-epics-and-user-stories.md §1.4). No invented personas.
  • Outcome is user-observable (a screen, a notification, a piece of data, a state change). Not a refactor — that's a Task.
  • Value is business-meaningful ("so that I can confirm without losing my place"), not technical ("so that the API responds faster").

3.2 Acceptance criteria

  • Gherkin form: Given … When … Then …. May add And and But.
  • 2 to 5 scenarios. Less than 2 = under-specified. More than 5 = oversize — split.
  • Every Given/When/Then is observable to the user, the API caller, the database query, the event consumer, or the log line. No "the code does X" wording.
  • Cover at minimum: happy path, one failure path, one boundary (empty / max / off-by-one). For multi-tenant work add a tenant-isolation scenario.
  • No "should" or "could" or "may" — only declarative statements.
  • No quantifiers without numbers ("fast", "many", "soon"). Use SLOs from services/<name>/SERVICE_OVERVIEW.md.
  • No technology names in AC unless the technology is the contract (e.g., "Then a Pub/Sub message is published on melmastoon.reservation.booking.confirmed.v1" is fine; "Then the Drizzle ORM saves the row" is not).

3.3 Cross-cutting attribute fields

  • Services populated; matches the spec story or extends it (justify in description if extended).
  • Frontend surfaces populated (or "none" for purely backend work).
  • DoD applicability populated; minimum is Universal. Add Data if any DB write, API if any HTTP route added/changed, Events if any event added/changed, Security if any auth/payment/lock/secret touch, AI if any AI call, Frontend if any UI change, Observability if any new metric/trace/log fan-out.
  • Test types required populated; minimum is Unit + Integration. E2E is required if any frontend surface touches; AI-eval if AI capability touches; Offline if any sync path touches; Security if Security is in DoD applicability.
  • Complexity = S, M, or L.
  • Wave matches the sprint's wave label.

3.4 Out-of-scope (explicit)

  • At least one bullet under "Out of scope" so the agent and engineer cannot drift.
  • If the story interacts with another story, the boundary is named ("Caching of search results is handled by US-MEL-NNNN; this story stops at the BFF response").

3.5 Ambiguity sentinel checks

The story FAILS DoR if any of these hold:

  • "TBD" or "TODO" appears in the description (without a linked clarification ticket).
  • A scenario uses a measurement word with no number (fast, large, many, often, rare).
  • A scenario references a screen / route / table / event / topic that does not exist in the spec corpus AND is not also created by this story.
  • The persona contradicts the BFF surface (e.g., a Walk-In Guest scenario expects a JWT-authenticated session).
  • A scenario implies a money operation but no currency is named.
  • A scenario implies a date/time operation but no timezone or calendar is named (Gregorian, Solar Hijri, Hijri lunar — see docs/frontend/04-frontend-design-guidelines.md §9).

4. Bug-specific guard rails

  • Symptom in user terms — not "the test fails" but "guests see two folios when they confirm twice".
  • Reproduction steps that an outsider can run.
  • Telemetry references — at least one of: trace ID, request ID, log query window. Without these the bug cannot be fact-checked.
  • Severity assigned. S1 (data loss / outage) requires an incident link. S2 / S3 / S4 do not but recommend one for S2.
  • Linked Story if this bug is a regression of an In Production story; otherwise add label regression:unmapped and triage to find the parent.
  • AC explicitly includes a regression test path. A bug PR without a failing-then-passing test is rejected.
  • Reachability noted (which tenants, what % of traffic) so triage can prioritize.

5. Task-specific guard rails

  • Why is one paragraph that names the observable problem the task solves (slow CI? broken local-dev? misaligned config? technical debt with a measurable cost?).
  • Scope bullets are concrete artifacts (files, configs, jobs, dashboards). No "various improvements".
  • Acceptance criteria are observable outcomes (CI job exists and is green; build time decreases by ≥ 10 %; ESLint rule lands and passes on develop).
  • Out of scope is set so the task cannot grow into a refactor wave.
  • Test plan names what we run to prove it (re-run the failing job; measure the build time before/after).

6. Spike-specific guard rails

  • One question. One spike = one question. If you have two questions, create two spikes.
  • Time-box in days, not "a sprint or two". Max 5 dev-days.
  • Expected output is one of: ADR draft, sized story, negative result. Code is not a deliverable of a spike. (If you need a prototype, label it as a sized Task.)
  • Inputs name the docs / specs / vendor pages the agent or engineer must read.
  • If the spike overruns, it closes as Won't Do and a follow-up spike is created. No silent extension.

7. ADR-specific guard rails

  • One decision per ADR. Multiple decisions = multiple ADRs that may be issued in the same PR.
  • Context is honest — captures the constraints, history, and the option that was previously the default.
  • Rationale ≥ 3 sentences. The point of an ADR is the rationale.
  • At least two alternatives considered, each with rejection reason.
  • Consequences include at least one negative or neutral bullet (no ADR is purely positive).
  • Affected specs named, and the same PR updates them.
  • Status is Proposed until accepted; Accepted only after sign-off per CODEOWNERS.
  • If this ADR supersedes another, the prior ADR's status is moved to Superseded by ADR-NNNN in the same PR.

8. Epic-specific guard rails

  • Maps 1:1 to a spec EP-MEL-NN. No invented epics; new epic requires an ADR.
  • Wave assigned. If an epic spans waves, decompose into per-wave sub-epics or document the cross-wave continuity in the description.
  • Outcome paragraph describes the user-visible end state, not the work.
  • Done definition matches docs/07-epics-and-user-stories.md §1.1.
  • Owning service named; its SERVICE_READINESS.md will sign off.
  • Cross-references include all journeys (J-NN) and workflows (W-NN) the epic serves.

9. Multi-service stories — extra rails

A story whose Services field lists more than one service:

  • Names the primary owner (the first service in the list).
  • Names every participating service with the responsibility delta (e.g., inventory-service adds an event consumer; billing-service adds a charge entry).
  • Decomposes the AC into scenarios per boundary when the boundary affects observability (e.g., one scenario for the BFF response, one for the published event).
  • Notes any saga ID (SAGA-MEL-…) it touches.
  • Mentions the outbox/inbox in at least one scenario if the story crosses a service boundary asynchronously.

10. Frontend stories — extra rails

A story whose Frontend surfaces field is non-empty:

  • Names the screens affected (use S-NN ids once 06-screen-catalog.md lands in Wave 1; until then name the route).
  • Names the workflow affected (W-NN).
  • AC includes at least one i18n / RTL scenario for any visible string change ("Then the screen renders in ps-AF with logical-direction layout and the Solar Hijri date format").
  • AC includes at least one accessibility scenario if the change introduces interactive UI ("Then keyboard tab order matches reading order; axe reports zero serious or critical violations").
  • AC includes at least one performance scenario if the change touches a budgeted screen ("Then LCP stays within the screen budget on a Moto G4 throttled connection").
  • Cites the design system primitive it uses ("Built from <DataTable /> and <EmptyState /> from @ghasi/ui-melmastoon").
  • If the story has an offline path, AC names the offline scenario explicitly.

11. AI stories — extra rails

A story whose DoD applicability includes AI:

  • Names the prompt ID under ai-orchestrator-service/prompts/<id>/v<n>.md (existing or new).
  • Declares whether the action is reversible or requires HITL approval.
  • Specifies which provenance fields appear in the UI (model, version, promptId, traceId, local|cloud).
  • Specifies the eval set (which inputs, what pass criteria) — see TESTING_STANDARDS.md §14.
  • Names the cost ceiling per call (USD micro-cents) and the per-tenant daily ceiling override.
  • Includes the refusal scenario ("Then if the AI refuses, the UI surfaces the refusal with the model-provided reason").
  • Calls AI only via ai-orchestrator-service. Direct vendor SDK use is forbidden — see CODING_STANDARDS.md §1.

12. Security / payment / lock stories — extra rails

A story whose Services touches iam-service, payment-gateway-service, billing-service, or lock-integration-service:

  • Risk score ≥ 7 set on the ticket so the security-reviewer subagent run is mandatory.
  • AC includes the canonical error code for at least one failure scenario (MELMASTOON.PAYMENT.GATEWAY_TIMEOUT, MELMASTOON.LOCK.VENDOR_UNREACHABLE, …).
  • AC includes the idempotency scenario for any write.
  • AC includes the secret resolution path if any new secret is introduced (Secret Manager, KMS-wrapped).
  • Threat model in description names the attacker and the mitigation (matches services/<name>/SECURITY_MODEL.md).

13. Offline / sync stories — extra rails

A story whose Test types required includes Offline:

  • Names the conflict resolution policy the affected aggregate uses (LWW, server-authoritative, append-only) per services/<name>/SYNC_CONTRACT.md.
  • AC includes a disconnect scenario ("Given the desktop is offline, When the user…, Then the action is queued in the outbox and the sync pill shows pending").
  • AC includes a reconnect scenario ("When the desktop reconnects, Then the queued actions flush in FIFO order and the sync pill shows synced").
  • AC includes a conflict scenario ("When a server-side change conflicts with a queued local change, Then the conflict resolves per <policy> and a melmastoon.<service>.<aggregate>.conflict_resolved.v1 event is published").

14. Stories that touch money — extra rails

  • Currency is named in every monetary scenario.
  • Money is represented as bigint micro-units in any data shape mentioned.
  • FX-snapshot rule is invoked at confirmation time if the story crosses currencies.
  • Tax handling is named (or explicitly out of scope) per services/billing-service/DOMAIN_MODEL.md.

15. Stories that touch dates / time — extra rails

  • Timezone named (UTC for all events; tenant-local for user-visible).
  • Calendar named when relevant (Gregorian for system; Solar Hijri / Hijri lunar / Gregorian for user-visible per locale; numerals always Latin in finance).
  • DST handling noted if the scenario crosses a DST boundary in a tenant locale.

16. Stories that touch i18n — extra rails

  • AC names at least two locales: one LTR and one RTL.
  • Strings are referenced by i18n key in description (e.g., booking.confirmation.title), not by literal text.
  • If the story introduces a new locale, the locale fallback chain is updated in services/tenant-service/DOMAIN_MODEL.md.

17. The "this is implementation" sentinel

If a Story or Bug AC contains any of the following words, it is likely encoding implementation details — push them into the architect stage instead:

  • "Use TypeORM" / "Use Prisma" → forbidden anyway.
  • "Add a column to the X table".
  • "Refactor the Y class".
  • "Add a method to the Z service".
  • "Wire the Q event handler".

The right form: AC describes outcome ("Then the reservation is persisted with the new cancellation_reason and surfaced in the folio history"); the architect stage decides whether that requires a column, a denormalized table, or an event-sourced view.


18. Anti-patterns (ticket smells)

  • A story that is "implement the X service".
  • A story that copies the entire spec section into AC verbatim (the spec is linked; AC summarizes the contract).
  • A story whose only AC is "the design from Figma is implemented" — name the screen and the visible behavior, not the artifact.
  • A bug filed without a trace ID.
  • A spike whose acceptance is "investigate".
  • A task whose scope is "various improvements".
  • An ADR whose status is Accepted on creation.
  • A multi-service story whose primary component is cross-cutting (it almost certainly has a real owner; find it).
  • A story whose risk score is 9 but DoD applicability has no Security box ticked.

19. Operational rules

  • The spec-review agent (multi-agent workflow stage) prints this checklist and refuses to proceed if any applicable rule fails. The agent's output is captured in planning/.../spec-review.md.
  • The pr-test-analyzer subagent uses the AC list to evaluate test coverage at PR time; missing scenarios trigger review feedback.
  • Humans creating tickets paste from JIRA_TICKET_TEMPLATE.md, which mirrors these rails. The DoR check is a single comment thread on the ticket.
  • A ticket can be temporarily exempted from one rule with a dor:waived:<rule-id> label and a written rationale in description; exemptions cap at 2 per sprint per service to prevent erosion.
  • Repeat DoR failures (≥ 3 bounces) escalate to the per-service component lead at the next refinement.

20. Cross-references


21. Versioning

Same governance as CODING_STANDARDS.md §19. Tightening a rule applies to new tickets only; loosening any rule requires an ADR.