fix(tags): treat attachment purpose='tags' as equivalent to type='tags' (1.3.1) - #66
Merged
Merged
Conversation
vCon 0.4.0 classifies attachments with `purpose`, not `type`, so a spec-correct vCon carrying its tags as `purpose: "tags"` landed with type NULL and was invisible to every tag read path and to vcon_tags_mv — silently, no error. Fixed at the single point every writer routes through: a BEFORE INSERT/UPDATE trigger on attachments mirrors 'tags' across both columns, plus a backfill for existing rows. All existing type = 'tags' consumers keep working unchanged, and exported vCons now carry the spec field. vcon_tags_mv is rebuilt on coalesce(type, purpose) so it no longer depends on the trigger. The Mongo backend has no trigger to lean on, so its tag paths now use a shared isTagsAttachment() predicate and dual-write both fields. E2E test creates a 0.4.0 vCon with a purpose='tags' attachment and asserts the tags are readable via get_tags, present in vcon_tags_mv, and findable via search_by_tags. Verified failing without the fix. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Patch release for the purpose='tags' attachment fix. Also untracks .omc/ session state that was swept into the previous commit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Resolves the conflicts #67 (flat JSON object tags bodies) created in the same tag read paths this branch touches. - read-surfaces.ts: keep both additions. isTagsAttachment() from this branch decides WHICH attachment holds the tags; parseTagsBody() from main decides HOW to read its body. extractTags uses both. - mongo-queries.ts getTags: same, find(isTagsAttachment) + parseTagsBody(). Collapsed the two read-surfaces import lines. - 20260817000000_tags_attachment_purpose.sql: dropped its vcon_tags_mv rebuild. main's 20260817120000 already defines the MV on coalesce(type, purpose), so the rebuild was redundant — and unsafe, since this file applies out of order on any database that already has 20260817120000 and would have reverted the MV to the array-only body predicate, silently re-breaking tags for 10k+ object-bodied vCons. Verified: applying this migration to the local dev DB out of order now leaves vcon_tags_mv at 10,268 rows. - CHANGELOG: 1.3.1 ships #65 and #67 as well, so both are listed, and the MV claim is reworded to say which migration owns it. Build clean, 808 tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.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.
Problem
vCon 0.4.0 classifies attachments with
purpose;typeexists only on Dialog and Analysis. But tag storage in this server keys entirely onattachments.type = 'tags'—src/db/queries.ts(7 read sites),database-analytics.ts, and thevcon_tags_mvdefinition.So a spec-correct 0.4.0 vCon carrying its tags as an attachment with
purpose: "tags"landed withtypeNULL and its tags were invisible to every tag read path and to the matview. No error raised — same silent-data-loss shape as the pre-0.4.0 field-name issue fixed earlier in the write path.Found 2026-08-17 while standing up the hosted
ietfdataset. That dataset has zero tags attachments, so it was latent there — but applying the migration to a local dev DB backfilled 10268 rows, so real corpora do hit it.Fix
One place, where every writer routes through: a
BEFORE INSERT OR UPDATEtrigger onattachmentsmirrors'tags'acrosstypeandpurposein both directions, plus a backfill for existing rows.type = 'tags'consumer keeps working with zero code changes — read paths, analytics, RPCs.purposefield, so exported vCons are 0.4.0-correct.vcon_tags_mvis rebuilt oncoalesce(type, purpose)so the matview does not depend on the trigger being present. Indexes recreated as-is; the incremental-detection partial index now covers either spelling.isTagsAttachment()predicate (which also fixesextractTagsinread-surfaces.ts) and dual-write both fields.Test
New e2e case creates a 0.4.0 vCon with a
purpose: "tags"attachment and asserts the tags are readable viaget_tags, present invcon_tags_mvafter refresh, and findable viasearch_by_tags.expected undefined to be 'purpose-…').Release
Includes the
1.3.1patch bump and CHANGELOG entry, so merging this cuts the release. Migration20260817000000_tags_attachment_purpose.sqlis required.🤖 Generated with Claude Code