Skip to content

fix(tags): treat attachment purpose='tags' as equivalent to type='tags' (1.3.1) - #66

Merged
howethomas merged 4 commits into
mainfrom
claude/peaceful-nobel-1d9adf
Aug 19, 2026
Merged

fix(tags): treat attachment purpose='tags' as equivalent to type='tags' (1.3.1)#66
howethomas merged 4 commits into
mainfrom
claude/peaceful-nobel-1d9adf

Conversation

@howethomas

Copy link
Copy Markdown
Contributor

Problem

vCon 0.4.0 classifies attachments with purpose; type exists only on Dialog and Analysis. But tag storage in this server keys entirely on attachments.type = 'tags'src/db/queries.ts (7 read sites), database-analytics.ts, and the vcon_tags_mv definition.

So a spec-correct 0.4.0 vCon carrying its tags as an attachment with purpose: "tags" landed with type NULL 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 ietf dataset. 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 UPDATE trigger on attachments mirrors 'tags' across type and purpose in both directions, plus a backfill for existing rows.

  • Every existing type = 'tags' consumer keeps working with zero code changes — read paths, analytics, RPCs.
  • Tags written by this server now also carry the spec purpose field, so exported vCons are 0.4.0-correct.
  • vcon_tags_mv is rebuilt on coalesce(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.
  • The Mongo backend has no trigger to lean on, so its tag paths use a shared isTagsAttachment() predicate (which also fixes extractTags in read-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 via get_tags, present in vcon_tags_mv after refresh, and findable via search_by_tags.

  • Passes with the fix.
  • Verified it fails without it (dropped the trigger → expected undefined to be 'purpose-…').
  • Unit suite: 797 passed, 14 skipped.

Release

Includes the 1.3.1 patch bump and CHANGELOG entry, so merging this cuts the release. Migration 20260817000000_tags_attachment_purpose.sql is required.

🤖 Generated with Claude Code

howethomas and others added 4 commits August 17, 2026 16:55
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>
@howethomas
howethomas merged commit acbe874 into main Aug 19, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant