Skip to content

feat(ingestion): Outline wiki connector with Confluence-parity silver staging - #1312

Merged
mitasovr merged 3 commits into
constructorfabric:mainfrom
mitasovr:claude/objective-cohen-764588
Jun 15, 2026
Merged

feat(ingestion): Outline wiki connector with Confluence-parity silver staging#1312
mitasovr merged 3 commits into
constructorfabric:mainfrom
mitasovr:claude/objective-cohen-764588

Conversation

@mitasovr

@mitasovr mitasovr commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds an Outline (getoutline.com) connector as the second contributor to the wiki silver classes (class_wiki_pages, class_wiki_activity, class_wiki_engagement), collecting values analogous to the existing wiki/confluence connector.

Streams (src/ingestion/connectors/wiki/outline/)

Stream Outline endpoint Sync mode
wiki_spaces collections.list full refresh
wiki_pages documents.list (statusFilter: [published, archived]) incremental, client-side cursor on updated_at
wiki_page_versions revisions.list (substream of wiki_pages) full refresh per parent
wiki_comments comments.list workspace-wide full refresh
wiki_users users.list (email directory) full refresh

The Outline API is RPC-style: every endpoint is POST {instance}/api/{method} with a JSON body, Bearer API key, offset/limit pagination injected into body_json. 429 is handled via Retry-After capped at 600s (same policy as Confluence). The manifest is fully inlined (no $ref) and passes both validate and validate-strict.

Mapping vs Confluence

  • collection → space, document → page, revision → page version
  • Confluence's 4 comment streams collapse into one threaded stream: reply = parentCommentId set, inline-vs-footer = anchorText non-empty (includeAnchorText: true)
  • Outline revisions have no version number (UUIDs) — the ordinal is derived in silver via row_number() over createdAt; pages_created counts ordinal 1
  • staging models keep column order positionally identical to confluence__* (union_by_tag emits positional UNION ALL); silver/wiki/schema.yml accepted_values extended with outline / insight_outline

Live-tested (self-hosted instance, 2026-06-12)

47 collections / 11,314 documents / 1,758 comments / 484 users — all 5 streams emit with 0 errors, every record carries tenant_id/source_id/unique_key, incremental resume returns a strict subset (8 of 11,314), substream verified end-to-end on a small collection.

Findings baked into the design:

  • statusFilter: ["draft"] → HTTP 500 on self-hosted Outline, and drafts are owner-scoped anyway → drafts excluded by design (documented in README)
  • embedded createdBy/updatedBy objects carry no email field on self-hosted instances (0 of 11,314 pages) → staging models LEFT JOIN the connector's own wiki_users stream, coalesce(embedded, wiki_users.email) keeps cloud deployments working
  • 11k+ documents → 11k+ substream partitions exceeds the 10k CDK futures limit → default_concurrency: 4, same deadlock class fixed for jira/confluence in fix(ingestion): bump jira/confluence concurrency to 4 to break CDK partition deadlock #1308

Validation

  • source.sh validate + validate-strict: both pass
  • dbt parse + dbt compile of all tag:outline models: clean
  • audit_rmt_read_dedup.py: no findings for outline models (every bronze read dedups via QUALIFY row_number())
  • bronze → RMT promotion bootstrap (outline__bronze_promoted) covers all 5 streams

Operational note

A full wiki_page_versions sync makes one revisions.list call per document (~1h for an 11k-doc instance at concurrency 4); the other four streams finish in minutes. A possible follow-up is deriving activity from events.list to avoid the per-document sweep.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added an Outline wiki connector to ingest spaces, pages, page revisions, comments, and users.
    • Added dbt models for wiki pages, daily edit activity (sessions), and comment engagement metrics.
    • Extended Silver wiki schemas to support the Outline source (including engagement/activity contributors).
  • Bug Fixes

    • Improved Outline ingestion reliability with configured retry behavior for rate limits and transient errors.
  • Documentation

    • Added connector README and example secret/descriptor files for authentication, pagination, and sync behavior.
    • Documented identity-resolution inputs requirements for Outline user email handling.

…lver staging

Second contributor to the wiki silver classes (class_wiki_pages,
class_wiki_activity, class_wiki_engagement) alongside Confluence.

Connector (wiki/outline, nocode, fully-inlined manifest — passes both
validate and validate-strict):
- wiki_spaces        <- collections.list (full refresh)
- wiki_pages         <- documents.list, client-side incremental on
                        updated_at, statusFilter [published, archived]
- wiki_page_versions <- revisions.list (substream of wiki_pages)
- wiki_comments      <- comments.list workspace-wide (single threaded
                        stream: reply = parentCommentId, inline = anchorText)
- wiki_users         <- users.list (email directory for identity resolution)

The Outline API is RPC-style (POST {instance}/api/{method}, Bearer key,
offset/limit pagination in the JSON body); 429 handled via Retry-After
capped at 600s, same policy as Confluence.

Live-tested against a self-hosted instance (47 collections / 11314 docs /
1758 comments / 484 users; all 5 streams green, incremental resume
verified). Findings baked in:
- statusFilter ["draft"] returns HTTP 500 on self-hosted Outline and
  drafts are owner-scoped anyway -> drafts excluded by design
- embedded createdBy/updatedBy objects carry no email field on
  self-hosted instances -> staging models LEFT JOIN the connector's own
  wiki_users stream (coalesce embedded-first for cloud deployments)
- 11k+ documents -> 11k+ substream partitions exceeds the 10k CDK
  futures limit -> default_concurrency: 4 (same deadlock class fixed
  for jira/confluence in constructorfabric#1308)

dbt: outline__bronze_promoted (RMT promotion for 5 bronze tables) +
3 staging models tagged silver:class_wiki_*; column order kept
positionally identical to the confluence__* models (union_by_tag emits
positional UNION ALL). silver/wiki/schema.yml accepted_values extended
with outline / insight_outline.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Roman Mitasov <Roman.Mitasov@constructor.tech>
@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d04b024e-cb1b-4874-8440-cb5e18f24565

📥 Commits

Reviewing files that changed from the base of the PR and between 005ca1c and cc1b88e.

📒 Files selected for processing (1)
  • src/ingestion/silver/_shared/identity_inputs.sql
✅ Files skipped from review due to trivial changes (1)
  • src/ingestion/silver/_shared/identity_inputs.sql

📝 Walkthrough

Walkthrough

Adds the Outline wiki connector with Bearer token authentication, five RPC-based ingestion streams (spaces, pages, versions, comments, users), Bronze deduplication and RMT promotion, identity pipeline (snapshot, fields history, identity inputs), and three Silver staging models that compute page metadata, per-author activity sessions, and per-page engagement metrics.

Changes

Outline Wiki Connector

Layer / File(s) Summary
Connector documentation, descriptor, and workflow guidance
src/ingestion/connectors/wiki/outline/README.md, src/ingestion/connectors/wiki/outline/descriptor.yaml, src/ingestion/secrets/connectors/outline.yaml.example, cypilot/.core/skills/connector/workflows/create.md, cypilot/.core/skills/connector/workflows/validate.md
Documents Outline RPC API authentication (Bearer token), configuration (instance URL, API token, optional start date and page size), ingestion streams (spaces, pages with incremental sync, versions, comments, users), identity-resolution guidance and validation checklist, and limitations (drafts, deleted docs, analytics gaps, revision fetch performance). Establishes connector version 1.0.0, schedule, secret keys, and Kubernetes Secret template.
RPC stream definitions, auth, and connector spec
src/ingestion/connectors/wiki/outline/connector.yaml
Defines five POST-based RPC streams with offset/limit pagination, record extraction from data field, Bearer token auth, error handling (retry 429/503 via Retry-After, fail 401/403), tenant/source stamping, timestamp normalization, unique_key computation, concurrency_level=4, and disabled auto-imported schemas. Includes connection specification with required/optional configuration fields.
Bronze-to-RMT promotion bootstrap
src/ingestion/connectors/wiki/outline/dbt/outline__bronze_promoted.sql
Declares an idempotent promotion trigger (materialized as view in staging schema) that invokes promote_bronze_to_rmt macros for all five Outline tables, ordered by unique_key.
Identity pipeline (snapshot → fields history → identity inputs)
src/ingestion/connectors/wiki/outline/dbt/outline__users_snapshot.sql, src/ingestion/connectors/wiki/outline/dbt/outline__users_fields_history.sql, src/ingestion/connectors/wiki/outline/dbt/outline__identity_inputs.sql, src/ingestion/connectors/wiki/outline/dbt/schema.yml, src/ingestion/silver/_shared/identity_inputs.sql
Adds users snapshot (incremental append tracking name/email/role/is_suspended), fields_history table to generate historical field values, and identity_inputs model wired to emit email/name identities and deactivate on suspension. Includes dbt schema definitions with column-level tests (not_null/unique) and accepted_values constraints. Adds shared identity_inputs dependency reference.
Silver staging models and dbt schema
src/ingestion/connectors/wiki/outline/dbt/outline__wiki_pages.sql, src/ingestion/connectors/wiki/outline/dbt/outline__wiki_activity.sql, src/ingestion/connectors/wiki/outline/dbt/outline__wiki_engagement.sql
Implements three Silver views: outline__wiki_pages deduplicates and joins pages with spaces and users to resolve author/editor emails; outline__wiki_activity collapses consecutive revisions into 30-minute sessions and aggregates per-author per-day edits/page counts; outline__wiki_engagement aggregates comments per page per day with reply/inline classification and engagement metrics.
Silver schema constraints and integration
src/ingestion/silver/wiki/schema.yml
Updates Silver schema to accept source: outline and data_source: insight_outline across class_wiki_pages, class_wiki_activity, and class_wiki_engagement. Refines documentation to note Outline staging reads email fields directly from bronze and lists Confluence and Outline as contributors.

Sequence Diagram

sequenceDiagram
  participant OutlineAPI as Outline API
  participant Connector as Connector CDK
  participant Bronze as Bronze Layer
  participant RMT as RMT
  participant Silver as Silver Staging

  OutlineAPI->>Connector: /collections.list (Bearer auth)
  OutlineAPI->>Connector: /documents.list (incremental via updated_at)
  OutlineAPI->>Connector: /revisions.list (partitioned by page_id)
  OutlineAPI->>Connector: /comments.list
  OutlineAPI->>Connector: /users.list
  
  Connector->>Connector: stamp tenant/source, derive unique_key, normalize timestamps
  Connector->>Bronze: write deduplicated records
  
  Bronze->>RMT: promote via outline__bronze_promoted
  
  RMT->>Silver: outline__wiki_pages (join spaces + users for email)
  RMT->>Silver: outline__wiki_activity (collapse sessions, aggregate)
  RMT->>Silver: outline__wiki_engagement (aggregate comments per page)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 A rabbit hops through Outline's halls so bright,
Spaces and pages gathered in the night,
Sessions collapse like folds in time,
Each comment counts, each edit rhymes,
Bronze to Silver, data takes its flight! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and specifically describes the main change: adding an Outline wiki connector with feature parity to Confluence in the silver staging layer.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/ingestion/connectors/wiki/outline/dbt/outline__wiki_pages.sql (1)

48-48: 💤 Low value

Consider the semantics of version_count = 0.

The coalesce(version_number, 0) at line 48 ensures version_count is never NULL (satisfying the not_null test at schema.yml:36-38), but it allows pages to have version_count = 0. Semantically, a page should have at least one version (the creation event). If version_number can legitimately be NULL in the bronze table, consider whether downstream consumers expect version_count ≥ 1 or if zero is a valid sentinel for "uncounted."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ingestion/connectors/wiki/outline/dbt/outline__wiki_pages.sql` at line
48, The select currently uses toUInt32(coalesce(version_number, 0)) AS
version_count which allows pages to have version_count = 0; change the coalesce
default to 1 (or otherwise enforce a minimum of 1) so a page always reports at
least one version: e.g. toUInt32(coalesce(version_number, 1)) AS version_count
or toUInt32(greatest(coalesce(version_number, 0), 1)) AS version_count, and
adjust any downstream expectations/tests if you intentionally want to keep 0 as
a sentinel.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/ingestion/connectors/wiki/outline/descriptor.yaml`:
- Around line 15-18: The descriptor.yaml's required_fields list erroneously
includes outline_start_date causing a contract mismatch; remove
outline_start_date from the required_fields array in
src/ingestion/connectors/wiki/outline/descriptor.yaml so the descriptor matches
the connector spec (connector.yaml) which provides a default for
outline_start_date and treats it as optional.

---

Nitpick comments:
In `@src/ingestion/connectors/wiki/outline/dbt/outline__wiki_pages.sql`:
- Line 48: The select currently uses toUInt32(coalesce(version_number, 0)) AS
version_count which allows pages to have version_count = 0; change the coalesce
default to 1 (or otherwise enforce a minimum of 1) so a page always reports at
least one version: e.g. toUInt32(coalesce(version_number, 1)) AS version_count
or toUInt32(greatest(coalesce(version_number, 0), 1)) AS version_count, and
adjust any downstream expectations/tests if you intentionally want to keep 0 as
a sentinel.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1809479b-8ecd-4d47-b004-4b4193bf783f

📥 Commits

Reviewing files that changed from the base of the PR and between 80ca589 and cff04d1.

📒 Files selected for processing (10)
  • src/ingestion/connectors/wiki/outline/README.md
  • src/ingestion/connectors/wiki/outline/connector.yaml
  • src/ingestion/connectors/wiki/outline/dbt/outline__bronze_promoted.sql
  • src/ingestion/connectors/wiki/outline/dbt/outline__wiki_activity.sql
  • src/ingestion/connectors/wiki/outline/dbt/outline__wiki_engagement.sql
  • src/ingestion/connectors/wiki/outline/dbt/outline__wiki_pages.sql
  • src/ingestion/connectors/wiki/outline/dbt/schema.yml
  • src/ingestion/connectors/wiki/outline/descriptor.yaml
  • src/ingestion/secrets/connectors/outline.yaml.example
  • src/ingestion/silver/wiki/schema.yml

Comment on lines +15 to +18
required_fields:
- outline_instance_url
- outline_api_token
- outline_start_date

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Configuration contract inconsistency: outline_start_date required status.

outline_start_date is listed as required here, but the connector spec (connector.yaml lines 764-768) does NOT include it in the required array, and it has a default value (lines 798-806: default: "2020-01-01"). The README (line 41) also documents it as optional.

Either remove outline_start_date from this required_fields list, or add it to the connector spec's required array and remove the default. The typical pattern is to make it optional with a default (as the connector spec currently does), so the fix would be to remove it from this list.

🔧 Proposed fix
 secret:
   required_fields:
     - outline_instance_url
     - outline_api_token
-    - outline_start_date
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
required_fields:
- outline_instance_url
- outline_api_token
- outline_start_date
required_fields:
- outline_instance_url
- outline_api_token
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ingestion/connectors/wiki/outline/descriptor.yaml` around lines 15 - 18,
The descriptor.yaml's required_fields list erroneously includes
outline_start_date causing a contract mismatch; remove outline_start_date from
the required_fields array in
src/ingestion/connectors/wiki/outline/descriptor.yaml so the descriptor matches
the connector spec (connector.yaml) which provides a default for
outline_start_date and treats it as optional.

Add the standard identity-inputs macro chain for the outline connector —
wiki_users exposes emails, so the connector must contribute observations
to identity.identity_inputs like zoom/zulip-proxy/bamboohr/ms-entra do:

- outline__users_snapshot: SCD2 snapshot of wiki_users
  (name/email/role/is_suspended)
- outline__users_fields_history: field-level change log, entity_id =
  user_id (Outline user UUID)
- outline__identity_inputs: email + display_name observations plus the
  canonical id binding row (emitted by the macro per ADR-0002);
  suspension (is_suspended=true) emits DELETE rows
- silver/_shared/identity_inputs.sql: depends_on line for first-run
  build ordering

Skill docs updated to close the gap that allowed this omission:

- connector-create.md gains §3.6b — when the chain is REQUIRED, model
  templates for all three macros, and the rules (canonical id row is
  automatic, bool fields stringify to 'true'/'false', snapshot source
  must be RMT-promoted, shared-union depends_on)
- connector-validate.md gains an "Identity Resolution inputs" checklist
  section

Verified: dbt compile of the new chain plus the shared identity_inputs
union is clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Roman Mitasov <Roman.Mitasov@constructor.tech>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/ingestion/connectors/wiki/outline/dbt/schema.yml (1)

14-34: 💤 Low value

Consider adding column-level tests for outline__users_fields_history and outline__identity_inputs.

The outline__users_snapshot model includes a not_null test on unique_key (line 21-22), but outline__users_fields_history and outline__identity_inputs have no column-level tests. Consider adding tests for critical columns such as entity_id, field_name, or identity key columns to catch data quality issues early.

📋 Example tests to add
  - name: outline__users_fields_history
    description: >
      Field-level change log derived from the users snapshot. One row per
      changed field per version transition.
    columns:
      - name: entity_id
        tests:
          - not_null
      - name: field_name
        tests:
          - not_null

  - name: outline__identity_inputs
    description: >
      Identity Manager input rows emitted from the user fields-history.
      Contributes `email` and `display_name` (from `name`) value types plus
      the canonical `id` binding row. Deactivation triggered when
      `is_suspended=true`.
    columns:
      - name: source_type
        tests:
          - not_null
      - name: value_type
        tests:
          - not_null
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ingestion/connectors/wiki/outline/dbt/schema.yml` around lines 14 - 34,
Add column-level tests to the two models missing them: update the
outline__users_fields_history and outline__identity_inputs entries in schema.yml
to declare critical columns and attach tests (e.g., for
outline__users_fields_history add columns entity_id and field_name with a
not_null test; for outline__identity_inputs add columns like source_type and
value_type and/or identity key columns with not_null tests). Keep the existing
outline__users_snapshot tests intact and mirror its style when adding the new
columns/tests so dbt picks them up.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/ingestion/connectors/wiki/outline/dbt/schema.yml`:
- Around line 14-34: Add column-level tests to the two models missing them:
update the outline__users_fields_history and outline__identity_inputs entries in
schema.yml to declare critical columns and attach tests (e.g., for
outline__users_fields_history add columns entity_id and field_name with a
not_null test; for outline__identity_inputs add columns like source_type and
value_type and/or identity key columns with not_null tests). Keep the existing
outline__users_snapshot tests intact and mirror its style when adding the new
columns/tests so dbt picks them up.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1a4df81c-f812-43bb-b566-a6c24622d2f6

📥 Commits

Reviewing files that changed from the base of the PR and between cff04d1 and 005ca1c.

📒 Files selected for processing (8)
  • cypilot/.core/skills/connector/workflows/create.md
  • cypilot/.core/skills/connector/workflows/validate.md
  • src/ingestion/connectors/wiki/outline/README.md
  • src/ingestion/connectors/wiki/outline/dbt/outline__identity_inputs.sql
  • src/ingestion/connectors/wiki/outline/dbt/outline__users_fields_history.sql
  • src/ingestion/connectors/wiki/outline/dbt/outline__users_snapshot.sql
  • src/ingestion/connectors/wiki/outline/dbt/schema.yml
  • src/ingestion/silver/_shared/identity_inputs.sql
✅ Files skipped from review due to trivial changes (2)
  • src/ingestion/silver/_shared/identity_inputs.sql
  • cypilot/.core/skills/connector/workflows/validate.md

…hen-764588

# Conflicts:
#	src/ingestion/silver/_shared/identity_inputs.sql
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.

2 participants