Skip to content

[PR #616] feat(jira): auto-discover projects — remove mandatory jira_project_keys - #941

Closed
il10241024 wants to merge 6 commits into
mainfrom
feat/jira-auto-project-discovery
Closed

[PR #616] feat(jira): auto-discover projects — remove mandatory jira_project_keys#941
il10241024 wants to merge 6 commits into
mainfrom
feat/jira-auto-project-discovery

Conversation

@il10241024

@il10241024 il10241024 commented Jun 5, 2026

Copy link
Copy Markdown

🔗 Mirrored PR cyberfabric/cyber-insight#616 | Author: mozhaev-dev | Opened: 2026-06-02T14:53:33Z | Status: open
GitHub API does not allow setting PR author or timestamps — attribution preserved here.


Problem

The Jira connector required jira_project_keys (comma-separated list) to be manually maintained in the K8s secret. As Jira projects are frequently added/removed, this became operationally painful.

Solution

Switch jira_issue stream to SubstreamPartitionRouter: on every sync the connector queries /rest/api/3/project/search to get all accessible project keys, then runs one JQL request per project:

project = "<KEY>" AND updated >= "..." AND updated <= "..."

New projects are picked up automatically on the next scheduled sync. Deleted/archived projects simply disappear from the parent stream and are no longer queried.

Changes

File What changed
connector.yaml Added SubstreamPartitionRouter with inline jira_project_keys parent stream to jira_issue.retriever; updated JQL to stream_slice.project_key; removed jira_project_keys from spec
descriptor.yaml Removed jira_project_keys from secret.required_fields
jira.yaml.example Removed jira_project_keys entry

State migration

Existing connections will lose their incremental cursor state and perform a full re-sync from jira_start_date on first run after upgrade. This is acceptable — current data has no production value.

Architecture

sync start
  └─ jira_project_keys (parent stream, internal)
       GET /rest/api/3/project/search  →  [PROJ-A, PROJ-B, PROJ-C, ...]
            │
            ├─ jira_issue [PROJ-A] × [t₀..t₁] [t₁..t₂] ...
            ├─ jira_issue [PROJ-B] × [t₀..t₁] [t₁..t₂] ...
            └─ jira_issue [PROJ-C] × [t₀..t₁] ...

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Refactor

    • Removed project-key allowlist — connector now auto-discovers projects and runs per-project syncs; upgrades may drop per-source incremental cursors and trigger full re-syncs from the configured start date.
    • Sync window end moved forward by a 14-hour buffer.
  • Documentation

    • Docs, ADRs, README and examples updated to reflect automatic project discovery and revised configuration guidance.
  • Chores

    • Connector version bumped to 2.0.0; example secret manifest updated (project key field removed).

mozhaev-dev and others added 6 commits June 2, 2026 17:52
Remove mandatory `jira_project_keys` config. The connector now queries
`/rest/api/3/project/search` at the start of each sync to get all
accessible project keys, then issues one JQL request per project:

  project = "<KEY>" AND updated >= "..." AND updated <= "..."

This eliminates the need to manually update the secret when Jira
projects are added or removed.

Changes
-------
- connector.yaml: add SubstreamPartitionRouter to jira_issue.retriever
  that uses an inline jira_project_keys parent stream
- connector.yaml: update shared JQL to use stream_slice.project_key
- connector.yaml: remove jira_project_keys from spec.required and
  spec.properties
- descriptor.yaml: remove jira_project_keys from secret.required_fields
- jira.yaml.example: remove jira_project_keys entry

State note: existing connections lose per-source incremental state;
full re-sync from jira_start_date will occur on first run after upgrade.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…N/PRD/README/YouTrack-ADR

Review feedback from PR #616:

1. Rename inline partition stream jira_project_keys → jira_project_discovery
   to avoid confusion with the removed config field.

2. Add Jira ADR-001 (auto-project-discovery) documenting the rationale
   for removing the jira_project_keys allowlist in favour of
   SubstreamPartitionRouter + token-scoped permissions.

3. Update DESIGN.md:
   - Remove jira_project_keys row from connection spec table
   - Update JQL example to project = "<KEY>" form
   - Add ADR-001 reference and rationale note

4. Update PRD.md step 4: replace manual project scope selection with
   auto-discovery description.

5. Update Jira connector README:
   - Remove jira_project_keys from prerequisites, K8s Secret example,
     fields table, and operational constraints
   - Add project scope note pointing to ADR-001

6. Update YouTrack ADR-003:
   - Context now reflects that Jira previously had jira_project_keys
     but removed it (ADR-001); Jira and YouTrack converge on full-ingestion
   - Fix Option 2 description and Traceability section accordingly
   - Remove stale cross-reference to jira_project_keys

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Jira interprets bare datetime literals in the user's local timezone.
Sending now_utc() as-is creates a blind spot for users east of UTC:
e.g. Europe/Sofia (UTC+3) shifts the JQL upper bound 3 h back, silently
excluding issues updated in the last 3 h of the scan window.

Adding PT14H (the maximum positive UTC offset globally) ensures the JQL
end bound is always in the future relative to the actual current moment
for any Jira user timezone.

Discovered during local end-to-end test against darthvolt.atlassian.net.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…DR-001

- PRD.md line 481: (see ADR-007) → (see ADR-001) — dangling ref caught
  in review; would have failed Cypilot cross-reference validation.
- ADR-001 §Known Behaviors: document two pre-existing edge cases raised
  in review as non-blockers:
  - /project/search returns archived projects by default (follow-up:
    add status=live filter if needed).
  - jira_issue uses OffsetIncrement paginator; nextPageToken-based
    pagination is a follow-up improvement.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Auto-discovery via SubstreamPartitionRouter changes the per-partition
incremental cursor state shape. Existing connections will perform a
full re-sync from jira_start_date on the first run after the upgrade,
rebuilding the staging tables with the new state layout.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Resolve conflict in jira/descriptor.yaml: keep version 2.0.0 (major bump for
the breaking removal of mandatory jira_project_keys) over main's 1.1.1 patch
bump (#609, #614). 2.0.0 > 1.1.1, and main's patch fixes auto-merged cleanly
into connector.yaml. Verified: merged tree vs main == the PR's original
8-file diff (+196/-31), unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR implements a foundational shift in how the Jira connector discovers and syncs projects. Instead of requiring operators to manually specify allowed project keys via configuration, the connector now automatically discovers all projects accessible to the provided API token at sync start, using Jira's project search endpoint. Issues are then partitioned and queried per discovered project using token-scoped Browse Projects permissions as the access boundary.

Changes

Jira auto-discovery migration

Layer / File(s) Summary
Architectural decision and specification updates
docs/components/connectors/task-tracking/jira/specs/ADR/ADR-001-auto-project-discovery.md, docs/components/connectors/task-tracking/jira/specs/DESIGN.md, docs/components/connectors/task-tracking/jira/specs/PRD.md
ADR-001 documents the switch from static jira_project_keys to automatic discovery via /rest/api/3/project/search; DESIGN.md updates JiraInstance, removes jira_project_keys from connection_specification, and changes incremental JQL from multi-project project IN ({project_keys}) to per-partition project = "{project_key}"; PRD.md adds the auto-discovery step to the connection configuration flow.
Partition router implementation and JQL wiring
src/ingestion/connectors/task-tracking/jira/connector.yaml
The shared request JQL template now uses stream_slice.project_key instead of config['jira_project_keys']. jira_issue gains a partition_router using SubstreamPartitionRouter with a parent jira_project_discovery substream that queries the project search endpoint and extracts project_key as partition values. The incremental end_datetime is changed to now_utc() + 14 hours. jira_project_keys is removed from connection_specification.required and property schema.
Configuration and secrets schema cleanup
src/ingestion/connectors/task-tracking/jira/descriptor.yaml, src/ingestion/secrets/connectors/jira.yaml.example
Connector version bumped to 2.0.0. secret.required_fields no longer includes jira_project_keys; the Secret example YAML removes the jira_project_keys entry.
Operational documentation for token-based scoping
src/ingestion/connectors/task-tracking/jira/README.md
Prerequisites updated to require an Atlassian account/token with Browse Projects permission and to scope ingestion via token permissions. K8s Secret example removes jira_project_keys. Fields documentation marks jira_start_date optional and adds a "Project scope" note describing automatic discovery per token permissions. Operational constraints describe automatic per-project querying controlled by Browse Projects permissions.
Cross-connector ADR alignment for YouTrack
docs/components/connectors/task-tracking/youtrack/specs/ADR/ADR-003-no-whitelist-full-ingestion.md
YouTrack ADR-003 now references Jira ADR-001, clarifying Jira removed its jira_project_keys allowlist in favor of auto-discovery; Option 2 and traceability sections updated with cross-reference and a 6-month revisit horizon.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 A rabbit hops through Jira's streams,
No more allowlists in config schemes,
Projects found by token's light,
Partitioned queries run each night,
Syncs awaken with permissions bright.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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.
Title check ✅ Passed The title clearly and concisely summarizes the main change: auto-discovery of Jira projects and removal of the mandatory jira_project_keys configuration.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/jira-auto-project-discovery

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: 3

🧹 Nitpick comments (1)
docs/components/connectors/task-tracking/jira/specs/ADR/ADR-001-auto-project-discovery.md (1)

89-91: 💤 Low value

Consider rewording to reduce repetition.

Three consecutive lines begin with "connector.yaml's", which affects readability slightly. While the static analysis tool flags this as a style concern, the current wording is clear and the repetition emphasizes the distinct surfaces being checked.

✨ Optional refactor for variety
-- `connector.yaml`'s shared JQL uses `project = "{{ stream_slice.project_key }}"` (not `project IN (...)`).
-- `connector.yaml`'s `spec.connection_specification.required` does **not** list `jira_project_keys`.
-- `descriptor.yaml:secret.required_fields` does **not** list `jira_project_keys`.
+- The shared JQL template uses `project = "{{ stream_slice.project_key }}"` (not `project IN (...)`).
+- The `spec.connection_specification.required` array does **not** list `jira_project_keys`.
+- `descriptor.yaml:secret.required_fields` does **not** list `jira_project_keys`.
🤖 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
`@docs/components/connectors/task-tracking/jira/specs/ADR/ADR-001-auto-project-discovery.md`
around lines 89 - 91, Reword the three lines to avoid repeating
"`connector.yaml`'s" while keeping the same checks: combine them into a single
sentence or parallel list mentioning each surface by its unique symbol names
(spec.connection_specification.required in connector.yaml,
secret.required_fields in descriptor.yaml, and jira_project_keys in
jira.yaml.example) and state that none include jira_project_keys; preserve the
meaning but use varied phrasing (e.g., "connector.yaml's
spec.connection_specification.required, descriptor.yaml's
secret.required_fields, and jira.yaml.example do not include jira_project_keys")
for improved readability.
🤖 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
`@docs/components/connectors/task-tracking/jira/specs/ADR/ADR-001-auto-project-discovery.md`:
- Line 17: The link fragment `#option-1--static-allowlist-in-ks-secret` is
misspelled and doesn't match the actual heading
`#option-1--static-allowlist-in-k8s-secret`; update the link target in the text
(the string `#option-1--static-allowlist-in-ks-secret`) to
`#option-1--static-allowlist-in-k8s-secret` so the anchor correctly points to
the heading.

In `@docs/components/connectors/task-tracking/jira/specs/PRD.md`:
- Line 481: The PRD currently references the wrong ADR ("ADR-007") in the
sentence starting with "Connector auto-discovers all projects..." — update that
reference to the correct ADR identifier/name used in this PR (e.g.,
ADR-001-auto-project-discovery.md or the ADR id
"cpt-insightspec-adr-jira-auto-project-discovery") so the PRD points to the
actual ADR added; edit the text in PRD.md to replace "ADR-007" with the correct
ADR token and, if present elsewhere, update any related anchor/link text to
match the ADR filename/ID.

In `@src/ingestion/connectors/task-tracking/jira/connector.yaml`:
- Around line 779-823: Add an operator-side optional allowlist so upgrades don't
expand scope: introduce a config key (e.g. config['jira_project_keys'] as an
optional list) and apply it in the jira_project_discovery DeclarativeStream
before partitioning; specifically update the retriever/transformations for the
stream named jira_project_discovery (or insert a Filter transformation) to drop
any project records whose record['key'] is not in config['jira_project_keys']
when that config is present, and leave behavior unchanged when the config is
absent or empty; keep the existing parent_key (project_key) and partition_field
(project_key) unchanged.

---

Nitpick comments:
In
`@docs/components/connectors/task-tracking/jira/specs/ADR/ADR-001-auto-project-discovery.md`:
- Around line 89-91: Reword the three lines to avoid repeating
"`connector.yaml`'s" while keeping the same checks: combine them into a single
sentence or parallel list mentioning each surface by its unique symbol names
(spec.connection_specification.required in connector.yaml,
secret.required_fields in descriptor.yaml, and jira_project_keys in
jira.yaml.example) and state that none include jira_project_keys; preserve the
meaning but use varied phrasing (e.g., "connector.yaml's
spec.connection_specification.required, descriptor.yaml's
secret.required_fields, and jira.yaml.example do not include jira_project_keys")
for improved readability.
🪄 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: cb015067-218d-44d5-9cde-8186dc4a4330

📥 Commits

Reviewing files that changed from the base of the PR and between a722069 and 45f216c.

📒 Files selected for processing (8)
  • docs/components/connectors/task-tracking/jira/specs/ADR/ADR-001-auto-project-discovery.md
  • docs/components/connectors/task-tracking/jira/specs/DESIGN.md
  • docs/components/connectors/task-tracking/jira/specs/PRD.md
  • docs/components/connectors/task-tracking/youtrack/specs/ADR/ADR-003-no-whitelist-full-ingestion.md
  • src/ingestion/connectors/task-tracking/jira/README.md
  • src/ingestion/connectors/task-tracking/jira/connector.yaml
  • src/ingestion/connectors/task-tracking/jira/descriptor.yaml
  • src/ingestion/secrets/connectors/jira.yaml.example
💤 Files with no reviewable changes (2)
  • src/ingestion/connectors/task-tracking/jira/descriptor.yaml
  • src/ingestion/secrets/connectors/jira.yaml.example

- [Consequences](#consequences)
- [Confirmation](#confirmation)
- [Pros and Cons of the Options](#pros-and-cons-of-the-options)
- [Option 1 — Static allowlist in K8s Secret](#option-1--static-allowlist-in-ks-secret)

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

Fix broken link fragment.

The static analysis tool flags this link fragment as invalid. The target #option-1--static-allowlist-in-ks-secret does not match the actual heading #option-1--static-allowlist-in-k8s-secret (missing "8").

🔗 Proposed fix
-  - [Option 1 — Static allowlist in K8s Secret](`#option-1--static-allowlist-in-ks-secret`)
+  - [Option 1 — Static allowlist in K8s Secret](`#option-1--static-allowlist-in-k8s-secret`)
📝 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
- [Option 1 — Static allowlist in K8s Secret](#option-1--static-allowlist-in-ks-secret)
- [Option 1 — Static allowlist in K8s Secret](`#option-1--static-allowlist-in-k8s-secret`)
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 17-17: Link fragments should be valid

(MD051, link-fragments)

🤖 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
`@docs/components/connectors/task-tracking/jira/specs/ADR/ADR-001-auto-project-discovery.md`
at line 17, The link fragment `#option-1--static-allowlist-in-ks-secret` is
misspelled and doesn't match the actual heading
`#option-1--static-allowlist-in-k8s-secret`; update the link target in the text
(the string `#option-1--static-allowlist-in-ks-secret`) to
`#option-1--static-allowlist-in-k8s-secret` so the anchor correctly points to
the heading.

Comment thread docs/components/connectors/task-tracking/jira/specs/PRD.md Outdated
Comment on lines +779 to +823
partition_router:
type: SubstreamPartitionRouter
parent_stream_configs:
- type: ParentStreamConfig
stream:
type: DeclarativeStream
name: jira_project_discovery
primary_key: []
retriever:
type: SimpleRetriever
record_selector:
type: RecordSelector
extractor:
type: DpathExtractor
field_path:
- values
paginator:
$ref: "#/definitions/linked/SimpleRetriever/paginator"
requester:
type: HttpRequester
http_method: GET
authenticator:
$ref: "#/definitions/linked/HttpRequester/authenticator"
error_handler:
$ref: "#/definitions/linked/HttpRequester/error_handler"
url: "{{ config['jira_instance_url'] }}/rest/api/3/project/search"
transformations:
- type: AddFields
fields:
- type: AddedFieldDefinition
path:
- project_key
value: "{{ record['key'] }}"
schema_loader:
type: InlineSchemaLoader
schema:
type: object
properties:
project_key:
type:
- string
- "null"
additionalProperties: true
parent_key: project_key
partition_field: project_key

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 | 🟠 Major | 🏗️ Heavy lift

Keep an operator-side scope guard for project discovery.

Removing jira_project_keys means an upgrade can expand an existing connection from a curated subset to every project visible to the token, and the first post-upgrade sync will backfill that broader scope from jira_start_date. Please keep an optional allowlist/filter so teams can preserve data-minimization without having to redesign Jira permissions.

Also applies to: 7945-7999

🤖 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/task-tracking/jira/connector.yaml` around lines 779
- 823, Add an operator-side optional allowlist so upgrades don't expand scope:
introduce a config key (e.g. config['jira_project_keys'] as an optional list)
and apply it in the jira_project_discovery DeclarativeStream before
partitioning; specifically update the retriever/transformations for the stream
named jira_project_discovery (or insert a Filter transformation) to drop any
project records whose record['key'] is not in config['jira_project_keys'] when
that config is present, and leave behavior unchanged when the config is absent
or empty; keep the existing parent_key (project_key) and partition_field
(project_key) unchanged.

@il10241024 il10241024 changed the title feat(jira): auto-discover projects — remove mandatory jira_project_keys [PR #616] feat(jira): auto-discover projects — remove mandatory jira_project_keys Jun 5, 2026
@mitasovr

Copy link
Copy Markdown
Contributor

Superseded by #1316, which reworks this idea against the current manifest (crediting the original design from this PR / cyberfabric/cyber-insight#616).

Why a rework rather than a rebase — the connector changed significantly since this was written:

  1. fix(ingestion): stop silent jira sync hang via lightweight substream parent #1283 introduced jira_issue_keys, a lightweight substream parent whose JQL also references jira_project_keys. Removing the config key without migrating that stream (which this PR predates) would silently zero out jira_issue_history/jira_comments/jira_worklogs — the JQL renders as project IN (), Jira returns 400, the error handler IGNOREs it, and all three substreams produce no partitions while the sync stays green.
  2. fix(ingestion): bump jira/confluence concurrency to 4 to break CDK partition deadlock #1308 (default_concurrency: 4) is required for the wider per-project partition fan-out — at concurrency 1 the concurrent CDK self-deadlocks once a sync generates ≥ ~10k partitions.
  3. The +14h end_datetime timezone workaround advances the cursor past the actual query time, permanently skipping records updated between the query moment and the future-dated window end. feat(jira): auto-discover projects — drop the jira_project_keys allowlist #1316 addresses the same timezone tail with lookback_window: PT14H instead.
  4. feat(jira): auto-discover projects — drop the jira_project_keys allowlist #1316 also adds an incremental discovery gate (expand=insight → client-side cursor on insight.lastIssueUpdateTime), so after the first full sync only projects with issue changes are scanned — verified live: 3/211 project partitions touched on a resume read.

Thanks for the groundwork — the inline discovery parent and per-project JQL shape carried over directly.

mitasovr added a commit that referenced this pull request Jun 15, 2026
…list (#1316)

Replaces the manually-maintained jira_project_keys secret field with
runtime project discovery. Supersedes #941 (idea and per-project JQL by
mozhaev-dev, cyberfabric/cyber-insight#616), reworked against the current
manifest:

- New inline-only parent `jira_project_discovery` under `definitions`
  (never visible to discover, so reconcile cannot select it as a bronze
  table): GET /rest/api/3/project/search?expand=insight enumerates every
  project the API token can see.
- BOTH jira_issue and jira_issue_keys (the lightweight parent of
  jira_issue_history/comments/worklogs from #1283) are partitioned per
  project with JQL `project = "<KEY>"`. #941 predates jira_issue_keys and
  would have silently broken all three substreams by removing the config
  their JQL still referenced.
- Incremental discovery gate: client-side cursor on the hoisted
  insight.lastIssueUpdateTime — first sync emits all projects, later syncs
  only projects whose issues changed. Verified live: first read enumerated
  211 projects (25 with fresh issues, 2478 records — the 4-project
  allowlist would have missed 9 of them), resume read touched only 3/211
  partitions. Projects without issues default to epoch and stay filtered
  until their first issue.
- Timezone tail fix done safely: lookback_window PT1H -> PT14H on the
  issue scans. #941 instead pushed end_datetime 14h into the future, which
  advances the cursor past the actual query time and permanently skips
  records updated in between.
- jira_project_keys removed from spec, descriptor required_fields, secret
  example and README.
- descriptor 1.2.1 -> 2.0.0 (major): per-project partitioning resets
  incremental state; reconcile dispatches a full refresh on major bumps,
  which is the intended migration.

Known costs, documented in-manifest: the first sync after rollout is a
full re-sync of every visible project since jira_start_date; the
client-side gate compares strictly against the cursor (lookback does not
widen it on resume), so a lagging insight aggregate delays that project
until its next update.

validate (CDK runtime): manifest valid. validate-strict: 14 pre-existing
errors on main (jira is the known whole-object-$ref anti-template);
unchanged by this diff.

Co-authored-by: Roman Mitasov <Roman.Mitasov@constructor.tech>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
mozhaev-dev added a commit that referenced this pull request Jun 15, 2026
Lesson applied from jira #1316: the incremental cursors on support_tickets and
zendesk_satisfaction_ratings had no lookback. Zendesk's incremental export is
eventually-consistent at the cursor boundary, so a record whose updated_at
lands at the edge is permanently skipped on the next sync — and for a ticket
that drops its entire audit activity (the only source of
updates/comments/solved). Added lookback_window: P1D to both cursors so each
sync re-queries a 1-day tail; append-only RMT bronze + read-time dedup absorb
the re-delivery (verified: bronze distinct stable 32/5/102, silver exact 36/36
across 4 syncs, zero duplicates).

NB the fix is lookback_window, NOT pushing end_datetime into the future (the
#941 approach #1316 flagged: that advances the cursor past the real query time
and permanently skips rows). descriptor 1.2.2 → 1.2.3 (patch: cursor state
stays valid, lookback only widens the start each run).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Sergei Mozhaev <mozhaev.dev@gmail.com>
@mitasovr mitasovr closed this Jun 18, 2026
mozhaev-dev added a commit that referenced this pull request Jun 18, 2026
…ts Link pagination

The org/user daily-metrics streams advanced the incremental cursor per-slice
unconditionally (the "Major #5" rule), starting the next run at cursor+1. But
Copilot daily reports lag (often >24-48h; weekends later) and can be restated,
so a recent day that returned HTTP 204 because its report wasn't ready yet still
advanced the cursor past it — and was then NEVER re-fetched once the data landed.
Silent data gaps for late-arriving / adjusted days (same class as the jira/#941
cursor-skip and the Zendesk lookback_window lesson).

Fix: both report streams now re-query a trailing lookback window
(config `metrics_lookback_days`, default 7) — start = max(start_date,
cursor - lookback_days) instead of cursor+1. The recent tail self-heals every
run; RMT dedup on unique_key ({tenant}-{source}-[user]-{day}) makes the overlap
idempotent. Genuinely-empty historical days still don't get re-fetched forever
(only the trailing window is revisited). lookback_days wired through source.py +
spec.json.

Also: seats pagination now follows GitHub's `Link: rel="next"` header (with the
count heuristic as fallback) instead of stopping purely on "page < 100".

descriptor 2.1.0 → 2.2.0 (minor: new optional config, no Bronze schema change).

Verified live against a real Copilot Business org: with cursor=2026-06-13 +
lookback=7 the read re-fetched 2026-06-06..06-15 (all 7 pre-cursor days,
incl. active days the old cursor+1 would have skipped), 0 errors; seats
paginated correctly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Sergei Mozhaev <mozhaev.dev@gmail.com>
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.

3 participants