Skip to content

Add publish lists and enhance report sharing for RoboInvestor#573

Merged
jfrench9 merged 2 commits intomainfrom
feature/roboinvestor-share-reports
Apr 2, 2026
Merged

Add publish lists and enhance report sharing for RoboInvestor#573
jfrench9 merged 2 commits intomainfrom
feature/roboinvestor-share-reports

Conversation

@jfrench9
Copy link
Copy Markdown
Member

@jfrench9 jfrench9 commented Apr 2, 2026

Summary

Introduces publish list functionality to the RoboLedger extension and enhances report sharing capabilities for the RoboInvestor platform. This feature enables investors to manage curated publish lists and integrates them with existing report and security workflows.

Key Accomplishments

Publish Lists (New Feature)

  • Added new PublishList database model in the RoboLedger extension with full CRUD support
  • Created comprehensive API router (/ledger/publish_lists) with endpoints for creating, reading, updating, and deleting publish lists
  • Defined Pydantic API models for publish list request/response serialization
  • Integrated publish lists into the ledger router module registration

Report Enhancements

  • Extended report functionality to support publish list integration, enabling reports to be shared or scoped through publish lists
  • Refactored report router logic with significant restructuring (~172 lines modified) to accommodate new sharing workflows
  • Updated report API models to support new fields and relationships

Model & Schema Updates

  • Added database migration (a1b2c3d4e5f7) introducing publish list tables and security source columns
  • Extended the Security model in RoboInvestor with a new source field
  • Updated Entity and Investor API models with additional attributes to support publish list relationships
  • Registered new models in the extensions __init__ modules

Investor Securities Refactor

  • Simplified the investor securities router, reducing ~69 lines — likely extracting shared logic or leveraging the new publish list abstractions

Breaking Changes

  • Database migration required: The new migration adds publish list tables and a security source column. This must be applied before deployment.
  • Entity API response changes: The entity API model has a new field, which may affect consumers expecting a strict schema.
  • Investor API model changes: Additional fields on the investor model could impact downstream integrations.
  • Report API/response changes: Modified report models and restructured router logic may alter response shapes or endpoint behavior.

Testing Notes

  • Added/expanded tests for report endpoints (test_reports.py — +84 lines) covering new publish list integration scenarios
  • Updated entity ledger tests to account for new model fields
  • Modified graph-related tests (test_backups.py, test_feature_flags.py) — likely fixture or setup adjustments to accommodate schema changes
  • Recommend verifying publish list CRUD operations end-to-end and validating report sharing flows with publish lists attached

Infrastructure Considerations

  • A new database migration must be executed in all environments prior to deploying this change
  • The migration introduces new tables and alters existing columns — ensure database backup procedures are followed
  • Review index and constraint additions in the migration for potential performance implications on large datasets
  • Updated .gitignore rules should be verified to ensure no necessary files are inadvertently excluded

🤖 Generated with Claude Code

Branch Info:

  • Source: feature/roboinvestor-share-reports
  • Target: main
  • Type: feature

Co-Authored-By: Claude noreply@anthropic.com

…tion

- Updated CreateSecurityRequest and SecurityResponse models to include source_graph_id for better entity association.
- Refactored ShareReportRequest to utilize publish_list_id instead of target_graph_ids, streamlining report sharing.
- Implemented logic in share_report to resolve publish list members and target graph IDs, ensuring accurate report distribution.
- Added tests for publish list handling in report sharing, including scenarios for missing publish lists and validation of existing linked entities.
@jfrench9
Copy link
Copy Markdown
Member Author

jfrench9 commented Apr 2, 2026

@claude please review this PR

@claude
Copy link
Copy Markdown

claude bot commented Apr 2, 2026

Claude finished @jfrench9's task in 3m 8s —— View job


PR Review: Add publish lists and enhance report sharing for RoboInvestor

  • Gather context and understand request
  • Read changed files
  • Review migration
  • Review new models and API models
  • Review routers (publish_lists, reports, securities, entity)
  • Review tests
  • Post comprehensive review

Good overall direction — the publish list abstraction for report sharing is clean and the multi-tenant wiring looks correct. A few bugs and gaps need attention before merge.


🔴 Critical Bugs

1. Incomplete downgrade() in migration — publish_lists table never dropped

migrations/extensions/versions/a1b2c3d4e5f7_...py, lines 134–155

def downgrade() -> None:
    ...
    op.drop_index("idx_plm_target", table_name="publish_list_members")
    op.drop_index("idx_plm_list", table_name="publish_list_members")
    op.drop_table("publish_list_members")
    op.drop_index("idx_publish_lists_name", table_name="publish_lists")
    # ← op.drop_table("publish_lists") IS MISSING

op.drop_table("publish_lists") is never called. Rolling back this migration leaves publish_lists orphaned in the public schema. Fix this →


2. Silent partial rollback in add_members

robosystems/routers/ledger/publish_lists.py, lines 357–373

for target_id in body.target_graph_ids:
    member = PublishListMember(...)
    session.add(member)
    try:
        session.flush()
        added.append(member)
    except IntegrityError:
        session.rollback()   # ← rolls back ALL previously flushed members
        raise HTTPException(status_code=409, ...)

If you add 3 IDs and the 3rd is a duplicate, session.rollback() silently discards the first two. The response is a 409 and none of the valid members are persisted. This is all-or-nothing behavior, which may be intentional, but:

  • Callers get no indication that the first two were rejected along with the third
  • Calling session.rollback() inside a context manager that owns the session lifecycle is fragile

Either: (a) pre-check for duplicates before inserting anything and return a clear error, or (b) use INSERT ... ON CONFLICT DO NOTHING / skip already-existing members. Fix this →


🟠 Significant Issues

3. No ownership authorization on publish list mutations

robosystems/routers/ledger/publish_lists.py, update_publish_list (line 229) and delete_publish_list (line 277)

Unlike delete_report and regenerate_report (which both check report_def.created_by != current_user.id → 403), any user with graph access can modify or delete any other user's publish list. There's no if row.created_by != current_user.id: raise HTTPException(403) guard. Fix this →


4. share_report opens N+1 sessions in the sharing loop

robosystems/routers/ledger/reports.py, lines 692–702

for target_graph_id in target_graph_ids:
    result = _share_to_target(...)
    if result.status == "shared":
        with extensions_session(graph_id) as source_session:  # ← N times
            share_record = ReportShare(...)
            source_session.commit()

One session open-commit-close cycle per target graph just to write a ReportShare row. Collect successful results and batch-insert at the end. Fix this →


5. N+1 queries in list_reports

robosystems/routers/ledger/reports.py, lines 279–283

for r in rows:
    structures = _load_structures(session, r.taxonomy_id)  # ← 1 query per report

_load_structures executes a SQL query per report. With 50 reports this is 51 queries. Consider grouping by taxonomy_id (likely only 1–2 distinct values) and deduplicating. Fix this →


🟡 Guidelines / Minor Issues

6. Migration was manually created — violates CLAUDE.md

The revision ID a1b2c3d4e5f7 is a manually crafted placeholder, and the file creation date 2026-04-01 23:00:00.000000 confirms this was written by hand. Per CLAUDE.md: "Never create migrations manually — always autogenerate." Additionally, the raw SQL in _create_publish_lists uses TIMESTAMPTZ while the Alembic op.create_table call uses sa.DateTime() (timezone-naive) — these diverge between the public schema and tenant schemas.

7. update_publish_list — null body.name in conflict error

publish_lists.py, line 252:

detail=f"A publish list named '{body.name}' already exists.",

body.name can be None in an UpdatePublishListRequest. This would surface as "A publish list named 'None' already exists." — confusing UX. Fix this →

8. AddMembersRequest — no max_length on target_graph_ids

models/api/extensions/publish_lists.py, line 25:

target_graph_ids: list[str] = Field(..., min_length=1)  # no max_length

Without a cap, a caller can submit thousands of IDs. Consider max_length=500 or similar.

9. Missing tests for the entire publish list router

robosystems/routers/ledger/publish_lists.py is 406 lines with zero test coverage. The new test_reports.py covers share_report with publish lists, but there are no tests for create_publish_list, list_publish_lists, get_publish_list, update_publish_list, delete_publish_list, add_members, or remove_member.

10. .gitignore scope broadened for .local/

Previously .local/config.json was ignored. This PR changes it to .local/ (entire directory). Likely intentional but worth confirming no local dev scripts or other files in .local/ should remain tracked.


✅ What Looks Good

  • PublishList and PublishListMember models — clean schema with correct FK cascade, ULID IDs, and appropriate indexes
  • _enrich_members — efficient batch lookup using a single platform DB query per call rather than per-member
  • Batch member count loading in list_publish_lists — one aggregated query instead of N count queries
  • _ensure_linked_entity — solid upsert pattern with metadata refresh on each share; the auto-link to securities is a nice touch
  • share_report provenance fieldssource_graph_id, source_report_id, shared_at on received reports is good data lineage
  • Investor securities refactor — the simplified router cleanly handles the pre-association pattern via source_graph_id
  • _share_to_target returns soft errors — prevents one bad target from aborting the whole share operation

- Added a maximum length constraint for graph IDs in AddMembersRequest to improve data validation.
- Implemented authorization checks in update and delete publish list routes to ensure only the creator can modify or delete their lists.
- Improved error handling for duplicate entries when adding members to a publish list.
- Optimized report listing by caching structure loading to reduce redundant database calls.
@jfrench9 jfrench9 merged commit f4466b0 into main Apr 2, 2026
7 checks passed
@jfrench9 jfrench9 deleted the feature/roboinvestor-share-reports branch April 2, 2026 02:18
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