Files
summercms/.planning/phases/14.1-oauth-identities-and-fonoteka-me-routes/14.1-01-SUMMARY.md
2026-10-05 21:25:29 +02:00

11 KiB

phase, plan, subsystem, tags, requires, provides, affects, actuals, plan_head_before, plugin_repo_head_after, app_repo_head_after, tech-stack, key-files, key-decisions, patterns-established, requirements-completed, requirements-blocked-until-02, coverage, duration, completed, status
phase plan subsystem tags requires provides affects actuals plan_head_before plugin_repo_head_after app_repo_head_after tech-stack key-files key-decisions patterns-established requirements-completed requirements-blocked-until-02 coverage duration completed status
14.1-oauth-identities-and-fonoteka-me-routes 01 api
oauth-identities
sm-user-plugin
me-token
parity
gormigrate
lagoon.Encrypted
wire.Time
throttle
phase provides
07-user-plugin-and-authentication sm-user-plugin sessionApp/insertUser, jwt.auth, lang YAML, controllers.writeJSON
phase provides
08-oauth2-1-authorization-server JWT group in fonoteka routes.go, MeToken personal-token bootstrap, TokenSurfaceIsolation coverage
phase provides
14-domain-jobs-and-external-integrations parity corpus at 175 PHP routes with 3 pending D-09 identity/me entries, fonoteka seed extras, php_parity.sh recording
OAuthIdentity model and gormigrate 202610050001_create_oauth_identities in sm-user-plugin (table golem15_user_oauth_identities, full PHP column set)
Exported OAuthIdentitiesIndex, OAuthIdentitiesDestroy, OAuthIdentitiesOptions; host JWT mounts GET/DELETE /oauth-identities with Winter HTML 404 and throttle:10,1 on DELETE
D-09 MeToken collection_ids JSON null when CollectionIDs is invalid or empty; scopes still wire.Slice
Parity extras oauth-identities-linked and me-unrestricted; three manifest routes ported; TestParityCorpus recorded 175/175 passing 175 failing 0 unrecorded 0 pending 0
14.1-02 unit tests
15-cutover (zero pending routes)
tokens tasks commits
5100 4 5
c3bbbcbb9f ac8aa16217556f70f1e1b2c0a3495c7eea6b5747 1584d7390d80b8f1d3e8de53c2f0144deb9ebd64
added patterns
Host-mounted constructors: sm-user-plugin exports Index/Destroy; the host chooses the path, throttle and WriteNotFound (Winter HTML 404), never a /_user route
Parity extras keyed as "<route id>#<case id>" on both PHP tinker reset and Go seedFonotekaCase
assertPortedMismatch wraps a ported fixture with mutateStatus and requires tide.MismatchError
created modified
../fonoteka.go/plugins/golem15/user/models/oauth_identity.go
../fonoteka.go/plugins/golem15/user/updates/202610050001_create_oauth_identities.go
../fonoteka.go/plugins/golem15/user/controllers/oauth_identities.go
../fonoteka.go/plugins/golem15/user/oauth_identities_test.go
../fonoteka.go/parity/fixtures/routes/GET___fonoteka_api_v1_oauth-identities_jwt__linked.yaml
../fonoteka.go/parity/fixtures/routes/GET__api_v1_fonoteka_me_personal_token__unrestricted.yaml
../fonoteka.go/plugins/golem15/user/lang/en/lang.yaml
../fonoteka.go/plugins/golem15/user/lang/pl/lang.yaml
../fonoteka.go/plugins/golem15/user/README.md
../fonoteka.go/plugins/golem15/fonoteka/routes.go
../fonoteka.go/plugins/golem15/fonoteka/phase08_coverage_test.go
../fonoteka.go/plugins/golem15/fonoteka/controllers/api/me_token_controller.go
../fonoteka.go/parity/manifest.yaml
../fonoteka.go/parity/parity_test.go
../fonoteka.go/parity/parity_contract_test.go
../fonoteka.go/parity/fonoteka_seed_test.go
../fonoteka.go/parity/fonoteka_reset.php
../fonoteka.go/parity/genres_seed_test.go
../fonoteka.go/parity/fixtures/routes/GET__api_v1_fonoteka_me_personal_token.yaml
Task 1 answered ship-shared-full before dispatch: table golem15_user_oauth_identities lives in sm-user-plugin with the full PHP column set (locked D-01 and D-07)
routes.go in sm-user-plugin stays byte-identical; identity handlers are host-mounted on the fonoteka JWT group
Last-method 409 counts identities only (D-06); HasSelfSetPassword is unused
Restricted /me was re-recorded from the fonoteka reset so {{id:collection}} resolves; the Phase 2 {{id:token}} placeholder would not
OAuth identity list is an explicit {provider, linked_at} map; Encrypted tokens and profile_data are Hidden and json:"-"
DELETE {provider} is unconstrained on the mux so unknown providers reach WriteWinterHTTPError
MeToken: scopes use wire.Slice; collection_ids is a bare any that stays JSON null when empty
HTTP-04
HTTP-06
API-09
HTTP-01
HTTP-03
DATA-02
DATA-07
I18N-01
id description requirement verification human_judgment
D1 golem15_user_oauth_identities exists in sm-user-plugin via gormigrate with Encrypted tokens, Hidden secrets, and empty Fillable DATA-02
kind ref status
unit plugins/golem15/user#TestOAuthIdentitiesIndexEmptyList pass
false
id description requirement verification human_judgment
D2 JWT GET /_fonoteka/api/v1/oauth-identities returns {"data":[]} and, with extra oauth-identities-linked, facebook then google linked_at +00:00 without secrets HTTP-06
kind ref status
e2e parity#TestParityCorpus coverage line recorded 175/175 passing 175 pass
kind ref status
unit plugins/golem15/user#TestOAuthIdentitiesIndexEmptyList pass
false
id description requirement verification human_judgment
D3 JWT DELETE /oauth-identities/{provider} is throttle:10,1, unconstrained, 204 keeps the sibling row HTTP-01
kind ref status
unit plugins/golem15/user#TestOAuthIdentitiesDestroyNoContentKeepsSibling pass
kind ref status
unit plugins/golem15/fonoteka#TestOAuthTokenSurfaceIsolationCoverage/test_oauth_identity_routes_exist_on_jwt_surface_only pass
false
id description requirement verification human_judgment
D4 Unrestricted /me emits collection_ids JSON null; three manifest routes are ported at 175/175/0 API-09
kind ref status
e2e parity#TestParityCorpus recorded 175/175 passing 175 failing 0 unrecorded 0 pending 0 pass
false
id description verification human_judgment
D5 sm-user-plugin README names OAuthIdentitiesIndex/Destroy/Options as host-mounted constructors without a consuming-application name
kind ref status
other grep OAuthIdentitiesIndex plugins/golem15/user/README.md pass
false
90min 2026-10-05 complete

Phase 14.1: OAuth identities and fonoteka me routes — Plan 01 Summary

Shared-plugin OAuth identities table and host-mounted GET/DELETE, /me collection_ids JSON null, and the three orphan routes ported at 175/175/0

Performance

  • Duration: 90 min
  • Started: 2026-10-05T19:04:22Z
  • Completed: 2026-10-05T19:24:44Z
  • Tasks: 4
  • Files modified: 20

Accomplishments

  • golem15_user_oauth_identities ships in sm-user-plugin with the full PHP column set, gormigrate ID 202610050001_create_oauth_identities, lagoon.Encrypted tokens, and Hidden profile_data.
  • Host JWT group mounts OAuthIdentitiesIndex and OAuthIdentitiesDestroy (Winter HTML 404, DELETE throttle:10,1, unconstrained {provider}). User plugin routes.go is unchanged.
  • Unrestricted /me emits "collection_ids": null; restricted /me still lists the pinned collection as {{id:collection}}.
  • Extras oauth-identities-linked and me-unrestricted recorded from isolated PHP; corpus recorded 175/175 passing 175 failing 0 unrecorded 0 pending 0.
  • README documents the exported constructors as host-mounted; it never names a consuming application.

Task Commits

Each task was committed atomically:

  1. Task 1: Table ownership checkpoint — no code commit (decision ship-shared-full answered before dispatch)
  2. Task 2: Tracer — table, Index, empty GET — 63ac504 (sm-user-plugin feat), 1bed435 (fonoteka.go feat)
  3. Task 3: Destroy, 409 lang, JWT DELETE throttle — 8e172c5 (sm-user-plugin feat), 696a35e (fonoteka.go feat)
  4. Task 4: /me null, D-10 extras, ported 175, README — ac8aa16 (sm-user-plugin docs), 1584d73 (fonoteka.go feat)

Plan metadata: this SUMMARY commit

Note: TDD was not applicable for this plan.

Files Created/Modified

  • plugins/golem15/user/models/oauth_identity.go — OAuthIdentity, TableName, Hidden secrets, empty Fillable
  • plugins/golem15/user/updates/202610050001_create_oauth_identities.go — gormigrate up/down
  • plugins/golem15/user/controllers/oauth_identities.go — Index/Destroy/Options
  • plugins/golem15/user/oauth_identities_test.go — empty list and 204 sibling smokes
  • plugins/golem15/user/lang/{en,pl}/lang.yaml — oauth.last_method_blocked
  • plugins/golem15/user/README.md — table, constructors, migration, model
  • plugins/golem15/fonoteka/routes.go — JWT GET/DELETE mounts
  • plugins/golem15/fonoteka/phase08_coverage_test.go — jwt-only surfaces + DELETE throttle
  • plugins/golem15/fonoteka/controllers/api/me_token_controller.go — D-09 null collection_ids
  • parity/manifest.yaml, parity_test.go, parity_contract_test.go, seed/reset PHP+Go, recorded fixtures

Decisions Made

  • ship-shared-full (Task 1, pre-dispatch): the table lives in sm-user-plugin with the full PHP column set. Locked D-01 and D-07.
  • Identity HTTP is host-mounted; sm-user-plugin /_user routes stay unchanged.
  • Last-method 409 uses identity count only (D-06).

Deviations from Plan

Auto-fixed Issues

1. [Rule 3 - Blocking] Isolated PHP has no user_api_tokens table

  • Found during: Task 4 (PHP recording)
  • Issue: fonoteka_reset.php wiped user_api_tokens; this Winter checkout still stores tokens in golem15_fonoteka_api_tokens, so tinker aborted before vars were written.
  • Fix: Guard both token tables and golem15_user_oauth_identities with Schema::hasTable and wipe the fonoteka token table the PHP mint actually uses.
  • Files modified: ../fonoteka.go/parity/fonoteka_reset.php
  • Verification: tinker reset ok; identities and unrestricted token rows present
  • Committed in: 1584d73 (Task 4)

2. [Rule 3 - Blocking] Restricted /me fixture used Phase 2 {{id:token}}

  • Found during: Task 4 (fonoteka seed_hook)
  • Issue: The existing /me case was recorded from the genres bootstrap (id:token = rock). Under seed_hook: fonoteka that placeholder does not resolve.
  • Fix: Re-recorded the case from a plain PARITY_CASE= tinker reset. Body is collection_ids:[{{id:collection}}], name parity-mcp.
  • Files modified: ../fonoteka.go/parity/fixtures/routes/GET__api_v1_fonoteka_me_personal_token.yaml
  • Verification: TestParityCorpus recorded 175/175 passing 175 failing 0 unrecorded 0 pending 0
  • Committed in: 1584d73 (Task 4)

Total deviations: 2 auto-fixed (2 blocking) Impact on plan: Both needed so PHP recording and Go replay share one seed. No scope creep.

Issues Encountered

  • First tinker died with no such table: user_api_tokens (deviation 1). Isolated PHP serve on 127.0.0.1:8423 stayed up; recording resumed after the guarded wipe.
  • Tracer verified end-to-end — expanding (Task 2 automated verify; no human-verify checkpoint, HUMAN_VERIFY_MODE=end-of-phase).

User Setup Required

None - no external service configuration required.

Next Phase Readiness

  • Plan 14.1-02 can port OAuthIdentityApiTest.php (EN/PL 409, Winter 404s, 401 on /google, secret-leak, migration up/down) against this code.
  • API-09 stays Pending until 14.1-02 SUMMARY exists (requirements.ready-ids shared-ID gate).
  • Do not push sm-user-plugin or fonoteka.go (plan does not ask). Plugin HEAD ac8aa16 is local-only.

Phase: 14.1-oauth-identities-and-fonoteka-me-routes Completed: 2026-10-05