29 KiB
Project Research Summary
Project: SummerCMS (Go) — v1 = Płytarium (fonoteka) headless backend port Domain: Go 1.27 WinterCMS/Laravel-shaped CMF, compiled-plugin model, porting a live PHP headless API to Go with byte-level response parity for an unchanged Nuxt 4 app and MCP/OAuth client Researched: 2026-09-16 Confidence: HIGH on stack versions, plugin-architecture mechanics, and PHP-source-grounded pitfalls; MEDIUM on architectural judgment calls (migration tool, OpenAPI approach, config format) and on features/patterns inferred from ecosystem comparison rather than direct source reading
Executive Summary
SummerCMS v1 is not a from-scratch CMS design problem — it is a constrained port of a fully-specified, already-running PHP application (Płytarium/fonoteka: 25 models, 154 routes, 27 migrations, 3 jobs, 3 console commands) onto a compiled-plugin Go framework whose shape (Caddy/xcaddy-style build-time registration) is already decided and well-precedented. All four research passes converge on the same operating principle: the PHP source is the spec, and the parity harness is the compiler. Every stack pick, feature, architectural pattern, and pitfall in this research is grounded in reading the actual fonoteka PHP source (models, controllers, routes.php, serializer traits) rather than a generic "how do headless CMSes work" survey — this is the single most load-bearing fact for how the roadmap should be sequenced.
The recommended approach: stand up a minimal kernel (config, plugin registry, event bus, container) only far enough to support one real vertical slice — GET /_fonoteka/api/v1/genres through every layer (config → DB → plugin registry → routing → JWT auth → GORM → JSON response), diffed byte-for-byte against a recorded PHP fixture — before any further kernel abstraction work. This directly avoids the one documented failure mode this project has already suffered once: the Scala predecessor stalled building "Illuminate-shaped infrastructure bottom-up with no real app pulling requirements" (why-go-not-scala.md). In parallel, an API-parity fixture-recording harness can start at t=0 with zero Go code, since it only needs a running PHP backend to record against — this should be treated as an independent workstream, not a phase gated behind framework progress.
The key risks are not "will Go's ecosystem support this" (STACK.md and go-ecosystem.md answer that affirmatively for every named concern, OAuth2/OIDC included) but silent parity drift: Go's idioms actively fight several PHP behaviors this app depends on byte-for-byte — nil slices marshal to null where PHP guarantees [], time.Time renders Z where Carbon renders +00:00, a plain bool cannot express PHP's tri-state null/true/false fields, GORM's default many-to-many silently drops the sort_order pivot column driving album-artist display order, and a "clean" unified response-envelope or error-middleware instinct will break the RFC 8414/6749 OAuth endpoints the MCP server and ChatGPT connector depend on. None of these fail loudly; all are exactly the class of bug a parity harness diffing full response bodies (not just status codes) is built to catch. The roadmap should treat the parity harness as a first-class deliverable from phase 1, not a late QA pass.
Key Findings
Recommended Stack
The stack is almost entirely already-decided (per PROJECT.md); this research pass confirmed versions and resolved five open integration questions. All core picks (Go 1.27, GORM v1.31.2 + gorm.io/driver/postgres v1.6.3, River v0.47.0, zitadel/oidc v3.51.0, golang-jwt/jwt v5.3.1, go-playground/validator v10.30.4, cobra v1.10.2, gocloud.dev v0.46.0, koanf/v2, go-i18n/v2) are current and actively maintained as of 2026-09-16 (verified via pkg.go.dev, GitHub, and official docs directly, not secondhand).
Core technologies and resolved questions:
- GORM + River share one
*sql.DB(viapgx/v5/stdlib), not two independent pools — River's own docs (riverqueue.com/docs/gorm) document this pattern directly; a small separatepgxpool.Poolis used only for River's LISTEN/NOTIFY wake-ups. - Migrations: gormigrate, not goose. This is a load-bearing correction to earlier documents (ARCHITECTURE.md and PITFALLS.md both still reference goose per the older
go-ecosystem.mdpick). STACK.md's deeper dive concludes gormigrate's plain[]*gormigrate.Migrationstructs (with first-classRollbackLast()/RollbackTo()) compose more naturally with per-plugin migration sets aggregated at boot than goose's filesystem-and-provider model, and matches the "drop the last migration and fix it" workflow PROJECT.md names verbatim. This is a genuine open decision to close before the data-layer phase — see Gaps below. - Config: koanf + plain YAML, not literal HOCON. No maintained Go HOCON parser exists at this project's dependency-quality bar. PROJECT.md's "HOCON-style layered config" should be read as a layering design (base + env overlay + per-plugin namespace), which koanf achieves natively with YAML — not a mandate for HOCON syntax.
- YAML parsing: goccy/go-yaml, not
gopkg.in/yaml.v3.gopkg.in/yaml.v3's upstream was archived by its maintainer April 2025 and is explicitly marked unmaintained (confirmed by reading the repo directly) — this changes a style preference into a must, for bothfields.yaml/columns.yamlparsing and (transitively, via koanf) config. - OpenAPI: swaggo/swag (comment-driven, code-first), not Huma. Huma's typed-handler convention would restructure all 154 ported routes right when byte-compatible parity with an existing contract is the entire point; swag annotates ordinary
net/httphandlers with no signature changes. - GORM plain structs, not gorm gen/go-gorm/cli codegen, for v1 — matches the explicit "simplest line-by-line Eloquent-to-GORM port" design goal.
Expected Features
Płytarium's actual source (not a generic CMS feature list) defines table stakes. FEATURES.md read Plugin.php, routes.php (569 lines / ~160 routes), all 5 admin controllers' YAML configs, all 25 models, 3 jobs, and 3 console commands directly.
Must have (table stakes, all P1):
- Plugin descriptor with register/boot lifecycle, dependency declaration, and cross-plugin extension via events (not inheritance) — the core mechanism plugins use to extend each other (
getApiArraypayload extension, model-created notification listeners, auth guard registration) - Full GORM relation/cast/hook fidelity: pivot models with business columns (not bare pivot arrays), polymorphic file attachments,
$jsonable/$fillable/$hidden/encrypted-cast discipline, cascading soft-delete inside transactions, custom attribute casts (e.g. money as fixed-decimal string) - Three parallel, mutually-exclusive auth groups sharing controllers: JWT (SPA), personal scoped token (
inv.scope:read|write|ai, used by MCP), and public/onboarding — the single hardest routing requirement, since the same controller must serve different route subsets per group, plus 7+ named per-bucket rate limiters - OAuth2.1 authorization server (auth code + PKCE, refresh tokens, RFC 7591 DCR, RFC 8414 discovery) for MCP/ChatGPT — already the named zitadel/oidc pick
fields.yaml/columns.yaml→ JSON schema pipeline including a relation-manager schema type (link/unlink UI for a belongsToMany with pivot) — used once in Płytarium (Collections' editors tab) but structurally necessary- River-backed jobs with a debounce/coalescing pattern (wishlist digest) and a self-redispatching rate-limit-aware pattern (Discogs match job) — not just "a queue exists"
- Centrifugo publisher + pluggable per-channel-namespace authorizer registry (kept as existing infra, only publisher/token-issuing ported)
- Typesense sync that is tenant-scoped and always treated as a pre-filter re-gated in SQL, never a source of truth
- Multi-step, resumable, queued CSV import pipeline (not a single upload action)
- 3-tier BYOK-or-org-or-site-admin credential resolver pattern (used identically for Discogs and AI credentials)
Should have / differentiators (not v1 blockers): compile-time plugin registration removing WinterCMS's class_exists/elevated boot-order fragility; a single typed "fire and collect vs. fire and forget" event bus generalizing two hand-rolled PHP patterns into one kernel primitive; generalizing the 3-tier credential resolver into one reusable piece instead of two copies; a first-class relation-manager schema type replacing the partial field-type escape hatch.
Explicitly deferred (confirmed unused by Płytarium, do not build for v1): server-rendered theme engine, backend AJAX/partial-refresh framework, Sluggable/Sortable/NestedTree/Revisionable model behaviors (none used anywhere in the plugin — slugs are hand-rolled in lifecycle hooks), MySQL support, payments/chat/forum/video, policy-based row/field-level admin permissions (Płytarium's admin RBAC is coarse role gates only).
Architecture Approach
Two-repo split: summercms.go (framework core, one Go module: pact, towel, compass, festival, backpack, party, bonfire, surf, lagoon, bouncer, lifeguard, cooler, conga, postcard, sandcastle, phrasebook, sunset) and fonoteka.go (the application, a go.work workspace of per-plugin Go modules requiring the framework). The framework has zero knowledge of Płytarium; plugins import the framework, never the reverse. Stack plugins (user, translate, websockets) live inside the app repo's workspace for v1 and are only extracted to their own repos when a second app needs them.
Major components:
party(plugin registry) — Caddy/xcaddy-style: each plugin self-registers viainit(),summer buildregenerates a blank-import list; one requiredPlugininterface (ID/Requires/Register/Boot) plus small optional capability interfaces (HasModels,HasRoutes,HasJobs, etc.) replace WinterCMS's optional-override inheritance.- Three concrete plugin-extends-plugin mechanisms, not one generic hook system: (a) typed "filter" events on
festivalfor response/serialization extension, (b) GORM's own callback registry (db.Callback()) for model lifecycle extension, (c) migration + companion embedding struct for schema extension (one plugin adding columns to another's table). Conflating these into one abstraction is explicitly called out as the wrong move. lagoon(GORM/Postgres) +conga(River) sharing one*sql.DB, with a separate pgx listener pool for LISTEN/NOTIFY — the concrete first-party-documented integration pattern from STACK.md.surf(routing) implementing the three-auth-group/per-bucket-rate-limit requirement as first-class routing structure, with OAuth/RFC routes in a structurally separate group carrying no shared envelope/error middleware.- First vertical slice:
GET /_fonoteka/api/v1/genresend-to-end (config → container → migration → plugin registry → JWT-guarded route → GORM → JSON diffed against a PHP fixture) is identified as the one phase that cannot be skipped or parallelized — every other phase's pattern is a repetition of this slice's pieces. - Suggested build order is a 9-layer DAG (pact/towel → compass/phrasebook/bonfire → festival → backpack → party → lagoon/surf → bouncer → [vertical-slice checkpoint] → lifeguard/cooler/conga/postcard/sandcastle → admin schema + parity harness + bulk model/route porting, parallelizable → sunset last, thin).
Critical Pitfalls
Fifteen critical pitfalls were identified, each cited to specific PHP source lines. The five most consequential for roadmap sequencing:
- Bottom-up kernel over-building (recurrence risk) — this project already failed once this way in Scala. Avoid by interleaving kernel work with the first real vertical slice; never let plugin descriptor/event-bus/config shape get a "finished" design pass before a real model/route exists end to end.
- Package-level global state instead of
context.Contextfor per-request org/tenant/collection resolution — PHP-FPM's process-per-request model silently protected against this; Go's shared-process concurrency does not. Must be settled as a convention before any plugin consumes it. - Reusing GORM model structs as JSON request-binding structs — loses the
$fillable/$guardedmass-assignment boundary that is a named security invariant in the PHP source (e.g.,market_price_source,public_tokendeliberately excluded). Requires per-endpoint DTOs from the first ported model onward, not retrofitted later. - A "clean" generic response envelope or blanket error/auth middleware — the PHP house style deliberately has at least three incompatible envelope families (house REST with no
linkskey, Laravel-shaped validation-error maps, and unenveloped RFC 8414/6749 OAuth responses with specific cache headers). A singleAPIResponse[T]generic wrapper is flagged explicitly as a smell to catch in phase planning, not a shortcut to allow. - Type-level parity gaps that don't fail loudly: nil slice →
nullvs PHP's guaranteed[];time.Time→Zvs Carbon's+00:00; plainboolunable to express tri-state null/true/false;float64reintroducing float-precision bugs PHP deliberately avoided for money fields via a custom cast. All of these require the parity harness to diff full response bodies field-by-field, not just check status codes or rough shape.
Implications for Roadmap
Phase 1: Kernel foundation + first vertical slice (interleaved, not sequential)
Rationale: Directly avoids Pitfall 1 (the documented Scala failure mode). The kernel must be built exactly as far as the first vertical slice needs it, no further, before any additional kernel design work.
Delivers: compass (config), backpack (container), festival (event bus), party (plugin registry + summer build), bonfire (CLI skeleton) — each only fleshed out to the point GET /_fonoteka/api/v1/genres requires. Also establishes the context.Context-based org/tenant-resolution convention (Pitfall 2) before any plugin uses it.
Addresses: Kernel/plugins table-stakes features (plugin descriptor, dependency declaration, event bus, console command registration).
Avoids: Pitfall 1 (bottom-up over-build), Pitfall 2 (global state vs. context).
Phase 2: First vertical slice checkpoint — one real endpoint end to end
Rationale: ARCHITECTURE.md identifies this as the one phase that cannot be skipped or parallelized: every later phase depends on all its pieces (config, container, migration, plugin registry, routing, JWT auth, GORM, JSON response) existing together.
Delivers: Genre (or Style) model + migration (via gormigrate — see Gaps), one JWT-guarded route through surf, a working party plugin registration, and the first parity-harness fixture (recorded from the live PHP backend, diffed byte-for-byte).
Uses: GORM + gormigrate, golang-jwt/jwt, stdlib ServeMux.
Implements: The full request-flow pattern in ARCHITECTURE.md that every subsequent route repeats.
Phase 3: API parity harness (parallel workstream, starts at t=0)
Rationale: Near-zero dependency on framework internals — it only needs a running PHP backend to record fixtures against. All four research passes agree this should not be gated behind kernel/framework progress.
Delivers: Fixture-recording tooling against the live PHP backend, an httptest.Server-based replay-and-diff harness with a normalizer for non-deterministic fields, and — critically — assertions covering the specific parity classes named in Pitfalls 4/6/7/8 (nil-vs-empty-array, date format/null-vs-omitted, tri-state bool, envelope/conditional-key shape), not just status-code/rough-shape checks.
Avoids: Pitfalls 4, 5, 6, 7, 8 (all of which "look done but aren't" without a byte-level diff).
Phase 4: Data layer — full model/relation/cast fidelity
Rationale: Every endpoint touches this; the security-load-bearing $fillable/$hidden/encrypted-cast discipline and the DTO-vs-model convention (Pitfall 3) must be established here, not retrofitted after models exist.
Delivers: Per-plugin gormigrate migration sets (one per PHP migration, up/down tested), all 25 models with correct relation types (dedicated pivot models with business columns, not bare arrays — Pitfall 12), polymorphic file attachments, soft-delete + partial unique index audits (Pitfall 11), money-as-fixed-decimal-string and other custom casts (Pitfall 5).
Addresses: Data/models table-stakes row in FEATURES.md in full.
Avoids: Pitfalls 3, 5, 11, 12, 13 (GORM AutoMigrate misuse).
Phase 5: HTTP/auth — three-group routing, rate limiting, personal tokens
Rationale: The entire 154-route API surface sits behind this; retrofitting the routing structure after routes exist is called out explicitly as expensive.
Delivers: JWT + personal-scoped-token (inv.scope) + public/onboarding route groups sharing controllers, named per-bucket rate limiters ported 1:1 (not redesigned), the must-change-password gate with its one deliberate exception route, and — critically — a structural exemption of any future OAuth/RFC route group from generic envelope/error middleware, decided now rather than after 26 models' worth of routes exist.
Uses: surf, bouncer, cooler (rate-limit buckets).
Avoids: Pitfall 9 (blanket middleware corrupting OAuth), "rate-limit bucket parity" moderate pitfall.
Phase 6: OAuth2.1 server (zitadel/oidc)
Rationale: Depends on the routing/middleware abstraction from Phase 5 existing first (same "not-the-SPA's-JWT" pattern), but is a distinct credential type (OAuthClient/OAuthAuthCode/OAuthRefreshToken vs. ApiToken) — build the shared abstraction once, then implement both guards against it.
Delivers: AuthStorage/OPStorage interface implementations mapped onto the existing OAuth models, RFC 8414/7591 endpoints, and exact WWW-Authenticate/Protected-Resource-Metadata header contract (Pitfall 10) verified against the actual fonoteka-mcp client, not just unit tests.
Avoids: Pitfalls 9, 10; the "trusting zitadel/oidc defaults without diffing" technical-debt pattern.
Research flag: needs verification of whether ClientCredentialsStorage/TokenExchangeStorage are actually needed (check wavepath.org/plugins/golem15/oauthserver before assuming not).
Phase 7: Admin schema pipeline (fields.yaml/columns.yaml → JSON) + minimal Vue 3 SPA
Rationale: Requires the data-layer serialization boundary (Phase 4) to already exist; cannot be phased independently of it per FEATURES.md's dependency notes.
Delivers: Form/list/relation-manager JSON schema for the 5 admin controllers, OpenAPI generation via swaggo/swag, generated TS types, and a decision on the partial field-type escape hatch (likely: replace with the relation-manager schema directly, per the Collections' editors-tab case).
Research flag: relation-manager schema design and the partial-type replacement are the least-precedented parts of this research pass (used once in the PHP source) — worth a --research-phase pass.
Phase 8: Background jobs, realtime, search, integrations
Rationale: Depends on Phase 4 (models/events) and benefits from Phase 1's event bus; independently parallelizable across CSV import, Discogs/AI clients, Centrifugo, and Typesense once the data layer is stable.
Delivers: River with the correct dual-driver split (riverdatabasesql for transactional enqueue, riverpgxv5 for LISTEN/NOTIFY work processing — Pitfall 14), the CSV import multi-step pipeline, wishlist digest debounce, Centrifugo suppress-then-emit-once broadcast pattern, Typesense sync with the SQL re-gate (never trusting search results as pre-authorized — Pitfall 15), Discogs/AI clients with the 3-tier BYOK credential resolver.
Avoids: Pitfalls 14, 15, and the moderate "Centrifugo broadcast-suppression" and "rate-limit bucket parity" pitfalls.
Phase 9: Bulk remaining route/model port + full parity cutover
Rationale: By this point the pattern is proven and repeatable; this phase is mostly volume, parallelizable across plugins/models gated only by FK order (the same DAG the 27 PHP migrations already encode).
Delivers: All remaining routes/models, full parity-harness green against all 154 routes, vue-fonoteka-app and fonoteka-mcp running unchanged against the Go backend — the literal definition of done in PROJECT.md.
Phase Ordering Rationale
- Kernel and first-slice phases are interleaved specifically to prevent the Scala-era failure mode from recurring — this is the single strongest signal across all four research documents.
- The parity harness is pulled out as an explicit parallel-from-t=0 workstream because three of the four research documents independently flag it as near-zero-dependency and because its assertions are the actual mechanism that catches most of the "critical pitfalls" — sequencing it late would defeat its purpose.
- Data layer precedes HTTP/auth precedes OAuth because each has a documented one-way dependency (auth needs models to authenticate against; OAuth reuses the auth-group routing pattern) — reversing this order was explicitly flagged as more expensive to retrofit.
- Admin schema pipeline is placed after data layer specifically because FEATURES.md's dependency notes say these "cannot be phased independently" without under-building the
$fillable/$hiddensecurity contract. - Background/integrations is placed late because it is the most parallelizable-once-unblocked phase and has the fewest cross-dependencies on routing/OAuth decisions.
Research Flags
Needs deeper research during planning:
- OAuth2.1 phase —
zitadel/oidcstorage-interface implementation against the real PHP OAuth models, and the exactClientCredentialsStorage/TokenExchangeStoragescope question (checkwavepath.org/plugins/golem15/oauthserverfirst). - Admin schema pipeline phase — the relation-manager schema type and the
partialfield-type replacement are the least-precedented design surface in this research (one real usage in the PHP source, no direct library equivalent). - Background/jobs phase — River's dual-driver (
riverdatabasesqlvsriverpgxv5) split is documented but not yet implemented in this codebase; verify the transactional-enqueue-plus-LISTEN/NOTIFY pattern against a real test before trusting the docs-only description.
Phases with standard, well-documented patterns (safe to skip --research-phase):
- Kernel/plugin-registry phase — Caddy/xcaddy's compiled-plugin model is directly precedented and already verified against real xcaddy-generated code.
- Data layer phase — GORM patterns (relations, casts, callbacks) are well-documented; the risk here is PHP-source-fidelity auditing (a planning/testing discipline), not unknown Go patterns.
Confidence Assessment
| Area | Confidence | Notes |
|---|---|---|
| Stack | HIGH | Versions and maintenance status verified directly via pkg.go.dev/GitHub/official docs this session; MEDIUM on judgment calls (migration tool, OpenAPI approach, config format) but those calls are well-reasoned and explicit about trade-offs |
| Features | HIGH for table stakes (grounded in direct reading of the full fonoteka PHP source — Plugin.php, routes.php, all models/controllers/YAML configs); MEDIUM for differentiators and anti-features (informed by ecosystem comparison, not exhaustively re-audited) |
|
| Architecture | HIGH for plugin-registration mechanics (verified against real Caddy/xcaddy docs) and request-lifecycle mapping (grounded in the real Plugin.php/routes.php); MEDIUM for the cross-plugin schema-extension pattern and the two-repo split (design recommendation, not yet validated by an actual build) |
|
| Pitfalls | HIGH for anything cited to actual PHP source (routes.php, controllers/api/*.php, models/Album.php, SerializesFonoteka.php, fonoteka-mcp/src/http.ts) or official GORM/River docs; MEDIUM for Go-ecosystem-general claims and the "Laravel-shaped framework in Go" class of pitfall |
Overall confidence: HIGH — this research is unusually well-grounded because three of the four passes read the actual target PHP application's source directly rather than reasoning from a generic domain description.
Gaps to Address
- gormigrate vs. goose is not yet settled across documents. STACK.md's deep-dive recommends gormigrate (better fit for per-plugin migration composition and the "drop the last migration and fix it" workflow); ARCHITECTURE.md and PITFALLS.md still reference goose per the earlier
go-ecosystem.mdpick and have not been updated. Resolve this explicitly before the data-layer phase — do not let two documents silently disagree into implementation. STACK.md's reasoning is more recent and more specific to this project's shape; the roadmap should default to gormigrate unless a phase decision overrides it. - PROJECT.md's field-type list needs correction. PROJECT.md's Active requirements list
number,fileupload,repeater,datepickeras WinterCMS field types Płytarium uses; FEATURES.md's direct read of all 5 controllers'fields.yamlfiles found the actual set istext,textarea,checkbox,switch,dropdown(model-method-fed),relation, andpartial(an escape hatch, not a clean schema type) — nonumber/fileupload/repeater/datepickeranywhere in the plugin. Update PROJECT.md's requirement line to match, and treatpartialas the one field type needing a bespoke design decision (see Phase 7's research flag). - PROJECT.md's "HOCON-style layered config" wording should be clarified as a design, not a syntax mandate — no viable Go HOCON parser exists; koanf + YAML achieves the same layering semantics. Worth a one-line PROJECT.md update so a future reader doesn't chase a literal HOCON parser.
- No Sluggable/Sortable/NestedTree/Revisionable behaviors are used anywhere in Płytarium — confirmed by direct source read. These should not appear as implicit v1 scope anywhere; PROJECT.md does not currently list them, but flag this explicitly so no phase accidentally builds a packaged Sluggable behavior when the PHP source only ever hand-rolls slugs in lifecycle hooks.
gopkg.in/yaml.v3is unmaintained (archived April 2025) — any lingering assumption (in code, docs, or a future dependency choice) that yaml.v3 is the default YAML library should be corrected togoccy/go-yamlbefore it's load-bearing anywhere.- typesense-go (v3.2.0, Mar 2025) and centrifugal/gocent/v3's exact latest tag are both flagged MEDIUM confidence in STACK.md due to research-session gaps since their last verified release — re-check both at the implementation time of the search/realtime phase rather than trusting the pinned versions blindly.
- zitadel/oidc's
ClientCredentialsStorage/TokenExchangeStoragenecessity is an open question STACK.md itself flags — resolve by readingwavepath.org/plugins/golem15/oauthserverbefore the OAuth phase's interface-implementation work begins.
Sources
Primary (HIGH confidence)
/media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/— Plugin.php, routes.php (569 lines/~160 routes), all 5 admin controllers + config_form/config_list/config_relation.yaml, all 25 models, 3 jobs, 3 console commands, config/fonoteka.php (read directly for FEATURES.md, ARCHITECTURE.md, PITFALLS.md)/media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/traits/SerializesFonoteka.php,controllers/api/AlbumApiController.php,controllers/api/OAuthTokenController.php,controllers/api/OAuthMetadataController.php,models/Album.php— serialization/envelope/security contract detail (PITFALLS.md)/media/nvme/dev/golem15/fonoteka/fonoteka-mcp/src/http.ts,vue-fonoteka-app/app/composables/useCentrifugo.ts— client-side contract confirmation- pkg.go.dev version pages, GitHub repos/releases for every core and supporting library (GORM, River, zitadel/oidc, golang-jwt, goccy/go-yaml, gormigrate, swaggo/swag, etc.) — fetched/read directly 2026-09-16
riverqueue.com/docs/gorm— official River+GORM pool-sharing integration guide- Caddy/xcaddy official docs and generated
main.gomechanics; PocketBase hook-system/schema-driven-admin docs - This repo's
.planning/PROJECT.md,.planning/notes/why-go-not-scala.md,.planning/research/go-ecosystem.md,CLAUDE.md
Secondary (MEDIUM confidence)
/media/nvme/dev/golem15/fonoteka/plugins/golem15/user/,plugins/golem15/websockets/— skimmed, not fully read- WebSearch: Directus/Strapi/PocketBase/Goravel 2026 comparisons (cross-checked against training knowledge, MEDIUM confidence)
- koanf's YAML parser migrating to goccy/go-yaml — WebSearch-sourced, corroborated by two independent results and go-i18n's own release notes
Tertiary (LOW confidence, needs validation)
- Exact latest tags for typesense-go and centrifugal/gocent/v3 (both flagged as needing a fresh check at implementation time)
- otter-vs-ristretto cache performance claim carried from go-ecosystem.md (maintainer benchmarks, not independently verified)
Research completed: 2026-09-16 Ready for roadmap: yes