Skip to content

feat(sla_enforcement): config-driven SLA rules as authoritative source for slaBreachesAt#6

Merged
shannonwho merged 3 commits intomainfrom
feature/sla-created-at-baseline
Apr 15, 2026
Merged

feat(sla_enforcement): config-driven SLA rules as authoritative source for slaBreachesAt#6
shannonwho merged 3 commits intomainfrom
feature/sla-created-at-baseline

Conversation

@shannonwho
Copy link
Copy Markdown
Collaborator

@shannonwho shannonwho commented Apr 15, 2026

Fixes SOL-237

Summary

  • resolveBreachFromRules — new method that computes slaBreachesAt = createdAt + rule.hours(priority) from slaRules in config.json. Replaces reliance on Linear's internally-computed SLA values, which are vulnerable to async race conditions (wrong priority's workflow firing during rapid priority changes).
  • Three-tier source priority applied across all SLA correction paths: (1) config slaRule match → always authoritative; (2) cached pre-workflow duration → safe fallback when no rule and drift detected; (3) bail — nothing reliable to act on.
  • setTimeout(2500ms) after authorized priority change — waits for Linear's internal workflow to settle, then reads liveIssue and applies the rule-based correction in one update. Includes idempotency check to skip if values are already correct.
  • Single-update architecture — when Linear's UI SLA rules are removed and config.json slaRules are the sole source, exactly one update fires per priority change (no double-write). Documented in README and IMPLEMENTATION_GUIDE.
  • Hierarchical allowlist with field-level permissionslinearTeamId-backed groups, union resolution, slaBaseline split from sla.

Changes

File What changed
src/enforcement-engine.ts resolveBreachFromRules method; setTimeout correction handler; three-tier source logic in all revert paths
src/types.ts SLARuleSet, SLAPriorityWindow, Permission types; AllowlistGroup/AllowlistLeaf hierarchy
src/linear-client.ts getTeamMembers for linearTeamId resolution at startup
config/config.json.example Added slaRules examples
.env.example OAuth vs personal API key guidance
README.md Updated slaCreatedAtBaseline calculation, added single-update architecture note
IMPLEMENTATION_GUIDE.md Updated SLA breach date calculation section, added single-update architecture section

Test plan

  • Priority change by allowlisted user → slaBreachesAt corrected to createdAt + rule.hours(newPriority) in one update (no Linear UI SLA rules active)
  • Unauthorized priority change → reverted; slaBreachesAt restored using rule lookup
  • slaStartedAt drift (silent reset by Linear workflow) → corrected unconditionally regardless of actor
  • User in linearTeamId-backed group → permissions resolved correctly at startup and after 4h refresh
  • Partial authorization → only unauthorized fields reverted, authorized fields pass through
  • No matching slaRule → falls back to cached duration when drift detected, bails when no drift

🤖 Generated with Claude Code

@linear-code
Copy link
Copy Markdown

linear-code Bot commented Apr 15, 2026

@shannonwho shannonwho force-pushed the feature/sla-created-at-baseline branch from 0404931 to bf03de3 Compare April 15, 2026 21:14
@shannonwho shannonwho requested a review from blkinney April 15, 2026 21:15
…el permissions

Introduces a hierarchical allowlist model with field-level permission grants.

- AllowlistGroup / AllowlistLeaf types with unlimited nesting depth
- linearTeamId: all members of a Linear team auto-match the group; membership
  fetched at startup via getTeamMembers and refreshed every 4 hours
- Four permissions: labels, sla, priority, slaBaseline (clock anchor — most restricted)
- Union resolution: user matching multiple entries receives the most permissive union
- Partial authorization: only unauthorized fields are reverted per webhook event
- Backward compatible: flat leaf entries with no permissions default to all four
- 36-case authorization test suite covering inheritance, union, partial auth, and
  linearTeamId resolution

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@shannonwho shannonwho force-pushed the feature/sla-created-at-baseline branch from 89bbce3 to 3daa1f4 Compare April 15, 2026 21:32
Copy link
Copy Markdown
Collaborator

@blkinney blkinney left a comment

Choose a reason for hiding this comment

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

one issue i see there, but otherwise seems good! great job!

cachedCreatedAt,
liveIssue.priority ?? 0,
liveIssue.labels ?? [],
(liveIssue as any).teamId
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

it looks like teamId isn't on the issue data coming from linear-client, you'll want to update the graphql to pull in team {id}.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

thanks for catching that! fixed with the UUID add-on

Shannon Hu and others added 2 commits April 15, 2026 14:36
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
getTeamMembers now detects whether the configured value is a UUID or a
short team key (e.g. "ADM", "ENG"). Keys use the teams(filter:{key:{eq:}})
query; UUIDs use the existing team(id:) query.

Previously, passing a team key to team(id:) caused "Entity not found" errors
at startup, leaving the team member cache empty and blocking all team-based
authorization until the next restart.

Docs updated in README and IMPLEMENTATION_GUIDE to recommend the team key
format and explain where to find it in the Linear UI.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@shannonwho shannonwho merged commit 8d54ddc into main Apr 15, 2026
shannonwho pushed a commit that referenced this pull request Apr 20, 2026
All conflicts resolved by keeping feature branch — our version is the
superset of main's squashed PR #6 (hierarchical auth + slaRules) plus
the new slaConfigurations API work. No functionality changes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.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.

2 participants