Migrate AI Hint feed generation
Bring dynamic hint generation into the account service while preserving the legacy key.osglab.com deployment for existing clients.
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
# AI Hint Feed migration and coexistence
|
||||
|
||||
## Boundary
|
||||
|
||||
`key.osglab.com` remains the legacy AI Hint Feed for installed clients that still
|
||||
use that origin. This repository must not:
|
||||
|
||||
- change the `key.osglab.com` DNS record;
|
||||
- redirect `key.osglab.com` to `account.osglab.com`;
|
||||
- reuse or delete the legacy container, image, settings file, or data volume;
|
||||
- require the legacy service to call this account service.
|
||||
|
||||
The migrated generator runs independently inside OSGAccountServer and publishes:
|
||||
|
||||
- `https://account.osglab.com/v1/content/hints/manifest`
|
||||
- `https://account.osglab.com/v1/content/hints/{locale}`
|
||||
- `https://account.osglab.com/hints/manifest.json`
|
||||
- `https://account.osglab.com/hints/hints-{locale}.json`
|
||||
|
||||
## Safe rollout
|
||||
|
||||
1. Back up the legacy `settings.json`, `manifest.json`, `hints-zh.json`, and
|
||||
`hints-en.json` from its persistent volume.
|
||||
2. Deploy OSGAccountServer with `HINT_FEED_ENABLED=false`.
|
||||
3. Apply Flyway migration `V24__hint_feed_generation.sql` and the matching
|
||||
runtime grants.
|
||||
4. Use the protected admin console to review generation settings and run one
|
||||
manual generation.
|
||||
5. Verify both v1 and legacy paths on `account.osglab.com`, including ETag/304.
|
||||
6. Set `HINT_FEED_ENABLED=true` only after the generated packs are accepted.
|
||||
7. Point only new client releases at `account.osglab.com`. Existing clients may
|
||||
continue to use `key.osglab.com`.
|
||||
|
||||
## Rollback
|
||||
|
||||
Disable `HINT_FEED_ENABLED` to stop scheduled generation. Published packs remain
|
||||
available from MySQL and manual publishing remains available. No rollback step
|
||||
depends on or modifies `key.osglab.com`.
|
||||
|
||||
Provider credentials such as `TOPHUB_API_KEY` stay in environment-backed secret
|
||||
storage. They are never written to the generation settings table or returned to
|
||||
the admin browser.
|
||||
@@ -50,6 +50,8 @@ GRANT SELECT ON osg_account.official_content_catalog TO 'osg_account_runtime'@'1
|
||||
GRANT SELECT ON osg_account.official_skills TO 'osg_account_runtime'@'10.20.%';
|
||||
GRANT SELECT ON osg_account.official_skill_localizations TO 'osg_account_runtime'@'10.20.%';
|
||||
GRANT SELECT ON osg_account.official_hint_packs TO 'osg_account_runtime'@'10.20.%';
|
||||
GRANT SELECT ON osg_account.hint_feed_settings TO 'osg_account_runtime'@'10.20.%';
|
||||
GRANT SELECT ON osg_account.hint_feed_generation_state TO 'osg_account_runtime'@'10.20.%';
|
||||
|
||||
GRANT INSERT, UPDATE, DELETE ON osg_account.accounts TO 'osg_account_runtime'@'10.20.%';
|
||||
GRANT INSERT, UPDATE ON osg_account.apple_credentials TO 'osg_account_runtime'@'10.20.%';
|
||||
@@ -94,6 +96,8 @@ GRANT UPDATE ON osg_account.official_content_catalog TO 'osg_account_runtime'@'1
|
||||
GRANT INSERT, UPDATE ON osg_account.official_skills TO 'osg_account_runtime'@'10.20.%';
|
||||
GRANT INSERT, UPDATE ON osg_account.official_skill_localizations TO 'osg_account_runtime'@'10.20.%';
|
||||
GRANT INSERT, UPDATE ON osg_account.official_hint_packs TO 'osg_account_runtime'@'10.20.%';
|
||||
GRANT UPDATE ON osg_account.hint_feed_settings TO 'osg_account_runtime'@'10.20.%';
|
||||
GRANT UPDATE ON osg_account.hint_feed_generation_state TO 'osg_account_runtime'@'10.20.%';
|
||||
|
||||
-- Deliberately absent: global privileges, GRANT OPTION, FILE, PROCESS, SUPER,
|
||||
-- CREATE USER, and UPDATE/DELETE on immutable ledger or usage-history tables.
|
||||
|
||||
@@ -726,6 +726,69 @@ paths:
|
||||
"204": { description: Skill disabled }
|
||||
"403": { description: SUPER_ADMIN role and valid CSRF are required }
|
||||
"404": { description: Skill was not found }
|
||||
/v1/admin/content/hints/generation/settings:
|
||||
get:
|
||||
security:
|
||||
- adminMtls: []
|
||||
adminSession: []
|
||||
summary: Return non-secret AI Hint generation settings
|
||||
responses:
|
||||
"200":
|
||||
description: Current generation settings and secret availability flags
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/HintFeedSettings" }
|
||||
"403": { description: SUPER_ADMIN or SUPPORT role is required }
|
||||
put:
|
||||
security:
|
||||
- adminMtls: []
|
||||
adminSession: []
|
||||
summary: Update non-secret AI Hint generation settings
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/AdminCsrf"
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/UpdateHintFeedSettingsRequest" }
|
||||
responses:
|
||||
"200":
|
||||
description: Updated generation settings
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/HintFeedSettings" }
|
||||
"400": { description: Settings are invalid }
|
||||
"403": { description: SUPER_ADMIN role and valid CSRF are required }
|
||||
/v1/admin/content/hints/generation/status:
|
||||
get:
|
||||
security:
|
||||
- adminMtls: []
|
||||
adminSession: []
|
||||
summary: Return AI Hint generation and scheduler status
|
||||
responses:
|
||||
"200":
|
||||
description: Durable generation status and current pack versions
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/HintFeedGenerationStatus" }
|
||||
"403": { description: SUPER_ADMIN or SUPPORT role is required }
|
||||
/v1/admin/content/hints/generation/regenerate:
|
||||
post:
|
||||
security:
|
||||
- adminMtls: []
|
||||
adminSession: []
|
||||
summary: Generate and atomically publish both AI Hint locale packs
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/AdminCsrf"
|
||||
responses:
|
||||
"200":
|
||||
description: Both locale packs were generated and published
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/HintFeedGenerationResponse" }
|
||||
"403": { description: SUPER_ADMIN role and valid CSRF are required }
|
||||
"409": { description: A generation is already in progress }
|
||||
"502": { description: Generation failed and the previous packs remain published }
|
||||
/v1/admin/content/hints/{locale}:
|
||||
get:
|
||||
security:
|
||||
@@ -1186,6 +1249,8 @@ paths:
|
||||
- CONTENT_SKILL_ENABLED
|
||||
- CONTENT_SKILL_DISABLED
|
||||
- CONTENT_HINT_PACK_PUBLISHED
|
||||
- CONTENT_HINT_FEED_SETTINGS_UPDATED
|
||||
- CONTENT_HINT_FEED_GENERATED
|
||||
- name: result
|
||||
in: query
|
||||
schema: { type: string, enum: [success, rejected] }
|
||||
@@ -1605,6 +1670,86 @@ components:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: [string, "null"]
|
||||
sources:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: array
|
||||
items: { type: string }
|
||||
HintFeedSettings:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- enabled
|
||||
- topHubApiKeyConfigured
|
||||
- generationIntervalHours
|
||||
- holidayCountriesZh
|
||||
- holidayCountriesEn
|
||||
- weatherCitiesZh
|
||||
- weatherCitiesEn
|
||||
- googleTrendsGeos
|
||||
properties:
|
||||
enabled: { type: boolean }
|
||||
topHubApiKeyConfigured: { type: boolean }
|
||||
generationIntervalHours: { type: integer, minimum: 1, maximum: 168 }
|
||||
holidayCountriesZh: { type: string, minLength: 2, maxLength: 255 }
|
||||
holidayCountriesEn: { type: string, minLength: 2, maxLength: 255 }
|
||||
weatherCitiesZh: { type: string, minLength: 1, maxLength: 2000 }
|
||||
weatherCitiesEn: { type: string, minLength: 1, maxLength: 2000 }
|
||||
googleTrendsGeos: { type: string, minLength: 2, maxLength: 255 }
|
||||
UpdateHintFeedSettingsRequest:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- generationIntervalHours
|
||||
- holidayCountriesZh
|
||||
- holidayCountriesEn
|
||||
- weatherCitiesZh
|
||||
- weatherCitiesEn
|
||||
- googleTrendsGeos
|
||||
properties:
|
||||
generationIntervalHours: { type: integer, minimum: 1, maximum: 168 }
|
||||
holidayCountriesZh: { type: string, minLength: 2, maxLength: 255 }
|
||||
holidayCountriesEn: { type: string, minLength: 2, maxLength: 255 }
|
||||
weatherCitiesZh: { type: string, minLength: 1, maxLength: 2000 }
|
||||
weatherCitiesEn: { type: string, minLength: 1, maxLength: 2000 }
|
||||
googleTrendsGeos: { type: string, minLength: 2, maxLength: 255 }
|
||||
HintFeedGenerationStatus:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- enabled
|
||||
- outcome
|
||||
- intervalHours
|
||||
- topHubApiKeyConfigured
|
||||
properties:
|
||||
enabled: { type: boolean }
|
||||
outcome: { type: string, enum: [IDLE, RUNNING, SUCCEEDED, FAILED] }
|
||||
intervalHours: { type: integer, minimum: 1, maximum: 168 }
|
||||
lastStartedAt: { type: string, format: date-time }
|
||||
lastCompletedAt: { type: string, format: date-time }
|
||||
lastErrorCode: { type: string, maxLength: 64 }
|
||||
nextScheduledAt: { type: string, format: date-time }
|
||||
topHubApiKeyConfigured: { type: boolean }
|
||||
zhVersion: { type: integer, minimum: 1 }
|
||||
zhCardCount: { type: integer, minimum: 0, maximum: 40 }
|
||||
enVersion: { type: integer, minimum: 1 }
|
||||
enCardCount: { type: integer, minimum: 0, maximum: 40 }
|
||||
HintFeedGenerationResponse:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required: [generationId, generatedAt, zh, en]
|
||||
properties:
|
||||
generationId: { type: string, format: uuid }
|
||||
generatedAt: { type: string, format: date-time }
|
||||
zh: { $ref: "#/components/schemas/HintFeedPackGenerationResult" }
|
||||
en: { $ref: "#/components/schemas/HintFeedPackGenerationResult" }
|
||||
HintFeedPackGenerationResult:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required: [version, cardCount]
|
||||
properties:
|
||||
version: { type: integer, minimum: 1 }
|
||||
cardCount: { type: integer, minimum: 0, maximum: 40 }
|
||||
AdminSessionState:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
|
||||
Reference in New Issue
Block a user