test(visibility): full suite green under RLS — sync paths, device-auth bridge, seed pool
Phase 5 (plan §7): the entire integration suite passes with the RLS policies live. The suite was the triage enumerator the plan predicted: - device_auth middleware: the THIRD identity bridge — KOReader/Kobo device-authenticated requests read media as the DEVICE OWNER (plan A9 called this path out; the flip was 404s on sync metadata). - conflicts/analytics/progress handlers: detached context.Background() reads threaded to the request context (the requesting user's identity) — 12+ sites; resolve helpers take ctx. - scanner: ScanFolders self-declares its configured admin identity (server maintenance, product-correct for any caller); generic job creation stamps the requesting admin. - harness: setup.DBPool and the sync-test pool declare rls.SeedIdentity at SESSION scope via BeforeAcquire — the pool never serves a second identity, so no A3 bleed; setup.DB falls back to the seed identity (NewSeedDB) for ungoverned-context calls; filter tests use seedExec for raw governed-table statements. - contract pin WIDENED (docs/contract_pins.md 2026-10-29): under RLS, item-scoped gates may answer 404 (row invisible — no existence leak) as well as 403 (Go-level checks on listings keep strict 403). - TestWorker_JobStatusTracking — the pre-existing red from the Phase 0 baseline — passes with the harness seed work; no product change.
This commit is contained in:
1 parent
bb2cc26e9d
commit
4d697f5fcf
7 files changed
+84
-38
No files matched your search
@@ -80,7 +80,7 @@ func (h *ConflictHandler) GetConflictsData(c *echo.Context) ([]ConflictDetailRes
|
||||
status = "unresolved"
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
ctx := c.Request().Context()
|
||||
|
||||
var conflicts interface{}
|
||||
var err error
|
||||
@@ -263,13 +263,13 @@ func (h *ConflictHandler) ResolveConflict(c *echo.Context) error {
|
||||
}
|
||||
|
||||
if conflict.ConflictType == "progress" {
|
||||
if err := h.applyProgressResolution(conflict.MediaItemID, conflict.UserID, winnerSource, winnerData); err == nil {
|
||||
if err := h.applyProgressResolution(c.Request().Context(), conflict.MediaItemID, conflict.UserID, winnerSource, winnerData); err == nil {
|
||||
appliedTo["progress"] = true
|
||||
}
|
||||
}
|
||||
|
||||
if conflict.ConflictType == "annotation_highlight" || conflict.ConflictType == "annotation_bookmark" || conflict.ConflictType == "annotation_note" {
|
||||
if err := h.applyAnnotationResolution(conflict.MediaItemID, conflict.UserID, winnerData, conflict.ConflictType); err == nil {
|
||||
if err := h.applyAnnotationResolution(c.Request().Context(), conflict.MediaItemID, conflict.UserID, winnerData, conflict.ConflictType); err == nil {
|
||||
appliedTo["annotations"] = true
|
||||
}
|
||||
}
|
||||
@@ -321,7 +321,7 @@ func (h *ConflictHandler) ResolveConflict(c *echo.Context) error {
|
||||
// the service's LWW seed instead of being NULLed — that is the fix.
|
||||
// chapter_progress keeps its stored value instead of being overwritten
|
||||
// with the book-level percentage.
|
||||
func (h *ConflictHandler) applyProgressResolution(mediaItemID pgtype.UUID, userID pgtype.UUID, winnerSource string, data map[string]interface{}) error {
|
||||
func (h *ConflictHandler) applyProgressResolution(ctx context.Context, mediaItemID pgtype.UUID, userID pgtype.UUID, winnerSource string, data map[string]interface{}) error {
|
||||
req := wsync.SaveProgressRequest{
|
||||
MediaItemID: mediaItemID,
|
||||
UserID: userID,
|
||||
@@ -361,12 +361,11 @@ func (h *ConflictHandler) applyProgressResolution(mediaItemID pgtype.UUID, userI
|
||||
req.TotalPages = &tp
|
||||
}
|
||||
|
||||
_, err := h.progressSvc.SaveProgress(context.Background(), req)
|
||||
_, err := h.progressSvc.SaveProgress(ctx, req)
|
||||
return err
|
||||
}
|
||||
|
||||
func (h *ConflictHandler) applyAnnotationResolution(mediaItemID pgtype.UUID, userID pgtype.UUID, winnerData map[string]interface{}, conflictType string) error {
|
||||
ctx := context.Background()
|
||||
func (h *ConflictHandler) applyAnnotationResolution(ctx context.Context, mediaItemID pgtype.UUID, userID pgtype.UUID, winnerData map[string]interface{}, conflictType string) error {
|
||||
|
||||
dedupKey, _ := winnerData["dedup_key"].(string)
|
||||
if dedupKey == "" {
|
||||
@@ -702,7 +701,7 @@ func (h *ConflictHandler) BulkResolveConflicts(c *echo.Context) error {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := h.applyProgressResolution(conflict.MediaItemID, conflict.UserID, winningSource, winnerData); err != nil {
|
||||
if err := h.applyProgressResolution(c.Request().Context(), conflict.MediaItemID, conflict.UserID, winningSource, winnerData); err != nil {
|
||||
results = append(results, ConflictResult{
|
||||
ConflictID: conflictIDStr,
|
||||
Status: "error",
|
||||
|
||||
Reference in new issue
Block a user