POSA_Copyrighter/docs/superpowers/plans/2026-05-26-multi-candidate-knowledge-promotion.md
유창욱 3f7b3a9cf2 chore: initial commit of copyrighter (rights_filter)
Image rights / copyright detection system: SQLite store, HTTP app,
search integrations (Naver, Google Custom Search, Google Cloud Vision
web detection), image analysis (fingerprints, face/person detection,
evidence enrichment, risk scoring), an admin/review layer, governance
and retention policies, batch jobs, and a browser-based operator GUI.

This baseline incorporates a full code-review remediation pass
(46 fixes; 358 tests passing). Highlights:

CRITICAL
- Prevent evidence cascade-delete during the schema-constraint
  migration by disabling FK enforcement around the table rebuild.

Security
- Sandbox served media (neutralize stored XSS from uploaded/collected
  SVGs) via CSP + nosniff on the untrusted media routes.
- Strip embedded EXIF/GPS from external image derivatives before they
  are sent to third-party APIs.
- Return a clean 404 (not an uncaught StopIteration) for PATCH on an
  unknown provider.

Correctness
- LLM-summary failures no longer add +30 to the risk score.
- Decode only explicit JS escapes so Korean image URLs are not mangled.
- Consume search quota only after a successful request.
- Naver/Google adapters map responses inside the failure boundary, so a
  malformed response degrades to evidence instead of crashing enrichment.
- Domain-aware provider attribution; face-box IoU de-duplication; count
  searches (not result items); per-box crop isolation; clamp evidence
  confidence and Google CSE num; real submittedEpoch; and more.

Robustness
- Offline LLM connect fast-fails (short connect timeout) so seed/reload
  requests are not stalled; full read timeout preserved for generation.
- Malformed numeric env vars fall back to defaults instead of crashing
  startup.

Performance
- Per-submission evidence reads (no full-table scan per rescore),
  audit-log LIMIT, lazy active-store lookup, hoisted timestamps.

Tests
- ~24 regression tests added pinning the above fixes.

Runtime data (data/, outputs/, *.sqlite3, *.log), secrets (.env), and
node_modules are gitignored.
2026-06-09 09:50:31 +09:00

4.9 KiB

Multi Candidate Knowledge Promotion Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Allow an operator to select several collected image-search candidates and promote them into one knowledge-base entry with multiple visual samples.

Architecture: Keep the current Naver candidate collection flow and add a batch-promotion path beside the existing single-candidate path. The SQLite store owns the merge behavior, the HTTP layer exposes one batch endpoint, and the GUI adds selection controls plus one shared promotion form.

Tech Stack: Python standard-library HTTP server, SQLite JSON payload store, pytest, static HTML/CSS/JavaScript operator GUI.


Task 1: Store Contract

Files:

  • Modify: tests/rights_filter/server/test_sqlite_store.py

  • Modify: src/rights_filter/server/sqlite_store.py

  • Step 1: Write the failing test

Add a test that collects two candidates and calls promote_collection_candidates({"candidate_ids": [...]}). Assert that one knowledge entry is created, both candidate fingerprints are preserved, and both candidates point to the same promotedKnowledgeId.

  • Step 2: Run the test to verify RED

Run: python -m pytest tests/rights_filter/server/test_sqlite_store.py::test_sqlite_store_promotes_multiple_candidates_into_one_knowledge_entry -q

Expected: fail because CopyrighterStore.promote_collection_candidates does not exist.

  • Step 3: Implement the store merge

Add promote_collection_candidates and let the existing promote_collection_candidate delegate to it with a single ID. The merged knowledge payload must include sourceCandidates, sampleFingerprints, imageAsset, imageAssets, imageFacts, and operator metadata.

  • Step 4: Run the store tests

Run: python -m pytest tests/rights_filter/server/test_sqlite_store.py::test_sqlite_store_collects_keyword_candidates_and_promotes_one_to_knowledge tests/rights_filter/server/test_sqlite_store.py::test_sqlite_store_promotes_multiple_candidates_into_one_knowledge_entry -q

Expected: pass.

Task 2: HTTP Endpoint

Files:

  • Modify: tests/rights_filter/server/test_http_app.py

  • Modify: src/rights_filter/server/http_app.py

  • Step 1: Write the failing test

Add a test that posts to POST /api/collections/candidates/promote-batch with two candidate IDs and asserts that the response contains one merged knowledge entry.

  • Step 2: Run the test to verify RED

Run: python -m pytest tests/rights_filter/server/test_http_app.py::test_http_server_promotes_multiple_collection_candidates_into_one_knowledge_entry -q

Expected: fail with HTTP 404 or missing route.

  • Step 3: Implement the route

Route /api/collections/candidates/promote-batch to store.promote_collection_candidates(body) and keep /api/collections/candidates/{id}/promote intact.

  • Step 4: Run the HTTP tests

Run: python -m pytest tests/rights_filter/server/test_http_app.py::test_http_server_collects_keyword_candidates_and_promotes_candidate tests/rights_filter/server/test_http_app.py::test_http_server_promotes_multiple_collection_candidates_into_one_knowledge_entry -q

Expected: pass.

Task 3: Operator GUI

Files:

  • Modify: tests/operator_gui/test_static_workbench.py

  • Modify: web/operator-gui/index.html

  • Modify: web/operator-gui/app.js

  • Modify: web/operator-gui/styles.css

  • Step 1: Write the static GUI test

Assert that the GUI exposes candidate checkboxes, a shared collection promotion form, and a call to /api/collections/candidates/promote-batch.

  • Step 2: Run the static GUI test to verify RED

Run: python -m pytest tests/operator_gui/test_static_workbench.py::test_operator_gui_exposes_keyword_candidate_collection_workflow -q

Expected: fail because the batch form and handler are not present.

  • Step 3: Implement the UI

Add checkboxes to candidate cards, a compact batch promotion form under the candidate list, and a promoteSelectedCollectionCandidates handler that posts selected IDs plus name/type/aliases/keywords/memo.

  • Step 4: Run GUI checks

Run: python -m pytest tests/operator_gui/test_static_workbench.py -q Run: node --check web/operator-gui/app.js

Expected: pass.

Task 4: End-To-End Verification

Files:

  • No additional source files.

  • Step 1: Run full automated verification

Run: python -m pytest

Expected: all tests pass.

  • Step 2: Restart local server on port 9500

Run: python run_copyrighter_server.py --host 127.0.0.1 --port 9500

Expected: /health returns {"status":"ok","port":9500}.

  • Step 3: Visual smoke check

Open http://127.0.0.1:9500, switch to the Knowledge Base view, and confirm candidate cards show stable checkboxes and a single batch-promotion control.