Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
# 目录不存在时 `pnpm scratch` 直接报错。
/e2e/scratch/*
!/e2e/scratch/.gitkeep
# drive 的浏览器状态与 serve 的交接件(含一次性会话 Cookie)。都是本次运行私有的。
/e2e/.drive/
/e2e/node_modules/
/e2e/test-results/
/e2e/playwright-report/
Expand Down
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Workspace-wide facts live in [`../AGENTS.md`](../AGENTS.md).
| Adding an endpoint / service / repository | [docs/architecture.md](docs/architecture.md) | Layering, dependency direction, "how to add an X" |
| Adding a scheduled task, or assuming only one replica is running | [docs/architecture.md](docs/architecture.md) | What you may assume about process-local state, how to add a cron job |
| Writing any test | [docs/testing.md](docs/testing.md) | What to write per layer, sqlmock vs mockgen, build tags, the guard tests |
| Confirming a change actually works | [docs/verification.md](docs/verification.md) | The twin e2e tracks, scratch workflow, report rules |
| Confirming a change actually works | [docs/verification.md](docs/verification.md) | Which form a one-off check takes — drive it by hand before writing a spec — scratch workflow, verdicts and report rules |
| Touching the frontend | [docs/design.md](docs/design.md) | Colour tokens, dark/light, responsive, i18n, the new-page recipe |
| Deploying, or changing the image/chart/workflow | [deploy/README.md](deploy/README.md) | Docker and Kubernetes deployment, chart values, etcd seeding, the Gitea pipeline |
| Adding a log line, metric or span | [docs/observability.md](docs/observability.md) | Log levels and fields, metrics, traces |
Expand Down Expand Up @@ -75,7 +75,7 @@ internal/
web/ embed.FS SPA mount, /v1 passthrough
migrations/ gormigrate; append-only
frontend/ React 19 + Vite + Tailwind + shadcn
e2e/ twin tracks: committed smoke + gitignored scratch
e2e/ committed smoke + on-demand full chain + `drive` (hand-driven) + gitignored scratch
```

Auth has three shapes, and which one a route uses is visible in `internal/api/router.go`:
Expand Down
2 changes: 1 addition & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ non-negotiables, and the routing table that says which of these to open when.
| [develop.md](develop.md) | Commands, repo layout, enforced rules + exemptions, migrations, commit flow |
| [architecture.md](architecture.md) | Layering, dependency direction, "how to add an X" |
| [testing.md](testing.md) | What to test per layer, sqlmock vs mockgen, build tags, guard tests |
| [verification.md](verification.md) | Twin e2e tracks, scratch workflow, report rules |
| [verification.md](verification.md) | Choosing the form a one-off verification takes, per surface; scratch workflow, verdicts and report rules |
| [design.md](design.md) | Tokens and the canvas↔code name mapping, type/spacing/radius scales, the auth shell and page skeleton, dark/light, responsive, i18n, new-page recipe |
| [observability.md](observability.md) | Logging, metrics, traces |
| [documentation.md](documentation.md) | Who owns which fact, how docs stay true |
Expand Down
138 changes: 94 additions & 44 deletions docs/references/verification-report-template.md
Original file line number Diff line number Diff line change
@@ -1,77 +1,127 @@
# Verification report template
<!-- Copy into e2e/scratch/<scenario>/ as report.md before running, and fill it in as you go. Headings stay English; write the record in the user's language. Delete unused sections and this comment. The rules behind this template are in ../verification.md. -->

Copy to `e2e/scratch/<task-name>/report.md` and fill in **as you go**, not afterwards.
Delete sections that do not apply — an empty heading is noise. The rules behind this
template are in [../verification.md](../verification.md).
# Verification: <scenario>

---
## Mode

# Verification: <what you built>
`verifying a change` | `reproducing a bug`

- **Date**: <YYYY-MM-DD>
- **Change**: <branch / commit / spec slug>
- **Verdict**: ✅ works / ⚠️ works with caveats / ❌ does not work
## Goal / problem

## What I claimed to have built
<The claim being tested — not a changelog. Or, for a bug, Expected/Actual.>

One or two sentences. The claim being tested — not a changelog.
## Environment

## How I verified it
<!-- What the run drove, so a reader can tell a mocked run from a real-backend one. -->

Environment (mocked / real MySQL + Redis / against staging), and the commands run.
- Form and entry point: `<curl / migrate command / pnpm serve + pnpm drive / full spec / pnpm dual>`
- Backend and data: `<mocked API, or the real server on :8443 with the db.dsn and redis.addr it used>`
- Build under test: `<branch, commit or spec slug>`
- Form factors driven: `<desktop-chromium / mobile-chromium / both — UI rows only>`

```bash
go run ./cmd/server
cd e2e && E2E_SCRATCH_AUTOSTART=1 pnpm scratch
```
## Verdict

<!-- Fill last. Keep verdicts only here. One row per claim — split a compound claim rather than averaging it. Where `not observed` came from unconfigured environment, "How observed" names the service and the absent config keys, never values. -->

| # | Requirement / bug claim | Verdict | Real / substituted | How observed | Check it yourself |
|---|---|---|---|---|---|
| V1 | `<one behaviour or bug claim, stated so it can only be true or false>` | holds / does not hold / not observed | real, or `substituted: <what stood in> — <what it does not cover>` | `<the runtime observation that decides it>` | `<command, or launch command plus steps>` |

Summary: <what holds, the deciding observation, every not-observed/failed item and shipping implication>.

| Label | Use it when | Requires |
|---|---|---|
| `holds` | you observed the behaviour at runtime | the deciding observation, and how a reader reaches it |
| `does not hold` | you observed it failing, or the bug reproducing | the failing output, assertion diff or error screenshot |
| `not observed` | you never reached the check | what stopped it |

An unreached check is never `holds`; a run that verified two of three claims is reported as two of three.

## Authorization

<!-- Keep only when a real dependency was substituted or an external effect was authorized. Calling a `mock*` helper from fixtures/app.ts is a deliberate substitution — mocking is opt-in here. -->

| # | Substitute or effect | The user's authorization, verbatim |
|---|---|---|
| V1 | `<what stood in for what, or the effect and what it touches>` | `<sentence>` |

## Reproduction steps

<!-- Keep for bug reproduction; state the polarity explicitly. -->

1. `<clean-checkout-to-observation steps>`

- [ ] Asserts the **expected** behaviour → currently **red**, turns green when fixed
- [ ] Asserts the **current buggy** behaviour → currently **green**, must be flipped when fixed

## Evidence
## Acceptance evidence

Inline, in whatever form fits what is being verified. One scroll to a verdict.
<!-- One `###` per verdict row, holding everything that decides it in the order observed. No verdict labels here. A row with no section is `not observed`. -->

### <Scenario 1>
### V1 · `<the claim, restated>`

```bash
```console
$ curl -s localhost:8443/v1/healthz
{"status":"ok","db_ping":true,"redis":true}
# exit 0
$ echo $?
0
```

What this proves: <one sentence>.
<What this proves>.

### <Scenario 2 — a UI change>
**Independent oracle.** Do not take the UI's word for it — read the data or the logs directly:

```sql
SELECT id, status, approved_at FROM device_flow_codes WHERE user_code = '<code>';
-- 1 row, status=approved, approved_at set
```

<!-- UI only; pair before/after or light/dark in one table so the comparison is one glance. Drop the backticks when filling this in — they keep the template itself from carrying broken image links. -->

| Before | After |
| --- | --- |
| `![before](screenshots/before.png)` | `![after](screenshots/after.png)` |
|---|---|
| `![before](screenshots/v1-before.png)` | `![after](screenshots/v1-after.png)` |

(Drop the backticks when you fill this in — they are here so the template itself
does not carry two permanently broken image links.)
## Evidence index

What this proves: <one sentence>.
- Commands/logs: `<inline deciding output plus optional full-file links>`
- Resources/data snapshots: `<paths and what each proves>`
- Screenshots/video: `<UI only; a recording carries a verdict only alongside its decisive stills>`

### Independent oracle
A scenario with no `screenshots/` is the right shape for an API, migration or daemon run.

Do not take the UI's word for it — check the data or the logs directly.
## Persistent data changes

```sql
SELECT id, status, approved_at FROM device_flow_codes WHERE user_code = 'A4F-7Q2';
-- 1 row, status=approved, approved_at set
```
<!-- Keep only when the run wrote data that outlives it — a real database, a migration against real rows. -->

| Change | Forward | Backward/backup | Before/after query |
|---|---|---|---|
| `<scope/blast radius>` | `<command/exit>` | `<command/exit or irreversible plan>` | `<evidence>` |

Dataset: `<source, size and representative edge values>`. **Green on an empty database is not evidence.**

## What I could NOT verify
## Execution record

Be specific about which part and why. "Everything checked out" with a gap left unstated is
the failure mode this section exists to prevent.
| Step | Status | Evidence/blocker |
|---|---|---|
| `<step>` | pending / passed / failed / blocked | `<path or observation>` |

## Known issues found along the way

Anything you noticed but did not fix — with enough detail for someone to pick it up.
Per the workspace rules, unrelated problems get reported, not fixed on the side.
<!-- Anything noticed but not fixed, with enough detail for someone to pick it up. Unrelated problems get reported, not fixed on the side. -->

## If this reproduces a bug
## Integrity and cleanup

State the polarity of the assertion explicitly:
- Initial/final HEAD: `<sha>` / `<sha>`
- Final `git status --porcelain=v1`: `<output>`
- Created artifacts, processes and external data, and how each was cleaned up: `<inventory; a server you started is one>`
- Redaction performed: `<what was removed>`

- [ ] Asserts the **expected** behaviour → currently **red**, turns green when fixed
- [ ] Asserts the **current buggy** behaviour → currently **green**, must be flipped when fixed
## Evidence rules

- Every `holds` names how the target was driven — command, or launch command plus steps — and the deciding observation.
- Where a claim changes state beyond the driven surface, that observation is an independent read with its own command: the database or the logs, not the UI.
- Embed decisive text and images inline; one scroll should reach a verdict. Bare links are for archives and binaries only, each with a note on what it holds.
- Paste terminal output as text. Screenshotting a terminal manufactures evidence instead of capturing it.
- Keep failed and unchecked steps visible. Redact tokens, secrets, real email addresses and session cookies before saving, and again before embedding.
- Keep every path relative to this file; the scenario directory, not `report.md` alone, is what you hand to a reviewer.
6 changes: 3 additions & 3 deletions docs/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ the package, which is the path by which a key would actually leak.
`git grep '//go:build'` returns nothing, and it should stay that way. Anything that seems
to need a tag is either (a) a test-only asset, which the package-isolation trick above
handles, or (b) a test needing external infrastructure — which gets its **own entry point**
(`make test-e2e`, or a scratch script per [verification.md](verification.md)), never a tag.
(`make test-e2e`, or a hand-driven run per [verification.md](verification.md)), never a tag.
A target you did not run is obvious; a tagged-out test is invisible.

## Migrations are deliberately untested
Expand All @@ -87,8 +87,8 @@ make dev # migrations run at startup against db.dsn; watch
mysql --host <host> --user <user> --password <database> -e 'SHOW TABLES' # read tables back directly
```

Write that check up under `e2e/scratch/` per [verification.md](verification.md) — for
migrations the evidence is the table list, not a screenshot.
Write that check up under `e2e/scratch/<scenario>/` per [verification.md](verification.md) —
for migrations the evidence is the table list, not a screenshot, and nothing needs authoring.

**What is untested is the DDL, not the runner.** `migrations/migrations_test.go` does use
sqlmock, on the named-lock wrapper `withMigrationLock` that serialises concurrently
Expand Down
Loading
Loading