QA: CLI, MCP, and API
Default to the smallest set of programmatic-interface lanes that can prove the
change. Automated tests are necessary but do not replace a live smoke test when
the changed boundary is only exercised by a real client or deployment.
This QA workflow is independent from releasing. A release request alone does
not invoke it, and its results do not approve or block a release.
Choose the scope first
Inspect the exact branch, commit, or diff and map changed code to its direct
consumers before creating fixtures or credentials. In a GitButler workspace,
use but status and but show <commit-or-branch>; do not use the synthetic
workspace HEAD as the candidate or combine unrelated applied branches.
Find the original reproduction in the current or past Codex task, linked
issue, PR, support report, or regression test. State the selected lanes and the
reason for each before testing.
Targeted regression mode (default)
Select lanes by behavior, not by the existence of this checklist:
- CLI parsing, output, or local DB access → CLI plus the closest contract tests.
- Webhook endpoints, signing, or delivery retries → the webhook cases only.
- Shared agent-access DTOs, exports, filtering, or pagination → every direct
consumer, including hosted REST or remote MCP when affected, plus
cross-surface parity only for the changed fields.
- Hosted auth, snapshots, entitlements, isolation, purge, or Supabase policy →
the affected hosted REST lifecycle and negative cases.
- Local or remote MCP protocol/tool changes → that MCP lane and its direct
transport/contract dependency.
- Shared hosted REST/MCP behavior → both hosted consumers, but not unrelated
local surfaces.
Run the closest affected automated tests, the original live reproduction, and
only credible boundary cases. A Rust or shared-crate change does not trigger
all lanes unless every lane consumes the changed behavior. Create only the
minimum non-sensitive fixture required for the selected checks. If a required
deployment, account, fixture, or client is unavailable, mark that check
BLOCKED; do not substitute unrelated lanes. Stop when the mapped risks are
covered.
Comprehensive Interface QA
Run every fixture, baseline, live lane, lifecycle case, privacy check, and
cross-surface comparison below only when the user explicitly requests full or
comprehensive programmatic-interface QA.
Safety and evidence
- Use a dedicated QA account and non-sensitive fixture meetings.
- Use a Pro or trialing account for the hosted lane and a separate free or
expired test account for entitlement checks.
- Never put API keys, JWTs, webhook secrets, database credentials, or personal
meeting content in commands, screenshots, reports, or repository files.
Load secrets into environment variables without echoing them.
- Store transient response bodies in a directory created with
mktemp -d.
Remove that directory and revoke all generated keys after the run.
- Record the exact candidate commit, app version, API deployment, Supabase
migration version, operating system, and client versions.
- In a GitButler workspace, identify the selected branch tip with
but status
and inspect it with but show <branch>; do not use the synthetic workspace
HEAD as the candidate identity.
- Mark a lane
BLOCKED, not PASS, when its required deployment, account,
fixture, or client is unavailable.
Comprehensive QA Fixture
Create two completed QA meetings through the desktop app:
- A standalone meeting whose title contains a unique run marker such as
agent-access-2026-07-28T120000Z.
- Two meetings in the same recurring series.
The fixture must include:
- a note and at least one generated summary;
- two participants and one action item;
- enough transcript words to require two pages when requested with a small
limit;
- punctuation and non-ASCII text;
- a later edit to the title, note, or summary;
- one meeting that will be deleted during lifecycle testing.
Record the meeting IDs and expected visible values. Do not seed SQLite or
Postgres directly for the live happy-path tests.
Comprehensive Automated Baseline
Run from the repository root:
cargo test -p anarlog-cli
cargo test -p tauri-plugin-local-api
cargo test -p api-cloud
cargo test -p api openapi::tests
cargo check -p api-client
supabase test db
pnpm -F desktop exec vitest run \
src/cloud-api/client.test.ts \
src/settings/developers/index.test.tsx
pnpm -F desktop typecheck
pnpm exec dprint check
Require the CLI and MCP contract snapshots, OpenAPI composition tests,
authentication tests, desktop account-switch tests, and database policy tests
to pass without updating snapshots or generated clients during the run.
If the change touches a shared DTO or generated client, regenerate it using the
repository command that owns the artifact, then require a clean second
generation. A generated diff after the second run is a failure.
Local CLI
Build the candidate CLI with cargo build -p anarlog-cli, then use the built
binary for every step.
- Run
anarlog --json doctor.
- PASS when it exits
0, reports schema_version: "1", uses command
doctor, reports ready: true, and resolves the intended QA database.
- Run
meetings list with JSON output.
- Verify default ordering, the unique title query, exact series filtering,
limit/offset pagination, and an empty result.
- For the recorded meeting ID, run:
meetings get ID
meetings note ID --kind note
meetings note ID --kind summary
meetings note ID --kind all
meetings transcript ID with at least two pages
meetings history ID with at least two pages
meetings export ID --format markdown
meetings export ID --format json
- Require every JSON response to have the correct
schema_version, command,
data, and pagination fields. Following next_offset must produce no
duplicates or gaps.
- Export to a temporary file. A second export without
--force must fail
without changing the file; --force must replace it.
- A missing meeting ID and an unreadable or incompatible database must return
machine-readable errors, a nonzero exit, and no panic or partial export.
Webhooks
Launch the exact desktop candidate and add a temporary receiver under
Settings → Developers → Webhooks.
- Adding an endpoint must return a
whsec_ secret exactly once, and a
non-HTTP URL must be rejected.
- Trigger a test delivery, a meeting completion, and a note enhancement.
- Verify the event name, delivery ID, timestamp, body, and HMAC-SHA256
signature over the exact raw body.
- Verify retry behavior with one deliberate transient failure.
- Confirm the last-delivery status shown in settings matches the receiver.
- Delete the webhook and prove that no later delivery arrives.
- Quit the desktop app, complete no further work, and confirm no deliveries
are queued or replayed on the next launch.
Local stdio MCP
Start anarlog mcp through a real MCP client over stdio. Do not validate this
lane by invoking server handlers directly.
- Connect with the current MCP lifecycle and run
tools/list. Repeat the handshake with a legacy 2025-11-25 client.
- Require exactly these tools:
list_meetings
get_meeting
get_meeting_transcript
get_recurring_meeting_history
export_meeting
propose_summary_edit
propose_memo_edit
list_proposals
get_proposal
decline_proposal
- Call every tool against the fixture. Verify query and series filters,
two-page transcript/history traversal, missing IDs, and invalid arguments.
- Run
resources/list, resources/templates/list, and resources/read for a
meeting, transcript page, and recurring series.
- Require read-only, non-destructive, and idempotent tool annotations.
- Compare the returned values with the CLI lane.
- Capture stdout and stderr separately. Stdout must contain only MCP protocol
frames; diagnostics belong on stderr. Client shutdown must terminate the
server without leaving a process behind.
Hosted Cloud API
Use the deployed candidate API and Supabase migration with the Pro QA account.
Begin with Cloud API & Connectors disabled.
- Before opt-in, confirm there are no server-readable snapshot rows for the
account and a previously valid key returns
403 cloud_api_not_enabled.
- Enable the feature in the desktop UI and wait for backfill to finish.
- Create a short-lived cloud key and exercise the same read endpoints,
filters, pagination, error cases, and exports as the CLI lane.
- Require:
- no key, malformed key, and revoked key →
401;
- free or expired account →
403 subscription_required;
- disabled account →
403 cloud_api_not_enabled;
- invalid input →
400 invalid_request;
- missing meeting →
404 not_found;
- request burst above the documented quota →
429 with retry-after.
- Edit the fixture locally and confirm the hosted result changes. Delete the
lifecycle fixture and confirm the hosted endpoint returns
404.
- Sign the desktop into another account before a queued upload or retry can
complete. No snapshot from the first account may appear in the second.
- Disable the feature.
- PASS when all server-readable snapshots are purged, the cloud key returns
cloud_api_not_enabled, local data remains, and normal encrypted sync
data is unchanged.
- Re-enable and confirm a fresh backfill restores only currently existing
meetings. Revoke the QA key when finished.
Remote MCP
Connect a real Streamable HTTP MCP client to the deployed /mcp endpoint.
- Use
server/discover with MCP 2026-07-28, list tools without creating a
session, and require exactly list_meetings, get_meeting,
get_meeting_transcript, get_recurring_meeting_history, and
export_meeting. Verify the required protocol metadata and standard HTTP
headers on every modern request. Repeat discovery with a legacy
2025-11-25 stateless client to prove compatibility.
- Call every tool and traverse at least two transcript/history pages.
- Compare its structured results with the hosted REST responses.
- Repeat initialization or a tool call with no credential, a malformed key, a
revoked key, an expired account, and after opt-out. Unauthenticated MCP
requests must return
WWW-Authenticate pointing at
https://api.anarlog.so/.well-known/oauth-protected-resource/mcp.
- When OAuth is affected, complete MCP OAuth 2.1 discovery and consent from at
least one documented host (Claude Code, Cursor, ChatGPT/Codex, or Copilot).
Confirm the consent screen is Anarlog's
/oauth/consent route, the issued
token is bound to https://api.anarlog.so/mcp, and a tool call then reads
the marked meeting. Repeat with a static anl_ key for hosts that cannot
complete OAuth.
- Connect at least one supported agent client using the documented setup and
ask it to identify the marked meeting, summarize it, and cite a transcript
detail. Verify the answer against the fixture.
- Close the client and confirm the server releases the session cleanly.
Cross-surface parity
For the marked meeting, compare CLI, local MCP, hosted REST, and remote MCP:
| Field |
Required parity |
| Meeting |
ID, title, kind, status, timestamps, timezone, language, series |
| Documents |
canonical note, summary titles and markdown |
| People |
participants and organizations |
| Actions |
text, assignee, completion state |
| Transcript |
text, word order, timestamps, speakers, page boundaries |
| History |
IDs, newest-first ordering, pagination |
| Errors |
stable code, appropriate protocol status, no secret leakage |
Local and hosted values must match after backfill settles. Hosted payloads must
not contain local paths, audio paths, file paths, control characters, secrets,
or fields outside the disclosed server-readable copy.
Reporting
For targeted mode, report the candidate branch/commit, base, selected lane and
risk, PASS/FAIL/BLOCKED, and a one-line evidence note. List unrelated
lanes once as NOT APPLICABLE, and say explicitly that the result is not
comprehensive interface QA.
For comprehensive mode, produce one table with rows for:
- automated baseline;
- CLI;
- webhooks;
- local stdio MCP;
- hosted REST;
- remote MCP;
- lifecycle and account isolation;
- privacy purge;
- cross-surface parity.
Use PASS, FAIL, or BLOCKED with a one-line evidence note. Include the
candidate and deployment identifiers, fixture marker, clients tested, and
redacted response artifact locations. List every skipped negative case.
Any required FAIL or BLOCKED result means comprehensive QA did not pass.
Report that outcome without inferring release approval or blocking.
1---2name: qa-cli-mcp-api3description: Select and run explicitly requested, risk-based QA for Anarlog's CLI, webhooks, stdio MCP, hosted Cloud API, and remote MCP. Test only affected lanes unless comprehensive coverage is requested.4---56# QA: CLI, MCP, and API78Default to the smallest set of programmatic-interface lanes that can prove the9change. Automated tests are necessary but do not replace a live smoke test when10the changed boundary is only exercised by a real client or deployment.1112This QA workflow is independent from releasing. A release request alone does13not invoke it, and its results do not approve or block a release.1415## Choose the scope first1617Inspect the exact branch, commit, or diff and map changed code to its direct18consumers before creating fixtures or credentials. In a GitButler workspace,19use `but status` and `but show <commit-or-branch>`; do not use the synthetic20workspace `HEAD` as the candidate or combine unrelated applied branches.2122Find the original reproduction in the current or past Codex task, linked23issue, PR, support report, or regression test. State the selected lanes and the24reason for each before testing.2526### Targeted regression mode (default)2728Select lanes by behavior, not by the existence of this checklist:2930- CLI parsing, output, or local DB access → CLI plus the closest contract tests.31- Webhook endpoints, signing, or delivery retries → the webhook cases only.32- Shared agent-access DTOs, exports, filtering, or pagination → every direct33 consumer, including hosted REST or remote MCP when affected, plus34 cross-surface parity only for the changed fields.35- Hosted auth, snapshots, entitlements, isolation, purge, or Supabase policy →36 the affected hosted REST lifecycle and negative cases.37- Local or remote MCP protocol/tool changes → that MCP lane and its direct38 transport/contract dependency.39- Shared hosted REST/MCP behavior → both hosted consumers, but not unrelated40 local surfaces.4142Run the closest affected automated tests, the original live reproduction, and43only credible boundary cases. A Rust or shared-crate change does not trigger44all lanes unless every lane consumes the changed behavior. Create only the45minimum non-sensitive fixture required for the selected checks. If a required46deployment, account, fixture, or client is unavailable, mark that check47`BLOCKED`; do not substitute unrelated lanes. Stop when the mapped risks are48covered.4950### Comprehensive Interface QA5152Run every fixture, baseline, live lane, lifecycle case, privacy check, and53cross-surface comparison below only when the user explicitly requests full or54comprehensive programmatic-interface QA.5556## Safety and evidence5758- Use a dedicated QA account and non-sensitive fixture meetings.59- Use a Pro or trialing account for the hosted lane and a separate free or60 expired test account for entitlement checks.61- Never put API keys, JWTs, webhook secrets, database credentials, or personal62 meeting content in commands, screenshots, reports, or repository files.63 Load secrets into environment variables without echoing them.64- Store transient response bodies in a directory created with `mktemp -d`.65 Remove that directory and revoke all generated keys after the run.66- Record the exact candidate commit, app version, API deployment, Supabase67 migration version, operating system, and client versions.68- In a GitButler workspace, identify the selected branch tip with `but status`69 and inspect it with `but show <branch>`; do not use the synthetic workspace70 `HEAD` as the candidate identity.71- Mark a lane `BLOCKED`, not `PASS`, when its required deployment, account,72 fixture, or client is unavailable.7374## Comprehensive QA Fixture7576Create two completed QA meetings through the desktop app:77781. A standalone meeting whose title contains a unique run marker such as79 `agent-access-2026-07-28T120000Z`.802. Two meetings in the same recurring series.8182The fixture must include:8384- a note and at least one generated summary;85- two participants and one action item;86- enough transcript words to require two pages when requested with a small87 limit;88- punctuation and non-ASCII text;89- a later edit to the title, note, or summary;90- one meeting that will be deleted during lifecycle testing.9192Record the meeting IDs and expected visible values. Do not seed SQLite or93Postgres directly for the live happy-path tests.9495## Comprehensive Automated Baseline9697Run from the repository root:9899```bash100cargo test -p anarlog-cli101cargo test -p tauri-plugin-local-api102cargo test -p api-cloud103cargo test -p api openapi::tests104cargo check -p api-client105supabase test db106pnpm -F desktop exec vitest run \107 src/cloud-api/client.test.ts \108 src/settings/developers/index.test.tsx109pnpm -F desktop typecheck110pnpm exec dprint check111```112113Require the CLI and MCP contract snapshots, OpenAPI composition tests,114authentication tests, desktop account-switch tests, and database policy tests115to pass without updating snapshots or generated clients during the run.116117If the change touches a shared DTO or generated client, regenerate it using the118repository command that owns the artifact, then require a clean second119generation. A generated diff after the second run is a failure.120121## Local CLI122123Build the candidate CLI with `cargo build -p anarlog-cli`, then use the built124binary for every step.1251261. Run `anarlog --json doctor`.127 - PASS when it exits `0`, reports `schema_version: "1"`, uses command128 `doctor`, reports `ready: true`, and resolves the intended QA database.1292. Run `meetings list` with JSON output.130 - Verify default ordering, the unique title query, exact series filtering,131 limit/offset pagination, and an empty result.1323. For the recorded meeting ID, run:133 - `meetings get ID`134 - `meetings note ID --kind note`135 - `meetings note ID --kind summary`136 - `meetings note ID --kind all`137 - `meetings transcript ID` with at least two pages138 - `meetings history ID` with at least two pages139 - `meetings export ID --format markdown`140 - `meetings export ID --format json`1414. Require every JSON response to have the correct `schema_version`, `command`,142 `data`, and pagination fields. Following `next_offset` must produce no143 duplicates or gaps.1445. Export to a temporary file. A second export without `--force` must fail145 without changing the file; `--force` must replace it.1466. A missing meeting ID and an unreadable or incompatible database must return147 machine-readable errors, a nonzero exit, and no panic or partial export.148149## Webhooks150151Launch the exact desktop candidate and add a temporary receiver under152**Settings → Developers → Webhooks**.1531541. Adding an endpoint must return a `whsec_` secret exactly once, and a155 non-HTTP URL must be rejected.1562. Trigger a test delivery, a meeting completion, and a note enhancement.157 - Verify the event name, delivery ID, timestamp, body, and HMAC-SHA256158 signature over the exact raw body.159 - Verify retry behavior with one deliberate transient failure.1603. Confirm the last-delivery status shown in settings matches the receiver.1614. Delete the webhook and prove that no later delivery arrives.1625. Quit the desktop app, complete no further work, and confirm no deliveries163 are queued or replayed on the next launch.164165## Local stdio MCP166167Start `anarlog mcp` through a real MCP client over stdio. Do not validate this168lane by invoking server handlers directly.1691701. Connect with the current MCP lifecycle and run `tools/list`. Repeat the handshake with a legacy `2025-11-25` client.1712. Require exactly these tools:172 - `list_meetings`173 - `get_meeting`174 - `get_meeting_transcript`175 - `get_recurring_meeting_history`176 - `export_meeting`177 - `propose_summary_edit`178 - `propose_memo_edit`179 - `list_proposals`180 - `get_proposal`181 - `decline_proposal`1823. Call every tool against the fixture. Verify query and series filters,183 two-page transcript/history traversal, missing IDs, and invalid arguments.1844. Run `resources/list`, `resources/templates/list`, and `resources/read` for a185 meeting, transcript page, and recurring series.1865. Require read-only, non-destructive, and idempotent tool annotations.1876. Compare the returned values with the CLI lane.1887. Capture stdout and stderr separately. Stdout must contain only MCP protocol189 frames; diagnostics belong on stderr. Client shutdown must terminate the190 server without leaving a process behind.191192## Hosted Cloud API193194Use the deployed candidate API and Supabase migration with the Pro QA account.195Begin with **Cloud API & Connectors** disabled.1961971. Before opt-in, confirm there are no server-readable snapshot rows for the198 account and a previously valid key returns `403 cloud_api_not_enabled`.1992. Enable the feature in the desktop UI and wait for backfill to finish.2003. Create a short-lived cloud key and exercise the same read endpoints,201 filters, pagination, error cases, and exports as the CLI lane.2024. Require:203 - no key, malformed key, and revoked key → `401`;204 - free or expired account → `403 subscription_required`;205 - disabled account → `403 cloud_api_not_enabled`;206 - invalid input → `400 invalid_request`;207 - missing meeting → `404 not_found`;208 - request burst above the documented quota → `429` with `retry-after`.2095. Edit the fixture locally and confirm the hosted result changes. Delete the210 lifecycle fixture and confirm the hosted endpoint returns `404`.2116. Sign the desktop into another account before a queued upload or retry can212 complete. No snapshot from the first account may appear in the second.2137. Disable the feature.214 - PASS when all server-readable snapshots are purged, the cloud key returns215 `cloud_api_not_enabled`, local data remains, and normal encrypted sync216 data is unchanged.2178. Re-enable and confirm a fresh backfill restores only currently existing218 meetings. Revoke the QA key when finished.219220## Remote MCP221222Connect a real Streamable HTTP MCP client to the deployed `/mcp` endpoint.2232241. Use `server/discover` with MCP `2026-07-28`, list tools without creating a225 session, and require exactly `list_meetings`, `get_meeting`,226 `get_meeting_transcript`, `get_recurring_meeting_history`, and227 `export_meeting`. Verify the required protocol metadata and standard HTTP228 headers on every modern request. Repeat discovery with a legacy229 `2025-11-25` stateless client to prove compatibility.2302. Call every tool and traverse at least two transcript/history pages.2313. Compare its structured results with the hosted REST responses.2324. Repeat initialization or a tool call with no credential, a malformed key, a233 revoked key, an expired account, and after opt-out. Unauthenticated MCP234 requests must return `WWW-Authenticate` pointing at235 `https://api.anarlog.so/.well-known/oauth-protected-resource/mcp`.2365. When OAuth is affected, complete MCP OAuth 2.1 discovery and consent from at237 least one documented host (Claude Code, Cursor, ChatGPT/Codex, or Copilot).238 Confirm the consent screen is Anarlog's `/oauth/consent` route, the issued239 token is bound to `https://api.anarlog.so/mcp`, and a tool call then reads240 the marked meeting. Repeat with a static `anl_` key for hosts that cannot241 complete OAuth.2426. Connect at least one supported agent client using the documented setup and243 ask it to identify the marked meeting, summarize it, and cite a transcript244 detail. Verify the answer against the fixture.2457. Close the client and confirm the server releases the session cleanly.246247## Cross-surface parity248249For the marked meeting, compare CLI, local MCP, hosted REST, and remote MCP:250251| Field | Required parity |252| ---------- | --------------------------------------------------------------- |253| Meeting | ID, title, kind, status, timestamps, timezone, language, series |254| Documents | canonical note, summary titles and markdown |255| People | participants and organizations |256| Actions | text, assignee, completion state |257| Transcript | text, word order, timestamps, speakers, page boundaries |258| History | IDs, newest-first ordering, pagination |259| Errors | stable code, appropriate protocol status, no secret leakage |260261Local and hosted values must match after backfill settles. Hosted payloads must262not contain local paths, audio paths, file paths, control characters, secrets,263or fields outside the disclosed server-readable copy.264265## Reporting266267For targeted mode, report the candidate branch/commit, base, selected lane and268risk, `PASS`/`FAIL`/`BLOCKED`, and a one-line evidence note. List unrelated269lanes once as `NOT APPLICABLE`, and say explicitly that the result is not270comprehensive interface QA.271272For comprehensive mode, produce one table with rows for:273274- automated baseline;275- CLI;276- webhooks;277- local stdio MCP;278- hosted REST;279- remote MCP;280- lifecycle and account isolation;281- privacy purge;282- cross-surface parity.283284Use `PASS`, `FAIL`, or `BLOCKED` with a one-line evidence note. Include the285candidate and deployment identifiers, fixture marker, clients tested, and286redacted response artifact locations. List every skipped negative case.287288Any required `FAIL` or `BLOCKED` result means comprehensive QA did not pass.289Report that outcome without inferring release approval or blocking.