Files
summercms/.planning/phases/15-journal-plugin/15-04-PLAN.md
2026-10-06 18:02:17 +02:00

22 KiB
Raw Blame History

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, estimate, must_haves
phase plan type wave depends_on files_modified autonomous requirements estimate must_haves
15-journal-plugin 04 execute 4
15-03
../sm-journal-plugin/updates/postgres_test.go
../sm-journal-plugin/updates/migrations_test.go
../sm-journal-plugin/models/fillable_test.go
../sm-journal-plugin/models/translatable_test.go
../sm-journal-plugin/classes/format_html_test.go
../sm-journal-plugin/admin_harness_test.go
../sm-journal-plugin/controllers/api/posts_test.go
../sm-journal-plugin/controllers/api/media_test.go
../sm-journal-plugin/search_test.go
../sm-journal-plugin/integration_test.go
../sm-grzybyfunkcjonalne-app/boot_test.go
scripts/check-phase15.sh
.planning/phases/15-journal-plugin/15-SECURITY-REVIEW.md
.planning/phases/15-journal-plugin/15-VALIDATION.md
true
D-01
D-02
D-03
D-04
D-05
D-06
D-07
D-08
D-09
D-10
D-11
D-12
D-13
D-14
D-15
D-16
D-17
D-18
D-19
D-20
D-21
D-22
D-23
tokens raw_tokens tasks confidence
90000 90000 3 low
truths artifacts key_links prohibitions
D-05: every RESEARCH §8 PHPUnit row has a named Go test (JOURNAL-001/002 fillable, JOURNAL-005 draft 404, JOURNAL-006 media, FormatHTML substitutes JOURNAL-003/004 templates, redactor_id not fillable).
Real Postgres migrates all golem15_journal_* tables plus author_slug, rolls back, and remigrates.
Anonymous list hides drafts; GET categories and tags return 200 PHP {data} lists; GET rss is well-formed RSS 2.0 honoring rss_*; write without Bearer is 401 Authentication required including featured-images POST/DELETE; Typesense gate off records zero HTTP.
Host Activate still returns user+translate+journal; CORS includes _journal/api/*; plugin README stays application-neutral.
scripts/check-phase15.sh --all is fail-closed; 15-SECURITY-REVIEW.md closes every high T-15-* threat.
path provides contains
../sm-journal-plugin/integration_test.go end-to-end migrate, admin create, public GET, Bearer write, draft 404 TestJournalEndToEnd
path provides contains
../sm-journal-plugin/models/fillable_test.go JOURNAL-001/002 TestFillable
path provides contains
../sm-journal-plugin/controllers/api/posts_test.go JOURNAL-005, 401 PHP shape, categories/tags lists, RSS XML, featured-image auth TestJournal005DraftShow
path provides contains
scripts/check-phase15.sh fail-closed phase gate sm-journal-plugin
path provides contains
.planning/phases/15-journal-plugin/15-SECURITY-REVIEW.md ASVS L1 threat evidence T-15-01
from to via pattern
../sm-journal-plugin/integration_test.go ../sm-journal-plugin/routes.go assembled public GET and Bearer POST through production handlers TestJournalEndToEnd
from to via pattern
scripts/check-phase15.sh ../sm-journal-plugin/updates/migrations_test.go full plugin suite with Docker/Postgres, not -short as final evidence go -C
from to via pattern
.planning/phases/15-journal-plugin/15-SECURITY-REVIEW.md ../sm-journal-plugin/controllers/api/posts_test.go each high threat cites an executed TestName T-15-
requirement_id category statement status verification
D-07 safety Gate fails if the PHP journal tree at SHA 02110eb has a git diff resolved test
requirement_id category statement status verification
D-16 architecture Gate fails if fonoteka.go parity/tide files newly mention /_journal/api/v1 resolved test
requirement_id category statement status verification
D-12 safety TestSearchGateOff must not skip and must observe zero search HTTP resolved test

Phase Goal

As a 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.

This plan's slice: the last plan of the phase — full unit/integration coverage, PHPUnit map, phase gate, and security review.

Finish Phase 15 with the dedicated test plan: PHPUnit behavioral map, migration rollback, HTTP/security cases, fail-closed gate, and ASVS L1 review.

Purpose: every locked D-ID and high threat fails closed when broken. Output: test matrix, scripts/check-phase15.sh, 15-SECURITY-REVIEW.md, validated 15-VALIDATION.md.

<execution_context> @/.codex/gsd-core/workflows/execute-plan.md @/.codex/gsd-core/templates/summary.md </execution_context>

@.planning/phases/15-journal-plugin/15-VALIDATION.md @.planning/phases/15-journal-plugin/15-RESEARCH.md @.planning/phases/15-journal-plugin/15-PATTERNS.md @../sm-translate-plugin/updates/postgres_test.go @../sm-translate-plugin/admin_harness_test.go @scripts/check-phase14.2.1.sh

Spec-less probe fallback

Visible skip: no REQUIREMENTS.md IDs and no phase SPEC Edge Coverage/Prohibitions to lift. Tests map to D-01..D-23, RESEARCH §8, and VALIDATION rows. Do not generate probe predicates. Do not claim API-09 or QA-05.

API coverage

No external API integration: this phase ports a compiled plugin's own /_journal/api/v1 surface and an optional beachcomber Gate that stays off; it does not integrate a third-party SaaS SDK. Do not fabricate a capability matrix.

Artifacts this phase produces

  • TestJournalEndToEnd spanning migrate, Activate, admin mlmarkdown save, anonymous list, Bearer write, JOURNAL-005, media 403.
  • Real-Postgres up/down for all seven journal migrations.
  • scripts/check-phase15.sh with plugin/host/PHP-pin/docs-neutral/security stages using go -C ../sm-journal-plugin and go -C ../sm-grzybyfunkcjonalne-app.
  • 15-SECURITY-REVIEW.md and completed 15-VALIDATION.md.

Multi-source coverage audit

SOURCE ID Feature/Requirement Plan Status Notes
GOAL — Port Golem15.Journal to sm-journal-plugin and mount in a host 01-04 COVERED Schema, admin, API, tests
REQ — No mapped requirement IDs (TBD) — COVERED Visible spec-less fallback; D-IDs used; API-09/QA-05 are Phase 20
CONTEXT D-01..D-04 Frozen PHP pin, tables, YAML/API binding 01-04 COVERED SHA asserted; PHP unchanged
CONTEXT D-05 PHPUnit map 04 COVERED RESEARCH §8
CONTEXT D-06 en+pl only 01,04 COVERED Lang files + gate
CONTEXT D-07 No PHP edits 01,04 COVERED git diff empty
CONTEXT D-08 Nav SVG 02,04 COVERED Embedded bytes
CONTEXT D-09 Translate is a prior phase; Journal Requires it 01,04 COVERED No Translate port inside Journal
CONTEXT D-10 Translatable attributes 01-04 COVERED MorphName PHP strings
CONTEXT D-11 cabana markdown 02,04 COVERED No-op; mlmarkdown only
CONTEXT D-12 Typesense off by default 03,04 COVERED Gate + TestSearchGateOff
CONTEXT D-13 CSV CLI + toolbar 02,04 COVERED TestJournalCommands
CONTEXT D-14 Full /_journal/api/v1 03,04 COVERED routes.php wins
CONTEXT D-15 Backend Bearer writes 03,04 COVERED Not frontend tokens
CONTEXT D-16 Not tide 03,04 COVERED Gate forbids harness add
CONTEXT D-17 Limiters + CORS 01,03,04 COVERED Buckets + http.yaml
CONTEXT D-18..D-23 Proof host and remotes 01,04 COVERED Three-plugin boot
RESEARCH — Squash golem15_journal_*; no rainlab-era names 01,04 COVERED Migration tests
RESEARCH — YAML rewrite; FilterScopes 02,04 COVERED Form compile tests
RESEARCH — FormatHTML plugin-local 02,04 COVERED XSS substitute tests
RESEARCH — PHP {error} JSON; per_page 9/30; slug show 03,04 COVERED API tests
RESEARCH — Sibling replace ../summercms.go 01,04 COVERED Isolated go test

Excluded (deferred / other phases): Phase 16 HTML/views/components; Winter.Pages menu types; dashboard widget; Apparatus personal tokens; 19 extra locales; WYSIWYG/redactor; editing wn-journal-plugin; tide 154-route harness; sitemap.

Task 1: Prove migrate → admin save → anonymous list → Bearer write → draft 404 end to end ../sm-journal-plugin/updates/postgres_test.go, ../sm-journal-plugin/admin_harness_test.go, ../sm-journal-plugin/integration_test.go, ../sm-grzybyfunkcjonalne-app/boot_test.go ../sm-translate-plugin/updates/postgres_test.go, ../sm-translate-plugin/admin_harness_test.go, ../sm-translate-plugin/integration_test.go, ../sm-journal-plugin/plugin.go, ../sm-journal-plugin/routes.go, ../sm-journal-plugin/controllers/posts.go, ../sm-journal-plugin/controllers/api/posts.go, .planning/phases/15-journal-plugin/15-VALIDATION.md, .planning/phases/15-journal-plugin/15-RESEARCH.md (§8 PHPUnit map) Copy the translate/user fail-closed TestMain: testcontainers Postgres, dedicated database, Docker unavailability fails full runs; only explicit -short may skip.

TestJournalEndToEnd (D-05, D-14, D-15, D-19): migrate user, translate, and journal in Requires order; party.Activate those three plus a process-local test fixture only if needed (no production fixture plugin); cabana.Activate; surf.Assemble. Mint a backend principal with golem15.journal.access_posts plus access_publish, and a second principal without access_other_posts.

Create a published post and a draft via admin or model helpers using mlmarkdown maps for en/pl. Assert English on host columns and Polish in golem15_translate_attributes under MorphName Golem15\Journal\Models\Post.

GET /_journal/api/v1/posts with no Authorization returns 200 and only the published post (D-14). POST /posts without Bearer is 401 Authentication required (D-15, T-15-01). POST with backend Bearer creates a row. GET draft slug as anonymous is 404 with no data key (JOURNAL-005, T-15-02). Owner or access_other_posts sees 200.

Host TestBootUserTranslateJournal still activates three plugins, sees Journal controller IDs, CORS path, and /_journal/api/v1 GET+POST. It must not duplicate the full fixture matrix.

Do not import sm-user-plugin from journal production code; host tests may join users if present. redactor_id column exists and is not in Fillable; set only via explicit assign (PostRedactor analog, D-05). go -C ../sm-journal-plugin test ./... -count=1 -v -run '^(TestJournalEndToEnd)$' && go -C ../sm-grzybyfunkcjonalne-app test ./... -count=1 -v -run '^(TestBootUserTranslateJournal)$' <fails_when>Non-zero exit; either run prints "--- FAIL", "--- SKIP", "no tests to run", or container startup treated as skip; lacks its named PASS line.</fails_when> <acceptance_criteria> - Integration uses real Postgres and production gormigrate/party/surf/cabana paths. - Anonymous list hides the draft; draft show 404 has no data key. - Bearer write succeeds for a permitted backend principal and 401s without Authorization using the PHP string error. - Polish title is in translate attributes keyed by the PHP Post morph string. - Host boot still lists exactly the three production plugins. </acceptance_criteria> The Phase 15 user-visible path is proven on real Postgres before the horizontal test matrix.

Task 2: Complete PHPUnit map, migrations, YAML, FormatHTML, and search-gate tests ../sm-journal-plugin/updates/migrations_test.go, ../sm-journal-plugin/models/fillable_test.go, ../sm-journal-plugin/models/translatable_test.go, ../sm-journal-plugin/classes/format_html_test.go, ../sm-journal-plugin/controllers/api/posts_test.go, ../sm-journal-plugin/controllers/api/media_test.go, ../sm-journal-plugin/search_test.go, ../sm-journal-plugin/admin_harness_test.go .planning/phases/15-journal-plugin/15-RESEARCH.md (§8 PHPUnit map, JOURNAL-001..006), /media/nvme/dev/golem15/fonoteka/plugins/golem15/journal/tests/security/AccessControlTest.php, /media/nvme/dev/golem15/fonoteka/plugins/golem15/journal/tests/security/MassAssignmentTest.php, /media/nvme/dev/golem15/fonoteka/plugins/golem15/journal/tests/security/XssTest.php, /media/nvme/dev/golem15/fonoteka/plugins/golem15/journal/tests/unit/models/PostRedactorTest.php, ../sm-journal-plugin/models/post.go, ../sm-journal-plugin/classes/format_html.go, ../sm-journal-plugin/controllers/api/media.go, modules/cabana/markdown_test.go Complete D-05 without porting Phase 16 Twig templates.

Migrations (D-01, D-04): TestJournalTables and rollback/remigrate assert every final table/column/index, unique slugs, JSONB metadata/sources, settings defaults including search_use_typesense false, author_slug on backend_users, and absence of rainlab-era journal table names.

Fillable (JOURNAL-001/002, T-15-04): Tag allow-list name/slug/description only; Category excludes nest_*; extra JSON keys dropped; Post API assigns never persist redactor_id or user_id from the body.

Translatable (D-10): Post/Category MorphName PHP strings; slug is indexed; Tag has no Translatable.

FormatHTML (JOURNAL-003/004 substitute): reject script, iframe, event handlers, javascript/vbscript/data schemes; footnotes/tables from goldmark extensions still pass the reject gate. Do not port .htm files.

API: per_page 9/30; slug show; numeric fallback; previous_post/next_post/related_posts present on show; unpublished lock prefix absent from JSON; editor Bearer on GET sees drafts; invalid frontend-audience token on POST is 401; publish without access_publish 403. Named TestJournalPublicCategories and TestJournalPublicTags: anonymous GET /_journal/api/v1/categories and /tags return 200 PHP {data} list shapes (categories honor include_empty; tags ordered by name). Named TestJournalRSS: GET /rss is well-formed RSS 2.0 XML honoring rss_title, rss_posts_per_feed, rss_include_content, and rss_enabled. Named TestJournalFeaturedImageUnauthenticated: featured-image POST/DELETE without Bearer is 401 Authentication required; without canEdit is 403 You do not have permission to edit this post.

Media (JOURNAL-006, T-15-03): 403 without access_posts; 201 with permission; folder .. rejected; stored path under journal/.

Search (D-12, T-15-11): TestSearchGateOff zero HTTP; unpublished ShouldBeSearchable false even if someone flipped the setting in-memory.

Admin: 403 without access_posts; YAML compile TestPostsFormCompiles; FilterScopes without illegal filter keys; commands registered; SVG embed present.

Limiter names and CORS: TestJournalBuckets; host or plugin test reading ../sm-grzybyfunkcjonalne-app/config/http.yaml requires _journal/api/* (D-17). Isolated plugin tests that cannot see the host file skip only that assertion, not the bucket test.

D-11: assert content field type mlmarkdown in compiled schema; assert modules/cabana/form_schema.go already lists markdown/mltext/mlmarkdown (no-op this phase). D-16: no new tide fixtures. go -C ../sm-journal-plugin test ./... -count=1 -v -race && go -C ../sm-grzybyfunkcjonalne-app test ./... -count=1 -v -race <fails_when>Non-zero exit; Go race detector reports a race; any package reports FAIL; integration tests unexpectedly SKIP in the plugin run; output lacks PASS lines for TestJournalPublicCategories, TestJournalPublicTags, TestJournalRSS, and TestJournalFeaturedImageUnauthenticated.</fails_when> <acceptance_criteria> - Named tests exist for JOURNAL-001, JOURNAL-002, JOURNAL-005, JOURNAL-006, FormatHTML unsafe tags, redactor_id not fillable, TestSearchGateOff, TestJournalCommands, TestPostsFormCompiles, TestJournalBuckets, TestJournalPublicCategories, TestJournalPublicTags, TestJournalRSS, TestJournalFeaturedImageUnauthenticated. - Migration rollback and remigrate pass on real Postgres. - High threats T-15-01, T-15-02, T-15-03, T-15-04, T-15-07, T-15-08, T-15-09, T-15-10, T-15-11, T-15-13 have fail-when-broken tests. - No test requires Pages menu types, dashboard widgets, extra locales, or PHP tree writes. </acceptance_criteria> Every D-05 PHPUnit row and every in-scope high threat has named Go evidence.

Task 3: Phase gate, security review, and validation sign-off scripts/check-phase15.sh, .planning/phases/15-journal-plugin/15-SECURITY-REVIEW.md, .planning/phases/15-journal-plugin/15-VALIDATION.md scripts/check-phase14.2.1.sh, .planning/phases/15-journal-plugin/15-VALIDATION.md, .planning/phases/15-journal-plugin/15-RESEARCH.md (Security Domain), .planning/phases/15-journal-plugin/15-01-PLAN.md, .planning/phases/15-journal-plugin/15-02-PLAN.md, .planning/phases/15-journal-plugin/15-03-PLAN.md Create scripts/check-phase15.sh modeled on check-phase14.2.1.sh. Stages: PHP SHA 02110eb1c0c3861370b0b9b47b209a0702ac5d88 and empty git diff on the PHP journal tree (D-01, D-02, D-03, D-07); plugin module/layout/replace ../summercms.go (D-22, D-23, pitfall 9); plugin go vet, full tests, race; host go vet/test/build including TestBootUserTranslateJournal (D-18..D-21); CORS _journal/api/*; plugin README forbidden-name scan (the application / blog only); no rainlab-era table names; no cabana field_markdown.go diff from this phase (D-11 no-op); no tide harness add (D-16); security-review file present. Use go -C ../sm-journal-plugin and go -C ../sm-grzybyfunkcjonalne-app. Full mode requires Postgres; Docker missing is failure; -short is not final evidence. Require named PASS lines for TestJournalEndToEnd, TestJournal005DraftShow, TestJournal006MediaUpload, TestFillable, TestSearchGateOff, TestJournalWriteUnauthenticated, TestBootUserTranslateJournal, TestJournalPublicCategories, TestJournalPublicTags, TestJournalRSS, TestJournalFeaturedImageUnauthenticated. End with Phase 15 gate passed.

Run the security-review lane over local Phase 15 changes (CONTEXT discretion: after implementation, not a code-writing plan). If a typed security-review subagent is unavailable, self-perform as 14.2.1-04 did and disclose that in 15-SECURITY-REVIEW.md frontmatter. Produce 15-SECURITY-REVIEW.md at ASVS L1, block_on high. Preserve unique threat IDs T-15-01 through T-15-15 plus T-15-SC (reserved, never colliding). For every high threat cite source control and executed TestName. Review draft enumeration, media traversal, fillable, stored XSS in content_html, cross-user edit, publish permission, frontend token on writes, Typesense leak, rate-limit XFF, envelope mixup, submodule provenance.

Record the API-coverage declaration in the review: no external SaaS SDK this phase.

Update 15-VALIDATION.md frontmatter to validated / nyquist_compliant / wave_0_complete only after mapped commands pass. Replace pending rows with exact test names and threat refs. Keep the manual SPA UAT row (admin login, Journal nav, mlmarkdown post) as human-check, not a silent pass.

Do not commit unless the user asks; this planner run also does not commit. bash scripts/check-phase15.sh --all <fails_when>Non-zero exit; any stage absent or skipped; output contains FAIL, a Go race, no-tests-to-run, unexpected SKIP, unmitigated high threat, PHP tree dirty; lacks PASS evidence for TestJournalPublicCategories, TestJournalPublicTags, TestJournalRSS, or TestJournalFeaturedImageUnauthenticated; or lacks the final Phase 15 gate passed line.</fails_when> <acceptance_criteria> - Gate uses go -C ../sm-journal-plugin and go -C ../sm-grzybyfunkcjonalne-app. - PHP pin SHA matches 02110eb1c0c3861370b0b9b47b209a0702ac5d88 and the PHP tree has no diff. - 15-SECURITY-REVIEW.md lists each T-15-NN once, keeps T-15-SC, and blocks on high findings. - VALIDATION rows name existing tests; frontmatter is validated only after green execution. - Gate confirms no PHP edits, no extra locales, no Pages/dashboard, no tide add, no Typesense contact in TestSearchGateOff, no consuming-application name in the plugin README. - Gate requires named PASS for TestJournalPublicCategories, TestJournalPublicTags, TestJournalRSS, and TestJournalFeaturedImageUnauthenticated. </acceptance_criteria> The two-repository phase gate is green, high threats are mitigated with executed evidence, and validation is signed off.

<threat_model>

Trust Boundaries

Boundary Description
Test/gate → production claims A no-op filter, skipped container, or dirty PHP tree must not pass
Security review → phase completion High findings block completion
Public HTTP → draft rows JOURNAL-005 regressions must fail the gate

STRIDE Threat Register

Threat ID Category Component Severity Disposition Mitigation Plan
T-15-14 Repudiation phase gate high mitigate Require named PASS lines and final marker; reject no-tests and unexpected skips
T-15-15 Information Disclosure unpublished title prefix on API medium mitigate Assert JSON titles omit the unpublished lock prefix
T-15-SC Tampering package installs high mitigate Gate confirms no new undecided require in plugin go.mod

ASVS L1: block_on high. T-15-01..T-15-13 originate in plans 01–03 and must appear in 15-SECURITY-REVIEW.md with executed tests, not as duplicate rows here. </threat_model>

Run TestJournalEndToEnd, the race suites, then bash scripts/check-phase15.sh --all.

<success_criteria>

  • PHPUnit map D-05 is covered by named Go tests.
  • Plugin and host vet/test/race are green.
  • Phase 15 gate passed.
  • High T-15 threats are mitigated with evidence.
  • Deferred Phase 16/Pages/dashboard/Apparatus-token/extra-locale/PHP-edit items remain absent. </success_criteria>
Create `.planning/phases/15-journal-plugin/15-04-SUMMARY.md` when done