Skip to content

fix(source-granola): declare missing record fields and datetime types - #84282

Draft
devin-ai-integration[bot] wants to merge 3 commits into
masterfrom
devin/1786498944-source-granola-schema-declarations
Draft

fix(source-granola): declare missing record fields and datetime types#84282
devin-ai-integration[bot] wants to merge 3 commits into
masterfrom
devin/1786498944-source-granola-schema-declarations

Conversation

@devin-ai-integration

Copy link
Copy Markdown
Contributor

What

source-granola certification sub-issue S-2 + S-6: the two inline schemas in manifest.yaml were hand-written from a partial view of the API and never re-diffed against live records or the vendor spec, so (a) all seven format: date-time properties were missing airbyte_type, and (b) six record fields present in every live record were not declared at all. Both schemas set additionalProperties: true, so those fields flowed through untyped, with no destination column and no schema contract.

Resolves https://github.com/airbytehq/airbyte-internal-issues/issues/16930:

This is sequenced to land before the incremental sub-issue (airbytehq/airbyte-internal-issues#16931), which needs notes.updated_at declared as its cursor field.

Devin session: https://app.devin.ai/sessions/d25de47b024f4522b779cfed4e4786ab

How

Manifest-only change to the two inline schemas:

  • airbyte_type: timestamp_with_timezone added to every format: date-time property (notes.created_at/updated_at; detailed_notes.created_at/updated_at, calendar_event.scheduled_start_time/scheduled_end_time, transcript[].start_time/end_time). spec.start_date uses format: date and is untouched.
  • Newly declared: notes.updated_at; detailed_notes.web_url; detailed_notes.space_membership; folder_membership[].parent_folder_id and .space_id; transcript[].speaker.attribution, .diarization_label and .name.
  • id remains the only required property on both streams, additionalProperties: true is unchanged, and nullable properties keep the file's existing type: ["null", string] convention.

Shapes were taken from live records, not guessed (see below). space_membership and folder_membership[].space_id are not in Granola's published OpenAPI, so they are declared permissively — nullable, nothing required inside.

Two deliberate omissions, both open to challenge:

  • web_url is declared without format: uri (the vendor declares it). Airbyte's destination type mappers only interpret date, time and date-time string formats; any other format is ignored with a warning, so adding it would produce log noise and no typing benefit.
  • No shared $ref'd shape for folder / transcript. The parity sub-issue (airbytehq/airbyte-internal-issues#16932) will add folders and note_transcripts streams that overlap these shapes. Factoring them out now would mean introducing an abstraction with a single consumer inside a PR that is otherwise purely additive; the second consumer arrives with that sub-issue, which is where the extraction belongs.
  • metadata.autoImportSchema left at false for both streams. It only affects Connector Builder drafts — useAutoImportSchema.ts in the webapp returns false unless the displayed version is a draft — so it would not keep this repo-managed manifest in sync with the API, and flipping it would let a Builder round-trip overwrite these curated declarations.

Review guide

  1. airbyte-integrations/connectors/source-granola/manifest.yaml — the schema declarations
  2. airbyte-integrations/connectors/source-granola/unit_tests/test_schema_declarations.py — the two regression tests
  3. metadata.yaml + docs/integrations/sources/granola.md — version bump and changelog

Reproduction and live verification

Reproduced against the real Granola API (credentials from 1Password direct-connector-credentials/granola; the SECRET_SOURCE-GRANOLA__CREDS secret named in metadata.yaml does not exist in dataline-integration-testing, which is worth fixing separately). Full-catalog read on master with airbyte/source-declarative-manifest:7.24.0: 136 notes + 136 detailed_notes records, then every record key diffed recursively against the declared schemas.

Show/Hide live evidence

Undeclared fields on master:

notes           -> updated_at 136/136
detailed_notes  -> web_url 136/136, space_membership 136/136,
                   folder_membership[].parent_folder_id 134/134,
                   folder_membership[].space_id 134/134,
                   transcript[].speaker.attribution, .diarization_label

Observed shapes:

space_membership   -> array, length 1 in 136/136 records, never null/empty
                      [{"object": "space", "id": "fol_zcpRJYqp455p0o", "name": "Airbyte team"}]
folder_membership[] -> {"object": "folder", "id": "fol_9PXzHG3MuC6pzy", "name": "Pylon",
                        "parent_folder_id": null, "space_id": "fol_zcpRJYqp455p0o"}
                       (134 elements; parent_folder_id null in all, space_id always a string)
web_url            -> string, non-null 136/136, e.g. https://notes.granola.ai/d/c9a54e60-...
notes.updated_at   -> string, non-null 136/136, e.g. 2025-11-12T23:33:17.158Z

Across 41,953 transcript[].speaker objects: source 41,953, attribution 40,816 (me/them), diarization_label 1,137 (e.g. Speaker A), never both on one object. name was never observed — it is declared from the vendor Speaker schema, which documents it as present only when a speaker is identified.

Datetime values all carry a timezone, which is what makes timestamp_with_timezone the right annotation: notes/transcript timestamps are Z-suffixed UTC (2025-11-10T22:00:41.213Z), and the two calendar_event ones carry explicit offsets (2025-11-10T14:00:00-08:00). No nulls in any of the seven.

After the fix, the same read against the same account reports zero undeclared fields in both streams.

Declarative-First Evaluation

This is a manifest-only (language:manifest-only, cdk:low-code) connector and the fix is entirely declarative: the change is confined to the two InlineSchemaLoader schemas under the manifest's top-level schemas: key. No custom Python component was written or needed — there is no runtime behaviour change at all, only schema declarations, so RecordFilter, AddFields/RemoveFields, transformations and the rest were not applicable.

Test Coverage

New unit_tests/ package (the connector had none), modelled on source-gocardless/unit_tests:

  • test_schema_declarations_cover_mocked_records — mocks /v1/notes and /v1/notes/{id} with payloads modelled on the real records above, reads both streams, and asserts that the set of record paths absent from the declared schema is empty, walking nested objects and array items recursively.
  • test_date_time_properties_declare_timezone_type — walks both schemas and asserts every format: date-time property declares airbyte_type: timestamp_with_timezone, so future datetime properties are covered too.

Both fail on master (assert {'updated_at'} == set() and the datetime assertion) and pass with this change: poe test-unit-tests2 passed. poe test-integration-tests2 passed, 6 skipped, unchanged from master (the 6 skips are acceptance scenarios that need the acceptance-test-config.yml tracked in airbytehq/airbyte-internal-issues#16924; the issue's stated 8 passed, 0 skipped baseline did not reproduce, on master either).

Breaking change evaluation

Assessed as non-breaking → MINOR bump, 0.2.110.3.0. No releases.breakingChanges entry and no migration guide. The reasoning, so a reviewer can challenge it:

  • Adding airbyte_type: timestamp_with_timezone to a property that already declares format: date-time does not change the destination column type. Every destination type mapper treats a missing airbyte_type on a date-time as timestamp_with_timezone already — AirbyteProtocolType.kt:71-84 (airbyteType == null || ... "timestamp_with_timezone"TIMESTAMP_WITH_TIMEZONE), AirbyteJsonSchemaType.kt:137-141 (null, "timestamp_with_timezone" -> TIMESTAMP_WITH_TIMEZONE), and JsonSchemaToAirbyteType.kt in the Bulk CDK. So these columns are already timestamps today; this change only makes the existing behaviour explicit.
  • The breaking-change rule that covers format/airbyte_type is about adding or changing a format hint ({"type":"string"}{"type":"string","format":"date-time"}), which flips a column from string to timestamp. No format is added, removed or changed here. The source-pinterest precedent (🚨🚨🐛 Source Pinterest: Update date-time fields with airbyte_type: timestamp_without_timezone #32595) changed field types; this does not.
  • Everything else is purely additive: new properties on schemas that already set additionalProperties: true. No field is removed or renamed, no type changes, primary key and cursor are untouched, no spec or state change. Users will need a schema refresh for the new columns to appear, which is the normal additive path.
  • enableProgressiveRollout is false, so no -rc.N suffix.

User Impact

notes.updated_at, detailed_notes.web_url and detailed_notes.space_membership (plus the nested folder/speaker fields) become declared, typed columns after a schema refresh instead of untyped passthrough data. The seven datetime columns are unchanged in behaviour — the annotation documents what destinations already do.

Can this PR be safely reverted and rolled back?

  • YES 💚
  • NO ❌

Co-Authored-By: bot_apk <apk@cognition.ai>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@github-actions

Copy link
Copy Markdown
Contributor

👋 Greetings, Airbyte Team Member!

Here are some helpful tips and reminders for your convenience.

💡 Show Tips and Tricks

PR Slash Commands

Airbyte Maintainers (that's you!) can execute the following slash commands on your PR:

  • 🛠️ Quick Fixes
    • /format-fix - Fixes most formatting issues.
    • /bump-version - Bumps connector versions, scraping changelog description from the PR title.
      • Bump types: patch (default), minor, major, major_rc, rc, promote.
      • The rc type is a smart default: applies minor_rc if stable, or bumps the RC number if already RC.
      • The promote type strips the RC suffix to finalize a release.
      • Example: /bump-version type=rc or /bump-version type=minor
    • /bump-progressive-rollout-version - Alias for /bump-version type=rc. Bumps with an RC suffix and enables progressive rollout.
  • ❇️ AI Testing and Review (internal link: AI-SDLC Docs):
    • /ai-prove-fix - Runs prerelease readiness checks, including testing against customer connections.
    • /ai-canary-prerelease - Rolls out prerelease to 5-10 connections for canary testing.
    • /ai-review - AI-powered PR review for connector safety and quality gates.
  • 📝 AI Documentation:
    • /ai-docs-review - AI-powered documentation review for PRs with connector changes.
    • /ai-create-docs-pr - Creates a documentation PR for connector changes, stacked on the current PR.
  • 🚀 Connector Releases:
    • /publish-connectors-prerelease - Publishes pre-release connector builds (tagged as {version}-preview.{git-sha}) for all modified connectors in the PR.
    • /enable-autopilot-rollouts - Enables autopilot progressive rollouts for the modified connector(s) in the PR, remediating "autopilot rollouts not enabled for {connector-name}" auto-merge blockers. Sets defaultRolloutMode: autopilot and enableProgressiveRollout: true, preserving any existing autopilotConfig.
      • Optional args: connector=<CONNECTOR_NAME> (defaults to the modified connectors in the PR), strategy=fast|slow|default (defaults to fast).
      • Example: /enable-autopilot-rollouts or /enable-autopilot-rollouts connector=source-faker strategy=slow
  • ☕️ JVM connectors:
    • /update-connector-cdk-version connector=<CONNECTOR_NAME> - Updates the specified connector to the latest CDK version.
      Example: /update-connector-cdk-version connector=destination-bigquery
  • 🐍 Python connectors:
    • /poe connector source-example lock - Run the Poe lock task on the source-example connector, committing the results back to the branch.
    • /poe source example lock - Alias for /poe connector source-example lock.
    • /poe source example use-cdk-branch my/branch - Pin the source-example CDK reference to the branch name specified.
    • /poe source example use-cdk-latest - Update the source-example CDK dependency to the latest available version.
  • ⚙️ Admin commands:
    • /force-merge reason="<REASON>" - Force merges the PR using admin privileges, bypassing CI checks. Requires a reason.
      Example: /force-merge reason="CI is flaky, tests pass locally"
📚 Show Repo Guidance

Helpful Resources

📝 Edit this welcome message.

@github-actions

Copy link
Copy Markdown
Contributor

Note

Autopilot progressive rollouts are not enabled for the following modified connector(s):

  • source-granola

This is a courtesy heads-up only — it does not block merge or fail any check.
To enable automatic progressive rollouts for the connector(s) above, comment
/enable-autopilot-rollouts on this PR. This sets defaultRolloutMode: autopilot
and enableProgressiveRollout: true in each connector's metadata.yaml,
preserving any existing autopilotConfig.

Co-Authored-By: bot_apk <apk@cognition.ai>
@github-actions

github-actions Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Deploy preview for airbyte-docs ready!

Project:airbyte-docs
Status: ✅  Deploy successful!
Preview URL:https://airbyte-docs-cvep2leky-airbyte-growth.vercel.app
Latest Commit:57aff5a

Deployed with vercel-action

Co-Authored-By: bot_apk <apk@cognition.ai>
@github-actions

Copy link
Copy Markdown
Contributor

source-granola Connector Test Results

5 tests   3 ✅  23s ⏱️
2 suites  2 💤
2 files    0 ❌

Results for commit 57aff5a.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

connectors/source/granola hyd-fix Hydra: ai-fix stage has run

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants