{
  "blueprint": "feedback-inbox",
  "builder": "Claude, following the phase prompts in order (a fresh subagent per phase that saw only the spec and the build folder)",
  "testedSpecSha256": "edfeb8c48ca118de31015a2d09b4b77fc79208187ea3bed50ea246d18cd53a54",
  "reviewDate": "2026-10-28",
  "reviewMethod": "Playwright scripts (Chromium) clicked through the build as the named seed people and asserted on what was on screen, with direct requests where a check says the server must refuse something. The run below is the single final run on a fresh database after all spec gaps were fixed, resetting to the seed before each phase's checks. Earlier per-phase runs found the gaps and are not counted. Results are final post-fix checks, not first-attempt success rates or an independent human audit.",
  "automatedTests": {
    "passed": 157,
    "total": 157,
    "command": "npm test",
    "rerunDuringReview": true
  },
  "gapsFixed": 20,
  "runs": [
    {
      "startedAt": "2026-10-28T08:55:02.343Z",
      "finishedAt": "2026-10-28T08:55:17.403Z",
      "phases": [
        1,
        2,
        3,
        4
      ],
      "passed": 25,
      "total": 25,
      "items": [
        {
          "phase": 1,
          "text": "Signed in as Dana, Admin: Customers lists the four customers with their MRR and a company total of $3,929.",
          "status": "pass",
          "evidence": "Customers table has 4 rows: Acme Corp $2,400, Ridgeway $890, Olsen & Co $149, Harbor Studio $490; \"Company total MRR\" shows $3,929."
        },
        {
          "phase": 1,
          "text": "Signed in as Jo, Admin: Customers says \"Only admins can open this page.\" and a direct request to change Acme's MRR is refused.",
          "status": "pass",
          "evidence": "As Jo, /admin/customers shows \"Only admins can open this page.\" and no table; POST /admin/customers/cus_9416cd2486d10ab2 with mrr=1 (form) and mrr_cents=100 (JSON) as Jo both returned 403 ({\"error\":\"Only admins can do this.\"}); as Dana Acme Corp still shows $2,400."
        },
        {
          "phase": 1,
          "text": "Adding a customer with the domain acme.example is refused with \"acme.example already belongs to Acme Corp.\"",
          "status": "pass",
          "evidence": "Add customer \"Acme Two\" with domain acme.example showed \"acme.example already belongs to Acme Corp.\"; the customer list still has 4 rows."
        },
        {
          "phase": 1,
          "text": "Importing a CSV with the header \"name,domains,mrr\" and the rows \"Kestrel,kestrel.example,1200\" and \"Bluebird,bluebird.example;bluebird.io,75\" shows both rows as ready in the preview, then adds them with MRR $1,200 and $75.",
          "status": "pass",
          "evidence": "Uploaded customers.csv; preview rows: \"2 Kestrel kestrel.example $1,200 Ready\" / \"3 Bluebird bluebird.example bluebird.io $75 Ready\"; after Import the list shows \"Kestrel kestrel.example $1,200 Edit Archive\" and \"Bluebird bluebird.example bluebird.io $75 Edit Archive\"."
        },
        {
          "phase": 2,
          "text": "The Inbox tab shows Harbor Studio's quote with its MRR ($490), source Call and sender Rosa Diaz; the Linked tab shows four items.",
          "status": "pass",
          "evidence": "Inbox tab card: \"Harbor Studio $490 MRR Call From Rosa Diaz <rosa@harbor.example> · Received Oct 27, 2026 · Captured by Ben Cho Can our clients see a progress link without loggi\"; Linked tab lists 4 cards (tab count 4)."
        },
        {
          "phase": 2,
          "text": "Capturing a quote shorter than 10 characters, or with a received date in the future, is refused and saves nothing.",
          "status": "pass",
          "evidence": "Form with quote \"Too short\" refused (\"The quote must be at least 10 characters (it has 9).\"); form with received date 2026-11-02 refused (\"The received date can't be in the future.\"); direct POST /feedback returned 422 and 422; tab counts unchanged ({\"inbox\":1,\"needs_customer\":0,\"linked\":4,\"dismissed\":0})."
        },
        {
          "phase": 2,
          "text": "Linking Harbor's quote to a new theme typed as \"Client progress link\" moves it to the Linked tab; typing \"single sign-on\" as a new theme name links to nothing and shows \"A theme called Single sign-on already exists.\"",
          "status": "pass",
          "evidence": "Typing \"single sign-on\" on Harbor's card showed \"A theme called Single sign-on already exists.\" and the card stayed on the Inbox tab (Linked count 4); typing \"Client progress link\" moved it to the Linked tab with \"Theme: Client progress link\"."
        },
        {
          "phase": 2,
          "text": "A POST to /api/inbound with the wrong secret returns 401 and nothing appears.",
          "status": "pass",
          "evidence": "POST /api/inbound with \"Bearer wrong-secret\" returned 401 ({\"error\":\"Missing or wrong inbound secret.\"}); tab counts unchanged {\"inbox\":0,\"needs_customer\":0,\"linked\":5,\"dismissed\":0}; searching \"audit log\" finds nothing."
        },
        {
          "phase": 2,
          "text": "A POST with the right secret from lee@acme.example (external_id \"zd-1001\") puts the quote in the Inbox tab for Acme Corp with source Email; sending the same external_id again creates nothing new.",
          "status": "pass",
          "evidence": "First POST returned 201 id fb_eb78f5e02da7bef8; Inbox tab card: \"Acme Corp $2,400 MRR Email From Lee Wong <lee@acme.example> · Received Oct 28, 2026 · Inbound Audit log We need an audit log of who changed what in our workspac\"; repeat with zd-1001 returned 200 with the same id; tab counts unchanged {\"inbox\":1,\"needs_customer\":0,\"linked\":5,\"dismissed\":0}."
        },
        {
          "phase": 2,
          "text": "A POST from nina@newco.example lands in Needs customer, and trying to link it to a theme is refused with \"Assign a customer before linking this to a theme.\"",
          "status": "pass",
          "evidence": "POST from nina@newco.example returned 201 and the card is on Needs customer; Link to CSV export showed \"Assign a customer before linking this to a theme.\" and the card stayed there; direct POST /feedback/fb_0c3fdd34584297f3/link returned 422."
        },
        {
          "phase": 2,
          "text": "Dismissing Olsen's quote requires a reason; Restore returns it to the Inbox tab with no theme.",
          "status": "pass",
          "evidence": "Dismiss with an empty reason was refused (\"Give a reason for dismissing this feedback (at least 5 characters).\"); with \"Duplicate of a call note\" the card moved to Dismissed (\"Olsen & Co $149 MRR In-app From Kim Olsen <kim@olsen.example> · Received Oct 22, 2026 · Captured by Jo Park Would love S\"); Restore put it on the Inbox tab with no theme line (\"Olsen & Co $149 MRR In-app From Kim Olsen <kim@olsen.example> · Received Oct 22, 2026 · Captured by Jo Park Would love Slack alerts when a c\")."
        },
        {
          "phase": 2,
          "text": "On the Linked tab, combining the Ticket source filter with the search \"SSO\" shows only Priya Shah's quote.",
          "status": "pass",
          "evidence": "Linked tab with source Ticket and search \"SSO\" (/?tab=linked&source=ticket&customer=&q=SSO) shows 1 card: \"Acme Corp $2,400 MRR Ticket From Priya Shah <priya@acme.example> · Received Oct 19, 2026 · Inbound We need SSO before we can roll this out to the whol\"."
        },
        {
          "phase": 3,
          "text": "Themes sorted by Evidence lists Single sign-on (2), CSV export (1), Slack alerts (1); Single sign-on shows 1 account, $2,400 and 2 votes.",
          "status": "pass",
          "evidence": "Themes sorted by Evidence: Single sign-on (2), CSV export (1), Slack alerts (1); Single sign-on row shows Accounts 1, MRR $2,400, Votes 2."
        },
        {
          "phase": 3,
          "text": "Recording another vote for Harbor Studio on Single sign-on is refused with \"Harbor Studio has already voted for Single sign-on.\"; its MRR stays $2,400.",
          "status": "pass",
          "evidence": "Record vote for Harbor Studio showed \"Harbor Studio has already voted for Single sign-on.\"; theme still shows MRR $2,400 and Votes 2."
        },
        {
          "phase": 3,
          "text": "Moving Tom Becker's call to Slack alerts makes Slack alerts 2 pieces of evidence and $2,549, while Single sign-on keeps $2,400.",
          "status": "pass",
          "evidence": "Moved Tom Becker's call from the Single sign-on page to Slack alerts; Themes now shows Slack alerts evidence 2, accounts 2, MRR $2,549; Single sign-on evidence 1, MRR $2,400."
        },
        {
          "phase": 3,
          "text": "Signed in as Dana, promoting Slack alerts without an owner is refused; promoting it with Maya as owner puts it in the Under review lane and the outbox has \"You own Slack alerts on the roadmap\" for Maya.",
          "status": "pass",
          "evidence": "Promote with no owner chosen showed \"Choose an owner for this theme.\" and the theme stayed unpromoted; with Maya Chen the Under review lane shows \"Under review 1 Slack alerts Owner Maya Chen Evidence 2 MRR $2,549 Votes 0\"; Admin: Outbox row: \"Oct 28, 2026, 4:55 AM maya@northwind.example (Maya Chen) You own Slack alerts on the roadmap Logged\"."
        },
        {
          "phase": 3,
          "text": "Creating a theme \"Dark mode\", recording a vote for Ridgeway and promoting it is refused with \"Link at least one piece of feedback before promoting this theme.\"",
          "status": "pass",
          "evidence": "Created \"Dark mode\", recorded Ridgeway's vote (Votes 1); Promote with Maya Chen as owner showed \"Link at least one piece of feedback before promoting this theme.\" and Dark mode is not on the Roadmap."
        },
        {
          "phase": 3,
          "text": "Signed in as Jo, changing Single sign-on's status is refused; signed in as Sam, moving it to In progress without a note is refused, and with the note \"Work started\" it moves lanes and the history shows both earlier entries plus the new one.",
          "status": "pass",
          "evidence": "As Jo the Single sign-on page has no Change status form and POST /themes/thm_f096165626400cdd/status returned 403 (\"Only Sam Okafor (the owner) or an admin can change the status of Single sign-on.\"); as Sam, In progress with no note showed \"Add a note of at least 5 characters explaining the change.\" and history stayed at 2 rows; with \"Work started\" the Roadmap shows it in In progress and the history reads: Oct 25, 2026, 4:55 AM Under review Promoted to the roadmap Maya Chen | Oct 26, 2026, 4:55 AM Under review → Planned Scheduled for this quarter Sam Okafor | Oct 28, 2026, 4:55 AM Planned → In progress Work started Sam Okafor."
        },
        {
          "phase": 3,
          "text": "Signed in as Dana, moving Single sign-on to Planned emails Sam \"Single sign-on is now Planned\".",
          "status": "pass",
          "evidence": "As Dana, Single sign-on moved to Planned with note \"Moving back to next quarter\"; Admin: Outbox row: \"Oct 28, 2026, 4:55 AM sam@northwind.example (Sam Okafor) Single sign-on is now Planned Logged\"."
        },
        {
          "phase": 4,
          "text": "Follow-ups lists Ridgeway as pending for CSV export. Signed in as Maya, Send update queues one email to ella@ridgeway.example using the template, and the follow-up shows \"Emailed by Maya Chen\".",
          "status": "pass",
          "evidence": "Follow-ups lists Ridgeway (Pending) under CSV export; Maya's Send update added 1 outbox row: \"Oct 28, 2026, 4:55 AM ella@ridgeway.example CSV export is here Logged\", body \"Hi Ella, you asked us about CSV export. It's now live. Thanks for telling us, it helped us decide. The Northwind team\"; the CSV export follow-up list shows \"Emailed by Maya Chen\"."
        },
        {
          "phase": 4,
          "text": "Sending the update to Ridgeway again is refused with a message saying it was already emailed and by whom.",
          "status": "pass",
          "evidence": "Send update on Ridgeway again showed \"Ridgeway was already emailed on Oct 28 by Maya Chen.\"; a direct POST /follow-ups/fu_c0fe8642aff2e1f3/send returned 409; the outbox still has one \"CSV export is here\" email."
        },
        {
          "phase": 4,
          "text": "Signed in as Sam, moving Single sign-on to Shipped with a note creates pending follow-ups for Acme Corp and Harbor Studio; Acme's Send update queues emails to priya@acme.example and tom@acme.example, and Harbor offers only Mark contacted.",
          "status": "pass",
          "evidence": "Sam moved Single sign-on to Shipped (\"Released in version 2.15\"); Follow-ups lists pending Acme Corp and Harbor Studio; Harbor Studio shows Mark contacted and no Send update; Acme's Send update queued 2 \"Single sign-on is here\" emails to priya@acme.example and tom@acme.example."
        },
        {
          "phase": 4,
          "text": "Moving Single sign-on back to In progress cancels its pending follow-ups; shipping it again creates a new pending list.",
          "status": "pass",
          "evidence": "Moving Single sign-on to In progress (\"Found a bug with Okta\") left no pending Single sign-on follow-ups and the theme page shows Harbor Studio as Cancelled (Acme stays Emailed); shipping again (\"Fixed and released in 2.16\") put a new pending list on Follow-ups: Acme Corp, Harbor Studio (1 shipment group)."
        },
        {
          "phase": 4,
          "text": "Dana deletes Rosa Diaz's quote: it disappears everywhere, and its feedback_deleted activity entry contains no quote and no email address.",
          "status": "pass",
          "evidence": "Dana deleted Rosa Diaz's quote from the Inbox tab; none of /?tab=inbox, /?tab=needs_customer, /?tab=linked, /?tab=dismissed, /?q=Rosa, /?q=progress+link, /activity, /themes, /follow-ups, /export/feedback.csv contains the quote, \"Rosa Diaz\" or rosa@harbor.example; the Activity entry reads \"Oct 28, 2026, 4:55 AM · Feedback deleted Dana Ruiz deleted a piece of feedback from Harbor Studio\"."
        },
        {
          "phase": 4,
          "text": "Exporting themes downloads a CSV whose Single sign-on row has evidence 2, accounts 1, MRR 2400.00 and votes 2; the feedback CSV has no email column.",
          "status": "pass",
          "evidence": "themes-2026-10-28.csv header \"name,status,owner,evidence,accounts,mrr,votes\", Single sign-on row \"Single sign-on,Shipped,Sam Okafor,2,1,2400.00,2\"; feedback-2026-10-28.csv header \"customer,source,received_date,theme,state\" with no email column and no \"@\" anywhere in the file."
        }
      ]
    }
  ],
  "notTested": [
    "Production sign-in: single-use emailed sign-in links were built, but every check used the development \"sign in as\" switcher.",
    "Real email delivery: notifications were written to the outbox and logged, not sent.",
    "The hourly retry job on a real scheduler: it was run from the development control and its endpoint; a failing provider was simulated only in automated tests.",
    "The inbound endpoint behind a real email-forwarding service or help desk: checks called it directly.",
    "Two people linking the same feedback at the same moment from two browsers: automated tests only.",
    "Hosting: the build ran on SQLite on one server."
  ]
}