-
Notifications
You must be signed in to change notification settings - Fork 1
fix(attachments): index reparsed attachment content into the content graph #1501
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
seonghobae
merged 25 commits into
claude/noema-contextualwisdomlab-commercialization-afow1j
from
claude/attachment-reparse-content-graph-index
Sep 2, 2026
Merged
Changes from all commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
780d910
fix(attachments): index reparsed attachment content into the content …
claude 5096d1f
fix(attachments): preload graph relationships for reparse
seonghobae beded49
fix(attachments): persist reparsed graph topology and vectors
seonghobae f63a109
fix(attachments): chunk reparsed embedding sources
seonghobae 41ae6a2
fix(db): skip absent legacy email read-state table
seonghobae bd4b5ae
Merge remote-tracking branch 'refs/remotes/origin/claude/noema-contex…
seonghobae 86074f6
test(attachments): skip the persisted-reparse postgres smoke cleanly …
claude 4b4b1cb
Merge remote-tracking branch into HEAD
claude 51245f7
test: fail reparse smoke on postgres defects
seonghobae de11149
fix(db): make 0011_email_read_state's legacy-table guard offline-safe
claude 3cbbba8
merge: refresh attachment reparse stack base
seonghobae 3a2246d
Merge remote-tracking branch 'origin/claude/attachment-reparse-conten…
seonghobae 87ef2e5
fix(db): defer 0011's legacy-table check to SQL, not Python
claude d78655a
Merge remote-tracking branch into HEAD
claude 8e47575
fix(ci): stop the root governance test step from crashing under PYTHO…
claude 09a2443
fix(db): resolve 0011's legacy-table check through search_path, not r…
claude c249096
docs(db): document 0011's op.execute as a deliberate structured-ops e…
claude 26e685e
merge: refresh reparse stack and narrow postgres skip
seonghobae d244ccc
fix(attachments): resolve reparse embedding source and migration down…
claude f316b2d
merge: bring in base-branch CI-wiring fixes merged onto this branch a…
claude f3af149
merge: bring in base-branch changes to resolve mergeable_state DIRTY
claude 6294b8b
fix(migrations): suppress bandit B608 false positive on 0011 DDL cons…
claude 0345eda
test(alembic): narrow the connectivity-probe exception handler in the…
claude cdcf2da
Merge remote-tracking branch 'origin/claude/noema-contextualwisdomlab…
claude d7e5d2d
fix(test): add missing refresh() to _LiveReparsePendingSession fake
claude File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,29 +1,116 @@ | ||
| """Add is_read to emails (IMAP \\Seen read state). | ||
|
|
||
| Existing rows default to read so historical/file imports do not surface as unread. | ||
|
|
||
| Deliberate exception to this repo's "Alembic migrations use structured | ||
| operations (``op.create_index``, ...), never ``sa.text(f"...")`` DDL" rule | ||
| (``AGENTS.md``/``CLAUDE.md``): ``upgrade()``/``downgrade()`` below use | ||
| ``op.execute()`` with the module-level ``_UPGRADE_SQL``/``_DOWNGRADE_SQL`` | ||
| constants instead of a structured ``op.*`` call. That rule's actual target is | ||
| DDL built from interpolated identifier strings (an injection-safety concern); | ||
| these constants interpolate only ``_IS_READ_PROVENANCE_MARKER``, a fixed | ||
| module-level literal, never an identifier or a value built from a variable, | ||
| external input, or runtime state -- the same safety property a structured | ||
| call would have. The reason a structured call isn't used is different: this | ||
| migration's behavior must be conditional on whether the legacy ``emails`` | ||
| table exists, evaluated at apply time (see the comment on ``_UPGRADE_SQL`` | ||
| below for why that check cannot live in Python), and no structured Alembic | ||
| operation expresses "run this DDL only if a runtime condition holds" -- a | ||
| ``DO $$ ... $$`` block is the correct primitive for that, not a workaround | ||
| for one. | ||
| """ | ||
|
|
||
| from alembic import op | ||
| import sqlalchemy as sa | ||
|
|
||
| # revision identifiers, used by Alembic. | ||
| revision = "0011_email_read_state" | ||
| down_revision = "0009_project_graph_projection" | ||
| branch_labels = None | ||
| depends_on = None | ||
|
|
||
| # Fresh installations materialize the current ``email_records`` model in the | ||
| # 0001 baseline, including ``is_read``. This historical side branch only | ||
| # applies to databases that still carry its legacy ``emails`` table. | ||
| # | ||
| # The condition has to be evaluated in SQL, not Python: offline SQL | ||
| # generation (``alembic upgrade --sql``, a real flag ``scripts/migrate_db.py`` | ||
| # exposes) has no live connection to introspect with and no specific target | ||
| # database to ask "does this legacy table exist" at generation time either -- | ||
| # the same static script is meant to later be applied by a DBA against | ||
| # whichever database they choose, fresh-install or legacy. A Python-side | ||
| # check (``sa.inspect(op.get_bind())``) can only ever answer that question | ||
| # for one hypothetical target chosen at generation time, so it is wrong for | ||
| # the other: skip unconditionally and the column silently never gets added | ||
| # for a legacy database that applies the generated script (while | ||
| # ``alembic_version`` still advances, permanently hiding the gap); inspect | ||
| # online and bake in one fixed answer and the same script fails outright | ||
| # against the other kind of target. A ``DO $$ ... $$`` block defers the | ||
| # check to apply time instead, so the one generated script is correct | ||
| # against either kind of target, online or offline-then-applied-later alike. | ||
| # | ||
| # ``to_regclass('emails')`` (not ``information_schema.tables`` by bare | ||
| # ``table_name``) deliberately: the unqualified ``ALTER TABLE emails`` below | ||
| # resolves through the connection's ``search_path``, and ``to_regclass`` | ||
| # resolves an unqualified name exactly the same way, returning NULL if it | ||
| # doesn't. ``information_schema.tables`` filtered only by ``table_name`` | ||
| # ignores ``search_path`` entirely and matches a same-named table in *any* | ||
| # schema the connecting role can see -- on a deployment with more than one | ||
| # accessible schema, that could find an unrelated ``emails`` table outside | ||
| # the search path while the unqualified ``ALTER TABLE emails`` targets a | ||
| # different (or no) table, passing the guard for the wrong relation or | ||
| # aborting the migration outright. Resolving both the check and the DDL | ||
| # through the same name lookup makes that mismatch structurally impossible. | ||
| # | ||
| # ``COMMENT ON COLUMN emails.is_read`` tags the column with a provenance | ||
| # marker (``_IS_READ_PROVENANCE_MARKER``) the moment upgrade() actually adds | ||
| # it. downgrade() only drops the column when that exact marker is present | ||
| # (CodeRabbit, naruon#1501): an ``emails.is_read`` column that already | ||
| # existed before this revision ran -- from some other, unrelated origin -- | ||
| # would upgrade()'s ``NOT EXISTS`` guard correctly leave alone, but an | ||
| # unconditional ``DROP COLUMN IF EXISTS`` on downgrade would still destroy it | ||
| # and its data, since a downgrade has no other way to tell "I added this" | ||
| # apart from "this happens to be present". Checking the marker via | ||
| # ``col_description`` makes downgrade drop only what this exact revision's | ||
| # upgrade created. | ||
| _IS_READ_PROVENANCE_MARKER = "0011_email_read_state:added" | ||
| _UPGRADE_SQL = f""" | ||
| DO $$ | ||
| BEGIN | ||
| IF to_regclass('emails') IS NOT NULL AND NOT EXISTS ( | ||
| SELECT 1 FROM pg_attribute | ||
| WHERE attrelid = to_regclass('emails') | ||
| AND attname = 'is_read' | ||
| AND NOT attisdropped | ||
| ) THEN | ||
| ALTER TABLE emails ADD COLUMN is_read boolean NOT NULL DEFAULT true; | ||
| COMMENT ON COLUMN emails.is_read IS '{_IS_READ_PROVENANCE_MARKER}'; | ||
| END IF; | ||
| END $$; | ||
| """ # nosec B608 | ||
|
|
||
| _DOWNGRADE_SQL = f""" | ||
| DO $$ | ||
| BEGIN | ||
| IF to_regclass('emails') IS NOT NULL AND EXISTS ( | ||
| SELECT 1 FROM pg_attribute | ||
| WHERE attrelid = to_regclass('emails') | ||
| AND attname = 'is_read' | ||
| AND NOT attisdropped | ||
| ) AND col_description(to_regclass('emails'), ( | ||
| SELECT attnum FROM pg_attribute | ||
| WHERE attrelid = to_regclass('emails') | ||
| AND attname = 'is_read' | ||
| AND NOT attisdropped | ||
| )) = '{_IS_READ_PROVENANCE_MARKER}' THEN | ||
| ALTER TABLE emails DROP COLUMN IF EXISTS is_read; | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| END IF; | ||
| END $$; | ||
| """ # nosec B608 | ||
|
|
||
|
|
||
| def upgrade() -> None: | ||
| op.add_column( | ||
| "emails", | ||
| sa.Column( | ||
| "is_read", | ||
| sa.Boolean(), | ||
| nullable=False, | ||
| server_default=sa.text("true"), | ||
| ), | ||
| ) | ||
| op.execute(_UPGRADE_SQL) | ||
|
|
||
|
|
||
| def downgrade() -> None: | ||
| op.drop_column("emails", "is_read") | ||
| op.execute(_DOWNGRADE_SQL) | ||
Oops, something went wrong.
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.