Files
summercms/.planning/phases/15-journal-plugin/15-VERIFICATION.md
2026-10-06 19:30:41 +02:00

273 lines
26 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
phase: 15-journal-plugin
verified: 2026-10-06T17:29:31Z
status: human_needed
score: 22/22 must-haves verified
covered_files:
- .planning/phases/15-journal-plugin/15-01-PLAN.md
- .planning/phases/15-journal-plugin/15-01-SUMMARY.md
- .planning/phases/15-journal-plugin/15-02-PLAN.md
- .planning/phases/15-journal-plugin/15-02-SUMMARY.md
- .planning/phases/15-journal-plugin/15-03-PLAN.md
- .planning/phases/15-journal-plugin/15-03-SUMMARY.md
- .planning/phases/15-journal-plugin/15-04-PLAN.md
- .planning/phases/15-journal-plugin/15-04-SUMMARY.md
- scripts/check-phase15.sh
covered_digest: "v2:sha256:dffb5e2bb692df38cfc2be6a36f6e3305b4d7d81304c9c6fa15bf86362f2aa80"
covered_files_note: "verification.fingerprint covers only paths under the summercms.go root. Sibling implementation read this pass: sm-journal-plugin d09edde (plugin.go, models, seven gormigrate steps, YAML admin, FormatHTML, /_journal/api/v1, commands, tests); sm-grzybyfunkcjonalne-app ff20e6c (summer.yaml, plugins.gen.go, go.work, gitlinks, boot_test.go, config/http.yaml). Host journal gitlink is d09edde. PHP pin /media/nvme/dev/golem15/fonoteka/plugins/golem15/journal HEAD 02110eb1c0c3861370b0b9b47b209a0702ac5d88, clean tree, not edited."
behavior_unverified: 0
overrides_applied: 0
decision_coverage:
honored: 23
total: 23
not_honored: []
deferred:
- truth: "REQUIREMENTS.md API-09 (all 154 Płytarium routes) is mapped to Phase 15 in the stale traceability table"
addressed_in: "Phase 20"
evidence: "Phase 20 goal: the parity harness is green on every manifest route. Plans 15-01..15-04 explicitly do not claim API-09; D-16 forbids adding Journal to the tide harness."
- truth: "REQUIREMENTS.md QA-05 (cutover: 154-route harness green against vue-fonoteka-app and fonoteka-mcp) is mapped to Phase 15 in the stale traceability table"
addressed_in: "Phase 20"
evidence: "Phase 20 goal is the Płytarium definition of done. Journal is not a Płytarium route set."
human_verification:
- test: "Boot sm-grzybyfunkcjonalne-app, sign in as an administrator holding golem15.journal.*, and open Journal in the admin SPA"
expected: "Journal nav appears; Posts/Categories/Tags list and create work; post content is an mlmarkdown field; a user without golem15.journal.access_posts cannot open Posts"
why_human: "Harvested from 15-VALIDATION.md manual row (D-19, SC-1). Named Go tests prove cabana HTTP 403/schema/create, not the running SPA chrome against the proof host."
- test: "Confirm the Gitea remotes git@git.golem15.com:golem15/sm-journal-plugin.git and git@git.golem15.com:golem15/sm-grzybyfunkcjonalne-app.git still exist"
expected: "git ls-remote against both remotes returns a HEAD SHA"
why_human: "Harvested from 15-VALIDATION.md. Local origin URLs match D-18/D-22; this pass could not complete git ls-remote within 8s (likely SSH/network)."
---
# Phase 15: Journal plugin Verification Report
**Phase Goal:** The Golem15 Journal plugin is ported to Go as `sm-journal-plugin` and mounts in a host application the same way `sm-user-plugin` does, so a blog can run on SummerCMS without the PHP plugin.
**Verified:** 2026-10-06T17:29:31Z
**Status:** human_needed
**Re-verification:** No — initial verification
**MVP note:** ROADMAP marks this phase `mode: mvp` but the ROADMAP goal is not in user-story form (`user-story.validate` → `valid: false`). PLAN.md goals are valid user stories. This report uses the PLAN story for User Flow Coverage and does not refuse verification.
**User story (from plans):** As an application developer, I want to mount `sm-journal-plugin` in a host the same way `sm-user-plugin` mounts, so that a blog can run on SummerCMS without the PHP plugin.
## User Flow Coverage
| Step | Expected | Evidence | Status |
|------|----------|----------|--------|
| Mount | Host lists journal beside user and translate as git submodules | `sm-grzybyfunkcjonalne-app/.gitmodules`, `git ls-files -s` mode `160000` for `plugins/golem15/{user,translate,journal}`; journal gitlink `d09edde`; `summer.yaml` + `plugins.gen.go` blank-import `sm-journal-plugin` | ✓ |
| Boot | Host Activate returns the three plugin IDs and assembles Journal routes | `boot_test.go:TestBootUserTranslateJournal` required by `check-phase15.sh --host` (PASS this pass) | ✓ |
| Public content | Anonymous GET lists published posts; categories/tags/rss exist | `TestJournalPublicList`, `TestJournalPublicCategories`, `TestJournalPublicTags`, `TestJournalRSS` in plugin suite (PASS this pass) | ✓ |
| Outcome | A blog can run on SummerCMS without the PHP plugin | Plugin module `git.golem15.com/golem15/sm-journal-plugin`, PHP pin clean at `02110eb1`, `--php` PASS; PHP components `journalPost` documented as Phase 16 successor in plugin README | ✓ |
## Goal Achievement
### Observable Truths
| # | Truth | Status | Evidence |
| --- | ------- | ---------- | -------------- |
| 1 | SC-1: Journal plugin repo exists with models, migrations and YAML admin screens, permission-gated | ✓ VERIFIED | `sm-journal-plugin` HEAD `d09edde`; models Post/Category/Tag/Settings; seven gormigrate IDs; controllers `golem15.journal.posts\|categories\|tags`; `TestPostsFormCompiles` (mlmarkdown/mltext); `TestPostsAdminForbidden` 403 without `access_posts` |
| 2 | SC-2: A host can mount the plugin as a submodule and serve public posts/categories/PHP-equivalent routes | ✓ VERIFIED | Host gitlink + `TestBootUserTranslateJournal` asserts GET/POST `/_journal/api/v1/posts`; `routes.go` public group; `TestJournalPublicList` PASS |
| 3 | SC-3: Plugin README stays application-neutral | ✓ VERIFIED | README uses `the application`, `acme`, `blog`; `--forbidden` greps README for fonoteka/płytarium/grzybyfunkcjonalne (PASS) |
| 4 | SC-4: Unit tests in the phase's last plan | ✓ VERIFIED | 15-04 added fillable/translatable/FormatHTML/migrations/API/search/integration tests; `check-phase15.sh --plugin` ran `go test ./...` and `-race` (PASS this pass) |
| 5 | D-01/D-02/D-03/D-07: Schema/models from PHP SHA `02110eb1`; PHP tree unmodified | ✓ VERIFIED | `check-phase15.sh --php`: HEAD `02110eb1c0c3861370b0b9b47b209a0702ac5d88`, porcelain empty |
| 6 | D-04: gormigrate creates `golem15_journal_*` tables, pivots, settings singleton, `backend_users.golem15_bloghub_author_slug`; rollback remigrates | ✓ VERIFIED | `updates/202610060001`–`007`; `TestJournalTables`; `TestJournalMigrationsRollbackAndRemigrate` in PLUGIN_REQUIRE (PASS) |
| 7 | D-05: RESEARCH §8 PHPUnit rows have named Go tests | ✓ VERIFIED | JOURNAL-001/002 `TestFillable`; JOURNAL-005 `TestJournal005DraftShow`; JOURNAL-006 `TestJournal006MediaUpload`; JOURNAL-003/004 substitute `TestFormatHTMLRejectsUnsafeHTML`; redactor_id `TestFillable` + `TestJournalAPIMassAssignRedactor` |
| 8 | D-06: HasLang catalogs exist for en and pl only | ✓ VERIFIED | `lang/en/lang.yaml`, `lang/pl/lang.yaml` only; `plugin.go` `HasLang` + `//go:embed lang` |
| 9 | D-08: Admin nav SVG is embedded | ✓ VERIFIED | `assets/images/journal-icon.svg` byte-identical to PHP; `TestJournalSVGEmbed` opens it from `AdminFS`; `NavigationItem` has no `IconSvg` (framework), so nav uses lucide `pencil` per RESEARCH |
| 10 | D-09/D-10: ID `golem15.journal`, Requires exactly `golem15.translate`, Post/Category Translatable/MorphName PHP strings, mltext/mlmarkdown | ✓ VERIFIED | `plugin.go` `Requires() []string{"golem15.translate"}`; `TestPostTranslatableSmoke`; `TestTranslatable`; `models/post/fields.yaml` `mltext`/`mlmarkdown` |
| 11 | D-11: post content YAML type is `mlmarkdown`; cabana markdown types not re-added this phase | ✓ VERIFIED | `TestPostsFormCompiles`; `TestCabanaMarkdownFieldTypes`; `git diff -- modules/cabana/field_markdown.go` empty; `--forbidden` refuses a dirty `field_markdown.go` |
| 12 | D-12: `search_use_typesense` defaults false; unpublished/gate-off not searchable; fresh save makes zero Typesense HTTP | ✓ VERIFIED | DDL `DEFAULT FALSE`; `TestSearchGateOff` must not skip (gate detector refuses skip); zero httptest hits this pass |
| 13 | D-13: `journal:export-posts` / `journal:import-posts` registered; Posts toolbar requires `golem15.journal.access_import_export` | ✓ VERIFIED | `TestJournalCommands` PASS; commands write JSON-per-slug files matching PHP ExportPosts/ImportPosts (not a Winter CSV framework — RESEARCH) |
| 14 | FormatHTML regenerates `content_html` on admin/API save with goldmark footnote/table/attribute + rejectUnsafe; cabana.RenderMarkdown not edited | ✓ VERIFIED | `controllers/posts.go` `preparePostWrite`; API `posts_helpers.go`; import path; `TestFormatHTMLRejectsUnsafeHTML`; `TestPostsAdminCreateSmoke` stores HTML |
| 15 | Without `access_other_posts`, list/form queries restrict to `user_id`; publish without `access_publish` is ForbiddenError | ✓ VERIFIED | `restrictToOwnPosts` in `controllers/posts.go`; `TestPostsAdminCreateSmoke/publish_without_access_publish` 403; JOURNAL-005 owner vs stranger vs `access_other_posts` |
| 16 | D-14: anonymous GET `/_journal/api/v1/posts`, `posts/{slug}`, `categories`, `tags`, `rss`; show is slug; per_page default 9 max 30 | ✓ VERIFIED | `routes.go`; `TestJournalPublicList` (per_page 9, drafts omitted); `TestJournalPerPageClamp`; `TestClampPerPage`; categories/tags/RSS named tests PASS |
| 17 | D-15: writes require cabana backend JWT (`aud=backend`); missing Bearer is JSON `{error:Authentication required}`; frontend audience rejected | ✓ VERIFIED | `requireBackendPrincipal`; `TestJournalWriteUnauthenticated`; `TestJournalWriteFrontendAudience`; `TestJournalFeaturedImageUnauthenticated` |
| 18 | D-16: Journal is not added to the Płytarium tide 154-route harness | ✓ VERIFIED | `TestNoTideJournalFixtures`; `--forbidden` greps fonoteka.go parity/tide and `modules/tide` for `/_journal/api/v1` (no hits) |
| 19 | D-17: buckets `journal-public-api` and `journal-api` Max 120; host CORS `_journal/api/*`; `supports_credentials` false | ✓ VERIFIED | `TestJournalBuckets`; `TestCORS`; `config/http.yaml`; 429 body remains surf `"Too Many Attempts."` (no Journal 429 writer) |
| 20 | D-18–D-23: proof host is `sm-grzybyfunkcjonalne-app` at the sibling path; three submodules; plugin module/ID/path | ✓ VERIFIED | `--layout` PASS; host not fonoteka.go / sm-summercmsio-app / in-module testhost; `go.mod` module `git.golem15.com/golem15/sm-journal-plugin`; origin URLs match D-18/D-22 |
| 21 | JOURNAL-005: unpublished or future `published_at` show is 404 with no `data` unless owner or `access_other_posts` | ✓ VERIFIED | `Show` in `controllers/api/posts.go`; `TestJournal005DraftShow`; `TestJournalEndToEnd` anonymous/stranger 404, owner/priv 200 |
| 22 | `scripts/check-phase15.sh --all` is fail-closed; 15-SECURITY-REVIEW.md closes every high T-15-* threat | ✓ VERIFIED | `--self-test` detector (fail/skip/zero/no-tests/race/missing-named); this pass executed every `--all` stage (self-test, php, layout, plugin, host, forbidden, security) — all PASS; review marks T-15-01..15 and T-15-SC mitigate |
**Score:** 22/22 truths verified (0 present, behavior-unverified)
### Deferred Items
Items not yet met but explicitly addressed in later milestone phases.
| # | Item | Addressed In | Evidence |
|---|------|-------------|----------|
| 1 | API-09 (154 Płytarium routes) stale map onto Phase 15 | Phase 20 | Phase 20 cutover goal; plans forbid claiming API-09; D-16 |
| 2 | QA-05 (cutover harness + Nuxt/MCP unchanged) stale map onto Phase 15 | Phase 20 | Phase 20 definition of done |
| 3 | Winter theme components `journalPost` / `journalPosts` / live site HTML | Phase 16 | CONTEXT deferred; plugin README names them as a later successor |
### Required Artifacts
| Artifact | Expected | Status | Details |
| -------- | ----------- | ------ | ------- |
| `../sm-journal-plugin/plugin.go` | compiled plugin, Requires translate, migrations/models | ✓ VERIFIED | ID `golem15.journal`; HasMigrations/Models/Lang/Commands/Routes; BucketProvider |
| `../sm-journal-plugin/updates/202610060001_create_golem15_journal_posts.go` | squashed posts DDL | ✓ VERIFIED | `golem15_journal_posts`; Rollback DROP |
| `../sm-journal-plugin/models/post.go` | TableName, MorphName, Translatable | ✓ VERIFIED | PHP class string `Golem15\Journal\Models\Post` |
| `../sm-grzybyfunkcjonalne-app/summer.yaml` | three compiled plugins including journal | ✓ VERIFIED | user, translate, journal |
| `../sm-grzybyfunkcjonalne-app/boot_test.go` | three-plugin boot | ✓ VERIFIED | `TestBootUserTranslateJournal` |
| `../sm-grzybyfunkcjonalne-app/config/http.yaml` | CORS `_journal/api/*` | ✓ VERIFIED | path present; `supports_credentials: false` |
| `../sm-journal-plugin/controllers/posts.go` | YAML controller, ListExtendQuery, FormatHTML | ✓ VERIFIED | `access_posts`; `preparePostWrite` |
| `../sm-journal-plugin/models/post/fields.yaml` | mlmarkdown content | ✓ VERIFIED | `type: mlmarkdown` |
| `../sm-journal-plugin/assets/images/journal-icon.svg` | embedded nav SVG | ✓ VERIFIED | bytes match PHP; go:embed in AdminFS |
| `../sm-journal-plugin/classes/format_html.go` | `func FormatHTML` | ✓ VERIFIED | goldmark + rejectUnsafe |
| `../sm-journal-plugin/console/export_posts.go` | `journal:export-posts` | ✓ VERIFIED | command name registered |
| `../sm-journal-plugin/models/settings/fields.yaml` | settings code journal | ✓ VERIFIED | `search_use_typesense` |
| `../sm-journal-plugin/routes.go` | `/_journal/api/v1` | ✓ VERIFIED | public GET + write groups |
| `../sm-journal-plugin/controllers/api/posts.go` | PHP `{error}` string JSON, slug show, draft 404 | ✓ VERIFIED | `Authentication required`; no `data` on draft 404 |
| `../sm-journal-plugin/controllers/api/media.go` | JOURNAL-006 media upload | ✓ VERIFIED | `media/upload`; `journal/` prefix |
| `../sm-journal-plugin/search.go` | beachcomber Gate | ✓ VERIFIED | reads ID=1 `search_use_typesense`; errors count as off |
| `../sm-journal-plugin/integration_test.go` | end-to-end | ✓ VERIFIED | `TestJournalEndToEnd` in package `journal_test` |
| `../sm-journal-plugin/models/fillable_test.go` | JOURNAL-001/002 | ✓ VERIFIED | `TestFillable` |
| `../sm-journal-plugin/controllers/api/posts_test.go` | clampPerPage unit | ✓ VERIFIED | HTTP JOURNAL-005 lives at plugin-root `journal_api_writes_test.go` (dispatch-authorized) |
| `scripts/check-phase15.sh` | fail-closed phase gate | ✓ VERIFIED | all stages PASS this pass |
| `.planning/phases/15-journal-plugin/15-SECURITY-REVIEW.md` | ASVS L1 | ✓ VERIFIED | T-15-01 present; `--security` PASS |
### Key Link Verification
| From | To | Via | Status | Details |
| ---- | --- | --- | ------ | ------- |
| `plugin.go` | `golem15.translate` | `Requires` | ✓ WIRED | `Requires() []string{"golem15.translate"}` |
| `models/post.go` | posts migration | `TableName` | ✓ WIRED | both `golem15_journal_posts` |
| `summer.yaml` | `plugins.gen.go` | `summer build` blank-import | ✓ WIRED | `_ "git.golem15.com/golem15/sm-journal-plugin"` |
| `controllers/posts.go` | `classes/format_html.go` | FormBeforeCreate/Update | ✓ WIRED | `classes.FormatHTML(post.Content)` |
| `admin_permissions.go` | `controllers/posts.go` | `RequiredPermissions` | ✓ WIRED | `golem15.journal.access_posts` |
| `console/export_posts.go` | `plugin.go` | `HasCommands` | ✓ WIRED | `console.ExportPosts` in `Commands()` |
| `routes.go` | `controllers/api/auth.go` | backend JWT on writes | ✓ WIRED | `requireBackendPrincipal` / `optionalBackendPrincipal` |
| `controllers/api/posts.go` | `classes/format_html.go` | store/update | ✓ WIRED | `posts_helpers.go` calls `FormatHTML` |
| `search.go` | `models/settings.go` | Gate ID=1 | ✓ WIRED | `SetGate` + `settingsSearchEnabled` |
| `integration_test.go` | `routes.go` | assembled handlers | ✓ WIRED | `TestJournalEndToEnd` hits public GET and Bearer POST |
| `check-phase15.sh` | plugin suite | `go -C` full, not `-short` | ✓ WIRED | `--plugin` PASS this pass |
| `15-SECURITY-REVIEW.md` | named tests | T-15-* | ✓ WIRED | `--security` requires mitigate on high IDs |
### Data-Flow Trace (Level 4)
| Artifact | Data Variable | Source | Produces Real Data | Status |
| -------- | ------------- | ------ | ------------------ | ------ |
| GET `/_journal/api/v1/posts` | `data` / `meta` | GORM `Model(&models.Post{})` + `lagoon.Paginate` | Yes — `TestJournalPublicList` seeds rows and asserts listed slug | ✓ FLOWING |
| Admin POST posts | stored `Post` + translate attributes | cabana form → GORM + `golem15_translate_attributes` | Yes — `TestPostsAdminCreateSmoke` / `TestJournalEndToEnd` | ✓ FLOWING |
| FormatHTML `content_html` | `post.ContentHTML` | goldmark convert of `content` | Yes — create smoke asserts markup, rejects script | ✓ FLOWING |
| Settings gate | `search_use_typesense` | `golem15_journal_settings` id=1 | Yes — default false in DDL; `TestSearchGateOff` | ✓ FLOWING |
| Nav SVG | AdminFS file | `//go:embed assets/images/journal-icon.svg` | Bytes present; SPA nav uses lucide `pencil` (no IconSvg field) | ✓ FLOWING (embed); SPA icon is lucide |
### Behavioral Spot-Checks
| Behavior | Command | Result | Status |
| -------- | ------- | ------ | ------ |
| PHP pin frozen and clean | `bash scripts/check-phase15.sh --php` | `phase15 php passed (02110eb1…)` | ✓ PASS |
| Host layout + gitlinks + CORS | `bash scripts/check-phase15.sh --layout` | `phase15 layout passed` | ✓ PASS |
| Plugin vet + full suite + race | `bash scripts/check-phase15.sh --plugin` | `phase15 plugin passed` (~85s) | ✓ PASS |
| Host vet + `TestBootUserTranslateJournal`/`TestCORS` + build | `bash scripts/check-phase15.sh --host` | `phase15 host passed` | ✓ PASS |
| Forbidden surfaces | `bash scripts/check-phase15.sh --forbidden` | `phase15 forbidden passed` | ✓ PASS |
| Security review + VALIDATION sign-off | `bash scripts/check-phase15.sh --security` | `phase15 security passed` | ✓ PASS |
| Detector fail-closed | `bash scripts/check-phase15.sh --self-test` | `phase15 self-test passed` | ✓ PASS |
### Probe Execution
No `scripts/*/tests/probe-*.sh`. Phase-declared gate is `scripts/check-phase15.sh`.
| Probe | Command | Result | Status |
| ----- | ------- | ------ | ------ |
| `scripts/check-phase15.sh --self-test` | `bash scripts/check-phase15.sh --self-test` | exit 0 | PASS |
| `scripts/check-phase15.sh --php` | `bash scripts/check-phase15.sh --php` | exit 0 | PASS |
| `scripts/check-phase15.sh --layout` | `bash scripts/check-phase15.sh --layout` | exit 0 | PASS |
| `scripts/check-phase15.sh --plugin` | `bash scripts/check-phase15.sh --plugin` | exit 0 | PASS |
| `scripts/check-phase15.sh --host` | `bash scripts/check-phase15.sh --host` | exit 0 | PASS |
| `scripts/check-phase15.sh --forbidden` | `bash scripts/check-phase15.sh --forbidden` | exit 0 | PASS |
| `scripts/check-phase15.sh --security` | `bash scripts/check-phase15.sh --security` | exit 0 | PASS |
`--all` is those seven stages in order. Each stage was executed in this verification process.
### Requirements Coverage
ROADMAP lists **Requirements: TBD**. REQUIREMENTS.md has **no Journal IDs**. Plan frontmatter IDs are CONTEXT decisions D-01–D-23. Every ID from plans is accounted for below.
| Requirement | Source Plan | Description | Status | Evidence |
| ----------- | ---------- | ----------- | ------ | -------- |
| D-01 | 01, 04 | Port contract PHP SHA `02110eb` | ✓ SATISFIED | `--php` HEAD match |
| D-02 | 01, 04 | Freeze at that SHA | ✓ SATISFIED | gate pins full SHA |
| D-03 | 01, 04 | Read frozen tree at fonoteka journal path | ✓ SATISFIED | `PHASE15_PHP` default path; tree present |
| D-04 | 01, 02, 04 | Models/tables/YAML/perms/commands/API | ✓ SATISFIED | migrations + YAML + routes + commands |
| D-05 | 04 | PHPUnit map | ✓ SATISFIED | named Go tests for §8 rows |
| D-06 | 01, 02, 04 | en+pl only | ✓ SATISFIED | two lang dirs; 02 prohibition |
| D-07 | 01, 04 | Do not edit PHP journal | ✓ SATISFIED | porcelain empty; test-tier prohibition wired in `--php` |
| D-08 | 02, 04 | Embed journal-icon.svg | ✓ SATISFIED | AdminFS + byte cmp |
| D-09 | 01, 04 | Do not port Translate inside Journal; Require translate not apparatus | ✓ SATISFIED | `Requires` exactly translate; no apparatus ID |
| D-10 | 01–04 | Translatable attributes | ✓ SATISFIED | Post/Category Translatable + MorphName |
| D-11 | 02, 04 | Use existing cabana markdown; no second field type | ✓ SATISFIED | mlmarkdown; field_markdown.go untouched |
| D-12 | 03, 04 | Typesense off by default | ✓ SATISFIED | `TestSearchGateOff` |
| D-13 | 02, 04 | Import/export CLI + toolbar (PHP JSON files, not Winter CSV) | ✓ SATISFIED | `TestJournalCommands` |
| D-14 | 03, 04 | Full `/_journal/api/v1` | ✓ SATISFIED | routes + public tests |
| D-15 | 03, 04 | Backend Bearer writes only | ✓ SATISFIED | 401 PHP shape; frontend aud rejected |
| D-16 | 03, 04 | Not tide | ✓ SATISFIED | `--forbidden` + `TestNoTideJournalFixtures` |
| D-17 | 01, 03, 04 | Limiters + CORS | ✓ SATISFIED | buckets + http.yaml |
| D-18 | 01, 04 | Proof host sm-grzybyfunkcjonalne-app | ✓ SATISFIED | sibling checkout + origin |
| D-19 | 01, 04 | Mount, migrate, admin, API (SPA chrome → human) | ✓ SATISFIED | boot + plugin HTTP; SPA listed under Human Verification |
| D-20 | 01, 04 | Local host path | ✓ SATISFIED | `/media/nvme/dev/golem15/summercms.io/summercms/sm-grzybyfunkcjonalne-app` |
| D-21 | 01, 04 | Submodules journal+translate+user | ✓ SATISFIED | gitlinks 160000 |
| D-22 | 01, 04 | Plugin remote/module/package/ID | ✓ SATISFIED | go.mod + plugin.go + origin |
| D-23 | 01, 04 | Local plugin checkout + host submodule replace | ✓ SATISFIED | sibling `sm-journal-plugin`; host `replace => ./plugins/golem15/journal` |
| API-09 | REQUIREMENTS.md map only | 154 Płytarium routes | deferred | Phase 20; not in any Phase 15 plan `requirements:` |
| QA-05 | REQUIREMENTS.md map only | Cutover harness | deferred | Phase 20; not in any Phase 15 plan `requirements:` |
**Orphaned REQUIREMENTS.md IDs mapped to Phase 15:** API-09, QA-05 — recorded under Deferred, not gaps. Plans treat them as Phase 20.
**Prohibitions (test-tier):** D-07, D-09, D-18, D-11, D-13 (no Pages/dashboard), D-06, D-15, D-14 (PHP error envelope), D-12, D-16 — each has wired `--php`/`--layout`/`--forbidden` or a named test. Not unverified.
### Decision Coverage
All trackable CONTEXT.md decisions are honored by shipped artifacts. **23/23 honored.**
### Test Quality Audit
| Test File | Linked Req | Active | Skipped | Circular | Assertion Level | Verdict |
|-----------|-----------|--------|---------|----------|-----------------|---------|
| `models/fillable_test.go` | D-05 JOURNAL-001/002 | yes | no | no | Value | OK |
| `journal_api_writes_test.go` | D-14/D-15/JOURNAL-005 | yes | no | no | Behavioral | OK |
| `journal_api_task3_test.go` | JOURNAL-006, RSS | yes | no | no | Behavioral | OK |
| `search_test.go` | D-12 | yes | no | no | Behavioral | OK |
| `classes/format_html_test.go` | JOURNAL-003/004 subst. | yes | no | no | Value | OK |
| `updates/migrations_test.go` | D-04 | yes | only `-short` | no | Behavioral | OK |
| `integration_test.go` | D-05/D-14/D-15 | yes | only `-short` | no | Behavioral | OK |
| empty `TestJournalEndToEnd` shims in other packages | gate `-run` hygiene | yes (no-op) | no | no | Existence | ⚠️ WARNING — real proof is `journal_test.TestJournalEndToEnd` |
**Disabled tests on requirements:** 0 blockers. `t.Skip("requires testcontainers postgres")` only when `-test.short`; full `--plugin` run is not `-short` and TestMain exits 1 if Docker fails.
**Circular patterns detected:** 0
**Insufficient assertions:** empty EndToEnd shims only (other tests cover the requirement)
### Anti-Patterns Found
| File | Line | Pattern | Severity | Impact |
| ---- | ---- | ------- | -------- | ------ |
| `plugin_test.go` (and sibling packages) | `TestJournalEndToEnd` empty | no-op test shim | ℹ️ Info | Keeps `go test ./... -run TestJournalEndToEnd` from printing `no tests to run`; real test is `integration_test.go` package `journal_test` |
| `updates/postgres_test.go` | rainlab table name in test | historical name check | ℹ️ Info | Test-only; `--forbidden` excludes `_test.go` |
| `go.mod` | require `sm-user-plugin` | test harness module | ℹ️ Info | Production `.go` files do not import it (`--forbidden` + `TestJournalEndToEndFixtureAbsentFromProduction`) |
| `15-SECURITY-REVIEW.md` | reviewer | executor self-review | ℹ️ Info | No dedicated auditor in this Cursor session; cited tests were re-run by `--plugin` this pass |
| production plugin sources | — | TBD/FIXME/XXX | none | No unreferenced debt markers |
### Human Verification Required
### 1. Journal admin SPA on the proof host
**Test:** Boot `sm-grzybyfunkcjonalne-app`. Sign in as an administrator holding `golem15.journal.*`. Open Journal nav. List and create a post whose content is `mlmarkdown`. Repeat without `golem15.journal.access_posts`.
**Expected:** Posts/Categories/Tags screens work; mlmarkdown editor is present; missing permission cannot open Posts.
**Why human:** Needs a running host, admin login, and visual confirmation. Automated tests cover HTTP 403/schema/create, not SPA chrome.
### 2. Gitea remotes still exist
**Test:** `git ls-remote git@git.golem15.com:golem15/sm-journal-plugin.git` and the proof-host remote.
**Expected:** Each remote returns a HEAD SHA.
**Why human:** Local `origin` URLs match D-18/D-22; this verifier could not complete `git ls-remote` within 8s.
### Gaps Summary
No blocking gaps. All 22 must-have truths are present, wired, and exercised by named tests or the fail-closed gate. Status is `human_needed` because harvested end-of-phase UAT (admin SPA and remote reachability) remains. ROADMAP/STATE were not marked complete.
PHPUnit XSS template tests (`\|raw_safe` on `.htm`) stay Phase 16 by RESEARCH; FormatHTML unsafe-tag tests substitute this phase and passed.
---
_Verified: 2026-10-06T17:29:31Z_
_Verifier: the agent (gsd-verifier)_