docs(12): record post-research decisions and validation strategy

This commit is contained in:
Jakub Zych
2026-10-02 08:04:23 +02:00
parent 77f8b371cf
commit d97b4292a3
2 changed files with 92 additions and 0 deletions

View File

@@ -91,6 +91,16 @@ Repo: `fonoteka.go`. Framework changes in `summercms.go` only where a gap is fou
- Which errors per route need a recording and which a Go test with bodies from PHP source, where recording a case is impractical (e.g. a mid-transaction race). Recording is the default.
- Plan count and split, subject to the plan-count checkpoint, "unit tests are the last plan" and the security-review agent (this phase touches authorization and public tokens).
### Post-research decisions (2026-10-02, confirmed at the plan-count checkpoint)
- **D-19:** Supersedes D-16 point 3. `meta.total`/`last_page` mirror Scout v10.25.0 `Builder::getTotalCount`: re-fetch up to `min(found, 1000)` ids from the engine and count them in SQL with the same access gating. The total is therefore re-gated and capped at 1000; stale/foreign ids still shorten the page. The D-18 leak test also asserts the total never counts a leaked row.
- **D-20:** `GET realtime/channels` (`{"data":{"collection":"collection:<id>"}}`) is ported in this phase. `me/context` returns only its recorded flags (AI, org, Discogs, currency) and no channel. API-01 and success criterion 1 are reworded at plan time.
- **D-21:** Error bodies are reproduced as PHP serves them in production (`APP_DEBUG=false`), including Winter HTML error pages where PHP throws `HttpException` (switch, household, invitations, accept). The existing accept 410 fixture is re-recorded under `APP_DEBUG=false`; the invitation `ValidationException` envelopes are recorded.
- **D-22:** Photo and attachment URLs match PHP (`/storage/app/uploads/public/<partition>/<disk>`). The Go attach URL prefix is fixed in this phase, restoring P5 D-16.
- **D-23:** The raw invitation token in River job args is encrypted with the app key and decrypted only inside the mail job. Amends D-13: the raw token never appears in plain text in `river_job.args`, logs or `summer_jobs.metadata`.
- **D-24:** The image guard accepts webp as PHP does. `golang.org/x/image` is bumped to v0.46.0 and its webp decoder registered (named here as the phase decision that authorises the dependency bump).
- **D-25:** The site-admin check in `me/context` reads user groups. User groups (`users_groups` and the groups relation) are added to the Go user plugin as an **additive, non-breaking** change; its existing contract is unchanged. The user signed off on this core-plugin change on 2026-10-02.
- **D-26:** Research-found bugs fixed in this phase: the token group applies exactly one `inv.scope:read|write` per route as PHP does (not `read` at group level), and deleting a collection removes its albums one by one so each album gets its Typesense removal and `deleted` broadcast, as PHP does.
</decisions>
<canonical_refs>

View File

@@ -0,0 +1,82 @@
---
phase: "12"
slug: "p-ytarium-api-collections-and-albums"
# status lifecycle: draft (seeded by plan-phase) → validated (set by validate-phase §6)
# audit-milestone §5.5 distinguishes NOT-VALIDATED (draft) from PARTIAL (validated + nyquist_compliant: false) (#2117)
status: draft
nyquist_compliant: false
wave_0_complete: false
created: "2026-10-02"
---
# Phase 12 — Validation Strategy
> Per-phase validation contract for feedback sampling during execution.
---
## Test Infrastructure
| Property | Value |
|----------|-------|
| **Framework** | Go `testing` (+ testify assert/require, Go fuzzing), testcontainers Postgres |
| **Config file** | none (`parity/parity_test.go` TestMain starts Postgres) |
| **Quick run command** | `cd fonoteka.go && go test ./plugins/golem15/fonoteka/... -short -count=1` |
| **Full suite command** | `cd fonoteka.go && go vet ./... && go test ./... -count=1`, plus `cd summercms.go && go vet ./... && go test ./... -count=1` for framework changes |
| **Parity command** | `cd fonoteka.go && go test ./parity -run 'TestParityCorpus|TestBroadcastGoldens|TestNuxtFlow' -count=1` |
| **Estimated runtime** | ~180 seconds (full suite, both repos, with containers) |
---
## Sampling Rate
- **After every task commit:** quick run command plus `go vet` in the touched repo
- **After every plan wave:** full suite in both repos plus the parity command
- **Before `/gsd-verify-work`:** full suite green in both repos; parity corpus with the new routes flipped to `ported` and passing; `check_corpus.go --require-recorded --check-secrets` green; `go test ./cmd/summer -run TestDocsTree` green
- **Max feedback latency:** 60 seconds (quick run)
---
## Per-Task Verification Map
Seeded from RESEARCH.md by requirement; task IDs are filled in once PLAN.md files exist.
| Task ID | Plan | Wave | Requirement | Threat Ref | Secure Behavior | Test Type | Automated Command | File Exists | Status |
|---------|------|------|-------------|------------|-----------------|-----------|-------------------|-------------|--------|
| TBD | 01 | 1 | framework | — | beachcomber `found`/weights, tide multipart + URL mask, Laravel-semantics validator + catalogs, attach URL export | unit | `cd summercms.go && go test ./modules/... -count=1` | ❌ W0 | ⬜ pending |
| TBD | 02 | 2 | API-01 | T-12-01, T-12-03, T-12-04, T-12-05 | Collections, switch, me/context, realtime/channels, share replays; token pin and narrowing | parity + integration | `go test ./parity -run TestParityCorpus/.*collection` | ❌ W0 | ⬜ pending |
| TBD | 03 | 3 | API-01 | T-12-06, T-12-07 | Invite mail enqueued in tx, absent on rollback; token encrypted in job args, never logged | integration + parity flow | `go test ./parity -run TestNuxtFlow/nuxt-collections` | ❌ W0 | ⬜ pending |
| TBD | 04 | 4 | API-02 | T-12-02, T-12-09, T-12-10, T-12-11 | Album CRUD, rating, photos, bulk, stats/value/missing/sync, search, lookups; upload guard; SSRF guard | parity + integration | `go test ./parity -run 'TestParityCorpus/.*albums|TestBroadcastGoldens'` | ❌ W0 | ⬜ pending |
| TBD | 05 | 5 | API-01, API-02 | T-12-01..T-12-11 | D-18 leak test (5 cases + total), request-DTO fuzz, route-table single-scope test | security + fuzz + unit | `go test ./plugins/golem15/fonoteka/... -run 'TestSearchLeak|TestRouteTable|FuzzWriteEndpoints' -count=1` | ❌ W0 | ⬜ pending |
*Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky*
---
## Wave 0 Requirements
- [ ] Re-record the HttpException cases under `APP_DEBUG=false` (accept 410, switch 404, household/members 404, invitations 404, token 404) — D-21
- [ ] tide request `body_file` (multipart) + `url`/`thumb_url` disk-name normalizer + publication date masking (framework)
- [ ] Seed hooks or flows for a second user and an outsider (reuse `id:outsider` from Phase 11)
- [ ] Fake `beachcomber` engine with scripted ids and `found` for D-18/D-19
---
## Manual-Only Verifications
| Behavior | Requirement | Why Manual | Test Instructions |
|----------|-------------|------------|-------------------|
| Recording new PHP fixtures against the isolated PHP instance | API-01, API-02 | Needs the running PHP reference instance and capture tooling | Follow the Phase 2 `tide` capture rules (private 0600 vars, no live tokens in git), then run `check_corpus.go --require-recorded --check-secrets` |
---
## Validation Sign-Off
- [ ] All tasks have `<automated>` verify or Wave 0 dependencies
- [ ] Sampling continuity: no 3 consecutive tasks without automated verify
- [ ] Wave 0 covers all MISSING references
- [ ] No watch-mode flags
- [ ] Feedback latency < 60s
- [ ] `nyquist_compliant: true` set in frontmatter
**Approval:** pending