Issues (Bugs)
Defects, bugs, and broken things. With the v2.0 diagnostic_state vocabulary. Vocabulary discipline: see VOCABULARY.md. Reference: METHOD.md for full methodology.
Issue has been a Canopy primitive since v3.0. This doc is the first canonical methodology spec, expanded with v2.0's
diagnostic_stateandowner_uncertainfields (D-07, D-12).
Definition
An Issue is a defect, bug, or broken behavior. Filed when something doesn't work as intended — a feature that worked yesterday doesn't work today, an API returns the wrong response, a UI element renders incorrectly, a deploy fails its smoke check. Issues are how the system tracks what is broken; Outcomes are how the system tracks what is being built.
An Issue is not an Outcome. Outcomes describe results that deliver value — "Users can log in reliably." When an Outcome's success criteria fail, the failure surfaces as an Issue. The Outcome that fixes the Issue is separate; it might be the same Outcome reopened, or a new Outcome describing the broader fix. The relationship is captured by linked_outcome_id: an Issue can point at the Outcome whose work introduced or is blocked by the defect.
The v2.0 audit caught a real failure mode in the v3.0 Issue model. The high-level status enum (open / in-progress / resolved / closed) captures lifecycle — where the Issue is in its journey to closure. But it misses the detective work. ROM Mobile filed Issues with internal status notes like "AWAITING DEVICE DETAILS," "HOST-APP CONFIG (not SDK)," "ROOT-CAUSE INVESTIGATION STILL PENDING," "SDK-VS-HOST-APP AMBIGUITY." Six clans shipping SDKs showed the same pattern. The v2.0 diagnostic_state enum captures these states as first-class.
diagnostic_state and status are orthogonal. They move independently. An Issue can be status: in-progress AND diagnostic_state: awaiting_evidence simultaneously — the lifecycle says "we're working on it"; the diagnostic state says "we can't make progress without the user's device logs." Reporting surfaces filter on both: "show me all Issues in awaiting_evidence for more than 7 days."
The owner_uncertain flag (v2.0, D-12) handles the common case where an Issue is filed before ownership is determined. Forcing a premature owner assignment — "assign to SDK team OR host-app team, pick one" — creates wrong-team handoffs and damages cross-team trust. With owner_uncertain: true, the Issue is filed honestly: the symptom is captured, the repro is captured, but ownership is "under investigation." Diagnostic work clarifies ownership; owner gets set then, and owner_uncertain flips to false.
Field Table
Fields marked NEW (v2.0) were added by D-07 and D-12. All other fields existed since v3.0.
| Field | Type | Required | Default | Description | Notes |
|---|---|---|---|---|---|
| id | uuid | yes | — | Unique identifier | — |
| title | string | yes | — | Short summary of the bug | Max 200 chars |
| description | text | yes | — | Repro steps, expected vs actual, environment | — |
| severity | enum | yes | — | critical / high / medium / low | Existing — see severity sub-table |
| status | enum | yes | open | open / in-progress / resolved / closed | Existing high-level lifecycle |
| diagnostic_state | enum | no | triage | NEW (D-07): triage / investigating / likely_resolved / awaiting_evidence / misdiagnosed / not_our_bug | Finer-grained than status; orthogonal |
| investigation_notes | text | no | null | NEW (D-07): free-form structured context, repro attempts, hypotheses | Living working hypothesis, not comment history |
| owner_uncertain | bool | no | false | NEW (D-12): true when SDK-vs-host-app or similar ownership ambiguity exists | Diagnostic-in-progress flag |
| owner | uuid | no | null | Single human accountable (may be null if owner_uncertain: true) |
— |
| reporter | uuid | yes | — | Who filed the issue | — |
| linked_outcome_id | uuid | no | null | Outcome whose work introduced or is blocked by this issue | — |
| created_at | timestamp | yes | now() | — | — |
| resolved_at | timestamp | no | null | When status moved to resolved | — |
| tags | string[] | no | [] |
— | — |
status enum (existing)
| Value | Meaning |
|---|---|
| open | Filed; not yet picked up. |
| in-progress | Someone is working on it. |
| resolved | Fix shipped; awaiting closure verification. |
| closed | Verified resolved. Terminal. |
diagnostic_state enum (NEW in v2.0)
| Value | Meaning |
|---|---|
| triage | Just filed, not yet looked at. The default. |
| investigating | Actively root-causing. Hypothesis under test. |
| likely_resolved | Fix shipped; awaiting user confirmation that the symptom is gone. |
| awaiting_evidence | Need logs, device, repro environment, or other external input to confirm or deny a hypothesis. |
| misdiagnosed | Turned out to be a different bug than originally framed. The Issue stays open under its new framing; the misdiagnosis is preserved in investigation_notes so the trail is visible. |
| not_our_bug | Confirmed external party owns this (SDK, third-party service, host app, upstream library). May be closed with link to external tracker. |
severity enum (existing)
Severity is the impact dimension for a bug — distinct from priority (Now/Next/Later). A low-severity bug can still be Now priority on the Outcome that fixes it; a critical-severity bug almost always is.
| Value | Meaning |
|---|---|
| critical | Production-blocking. Wakes someone. |
| high | Affects many users or major functionality; no immediate workaround. |
| medium | Workaround exists or affects a subset of users; fix within the milestone. |
| low | Cosmetic, edge-case, or deferred. Fix when convenient. |
This table is the source of truth. Phase 12 derives the Zod schema additions in @canopy/shared directly from this table — the new fields integrate with the existing v3.0 Issue schema.
Examples
Deep example: ROM Mobile — SDK-vs-host-app diagnostic case
ROM Mobile shipped an iOS SDK. A user reported intermittent network failures. The Issue's diagnostic arc — captured under v2.0 vocabulary — is the canonical example of why the new fields matter.
The Issue record at filing:
| Field | Value (Day 1) |
|---|---|
| title | Network calls failing on iOS only, intermittent |
| description | User reports network calls fail roughly 30% of the time on iOS 17.4, iPhone 14 Pro. iOS 16 and Android unaffected. Failure mode: request returns 0 bytes after ~5s timeout. Expected: 200 OK with payload. No correlation with network type (wifi vs LTE). Failure happens both in app foreground and after backgrounding. |
| severity | high |
| status | open |
| diagnostic_state | triage |
| owner_uncertain | false (initially — we assumed SDK) |
| owner | (ROM Mobile SDK team) |
| reporter | (user-reporting agent) |
| linked_outcome_id | null |
Day 2: triage promoted to investigating. diagnostic_state: investigating. status: in-progress.
Day 3: SDK team realizes the failure mode looks like it could be either SDK or host-app config. They flip owner_uncertain: true. owner stays set but the team treats it as provisional.
Day 5: Investigators request device logs from the user. diagnostic_state: awaiting_evidence. The Issue can't progress until the logs arrive.
Day 8: Logs arrive. They show a host-app code path setting an HTTP timeout to 5s that overrides the SDK default of 30s. The SDK is fine; the host app is misconfigured. investigation_notes updated with the full trace.
Day 9: diagnostic_state: misdiagnosed (we thought SDK; the actual bug is host-app config). The Issue stays open under its new framing.
Day 10: Confirmed with host-app team that they own the config. diagnostic_state: not_our_bug. owner: null (host-app team owns; they file their own Issue). status: resolved (from ROM Mobile's perspective). owner_uncertain: false.
Why this matters: v3.0's open → in-progress → resolved → closed would have shown a flat lifecycle with no visibility into the investigation arc. Investigators looking at the Issue mid-arc would not have known if "in-progress" meant "we're root-causing" or "we're waiting for device logs" or "we already think it's misdiagnosed." The diagnostic_state timeline is the audit trail of the detective work. investigation_notes captures the working hypothesis without polluting the comment thread.
Audit reference: VALIDATION.md ROM Mobile section; v7.0 decisions in DECISIONS-RESOLVED.md#D-07 and DECISIONS-RESOLVED.md#D-12.
Short examples
likely_resolved + awaiting user confirmation. A fix shipped to production. The reporting user has been notified.
status: resolved,diagnostic_state: likely_resolved. The Issue stays open until the user confirms the symptom is gone. If the user reports the symptom persists,diagnostic_statereturns toinvestigatingandstatusreturns toin-progress. Common pattern, especially for intermittent bugs where the team can't reproduce.misdiagnosed. A Cortex Issue filed as "Cortex inference latency spike" turned out to be a database connection-pool exhaustion in a downstream service. The Cortex Issue gets
diagnostic_state: misdiagnosed; the team opens a new Issue against the downstream service withlinked_outcome_idto whatever capacity work fixes it. The original Issue stays in the audit trail withinvestigation_notesdocumenting the misdiagnosis.not_our_bug (clean). A third-party API returns 500s on a specific input. Logged for awareness with
diagnostic_state: not_our_bugfrom the start; ownership is unambiguous (the third-party tracker URL goes ininvestigation_notes). The Issue closes quickly because the team's role is "track it for our incident timeline," not "fix it."
Anti-patterns
Anti-pattern: Using only OPEN/IN_PROGRESS/DONE for SDK debugging
ROM Mobile tried to live with v3.0 status only. Investigators kept losing context — "is this open because we haven't looked at it, or because we're awaiting device logs from the user, or because we already think it's misdiagnosed?" Without diagnostic_state, the status field had to carry too many meanings; reviewers in the Weekly Pulse couldn't tell the difference between "we haven't started" and "we're blocked on the reporter."
The correct framing: status captures lifecycle (open → in-progress → resolved → closed). diagnostic_state captures investigation state. Both are needed. They move independently. Reports filter on both: "show me Issues stuck in awaiting_evidence more than 7 days" is the kind of question v3.0 couldn't answer cleanly.
Anti-pattern: Forcing owner assignment when ownership is uncertain
Filing an Issue and assigning it to a team that turns out not to own the code wastes that team's time and damages cross-team trust. The SDK team gets pinged about a host-app config bug; they triage, eventually find it's not their code, push back; the host-app team sees the bounce and assumes the SDK team is dodging work. Cross-team trust erodes faster than the bug gets fixed.
The correct framing: owner_uncertain: true on initial filing when SDK-vs-host-app or similar ambiguity exists. The Issue is filed with the symptom and repro; ownership is honestly "under investigation." Diagnostic work clarifies ownership; owner gets set then. No team gets pinged about something that isn't theirs.
Anti-pattern: Burying investigation history in comments
Plain debugged a single Issue across 27 comments over 3 weeks. Reconstructing "what did we already try?" required reading every comment in order, including the ones that were dead-ends, including the ones that were back-and-forth about scheduling the next investigation session. The signal-to-noise ratio for someone joining the investigation late was awful.
The correct framing: investigation_notes is the structured field for the live working hypothesis. Comments are for back-and-forth conversation. When someone joins the investigation, they read investigation_notes first — that's the current state of the detective work. Comments are the meeting transcript; investigation_notes is the case file.
Cross-refs
- METHOD.md — Issue is one of the 9 method primitives
- OUTCOMES.md — Outcomes that fix bugs link to Issues via
linked_outcome_id - FLOW-AND-SIGNAL.md — Issues with
diagnostic_state: awaiting_evidencebeyond N days surface as Attention Alerts - CROSS-CLAN.md — CrossClanDependency vs Issue disambiguation (coordination vs defect)
- VOCABULARY.md — preferred terms (and "ticket"→"Issue" is in the banned-terms list)
- PORTFOLIO.md — clan context for ROM Mobile, Plain
docs/methodology/validation/VALIDATION.md— ROM Mobile diagnostic evidence; 6-clan ambiguity patterndocs/methodology/validation/DECISIONS-RESOLVED.md— D-07 (diagnostic_state+investigation_notes) and D-12 (owner_uncertain)
Relationship to Phase 12
This doc is the contract Phase 12 (Outcome / Issue / Milestone Field Additions) must satisfy for the Issue surface. The three new fields — diagnostic_state, investigation_notes, owner_uncertain — get added to the existing v3.0 Issue schema:
- Zod schema additions in
@canopy/shared/issue.ts - Drizzle migration adding the three columns to
issues - tRPC procedure updates in
@canopy/api/router/issue.tsfor the new fields - UI updates in
apps/web/app/[workspace]/issues/—diagnostic_statedropdown,investigation_notestextarea,owner_uncertaincheckbox
Migration safety: all three fields are nullable with defaults that match existing rows (diagnostic_state: triage, investigation_notes: null, owner_uncertain: false). No backfill required.