feat: versioned artifact DAG reconciliation for #1679, with tombstone fix and artifact retention - #47
Draft
lyingbug wants to merge 12 commits into
Draft
feat: versioned artifact DAG reconciliation for #1679, with tombstone fix and artifact retention#47lyingbug wants to merge 12 commits into
lyingbug wants to merge 12 commits into
Conversation
… RAG wait row The wait row only appeared once a completed knowledge_search step existed, so attachment-only turns (attachment_parsing / image_analysis with no KB hit) still showed nothing between the last step and the first answer token. Model waiting is now a three-state kind: 'model' after retrieval finished, the neutral 'preparing' row otherwise. A dropped SSE connection never sets is_completed (the stream layer only raises a toast), so the row used to promise an answer forever. It now stops claiming progress after RAG_WAIT_STALL_DELAY_MS and drops the shimmer. Also share RAG_RETRIEVAL_TOOL_NAMES instead of repeating the knowledge_search / search_knowledge alias check, name the reveal delay, and move the reveal/stall timers into a controller with an injectable scheduler so the timing is covered by tests instead of by source-matching alone. Co-authored-by: lyingbug <lyingbug@users.noreply.github.com>
role="status" lived on the wait row itself, so the live region was inserted together with its own text and screen readers had nothing to announce. Move the announcement to an sr-only region that stays mounted for the whole turn, which also covers the pre-pipeline wait row that had no announcement at all. Co-authored-by: lyingbug <lyingbug@users.noreply.github.com>
Resolve knowledge_process.go conflicts by keeping the attempt-conditional status publish from the reconciliation work while adopting markKnowledgeProcessing so stale error_message is still cleared. Co-authored-by: lyingbug <lyingbug@users.noreply.github.com>
Chunk IDs are content-addressed, so a paragraph that is removed in one reparse and restored in a later one resolves to the same primary key. Stale cleanup only soft-deletes, so that key is still held by a row the reconciliation read path cannot see, and the restore fails with a duplicate key error. Add PurgeSoftDeletedChunks, which hard-deletes only tombstoned rows among an explicit ID list, and call it from processChunks before inserting the added chunks. Live rows are never touched, so a concurrent attempt cannot lose data. Co-authored-by: lyingbug <lyingbug@users.noreply.github.com>
The artifact table carries last_hit_at and a (tenant_id, created_at) index but nothing ever reclaimed rows, so a cache holding parse output and embedding payloads grew without bound. Add a sweep that drops entries untouched for the retention window (30 days by default, retunable via WEKNORA_ARTIFACT_RETENTION_DAYS, 0 to disable). Work per tick is bounded so a first run against a neglected table cannot hold locks for minutes, and offloaded payloads are skipped so their objects cannot be orphaned. Co-authored-by: lyingbug <lyingbug@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes Tencent#1679.
This takes PR #2360 by @Even1115 as the base — of all the submissions for this issue it is the only one whose migrations load cleanly both on its own head and after merging
main, so the service actually starts — and adds the two things it was missing.Base: what Tencent#2360 provides
Reparse stops being a delete-and-rebuild and becomes a reconciliation against a desired state.
internal/artifact/key.godefines a versioned canonical key envelope:KeyVersion,Stage, orderedDirectInputs(upstream artifact digests, not upstream configuration),Processoridentity, the rendered request, and the output schema version. Changing a VLM prompt therefore invalidates the VLM layer without cascading into layers whose inputs did not move.RejectSecretFieldsfails closed rather than silently dropping credential fields.internal/artifact/identity.goanchors chunk identity on the content digest with aDuplicateOrdinalfor repeated text, so inserting a paragraph does not shift the IDs of everything after it.knowledge_reconcile.gocomputes Added / Updated / Stale, applies additions and updates before cleanup, andpreserveLiveChunkFieldskeeps user-owned state (enabled flag, tags, metadata, relations) across a rebuild.000079, SQLite000002, plus the MySQL and ParadeDB bootstrap schemas.Added here
1. Merge
mainand resolve the conflict correctly.mainlandedmarkKnowledgeProcessing, which clears a staleerror_messagewhen a run starts; Tencent#2360 replaced the same write with an attempt-conditional publish so a delayed worker cannot overwrite a newer attempt. Both are kept: the status write clearserror_messageand goes throughUpdateKnowledgeIfAttemptCurrent.2. Reclaim tombstoned chunk IDs. This was the one functional defect in the base. Because chunk IDs are content-addressed, a paragraph removed in one reparse and restored in a later one resolves to the same primary key. Stale cleanup soft-deletes, so that key is still held by a row the reconciliation read path cannot see, and the restore fails:
PurgeSoftDeletedChunkshard-deletes only tombstoned rows among an explicit ID list, andprocessChunkscalls it before insertingdesired.Added. The predicate is tenant + explicit IDs +deleted_at IS NOT NULL, so a live row owned by a concurrent attempt can never be dropped.3. Prune cold processing artifacts.
processing_artifactscarrieslast_hit_atand a(tenant_id, created_at)index, but nothing ever reclaimed rows — a cache holding parse output and embedding payloads grew without bound. A new housekeeping sweep drops entries untouched for the retention window (30 days by default, retunable withWEKNORA_ARTIFACT_RETENTION_DAYS,0disables). Work per tick is bounded so a first run against a neglected table cannot hold locks for minutes, and offloaded payloads are skipped so their objects cannot be orphaned.Verification
go build ./...,go vet ./internal/...andgo test ./internal/...all pass — 65 packages, no failures.New regression coverage:
TestPurgeSoftDeletedChunksAllowsStableIDReinsert— the restore path that previously failed.TestPurgeSoftDeletedChunksLeavesLiveRowsIntact/...IsTenantScoped— the purge cannot reach live rows or another tenant.TestBuildDesiredDocumentChunksRestoresRemovedContentUnderSameID— proves at the reconciliation layer that a restored paragraph really does come back under the tombstoned key.Follow-ups worth separate PRs
planChunkReusefrom PR #2035 skips the vector write entirely for chunks that are already indexed with an unchanged embedding fingerprint. This branch still rewrites vectors; that change is a few hundred lines and is easier to review on its own.StableIdentitycolumn from PR #2467 decouples identity from the row primary key, which makes the tombstone class of problem structurally impossible rather than handled.image_ocr,image_caption) still use random UUIDs, so their vectors churn on every rebuild even though the VLM text itself is cached.