{
  "blueprint": "saas-spend-tracker",
  "builder": "Claude, following the phase prompts in order",
  "testedSpecSha256": "0a1593e4b4844c6ffd22e8f09b361d3554c8c44768ef3c6ec8255d6e87090156",
  "reviewDate": "2026-10-16",
  "reviewMethod": "Reviewed both saved full-browser-run records against all 31 checklist items and re-ran the 99 unit tests. This review did not repeat browser execution. Results are final post-fix checks, not first-attempt success rates or an independent human audit.",
  "automatedTests": {
    "passed": 99,
    "total": 99,
    "command": "npm test",
    "rerunDuringReview": true
  },
  "gapsFixed": 6,
  "runs": [
    {
      "startedAt": "2026-10-16T00:06:59.366Z",
      "finishedAt": "2026-10-16T00:07:16.623Z",
      "phases": [
        1,
        2,
        3,
        4
      ],
      "passed": 31,
      "total": 31,
      "items": [
        {
          "phase": 1,
          "text": "Admin: People lists seven people, with Dana Ruiz and Ana Lima as admins. Tools owned: Dana 2, Maya 1, Sam 1, Ben 1, and 0 for Ana, Jo and Lee.",
          "status": "pass",
          "evidence": "People table shows 7 rows; Admin badge on Ana Lima and Dana Ruiz; Tools owned column: Ana 0, Ben 1, Dana 2, Jo 0, Lee 0, Maya 1, Sam 1."
        },
        {
          "phase": 1,
          "text": "Signed in as Jo, there are no Admin links, and opening the People page by its address shows \"Only finance admins can open this page.\" Dana and Ana can open all three admin pages.",
          "status": "pass",
          "evidence": "As Jo: 0 links to /admin and no Admin item in the nav; /admin/people shows \"Only finance admins can open this page.\" and no table; POST /api/admin/departments as Jo returned 403. Dana and Ana each opened People, Departments and Settings (headings shown: 6/6)."
        },
        {
          "phase": 1,
          "text": "Admin: Departments lists Design, Engineering, Finance, IT and Ops. Adding \"ops\" is refused with \"A department called Ops already exists.\" Deleting Ops is refused with \"Ops has 2 tools. Move them first.\"",
          "status": "pass",
          "evidence": "Departments table lists Design, Engineering, Finance, IT, Ops. Adding \"ops\" showed \"A department called Ops already exists.\"; Delete on Ops showed \"Ops has 2 tools. Move them first.\"; after reload the same five departments are listed."
        },
        {
          "phase": 1,
          "text": "As Ana, deactivating Sam asks for a new owner for GitHub. After choosing Lee Park, Sam is inactive, Lee owns 1 tool, and \"sign in as\" Sam shows \"Ask a finance admin to add you.\"",
          "status": "pass",
          "evidence": "Deactivate dialog said: \"Sam Okafor owns 1 tool: GitHub. Choose one new owner for it.\". After choosing Lee Park: Sam's row shows Inactive, Lee's Tools owned is 1. Signing in as Sam shows only \"Ask a finance admin to add you.\""
        },
        {
          "phase": 1,
          "text": "As Ana, removing Dana's admin flag works. Removing Ana's own admin flag is then refused with \"At least one active admin is required.\"",
          "status": "pass",
          "evidence": "Edit Dana → untick Finance admin → Save: \"Saved Dana Ruiz.\" and her Admin badge is gone. Edit Ana → untick → Save: the dialog shows \"At least one active admin is required.\"; after reload Ana is still Admin."
        },
        {
          "phase": 1,
          "text": "In the development toolbar, \"Today is\" shows today's date; +1 day shows tomorrow, and Back to real today returns to today. Reset seed data restores Sam (active, owner of GitHub) and Dana's admin flag.",
          "status": "pass",
          "evidence": "Toolbar read \"Today is Oct 16, 2026\" (real UTC date 2026-10-16); after +1 day \"Today is Oct 17, 2026\"; after Back to real today \"Today is Oct 16, 2026\". After Reset seed data: Sam Active with 1 tool owned (his deactivate prompt names GitHub), Dana has the Admin badge."
        },
        {
          "phase": 2,
          "text": "The Inventory tiles show annual run rate $31,320, 4 active tools, 141 of 190 seats used, estimated unused seat cost $8,940, and Ending: 1 tool, $2,400.",
          "status": "pass",
          "evidence": "Signed in as Jo, the Inventory tiles read: Annual run rate $31,320; Active tools 4; Seats used 141 of 190; Estimated unused seat cost $8,940; Ending 1 tool, $2,400."
        },
        {
          "phase": 2,
          "text": "Notion's row shows $1,000 / month and $12,000 annualized. Zoom's row shows renews on T+49 and notice deadline T+19.",
          "status": "pass",
          "evidence": "Notion row: price \"$1,000 / month\", annualized \"$12,000\". Zoom row: renews on \"Dec 4, 2026\" (T+49 with T = Oct 16, 2026), notice deadline \"Nov 4, 2026\" (T+19)."
        },
        {
          "phase": 2,
          "text": "Searching \"maya\" shows only Figma. With the search cleared, the Cancelling filter shows only Monday.com, Cancelled shows only Loom, and All shows six tools.",
          "status": "pass",
          "evidence": "Search \"maya\" → table shows only Figma. Search cleared; Status = Cancelling → only Monday.com; Cancelled → only Loom; All → 6 rows (Figma, GitHub, Loom, Monday.com, Notion, Zoom), \"Showing 6 tools\"."
        },
        {
          "phase": 2,
          "text": "Departments shows Design $9,600 / $2,880, Engineering $2,520 / $0, Finance $0 / $0, IT $7,200 / $2,700, Ops $12,000 / $3,360 with 1 ending ($2,400), and a total row of $31,320 / $8,940.",
          "status": "pass",
          "evidence": "Departments table: Design $9,600 / $2,880 (ending: none); Engineering $2,520 / $0 (ending: none); Finance $0 / $0 (ending: none); IT $7,200 / $2,700 (ending: none); Ops $12,000 / $3,360 (ending: 1 tool, $2,400); Total $31,320 / $8,940 (ending: 1 tool, $2,400), which matches the Inventory tiles."
        },
        {
          "phase": 2,
          "text": "As Ana, add Linear: owner Lee, Engineering, $100 monthly, 10 seats purchased, 8 used, renews T+20. The preview shows $1,200 annualized before saving. After saving, the run rate is $32,520, unused seat cost $9,180, and Engineering $3,720.",
          "status": "pass",
          "evidence": "Add tool form filled (Linear, Lee Park, Engineering, monthly, 100, 10 purchased, 8 used, renews Nov 5, 2026); preview before saving: \"Annualized: $1,200 · Notice deadline: Nov 5, 2026 · Reminder on: Oct 6, 2026\". Saved: detail shows $1,200 annualized; tiles show run rate $32,520, unused seat cost $9,180; Departments shows Engineering $3,720."
        },
        {
          "phase": 2,
          "text": "As Ana, adding a tool named \"figma \" is refused with \"A tool named Figma already exists.\" A tool with 25 seats used of 20 is refused with \"Seats used (25) cannot be more than seats purchased (20).\" The form keeps the typed values and no tool is saved.",
          "status": "pass",
          "evidence": "\"figma \" → the Name field shows \"A tool named Figma already exists.\" and keeps \"figma \". Renamed to Framer with 20 purchased / 25 used → Seats used shows \"Seats used (25) cannot be more than seats purchased (20).\"; the form still holds {\"name\":\"Framer\",\"purchased\":\"20\",\"used\":\"25\",\"price\":\"500\",\"renews\":\"2026-12-15\"}. Inventory (All) lists 7 tools: one Figma, no Framer."
        },
        {
          "phase": 2,
          "text": "As Maya, change Figma's seats used from 14 to 12. Figma's unused seat cost becomes $3,840, the company total rises by $960, and History shows \"Seats used 14 → 12\" by Maya Chen.",
          "status": "pass",
          "evidence": "As Maya: Edit Figma, Seats used 14 → 12, Save. Detail shows unused seat cost \"$3,840 (8 unused seats)\". Inventory tile Estimated unused seat cost $9,180 → $10,140 (+$960). History entry: \"Oct 16, 2026, 00:07 Maya Chen Edited Seats used 14 → 12\" (who: Maya Chen); newest entry: \"Oct 16, 2026, 00:07 Daily job Renewal reminder queued To Maya Chen: Figma: decide by Nov 15, 2026\"."
        },
        {
          "phase": 2,
          "text": "As Maya, Zoom has no Edit button and a direct save request for Zoom is refused. On Figma, owner and department are read-only, and a direct request that changes Figma's owner is refused.",
          "status": "pass",
          "evidence": "As Maya: Zoom's actions area shows no Edit (\"\"); PATCH /api/tools/<Zoom> (seats_used 20, current version) → 403 \"Only the owner of Zoom or a finance admin can change it.\", Zoom still 25 used of 40. Figma's edit form shows Owner \"Maya Chen\" and Department \"Design\" as read-only text (no selects); PATCH /api/tools/<Figma> with owner_id = Lee Park → 403 \"Only finance admins can change a tool's owner or department.\", owner still Maya Chen."
        },
        {
          "phase": 2,
          "text": "As Ana, open Figma's edit form in two tabs. Save a price change in the first; saving the second is refused with \"Someone else changed Figma. Reload to see their changes.\"",
          "status": "pass",
          "evidence": "Two tabs on Figma's edit form. Tab 1: price 9000 → \"Saved Figma.\". Tab 2: price 8800 → Save shows \"Someone else changed Figma. Reload to see their changes.\" (with a Reload link) and keeps 8800 in the field. Figma's price is $9,000 / year (tab 1's change only)."
        },
        {
          "phase": 3,
          "text": "Press Run daily job now. The outbox holds exactly four renewal reminders, all logged: Figma to Maya, Notion to Dana, Zoom to Ben, GitHub to Sam, and nothing for Monday.com or Loom. Running the job again still leaves four. As Ben, Notifications shows only the Zoom reminder, which says it renews on T+49 and the last day to cancel or change it is T+19.",
          "status": "pass",
          "evidence": "Outbox empty after reset; Run daily job now → 4 rows, all \"Renewal reminder\" with status logged: Figma → Maya Chen, GitHub → Sam Okafor, Notion → Dana Ruiz, Zoom → Ben Cho; none for Monday.com or Loom. Second run → still 4 rows. As Ben, Notifications lists 1 item: \"Zoom: decide by Nov 4, 2026\" — \"Zoom renews on Dec 4, 2026, automatically. Last day to cancel or change it: Nov 4, 2026 (in 19 days). Cost: $7,200 a year. Seats: 25 of 40 used.\""
        },
        {
          "phase": 3,
          "text": "Renewals lists, under \"Decide within 30 days\": Notion (T+7), GitHub (T+19), Zoom (T+19), Figma (T+30), in that order. Monday.com appears only under Ending, ending T+12.",
          "status": "pass",
          "evidence": "\"Decide within 30 days\" rows in order (name and notice deadline): Notion (Oct 23, 2026), GitHub (Nov 4, 2026), Zoom (Nov 4, 2026), Figma (Nov 15, 2026). Other groups empty of Monday.com; Ending lists only Monday.com, ends Oct 28, 2026 (T+12)."
        },
        {
          "phase": 3,
          "text": "As Ben, Mark cancelling on Zoom without a reason is refused with \"Give a reason of at least 3 characters.\" With \"Moving to Google Meet\", the run rate drops to $24,120 and Zoom appears under Ending. Undo cancelling brings the run rate back to $31,320.",
          "status": "pass",
          "evidence": "As Ben on Zoom: Mark cancelling with an empty reason → dialog shows \"Give a reason of at least 3 characters.\" (a direct POST with reason \"no\" → 400 \"Give a reason of at least 3 characters.\"). With \"Moving to Google Meet\" → Zoom badge Cancelling; run rate tile $24,120; Ending tile \"2 tools, $9,600\"; Renewals → Ending lists Monday.com, Zoom. Undo cancelling → Active; run rate $31,320."
        },
        {
          "phase": 3,
          "text": "Move the clock to T+8. Notion's renewal date is now one month after T+7, its History shows \"Renewed automatically\" by Daily job, the run rate is still $31,320, and the outbox has a fifth reminder (Notion to Dana, for the new date). Move on to T+15: Monday.com is Cancelled, ended on T+12, with \"Term ended\" in its History, and the Ending tile shows 0 tools.",
          "status": "pass",
          "evidence": "Clock +7 days, +1 day → toolbar Oct 24, 2026. Notion renews on Nov 23, 2026 (one month after Oct 23, 2026); History: \"Renewed automatically: Oct 23 → Nov 23, 2026\" by Daily job. Run rate tile $31,320. Outbox has 5 reminders, newest \"Notion: decide by Nov 23, 2026\" to Dana Ruiz. Clock +7 days → Oct 31, 2026: Monday.com badge Cancelled, Ended on Oct 28, 2026, History \"Term ended\" by Daily job; Ending tile \"0 tools\"."
        },
        {
          "phase": 3,
          "text": "Move the clock to T+22. Zoom is under \"Notice deadline passed\", and as Ben, Mark cancelling on Zoom shows \"The notice deadline for Zoom was …\" before anything is saved. Close the dialog without confirming.",
          "status": "pass",
          "evidence": "Clock +7 days → Nov 7, 2026. Renewals: Zoom under \"Notice deadline passed\" (deadline Nov 4, 2026, \"Notice deadline passed 3 days ago\"). As Ben, Mark cancelling on Zoom opens the dialog with \"The notice deadline for Zoom was Nov 4, 2026. The vendor may renew it anyway. If they will, record the renewal first, then mark it cancelling.\" before anything is saved; Close → Zoom still Active, same version (3) and same 4 history entries."
        },
        {
          "phase": 3,
          "text": "Move the clock to T+31. Figma (no auto-renew) is under \"Renewal date passed\" and still in the run rate. As Maya, Record renewal with the date T+20 is refused. The default date (one year after T+30) with price $8,400 and 16 seats purchased is accepted: Figma shows $8,400 annualized, the run rate drops by $1,200, and History shows the old and new date, price and seats.",
          "status": "pass",
          "evidence": "Clock → Nov 16, 2026. Renewals \"Renewal date passed\": Figma; run rate tile $31,320 with Figma Active. As Maya, Record renewal with Nov 5, 2026 → \"The new renewal date must be after today and after the current renewal date (Nov 15, 2026).\". Default date Nov 15, 2027, price 8400, seats 16 (preview \"Annualized: $9,600 now → $8,400 after this renewal ($8,400 / year)\") → \"Renewal recorded.\"; Figma annualized $8,400; run rate $31,320 → $30,120; History \"Recorded a renewal\" by Maya Chen: Price per cycle $9,600 → $8,400; Renews on Nov 15, 2026 → Nov 15, 2027; Seats purchased 20 → 16."
        },
        {
          "phase": 3,
          "text": "As Ana, change GitHub's owner to Lee. Lee's Notifications show \"Ana Lima made you the owner of GitHub\" and a renewal reminder for GitHub's current renewal date. Sam's earlier GitHub reminders are still addressed to Sam.",
          "status": "pass",
          "evidence": "As Ana: Edit GitHub → owner Lee Park → \"Saved GitHub.\" (renews on Dec 4, 2026). Lee's Notifications: \"Ana Lima made you the owner of GitHub (renews Dec 4, 2026, $2,520 a year).\" and reminder \"GitHub: decide by Dec 4, 2026\" (\"GitHub renews on Dec 4, 2026, automatically. Last day to can…\"). Sam's Notifications still list his 2 GitHub reminders (GitHub: decide by Dec 4, 2026; GitHub: decide by Nov 4, 2026); outbox GitHub reminders: 2 to Sam Okafor, 1 to Lee Park."
        },
        {
          "phase": 3,
          "text": "As Maya, Loom has no Restore button. As Ana, restore Loom with a renewal date 60 days after the clock's today: Loom is active, the run rate rises by $1,500, and History shows the restore.",
          "status": "pass",
          "evidence": "As Maya, Loom's actions show \"(none)\" (no Restore) and a direct restore request → 403 \"Only finance admins can restore tools.\". As Ana (clock Nov 16, 2026), Restore with Jan 15, 2027 → \"Restored.\"; badge Active, renews on Jan 15, 2027; History \"Restored\" by Ana Lima: Status cancelled → active; Reason Replaced by Zoom recordings → (none); Ended on Sep 1, 2026 → (none); Renews on Sep 1, 2026 → Jan 15, 2027. Run rate $30,120 → $31,620 (+$1,500)."
        },
        {
          "phase": 4,
          "text": "Filter the Inventory to Design and press Export CSV. The file has the header and one row, Figma, with cycle_price 9600.00, annualized_cost 9600.00, auto_renew no, notice_deadline equal to renews_on, seats_purchased 20 and seats_used 14.",
          "status": "pass",
          "evidence": "As Jo, Department = Design → table shows only Figma; Export CSV downloaded tools-2026-10-16.csv with 2 lines (header of 19 columns + 1 row): name Figma, cycle_price 9600.00, annualized_cost 9600.00, auto_renew no, renews_on 2026-11-15, notice_deadline 2026-11-15, seats_purchased 20, seats_used 14."
        },
        {
          "phase": 4,
          "text": "The Departments export has one row per department and a Total row with annual_run_rate 31320.00 and unused_seat_cost 8940.00.",
          "status": "pass",
          "evidence": "Departments → Export CSV downloaded departments-2026-10-16.csv: Design 9600.00/2880.00, Engineering 2520.00/0.00, Finance 0.00/0.00, IT 7200.00/2700.00, Ops 12000.00/3360.00, Total 31320.00/8940.00; Total row annual_run_rate 31320.00, unused_seat_cost 8940.00, active_tools 4, ending 1 (2400.00)."
        },
        {
          "phase": 4,
          "text": "As Maya, there is no Import CSV button, and a direct import request is refused.",
          "status": "pass",
          "evidence": "As Maya the Inventory has no Import CSV button; direct multipart POSTs of the sample file to /api/import/preview → 403 and /api/import/apply → 403 (\"Only finance admins can do this.\"); /import shows \"Only finance admins can open this page.\"; no tools were added."
        },
        {
          "phase": 4,
          "text": "As Ana, uploading the sample file from section 12 shows exactly the expected preview (1Password Create, GitHub Update with seats purchased 30 → 35, Miro Create, an error on line 5 and two errors on line 6). Import is disabled and the Inventory is unchanged.",
          "status": "pass",
          "evidence": "Preview: line 2 1Password Create [$450 / quarter, $1,800 a year, Ben Cho, IT]; line 3 GitHub Update [Seats purchased 30 → 35]; line 4 Miro Create [$1,000 a year, Jo Park, Design, no auto-renew, 30 days' notice]; line 5 Slack Error [Unknown department: Marketing (add it in Admin: Departments first)]; line 6 Asana Error [Invalid date: 2030-02-30 (use a real date in YYYY-MM-DD form) | Seats used (12) cannot be more than seats purchased (10).]. Ignored columns: none. Import button disabled with \"Fix the errors in the file and upload it again.\". Inventory (All) tiles and rows identical before and after."
        },
        {
          "phase": 4,
          "text": "Delete lines 5 and 6 and upload again. Import creates 1Password ($1,800 annualized) and Miro ($1,000 annualized, unused seat cost $666.67) and updates GitHub (unused seat cost $360). The run rate is $34,120 and the unused seat cost $10,416.67. GitHub's History shows \"Seats purchased 30 → 35\" from a CSV import by Ana Lima.",
          "status": "pass",
          "evidence": "File without lines 5 and 6 → preview 1Password Create, GitHub Update, Miro Create; Import → \"Created 2 tools, updated 1, 0 unchanged, 0 skipped.\". 1Password annualized $1,800; Miro $1,000 annualized, unused $666.67 (2 unused seats); GitHub unused $360 (5 unused seats); GitHub History \"Updated from a CSV import\" by Ana Lima: Seats purchased 30 → 35. Tiles: run rate $34,120, unused seat cost $10,416.67."
        },
        {
          "phase": 4,
          "text": "Ben has a notification \"Ana Lima made you the owner of 1Password\" and Jo has one for Miro. Departments shows Design $10,600 and IT $9,000.",
          "status": "pass",
          "evidence": "Ben's Notifications: \"Ana Lima made you the owner of 1Password (renews Jan 15, 2030, $1,800 a year).\". Jo's: \"Ana Lima made you the owner of Miro (renews Mar 1, 2030, $1,000 a year).\". Departments: Design $10,600, IT $9,000."
        },
        {
          "phase": 4,
          "text": "As Maya, set Figma's notes to =1+1 and save. In the exported file, opened in a text editor, the notes cell reads '=1+1.",
          "status": "pass",
          "evidence": "As Maya: Figma notes set to =1+1 and saved (detail shows \"=1+1\"). Exported line in the file: Figma,maya@northwind.example,Design,annual,9600.00,2026-11-15,no,0,20,14,,'=1+1,Maya Chen,9600.00,2026-11-15,2880.00,active,,"
        },
        {
          "phase": 4,
          "text": "Set the status filter to All, export, and upload that file unchanged as Ana. Every current tool shows No change, Loom shows Skipped (cancelled), the export-only columns are listed as ignored, and importing reports \"Created 0 tools, updated 0, 7 unchanged, 1 skipped.\"",
          "status": "pass",
          "evidence": "Status All → 8 rows; Export CSV (tools-2026-10-16.csv) uploaded unchanged: 1Password No change, Figma No change, GitHub No change, Miro No change, Monday.com No change, Notion No change, Zoom No change; Loom Skipped (cancelled); \"Ignored columns: owner_name, annualized_cost, notice_deadline, unused_seat_cost, status, status_reason, ended_on\". Import → \"Created 0 tools, updated 0, 7 unchanged, 1 skipped.\"."
        }
      ]
    },
    {
      "startedAt": "2026-10-16T00:10:48.831Z",
      "finishedAt": "2026-10-16T00:11:06.266Z",
      "phases": [
        1,
        2,
        3,
        4
      ],
      "passed": 31,
      "total": 31,
      "items": [
        {
          "phase": 1,
          "text": "Admin: People lists seven people, with Dana Ruiz and Ana Lima as admins. Tools owned: Dana 2, Maya 1, Sam 1, Ben 1, and 0 for Ana, Jo and Lee.",
          "status": "pass",
          "evidence": "People table shows 7 rows; Admin badge on Ana Lima and Dana Ruiz; Tools owned column: Ana 0, Ben 1, Dana 2, Jo 0, Lee 0, Maya 1, Sam 1."
        },
        {
          "phase": 1,
          "text": "Signed in as Jo, there are no Admin links, and opening the People page by its address shows \"Only finance admins can open this page.\" Dana and Ana can open all three admin pages.",
          "status": "pass",
          "evidence": "As Jo: 0 links to /admin and no Admin item in the nav; /admin/people shows \"Only finance admins can open this page.\" and no table; POST /api/admin/departments as Jo returned 403. Dana and Ana each opened People, Departments and Settings (headings shown: 6/6)."
        },
        {
          "phase": 1,
          "text": "Admin: Departments lists Design, Engineering, Finance, IT and Ops. Adding \"ops\" is refused with \"A department called Ops already exists.\" Deleting Ops is refused with \"Ops has 2 tools. Move them first.\"",
          "status": "pass",
          "evidence": "Departments table lists Design, Engineering, Finance, IT, Ops. Adding \"ops\" showed \"A department called Ops already exists.\"; Delete on Ops showed \"Ops has 2 tools. Move them first.\"; after reload the same five departments are listed."
        },
        {
          "phase": 1,
          "text": "As Ana, deactivating Sam asks for a new owner for GitHub. After choosing Lee Park, Sam is inactive, Lee owns 1 tool, and \"sign in as\" Sam shows \"Ask a finance admin to add you.\"",
          "status": "pass",
          "evidence": "Deactivate dialog said: \"Sam Okafor owns 1 tool: GitHub. Choose one new owner for it.\". After choosing Lee Park: Sam's row shows Inactive, Lee's Tools owned is 1. Signing in as Sam shows only \"Ask a finance admin to add you.\""
        },
        {
          "phase": 1,
          "text": "As Ana, removing Dana's admin flag works. Removing Ana's own admin flag is then refused with \"At least one active admin is required.\"",
          "status": "pass",
          "evidence": "Edit Dana → untick Finance admin → Save: \"Saved Dana Ruiz.\" and her Admin badge is gone. Edit Ana → untick → Save: the dialog shows \"At least one active admin is required.\"; after reload Ana is still Admin."
        },
        {
          "phase": 1,
          "text": "In the development toolbar, \"Today is\" shows today's date; +1 day shows tomorrow, and Back to real today returns to today. Reset seed data restores Sam (active, owner of GitHub) and Dana's admin flag.",
          "status": "pass",
          "evidence": "Toolbar read \"Today is Oct 16, 2026\" (real UTC date 2026-10-16); after +1 day \"Today is Oct 17, 2026\"; after Back to real today \"Today is Oct 16, 2026\". After Reset seed data: Sam Active with 1 tool owned (his deactivate prompt names GitHub), Dana has the Admin badge."
        },
        {
          "phase": 2,
          "text": "The Inventory tiles show annual run rate $31,320, 4 active tools, 141 of 190 seats used, estimated unused seat cost $8,940, and Ending: 1 tool, $2,400.",
          "status": "pass",
          "evidence": "Signed in as Jo, the Inventory tiles read: Annual run rate $31,320; Active tools 4; Seats used 141 of 190; Estimated unused seat cost $8,940; Ending 1 tool, $2,400."
        },
        {
          "phase": 2,
          "text": "Notion's row shows $1,000 / month and $12,000 annualized. Zoom's row shows renews on T+49 and notice deadline T+19.",
          "status": "pass",
          "evidence": "Notion row: price \"$1,000 / month\", annualized \"$12,000\". Zoom row: renews on \"Dec 4, 2026\" (T+49 with T = Oct 16, 2026), notice deadline \"Nov 4, 2026\" (T+19)."
        },
        {
          "phase": 2,
          "text": "Searching \"maya\" shows only Figma. With the search cleared, the Cancelling filter shows only Monday.com, Cancelled shows only Loom, and All shows six tools.",
          "status": "pass",
          "evidence": "Search \"maya\" → table shows only Figma. Search cleared; Status = Cancelling → only Monday.com; Cancelled → only Loom; All → 6 rows (Figma, GitHub, Loom, Monday.com, Notion, Zoom), \"Showing 6 tools\"."
        },
        {
          "phase": 2,
          "text": "Departments shows Design $9,600 / $2,880, Engineering $2,520 / $0, Finance $0 / $0, IT $7,200 / $2,700, Ops $12,000 / $3,360 with 1 ending ($2,400), and a total row of $31,320 / $8,940.",
          "status": "pass",
          "evidence": "Departments table: Design $9,600 / $2,880 (ending: none); Engineering $2,520 / $0 (ending: none); Finance $0 / $0 (ending: none); IT $7,200 / $2,700 (ending: none); Ops $12,000 / $3,360 (ending: 1 tool, $2,400); Total $31,320 / $8,940 (ending: 1 tool, $2,400), which matches the Inventory tiles."
        },
        {
          "phase": 2,
          "text": "As Ana, add Linear: owner Lee, Engineering, $100 monthly, 10 seats purchased, 8 used, renews T+20. The preview shows $1,200 annualized before saving. After saving, the run rate is $32,520, unused seat cost $9,180, and Engineering $3,720.",
          "status": "pass",
          "evidence": "Add tool form filled (Linear, Lee Park, Engineering, monthly, 100, 10 purchased, 8 used, renews Nov 5, 2026); preview before saving: \"Annualized: $1,200 · Notice deadline: Nov 5, 2026 · Reminder on: Oct 6, 2026\". Saved: detail shows $1,200 annualized; tiles show run rate $32,520, unused seat cost $9,180; Departments shows Engineering $3,720."
        },
        {
          "phase": 2,
          "text": "As Ana, adding a tool named \"figma \" is refused with \"A tool named Figma already exists.\" A tool with 25 seats used of 20 is refused with \"Seats used (25) cannot be more than seats purchased (20).\" The form keeps the typed values and no tool is saved.",
          "status": "pass",
          "evidence": "\"figma \" → the Name field shows \"A tool named Figma already exists.\" and keeps \"figma \". Renamed to Framer with 20 purchased / 25 used → Seats used shows \"Seats used (25) cannot be more than seats purchased (20).\"; the form still holds {\"name\":\"Framer\",\"purchased\":\"20\",\"used\":\"25\",\"price\":\"500\",\"renews\":\"2026-12-15\"}. Inventory (All) lists 7 tools: one Figma, no Framer."
        },
        {
          "phase": 2,
          "text": "As Maya, change Figma's seats used from 14 to 12. Figma's unused seat cost becomes $3,840, the company total rises by $960, and History shows \"Seats used 14 → 12\" by Maya Chen.",
          "status": "pass",
          "evidence": "As Maya: Edit Figma, Seats used 14 → 12, Save. Detail shows unused seat cost \"$3,840 (8 unused seats)\". Inventory tile Estimated unused seat cost $9,180 → $10,140 (+$960). History entry: \"Oct 16, 2026, 00:10 Maya Chen Edited Seats used 14 → 12\" (who: Maya Chen); newest entry: \"Oct 16, 2026, 00:10 Daily job Renewal reminder queued To Maya Chen: Figma: decide by Nov 15, 2026\"."
        },
        {
          "phase": 2,
          "text": "As Maya, Zoom has no Edit button and a direct save request for Zoom is refused. On Figma, owner and department are read-only, and a direct request that changes Figma's owner is refused.",
          "status": "pass",
          "evidence": "As Maya: Zoom's actions area shows no Edit (\"\"); PATCH /api/tools/<Zoom> (seats_used 20, current version) → 403 \"Only the owner of Zoom or a finance admin can change it.\", Zoom still 25 used of 40. Figma's edit form shows Owner \"Maya Chen\" and Department \"Design\" as read-only text (no selects); PATCH /api/tools/<Figma> with owner_id = Lee Park → 403 \"Only finance admins can change a tool's owner or department.\", owner still Maya Chen."
        },
        {
          "phase": 2,
          "text": "As Ana, open Figma's edit form in two tabs. Save a price change in the first; saving the second is refused with \"Someone else changed Figma. Reload to see their changes.\"",
          "status": "pass",
          "evidence": "Two tabs on Figma's edit form. Tab 1: price 9000 → \"Saved Figma.\". Tab 2: price 8800 → Save shows \"Someone else changed Figma. Reload to see their changes.\" (with a Reload link) and keeps 8800 in the field. Figma's price is $9,000 / year (tab 1's change only)."
        },
        {
          "phase": 3,
          "text": "Press Run daily job now. The outbox holds exactly four renewal reminders, all logged: Figma to Maya, Notion to Dana, Zoom to Ben, GitHub to Sam, and nothing for Monday.com or Loom. Running the job again still leaves four. As Ben, Notifications shows only the Zoom reminder, which says it renews on T+49 and the last day to cancel or change it is T+19.",
          "status": "pass",
          "evidence": "Outbox empty after reset; Run daily job now → 4 rows, all \"Renewal reminder\" with status logged: Figma → Maya Chen, GitHub → Sam Okafor, Notion → Dana Ruiz, Zoom → Ben Cho; none for Monday.com or Loom. Second run → still 4 rows. As Ben, Notifications lists 1 item: \"Zoom: decide by Nov 4, 2026\" — \"Zoom renews on Dec 4, 2026, automatically. Last day to cancel or change it: Nov 4, 2026 (in 19 days). Cost: $7,200 a year. Seats: 25 of 40 used.\""
        },
        {
          "phase": 3,
          "text": "Renewals lists, under \"Decide within 30 days\": Notion (T+7), GitHub (T+19), Zoom (T+19), Figma (T+30), in that order. Monday.com appears only under Ending, ending T+12.",
          "status": "pass",
          "evidence": "\"Decide within 30 days\" rows in order (name and notice deadline): Notion (Oct 23, 2026), GitHub (Nov 4, 2026), Zoom (Nov 4, 2026), Figma (Nov 15, 2026). Other groups empty of Monday.com; Ending lists only Monday.com, ends Oct 28, 2026 (T+12)."
        },
        {
          "phase": 3,
          "text": "As Ben, Mark cancelling on Zoom without a reason is refused with \"Give a reason of at least 3 characters.\" With \"Moving to Google Meet\", the run rate drops to $24,120 and Zoom appears under Ending. Undo cancelling brings the run rate back to $31,320.",
          "status": "pass",
          "evidence": "As Ben on Zoom: Mark cancelling with an empty reason → dialog shows \"Give a reason of at least 3 characters.\" (a direct POST with reason \"no\" → 400 \"Give a reason of at least 3 characters.\"). With \"Moving to Google Meet\" → Zoom badge Cancelling; run rate tile $24,120; Ending tile \"2 tools, $9,600\"; Renewals → Ending lists Monday.com, Zoom. Undo cancelling → Active; run rate $31,320."
        },
        {
          "phase": 3,
          "text": "Move the clock to T+8. Notion's renewal date is now one month after T+7, its History shows \"Renewed automatically\" by Daily job, the run rate is still $31,320, and the outbox has a fifth reminder (Notion to Dana, for the new date). Move on to T+15: Monday.com is Cancelled, ended on T+12, with \"Term ended\" in its History, and the Ending tile shows 0 tools.",
          "status": "pass",
          "evidence": "Clock +7 days, +1 day → toolbar Oct 24, 2026. Notion renews on Nov 23, 2026 (one month after Oct 23, 2026); History: \"Renewed automatically: Oct 23 → Nov 23, 2026\" by Daily job. Run rate tile $31,320. Outbox has 5 reminders, newest \"Notion: decide by Nov 23, 2026\" to Dana Ruiz. Clock +7 days → Oct 31, 2026: Monday.com badge Cancelled, Ended on Oct 28, 2026, History \"Term ended\" by Daily job; Ending tile \"0 tools\"."
        },
        {
          "phase": 3,
          "text": "Move the clock to T+22. Zoom is under \"Notice deadline passed\", and as Ben, Mark cancelling on Zoom shows \"The notice deadline for Zoom was …\" before anything is saved. Close the dialog without confirming.",
          "status": "pass",
          "evidence": "Clock +7 days → Nov 7, 2026. Renewals: Zoom under \"Notice deadline passed\" (deadline Nov 4, 2026, \"Notice deadline passed 3 days ago\"). As Ben, Mark cancelling on Zoom opens the dialog with \"The notice deadline for Zoom was Nov 4, 2026. The vendor may renew it anyway. If they will, record the renewal first, then mark it cancelling.\" before anything is saved; Close → Zoom still Active, same version (3) and same 4 history entries."
        },
        {
          "phase": 3,
          "text": "Move the clock to T+31. Figma (no auto-renew) is under \"Renewal date passed\" and still in the run rate. As Maya, Record renewal with the date T+20 is refused. The default date (one year after T+30) with price $8,400 and 16 seats purchased is accepted: Figma shows $8,400 annualized, the run rate drops by $1,200, and History shows the old and new date, price and seats.",
          "status": "pass",
          "evidence": "Clock → Nov 16, 2026. Renewals \"Renewal date passed\": Figma; run rate tile $31,320 with Figma Active. As Maya, Record renewal with Nov 5, 2026 → \"The new renewal date must be after today and after the current renewal date (Nov 15, 2026).\". Default date Nov 15, 2027, price 8400, seats 16 (preview \"Annualized: $9,600 now → $8,400 after this renewal ($8,400 / year)\") → \"Renewal recorded.\"; Figma annualized $8,400; run rate $31,320 → $30,120; History \"Recorded a renewal\" by Maya Chen: Price per cycle $9,600 → $8,400; Renews on Nov 15, 2026 → Nov 15, 2027; Seats purchased 20 → 16."
        },
        {
          "phase": 3,
          "text": "As Ana, change GitHub's owner to Lee. Lee's Notifications show \"Ana Lima made you the owner of GitHub\" and a renewal reminder for GitHub's current renewal date. Sam's earlier GitHub reminders are still addressed to Sam.",
          "status": "pass",
          "evidence": "As Ana: Edit GitHub → owner Lee Park → \"Saved GitHub.\" (renews on Dec 4, 2026). Lee's Notifications: \"Ana Lima made you the owner of GitHub (renews Dec 4, 2026, $2,520 a year).\" and reminder \"GitHub: decide by Dec 4, 2026\" (\"GitHub renews on Dec 4, 2026, automatically. Last day to can…\"). Sam's Notifications still list his 2 GitHub reminders (GitHub: decide by Dec 4, 2026; GitHub: decide by Nov 4, 2026); outbox GitHub reminders: 2 to Sam Okafor, 1 to Lee Park."
        },
        {
          "phase": 3,
          "text": "As Maya, Loom has no Restore button. As Ana, restore Loom with a renewal date 60 days after the clock's today: Loom is active, the run rate rises by $1,500, and History shows the restore.",
          "status": "pass",
          "evidence": "As Maya, Loom's actions show \"(none)\" (no Restore) and a direct restore request → 403 \"Only finance admins can restore tools.\". As Ana (clock Nov 16, 2026), Restore with Jan 15, 2027 → \"Restored.\"; badge Active, renews on Jan 15, 2027; History \"Restored\" by Ana Lima: Status cancelled → active; Reason Replaced by Zoom recordings → (none); Ended on Sep 1, 2026 → (none); Renews on Sep 1, 2026 → Jan 15, 2027. Run rate $30,120 → $31,620 (+$1,500)."
        },
        {
          "phase": 4,
          "text": "Filter the Inventory to Design and press Export CSV. The file has the header and one row, Figma, with cycle_price 9600.00, annualized_cost 9600.00, auto_renew no, notice_deadline equal to renews_on, seats_purchased 20 and seats_used 14.",
          "status": "pass",
          "evidence": "As Jo, Department = Design → table shows only Figma; Export CSV downloaded tools-2026-10-16.csv with 2 lines (header of 19 columns + 1 row): name Figma, cycle_price 9600.00, annualized_cost 9600.00, auto_renew no, renews_on 2026-11-15, notice_deadline 2026-11-15, seats_purchased 20, seats_used 14."
        },
        {
          "phase": 4,
          "text": "The Departments export has one row per department and a Total row with annual_run_rate 31320.00 and unused_seat_cost 8940.00.",
          "status": "pass",
          "evidence": "Departments → Export CSV downloaded departments-2026-10-16.csv: Design 9600.00/2880.00, Engineering 2520.00/0.00, Finance 0.00/0.00, IT 7200.00/2700.00, Ops 12000.00/3360.00, Total 31320.00/8940.00; Total row annual_run_rate 31320.00, unused_seat_cost 8940.00, active_tools 4, ending 1 (2400.00)."
        },
        {
          "phase": 4,
          "text": "As Maya, there is no Import CSV button, and a direct import request is refused.",
          "status": "pass",
          "evidence": "As Maya the Inventory has no Import CSV button; direct multipart POSTs of the sample file to /api/import/preview → 403 and /api/import/apply → 403 (\"Only finance admins can do this.\"); /import shows \"Only finance admins can open this page.\"; no tools were added."
        },
        {
          "phase": 4,
          "text": "As Ana, uploading the sample file from section 12 shows exactly the expected preview (1Password Create, GitHub Update with seats purchased 30 → 35, Miro Create, an error on line 5 and two errors on line 6). Import is disabled and the Inventory is unchanged.",
          "status": "pass",
          "evidence": "Preview: line 2 1Password Create [$450 / quarter, $1,800 a year, Ben Cho, IT]; line 3 GitHub Update [Seats purchased 30 → 35]; line 4 Miro Create [$1,000 a year, Jo Park, Design, no auto-renew, 30 days' notice]; line 5 Slack Error [Unknown department: Marketing (add it in Admin: Departments first)]; line 6 Asana Error [Invalid date: 2030-02-30 (use a real date in YYYY-MM-DD form) | Seats used (12) cannot be more than seats purchased (10).]. Ignored columns: none. Import button disabled with \"Fix the errors in the file and upload it again.\". Inventory (All) tiles and rows identical before and after."
        },
        {
          "phase": 4,
          "text": "Delete lines 5 and 6 and upload again. Import creates 1Password ($1,800 annualized) and Miro ($1,000 annualized, unused seat cost $666.67) and updates GitHub (unused seat cost $360). The run rate is $34,120 and the unused seat cost $10,416.67. GitHub's History shows \"Seats purchased 30 → 35\" from a CSV import by Ana Lima.",
          "status": "pass",
          "evidence": "File without lines 5 and 6 → preview 1Password Create, GitHub Update, Miro Create; Import → \"Created 2 tools, updated 1, 0 unchanged, 0 skipped.\". 1Password annualized $1,800; Miro $1,000 annualized, unused $666.67 (2 unused seats); GitHub unused $360 (5 unused seats); GitHub History \"Updated from a CSV import\" by Ana Lima: Seats purchased 30 → 35. Tiles: run rate $34,120, unused seat cost $10,416.67."
        },
        {
          "phase": 4,
          "text": "Ben has a notification \"Ana Lima made you the owner of 1Password\" and Jo has one for Miro. Departments shows Design $10,600 and IT $9,000.",
          "status": "pass",
          "evidence": "Ben's Notifications: \"Ana Lima made you the owner of 1Password (renews Jan 15, 2030, $1,800 a year).\". Jo's: \"Ana Lima made you the owner of Miro (renews Mar 1, 2030, $1,000 a year).\". Departments: Design $10,600, IT $9,000."
        },
        {
          "phase": 4,
          "text": "As Maya, set Figma's notes to =1+1 and save. In the exported file, opened in a text editor, the notes cell reads '=1+1.",
          "status": "pass",
          "evidence": "As Maya: Figma notes set to =1+1 and saved (detail shows \"=1+1\"). Exported line in the file: Figma,maya@northwind.example,Design,annual,9600.00,2026-11-15,no,0,20,14,,'=1+1,Maya Chen,9600.00,2026-11-15,2880.00,active,,"
        },
        {
          "phase": 4,
          "text": "Set the status filter to All, export, and upload that file unchanged as Ana. Every current tool shows No change, Loom shows Skipped (cancelled), the export-only columns are listed as ignored, and importing reports \"Created 0 tools, updated 0, 7 unchanged, 1 skipped.\"",
          "status": "pass",
          "evidence": "Status All → 8 rows; Export CSV (tools-2026-10-16.csv) uploaded unchanged: 1Password No change, Figma No change, GitHub No change, Miro No change, Monday.com No change, Notion No change, Zoom No change; Loom Skipped (cancelled); \"Ignored columns: owner_name, annualized_cost, notice_deadline, unused_seat_cost, status, status_reason, ended_on\". Import → \"Created 0 tools, updated 0, 7 unchanged, 1 skipped.\"."
        }
      ]
    }
  ],
  "notTested": [
    "Production sign-in: Google OpenID Connect sign-in limited to ALLOWED_EMAIL_DOMAIN is implemented but was not exercised (no Google credentials here). Every check signed in with the development-only \"sign in as\" switcher, which sets the same session cookie and goes through the same active-person check.",
    "Real email delivery: the Resend provider (RESEND_API_KEY, EMAIL_FROM) was not exercised. With no provider configured every notification was marked logged, which is what the browser checks saw. Retries and the failed status after 5 attempts are covered only by unit tests with a simulated failing provider.",
    "Scheduler: no real hourly scheduler ran. The job ran from the development toolbar (Run daily job now and clock moves); /api/cron/daily was only checked with curl (401 without or with a wrong secret, 200 with the right one).",
    "Production configuration as a whole (Google sign-in, email provider and scheduler together) was not run. With ENABLE_DEV_TOOLS unset, a curl spot-check showed every /api/dev/* endpoint answering 404 and no development toolbar.",
    "Time zones other than UTC and currencies other than USD are covered by unit tests only, not by the browser checks.",
    "Overlapping daily-job runs in separate processes were not load-tested; the unique dedupe_key and claimed send attempts are covered by unit tests in one process.",
    "Phone layout: checked in Chromium by measuring that no screen scrolls sideways at 390 px and by one phone screenshot; not tested on real phones or other browsers."
  ]
}
