# scout — changelog

Newest first. Every code change bumps `main/app/version.py` and adds a line here.

## 0.4.1 — 2026-08-18

Fixes for two bugs the M3 test suite caught in 0.4.0 — both broke the milestone's
own target scenarios:

- Per-project assignability: a scoped coordinator's power now resolves from
  their level ON THAT PROJECT (`assignable_on`), not their app-wide level, so
  the members-only coordinator can actually add people. (Kit feedback T9.)
- View As rank is derived from a level's permissions instead of its name, so a
  custom level holding `scout.view_as` can view lower-ranked users instead of
  being refused as rank zero. (Kit feedback T10.)
- Account routes now enforce the exact permissions the catalog claims:
  `accounts.add` to create, `accounts.change_level` to change levels,
  `accounts.delete` to deactivate (previously all gated on `accounts.view`).
- Invites can no longer change an existing account's level (a side door around
  the owner/self guards; refused with ACCOUNT_EXISTS).

## 0.4.0 — 2026-08-18 (the full BW Auth suite)

Everything the BW Auth standard offers, wired end to end — deliberately, to find
its edges. Findings live in `notes/bw-auth-feedback.md`.

- **View As** (`bw_view_as.py` vendored): the owner — or any level holding
  `scout.view_as` — renders Scout as a lower-ranked user. Read-only by default
  with a structural middleware write-block; `act` mode is owner-only. Per-request
  fail-closed re-verification; every impersonation reported to the central Live
  page; un-hideable banner.
- **Level editor**: levels are fully editable data now — create/edit/delete with
  a described permission catalog (kit standard + scout.*), custom permission
  strings allowed and honestly badged "not enforced yet", assignability as
  chips. Kit guards decide who may edit (owner, by default).
- **Finer permissions**: `scout.project.members` (manage one project's people —
  the scoped-coordinator level) and `scout.projects.delete` (per-project;
  deletion refuses while reviews exist unless forced, and cleans the disk).
  Membership routes accept manage OR members.
- **Invites**: create-and-email a BW account from Scout (gateway sends the link;
  Scout never sees it), app-wide via `accounts.add` or scoped to a project you
  hold membership powers on.
- **Resets**: self-service and admin (`accounts.reset_password`); anonymous
  "Forgot password?" on the sign-in screen — public, throttled,
  constant-response (a flagged stopgap: this belongs on the central login page).
- **Access matrix**: every user × every project with the effective level in each
  cell, resolved exactly like requests are.
- **Auth tab**: BW status/reachability, one-click re-sync of central visibility,
  cross-app access lookup per user.
- `/api/me` now carries the view-as block, `assignable_levels` (drives every
  level select — no hardcoded lists), and the central manage-account URL.
- Migration `0005_full_suite_permissions` (admin gains the two new permissions,
  append-only); `Meta.project_levels` removed in favor of `Me.assignable_levels`.

## 0.3.0 — 2026-08-18

Per-project access levels, on the BW Auth accounts kit (`app/bw_accounts.py`).
The app-wide two-value role is gone: a person now holds a named level, and that
level is assigned **per project**.

- Levels are data, in `app_levels`, seeded admin / lead / reviewer. `rian` is the
  owner — an immutable super admin the kit synthesizes, not a row.
- `lead` is the new capability: a client decision-maker who reviews *and* sees
  their project's results rollup. A `reviewer` on the same project cannot.
- Migration `0003_per_project_levels`: `app_levels`; `level` + `all_instances` on
  `app_accounts`; `level` on `project_members`; `role` dropped, not shadowed.
- Storage adapter (`services/accounts_store.py`) puts the kit's model on Scout's
  existing tables — no parallel schema.
- Two distinct questions, never interchangeable: `app_can` for app-wide
  permissions, `project_can` for per-project ones. Options and materials moved
  from an app-wide admin gate to a genuine per-project check.
- A project you cannot see is still 404; a project you can see but lack the
  permission on is now 403 — the distinction is deliberate.
- Deactivating an account removes it from the permission model entirely, so
  access stops on the next request rather than at the next login.
- The API sends capability booleans (`can_manage`, `can_view_results`,
  `is_staff`, …) instead of level names; the UI never re-derives policy.
- Access mirrors to auth.bowden.works via `report_access` / `report_instances`
  (display only — Scout remains the authority; no write-back, per D24).

## 0.2.1 — 2026-08-12

- Fix: `client_website` was silently dropped when creating a project. The schema
  accepted it and the New Project dialog sent it, but the router never passed it
  to the service, so the value vanished and only a later edit-and-save persisted
  it. Found while seeding test projects through the service layer.

## 0.2.0 — 2026-08-12 (overnight M1 build)

The direction loop, end to end: intake → briefing export → (Claude research
outside the app) → research-package import → per-person review game → results →
Scouting Report. Scout contains zero AI calls (D6); the two file contracts are
the seams.

- Schema `0002_direction_loop`: materials, options, reviews, aspect_votes,
  final_picks; `projects.client_website`.
- Research-package import (zip): whole-package validation before any write,
  zip-slip guard, schema_version check, idempotent by option slug — re-import
  updates in place and never touches collected reviews or publish state.
- Options admin: edit, publish/unpublish, reorder, delete (refused while
  reviews exist), anonymized-by-default with per-option `reveal_source` (D8).
- The review game (client-facing, mobile-first): welcome → one option per
  screen (gut scale 0–3, six aspect thumbs, optional note) → final pick →
  done. Per-reviewer stable-random option order (D11); every tap autosaves
  with upsert semantics (D12); partial sessions are real data. Admins preview
  through the same screens into their own rows.
- Results tab: per-option distributions + means (client ratings only), aspect
  tallies, attributed notes, reviewer completion, final picks, biggest-split
  callout. Admin preview rows shown separately, never averaged in (D7).
- Scouting Report download: markdown assembled server-side, sources revealed,
  designer-synthesis section up top, instructions-for-the-next-Claude footer (D16).
- Materials tab: notes, links, file uploads (25 MB cap, extension allowlist,
  UUID storage names); briefing-package export with `INSTRUCTIONS-FOR-CLAUDE.md`
  embedding the full import contract (D13).
- All files served through authed, membership-checked routes; draft screenshots
  are admin-only and return 404, never 403, to non-admins.
- `tools/capture/`: host-side Playwright screenshot tool (desktop 1440x900 +
  mobile 390x844 full-page) running in the pinned Playwright Docker image.
- `HEAD /` now serves the SPA shell instead of 405.
- pytest suite (42 tests) over the HTTP boundary against real Postgres: authz
  asymmetries, autosave idempotence, the whole import contract, one full loop.
- Compose: `./data:/app/data` bind mount for runtime files (workspace root, v8).

## 0.1.0 — 2026-08-11

Baseline: the shell the product hangs off.

- v8 workspace scaffolded; code moved into the `main/` instance.
- Stack stood up per `/srv/projects/standards/react.md`: FastAPI + React SPA in one
  Python process, Postgres sidecar on an internal-only network with no published port.
- BW Auth (Pattern B) sign-in: silent probe, interactive login, callback, full sign-out.
  Scout holds no credentials; `scout.bowden.works` registered as an app client and
  un-gated as Pattern B requires.
- Default-deny middleware over the whole API, with `/api/me` and `/api/meta` as the only
  public endpoints.
- Two roles (admin, user), seeded for the bootstrap admins from `SCOUT_INITIAL_ADMINS`.
- Scouting projects with membership as the access boundary; a project a user cannot see
  returns 404, not 403.
- Admins can add someone by BW username before that person's first sign-in — the username
  is resolved against BW, so an unknown one is rejected rather than creating an orphan row.
- TypeScript API client generated from the live OpenAPI schema inside the image build.
