#0584 record extraction provenance so content-free docs are not indexed as success - #79
Conversation
…success A document whose primary-content extractor failed (vision describe timeout, transcription failure, PDF page OCR failure) landed in the index looking exactly like a fully-extracted document. The only trace was an aggregate WARNING in the flow log, so a search consumer — or a health check reading outcome state — saw a plain success. Worse, the EXIF/dimensions header made `full_text` non-empty, so enrichment still ran and turned the *file* into content topics: production emitted `topics=image, photo, MPO format` for tenant maintenance photos nobody had ever looked at, and a different label set on nearly every pass. Recorded at the existing `ExtractionResult` seam rather than per extractor: - `ExtractionResult.primary_content` marks whether the content path actually yielded anything, so the EXIF header is no longer mistaken for content. `content_status()` classifies each doc `complete` / `partial` / `missing` from that plus the document's extraction degradations — one path for OCR, describe, transcription and PDF text, no per-extractor special-casing. - The indexing pipeline writes `content_status` and `content_failure_reasons` onto every chunk, so the failure is legible in the record a consumer reads back, and slim search hits surface a non-`complete` status. - LLM enrichment is skipped when primary content is missing, which removes the fabricated filler topics and a per-run LLM call for a doc that cannot be enriched. Two runs over an unchanged failed-describe file now produce identical metadata. - Enrichment labels are canonicalized (`img` -> `image`) and deduplicated case-insensitively at the normalization boundary, so identical input maps to identical labels and the taxonomy stops accumulating variants. - Successful incomplete writes feed a `docs_indexed_incomplete` count in the run summary and `index_metadata.json`. Regression coverage: an integration test drives the real image extractor, a timing-out vision provider and a real LanceDB upsert, then asserts on the read-back chunk and re-indexes the unchanged file to prove stability; a staging-e2e test arms the provider sim's describe fault and asserts the same through the public MCP boundary. Refs: Maint-Manager #0584 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Nightly reconciliation for 2026-07-31. Each defect was re-derived independently against 7-10 days of production evidence BEFORE reading its fix; each PR's regression tests were run against origin/main (RED) and this branch (GREEN). Merged: #77 fix(embed): bound embedding inputs to the model context window (#0569/#0731) #80 one log record is one physical line (#0546) #82 fix(index): never overwrite a good row because the provider was down (#0619) #83 fix(index): report interrupted run progress (#0544) #85 fix(health): client-invalid 400/413/422 are not provider-critical (#0705) #86 fix(ocr): enforce the configured ocr.concurrency (#0732) #88 fix(index): ledger unchanged empty files (#0758) #76 make the doc-ID sweep converge and report what it did (#0545) Cluster bake-offs (operator reconcile-notes): #0731 embed cluster: kept #77, closed #87. Production's own 400 body states "This model's maximum context length is 40960 tokens"; #87 encoded 32768 and truncated by UTF-8 bytes, discarding ~80% of a valid input. #0732 OCR cluster: kept #86, closed #84. Same mechanism; #86 fails fast on a non-integer/bool/<1 concurrency instead of int()-coercing it. Left open (not merged): #75, #79, #81. Closed as superseded by main: #78. Gate on this branch: static pass, unit 1494/0 (origin/main baseline 1413/0), integration 77/0, staging-e2e 31/0 across 4 consecutive runs. NOT DEPLOYED — production has been in an unrelated outage since 08:36 UTC (#0771, LanceDB ArrowInvalid projection error), so post-deploy verify could neither confirm a deploy nor safely auto-roll-back. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Left open — the headline claim does not reproduce; the residual is a weaker, different defectReviewed by the 2026-07-31 nightly reconciliation. Not merged, not closed. #0584's central claim is that a doc whose vision describe timed out is indexed as SUCCESS and enrichment fabricates filler topics from the filename/EXIF, with the same image yielding different Across all 2,999 Zero. The fabrication half of the claim does not reproduce at all today. The two docs currently flagged What I did find is real but narrower: 205 of those 2,999 media docs sit in the index carrying only a This ticket's own most recent nightly note (2026-07-31) reaches the same place from the other direction: "consistent with later self-heal rather than permanent wrong data" and "No identifier reuse, payload contradiction, missing downstream write, or decision flip emerged." Given that, a 514-line diff touching What would make this mergeable: re-scope it to the residue I can actually measure — 205 media docs indexed with zero extracted content and no provenance marker — and give it a red that fails on that specific state. The Ticket #0584 stays in |
Nightly reconciliation 2026-08-01 — verified real, but left open: schema widening into an actively-failing tableI reproduced the defect. Not merging it tonight, and the reason is not about the diff's quality. The defect is real (independently observed, not taken from the ticket)
Describe failed, and the doc is still enriched with filler describing the file and upserted as a success. It finally got a real description on 2026-07-29 20:17 ( Why it is not merging tonight
Production is currently down with exactly this class of error, on every index run since 07:04 UTC today: 10 consecutive failed runs; last success 06:57 UTC. That is #0771 recurring after PR #89 was merged and deployed (the merged sparse-fragment narrowing is present in the running image — I checked Per the reconciliation brief, schema changes are explicitly not exempt from review and structural changes are better left open with a written reason than merged at 4AM. This is that reason. What would unblock it
The ticket stays open; I have not moved it. Nothing here is a criticism of the change, which I would merge once the store is healthy. |
… #0515, #0618) Nightly PR reconciliation pass. Each defect was independently reproduced against production before its fix was read, and each has a test that is RED on origin/main and GREEN after — both sides run in this pass. - #92 (#0785) live preflight fails closed + verifies indexer PID identity. Reproduced both directions against the live container: main's heartbeat-only check returned ok=True on docker-missing / probe-error / unreadable heartbeat, and simultaneously reported 'prod indexer active (heartbeat 104s old)' while no indexer process existed. RED 12 failed -> GREEN 48 passed. - #91 (#0780) + #90 (#0781) exclude .evals/.worktrees/.gitnexus/logs/test_index from the Docker context. Measured on the real deploy path: 7.5 GB copied into the image before, 13.6 MB after, runtime files intact. RED/GREEN each. - #75 (#0515) age the indexer heartbeat against the current run. Reproduced in the raw indexer.log: 2026-07-31 02:21:47 prev heartbeat, 02:34:33 new run registered, first stamp only at 03:42:15 -> /health false-503 'stalled' for ~50 minutes, matching a watcher 'unhealthy' recurrence at 02:53:47 UTC. RED 1 failed -> GREEN 31 passed. - #81 (#0618) account for every degraded-ledger entry. Reproduced live: ledger held 70 entries while every run logged 'Re-queued 2'; 16 entries permanently unresolvable because upstream dedupe set canonical_message_id and the source query filters it out. RED/GREEN on a termination-invariant test written here. Conflict resolution: PR #81 was written before main gained exponential re-queue backoff and would have silently reverted it. Kept both — a resolvable entry is only re-queued once its backoff window elapses, in its own report bucket. Adversarial review also found the escalation could lose entries when the terminal-ledger write failed; fixed and covered by a test. Gate: static+unit+integration PASS (unit 1521/0 failures vs 1505/0 on origin/main). staging-e2e is 1 failed / 30 passed on this branch AND identically on origin/main and on 20d3a8b, so that failure is pre-existing, not introduced here. Live tier not run (spends money; another worker held it). PR #79 (#0584) deliberately left open — it widens the Lance chunks metadata struct while production is down with a Lance projection error.
Left open again (2026-08-02 reconciliation) — defect re-verified, but the diff no longer applies and its stated blocker has only partly clearedI verified the defect independently before reading the fix, and I am not closing this as unreproducible. The symptom is real over a 9-day window, read from The 07-30/07-31 collapse is not remission: indexing was frozen from 08-01 07:04 by #0771, so nothing was describing anything. A quiet window here means the pipeline stopped, not that the defect stopped. Why it still does not merge tonight, in order of weight:
What makes this mergeable, unchanged from the 07-31 review plus one item:
Ticket #0584 stays in |
2026-08-03 04:00 reconciliation — defect CONFIRMED, merge BLOCKED on a deploy hazardThe defect is real and I reproduced it independently. I am not disputing it. Raw production log A doc with no visual content and no enrichment, written to the index as a normal completed write. That is this ticket's signature, 3 days ago, on the code currently running. The signature is quiet in the last 48h only because describe volume collapsed (300+ describe lines/day 07-24..07-28 → 20 today) as the attachment backlog drained — not because anything was fixed. I am therefore explicitly NOT closing this as not-reproducible. Why I am not merging it tonightDeploying this PR would, on the first index run, trigger a full-table rewrite that this container almost certainly cannot survive.
I put this to an adversarial second-opinion review (GPT-5.6 Sol, instructed to refute). Verdict: CANNOT REFUTE. It independently ran the materialization against a live snapshot and produced the 3.85/4.56 GB figures above, which are worse than my original estimate. It correctly corrected three points in my supporting narrative (the 12 GB on-disk figure is mostly MVCC history — the live Arrow payload is 1.88 GB; my "62 hours" and "~1 day soak" numbers were both imprecise); those corrections are folded in above and none of them change the conclusion. What this PR needs before it can land
Merging #95 (#0814) and #96 (#0830) tonight; leaving this open. Ticket #0584 stays in |
Both defects independently reproduced against live production before the fixes were read; both have a test that is RED on 4a9b786 and GREEN here, run on both sides in this pass. #0830 — degraded backoff bypassed on the normal diff (PR #96). Prod at 2026-08-03 09:32 UTC: documents::001Og and documents::001Oo each carried {"attempts": 0, "transient_attempts": 204} in degraded_docs.json, 0 rows in chunks.lance, and retried 19 times that day ~16 min apart — the scheduler cadence, not the 6h cap their 204 tries earn. A never-indexed doc is selected by the normal source/index diff every scan, so reconciliation assigned it "already_queued" and never evaluated _degraded_backoff_seconds. RED on main: a simulated 24h of 16-minute scans processes the doc 90 times (assert 90 <= 4). #0814 — Lance compaction recorded success without reading anything back (PR #95). _compact_data_files_if_due wrote the daily cadence marker as soon as the worker exited 0. On 2026-07-31 a compaction committed a fragment whose manifest over-claimed its columns; the marker recorded success and the table became unreadable. Production ran from 2026-07-31 08:21 to 2026-08-03 02:39 with exactly one completed index run (ArrowInvalid ... 'ran out at field metadata'). RED on main: corrupt output returns True and is recorded; readable output receives zero fragment probes. Gate, this branch vs 4a9b786 run identically: unit 1530 vs 1523 (+7 = the new tests), integration 77, staging-e2e 31, tool coverage 21/21 covered and traced — 0 failures on both sides. Live tier did not run on either side (known host preflight failure on comm-data-store-postgres-1 DNS; it drives paid providers and was not forced). No new failures. PR #79 (#0584) deliberately NOT included: defect confirmed, but it widens the chunks.lance metadata struct 102 -> 104 fields, which on first write triggers _evolve_metadata_schema — an unguarded whole-table to_arrow() measured at 4.56 GB peak RSS against a container at 5.4 GiB under an 8g limit. Left open. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ticity PR #97 verified this pass: reproduced RED on origin/main by pointing pytest --basetemp at a real 93.8%-full tmpfs (no mocks, no env overrides) — 7 failed, 8 passed in tests/test_mcp_contract.py -k health_probe, including #0515's own regression test. GREEN on the PR branch against the same filesystem: 15 passed. The new staging-compose contract test is red against main's compose (KeyError: 'DISK_USAGE_MAX_PERCENT') and green with the pin. PR #79 (#0584) deliberately NOT included — see the reconciliation report.
2026-08-06 04:00 reconciliation — defect RE-CONFIRMED (fresh, yesterday). Still NOT merged: the deploy hazard got worse.Verified independently from the raw production log before reading the diff, per rule zero. The defect is real and fired yesterdayThe 2026-08-03 pass noted the signature had gone quiet as describe volume collapsed. It came back.
Those topics are the enricher describing the file — "Image Metadata", "Samsung G[alaxy]" is the EXIF camera string. What the image actually contains only became visible at 23:55, when the provider recovered and a real describe ran: It is a bank deposit receipt, not a property-maintenance photo. For ~4 hours the index served confident, wholly fabricated metadata for it, as a normal completed write. Note which pass leaks: the Verdict: not closable as not-reproducible. This PR is solving a real problem. Why it still cannot be merged tonightThe 2026-08-03 hazard was re-measured, not taken on faith, and it is worse:
Measured read-only against the live dataset just now; the 6.95 GiB figure is stable across five samples with no index run active, so it is baseline, not a spike. Headroom is ~1.05 GiB. The trigger is unchanged and still unguarded. Blockers, updated
Deliberately not rebased or partially merged tonight: splitting the enrichment/normalization half from the metadata-provenance half would land the cosmetic part and leave the recall hole open, and would still need the same review. Ticket #0584 stays in |
2026-08-08 04:00 reconciliation — defect RE-CONFIRMED (3rd time), still not mergedThe defect is real. Re-derived from the raw production log before reading this diff, per rule zero. Describe never succeeded; both docs were enriched from EXIF and written as normal completed writes. The Per-day Why it is still not mergedNot a verdict on the fix — the blocker is unchanged and now has an owner.
Per the operator's brief, genuinely structural changes are left open with a written reason rather than merged at 4AM. #0926 is the thing to fix; it unblocks this. Ticket #0584 stays in |
|
Left open by the 2026-08-10 nightly reconciliation. What I could verify about the defect (it is real, but narrower and quieter than the ticket says): The most recent instance is 2026-08-05, and it is a clean reproduction of defect 1. Describe failed, the doc was enriched from EXIF alone, and a chunk was inserted — with the run closing green. And the consumer still cannot tell: I read the live Three things that argue against merging it tonight:
What the owner should do: rebase onto |
|
Left open by the 2026-08-11 04:00 reconciliation — but for a newly measured reason, not the old one. The reconciliation work is done and pushed to The defect is real — re-derived from the raw log before reading the diff
Ground truth at 23:55 the same evening, once the provider recovered: Red/green, run here
So the merge gate is satisfied. The deploy is what fails. What actually blocks it (new, measured tonight)The prior deferrals cited #0926 — the unbounded whole-table rewrite. #0926 landed ( Adding exactly this PR's two fields:
Phase-split: the allocation is in Production headroom: #0926's check misses it because it builds at Second opinionI asked GPT-5.6 Sol to refute my original conclusion (that this should be held). It returned WEAK and was right to: my stated reason was unfalsifiable — waiting buys no evidence — and it ran an 8,000-row control showing 15.7 s and zero reader errors. So I stopped holding on judgement and went and measured at production shape instead. That measurement is what now blocks the deploy, and it is the opposite of the small-table result. Its other objections were also actioned: the tested tree is now re-parented onto current What this needs
Branch |
Verified tonight against production, each with a test that is red on 73905ed and green here: - #108 (#1065) a run that logged completion=100.0% at 04:19:51 was marked status=lost 27 s later by the instance that came up at 04:20:08. Only completed same-run heartbeat evidence is promoted; every weaker form stays lost. - #107 (#1064) credential-shaped message content reached enrichment, the OpenRouter embedding call and LanceDB verbatim; the live index holds 214 such chunks across 169 docs. Redaction and quarantine now run before every provider and storage boundary. Two corrections to the PR are folded in: it redacted ordinary English as a Basic/Bearer credential (47 matches over 25 live docs), and it missed a digit-free base64 value. - #106 (#1042) the widening's write side allocated 3.72 GiB against 2.74 GiB of production headroom. Bounded writes cost 0.45-0.75 GiB measured at production shape and on a copy of the live table under production's own 8 GiB cgroup. - Plus a data-loss window that adversarial review surfaced and this branch closes: dying between the swap's two renames left no active table at all, and the next widening deleted the backup as stale. Recovery is marker-gated, fail-closed, and proved by killing a real widening with os._exit in that window. PR #79 (#0584) is deliberately NOT in this merge: it is the change that triggers the metadata struct widening, and it also alters the file_search tool contract. Reasons on the PR. Gate on this tree: static pass, unit 1585, integration 78, staging-e2e 32, 21/21 MCP tools covered and traced (.evals/gate-runs/20260812-081511). The live tier did not run: its comm-store Postgres preflight fails identically on 73905ed because a docker-network hostname does not resolve from the host.
|
Left open for the fifth time — but the reason is now different, and much narrower. The defect is real; I re-derived it from the raw log before reading the diff, as every prior pass did. The merge gate is satisfied: RED against The previous blocker is gone. #1042 landed tonight in What held it tonight instead. Adversarial review of the reconciliation found a data-loss window in the very migration this PR triggers: What remains before this lands (all small):
Ticket #0584 stays in |
…ompaction guard Two changes, both independently reproduced against production before any fix was read, and both with a test this pass ran RED on origin/main and GREEN here. #135 #1254 run the daily Lance compaction in the idle window, bounded by cgroup headroom (reconciled against #118, which landed after it) -- #1101/#1110/#1436 key the fragment-repair column layout on the data file version (authored by this pass; supersedes PRs #113, #114 and #139, all three of which still fail the defect's own test when applied to current main under the deployed lance 10) Test comparison, both sides run by this pass: tier origin/main reconcile static (ruff + collect) pass pass unit (host venv, lance 4.0.0) 1710 passed 1723 passed integration (host venv) 90 passed 90 passed unit (deployed image, lance 10) 2 failed / 1707 1722 passed integration (deployed image) 90 passed 90 passed staging-e2e (compose stack) - 39 passed, 21/21 tools live not runnable not runnable The two lance-10 failures on main are the fragment-repair defect; they are gone here and nothing new failed. The live tier is blocked on both sides by its own safety preflight, 'FAIL prod_indexer_idle: prod indexer active (pid 176)' — a 5,617-document production run was in flight for this whole pass. Left open deliberately, with findings written on the PRs: #79 (#0584) — its defect still reproduces on main, but it adds two persisted fields to every chunk, which triggers a whole-table Lance metadata-struct widening (production last did one over 75,001 rows on 2026-08-22) on a container currently sitting at 7.15 GiB of an 8 GiB cap; it should land after this compaction guard is proven, not beside it. #140 (#1468) — the flake is real and machine-recorded, but the mechanism the PR closes was measured not to exist (0 of 600 trials at two load levels). Contract change flagged for after-the-fact review: ensure_fts_index() loses its compact_data parameter, restoring the signature core/storage.py::StorageInterface already declared. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Left open, deliberately, with a reason and a sequencing. The defect is real and still reproduces on current The defect reproduces. I ran this PR's own regression file against So a doc whose vision The trigger is quiet, but that is not why I am holding it. Extraction failures in the retained log: 88–330/day from 2026-07-22 to 07-31, then 12 on 2026-08-05, then zero for 21 days. The aggregate Why it waits. This PR writes two new keys onto every chunk's "content_status": content_status,
"content_failure_reasons": ", ".join(content_failures),In this store a new metadata key triggers That is a stored-record schema change on 75k+ rows, and it would run on the first index run after deploy. At reconcile time the container was at What it needs to land
Nothing here is a criticism of the change — the two-defect analysis in the description holds up and its regression coverage is the right shape (asserting on the read-back chunk, not on flow state). Ticket #0584 stays in |
Left open by the 2026-08-27 reconciliation — real defect, but this PR no longer matches itReviewed against production over a five-week window ( Defect 1 — content-free docs indexed as success: trigger quiet for 22 days
Last occurrence 2026-08-05. Zero in 22 days. The routing moved to cloud describes The structural defect is still in Defect 2 — unstable enrichment: live, huge, and NOT fixed by this PRThis is the part that is firing right now. Of the 593 documents re-enriched since 2026-08-20:
on unchanged content. Examples straight out of the log: and one document across five passes: This PR's Acceptance box 5 — "two runs over an unchanged file produce identical enrichment output" — is Also blocking
Suggested split
The measured 86.3 % / 96.6 % instability has been recorded on ticket #0584 so it is not lost. |
…tion Every defect below was reproduced from production state or from a control run here before its fix was read, and every red/green was executed on both sides by this pass rather than taken from a PR body. Where a PR's red was only an ImportError on a new symbol (#1655, #1657), a behavioural test that runs unchanged on both sides was written so the red proves wrong behaviour, not an absent name. Landed (PR -> ticket): #154 -> #1625 serve queued source work at bounded checkpoints during a long sweep, and return 503 stale_tail once a request waits past its SLO #153 -> #1654 tier tests by location before name; match the _live.py suffix #152 -> #1663 stop the unit tier reaching the real detached indexer launcher #151 -> #1659 stop the staging gate racing for fixed host ports #150 -> #1661 keep Lance stale-read recovery in the store, not in mcp_server #149 -> #1662 keep the upstream body at every provider rejection site #148 -> #1657 keep the upstream body on a permanent HTTP rejection #147 -> #1658 match provider-sim's embedding character cap to the real route #146 -> #1650 let the telemetry seam see qwen's discarded first answer #145 -> #1655 bound embed inputs by the route's character cap, not tokens alone #144 -> #1656 recover Lance reads while a peer writer keeps moving the table #143 -> #1649 bound the nested index write lock join for deadlock, not scheduling #142 -> #1629 observe the enrichment route contract on every answer Overlaps resolved rather than merged blind: #149 stacks on #148, so one merge lands both; #150 depends on #144's table-identity recovery and would be a downgrade alone, so they land together; #142 and #146 are two fixes to one root cause (the in-request JSON validation retry) and compose cleanly rather than competing; #145 and #147 are the client and simulator halves of the same character cap. One change is this pass's own, written test-first: #1662 left providers/llm/litellm_llm.py and providers/llm/openrouter_llm.py on the bare raise_for_status, and litellm_llm is the enrichment route that was observably discarding 400 bodies in production on 2026-08-26 and 2026-08-27. Gate on the reconcile branch, all five tiers: static PASS | unit 1864 passed | integration 90 passed staging-e2e 41 passed (tool coverage 21/21) | live 123 passed, 0 failed Baseline at origin/main run the same way: unit 1723 passed, integration 90 passed. No new failures. Because lancedb_store.py changed and the venv resolves lance 4.0.0 while the production image resolves lance 10.0.0, the deterministic suite was re-run inside the candidate image: tests/test_store.py 149 passed on lance 10. PROTECTED SURFACE: #154 changes the /health contract (new 503 stale_tail, new INDEX_PENDING_MAX_AGE_S in docker-compose.yml). The container healthcheck raises on 503 and the post-deploy verify requires status=ok, so a stale tail can mark the container unhealthy and can trip an auto-rollback. Status composition was checked: stale_tail sits between index_failed and degraded, and disk_full still overwrites last, so #0910's ordering holds. Left open with written reasons: #141 (#1630 — hardens a path that has executed zero times in five weeks; the 440 orphan _indices were reclaimed by the existing incremental finalizer during this pass, confirming it) and #79 (#0584 — a month stale, and its label alias map matches none of the 512 unstable doc_types measured in production). Report: Maint-Manager/.briefs/reconcile/20260827/RAG-in-a-Box.md
Summary
Maint ticket #0584. A doc-organizer document whose vision
describetimed out wasindexed as a plain success with no description: the only trace was an aggregate
55 docs indexed with degradationsWARNING, and because the EXIF/dimensions header madefull_textnon-empty, enrichment still ran and fabricated content topics from the file(
topics=image, photo, MPO formatfor tenant maintenance photos that were never read) —a different label set on nearly every one of 7 passes over the same unchanged image.
Two defects, both fixed at existing seams:
Content-free docs indexed as success.
ExtractionResultnow carriesprimary_content, so an EXIF header is no longer mistaken for content, andcontent_status()classifies each doccomplete/partial/missingfrom that plusits extraction degradations. This is one path for OCR, describe, transcription and PDF
text — no per-extractor or per-doc-id special-casing. The pipeline writes
content_status/content_failure_reasonsonto every chunk (so the failure is legiblein the record a consumer reads back, and slim search hits surface it), skips LLM
enrichment when primary content is missing, and counts successful incomplete writes into
a new
docs_indexed_incompleterun-summary field.Same input → different output. Skipping enrichment for content-free docs makes the
failed-describe path deterministic and drops a wasted per-run LLM call. Labels are also
canonicalized (
img→image) and deduplicated case-insensitively at the enrichmentnormalization boundary, so identical input maps to identical labels and the taxonomy
stops accumulating both spellings.
Test result
Full gate at
cb156catree (python scripts/gate.py):MCP tool coverage 21/21 covered, 21/21 traced.
The
livetier does not run on this host: its preflight fails oncomm-data-store-postgres-1DNS (container not on this host's network) and onconfig_test.yamlbeing absent from linked worktrees. That tier uses real paid providersand is preflight-guarded by design, so it was not forced.
Candidate-container E2E (the pre-deploy gate): image built from this branch's
Dockerfileviadocker-compose.staging.yml— isolated project, ports 17788/19999, ownnamed volumes, provider simulator, no production state or outbound writes.
cb156ca24aa4d7a6a07845a0e84fb2f249853311(squashed to this PR's commit)sha256:f5dd2159e4f9866078a4acdc9e951b3dc6d5166da9ec1a31ea321db435c36992Healthy; no restarts, no OOMdown -vremoved all containers, volumes and the network (verified)Regression coverage fails for the right reason before the fix:
KeyError: 'content_status'on the read-back chunk, and
expected 'image, photo', got 'img, image, IMAGE, photo, Photo'at the label boundary.
Maint ticket: #0584 — Maint-Manager
tickets/review/0584-doc-organizer-a-doc-whose-vision-describe-timed-ou.mdOn merge: verify this ticket's acceptance boxes against production (not tests),
tick them, then move the ticket to
tickets/done/and setstatus: done.A merged PR is not a closed ticket — an unmerged PR is not a shipped fix.