303 lines
44 KiB
Markdown
303 lines
44 KiB
Markdown
---
|
|
phase: quick-261002-esz
|
|
plan: 01
|
|
type: execute
|
|
wave: 1
|
|
depends_on: []
|
|
files_modified:
|
|
# New repo sm-user-plugin (git@git.golem15.com:golem15/sm-user-plugin.git), worked on as the
|
|
# submodule checkout ../fonoteka.go/plugins/golem15/user. Task 2, three commits there.
|
|
- ../fonoteka.go/plugins/golem15/user/go.mod
|
|
- ../fonoteka.go/plugins/golem15/user/go.sum
|
|
- ../fonoteka.go/plugins/golem15/user/README.md
|
|
- ../fonoteka.go/plugins/golem15/user/plugin.go
|
|
- ../fonoteka.go/plugins/golem15/user/routes.go
|
|
- ../fonoteka.go/plugins/golem15/user/account_test.go
|
|
- ../fonoteka.go/plugins/golem15/user/avatar_test.go
|
|
- ../fonoteka.go/plugins/golem15/user/console_test.go
|
|
- ../fonoteka.go/plugins/golem15/user/mail_test.go
|
|
- ../fonoteka.go/plugins/golem15/user/profile_test.go
|
|
- ../fonoteka.go/plugins/golem15/user/register_test.go
|
|
- ../fonoteka.go/plugins/golem15/user/sequence_test.go
|
|
- ../fonoteka.go/plugins/golem15/user/session_test.go
|
|
- ../fonoteka.go/plugins/golem15/user/classes/codes.go
|
|
- ../fonoteka.go/plugins/golem15/user/classes/codes_test.go
|
|
- ../fonoteka.go/plugins/golem15/user/classes/events.go
|
|
- ../fonoteka.go/plugins/golem15/user/classes/events_test.go
|
|
- ../fonoteka.go/plugins/golem15/user/classes/mail.go
|
|
- ../fonoteka.go/plugins/golem15/user/classes/throttle.go
|
|
- ../fonoteka.go/plugins/golem15/user/classes/throttle_test.go
|
|
- ../fonoteka.go/plugins/golem15/user/classes/user_lookup.go
|
|
- ../fonoteka.go/plugins/golem15/user/console/require_password_change.go
|
|
- ../fonoteka.go/plugins/golem15/user/controllers/api_controller.go
|
|
# fonoteka.go (application repo). Task 1 commit (mount) and Task 2 commit (rename importers).
|
|
- ../fonoteka.go/.gitmodules
|
|
- ../fonoteka.go/plugins/golem15/user # in-tree directory replaced by a gitlink
|
|
- ../fonoteka.go/go.mod
|
|
- ../fonoteka.go/summer.yaml
|
|
- ../fonoteka.go/plugins.gen.go # regenerated by `summer build`, never hand-edited
|
|
- ../fonoteka.go/README.md
|
|
- ../fonoteka.go/CLAUDE.md
|
|
- ../fonoteka.go/app/app.go
|
|
- ../fonoteka.go/parity/broadcast_goldens_test.go
|
|
- ../fonoteka.go/parity/genres_seed_test.go
|
|
- ../fonoteka.go/parity/migrate_test.go
|
|
- ../fonoteka.go/parity/parity_test.go
|
|
- ../fonoteka.go/parity/realtime_seed_test.go
|
|
- ../fonoteka.go/parity/user_api_seed_test.go
|
|
- ../fonoteka.go/plugins/golem15/fonoteka/go.mod
|
|
- ../fonoteka.go/plugins/golem15/fonoteka/plugin.go
|
|
- ../fonoteka.go/plugins/golem15/fonoteka/realtime.go
|
|
- ../fonoteka.go/plugins/golem15/fonoteka/get_api_array_test.go
|
|
- ../fonoteka.go/plugins/golem15/fonoteka/phase07_coverage_test.go
|
|
- ../fonoteka.go/plugins/golem15/fonoteka/plugin_boot_test.go
|
|
- ../fonoteka.go/plugins/golem15/fonoteka/realtime_smoke_test.go
|
|
- ../fonoteka.go/plugins/golem15/fonoteka/routes_group_test.go
|
|
- ../fonoteka.go/plugins/golem15/fonoteka/token_locale_test.go
|
|
- ../fonoteka.go/plugins/golem15/fonoteka/classes/auth/postgres_test.go
|
|
- ../fonoteka.go/plugins/golem15/fonoteka/classes/auth/token_guard.go
|
|
- ../fonoteka.go/plugins/golem15/fonoteka/classes/hidden_marshal_test.go
|
|
- ../fonoteka.go/plugins/golem15/fonoteka/classes/postgres_test.go
|
|
- ../fonoteka.go/plugins/golem15/fonoteka/classes/ws/collection_authorizer.go
|
|
- ../fonoteka.go/plugins/golem15/fonoteka/console/oauth_client_test.go
|
|
- ../fonoteka.go/plugins/golem15/fonoteka/controllers/api/me_locale_controller.go
|
|
- ../fonoteka.go/plugins/golem15/fonoteka/controllers/api/oauth_consent_controller_test.go
|
|
- ../fonoteka.go/plugins/golem15/fonoteka/controllers/collections_admin_controller.go
|
|
- ../fonoteka.go/plugins/golem15/fonoteka/models/collection.go
|
|
- ../fonoteka.go/plugins/golem15/fonoteka/updates/oauth_schema_correction_test.go
|
|
# summercms.go planning docs. Task 3, one docs commit.
|
|
- .planning/notes/core-plugins-own-repos.md
|
|
- .planning/phases/01-framework-kernel-foundation/01-CONTEXT.md
|
|
- .planning/PROJECT.md
|
|
- .planning/ROADMAP.md
|
|
- .planning/phases/12-p-ytarium-api-collections-and-albums/12-01-PLAN.md
|
|
- .planning/phases/12-p-ytarium-api-collections-and-albums/12-02-PLAN.md
|
|
- .planning/phases/12-p-ytarium-api-collections-and-albums/12-05-PLAN.md
|
|
autonomous: true
|
|
requirements: [QUICK-261002-esz]
|
|
|
|
estimate:
|
|
tokens: 150000
|
|
raw_tokens: 150000
|
|
tasks: 3
|
|
confidence: low
|
|
|
|
must_haves:
|
|
truths:
|
|
- "The remote git@git.golem15.com:golem15/sm-user-plugin.git has a master branch holding the plugin's full history split out of fonoteka.go (one commit per fonoteka.go commit that touched plugins/golem15/user, 21 at planning time), followed by the module-path rename, a go.mod tidy and a README commit; the remote master SHA equals the submodule HEAD that fonoteka.go records"
|
|
- "fonoteka.go/plugins/golem15/user is a git submodule with url git@git.golem15.com:golem15/sm-user-plugin.git at the same path, so go.work still lists ./plugins/golem15/user unchanged; `git submodule status` shows it clean (no -, + or U prefix)"
|
|
- "The plugin's module path is git.golem15.com/golem15/sm-user-plugin everywhere: its go.mod, every import in the plugin, the app, parity and the fonoteka plugin, both go.mod require/replace pairs, summer.yaml and the regenerated plugins.gen.go; no text file under fonoteka.go (outside .git) contains the old app-nested module path"
|
|
- "Pure move per locked decision 1: plugin ID golem15.user, table names, migration IDs, config keys golem15.user.*, routes and payloads are unchanged; every changed line in the plugin's .go files and in fonoteka.go's code files is an import or module-path line"
|
|
- "fonoteka.go builds (`go build -o bin/fonoteka .`) and the full workspace suite (root module, plugins/golem15/fonoteka, plugins/golem15/user, Docker/testcontainers included) passes; the plugin also vets in standalone module mode (GOWORK=off)"
|
|
- "The plugin README follows the module README structure and names no consuming application"
|
|
- "A decision note in summercms.go/.planning/notes supersedes the Phase 1 deferral of shared-plugin extraction, the deferral line points to it, and PROJECT.md, ROADMAP.md and the unexecuted Phase 12 plans name sm-user-plugin and the submodule commit workflow"
|
|
artifacts:
|
|
- path: "../fonoteka.go/.gitmodules"
|
|
provides: "Submodule entry for plugins/golem15/user"
|
|
contains: "url = git@git.golem15.com:golem15/sm-user-plugin.git"
|
|
- path: "../fonoteka.go/plugins/golem15/user/go.mod"
|
|
provides: "Renamed module (inside the submodule)"
|
|
contains: "module git.golem15.com/golem15/sm-user-plugin"
|
|
- path: "../fonoteka.go/plugins/golem15/user/README.md"
|
|
provides: "Standalone plugin README, no consuming application named"
|
|
contains: "golem15.user"
|
|
- path: "../fonoteka.go/go.mod"
|
|
provides: "require + replace of the new module path to ./plugins/golem15/user"
|
|
contains: "git.golem15.com/golem15/sm-user-plugin => ./plugins/golem15/user"
|
|
- path: "../fonoteka.go/plugins/golem15/fonoteka/go.mod"
|
|
provides: "require + replace of the new module path to ../user"
|
|
contains: "git.golem15.com/golem15/sm-user-plugin => ../user"
|
|
- path: "../fonoteka.go/plugins.gen.go"
|
|
provides: "Regenerated blank import of the renamed plugin"
|
|
contains: "_ \"git.golem15.com/golem15/sm-user-plugin\""
|
|
- path: ".planning/notes/core-plugins-own-repos.md"
|
|
provides: "Decision note superseding the Phase 1 deferral"
|
|
contains: "Supersedes"
|
|
key_links:
|
|
- from: "../fonoteka.go/go.work"
|
|
to: "../fonoteka.go/plugins/golem15/user (submodule checkout)"
|
|
via: "use ./plugins/golem15/user (unchanged line)"
|
|
pattern: "./plugins/golem15/user"
|
|
- from: "../fonoteka.go/summer.yaml"
|
|
to: "../fonoteka.go/plugins.gen.go"
|
|
via: "summer build reads plugins[].module and emits the blank import"
|
|
pattern: "sm-user-plugin"
|
|
- from: "fonoteka.go gitlink plugins/golem15/user"
|
|
to: "sm-user-plugin remote master"
|
|
via: "recorded submodule SHA equals the pushed master SHA"
|
|
pattern: "git ls-remote"
|
|
- from: ".planning/phases/01-framework-kernel-foundation/01-CONTEXT.md deferred line"
|
|
to: ".planning/notes/core-plugins-own-repos.md"
|
|
via: "Superseded pointer"
|
|
pattern: "core-plugins-own-repos"
|
|
---
|
|
|
|
<objective>
|
|
Extract the shared user plugin `golem15.user` out of the application repo fonoteka.go into its own repository `sm-user-plugin` (remote `git@git.golem15.com:golem15/sm-user-plugin.git`, created empty by the user), with history preserved, renamed to module `git.golem15.com/golem15/sm-user-plugin`, and mount it back into fonoteka.go as a git submodule at the same path `plugins/golem15/user`. Then record the decision in summercms.go planning docs and point the unexecuted Phase 12 plans at the new repo.
|
|
|
|
Purpose: the user plugin is a core plugin shared across Golem15 projects. A second application (sm-summercmsio-app) now exists and the Journal port is next, so the plugin must live once, in its own `sm-<name>-plugin` repo, the same way sm-summercmsio-app already mounts sm-summercmsio-plugin. This supersedes the Phase 1 deferral ("only when a second app (keios.eu) needs one").
|
|
|
|
Output: the pushed sm-user-plugin repo; fonoteka.go with the submodule, rewritten imports, updated go.mod/summer.yaml/plugins.gen.go, README and CLAUDE.md layout notes; a decision note and planning-doc updates in summercms.go.
|
|
|
|
Locked decisions (from the orchestrator; implement exactly):
|
|
- LD-1 Module path `git.golem15.com/golem15/sm-user-plugin`. Plugin ID stays `golem15.user`; table names, migration IDs, config keys `golem15.user.*` and the API are unchanged. Pure move, no behaviour change.
|
|
- LD-2 Preserve history with `git subtree split --prefix=plugins/golem15/user`, commit the module-path rename in the new repo, push `master` to the remote. Pushing to that empty remote is authorized. Never force-push.
|
|
- LD-3 Mount back as a git submodule at fonoteka.go/plugins/golem15/user (same path, go.work unchanged); update the fonoteka.go go.mod replace to the new module path; rewrite all imports; update summer.yaml; regenerate generated files.
|
|
- LD-4 Decision note in summercms.go/.planning/notes superseding the Phase 1 deferral; update planning docs (Phase 12 plans, ROADMAP, PROJECT) to name sm-user-plugin. Planning docs and code in separate commits; the planning commit is in summercms.go.
|
|
- LD-5 summercms.go code and docs must not reference the old module path (verified at planning time: only `.planning/` and the `scripts/check-phase*.sh` directory paths `./plugins/golem15/user/...` mention the plugin, and those paths stay valid because the mount path is unchanged; no summercms.go code change). Update the fonoteka.go README and CLAUDE.md repo-structure text. The plugin README must not name the consuming application.
|
|
|
|
Repos and commits: sm-user-plugin (submodule checkout), fonoteka.go, summercms.go. Commits in every repo carry no co-author or attribution trailers (user's global rule overrides any attribution reminder). One logical change per commit. workflow.use_worktrees=false: work sequentially on the main trees. A dev server (`bin/fonoteka serve` on 127.0.0.1:8080) may be running; rebuilding bin/fonoteka is fine, do not kill it.
|
|
</objective>
|
|
|
|
<execution_context>
|
|
@~/.claude/gsd-core/workflows/execute-plan.md
|
|
@~/.claude/gsd-core/templates/summary.md
|
|
</execution_context>
|
|
|
|
<context>
|
|
@.planning/STATE.md
|
|
@CLAUDE.md
|
|
@../fonoteka.go/CLAUDE.md
|
|
@../fonoteka.go/README.md
|
|
@../fonoteka.go/go.work
|
|
@../fonoteka.go/summer.yaml
|
|
@../sm-summercmsio-app/README.md
|
|
|
|
Facts gathered at planning time (re-check with the commands named; do not trust counts blindly):
|
|
- fonoteka.go has no git remote, a clean tree, and 184 commits; `git log --oneline -- plugins/golem15/user | wc -l` = 21, no merge commits touch the path. `git status --ignored plugins/golem15/user` shows no untracked or ignored files inside the plugin.
|
|
- Old module path appears in: the plugin's go.mod and 20 of its .go files; fonoteka.go go.mod (require line ~10, replace line ~121); plugins/golem15/fonoteka/go.mod (require line ~6, `replace ... => ../user` line ~123); summer.yaml; plugins.gen.go; app/app.go; six parity/*.go files; 19 .go files under plugins/golem15/fonoteka. main.go does not import the plugin. Re-derive the list with `grep -rlI --exclude-dir=.git` at execution time.
|
|
- The plugin's own go.mod keeps `replace git.golem15.com/golem15/summercms => ../../../../summercms.go`, which resolves only when mounted at `<app>/plugins/golem15/user` next to summercms.go. Precedent: sm-summercmsio-plugin's go.mod has the same replace, and sm-summercmsio-app mounts it as submodule `plugins/golem15/summercms` with module path `git.golem15.com/golem15/sm-summercmsio-plugin`, `replace ... => ./plugins/golem15/summercms` in the app go.mod and an SSH url in .gitmodules.
|
|
- `GOWORK=off go -C plugins/golem15/user vet ./...` currently fails with "updates to go.mod needed". `go mod tidy -diff` shows only added `// indirect` requirements (github.com/tidwall/gjson, match, pretty, sjson) and go.sum lines (robfig/cron/v3, tidwall/*): transitive framework deps the plugin go.mod never recorded.
|
|
- `summer build` (summercms.go internal/build) regenerates main.go and plugins.gen.go from summer.yaml with writeIfChanged, runs the models-leaf check (resolves plugin dirs through go.mod replace), then `go build -o bin/<binary> .` with the workspace. No `summer` binary is on PATH; build it from summercms.go.
|
|
- In workspace mode, `go test ./...` from the fonoteka.go root covers only the root module; plugin modules need explicit patterns `./plugins/golem15/fonoteka/... ./plugins/golem15/user/...` (as the summercms.go scripts/check-phase*.sh do).
|
|
- Known test coupling, kept as is (pure move): TestRegisterCORSPath in register_test.go walks up to the host app's `config/http.yaml`, so that test passes only when the plugin is mounted inside an application checkout. Record it in the SUMMARY as a follow-up, do not change it.
|
|
- Plugin code comments mention the consuming application in two places (models/organisation.go, updates/10_organisations.go) and register_test.go guards the import direction against the app plugin's path. These are code, not the README; leave them (pure move) and list them in the SUMMARY as follow-ups.
|
|
- summercms.go working tree has an unrelated modified `.planning/state.json` and an untracked zip; stage only this plan's files by explicit path.
|
|
</context>
|
|
|
|
<tasks>
|
|
|
|
<task type="tracer">
|
|
<name>Task 1: fonoteka.go builds golem15.user from the sm-user-plugin submodule (history pushed, same path, same bytes)</name>
|
|
<precondition>`git -C ../fonoteka.go status --porcelain` prints nothing, and `git ls-remote git@git.golem15.com:golem15/sm-user-plugin.git` exits 0 and prints nothing (remote still empty). If the remote already has refs, stop and report; never force-push.</precondition>
|
|
<files>../fonoteka.go/.gitmodules, ../fonoteka.go/plugins/golem15/user (directory replaced by gitlink)</files>
|
|
<read_first>../fonoteka.go/go.work, ../fonoteka.go/go.mod (replace block), ../sm-summercmsio-app/.gitmodules (submodule precedent), ../fonoteka.go/plugins/golem15/user/go.mod</read_first>
|
|
<action>Work in /media/nvme/dev/golem15/summercms.io/summercms/fonoteka.go. Per LD-2 and LD-3:
|
|
(1) Record the baseline: the pre-extraction tree hash `git rev-parse HEAD:plugins/golem15/user` and the count `git log --oneline -- plugins/golem15/user | wc -l`. Write both into the SUMMARY later.
|
|
(2) Split the history: `git subtree split --prefix=plugins/golem15/user -b split/sm-user-plugin`. Confirm the split tip's tree equals the baseline tree hash (`git rev-parse 'split/sm-user-plugin^{tree}'`) and `git rev-list --count split/sm-user-plugin` equals the baseline count. If the counts differ but the tree hash matches, record both numbers in the SUMMARY and continue; if the tree hash differs, stop.
|
|
(3) Secret scan before publishing (T-261002-01): `git log -p split/sm-user-plugin` must contain no private key block and no non-empty secret, password or DSN value (config keeps `secret: ""`). Stop and report if anything matches.
|
|
(4) Publish: `git push git@git.golem15.com:golem15/sm-user-plugin.git split/sm-user-plugin:refs/heads/master` (a plain push to the empty remote, authorized by the user). Then delete the local branch with `git branch -D split/sm-user-plugin`.
|
|
(5) Replace the in-tree directory with the submodule at the same path: `git rm -r -q plugins/golem15/user`, remove any leftover empty directory, then `git submodule add git@git.golem15.com:golem15/sm-user-plugin.git plugins/golem15/user` (SSH url, no `-b`, matching the sm-summercmsio-app precedent). Make sure the checkout is on branch master tracking origin/master (`git -C plugins/golem15/user checkout master` if the clone left a detached HEAD). go.work, go.mod and every import stay untouched in this task: the submodule HEAD is the split tip, whose module path is still the old one, so the workspace builds exactly as before.
|
|
(6) Verify (see verify block), then commit .gitmodules plus the gitlink and the staged deletions in fonoteka.go with exactly the subject `refactor(user): mount golem15.user from the sm-user-plugin submodule` (the verify gates locate the commit by this subject) and a body noting the history was split with git subtree and the tree is byte-identical. No co-author or attribution trailers. Do not edit README.md or CLAUDE.md here; Task 2 documents the final layout.</action>
|
|
<verify>
|
|
<automated>cd /media/nvme/dev/golem15/summercms.io/summercms/fonoteka.go && M=$(git log -1 --format=%H --grep='mount golem15.user from the sm-user-plugin submodule') && test -n "$M" && SS=$(git submodule status plugins/golem15/user) && printf '%s\n' "$SS" | grep -Eq '^ [0-9a-f]{40} plugins/golem15/user' && grep -q 'url = git@git.golem15.com:golem15/sm-user-plugin.git' .gitmodules && grep -q './plugins/golem15/user' go.work && SUB=$(git -C plugins/golem15/user rev-parse HEAD) && RM=$(git ls-remote git@git.golem15.com:golem15/sm-user-plugin.git refs/heads/master) && test "${RM%%[[:space:]]*}" = "$SUB" && LINK=$(git rev-parse "$M:plugins/golem15/user") && test "$LINK" = "$SUB" && SUBTREE=$(git -C plugins/golem15/user rev-parse 'HEAD^{tree}') && PRETREE=$(git rev-parse "$M^:plugins/golem15/user") && test "$SUBTREE" = "$PRETREE" && go vet ./... ./plugins/golem15/fonoteka/... ./plugins/golem15/user/... && go test -short -count=1 ./... ./plugins/golem15/fonoteka/... ./plugins/golem15/user/... && ST=$(git status --porcelain) && test -z "$ST"</automated>
|
|
<fails_when>Any command exits non-zero: the submodule is missing, uninitialized or modified; the .gitmodules url differs; the remote master is not the recorded gitlink; the submodule tree differs from the pre-extraction directory tree; vet or the short test run fails; or fonoteka.go has uncommitted changes after the commit.</fails_when>
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- `git -C ../fonoteka.go show --stat <mount commit>` (the commit with subject `refactor(user): mount golem15.user from the sm-user-plugin submodule`) shows .gitmodules added, plugins/golem15/user as a gitlink, and the old plugin files deleted, and nothing else.
|
|
- The submodule HEAD tree hash equals the pre-extraction tree hash recorded in step (1).
|
|
- `git -C ../fonoteka.go branch --list 'split/*'` prints nothing.
|
|
</acceptance_criteria>
|
|
<done>fonoteka.go builds and passes its short suite with golem15.user coming from the sm-user-plugin repo, whose master on git.golem15.com holds the plugin's full history; the commit is in fonoteka.go.</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Task 2: The plugin is module git.golem15.com/golem15/sm-user-plugin in its repo and in every fonoteka.go importer, with the full suite green</name>
|
|
<precondition>Task 1 is committed: `git -C ../fonoteka.go submodule status plugins/golem15/user` starts with a space, and Docker is reachable for testcontainers (`docker info` exits 0).</precondition>
|
|
<files>../fonoteka.go/plugins/golem15/user/{go.mod, go.sum, README.md, and the 20 .go files listed in files_modified}, ../fonoteka.go/go.mod, ../fonoteka.go/plugins/golem15/fonoteka/go.mod, ../fonoteka.go/summer.yaml, ../fonoteka.go/plugins.gen.go, ../fonoteka.go/app/app.go, ../fonoteka.go/parity/*.go (six files), ../fonoteka.go/plugins/golem15/fonoteka/**/*.go (19 files), ../fonoteka.go/README.md, ../fonoteka.go/CLAUDE.md</files>
|
|
<read_first>../fonoteka.go/plugins/golem15/user/plugin.go, ../fonoteka.go/plugins/golem15/user/routes.go, ../fonoteka.go/plugins/golem15/user/config/config.yaml (facts for the README), ../sm-summercmsio-app/plugins/golem15/summercms/README.md (plugin README precedent), ../fonoteka.go/README.md, ../fonoteka.go/CLAUDE.md, CLAUDE.md "Documentation" section (module README structure), internal/build/build.go (what summer build writes)</read_first>
|
|
<action>Per LD-1, LD-3 and LD-5. The rewrite is a prefix replacement of the old app-nested module path with `git.golem15.com/golem15/sm-user-plugin`; subpackages keep their suffixes (`.../classes`, `.../models`, `.../updates`, `.../controllers`, `.../console`).
|
|
<!-- planner-discipline-allow: fonoteka/plugins/golem15/user -->
|
|
<!-- planner-discipline-allow: golem15/fonoteka/plugins -->
|
|
The old path is `git.golem15.com/golem15/fonoteka/plugins/golem15/user`.
|
|
|
|
A. In the submodule (git -C ../fonoteka.go/plugins/golem15/user), three commits, not pushed yet:
|
|
(1) Rename: change the go.mod module line and rewrite every occurrence in the plugin's .go files (`grep -rlI` inside the submodule, then sed with an escaped-dot pattern). Run `gofmt -l .` (must print nothing; the new path sorts between the same neighbours as the old one, so no import reordering is expected). Touch nothing else: no identifier, string, table name, migration ID, config key or comment changes. Commit `refactor: rename module to git.golem15.com/golem15/sm-user-plugin`.
|
|
(2) Tidy for standalone module mode: `GOWORK=off go mod tidy` in the submodule. The go.mod diff may only add `// indirect` requirement lines (at planning time: tidwall gjson/match/pretty/sjson) and go.sum lines; if tidy wants to change or drop any direct requirement or bump a version, revert it and report instead. Commit `build: tidy go.mod for standalone module mode`.
|
|
(3) README.md (new, Claude's discretion on wording, LD-5 binding): follow the module README structure from CLAUDE.md "Documentation": H1 `golem15.user`, a one-sentence summary, the module line `git.golem15.com/golem15/sm-user-plugin`, then Overview, Features (registration, login, JWT issue/refresh and blacklist, organisations, personal API tokens, throttling, must-change-password lock, avatar upload, mail templates; derive the exact list from plugin.go and routes.go, name only identifiers that exist), Usage (mount in an application: `git submodule add git@git.golem15.com:golem15/sm-user-plugin.git plugins/golem15/user`, add `./plugins/golem15/user` to go.work, require plus `replace git.golem15.com/golem15/sm-user-plugin => ./plugins/golem15/user` in the app go.mod, and a summer.yaml entry `id: golem15.user` with that module; the plugin expects the framework checkout at `../../../../summercms.go` relative to itself), Configuration (keys under `golem15.user.*`, including `golem15.user.jwt.secret` overridable with `SUMMER_GOLEM15__USER__JWT__SECRET`, no production default), Dependencies (the framework `git.golem15.com/golem15/summercms`) and Testing (`go test -short ./...` for the fast run, full run needs Docker; one test reads the host application's `config/http.yaml`, so run tests inside an application checkout). Use "the application" or "host application" and neutral example names (acme, blog) only; never name the consuming application. Commit `docs: describe the user plugin`.
|
|
Then check the submodule alone: `GOWORK=off go -C ../fonoteka.go/plugins/golem15/user vet ./...` must pass.
|
|
|
|
B. In fonoteka.go:
|
|
(4) Rewrite every importer and manifest: root go.mod (require and replace lines; the replace target stays `./plugins/golem15/user`), plugins/golem15/fonoteka/go.mod (require and replace; the target stays `../user`), summer.yaml (`module:` of golem15.user), app/app.go, the six parity files and the 19 fonoteka-plugin .go files. Re-derive the file list with `grep -rlI --exclude-dir=.git` over the old path and exclude plugins.gen.go, main.go and the submodule directory (already done in A). Do not run go mod tidy in fonoteka.go modules.
|
|
(5) Regenerate, never hand-edit plugins.gen.go/main.go: build the CLI into a temporary directory outside both repos (the executor scratchpad or `mktemp -d`) with `go -C /media/nvme/dev/golem15/summercms.io/summercms/summercms.go build -o <tmp>/summer ./cmd/summer`, then run `<tmp>/summer build` with cwd fonoteka.go. It rewrites plugins.gen.go (the blank import becomes `_ "git.golem15.com/golem15/sm-user-plugin"`, PluginIDs order unchanged), leaves main.go unchanged, and builds bin/fonoteka (overwriting the binary of a running dev server is fine; do not kill it).
|
|
(6) Docs (fonoteka.go is the application, so naming it is fine here): README.md gets a short "Layout" section after the intro, modeled on the sm-summercmsio-app README table: `plugins/golem15/fonoteka/` is the application plugin in this repo; `plugins/golem15/user/` is submodule `golem15/sm-user-plugin`, the shared user plugin `golem15.user`, module `git.golem15.com/golem15/sm-user-plugin`; after cloning run `git submodule update --init`. CLAUDE.md gets a "Submodules" section: plugins/golem15/user is the sm-user-plugin submodule; change it inside the submodule, commit and push its master first, then commit the bumped pointer in fonoteka.go as a separate commit; it is a core plugin shared across projects, so no breaking changes to its contract unless the user asks.
|
|
(7) Gates, in this order: gofmt on the touched trees; workspace vet `go vet ./... ./plugins/golem15/fonoteka/... ./plugins/golem15/user/...`; `go build -o bin/fonoteka .`; the negative grep for the old path (text files only, `-I`, excluding .git); the pure-move diff gates in the verify block; then the full Docker suite `go test -count=1 -timeout 30m ./... ./plugins/golem15/fonoteka/... ./plugins/golem15/user/...`. The full run can exceed the 10-minute foreground limit: run it in the background with output captured to a log in the scratchpad, then require exit status 0 and no `FAIL` line in the log. If any gate needs a plugin change, add it as another submodule commit before pushing.
|
|
(8) Publish and record: `git -C plugins/golem15/user push origin master` (fast-forward only, never force). Then in fonoteka.go stage the gitlink bump, both go.mod files, summer.yaml, plugins.gen.go, the rewritten .go files, README.md and CLAUDE.md, confirm `git status --porcelain` lists nothing unexpected, and commit with exactly the subject `refactor(user): import golem15.user from git.golem15.com/golem15/sm-user-plugin` (the verify gates locate the commit by this subject). No co-author or attribution trailers in any commit.</action>
|
|
<verify>
|
|
<automated>cd /media/nvme/dev/golem15/summercms.io/summercms/fonoteka.go && test -z "$(gofmt -l app parity plugins/golem15/fonoteka plugins/golem15/user *.go)" && ! grep -rIn --exclude-dir=.git 'fonoteka/plugins/golem15/user' . && grep -qx 'module git.golem15.com/golem15/sm-user-plugin' plugins/golem15/user/go.mod && grep -q 'module: git.golem15.com/golem15/sm-user-plugin' summer.yaml && grep -q '_ "git.golem15.com/golem15/sm-user-plugin"' plugins.gen.go && grep -q 'git.golem15.com/golem15/sm-user-plugin => ./plugins/golem15/user' go.mod && grep -q 'git.golem15.com/golem15/sm-user-plugin => ../user' plugins/golem15/fonoteka/go.mod && grep -q '"golem15.user"' plugins.gen.go && go vet ./... ./plugins/golem15/fonoteka/... ./plugins/golem15/user/... && GOWORK=off go -C plugins/golem15/user vet ./... && go -C plugins/golem15/user vet ./... && go build -o bin/fonoteka . && test -f plugins/golem15/user/README.md && ! grep -qiE 'fonoteka|plytarium|płytarium' plugins/golem15/user/README.md && M=$(git log -1 --format=%H --grep='mount golem15.user from the sm-user-plugin submodule') && R=$(git log -1 --format=%H --grep='import golem15.user from git.golem15.com/golem15/sm-user-plugin') && test -n "$M" && test -n "$R" && BASE=$(git rev-parse "$M:plugins/golem15/user") && PD=$(git -C plugins/golem15/user diff "$BASE"..HEAD -- '*.go') && test -z "$(printf '%s\n' "$PD" | grep '^[-+]' | grep -v '^[-+][-+]' | grep -v 'git.golem15.com/golem15/')" && AD=$(git diff "$M".."$R" -- '*.go' summer.yaml go.mod plugins/golem15/fonoteka/go.mod ':!plugins/golem15/user') && test -z "$(printf '%s\n' "$AD" | grep '^[-+]' | grep -v '^[-+][-+]' | grep -v 'git.golem15.com/golem15/')" && SUB=$(git -C plugins/golem15/user rev-parse HEAD) && LINK=$(git rev-parse "$R:plugins/golem15/user") && test "$LINK" = "$SUB" && SS=$(git submodule status plugins/golem15/user) && printf '%s\n' "$SS" | grep -Eq '^ [0-9a-f]{40} plugins/golem15/user' && RM=$(git ls-remote git@git.golem15.com:golem15/sm-user-plugin.git refs/heads/master) && test "${RM%%[[:space:]]*}" = "$SUB" && SUBST=$(git -C plugins/golem15/user status --porcelain) && test -z "$SUBST" && ST=$(git status --porcelain) && test -z "$ST" && go test -count=1 -timeout 30m ./... ./plugins/golem15/fonoteka/... ./plugins/golem15/user/...</automated>
|
|
<fails_when>Any command exits non-zero: gofmt reports a file; any text file still carries the old module path; a manifest, go.mod or plugins.gen.go lacks the new path; vet fails in workspace or standalone mode; the binary does not build; the README is missing or names the consuming application; a changed .go/manifest line is not a module-path line (behaviour or identifier change slipped in); the submodule is dirty, unpushed or not at the recorded gitlink; fonoteka.go has uncommitted changes; or the full suite (Docker) has any failure.</fails_when>
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- `git -C ../fonoteka.go/plugins/golem15/user log --oneline -3` shows the README, tidy and rename commits on top of the split history, and origin/master points at the same SHA.
|
|
- `git -C ../fonoteka.go/plugins/golem15/user diff <split-tip-sha>..HEAD -- go.mod` shows only the module line and added `// indirect` lines.
|
|
- `git -C ../fonoteka.go diff --stat <mount-sha>..<rename-sha> -- main.go` prints nothing (main.go unchanged by summer build).
|
|
- `grep -q 'id: golem15.user' ../fonoteka.go/summer.yaml` succeeds and the plugin ID string in ../fonoteka.go/plugins/golem15/user/plugin.go is unchanged.
|
|
- `grep -q 'sm-user-plugin' ../fonoteka.go/README.md && grep -q 'git submodule update --init' ../fonoteka.go/README.md && grep -q 'sm-user-plugin' ../fonoteka.go/CLAUDE.md` succeeds.
|
|
- Full-suite log shows `ok` for the root module packages, plugins/golem15/fonoteka and plugins/golem15/user packages and no `FAIL`.
|
|
</acceptance_criteria>
|
|
<done>The plugin's own repo and every fonoteka.go importer use git.golem15.com/golem15/sm-user-plugin, the plugin vets on its own, its README names no consuming application, the binary builds, the full suite passes, the submodule commits are pushed and fonoteka.go records them.</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Task 3: summercms.go planning docs record that core plugins live in sm-*-plugin repos and point Phase 12 at sm-user-plugin (planning docs only)</name>
|
|
<precondition>Task 2 is committed in fonoteka.go and the sm-user-plugin remote master equals the recorded gitlink.</precondition>
|
|
<files>.planning/notes/core-plugins-own-repos.md, .planning/phases/01-framework-kernel-foundation/01-CONTEXT.md, .planning/PROJECT.md, .planning/ROADMAP.md, .planning/phases/12-p-ytarium-api-collections-and-albums/12-01-PLAN.md, .planning/phases/12-p-ytarium-api-collections-and-albums/12-02-PLAN.md, .planning/phases/12-p-ytarium-api-collections-and-albums/12-05-PLAN.md</files>
|
|
<read_first>.planning/notes/lagoon-per-query-collation.md (note frontmatter and Supersedes precedent), .planning/phases/01-framework-kernel-foundation/01-CONTEXT.md (deferred section, line ~111), .planning/PROJECT.md (Constraints "Two repositories" bullet line ~137, Key Decisions table line ~142), .planning/ROADMAP.md (Phase 12 section, Repos line and the 12-01 plan list entry; Phase 11.2 naming-convention line), .planning/phases/12-p-ytarium-api-collections-and-albums/12-01-PLAN.md (objective, Task 3 action and acceptance criteria), 12-02-PLAN.md (interface context line ~145), 12-05-PLAN.md (Task 3 action)</read_first>
|
|
<action>Per LD-4 and LD-5. Use Edit (scoped replacements) on existing files; Write only for the new note.
|
|
(1) New note `.planning/notes/core-plugins-own-repos.md` with frontmatter title, date 2026-10-02 and context (quick task 261002-esz). Sections: Decision (shared core plugins, starting with the user plugin and later blog, pages, payment and the other cross-project Golem15 plugins as they are ported, live in their own repos `git.golem15.com/golem15/sm-<name>-plugin` with module path equal to the repo path; the Go package and plugin ID keep their plain names, so `golem15.user` is unchanged; each application mounts them as git submodules at `plugins/<vendor>/<name>`, lists them in go.work and adds a go.mod require plus local replace; the plugin's own go.mod replaces the framework at `../../../../summercms.go`; application-specific plugins such as golem15.fonoteka stay in the application repo). Supersedes (the Phase 1 01-CONTEXT deferred item about extracting shared stack plugins only when keios.eu needs one). Trigger (a second application, sm-summercmsio-app, exists and the Journal port is next; the sm-summercmsio-app/sm-summercmsio-plugin submodule layout is the precedent). How it was done for golem15.user (history split with git subtree, pushed as master, module renamed to `git.golem15.com/golem15/sm-user-plugin`, mounted back at fonoteka.go/plugins/golem15/user, no behaviour change: tables, migration IDs, config keys and API unchanged). Workflow (change a submodule plugin inside its checkout, commit and push its master first, then commit the bumped pointer in the application repo as a separate commit; core plugin contracts stay non-breaking unless the user asks). Consequences and follow-ups (the plugin README never names a consuming application; TestRegisterCORSPath reads the host application's config/http.yaml so the plugin's tests run only inside an application checkout; two code comments still name the application; fonoteka.go itself becoming sm-fonoteka-app is a separate pending rename).
|
|
(2) 01-CONTEXT.md: append to the deferred line about extracting shared stack plugins a pointer that it was superseded on 2026-10-02 by `.planning/notes/core-plugins-own-repos.md` (golem15.user moved to sm-user-plugin). Change nothing else in that file.
|
|
(3) PROJECT.md: in the Constraints "Two repositories" bullet, say the fonoteka.go workspace holds the application plugins (fonoteka, translate, feedback, sitemap) and mounts shared core plugins from their own `sm-<name>-plugin` repos as git submodules, the user plugin being `sm-user-plugin` (module `git.golem15.com/golem15/sm-user-plugin`) at `plugins/golem15/user`; keep the rest of the bullet (websockets/lighthouse wording, the framework replace, roadmap repos, planning docs location). Add a Key Decisions row: shared core plugins live in their own sm-*-plugin repos mounted as submodules (first sm-user-plugin); rationale: a second application exists and Journal is next, one copy per core plugin, supersedes the Phase 1 keios.eu trigger; outcome `✓ Good (2026-10-02, quick 261002-esz)`.
|
|
(4) ROADMAP.md, Phase 12 section only: the `**Repos:**` line becomes `fonoteka.go, sm-user-plugin (submodule at fonoteka.go/plugins/golem15/user)`; in the 12-01 plan list entry, the user-groups clause names the Go user plugin as sm-user-plugin. Do not touch Phase 12 goal or criteria wording (12-01 Task 4 rewrites them and greps them), and do not edit completed phases.
|
|
(5) 12-01-PLAN.md: in the objective, the parenthetical that places the user plugin in fonoteka.go becomes: repo sm-user-plugin, module `git.golem15.com/golem15/sm-user-plugin`, mounted as a git submodule at `../fonoteka.go/plugins/golem15/user`; the objective's `Repos:` sentence lists summercms.go (framework and planning docs), sm-user-plugin (user groups, committed inside the submodule and pushed to its origin master first) and fonoteka.go (parity schema allow-list plus the bumped submodule pointer). In Task 3's action, item (3)'s parenthetical naming the fonoteka.go user plugin becomes sm-user-plugin, and append item (6) Commits: commit the user-plugin files inside the submodule (`git -C ../fonoteka.go/plugins/golem15/user`), push its master, then commit parity/schema_diff_test.go together with the bumped submodule pointer in fonoteka.go, no co-author tags. In Task 3's acceptance criteria, rewrite the second clause of the `"groups":` bullet so it says this task's sm-user-plugin commit touches no file under `controllers/` (payload untouched). File paths under `../fonoteka.go/plugins/golem15/user/` stay as they are (the mount path is unchanged).
|
|
(6) 12-02-PLAN.md: where the interface context names the user plugin's `classes.HasGroupCode(ctx, db, userID, "admin")`, add its import path `git.golem15.com/golem15/sm-user-plugin/classes`.
|
|
(7) 12-05-PLAN.md: append to Task 3's action one sentence: the user plugin test file lives in the sm-user-plugin submodule; commit it there and push its master, then commit the bumped pointer in fonoteka.go with the other fonoteka.go test changes.
|
|
(8) Validate the three Phase 12 plans still parse (verify block), then commit only these seven files by explicit path (never `git add -A`: `.planning/state.json` and an untracked zip must stay out) with exactly the subject `docs: move golem15.user to sm-user-plugin and supersede the Phase 1 extraction deferral` (the verify gate locates the commit by this subject). No co-author or attribution trailers. No code in this commit.</action>
|
|
<verify>
|
|
<automated>cd /media/nvme/dev/golem15/summercms.io/summercms/summercms.go && N=.planning/notes/core-plugins-own-repos.md && P=.planning/phases/12-p-ytarium-api-collections-and-albums && grep -q 'Supersedes' $N && grep -q 'git.golem15.com/golem15/sm-user-plugin' $N && grep -q 'submodule' $N && grep -q 'core-plugins-own-repos' .planning/phases/01-framework-kernel-foundation/01-CONTEXT.md && grep -q 'sm-user-plugin' .planning/PROJECT.md && grep -A6 '### Phase 12:' .planning/ROADMAP.md | grep -q 'sm-user-plugin' && grep -q 'git.golem15.com/golem15/sm-user-plugin' $P/12-01-PLAN.md && grep -q 'sm-user-plugin commit touches no file' $P/12-01-PLAN.md && grep -q 'git.golem15.com/golem15/sm-user-plugin/classes' $P/12-02-PLAN.md && grep -q 'sm-user-plugin' $P/12-05-PLAN.md && for f in $P/12-01-PLAN.md $P/12-02-PLAN.md $P/12-05-PLAN.md; do node ~/.claude/gsd-core/bin/gsd-tools.cjs query verify.plan-structure "$f" | grep -q '"valid": *true' || exit 1; done && D=$(git log -1 --format=%H --grep='supersede the Phase 1 extraction deferral') && test -n "$D" && FL=$(git diff-tree --no-commit-id --name-only -r "$D") && test "$(printf '%s\n' "$FL" | wc -l)" -eq 7 && test -z "$(printf '%s\n' "$FL" | grep -v '^\.planning/')" && ! printf '%s\n' "$FL" | grep -q 'state.json' && ! grep -rIn --exclude-dir=.git --exclude-dir=.planning --exclude-dir=node_modules 'golem15/fonoteka/plugins' . && go vet ./...</automated>
|
|
<fails_when>Any command exits non-zero: the note lacks Supersedes, the new module path or the submodule workflow; the 01-CONTEXT pointer, the PROJECT or ROADMAP Phase 12 mention, or any Phase 12 plan edit is missing; a Phase 12 plan no longer passes verify.plan-structure; the commit does not contain exactly seven files, contains a non-.planning path or includes state.json; summercms.go code or docs reference an app-nested plugin module path; or go vet fails.</fails_when>
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- `git show --stat <docs commit>` (subject `docs: move golem15.user to sm-user-plugin and supersede the Phase 1 extraction deferral`) lists exactly the seven files of this task.
|
|
- The ROADMAP Phase 12 goal and success-criteria lines are byte-identical to before (`git diff <docs-sha>^..<docs-sha> -- .planning/ROADMAP.md` touches only the Repos line and the 12-01 plan entry).
|
|
- `grep -q 'keios.eu' .planning/phases/01-framework-kernel-foundation/01-CONTEXT.md` still succeeds (the deferral is annotated, not deleted).
|
|
</acceptance_criteria>
|
|
<done>Anyone reading the planning docs learns that core plugins live in sm-*-plugin repos mounted as submodules, that golem15.user is sm-user-plugin, and how Phase 12 commits user-plugin changes; the Phase 1 deferral points to the superseding note.</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<threat_model>
|
|
## Trust Boundaries
|
|
|
|
| Boundary | Description |
|
|
|----------|-------------|
|
|
| local fonoteka.go history -> git.golem15.com remote | Plugin source and its full history leave the machine for the first time |
|
|
| remote sm-user-plugin -> fonoteka.go build | The application now compiles code fetched from a submodule url |
|
|
|
|
## STRIDE Threat Register
|
|
|
|
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|
|
|-----------|----------|-----------|----------|-------------|-----------------|
|
|
| T-261002-01 | Information Disclosure | split history pushed to sm-user-plugin | medium | mitigate | Task 1 step (3) scans `git log -p` of the split branch for private keys and non-empty secret/password/DSN values before the push; config ships `secret: ""`; the remote is the user's private golem15 repo. |
|
|
| T-261002-02 | Tampering | .gitmodules url and recorded gitlink | medium | mitigate | Verify gates require the exact SSH url, remote master SHA equal to the gitlink, and (Task 1) a submodule tree hash identical to the pre-extraction directory tree. |
|
|
| T-261002-03 | Tampering | rename slipping behaviour changes into a core plugin | high | mitigate | Task 2 diff gates fail if any changed .go, go.mod or summer.yaml line is not a module-path line; the full suite including parity and user-plugin tests must pass; plugin ID, tables, migration IDs and config keys are untouched by construction. |
|
|
| T-261002-04 | Repudiation | overwriting remote history | low | mitigate | Precondition requires an empty remote; pushes are plain fast-forward pushes, never force. |
|
|
| T-261002-SC | Tampering | go mod tidy in the plugin | low | mitigate | No new packages are installed; tidy may only add `// indirect` lines for modules already in the framework's dependency graph (tidwall/*, robfig/cron/v3); any direct requirement change aborts the tidy commit. |
|
|
</threat_model>
|
|
|
|
<verification>
|
|
- sm-user-plugin: remote master = submodule HEAD = fonoteka.go gitlink; history = split commits + rename + tidy + README; `GOWORK=off go -C ../fonoteka.go/plugins/golem15/user vet ./...` green; README names no consuming application.
|
|
- fonoteka.go: `git submodule status` clean for plugins/golem15/user; go.work unchanged; `grep -rIn --exclude-dir=.git` for the old module path prints nothing; `go vet` and the full `go test -count=1 ./... ./plugins/golem15/fonoteka/... ./plugins/golem15/user/...` (Docker) green; `go build -o bin/fonoteka .` works; two commits (mount, rename).
|
|
- summercms.go: one planning-docs commit with seven files; Phase 12 plans still pass verify.plan-structure; no code or docs reference an app-nested plugin module path; `go vet ./...` green (no code touched).
|
|
</verification>
|
|
|
|
<success_criteria>
|
|
- The user plugin lives in git.golem15.com/golem15/sm-user-plugin with its history, as module git.golem15.com/golem15/sm-user-plugin, and fonoteka.go consumes it as a submodule at the unchanged path with no behaviour change.
|
|
- Every gate in the three verify blocks passes; no commit in any repo carries a co-author or attribution trailer.
|
|
- The decision note supersedes the Phase 1 deferral and the planning docs, including the unexecuted Phase 12 plans, describe the submodule workflow.
|
|
</success_criteria>
|
|
|
|
<output>
|
|
Create `.planning/quick/261002-esz-extract-golem15-user-plugin-from-fonotek/261002-esz-SUMMARY.md` when done. Record: baseline tree hash and commit count vs the split result, the pushed sm-user-plugin SHAs, the fonoteka.go and summercms.go commit SHAs, the full-suite duration and package results, the tidy diff, and follow-ups (TestRegisterCORSPath host coupling, the two application-naming code comments in the plugin, the pending fonoteka.go to sm-fonoteka-app rename).
|
|
</output>
|