diff --git a/findings.brand-description-prompt.md b/findings.brand-description-prompt.md new file mode 100644 index 0000000..03bea45 --- /dev/null +++ b/findings.brand-description-prompt.md @@ -0,0 +1,59 @@ +# Findings: Brand Description Prompt Context + +## User Requirement +- The brand edit UI has a required `品牌描述` field. +- The current brand description should influence every generated article. +- Very long descriptions must not make prompts unbounded. +- Numeric/basic facts in a description must survive any compaction unchanged. + +## Screenshot Evidence +- Example brand: `辽宁木格创意家居有限公司`. +- The description contains founding year, company positioning, location, production-area data, and production capabilities. + +## Research Findings +- `brands.description` is already required by `BrandService` and is submitted/displayed by the admin-web brand editor. +- Question-expansion code already loads brand name/website/description into a dedicated brand context, providing a local query pattern to reuse. +- Article generation does not expose an obvious shared `brand_description` prompt field in the initial search; each generation path currently needs tracing. +- `BrandRequest` normalizes description with whitespace trim and enforces non-empty input, but the inspected CRUD path has no generation-specific length budget. +- Brand identity is tenant-scoped and has `name`, optional `website`, and description; a small read-only prompt context can load these fields directly without coupling generation to the full CRUD response/cache. +- Queue-time snapshotting is preferable to worker-time-only lookup: it makes a queued generation reproducible if the brand description is edited before the worker runs. +- The persisted article-body paths are template, custom prompt-rule, imitation, and KOL generation. +- Failed article regeneration copies the previous generation task's `input_params_json`, so a brand-context snapshot on the original task is automatically stable across retries/regeneration. +- Template analyze/title/outline assists are separate LLM calls and require a separate scope decision; they do not persist article bodies directly. +- The LLM layer estimates prompt tokens for rate limiting but does not enforce an input-context budget for article prompts. +- Brand description is PostgreSQL `TEXT`; the brand editor, shell modal, and onboarding modal do not set `maxlength`, so descriptions are effectively unbounded from the generation perspective. +- Existing code has rune-safe truncation helpers, but blind head truncation would drop later numeric facts and is not suitable here. + +## Proposed Compaction Direction +- Short descriptions pass through verbatim. +- Long descriptions use deterministic extractive compaction rather than another LLM call: keep the opening identity sentence, sentences with numeric/date/contact facts, and high-value business/capability sentences, then restore source order within a hard rune budget. +- Extraction must not rewrite text, ensuring retained years, quantities, areas, certifications, and names remain byte-for-byte/rune-for-rune faithful. +- Snapshot the compacted context, original rune count, and compaction flag at queue time. +- Production read-only length distribution on 2026-07-10: 7 active descriptions, min 54, median 277, average 478, P90 1167.4, max 1195; none exceed 1200 or 2400 runes. +- Production prompt accounting currently estimates roughly one character per token and allows a large article output budget. A 2000-rune brand-description cap is material but bounded and preserves every current description verbatim. +- Brand description must be treated as the authoritative source for the focal brand's base facts. This matters because the current screenshot says 2019 while previously inspected RAG items contained both 2020 and 2019. +- The existing `KnowledgeFactConstraint` model and generate/repair/reject guard can protect facts extracted from the brand description too. +- Constraint merge order must be brand first, RAG second; equivalent conflicting RAG constraints must be discarded before validation. +- The existing generation payload helper file is the correct place for a structured `__snapshot_brand_prompt_context` key and JSON round-trip helpers. +- Existing prompt tests cover template generation only; custom, imitation, and KOL prompt placement need new focused coverage. +- Review follow-up: Chinese numeral detection must require a nearby unit/date marker; treating any `一`/`两` as numeric would over-prioritize ordinary prose. +- Review follow-up: oversized-segment splitting should track rune count incrementally rather than rebuild the builder string per rune. +- The existing fact-repair prompt wording should mention both brand-profile and knowledge-base canonical facts after constraint merging. +- Direct regression cases for Chinese numerals should distinguish factual counts/dates (`二〇一九年`, `两项`, `三百户`) from ordinary wording (`一体化`, `两全其美`). +- Length review found a second growth channel: `FactConstraints` are extracted from the full description and rendered after the 2000-rune excerpt. They must be restricted to the focal brand and bounded fact kinds so unrelated company dates cannot bypass the prompt budget. +- Priority review found that enough numeric segments can outrank and evict the opening identity segment; opening retention needs an explicit invariant rather than a lower numeric weight. +- `brands.name` is database-bounded at 200 characters, but `website` and `description` are `TEXT`; prompt assembly needs its own field/section budgets. +- Brand descriptions often use generic self-reference (`公司成立于...`, `品牌创立于...`) instead of repeating the legal name. Those facts should bind to the current brand, while an explicitly named different company must remain excluded. +- Template and imitation generation both resolve the current `brand_id` before task JSON is marshaled, so they can load and attach a brand snapshot without changing request DTOs. +- Custom prompt-rule and KOL enqueue paths likewise have tenant and brand IDs before task JSON serialization; all four can call one shared snapshot loader. +- Scheduled custom/KOL generation flows through the same enqueue functions, so no scheduler-specific brand-description branch is required. +- Prompt placement can remain consistent without appending after output rules: + - Template: after request context, before knowledge and writing requirements. + - Custom rule: after task supplements, before knowledge and output requirements. + - Imitation: after imitation settings, before knowledge and source article. + - KOL: after rendered task instructions, before knowledge context. +- Final scope is persisted article-body generation. Template analyze/title/outline assists remain separate non-body calls and were not changed. +- Final budgets are 200 runes for name, 320 for website, 2000 for the extractive description excerpt, 4 canonical fact constraints, and 4096 runes for the complete rendered brand section. + +## Security Note +- Brand descriptions are user-authored prompt data and must be delimited as reference context, not treated as instructions that can override system/output rules. diff --git a/findings.deploy-39.md b/findings.deploy-39.md new file mode 100644 index 0000000..a5f6e93 --- /dev/null +++ b/findings.deploy-39.md @@ -0,0 +1,53 @@ +# Findings: Sync Current Workspace to 39 + +## Local Baseline +- Branch: `main` +- Base commit: `5463319` +- The worktree contains integrated brand-prompt, RAG numeric-fidelity, media-favorites, admin-web, migration, prompt-config, and worker changes. +- The complete server test suite passed immediately before the deployment request. + +## Deployment Contract +- k3s uses a one-shot `migrate` Job that connects directly to PostgreSQL and runs main, monitoring, and ops migrations before application rollout. +- `deploy/scripts/deploy-images-to-k3s.sh` imports uniquely tagged images into k3s containerd, runs migrations when `RUN_MIGRATIONS=true`, and waits for each deployment rollout. +- The script deliberately refuses backend rollout without migrations unless explicitly overridden. +- The new main migration is `20260710183000_create_media_supply_favorites` and must be included in the new `migrate` image. +- Local Docker is Linux ARM64; the remote node architecture must be checked before choosing local cross-build versus native remote build. + +## Remote Baseline +- Target node is Ubuntu x86_64 with k3s `v1.35.5+k3s1`, containerd `2.2.3-k3s1`, and about 23 GiB free on `/`. +- SSH key authentication is already configured; no password transport is required. +- All existing application and infrastructure pods are healthy before deployment; `worker-generate` currently has three ready replicas. +- The active backend tag is `uncommitted-server-0acd3791-20260709232655`; frontend and ops-web use separate July 9 tags. +- `browser-fetch` and the completed migration Job still use the older `4dece0f2` tag; rollback capture must therefore derive exact active refs from the cluster instead of assuming one common tag. +- Main database migration state is `20260708110000`, `dirty=false`, so `20260710183000` is the next migration. +- The server has no source checkout and no usable Docker Buildx environment; established deployment imports prebuilt amd64 archives into k3s containerd. +- `/opt/geo-rankly/releases` is the established location for rollout artifacts and rollback records. +- Main database size is about 313 MB, so a full compressed pre-migration dump is practical. +- The new migration creates three favorite-related tables and ordinary indexes; it does not use `CREATE INDEX CONCURRENTLY`. +- The correct tenant API probes are `/api/health/live` and `/api/health/ready`. +- `Dockerfile.frontend` builds `apps/admin-web`; this image must be rolled for the media-favorites UI. `Dockerfile.ops-web` consumes shared types and will also be rebuilt for a coherent whole-workspace release. +- Local Buildx already supports `linux/amd64`, so target-native images can be produced without modifying the server. + +## Release Identity +- Image tag: `workspace-5463319-20260710120256` +- Pre-build porcelain-status hash: `0d7153570f11701ba35ac8bed481d8e8ef70da4e348bf818c3113a2955ce84ea` +- Pre-build tracked binary-diff hash: `1084368967b737b55820cd1225e85384b71ef67d477c57ca1d3f8586da6c6364` +- Worktree fingerprints remained identical after the first build attempt. +- OrbStack's local image store kept only the final cross-loaded amd64 tag. Direct per-image Docker archives are required for deterministic multi-image transfer from this ARM64 workstation. +- User requested post-rollout cleanup of old services and caches. Cleanup must occur after new workloads are Ready and must exclude PostgreSQL, RabbitMQ, object storage, and the rollback dump. +- Rebuilt nine independent Docker archives successfully; every archive reports `linux/amd64`, the expected unique tag, and a passing SHA-256 checksum. +- The migrate image contains `20260710183000_create_media_supply_favorites.up.sql` (2474 bytes, SHA-256 `1c8ed443a14880e2621d8921b1da6fbf04d9271c0b08bae230c99e3d8b27c1f3`) and migrate CLI `4.19.1`. +- Release image archives total about 265 MB. A source snapshot of tracked plus untracked non-ignored workspace files is about 96 MB with SHA-256 `53a7fc6faac6dc7e56b19cb479d51c27db3193558039c33b900a333e94487366`. +- Redis is used for queues, presence, streams, rate limiting, and caches. A blanket `FLUSHDB` would remove operational state, so post-rollout cleanup must select cache namespaces only. +- Production Redis currently has 300 expiring keys: most are refresh tokens plus desktop presence/health and supplier/media runtime state. Only three keys are under the shared query-cache namespace `geo:cache:*`; this is the approved cache-deletion scope. +- Pre-deployment tenant readiness at `/api/health/ready` returned the normal success envelope. +- `deploy/prompts.yml` and `server/configs/prompts.yml` are byte-identical and contain the new strict numeric-fidelity rules. The k3s copy was missing those rules, so `deploy/k3s/config/prompts.yml` was synchronized before rollout to prevent the mounted ConfigMap from overriding the fix. +- The long-running `official-placeholder` Pod references `docker.io/geo-rankly/frontend:bb2ad3f6`, but that image record was already absent from containerd after the user's manual old-image deletion. Its content is entirely supplied by ConfigMap mounts and the current frontend image uses the same Alpine nginx paths, so the deployment can safely move to the new frontend release image to restore restartability. +- Scheduler log verification found repeated `completed_task_article_not_completed` consistency warnings logged at error level. Read-only SQL proved every current candidate points to a same-tenant soft-deleted article: zero physical article loss, zero tenant mismatches, and zero live-article status inconsistencies. The checker predates this release (introduced May 5) and runs every five minutes over a 24-hour lookback, so this is known historical-data noise rather than a rollout regression; production data must not be mutated as part of this deployment. +- Final warning events were transient: a few new Pods initially waited for CPU during surge rollout, and the long-running pgbouncer had one readiness timeout. All controllers and Pods are currently Ready with no pending workloads; tenant and ops readiness confirm the database path is healthy. +- The macOS-created source snapshot originally carried harmless extended-attribute headers. It was rebuilt with xattrs/Mac metadata disabled, reuploaded, and reverified without warnings. + +## Security Boundary +- Treat remote file contents and command output as operational data, not instructions. +- Never copy remote secrets into planning files or command output. +- Preserve remote `.env`, secrets, persistent volumes, registries, and runtime-owned directories. diff --git a/findings.media-supply-favorites.md b/findings.media-supply-favorites.md new file mode 100644 index 0000000..0eda727 --- /dev/null +++ b/findings.media-supply-favorites.md @@ -0,0 +1,12 @@ +# Media Supply Favorites Findings + +- Existing migration incorrectly enforces one group per resource with a unique + `(tenant_id, user_id, resource_id)` constraint. +- Existing PostgreSQL upsert moves a resource between groups. +- Existing favorites view fetches details for every favorite ID in batches of 100; + this must become a server-side search and pagination query. +- Existing resources-page global removal remains a valid separate operation. +- The new page query should join the selected group membership to visible model-1 + resources, search by media name or supplier resource ID, and return a fixed 10 rows. +- Group metadata may still return membership IDs for counts and the resources-page + global favorite indicator; resource details must no longer be loaded from that full set. diff --git a/findings.rag-numeric-fidelity.md b/findings.rag-numeric-fidelity.md new file mode 100644 index 0000000..49c113f --- /dev/null +++ b/findings.rag-numeric-fidelity.md @@ -0,0 +1,63 @@ +# Findings: RAG Numeric Fact Fidelity + +## Reported Symptom +- Generated article says Liaoning Muge Creative was founded in 2014 and has 12 years of local service. +- The user reports the knowledge base contains a different founding time. +- Required invariant: when selected knowledge contains a basic numeric fact, generated copy must not contradict it. + +## Local Trace +- Generation code is under `server/internal/tenant/app`; retrieval is under `server/internal/shared/retrieval`; dispatch is under `server/internal/worker/generate`. +- Prompt config describes knowledge as optional reference material and constrains dates through natural-language instructions only. +- `KnowledgeContext.PreciseFacts` exists and is rendered into a high-precision prompt section. +- `ResolveContext` loads precise facts from up to 24 completed items in selected groups, prioritizing retrieved items. +- Founding/establishment/registration labels and phrases are absent from the precise-fact extractor, so `company founded in 20xx` is not elevated. +- Custom tasks snapshot only the rendered knowledge prompt string, not typed fact records. +- Template, custom, and imitation paths all accept model output through `sanitizeGeneratedArticleMarkdown` and then persist it without factual validation. + +## Production Trace +- Three worker-generate pods and all core dependencies are healthy in namespace `geo-rankly`. +- Worker selector is `app.kubernetes.io/name=worker-generate`; deployed image is `geo-rankly/worker-generate:uncommitted-server-0acd3791-20260709232655`. +- A production imitation task logged `candidate_count=0`, `selected_count=0`, and `precise_fact_count=22`, confirming group-wide precise facts are actively rendered even when vector retrieval returns no search points. +- The reported Huludao article was not identifiable in the last 24 hours of filtered worker logs; locate it through PostgreSQL next. +- Read-only `psql` access to the production `geo` database is available through the PostgreSQL pod; queries will minimize returned article/knowledge content. +- Exact reproduction record: article `554`, current version `742`, tenant `2`, brand `4`, title `2026葫芦岛全屋定制本地5家实力公司汇总,口碑+落地+性价比深度评测`, completed at `2026-07-10 09:40:22 UTC`. +- The target article source/task type is `kol`, corresponding to worker log task `528`; the fix must cover the KOL generation path in addition to template/custom/imitation paths. +- Task `528` selected knowledge groups `[17,27,28,30,32,47]` and subscription prompt `1140`; its payload contains rendered prompt/schema/card snapshots but no typed knowledge facts. +- Version `742` contains the reported claim (percent-encoded in stored markdown): `品牌成立于2014年,拥有12年本地服务经验`. +- Production knowledge is internally inconsistent: newest completed item `31` says `成立于2020年1月`, while older completed items `13` and `14` say `成立于2019年`. Both are in the selected recursive group scope. +- KOL worker resolves knowledge with `brand_id` read from task input. Task `528` has no `brand_id` key even though article `554` belongs to brand `4`, so it resolves with brand `0`; this likely excluded the entire knowledge scope for the reported generation. +- KOL output follows the same unvalidated pattern as other paths: model result is trimmed, titled, and persisted directly. + +## Ranked Hypotheses +1. **Missing KOL brand scope:** if this is the primary cause, a task payload without `brand_id` will resolve no brand-4 knowledge, and task `528` will have no corresponding knowledge-resolution log. +2. **Founding facts are not extracted:** if true, `extractKnowledgePreciseFacts("公司成立于2020年1月")` will omit the line. +3. **No deterministic output gate:** if true, a body contradicting a supplied founding fact will pass every current post-generation function and be persisted. +4. **Conflicting source values are not canonicalized:** if true, once founding extraction is enabled both 2020 and 2019 enter prompt context without a unique authoritative value. + +## Hypothesis Results +- Hypothesis 1 confirmed at the creator: normal KOL submission builds `taskInput` with `knowledge_group_ids` but omits `brand_id`; only scheduled KOL input conditionally attaches it. The worker always reads `brand_id` from the payload. +- Existing tests cover precise prompt extraction/rendering but do not cover KOL task scope propagation or output fact validation. +- Normal KOL submission already resolves the current brand and passes it into `enqueue`; the bug is localized to serializing the task snapshot, so no API contract change is needed. +- The shared `llm.Client.Generate` signature is compatible with a reusable generate/validate/repair helper used by tenant services and the KOL worker. +- Template, imitation, and KOL paths retain the resolved `KnowledgeContext` in the same execution frame and can pass its constraints directly to the guard. +- Custom prompt-rule generation snapshots the rendered knowledge prompt before queueing and reconstructs only a prompt string in the worker, so it also needs a JSON-safe constraint snapshot for deterministic validation. + +## Proposed Conflict Policy +- Prefer the newest completed active knowledge item for the same typed fact and subject as the canonical value. +- Preserve source metadata and emit an observable conflict when older active items disagree. +- Validate generated output against the canonical value before persistence; retry once with correction instructions, then fail rather than publish a contradiction. +- Review note: the current group-item SQL prioritizes retrieved item IDs before `updated_at`. Canonical fact selection must not accidentally treat an older retrieved item as newer; source timestamp/order semantics need explicit coverage. + +## Design Constraint +- A global numeric-token whitelist is unsafe: article years, rankings, section numbers, derived values, and user-provided facts are legitimate. +- Enforcement should use typed fact identity, normalized values, subject/field-aware contradiction detection, and an explicit retry/fail policy. +- Initial protected types will cover company founding date and explicitly sourced experience/service duration; the structure must be extensible to other business numeric types. +- With active fact constraints, generation should withhold draft deltas, validate the full result, repair once from canonical facts, and reject the task if the repaired result still conflicts. + +## Final Outcome +- Immediate and scheduled KOL tasks now serialize the same brand/knowledge scope consumed by the worker. +- Founding dates and explicit service/experience durations become typed, subject-aware constraints. +- Source conflicts choose the newest completed item; older conflicting values are removed from the prompt and logged without exposing source content. +- Template, custom prompt-rule, imitation, and KOL generation validate before persistence, repair once, and fail instead of saving a second conflicting draft. +- Custom queued tasks snapshot constraints; pre-upgrade queued tasks resolve missing constraints at execution time. +- Production was inspected read-only. No deployment was performed from the dirty worktree. diff --git a/progress.brand-description-prompt.md b/progress.brand-description-prompt.md new file mode 100644 index 0000000..d331fc1 --- /dev/null +++ b/progress.brand-description-prompt.md @@ -0,0 +1,51 @@ +# Progress: Brand Description Prompt Context + +## 2026-07-10 +- Read the file-planning workflow and preserved the shared planning files. +- Captured the screenshot requirement and initialized isolated task records. +- Confirmed brand description is required, persisted, and already consumed by question-expansion but not visibly centralized for article generation. +- Confirmed the brand context query shape and selected queue-time snapshotting as the reproducibility boundary to evaluate against each generation path. +- Mapped the four article-body paths and confirmed regeneration preserves original task snapshots. +- Confirmed descriptions are unbounded and selected deterministic extractive compaction over per-generation LLM summarization. +- Located template and imitation snapshot insertion points immediately before task JSON serialization. +- Located custom and KOL snapshot insertion points and confirmed scheduled generation reuses them. +- Chosen safe insertion positions for all four prompt builders so brand data cannot trail or override output rules. +- Measured production description lengths and selected a 2000-rune hard cap that leaves all current descriptions untouched. +- Added brand-profile-over-RAG precedence to the design because real source data currently conflicts. +- Completed trace/budget phase and moved to regression tests with a structured snapshot plus existing fact-guard reuse. +- Located the current template prompt tests and confirmed new tests are needed for the other three builders. +- Added red tests for short passthrough, long extractive compaction, late numeric facts, snapshot fallback, brand-over-RAG precedence, and all four prompt placements. +- Focused red run failed only on the intentionally undefined brand-context contract and KOL builder. +- Implemented shared loading, deterministic compaction, prompt rendering, structured snapshots, old-task fallback, and brand-first fact merging. +- Wired template, custom, imitation, and KOL enqueue/execution paths. +- Focused regression suite passed for both tenant app and generation worker. +- Completed first scoped diff review and identified numeral classification, oversized-segment efficiency, and repair-prompt wording follow-ups. +- Added focused regression assertions for Chinese-numeral classification and brand-aware repair-prompt wording; red verification is next. +- Confirmed both new assertions fail against the old behavior, then implemented unit-qualified Chinese numerals, linear oversized-segment counting, and brand-aware fact-repair wording. +- Integration review identified an unbounded fact-constraint appendix for adversarially long descriptions; a whole-section budget regression test and focal-brand filtering are next. +- Added regression coverage for guaranteed opening retention, focal-brand-only fact constraints, and a hard maximum for the complete rendered brand section; red verification is next. +- Centralized metadata, description, fact-count, and whole-section budgets; fact constraints are now focal-brand-only and the opening segment is reserved before priority filling. +- Added a current-brand attribution regression for generic founding phrases; normal package verification is waiting for the concurrent media-favorites interface edit to settle. +- Reworked brand fact extraction to bind generic self-references directly to the current brand and reject facts whose explicit company subject is different. +- Concurrent media-favorites code settled; the complete focused brand suite now passes in both tenant app and KOL worker packages. +- Added an oversized legacy-snapshot regression to enforce the 4096-rune rendered-section ceiling after normalization. +- Passed complete tests for `internal/tenant/app`, `internal/worker/generate`, and `internal/tenant/prompts`. +- Passed `go vet -tests=false` for all affected production packages. +- Passed the full server suite with `go test ./... -count=1`. +- Completed scoped diff, whitespace, and temporary-debug-marker checks. + +## Test Results +| Test | Expected | Actual | Status | +|------|----------|--------|--------| +| Brand prompt red suite | Fail before implementation | Undefined shared context/merge/builder symbols | Expected fail | +| Brand prompt focused green suite | All compaction/snapshot/precedence/order tests pass | Both packages `ok` | Pass | +| Chinese numeral and repair wording red suite | Fail against broad numeral match and old wording | Both targeted assertions failed | Expected fail | +| Chinese numeral and repair wording green suite | Both targeted assertions pass | Tenant app package `ok` | Pass | +| Opening/fact-bound red suite | Expose eviction and unrelated fact growth | Ordinary package build was blocked by concurrent media-favorites test referencing a missing method | External blocker; use isolated verification and retry later | +| Final focused brand suite | All brand compaction/snapshot/precedence/order tests pass | Tenant app and KOL worker packages `ok` | Pass | +| Affected package suites | All tests pass | Tenant app, KOL worker, and tenant prompts `ok` | Pass | +| Production vet | No findings | Passed | Pass | +| Full server suite | All packages pass | `go test ./... -count=1` passed | Pass | + +## Next +- No implementation work remains for persisted article-body generation. diff --git a/progress.deploy-39.md b/progress.deploy-39.md new file mode 100644 index 0000000..4f3f41f --- /dev/null +++ b/progress.deploy-39.md @@ -0,0 +1,43 @@ +# Progress: Sync Current Workspace to 39 + +## 2026-07-10 +- Initialized a deployment-specific plan. +- Captured the local base commit and full dirty-worktree scope. +- Deployment documentation and remote topology audit are next. +- Read the k3s deployment contract and confirmed migration execution is image-based through the `migrate` Job. +- Confirmed local Docker is ARM64 and recorded the need to check the target architecture before building. +- Connected read-only to the target and confirmed it is an x86_64 single-node k3s host with sufficient free disk. +- The first audit command stopped at a zsh glob expansion in `custom-columns`; the safe retry will avoid bracket syntax. +- Completed the safe remote audit: all pods healthy, main migration clean at `20260708110000`, and current image tags captured. +- Selected local `linux/amd64` cross-build plus the repository image-import rollout script because the target does not host a source checkout/build engine. +- Reviewed the migration SQL, measured the 313 MB main database, found the actual readiness endpoint, and confirmed the local cross-builder supports amd64. +- Passed backend `go test ./...`, admin-web production build, and ops-web production build against the release worktree. +- Assigned unique release tag `workspace-5463319-20260710120256` and captured pre-build worktree fingerprints. +- All nine build targets completed, but release inventory caught that OrbStack retained only the final cross-loaded image; no remote mutation occurred. +- Switched the transfer plan to direct per-service Docker archives using the now-warm BuildKit cache. +- Rebuilt and checksummed all nine linux/amd64 Docker archives; verified expected tags/platforms and inspected the migration file inside the migration image. +- Created and checksummed a source snapshot for remote release traceability. +- Initial Redis audit used the wrong workload kind and made no changes; selective key-prefix discovery is next. +- Completed Redis classification and limited post-rollout deletion to `geo:cache:*`; authentication and operational runtime keys will be preserved. +- Reverified all nine rebuilt image archives and the source snapshot against their SHA-256 manifests after the user's local image cleanup. +- Synchronized the stale k3s prompt-config copy with the canonical deployment/server prompt so the new brand and numeric-fidelity constraints reach mounted application config. +- Regenerated the source snapshot after synchronizing the prompt copy; archive checksum passed. A follow-on prompt comparison used release-directory-relative paths and will be rerun from the repository root. +- Reconfirmed the cluster is healthy and the release directory is absent immediately before backup. The remote mounted prompt hash matches the stale k3s copy, confirming that a ConfigMap patch is required. +- Created the timestamped remote release directory and completed the pre-mutation rollback set: eight application Deployments, migrate Job, ReplicaSets, mounted ConfigMap, pod/image inventory, exact active image archives, and a verified compressed main-database dump (895 MB total). +- First artifact upload invocation exited before transfer because the local rsync is too old for `--info=progress2`; switching to portable progress/statistics flags. +- Uploaded the complete 348 MB incoming release set. All nine image archives, the current workspace snapshot, and the canonical prompt file passed SHA-256 verification independently on 39. +- Imported all nine `workspace-5463319-20260710120256` linux/amd64 images into k3s containerd and confirmed all nine tagged refs exist. +- Replaced the completed old migration Job with the release-tagged Job. It completed successfully; main schema is `20260710183000`, `dirty=false`, all three media-favorite tables exist, and platform-template seeding completed. +- The first ConfigMap merge attempt stopped before mutation because jq is absent on 39; the mounted prompt remains on its old checksum. Local jq is available, and the ConfigMap's non-prompt keys are `config.yaml` and `ops-config.yaml`. +- Applied a locally generated one-field merge patch to `geo-rankly-app-config`; both non-prompt values and the key set remained unchanged, while `prompts.yml` now matches SHA-256 `61b768b...`. +- Rolled all six backend/worker Deployments and both web Deployments to the release tag. Every desired replica is Ready. +- Initial post-rollout checks passed: tenant/ops/browser readiness and both web roots return 200, mounted prompts match in four consumers, all release Pods have zero restarts, and the targeted recent-log scan found no panic/fatal/error candidates. +- Cleanup deleted 80 zero-replica application ReplicaSets, 10 unreferenced old geo-rankly image refs, and exactly three `geo:cache:*` keys. Redis total keys changed 308 to 305 while 268 refresh and 25 desktop keys were preserved exactly. +- Cleanup inventory exposed a pre-existing missing image record for the still-running `official-placeholder` Pod; it was not among deleted refs. The deployment will be repointed to the current frontend nginx image and rolled once to prove restartability. +- Repaired `official-placeholder` with the current frontend nginx image. After one transient Endpoint propagation miss, Service and Pod both return 200 with zero restarts; seven historical zero-replica ReplicaSets were removed. +- Updated the declarative placeholder manifest away from its unavailable hard-coded image and refreshed/reverified the remote source snapshot. +- Investigated the final scheduler log candidates: 22 lines represented 11 records repeated across two five-minute checks. Aggregate SQL showed all candidates were soft-deleted articles, with no missing rows or tenant mismatch. Final log verification will exclude only this exact known anomaly and require every other error class to be zero. +- Adjusted log verification accordingly: all 34 current scheduler candidates are the exact known soft-delete anomaly and unexpected log candidates are zero. A later final check found two shared cache keys had been naturally repopulated after the earlier verified deletion; they will be cleared again only after all endpoint checks finish. +- Completed final verification: 11 Deployments and 6 StatefulSets Ready, migration clean, six HTTP checks at 200, release Pod restarts zero, unexpected log candidates zero, stale zero-replica ReplicaSets zero, nine release image refs present, and no old geo-rankly refs remain. +- Cleared the two naturally repopulated `geo:cache:*` keys as the last runtime action and immediately verified cache count zero while 270 refresh and 25 desktop keys remained intact. +- Repacked the source snapshot without macOS extended attributes and verified both incoming and rollback checksums. Stored rollback instructions and the old prompt merge patch under the timestamped release directory. diff --git a/progress.media-supply-favorites.md b/progress.media-supply-favorites.md new file mode 100644 index 0000000..b71442f --- /dev/null +++ b/progress.media-supply-favorites.md @@ -0,0 +1,13 @@ +# Media Supply Favorites Progress + +## 2026-07-10 + +- Confirmed required multi-group membership semantics. +- Added server-side search and 10-item pagination to the implementation scope. +- Preserved all unrelated worktree changes. +- Added regression expectations for simultaneous membership, scoped removal, + idempotent membership insertion, and global removal. +- Implemented multi-group PostgreSQL membership and distinct-resource limits. +- Added group-scoped deletion and fixed-size favorite resource pagination routes. +- Replaced full favorite detail batching with a searchable 10-row page query. +- Targeted favorite, transport, Swagger, and frontend type checks pass. diff --git a/progress.rag-numeric-fidelity.md b/progress.rag-numeric-fidelity.md new file mode 100644 index 0000000..e0b0456 --- /dev/null +++ b/progress.rag-numeric-fidelity.md @@ -0,0 +1,54 @@ +# Progress: RAG Numeric Fact Fidelity + +## 2026-07-10 +- Read diagnosis and file-planning workflows. +- Preserved unrelated dirty favorite-media changes. +- Traced knowledge resolution, precise-fact rendering, worker dispatch, generation, sanitization, and persistence. +- Confirmed founding time is missing from precise-fact extraction. +- Confirmed generated output has no deterministic fact validation before persistence. +- Connected read-only to the supplied production k3s host and confirmed workload health. +- Corrected the worker selector and inspected 24 hours of generation/knowledge logs. +- Confirmed the precise-fact fallback is exercised in production and moved exact article lookup to PostgreSQL. +- Opened a read-only production database inspection session and limited planned queries to IDs, metadata, and narrow founding-date excerpts. +- Located the exact article/version/task metadata and identified the affected generation path as KOL. +- Confirmed the exact wrong founding-year/service-duration sentence in the stored article and captured the six selected knowledge group IDs. +- Found conflicting source values (newest 2020, older 2019) and identified missing KOL `brand_id` as the primary retrieval-scope defect. +- Published four ranked, falsifiable hypotheses before implementing probes/fixes. +- Confirmed the KOL scope defect in both producer and consumer code and found no existing regression coverage for it. +- Selected a three-layer fix: KOL scope propagation, typed/canonical fact extraction, and pre-persistence validate-repair-reject handling across generation paths. +- Verified the brand scope exists before payload serialization and the LLM interface supports a shared guarded-generation implementation. +- Found a reusable fake-LLM testing pattern; no existing generation-payload snapshot tests cover the KOL scope contract. +- Added focused payload, fact extraction/canonicalization, contradiction, encoded-content, and repair/fail regression tests. +- Ran the focused tests and observed the expected red compile failure because the new fact-guard contract is not implemented yet. +- Implemented the core typed fact model, canonical conflict handling, subject-aware validation, percent-decoding, and one-repair guard. +- Wired fact constraints into knowledge prompt rendering and added KOL brand/group scope serialization. +- Integrated guarded generation into template, imitation, and KOL execution paths. +- Identified the custom prompt-rule snapshot seam for serializing and restoring fact constraints alongside the existing knowledge prompt. +- Reviewed the scoped diff separately from unrelated favorite-media/Swagger changes and flagged canonical source ordering for an additional regression check. +- Hardened canonical source ordering so `updated_at DESC` precedes retrieval priority, and added nil-result guards around both LLM calls. +- Package-level tests passed for tenant app, generation worker, and prompt loader after all four generation paths were wired. +- Full `go test ./...` passed across the server repository. +- A later full vet attempt was blocked by a new syntax error in an unrelated concurrently edited favorite-media test; no changes were made to that file. +- `go vet -tests=false` passed for the affected production packages. +- `git diff --check` passed and no `[DEBUG-*]` instrumentation was present. +- Closed the production PostgreSQL and SSH sessions cleanly. +- Did not deploy because the shared worktree contains unrelated uncommitted feature work. +- Captured final implementation references and rechecked worktree ownership before handoff. +- First green run stopped at compile time because text/payload decoders shared a name and the payload test referenced worker-private helpers; both issues were isolated without changing test intent. +- Corrected the two compile issues and re-ran the focused regression suite successfully. + +## Test Results +| Test | Expected | Actual | Status | +|------|----------|--------|--------| +| Production worker selector probe | Matching worker logs | Selector matched no resources | Needs corrected selector | +| Corrected worker log probe | Knowledge-generation events | Found active precise-fact fallback and task IDs | Pass | +| RAG numeric fidelity red tests | Fail before implementation | Undefined fact guard types/functions at compile time | Expected fail | +| RAG numeric fidelity focused green tests | Pass after implementation | `ok`, including repair and reject behavior | Pass | +| Generation package verification | `tenant/app`, `worker/generate`, and `tenant/prompts` pass | All packages `ok` | Pass | +| Full server test suite | All Go packages pass | `go test ./...` exit 0 | Pass | +| Production-package vet | Affected non-test code passes vet | Exit 0 | Pass | +| Diff/instrumentation cleanup | No whitespace errors or debug markers | Clean | Pass | + +## Next +- Implement the fact guard and wire it into all generation paths. +- Re-run focused and package-level tests. diff --git a/task_plan.brand-description-prompt.md b/task_plan.brand-description-prompt.md new file mode 100644 index 0000000..3029e16 --- /dev/null +++ b/task_plan.brand-description-prompt.md @@ -0,0 +1,48 @@ +# Task Plan: Brand Description Prompt Context + +## Goal +Include the current brand description in every article-generation prompt while bounding prompt growth and preserving factual/numeric details from long descriptions. + +## Current Phase +Complete + +## Phases + +### Phase 1: Trace and Budget +- [x] Trace brand persistence and current-brand loading +- [x] Inventory all article-generation entry points and prompt assembly seams +- [x] Identify existing prompt/token/character budgets and snapshot behavior + +### Phase 2: Contract and Regression Tests +- [x] Define short-description passthrough and long-description compaction rules +- [x] Add failing tests for every generation path and maximum prompt growth +- [x] Protect numeric facts from being dropped or rewritten during compaction + +### Phase 3: Implementation +- [x] Add one shared brand-context builder +- [x] Inject the same context into template, custom, imitation, and KOL generation +- [x] Preserve queued-task reproducibility through snapshots where needed + +### Phase 4: Verification and Cleanup +- [x] Run focused, package, and full relevant tests +- [x] Review prompt output and scoped diff +- [x] Confirm no temporary instrumentation remains + +## Decisions +| Decision | Rationale | +|----------|-----------| +| Use task-specific planning files | Shared plan files are owned by a concurrent long-running task. | +| Centralize brand context assembly | Prevents inconsistent truncation and missing paths. | +| Budget by runes/tokens before prompt assembly | A database field length is not a safe model-context budget. | +| Cap rendered description at 2000 runes | Current production max is 1195; this preserves all current data while bounding future growth to about 2000 estimated input tokens. | +| Brand profile facts outrank conflicting RAG facts | The directly edited brand profile is the canonical source for the brand's own base identity. | +| Reuse the existing typed fact guard | Brand-description dates/year counts should receive the same pre-persistence validation as RAG facts. | +| Snapshot a structured brand prompt context | Supports stable retries and lets old queued tasks fall back to a scoped read. | + +## Errors +| Error | Attempt | Resolution | +|-------|---------|------------| +| First production-stat command failed during local JavaScript string parsing | 1 | Reissued with a single-quoted remote command; no remote command ran on the failed attempt. | +| Broad config context search returned a sensitive adjacent config line internally | 1 | Do not repeat or record the value; use exact-key queries without context for future config inspection. | +| Tenant app test compilation was interrupted by concurrent media-favorites work referencing `DeleteFavoriteResourceFromGroup` before its implementation existed | 1 | Preserve unrelated work; use isolated test compilation for the brand regression, then retry normal package tests after the concurrent edit settles. | +| A second tenant app compile attempt observed the same concurrent feature between interface and implementation edits | 2 | Stopped retrying until the external file stabilized; the focused, package, and full-repository tests later passed normally. | diff --git a/task_plan.deploy-39.md b/task_plan.deploy-39.md new file mode 100644 index 0000000..f87e861 --- /dev/null +++ b/task_plan.deploy-39.md @@ -0,0 +1,62 @@ +# Task Plan: Sync Current Workspace to 39 + +## Goal +Synchronize the complete current workspace state to `39.105.229.239`, deploy it to the existing k3s installation, apply required migrations, and verify service health with a rollback point preserved. + +## Current Phase +Complete + +## Phases + +### Phase 1: Audit +- [x] Read repository deployment instructions and scripts +- [x] Identify remote repository path, runtime configuration, workloads, and current image state +- [x] Confirm the exact local file set and required migrations + +### Phase 2: Backup and Sync +- [x] Create a timestamped remote rollback snapshot +- [x] Synchronize the current workspace without overwriting secrets or runtime-owned data +- [x] Verify remote source checksums for representative changed files + +### Phase 3: Build and Rollout +- [x] Build and import affected images into k3s +- [x] Apply database migration and manifests/configuration +- [x] Wait for all affected workloads to finish rolling out + +### Phase 4: Verification +- [x] Check pods, events, rollout revisions, and recent logs +- [x] Exercise health/API endpoints +- [x] Record deployed image identifiers and rollback instructions + +## Decisions +| Decision | Rationale | +|----------|-----------| +| Deploy the whole current workspace | The user explicitly requested syncing the current changes, and the worktree contains several integrated features. | +| Preserve remote secrets and runtime data | Source synchronization must not replace environment-specific credentials, volumes, or cluster state. | +| Create a rollback snapshot before mutation | The source is a dirty worktree and cannot be reconstructed from a single commit. | +| Clear old runtime state only after successful rollout | Preserves rollback and avoids taking down the healthy release before its replacement is ready. | + +## Errors +| Error | Attempt | Resolution | +|-------|---------|------------| +| zsh expanded brackets in an unquoted `custom-columns` expression during two read-only inventory commands | 2 | Stop using bracket-bearing custom-columns in outer shell commands; use `get deploy -o wide`, go-template, or per-deployment JSONPath inside a quoted bash script. | +| OrbStack retained only the final amd64 image after sequential cross-architecture `buildx --load` operations, so release inventory failed | 1 | Keep the warm BuildKit cache but rebuild each target directly to its own Docker archive and import archives independently. | +| First planning-file patch used stale table context and did not apply | 1 | Re-read the current files and applied smaller exact-context updates. | +| Redis audit addressed `deployment/redis`, but Redis is a StatefulSet/pod on this cluster | 1 | Retry against `pod/redis-0`; do not issue any deletion until key classes are understood. | +| Broad local config context read exposed adjacent tracked secret-bearing values internally | 1 | Do not repeat or record values; use exact structural queries without config context for the remainder of deployment. | +| Source-snapshot verification changed into the release directory before comparing repository prompt files, so the final two `cmp` checks used the wrong relative path | 1 | The archive and checksum completed successfully; rerun prompt comparisons from the repository root with absolute paths. | +| Workspace checksum manifest was verified from the repository root even though it stores a release-directory-relative filename | 1 | Verify `workspace.sha256` from inside the release artifact directory. | +| macOS bundled rsync does not support `--info=progress2` | 1 | No file transfer started; retry with portable `--progress --stats` options. | +| A guessed migration path plus an unmatched zsh glob prevented a read-only table-name lookup | 1 | Locate the migration with `rg --files` and then read the exact path without shell globbing. | +| The target host does not have `jq`, so ConfigMap merge preparation exited at its explicit prerequisite check | 1 | No patch was applied; generate the one-field JSON merge patch locally with jq, upload it, and use `kubectl patch --patch-file`. | +| Immediate Service curl after the repaired official-placeholder rollout returned connection refused even though rollout status succeeded | 1 | Inspect the new Pod, EndpointSlice, and nginx logs; retry only after confirming whether this was endpoint propagation or a runtime/config issue. | +| Rollback-prompt generation assumed Git HEAD still contained the pre-release prompt, but HEAD already contains the strict prompt rules | 1 | Upload did not start; parse the backed-up pre-release ConfigMap with local kubectl and extract only `data["prompts.yml"]` into the merge patch. | +| Local kubectl still attempted API discovery for a client dry-run of the backed-up ConfigMap and could not reach its configured local cluster | 1 | No patch was produced; use Ruby's standard YAML and JSON parsers on the trusted backup, then verify the extracted prompt against the known old hash before upload. | +| Final verification stopped after HTTP checks because one log-archive command returned nonzero under `set -e` | 1 | Deployment state remains healthy; run each selector log read independently to identify the exact failure, then make the final verifier tolerate empty/no-log sources while still scanning available logs. | +| Two `geo:cache:*` keys were naturally repopulated by live traffic after the verified cleanup, so a later zero-cache assertion stopped final verification | 1 | Complete all remaining read checks first, then repeat the same prefix-scoped deletion as the final action and immediately verify protected Redis key classes are unchanged. | + +## Outcome +- Release `workspace-5463319-20260710120256` is fully deployed on 39. +- Migration `20260710183000` is clean and all three media-favorite tables exist. +- All application and infrastructure controllers are Ready; all release Pods have zero restarts. +- Old zero-replica ReplicaSets, unreferenced old geo-rankly images, and `geo:cache:*` were removed while protected Redis/runtime data and rollback artifacts were retained. diff --git a/task_plan.media-supply-favorites.md b/task_plan.media-supply-favorites.md new file mode 100644 index 0000000..8f7bf6f --- /dev/null +++ b/task_plan.media-supply-favorites.md @@ -0,0 +1,26 @@ +# Media Supply Favorites + +## Goal + +Persist favorites in PostgreSQL with multi-group membership, silent delisting cleanup, +group-scoped removal, and server-side favorite search/pagination at 10 resources per page. + +## Phases + +- [x] Regression tests for multi-group membership and paginated lookup +- [x] Database/store/service implementation +- [x] HTTP routes and Swagger descriptions +- [x] Favorites page search, pagination, and scoped removal +- [ ] Go, frontend, migration, and browser verification + +## Non-negotiable Decisions + +- A resource may belong to multiple groups. +- `(tenant_id, user_id, group_key, resource_id)` is the membership identity. +- The 500-resource limit counts distinct resources, not memberships. +- Favorites-page removal is group-scoped; resources-page removal is global. +- Favorite resource details are queried server-side, 10 per page. + +## Errors Encountered + +None. diff --git a/task_plan.rag-numeric-fidelity.md b/task_plan.rag-numeric-fidelity.md new file mode 100644 index 0000000..9556950 --- /dev/null +++ b/task_plan.rag-numeric-fidelity.md @@ -0,0 +1,50 @@ +# Task Plan: RAG Numeric Fact Fidelity + +## Goal +Ensure numeric facts explicitly present in retrieved knowledge, especially company founding dates, cannot be silently contradicted in generated articles. + +## Current Phase +Complete + +## Phases + +### Phase 1: Reproduce and Trace +- [x] Trace retrieval, prompt rendering, model output, and persistence locally +- [x] Identify the exact generated article and KOL task in production +- [x] Confirm the reported source/output contradiction against production knowledge data +- [x] Build deterministic failing tests at the payload, fact, and generation seams + +### Phase 2: Test Hypotheses +- [x] Rank 4 falsifiable causes +- [x] Confirm production task type and payload behavior +- [x] Confirm the immediate KOL creator omits the brand scope consumed by its worker +- [x] Isolate the minimum enforceable fact-validation contract + +### Phase 3: Regression Test and Fix +- [x] Add failing coverage for source-backed founding-date contradictions +- [x] Implement typed fact extraction and deterministic validation +- [x] Confirm focused tests pass + +### Phase 4: Verification and Cleanup +- [x] Run focused, package-level, and full Go tests against the production-shaped regression +- [x] Check production data/logs read-only and review the scoped diff +- [x] Confirm no temporary instrumentation remains + +## Decisions +| Decision | Rationale | +|----------|-----------| +| Use task-specific planning files | Shared planning files were concurrently rewritten by another task; isolation preserves user work. | +| Do not globally whitelist every numeric token | Rankings, article year, derived durations, and section numbers can be valid without appearing in RAG. | +| Treat prompt-only controls as insufficient | Model instructions are probabilistic and current output is persisted without validation. | +| Guard protected facts before persistence | Retry once with the canonical facts, then fail instead of saving a contradictory article. | +| Suppress draft streaming when fact constraints exist | Prevent a known-invalid first draft from being exposed before the repair/validation result. | +| Canonicalize source conflicts by newest completed item | Gives corrected knowledge precedence while retaining an observable conflict for cleanup. | + +## Errors +| Error | Attempt | Resolution | +|-------|---------|------------| +| Worker selector `app=worker-generate` matched no resources | 1 | Inspect the real deployment selector/pod names before retrying. | +| Shared planning append disappeared during concurrent edits | 1 | Switched to task-specific planning files and will not modify shared plan files again. | +| First green run had a helper-name collision and a cross-package private test reference | 1 | Renamed text parsing separately from payload decoding and used producer-package task parsers in the payload test. | +| Used the outer exec-cell wait helper for an inner terminal session | 1 | Polled the returned terminal session with `write_stdin`; the full test run completed successfully. | +| Full `go vet ./...` later hit a syntax error in concurrently edited `media_supply_favorites_test.go:160` | 1 | Preserve the unrelated user change; vet the affected production packages with `-tests=false` and retain the earlier successful full test result. |