Sphere targetApp enum cleanup: drop DINING_APP, add SERVICES_APP
ADR: Sphere targetApp enum cleanup — drop DINING_APP, add SERVICES_APP
Section titled “ADR: Sphere targetApp enum cleanup — drop DINING_APP, add SERVICES_APP”1. Context
Section titled “1. Context”The DINING vertical was provisionally seeded during migration 0037_activity_spheres.sql as a triple layer:
- enum value
DINING_APPon theactivities.sphere_target_appPG enum (and DrizzleSphereTargetAppEnum), - enum value
DININGon theactivities.activity_typePG enum (and DrizzleActivityTypeEnum), - a seeded sphere row
code='DINING'inactivities.spheres,target_app='DINING_APP'.
The vertical has been frozen since planning — no customer, no Flutter app surface to render it (there is no dining_app under apps/), and the gym_app/tickets_app routers never branch on it. The triple-layer presence clutters every targetApp/activityType decision and blocks the next two cleanups in the chain:
869dpxbj6— collapse activity types to[SLOT_BASED, SERVICE]+ addhasSeatSelection: boolean; mergeCINEMA+SHOWSsphere rows intoEVENTS.869dq1q27— extractlibs/shared/target-app/as a shared library (helper relocation).
Both downstream tickets assume DINING is already gone. This ADR formalises the triple-drop and seeds the additive SERVICES_APP target-app enum value (reserved for a future dedicated salons/barbershops mobile app — salons currently live in gym_app via the existing SERVICES sphere targeted at GYM_APP, and that targeting is intentionally NOT changed here).
This is the first ticket in a 3-vertical cleanup chain.
2. Decision
Section titled “2. Decision”Drop all three DINING layers and add a single additive SERVICES_APP enum value, in one MR across backend + two Angular consumers, gated by a single auto-generated Drizzle migration.
The Drizzle migration MUST execute in this strict order:
DELETE FROM activities.spheres WHERE code='DINING'(clear the only dependent row before the enum rebuild — otherwise the column re-cast aborts).- PG enum rebuild for
sphere_target_app— rename old → create new('GYM_APP','TICKETS_APP','SERVICES_APP')→ALTER TABLE activities.spheres ALTER COLUMN target_app TYPE … USING target_app::text::activities.sphere_target_app→ drop old. - PG enum rebuild for
activity_type— rename old → create new('SHOW','MOVIE','SLOT_BASED','SERVICE')→ re-castactivities.activities.type,activities.spheres.allowed_activity_types(array column), andactivities.spheres.default_activity_type→ drop old.
The migration is auto-generated via the backend-drizzle-migration skill from the schema edits, NOT hand-written. The generated SQL is reviewed via the migration-safety-check skill before apply. All three OpenAPI contracts are refreshed via the backend-export-openapi skill (= pnpm run sync:contracts) after the backend boots cleanly with the new schema.
Both Angular consumers (tktspace-web, tktspace-business) carry one hand-written line each that references 'DINING' in code typed against the generated union (Record<string, string> icon map in web; ActivityResponseDto['type'][] picker list in business). Both are folded into this MR so neither consumer’s dev branch breaks ng build after the contract refresh.
Mobile (tktspace-mobile-app) is out of scope — its generated Dart enums regenerate on the next mobile cycle and the two Dart references are doc comments only (no compile break).
3. Considered alternatives
Section titled “3. Considered alternatives”A. Defer SERVICES_APP and ship DINING drop alone. Trade-off: smaller MR, simpler safety analysis. Rejected — adding an unused enum value is a 1-line schema edit and the PG enum rebuild is the same shape whether we drop-only or drop-and-add. Bundling avoids a second enum-rebuild migration two weeks from now when product green-lights the salons app.
B. Mark DINING_APP / DINING as @deprecated and leave them in the schema.
Trade-off: zero migration risk, zero consumer churn. Rejected — both downstream cleanups (869dpxbj6, 869dq1q27) need to grep cleanly for the live target-app domain; leaving deprecated values makes their diffs much noisier and forces every new contributor to learn which values are “real”. The current sphere row referencing DINING_APP (code='DINING') has zero downstream consumers, so the safety case for deprecation is weak.
C. Soft-delete the DINING sphere row but keep the enum values.
Trade-off: smaller migration, no PG enum rebuild. Rejected for the same reason as B — the value still appears in every generated client union (SphereTargetApp, ActivityType) and continues to confuse super-admins picking targetApp for new spheres.
4. Why PG enum rebuild (not DROP VALUE)
Section titled “4. Why PG enum rebuild (not DROP VALUE)”Postgres has no native ALTER TYPE ... DROP VALUE. The drizzle-kit-emitted pattern for an enum-value drop + add (mixed) is the rebuild:
ALTER TYPE activities.sphere_target_app RENAME TO sphere_target_app_old;CREATE TYPE activities.sphere_target_app AS ENUM ('GYM_APP','TICKETS_APP','SERVICES_APP');ALTER TABLE activities.spheres ALTER COLUMN target_app TYPE activities.sphere_target_app USING target_app::text::activities.sphere_target_app;DROP TYPE activities.sphere_target_app_old;This is destructive at the type level: any row whose target_app::text = 'DINING_APP' (or type::text = 'DINING', allowed_activity_types containing 'DINING', default_activity_type = 'DINING') would abort the ALTER COLUMN ... USING step because the cast finds no matching value in the new enum. That is the desired safety — production must not silently mangle data.
The pre-migration data-integrity check (AC-13) MUST therefore pass before apply on dev or prod:
SELECT count(*) FROM activities.activities WHERE type::text = 'DINING';SELECT count(*) FROM activities.spheres WHERE 'DINING' = ANY(allowed_activity_types::text[]) OR default_activity_type::text = 'DINING';SELECT count(*) FROM activities.spheres WHERE target_app::text = 'DINING_APP';Acceptance: queries (1) and (2) MUST return 0. Query (3) is allowed to
return exactly 1 if and only if that single row is the seeded
code='DINING' row (step 1 of the migration deletes it). To make this
check unambiguous, run the verifier below instead — it returns 0 for
the healthy case and otherwise lists offending rows:
SELECT id, code, target_app::text FROM activities.spheresWHERE target_app::text = 'DINING_APP' AND code <> 'DINING';-- expected: 0 rowsIf the verifier returns ≥1 row, STOP and surface to the user — additional DINING_APP usage exists beyond the seeded row.
The migration-safety-check skill reads the generated SQL and surfaces
any unguarded CASCADE or table drops outside the enum rebuild — none
expected here.
5. Backend file layout (verified against current code 2026-06-15)
Section titled “5. Backend file layout (verified against current code 2026-06-15)”All line numbers below confirmed by Read before drafting this ADR.
5.1 Schema (Drizzle)
Section titled “5.1 Schema (Drizzle)”libs/shared/data-access-db/src/lib/schema/activities.schema.ts:26-30—SphereTargetAppEnumarray currently['GYM_APP','TICKETS_APP','DINING_APP']→ must become['GYM_APP','TICKETS_APP','SERVICES_APP'].libs/shared/data-access-db/src/lib/schema/activities.schema.ts:39-45—ActivityTypeEnumarray currently['SHOW','MOVIE','SLOT_BASED','DINING','SERVICE']→ must become['SHOW','MOVIE','SLOT_BASED','SERVICE'].
5.2 Sphere DTOs (7 hardcoded literal spots)
Section titled “5.2 Sphere DTOs (7 hardcoded literal spots)”libs/features/spheres/src/lib/dto/sphere.dto.ts:31—example: ['DINING', 'SERVICE']→example: ['SERVICE'].libs/features/spheres/src/lib/dto/sphere.dto.ts:70—@ApiProperty({ enum: ['GYM_APP','TICKETS_APP','DINING_APP'], ... })→ triple updated to['GYM_APP','TICKETS_APP','SERVICES_APP'].libs/features/spheres/src/lib/dto/sphere.dto.ts:72—@IsIn(['GYM_APP','TICKETS_APP','DINING_APP'])→ same triple.libs/features/spheres/src/lib/dto/sphere.dto.ts:80—example: ['DINING', 'SERVICE']→['SERVICE'].libs/features/spheres/src/lib/dto/sphere.dto.ts:118—@ApiPropertyOptional({ enum: ['GYM_APP','TICKETS_APP','DINING_APP'] })→ new triple.libs/features/spheres/src/lib/dto/sphere.dto.ts:121—@IsIn(['GYM_APP','TICKETS_APP','DINING_APP'])→ new triple.libs/features/spheres/src/lib/dto/sphere.dto.ts:129—example: ['DINING', 'SERVICE']→['SERVICE'].
Phase C dev: grep 'DINING' and 'DINING_APP' across this file to be exhaustive — do not rely on the count above alone.
5.3 Spheres service (3 type-cast spots)
Section titled “5.3 Spheres service (3 type-cast spots)”libs/features/spheres/src/lib/services/spheres.service.ts:32— internal type aliastype ActivityTypeValue = 'SHOW' | 'MOVIE' | 'SLOT_BASED' | 'DINING' | 'SERVICE'→ drop'DINING'.libs/features/spheres/src/lib/services/spheres.service.ts:92—dto.targetApp as 'GYM_APP' | 'TICKETS_APP' | 'DINING_APP'→'GYM_APP' | 'TICKETS_APP' | 'SERVICES_APP'.libs/features/spheres/src/lib/services/spheres.service.ts:184— identical cast updated identically.
5.4 Activities DTOs
Section titled “5.4 Activities DTOs”libs/features/activities/src/lib/dto/create-activity.dto.ts:23— hand-written TS enumActivityTypecarriesDINING = 'DINING'. This is SEPARATE from the Drizzle PG enum atactivities.schema.ts:39-45— both must be kept in sync. Drop the line.libs/features/activities/src/lib/dto/favorite.dto.ts:41— docstring example'(e.g. SHOW, MOVIE, SLOT_BASED, DINING)'→ drop, DININGfrom the prose.
5.5 Helper comments (accuracy fixes, not behaviour changes)
Section titled “5.5 Helper comments (accuracy fixes, not behaviour changes)”libs/shared/common/src/helpers/resolve-target-app.ts:9— JSDoc reads(GYM_APP / TICKETS_APP / DINING_APP today)→(GYM_APP / TICKETS_APP / SERVICES_APP today).libs/features/auth/src/lib/interceptors/target-app.interceptor.spec.ts:10—X-Target-App: GYM_APP (or TICKETS_APP / DINING_APP)→(or TICKETS_APP / SERVICES_APP).
These two changes do not alter behaviour. They keep the prose accurate so the next reader does not misroute a bug report.
5.6 Tests requiring rewrite (5 spots)
Section titled “5.6 Tests requiring rewrite (5 spots)”libs/features/spheres/src/lib/__tests__/spheres-unit.spec.ts:38— caseAC-1: DINING sphere has targetApp=DINING_APP and allowedActivityTypes=[DINING]no longer holds. Rewrite to assertSERVICES sphere has targetApp=GYM_APP and allowedActivityTypes includes SERVICE.libs/features/spheres/src/lib/dto/sphere.dto.spec.ts:96-99— fixture uses'DINING'; swap for another currently-validActivityType('SERVICE').apps/api-e2e/src/spheres/spheres-schema.e2e-spec.ts:41—expect(spheres).toHaveLength(5)→4; drop'DINING'from the expectedcodesordering.apps/api-e2e/src/spheres/spheres-superadmin.e2e-spec.ts:196,202— payload['DINING','SERVICE']was the shrink-conflict path. Rewrite to['SERVICE']. Semantic preserved: still dropsSLOT_BASEDfromallowedActivityTypes, still triggers AC-24 (errors.sphere.activity_type_in_use). Do NOT change the activities seeded in the test setup — only the request payload.apps/api-e2e/src/client/wallet-target-app.e2e-spec.ts:38— docstring mention ofDINING_APP→ swap for a currently-valid value (illustrative only).
5.7 Backend module placement
Section titled “5.7 Backend module placement”No new modules. All edits stay in existing libs/features/spheres/, libs/features/activities/, libs/shared/data-access-db/, libs/shared/common/, and libs/features/auth/ libraries. No new shared utilities in libs/shared/ — helper relocation is explicitly deferred to 869dq1q27.
6. Consumer file layout (verified)
Section titled “6. Consumer file layout (verified)”6.1 tktspace-web
Section titled “6.1 tktspace-web”- Auto-regenerated by
npm run generatefrom refreshed_workflow/contracts/client.openapi.yaml:src/app/core/api/models/activity-type-array.ts:13src/app/core/api/models/activity-type.ts:8src/app/core/api/models/favorite-activity-dto.ts:23
- Hand-written (manual fix in this MR):
src/app/pages/explore/explore.page.ts:118— drop theDINING: '@tui.utensils'entry fromSPHERE_FALLBACK_ICONS(lines 113-119). Index type isRecord<string, string>(verified), so the removal is a clean one-line delete with nokeyof typeofcast needed.
After both: run npm run generate then npm run build (= ng build). Build MUST exit 0. No new tests.
6.2 tktspace-business — known regen gotcha
Section titled “6.2 tktspace-business — known regen gotcha”- Auto-regenerated (after the manual recipe below):
client/src/app/core/api/models/sphere-target-app.ts:7,sphere-target-app-array.ts:12,sphere-code.ts:7,sphere-code-array.ts:14,activity-type-code.ts:11,activity-type-code-array.ts:13,create-activity-dto.ts:38,update-activity-dto.ts:38,activity-response-dto.ts:36,fn/activities/activities-admin-list.ts:41. - Hand-written (manual fix in this MR):
client/src/app/features/dashboard/activities/pages/activity-form/activity-form.page.ts:106— drop the'DINING',entry frompublic activityTypes: ActivityResponseDto['type'][] = [...](lines 102-108). Final list:['SHOW','MOVIE','SLOT_BASED','SERVICE'].
The regen is NOT self-contained. Verified 2026-06-15:
tktspace-business/tools/gen/sync-business-contract.jsis referenced frompatch-passes-swagger.js:16(header comment “Run automatically viatools/gen/sync-business-contract.jsbeforeng-openapi-gen”) but DOES NOT exist on disk.ls tools/gen/returned:api-gateway.json,business.openapi.json,business.openapi.yaml,patch-passes-swagger.js,patch-spheres-swagger.js,swagger-api.json.npm run generate:apionly runsng-openapi-gen -c tools/gen/api-gateway.jsonagainst the existingtools/gen/swagger-api.json(286 KB hand-curated snapshot augmented bypatch-spheres-swagger.js+patch-passes-swagger.jswith ~40 schemas absent from the canonical YAML).- A naive overwrite of
swagger-api.jsonfrom_workflow/contracts/business.openapi.yaml(or fromlocalhost:5005/api/business/openapi.json) would strip those ~40 patched schemas and downstreamng buildwould fail (the picker, customer-pass adjust page, activities-list filters all reference them).
Phase C dev MUST follow this manual recipe (per spec AC-18, repeated verbatim):
-
Step A: Leave
tools/gen/swagger-api.jsonas the live working snapshot. Do NOT overwrite from_workflow/contracts/business.openapi.yamlor from a running backend. -
Step B: Hand-edit
tools/gen/swagger-api.jsonin place. Locate the three enum schemas undercomponents.schemas:SphereTargetApp—-DINING_APP,+SERVICES_APP.ActivityTypeCode—-DINING.SphereCode—-DINING.
Then update
tools/gen/patch-spheres-swagger.jsso the patch remains idempotent on the next dev’s machine. Verified line numbers (read 2026-06-15):patch-spheres-swagger.js:43—SphereCode.enum: ['SPORT','CINEMA','SHOWS','SERVICES','DINING']→ drop'DINING'.patch-spheres-swagger.js:49—SphereTargetApp.enum: ['GYM_APP','TICKETS_APP','DINING_APP']→['GYM_APP','TICKETS_APP','SERVICES_APP'].patch-spheres-swagger.js:55—ActivityTypeCode.enum: ['SHOW','MOVIE','SLOT_BASED','DINING','SERVICE']→ drop'DINING'.
-
Step C: Run
npm run generate:api. Confirm the regeneratedclient/src/app/core/api/models/files reflect the new enum domains. -
Step D: Apply the hand-written fix in
activity-form.page.ts:106(drop the'DINING',line). -
Step E: Run
npm run build. Build MUST exit 0.
Fallback (if Step B feels too brittle): same as any stub pattern used in 869dp8b71 (refundable, shipped 2026-06-13). Keep swagger-api.json unchanged, apply only the AC-17 manual fix in activity-form.page.ts, and add a // TODO(869e-followup): regenerate when sync-business-contract.js wrapper lands comment next to the cast. The chosen path (manual recipe vs as any fallback) MUST be documented in the MR description.
6.3 tktspace-landing
Section titled “6.3 tktspace-landing”Astro static site. No API references touched. No changes.
6.4 tktspace-mobile-app (NOT in scope)
Section titled “6.4 tktspace-mobile-app (NOT in scope)”Generated Dart enums in packages/api/lib/src/generated/swagger_api.enums.swagger.dart and the bundled swagger-api.json will regenerate clean on the next melos run sync:spec && melos run generate:api. Hand-written doc comments in packages/favorites/lib/src/favorites_repository.dart:50 and favorites_page.dart:238 mention DINING but do not compile-break — cosmetic follow-up.
7. Data model
Section titled “7. Data model”7.1 Tables touched
Section titled “7.1 Tables touched”activities.spheres— one row deleted (code='DINING'). Columns re-cast:target_app,allowed_activity_types(array),default_activity_type. Other rows (SPORT,CINEMA,SHOWS,SERVICES) untouched incode,target_app,allowed_activity_types,default_activity_type.activities.activities— columntypere-cast through the activity-type enum rebuild. No row content changes (zero rows reference'DINING'on dev — see AC-13).
7.2 Drizzle schema diff (preview)
Section titled “7.2 Drizzle schema diff (preview)”export const SphereTargetAppEnum = activitiesSchema.enum('sphere_target_app', [ 'GYM_APP', 'TICKETS_APP',- 'DINING_APP',+ 'SERVICES_APP',]);
export const ActivityTypeEnum = activitiesSchema.enum('activity_type', [ 'SHOW', 'MOVIE', 'SLOT_BASED',- 'DINING', 'SERVICE',]);7.3 Migration outline (preview only)
Section titled “7.3 Migration outline (preview only)”See drafts/migration-sphere-targetapp-enum-cleanup-drop-dining-add-services.sql. Real migration is generated by drizzle-kit generate in the tktspace-backend repo via the backend-drizzle-migration skill; it lands under libs/shared/data-access-db/migrations/.
7.4 Indexes
Section titled “7.4 Indexes”None added, none dropped. The PG enum rebuild does not touch indexes — ALTER COLUMN ... TYPE … USING … preserves them.
8. API design (per surface)
Section titled “8. API design (per surface)”Pure enum-value diff. No endpoint paths added or removed, no request/response field changes, no auth changes. All three surfaces emit the same enum schemas (SphereTargetApp, ActivityType) — that is intentional duplication consistent with the project’s “never share types across surfaces” rule, but the underlying domain is by definition identical because there is one PG enum per type.
| Contract | Endpoint | Schema diff |
|---|---|---|
contracts/client.openapi.yaml | GET /api/client/spheres, POST /api/client/favorites/activities (response shapes) | SphereTargetApp: -DINING_APP, +SERVICES_APP. ActivityType: -DINING. |
contracts/business.openapi.yaml | activity-form picker, activities-list filter, sphere read shapes | same diff |
contracts/super-admin.openapi.yaml | POST/PUT /api/superadmin/spheres (CreateSphereDto, UpdateSphereDto — targetApp, allowedActivityTypes, defaultActivityType) | same diff |
Refresh via pnpm run sync:contracts (= the backend-export-openapi skill) against a freshly migrated locally-booted backend on :5005. The script canonicalises key order — commit the YAML diffs alongside the backend edits. pnpm run sync:contracts:check MUST exit 0 in CI.
9. Surface impact (field-level visibility)
Section titled “9. Surface impact (field-level visibility)”No field-level differences across surfaces — the enum-value domain is global (one PG type per enum). The three contracts are still regenerated independently because each is the canonical snapshot for its own client codegen.
Visibility justification per surface:
/api/client: client-facing UI rendering a sphere picker / activity-type badge.SphereTargetAppMUST narrow to what mobile actually renders. LeavingDINING_APPadvertises a vertical that has no UI surface. AddingSERVICES_APPis forward-compatible: clients reading it as an unknown enum value will fall back to the default sphere icon (verified —SPHERE_FALLBACK_ICONSis aRecord<string, string>withDEFAULT_SPHERE_ICON = '@tui.sparkles'fallback attktspace-web/src/app/pages/explore/explore.page.ts:121)./api/business: business-admin activity-form picker for staff creating activities under a tenant. Same narrowing rationale; salons live underGYM_APPuntilSERVICES_APPboots, so the picker continues to show today’s options minusDINING./api/superadmin: super-admin sphere CRUD.@IsInvalidators reject invalidtargetApp/defaultActivityType/allowedActivityTypespayloads with HTTP 400. After the change, attempts to POSTtargetApp: 'DINING_APP'are 400. Attempts withtargetApp: 'SERVICES_APP'are 201 (no sphere row uses it yet, but the type accepts it). Internal fields (audit log, raw flags) are not affected.
No super-admin-only enum value is being introduced. SERVICES_APP is exposed identically on all three surfaces because the underlying PG enum is single-source.
10. Frontend implications
Section titled “10. Frontend implications”tktspace-business
Section titled “tktspace-business”client/src/app/features/dashboard/activities/pages/activity-form/activity-form.page.ts:106— drop'DINING',fromactivityTypespicker (ActivityResponseDto['type'][]).- Routing unchanged. No Taiga component swap. No new pages.
- Regeneration: follow §6.2 Steps A–E exactly. If the manual recipe is rejected by the dev,
as anyfallback documented in the MR description.
tktspace-web
Section titled “tktspace-web”src/app/pages/explore/explore.page.ts:118— drop theDINING: '@tui.utensils'icon-map entry.- Routing unchanged. No new pages.
- Regeneration:
npm run generateagainst refreshed_workflow/contracts/client.openapi.yaml. Thennpm run buildexits 0.
tktspace-landing
Section titled “tktspace-landing”Not touched. Astro static content has no DINING/DINING_APP references on disk (re-grepped at draft time — landing’s i18n strings live in Google Sheets; the consumer-list verification in AC-11 explicitly scoped to backend + web + business).
11. Mobile implications
Section titled “11. Mobile implications”NOT in scope.
- Affected apps if shipped later: both
apps/gym_appandapps/tickets_appconsumepackages/api, so a regen ofpackages/api/lib/src/generated/swagger_api.enums.swagger.dartwould propagate to both. No shared packages need source changes — the enum drop is purely additive on the client side. - Hand-written references that don’t compile-break:
packages/favorites/lib/src/favorites_repository.dart:50— doc comment.packages/favorites/lib/src/favorites_page.dart:238— doc comment.
- No screen, state, offline, deep-link, brand, or flavor changes.
- No new mobile app being added in this ADR.
SERVICES_APPFlutter app is a future product decision NOT scoped here.
12. Risks
Section titled “12. Risks”- Migration aborts on a non-empty DINING reference set. Mitigated by AC-13 pre-migration counts (three SQL queries returning
0). The seededDININGrow from migration0037is deleted by step 1 of the migration itself. If dev or staging has additional DINING activities created during testing, the migration will abort and the dev must clean up before re-running. This is the desired safety. - business regen gotcha leaves typed-client divergence. If the dev takes the
as anyfallback path instead of hand-editingswagger-api.json, the typed client fortktspace-businesswill continue advertising'DINING'inActivityResponseDto['type']until a follow-up regen lands. Mitigated by requiring the MR description to explicitly flag the fallback. - Stale patched schemas in
swagger-api.jsondrift further from canonical. Already known debt (the 286 KB hand-curated snapshot has ~40 schemas absent from_workflow/contracts/business.openapi.yaml). Not made worse by this MR. Surfaced as a recommendation in §15. - Performance: none. Enum rebuilds rewrite the column data files for affected columns. On a fresh dev DB this is microseconds. On production with no DINING references, the
ALTER COLUMN ... USINGis a fast no-op data check. - Security: none. No new endpoints, no auth changes, no PII surface.
13. Rollout plan
Section titled “13. Rollout plan”Single MR, no feature flag, no phased rollout.
- Backend MR contains: schema edit (§5.1) + DTO edits (§5.2) + service casts (§5.3) + activities DTO edits (§5.4) + helper comment fixes (§5.5) + test rewrites (§5.6) + auto-generated Drizzle migration (§7.3) + refreshed contract YAMLs (§8).
- Web sub-MR (or same MR if monorepo wiring allows — both repos are independent here, so two MRs in parallel are fine) contains: regenerated
core/api/**+ manual icon-map fix. - Business sub-MR contains: hand-edited
swagger-api.json+ updatedpatch-spheres-swagger.jsline numbers 43/49/55 + regeneratedcore/api/**+ manual picker fix. ORas anyfallback path documented. - No backfill. No data migration beyond the single DELETE in migration step 1. No rollback automation needed;
0037-generation seed SQL preserved in git history if ever required. - CI gates:
pnpm run sync:contracts:checkexit 0;npm testgreen in spheres feature; e2e green on superadmin + client spheres + wallet-target-app;npm run buildgreen in both Angular apps. - Required runtime smoke (per
feedback_runtime_smoke):npx nx serve apiboots; Swagger UI athttp://localhost:5005/api/superadmin/docsshows the two new enum domains.
14. Cross-surface coordination — mandatory backend checklist (verbatim, do NOT skip)
Section titled “14. Cross-surface coordination — mandatory backend checklist (verbatim, do NOT skip)”- Pre-migration data integrity check (AC-13) — run the three SQL queries from §4 against dev DB before applying. Each MUST return
0(the DINING sphere row will be deleted by step 1 of the migration; the queries account for that). If any query returns non-zero on dev or any pre-prod environment, STOP and surface to the user — do not attempt to force the migration through. - All 7 file edits in
sphere.dto.ts— do not miss any. Grep'DINING'and'DINING_APP'after edits to verify zero hits in this file. - Both TS enum + Drizzle PG enum updated. The hand-written
ActivityTypeTS enum atcreate-activity.dto.ts:23is SEPARATE from the Drizzle PG enum atactivities.schema.ts:39. Both must dropDINING. - Comment updates in
resolve-target-app.ts:9+target-app.interceptor.spec.ts:10are accuracy fixes, not behaviour changes. Do not refactor surrounding code. - E2e test rewrites (5 spots) — see §5.6. The shrink-conflict path in
spheres-superadmin.e2e-spec.tsMUST keep its semantic: still dropsSLOT_BASEDfromallowedActivityTypes, still triggers AC-24 (errors.sphere.activity_type_in_use). Verify by reading the surroundingit()block before editing. - Use the
backend-drizzle-migrationskill to auto-generate the SQL. Do NOT hand-write the migration. - Use the
migration-safety-checkskill on the generated SQL before applying. Surface any unguardedCASCADEor non-enum-rebuild table drop to the user. - Use the
backend-export-openapiskill (=pnpm run sync:contracts) against a locally booted backend with migrations applied. Commit the three YAML diffs in the same MR as the backend edits. pnpm run sync:contracts:checkMUST exit 0 before pushing.
15. Consumer instructions for Phase C
Section titled “15. Consumer instructions for Phase C”tktspace-webdev: drop the icon-map line atsrc/app/pages/explore/explore.page.ts:118. Runnpm run generate, confirm regenerated files incore/api/models/. Runnpm run build, confirm exit 0. No new tests.tktspace-businessdev: follow §6.2 Steps A–E exactly. Hand-edittools/gen/swagger-api.jsonAND updatetools/gen/patch-spheres-swagger.js:43,49,55AND drop'DINING',fromactivity-form.page.ts:106. Thennpm run generate:api, thennpm run build. If the manual recipe is rejected, take theas anyfallback path AND document the choice in the MR description for the reviewer.
16. Open follow-ups (NOT decided here)
Section titled “16. Open follow-ups (NOT decided here)”-
869dpxbj6— next in the cleanup chain. CollapsesActivityTypeto[SLOT_BASED, SERVICE]+ addshasSeatSelection: boolean. MergesCINEMA+SHOWSsphere rows intoEVENTS. Assumes this ADR shipped first. -
869dq1q27— extractlibs/shared/target-app/library. Relocatesresolve-target-app.ts+target-app.interceptor.ts. Assumes this ADR shipped first. -
NEW recommendation (NOT in scope here, flagged for user decision): open a separate ticket for
tktspace-business/tools/gen/sync-business-contract.jswrapper that does fetch-from-canonical + run allpatch-*.jsscripts +ng-openapi-genin one command. Verified absent on disk at draft time (ls tools/gen/) yet referenced bypatch-passes-swagger.js:16. Without it, every business contract refresh repeats the brittle Step B hand-edit. Recommend prioritising before869dpxbj6(which has even more enum churn — activity-type collapse + sphere row merges + new boolean field). Final decision deferred to user.Tech-debt compounding note (per critic SUGGESTION): if the Phase C dev picks the
as anyfallback in AC-18 instead of the manual recipe, the divergence betweentools/gen/swagger-api.jsonand the canonical_workflow/contracts/business.openapi.yamlwidens further. Each successive ticket in the cleanup chain (869dpxbj6adds 3+ enum changes;869dq1q27no codegen impact) compounds the gap. Strongly prefer the manual recipe even though it’s brittle, OR escalate the wrapper ticket priority above869dpxbj6so the next cleanup ships with a clean regen flow.
17. Verification notes from drafting (2026-06-15)
Section titled “17. Verification notes from drafting (2026-06-15)”- Spec line numbers re-verified against current code via Read. All 22 cited line numbers match exactly: schema
26-30,39-45;sphere.dto.ts31,70,72,80,118,121,129;spheres.service.ts32,92,184;create-activity.dto.ts:23;favorite.dto.ts:41;resolve-target-app.ts:9;target-app.interceptor.spec.ts:10;patch-spheres-swagger.js:43,49,55;explore.page.ts:118;activity-form.page.ts:106. tools/gen/sync-business-contract.jsconfirmed ABSENT (ls tools/gen/returned 6 files, no wrapper).patch-passes-swagger.js:16header confirmed to reference the missing wrapper.SPHERE_FALLBACK_ICONSindex type re-verified asRecord<string, string>atexplore.page.ts:113(clean delete confirmed safe, nokeyof typeofcast surface).- No drift discovered.
STATUS: READY_FOR_REVIEW