10 KiB
phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, key-decisions, patterns-established, requirements-completed, duration, completed
| phase | plan | subsystem | tags | requires | provides | affects | tech-stack | key-files | key-decisions | patterns-established | requirements-completed | duration | completed | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 05-data-layer-full-fidelity | 03 | database |
|
|
|
|
|
|
|
|
|
15min | 2026-09-18 |
Phase 5 Plan 03: Secrets slice Summary
AES-256-GCM lagoon.Encrypted with stdlib HKDF and app.previous_keys rotation, summer key:generate, a standalone Laravel CBC decrypt helper, user-plugin golem15_user_organisations, and Fonoteka api_tokens/OAuth/credential tables
Performance
- Duration: 15 min
- Started: 2026-09-18T17:26:51Z
- Completed: 2026-09-18T17:41:45Z
- Tasks: 3
- Files modified: 28
Accomplishments
lagoon.Encryptedround-trips AES-256-GCM ciphertext keyed by HKDF fromapp.key, redacts on JSON/String/%#v, and decrypts rows after primary rotation viaapp.previous_keys- Missing, short, or undecodable
app.keyfails loudly mentioningSUMMER_APP__KEY;key:generateprints a fresh 32-byte base64 key usingcrypto/rand DecryptLaravelPayloaddecrypts a real Laravel AES-256-CBC+HMAC fixture and is not on the live Scan/Value pathgolem15_user_organisations(slug/name/description, structure only) is created by the user plugin; no users-table ALTER ships this phase- Three Fonoteka migrations create api_tokens, 3 OAuth tables, and 4 credential tables; hash/secret columns are
json:"-"/Hidden(), andapi_key/tokenarelagoon.Encrypted
Task Commits
Each task was committed atomically:
- Task 1 RED: Encrypted tests -
06bad37(test) in summercms.go - Task 1 GREEN: Encrypted implementation -
8d9109a(feat) in summercms.go - Task 2: create_organisations in user plugin -
c291280(feat) in fonoteka.go - Task 3: secrets-slice migrations and models -
f3bf0dd(feat) in fonoteka.go
Plan metadata: pending (this file)
Files Created/Modified
lagoon/encrypted.go,lagoon/encrypted_test.go— AES-256-GCM cast, HKDF, previous_keys, redactionlagoon/keygen.go,lagoon/keygen_test.go—key:generatelagoon/laravel_decrypt.go,lagoon/laravel_decrypt_test.go— cutover-only Laravel payload helperlagoon/commands.go— RuntimeCommands appends KeyGenerateCommandlagoon/connection.go— OpenFromApp publishes encryption keys fromapp.keyfonoteka.go/config/app.yaml— documented emptyapp.keyplaceholderfonoteka.go/plugins/golem15/user/updates/10_organisations.go,models/organisation.go— FK target tablefonoteka.go/plugins/golem15/fonoteka/updates/11_secrets_slice.go— api_tokens / OAuth / credentialsfonoteka.go/plugins/golem15/fonoteka/models/{api_token,oauth_*,*_credential}.go— 8 modelsfonoteka.go/parity/migrate_test.go— 11 Fonoteka IDs, 2 user IDs, secrets rollback coverage
Decisions Made
- Column keys use Go 1.27
crypto/hkdf(infosummercms.lagoon.encrypted.v1); no new dependency - Ciphertext format is version nibble
0x1plus key-id, 12-byte nonce, GCM ciphertext+tag, base64 fortextcolumns OpenFromAppis the boot wiring site forLoadAppKey+PublishEncryptionKeys; Scan/Value use the package-level key set- Organisations belong to
golem15.user; Phase 5 does not ALTERusers(confirmed D-03 deviation, Phase 7 AUTH-02) DecryptLaravelPayloadstays unwired from the live cast (D-10)
Deviations from Plan
Auto-fixed Issues
1. [Rule 2 - Missing Critical] Write-path constructor for Encrypted
- Found during: Task 1 (GREEN)
- Issue: Scan/Value/Reveal cannot set plaintext from another package
- Fix: Exported
NewEncrypted(plaintext string) - Files modified:
lagoon/encrypted.go - Verification:
TestEncryptedRoundTrip - Committed in:
8d9109a
2. [Rule 3 - Blocking] OpenFromApp now requires a valid app.key
- Found during: Task 1 (wiring PublishEncryptionKeys into OpenFromApp)
- Issue:
TestOpenFromAppReadsDSNhad noSUMMER_APP__KEY - Fix: Test supplies a 32-byte base64 key via environ
- Files modified:
lagoon/connection_test.go - Verification:
go test ./lagoon/... - Committed in:
8d9109a
3. [Rule 3 - Blocking] RuntimeCommands name list omitted key:generate
- Found during: Task 1
- Issue:
TestRuntimeCommandsRegisterBareAndColonNamesexpected the old trio - Fix: Assert
key:generateis registered - Files modified:
lagoon/migrations_test.go - Verification: that test
- Committed in:
8d9109a
4. [Rule 2 - Missing Critical] Organisations columns from PHP
- Found during: Task 2 (reading
create_organisations_table.php) - Issue: Plan listed id/name/timestamps as minimum; PHP also has unique
slugand nullabledescription - Fix: Table and model include slug and description; no membership/role tables
- Files modified:
plugins/golem15/user/updates/10_organisations.go,models/organisation.go - Verification:
TestCreateOrganisationsMigration - Committed in:
c291280
5. [Rule 3 - Blocking] Existing migrate tests hardcoded ID counts
- Found during: Task 3
- Issue: Parity tests expected 1 user ID and 8 Fonoteka IDs; last rollback targeted album_reservations
- Fix: Expect 2 user IDs and 11 Fonoteka IDs; RollbackLast now drops credential tables first
- Files modified:
fonoteka.go/parity/migrate_test.go - Verification:
TestMigrateSeedsCanonicalGenres,TestSecretsSliceMigrationsUpDown,TestRollbackLastIsolatesFonoteka - Committed in:
f3bf0dd
6. [Rule 3 - Blocking] Acceptance grep for widen_users vs required doc comment
- Found during: Task 2
- Issue: Plan required a comment naming the omitted users ALTER, which would fail
grep -rln widen_users - Fix: Comment states the decision without the
widen_userstoken - Files modified:
plugins/golem15/user/updates/10_organisations.go - Verification: grep returns no matches
- Committed in:
c291280
Total deviations: 6 auto-fixed (2 missing critical, 4 blocking)
Impact on plan: Required for a compiling write path, boot-time key loading, PHP column fidelity, and green existing migrate tests. No scope creep. LoadAppKey returns [][]byte previous keys rather than a concatenated blob so Scan can try each key.
Issues Encountered
None beyond the auto-fixes above.
Authentication Gates
None.
TDD Gate Compliance
- RED:
06bad37test(05-03): add failing tests for Encrypted, key:generate, Laravel decrypt - GREEN:
8d9109afeat(05-03): implement lagoon.Encrypted, key:generate, Laravel decrypt helper - REFACTOR: not needed
Known Stubs
fonoteka.go/config/app.yamlapp.key: ""is an intentional documented placeholder; empty fails boot once an encrypted-cast model runs /OpenFromApploads keys. Operators setSUMMER_APP__KEYfromsummer key:generate.
User Setup Required
None - no external service configuration required. Operators must set SUMMER_APP__KEY before serving or migrating through OpenFromApp.
Next Phase Readiness
Ready for 05-04 (attachments). Encrypted credentials, OAuth tables, and the organisations FK target exist. zitadel/oidc storage remains Phase 8; organisation membership/roles remain Phase 7.
Self-Check: PASSED
- Key files exist on disk (
lagoon/encrypted.go,keygen.go,laravel_decrypt.go, user10_organisations.go, fonoteka11_secrets_slice.go, 8 models) - Commits
06bad37,8d9109a(summercms.go) andc291280,f3bf0dd(fonoteka.go) exist go vet ./lagoon/...andgo test ./lagoon/...green in summercms.gogo vet ./...andgo test ./...green in fonoteka.goDecryptLaravelPayload(is called only from its file and testlagoon/encrypted.godoes not importgolang.org/x/crypto;keygen.godoes not importmath/rand
Phase: 05-data-layer-full-fidelity Completed: 2026-09-18