Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
be90ea8
feat(naruon): persist DiskSage file lineage ontology
seonghobae Aug 13, 2026
d79dbf7
feat(naruon): preserve provider sync state in file lineage
seonghobae Aug 13, 2026
5baa5d8
fix: accept DiskSage lineage schema v2
seonghobae Aug 13, 2026
39dff05
fix: preserve DiskSage v2 copy approval lineage
seonghobae Aug 13, 2026
f5aa966
docs: add DiskSage lineage DBML projection
seonghobae Aug 13, 2026
d287dad
fix: harden DiskSage lineage persistence
seonghobae Aug 13, 2026
bd01171
fix: close lineage schema and approval validation gaps
seonghobae Aug 13, 2026
3437e44
fix: migrate legacy DiskSage lineage schemas
seonghobae Aug 13, 2026
c7b46d0
fix: keep DiskSage Alembic revisions within storage limits
seonghobae Aug 13, 2026
5ea7325
fix: keep Python 3.14 safety checks green
seonghobae Aug 13, 2026
b6167d8
docs: document current lineage schema versions
seonghobae Aug 13, 2026
f724378
fix: hide DAV extensions and preserve comment text
seonghobae Aug 13, 2026
a76ef92
test: cover DAV GET dispatch
seonghobae Aug 13, 2026
d5bbed8
fix(naruon): enforce metadata-first lineage boundary
seonghobae Aug 14, 2026
ab5201b
fix(naruon): accept provider API lineage evidence
seonghobae Aug 14, 2026
801dbe5
fix: fail closed on browser origin gaps
seonghobae Aug 14, 2026
f8f91e7
fix: accept DiskSage timeliness lineage fields
seonghobae Aug 14, 2026
a760664
fix: accept DiskSage readiness schema v6
seonghobae Aug 14, 2026
d5a83c8
fix: accept native readiness verifier summary
seonghobae Aug 14, 2026
272b6d7
Merge protected develop into DiskSage lineage branch
seonghobae Aug 14, 2026
ab9d3fa
Merge protected develop into DiskSage lineage branch
seonghobae Aug 14, 2026
8d2b937
test(security): cover DAV cross-site origin guard
seonghobae Aug 15, 2026
208f746
fix(security): enforce browser-origin guard on DAV mutations
seonghobae Aug 15, 2026
9121cda
Merge remote-tracking branch 'origin/develop' into feat/disksage-file…
seonghobae Aug 19, 2026
7ae7176
chore(naruon): keep lineage PR scoped
seonghobae Aug 19, 2026
32b00ce
fix: keep DAV protocol route out of OpenAPI
seonghobae Aug 19, 2026
5c5803d
docs: record pg-erd DBML validation
seonghobae Aug 19, 2026
87c54db
feat: persist DiskSage organization lineage
seonghobae Aug 19, 2026
34b0caf
ci: bound Playwright browser setup and smoke
seonghobae Aug 20, 2026
017cdef
Merge branch 'develop' of https://github.com/ContextualWisdomLab/naru…
seonghobae Aug 25, 2026
298a3f2
fix(disksage): map wrapped encryption key failures to 503
seonghobae Aug 25, 2026
b130cc7
fix(disksage): reject cross-organization lineage races
seonghobae Aug 25, 2026
dd947ed
fix(disksage): handle cross-organization batch races
seonghobae Aug 25, 2026
7c1f936
Merge branch 'develop' into feat/disksage-file-lineage-ontology
seonghobae Aug 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .github/workflows/app-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,14 @@ jobs:
cd frontend && pnpm run build

- name: Install Playwright Chromium
run: cd frontend && pnpm exec playwright install --with-deps chromium
timeout-minutes: 10
# The hosted Ubuntu runner already provides the system libraries needed by
# Chromium. Installing them again with --with-deps can block on apt for
# hours and turn a real frontend result into a cancelled check.
run: cd frontend && pnpm exec playwright install chromium

- name: Run full product smoke
timeout-minutes: 15
env:
NEXT_TELEMETRY_DISABLED: "1"
NARUON_FULL_PRODUCT_BASE_URL: "http://127.0.0.1:3001"
Expand Down
86 changes: 86 additions & 0 deletions backend/alembic/versions/0018_disksage_file_lineage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
"""persist scoped, encrypted DiskSage file lineage envelopes

Revision ID: 0018_disksage_file_lineage
Revises: 0017_merge_newsdom_carddav_heads
Create Date: 2026-08-13 00:00:00.000000

The Rust DiskSage verifier remains authoritative for copy/provider proof. Naruon
stores the validated envelope for workspace-scoped provenance and exposes only
the redacted graph projection in list responses.
"""

from alembic import op
import sqlalchemy as sa


revision = "0018_disksage_file_lineage"
down_revision = "0017_merge_newsdom_carddav_heads"
branch_labels = None
depends_on = None

_TABLE = "disksage_file_lineage_records"


def upgrade() -> None:
connection = op.get_bind()
inspector = sa.inspect(connection)
if not inspector.has_table(_TABLE):
op.create_table(
_TABLE,
sa.Column("lineage_record_uid", sa.String(length=96), nullable=False),
sa.Column("user_id", sa.String(), nullable=False),
sa.Column("organization_id", sa.String(), nullable=True),
sa.Column("workspace_id", sa.String(), nullable=False),
sa.Column("lineage_fingerprint", sa.String(length=64), nullable=False),
sa.Column("envelope_sha256", sa.String(length=64), nullable=False),
sa.Column("schema_version", sa.Integer(), nullable=False),
sa.Column("schema_kind", sa.String(length=96), nullable=False),
sa.Column("source_kind", sa.String(length=64), nullable=False),
sa.Column("archive_kind", sa.String(length=64), nullable=False),
sa.Column("raw_content_sha256", sa.String(length=64), nullable=False),
sa.Column("raw_content_blake3", sa.String(length=64), nullable=False),
sa.Column("content_bytes", sa.BigInteger(), nullable=False),
sa.Column("ontology_class", sa.String(length=256), nullable=False),
sa.Column("ontology_relation_count", sa.Integer(), nullable=False),
sa.Column("ontology_predicates", sa.JSON(), nullable=False),
sa.Column("provider_name", sa.String(length=32), nullable=False),
sa.Column("provider_sync_confirmed", sa.Boolean(), nullable=False),
sa.Column(
"provider_sync_state",
sa.String(length=32),
nullable=False,
server_default="unknown",
),
sa.Column("envelope_json_encrypted", sa.String(), nullable=False),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
sa.PrimaryKeyConstraint("lineage_record_uid"),
sa.UniqueConstraint(
"user_id",
"workspace_id",
"lineage_fingerprint",
name="uq_disksage_lineage_workspace_fingerprint",
),
)

op.create_index(
"ix_disksage_lineage_scope_time",
_TABLE,
["user_id", "organization_id", "workspace_id", "created_at"],
if_not_exists=True,
)
op.create_index(
"ix_disksage_lineage_ontology_class",
_TABLE,
["workspace_id", "ontology_class"],
if_not_exists=True,
)


def downgrade() -> None:
op.drop_index(
"ix_disksage_lineage_ontology_class", table_name=_TABLE, if_exists=True
)
op.drop_index("ix_disksage_lineage_scope_time", table_name=_TABLE, if_exists=True)
connection = op.get_bind()
if sa.inspect(connection).has_table(_TABLE):
op.drop_table(_TABLE)
43 changes: 43 additions & 0 deletions backend/alembic/versions/0019_disksage_provider_sync_state.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""persist provider-native sync state for DiskSage lineage envelopes

Revision ID: 0019_disksage_sync_state
Revises: 0018_disksage_file_lineage
Create Date: 2026-08-13 00:00:00.000000
"""

from alembic import op
import sqlalchemy as sa


revision = "0019_disksage_sync_state"
down_revision = "0018_disksage_file_lineage"
branch_labels = None
depends_on = None

_TABLE = "disksage_file_lineage_records"


def upgrade() -> None:
inspector = sa.inspect(op.get_bind())
if inspector.has_table(_TABLE) and not any(
column["name"] == "provider_sync_state"
for column in inspector.get_columns(_TABLE)
):
op.add_column(
_TABLE,
sa.Column(
"provider_sync_state",
sa.String(length=32),
nullable=False,
server_default="unknown",
),
)


def downgrade() -> None:
inspector = sa.inspect(op.get_bind())
if inspector.has_table(_TABLE) and any(
column["name"] == "provider_sync_state"
for column in inspector.get_columns(_TABLE)
):
op.drop_column(_TABLE, "provider_sync_state")
39 changes: 39 additions & 0 deletions backend/alembic/versions/0020_disksage_lineage_column_names.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"""rename ambiguous DiskSage lineage column names

Revision ID: 0020_disksage_columns
Revises: 0019_disksage_sync_state
Create Date: 2026-08-13 00:00:00.000000
"""

from alembic import op
import sqlalchemy as sa


revision = "0020_disksage_columns"
down_revision = "0019_disksage_sync_state"
branch_labels = None
depends_on = None

_TABLE = "disksage_file_lineage_records"


def _rename_if_present(inspector: sa.Inspector, old: str, new: str) -> None:
columns = {column["name"] for column in inspector.get_columns(_TABLE)}
if old in columns and new not in columns:
op.alter_column(_TABLE, old, new_column_name=new)


def upgrade() -> None:
inspector = sa.inspect(op.get_bind())
if inspector.has_table(_TABLE):
_rename_if_present(inspector, "bytes", "content_bytes")
inspector = sa.inspect(op.get_bind())
_rename_if_present(inspector, "provider", "provider_name")


def downgrade() -> None:
# This revision repairs legacy installations that still have the old
# single-word names. Revision 0019 and the canonical 0018 schema both
# use content_bytes/provider_name, so a downgrade must not reintroduce
# the legacy shape or leave the model and migration chain inconsistent.
pass
39 changes: 39 additions & 0 deletions backend/alembic/versions/0021_disksage_lineage_scope_index.py
Comment thread
seonghobae marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"""align the DiskSage lineage scope index with the list query"""

from alembic import op
import sqlalchemy as sa


revision = "0021_disksage_scope_idx"
down_revision = "0020_disksage_columns"
branch_labels = None
depends_on = None

_TABLE = "disksage_file_lineage_records"
_INDEX = "ix_disksage_lineage_scope_time"


def upgrade() -> None:
inspector = sa.inspect(op.get_bind())
if not inspector.has_table(_TABLE):
return
op.drop_index(_INDEX, table_name=_TABLE, if_exists=True)
op.create_index(
_INDEX,
_TABLE,
["user_id", "workspace_id", "created_at"],
if_not_exists=True,
)
Comment thread
seonghobae marked this conversation as resolved.


def downgrade() -> None:
inspector = sa.inspect(op.get_bind())
if not inspector.has_table(_TABLE):
return
op.drop_index(_INDEX, table_name=_TABLE, if_exists=True)
op.create_index(
_INDEX,
_TABLE,
["user_id", "organization_id", "workspace_id", "created_at"],
if_not_exists=True,
)
51 changes: 51 additions & 0 deletions backend/alembic/versions/0022_disksage_organization_lineage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""persist path-free DiskSage organization lineage batches"""

from alembic import op
import sqlalchemy as sa


revision = "0022_disksage_org_lineage"
down_revision = "0021_disksage_scope_idx"
branch_labels = None
depends_on = None

_TABLE = "disksage_organization_lineage_records"


def upgrade() -> None:
inspector = sa.inspect(op.get_bind())
if inspector.has_table(_TABLE):
return
op.create_table(
_TABLE,
sa.Column("organization_lineage_record_uid", sa.String(length=96), nullable=False),
sa.Column("user_id", sa.String(), nullable=False),
sa.Column("organization_id", sa.String(), nullable=True),
sa.Column("workspace_id", sa.String(), nullable=False),
sa.Column("batch_fingerprint_sha256", sa.String(length=64), nullable=False),
sa.Column("envelope_sha256", sa.String(length=64), nullable=False),
sa.Column("schema_version", sa.Integer(), nullable=False),
sa.Column("item_count", sa.Integer(), nullable=False),
sa.Column("ontology_classes", sa.JSON(), nullable=False),
sa.Column("envelope_json_encrypted", sa.String(), nullable=False),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
sa.PrimaryKeyConstraint("organization_lineage_record_uid"),
sa.UniqueConstraint(
"user_id",
"workspace_id",
"batch_fingerprint_sha256",
name="uq_disksage_org_lineage_workspace_fingerprint",
),
)
op.create_index(
"ix_disksage_org_lineage_scope_time",
_TABLE,
["user_id", "workspace_id", "created_at"],
if_not_exists=True,
)


def downgrade() -> None:
op.drop_index("ix_disksage_org_lineage_scope_time", table_name=_TABLE, if_exists=True)
if sa.inspect(op.get_bind()).has_table(_TABLE):
op.drop_table(_TABLE)
1 change: 1 addition & 0 deletions backend/api/dav.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ async def _handle_project_propfind(
@router.api_route(
"/{path:path}",
methods=["PROPFIND", "REPORT", "MKCOL", "GET", "PUT", "DELETE", "OPTIONS"],
include_in_schema=False,
)
async def dav_handler(
request: Request,
Expand Down
Loading
Loading