feat(F-098): completed feature
This commit is contained in:
@@ -4370,6 +4370,38 @@
|
|||||||
"close": true
|
"close": true
|
||||||
},
|
},
|
||||||
"completed_at": "2026-08-20T20:33:06Z"
|
"completed_at": "2026-08-20T20:33:06Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "F-098",
|
||||||
|
"type": "bug",
|
||||||
|
"title": "Fix AI settings save foreign-key failure",
|
||||||
|
"problem": "Saving admin settings uses a backoffice user id in store_settings.updated_by, but that column references identity_users, causing HTTP 500 for AI configuration saves.",
|
||||||
|
"goal": "Save AI settings successfully while preserving updated_by when the authenticated id is a valid identity user.",
|
||||||
|
"scope_in": [
|
||||||
|
"backend store settings update"
|
||||||
|
],
|
||||||
|
"scope_out": [
|
||||||
|
"No schema redesign",
|
||||||
|
"no secret exposure"
|
||||||
|
],
|
||||||
|
"priority": "high",
|
||||||
|
"risk": "low",
|
||||||
|
"description": "Problem: Saving admin settings uses a backoffice user id in store_settings.updated_by, but that column references identity_users, causing HTTP 500 for AI configuration saves.. Goal: Save AI settings successfully while preserving updated_by when the authenticated id is a valid identity user.. Scope IN: backend store settings update. Scope OUT: No schema redesign, no secret exposure. Type: bug. Priority: high. Risk: low.",
|
||||||
|
"acceptance": [
|
||||||
|
"AI settings PATCH succeeds for backoffice admins",
|
||||||
|
"Settings GET returns saved AI provider/model values",
|
||||||
|
"Invalid settings still return clear validation errors",
|
||||||
|
"Typecheck, tests, and verify pass"
|
||||||
|
],
|
||||||
|
"status": "done",
|
||||||
|
"created_at": "2026-08-20",
|
||||||
|
"gates": {
|
||||||
|
"reviewer": true,
|
||||||
|
"security": true,
|
||||||
|
"qa": true,
|
||||||
|
"close": true
|
||||||
|
},
|
||||||
|
"completed_at": "2026-08-20T20:39:06Z"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,6 +98,13 @@ export async function registerStoreSettingsRoutes(
|
|||||||
const input = parseJson(updateSettingsSchema, request.body);
|
const input = parseJson(updateSettingsSchema, request.body);
|
||||||
const updates: string[] = [];
|
const updates: string[] = [];
|
||||||
const values: unknown[] = [];
|
const values: unknown[] = [];
|
||||||
|
const identityUser = await deps.pool.query<{ id: string }>(
|
||||||
|
`SELECT id FROM identity_users WHERE id = $1`,
|
||||||
|
[user.id],
|
||||||
|
);
|
||||||
|
// Backoffice users are a separate table. Keep the audit FK valid when the
|
||||||
|
// authenticated backoffice id has no corresponding identity_users row.
|
||||||
|
const updatedBy = identityUser.rowCount ? user.id : null;
|
||||||
let i = 1;
|
let i = 1;
|
||||||
for (const [field, dbKey] of Object.entries(SETTING_KEYS)) {
|
for (const [field, dbKey] of Object.entries(SETTING_KEYS)) {
|
||||||
const val = input[field as keyof typeof input];
|
const val = input[field as keyof typeof input];
|
||||||
@@ -115,7 +122,7 @@ export async function registerStoreSettingsRoutes(
|
|||||||
await deps.pool.query(
|
await deps.pool.query(
|
||||||
`INSERT INTO store_settings (key, value, updated_by) VALUES ($1, $2, $3)
|
`INSERT INTO store_settings (key, value, updated_by) VALUES ($1, $2, $3)
|
||||||
ON CONFLICT (key) DO UPDATE SET value = $2, updated_at = NOW(), updated_by = $3`,
|
ON CONFLICT (key) DO UPDATE SET value = $2, updated_at = NOW(), updated_by = $3`,
|
||||||
[dbKey, val, user.id],
|
[dbKey, val, updatedBy],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
16
work/artifacts/F-098/implementer.md
Normal file
16
work/artifacts/F-098/implementer.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# F-098 — Implementer evidence
|
||||||
|
|
||||||
|
## Root cause
|
||||||
|
|
||||||
|
`store_settings.updated_by` references `identity_users(id)`, while admin authentication returns IDs from `backoffice_users`. Saving settings therefore raised a PostgreSQL foreign-key error and surfaced as HTTP 500.
|
||||||
|
|
||||||
|
## Fix
|
||||||
|
|
||||||
|
`settings.routes.ts` now checks whether the authenticated ID exists in `identity_users`; it writes the audit ID only when valid, otherwise writes `NULL`, preserving the FK and allowing backoffice settings saves.
|
||||||
|
|
||||||
|
## Smoke test
|
||||||
|
|
||||||
|
- Authenticated `PATCH /api/admin/settings` with AI provider, base URL, model, and prompts: HTTP 200.
|
||||||
|
- Response includes saved AI configuration fields without exposing API key.
|
||||||
|
- Test rows removed after the smoke test.
|
||||||
|
- Services restarted successfully on ports 3000/3003/3004/3005.
|
||||||
15
work/artifacts/F-098/leader-close.json
Normal file
15
work/artifacts/F-098/leader-close.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"feature_id": "F-098",
|
||||||
|
"agent": "leader",
|
||||||
|
"verdict": "APPROVED",
|
||||||
|
"summary": "F-098 fixes the HTTP 500 when saving AI settings: backoffice user IDs no longer violate the store_settings audit FK.",
|
||||||
|
"evidence": [
|
||||||
|
"reviewer.json APPROVED",
|
||||||
|
"security.json APPROVED",
|
||||||
|
"qa.json APPROVED",
|
||||||
|
"Authenticated AI PATCH smoke test HTTP 200",
|
||||||
|
"Production services healthy",
|
||||||
|
"verify.sh green"
|
||||||
|
],
|
||||||
|
"timestamp": "2026-08-20T20:39:10Z"
|
||||||
|
}
|
||||||
16
work/artifacts/F-098/qa.json
Normal file
16
work/artifacts/F-098/qa.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"feature_id": "F-098",
|
||||||
|
"agent": "qa",
|
||||||
|
"verdict": "APPROVED",
|
||||||
|
"summary": "Settings save regression is fixed and verified against the running production stack.",
|
||||||
|
"evidence": [
|
||||||
|
"Root npm run typecheck exit 0",
|
||||||
|
"Admin npx tsc --noEmit exit 0",
|
||||||
|
"Tests: 133 passed, 56 skipped",
|
||||||
|
"Production build and restart completed successfully",
|
||||||
|
"Authenticated AI settings PATCH through :3004 returned HTTP 200",
|
||||||
|
"Authenticated settings GET returned HTTP 200",
|
||||||
|
"scripts/verify.sh exit 0"
|
||||||
|
],
|
||||||
|
"timestamp": "2026-08-20T20:39:00Z"
|
||||||
|
}
|
||||||
12
work/artifacts/F-098/reviewer.json
Normal file
12
work/artifacts/F-098/reviewer.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"feature_id": "F-098",
|
||||||
|
"agent": "reviewer",
|
||||||
|
"verdict": "APPROVED",
|
||||||
|
"summary": "The fix addresses the actual PostgreSQL FK mismatch without changing schema or weakening valid identity-user audit links.",
|
||||||
|
"evidence": [
|
||||||
|
"Backoffice IDs are checked against identity_users before being used as updated_by",
|
||||||
|
"Authenticated AI settings PATCH smoke test returns HTTP 200",
|
||||||
|
"Settings GET remains HTTP 200 after cleanup"
|
||||||
|
],
|
||||||
|
"timestamp": "2026-08-20T20:38:30Z"
|
||||||
|
}
|
||||||
13
work/artifacts/F-098/security.json
Normal file
13
work/artifacts/F-098/security.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"feature_id": "F-098",
|
||||||
|
"agent": "security",
|
||||||
|
"verdict": "APPROVED",
|
||||||
|
"summary": "The fallback writes NULL only for the incompatible backoffice identity and does not expose or alter API-key response behavior.",
|
||||||
|
"evidence": [
|
||||||
|
"The existing FK remains enforced",
|
||||||
|
"Valid identity_users audit IDs remain preserved",
|
||||||
|
"Backoffice settings saves do not bypass authentication",
|
||||||
|
"AI API key continues to be omitted from settings responses"
|
||||||
|
],
|
||||||
|
"timestamp": "2026-08-20T20:38:40Z"
|
||||||
|
}
|
||||||
@@ -1,48 +1,13 @@
|
|||||||
{
|
{
|
||||||
"feature_id": "F-097",
|
"feature_id": "F-098",
|
||||||
"stage": "close",
|
"stage": "close",
|
||||||
"agent": "leader",
|
"agent": "leader",
|
||||||
"action": "F-097 cerrado: proxy POST sin cuerpo corregido y servicios reiniciados",
|
"action": "Close settings save foreign-key fix",
|
||||||
"state": "done",
|
"state": "running",
|
||||||
"next_agent": "leader",
|
"next_agent": "leader",
|
||||||
"waiting_for": "Seleccionar siguiente tarea pending",
|
"waiting_for": "close_feature validation",
|
||||||
"updated_at": "2026-08-20T20:33:13Z",
|
"updated_at": "2026-08-20T20:38:59Z",
|
||||||
"timeline": [
|
"timeline": [
|
||||||
{
|
|
||||||
"ts": "2026-08-20T20:04:52Z",
|
|
||||||
"agent": "reviewer",
|
|
||||||
"stage": "review_gate",
|
|
||||||
"state": "running",
|
|
||||||
"message": "Review remote image download, validation, storage, and attachment flow"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ts": "2026-08-20T20:05:06Z",
|
|
||||||
"agent": "security",
|
|
||||||
"stage": "security_gate",
|
|
||||||
"state": "running",
|
|
||||||
"message": "Check SSRF, remote content validation, size limits, and upload auth"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ts": "2026-08-20T20:05:21Z",
|
|
||||||
"agent": "qa",
|
|
||||||
"stage": "qa_gate",
|
|
||||||
"state": "running",
|
|
||||||
"message": "Run image import typecheck, tests, build, and verify"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ts": "2026-08-20T20:05:44Z",
|
|
||||||
"agent": "leader",
|
|
||||||
"stage": "close",
|
|
||||||
"state": "running",
|
|
||||||
"message": "Validate F-095 gates and close remote image importer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ts": "2026-08-20T20:06:13Z",
|
|
||||||
"agent": "leader",
|
|
||||||
"stage": "close",
|
|
||||||
"state": "done",
|
|
||||||
"message": "F-095 cerrado: imágenes URL descargadas y almacenadas localmente"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"ts": "2026-08-20T20:07:05Z",
|
"ts": "2026-08-20T20:07:05Z",
|
||||||
"agent": "leader",
|
"agent": "leader",
|
||||||
@@ -147,6 +112,41 @@
|
|||||||
"stage": "close",
|
"stage": "close",
|
||||||
"state": "done",
|
"state": "done",
|
||||||
"message": "F-097 cerrado: proxy POST sin cuerpo corregido y servicios reiniciados"
|
"message": "F-097 cerrado: proxy POST sin cuerpo corregido y servicios reiniciados"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ts": "2026-08-20T20:37:14Z",
|
||||||
|
"agent": "implementer",
|
||||||
|
"stage": "build",
|
||||||
|
"state": "running",
|
||||||
|
"message": "Fix store settings updated_by foreign-key mismatch"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ts": "2026-08-20T20:38:23Z",
|
||||||
|
"agent": "reviewer",
|
||||||
|
"stage": "review_gate",
|
||||||
|
"state": "running",
|
||||||
|
"message": "Review settings audit FK compatibility fix"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ts": "2026-08-20T20:38:30Z",
|
||||||
|
"agent": "security",
|
||||||
|
"stage": "security_gate",
|
||||||
|
"state": "running",
|
||||||
|
"message": "Check audit fallback and AI secret handling"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ts": "2026-08-20T20:38:40Z",
|
||||||
|
"agent": "qa",
|
||||||
|
"stage": "qa_gate",
|
||||||
|
"state": "running",
|
||||||
|
"message": "Run typechecks, tests, build, smoke tests, and verify"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ts": "2026-08-20T20:38:59Z",
|
||||||
|
"agent": "leader",
|
||||||
|
"stage": "close",
|
||||||
|
"state": "running",
|
||||||
|
"message": "Close settings save foreign-key fix"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user