onetaskgraph-linear 0.2.27

A onetaskgraph source over the Linear API.
Documentation
# llmlint: ignore-file[contracts_have_one_source_or_a_drift_gate] Linear exposes its schema only through an authenticated, unversioned explorer, so an authoritative automated freshness check would require the credential the live lane holds, and that lane skips where no credential was ever expected — a fork pull request, and a contributor with no keys — so it could not be the deterministic offline gate this rule asks for; this documentation-derived subset pins the acceptance criterion's recorded 2026-08-24 contract instead.
# Documentation-derived Linear GraphQL schema subset, pinned 2026-08-24, with
# `projectDelete` re-observed and pinned 2026-08-29 from the same published schema, and
# the whole document contract below — `document`, `documents`, `Document`,
# `DocumentConnection`, `DocumentFilter`, the two document inputs, both document payloads
# and the three document mutations — re-observed and pinned 2026-09-01 from it.
#
# `Query.projectStatuses` is corrected on 2026-09-04 from the real API's own answer, which
# is the only authority this file ever gets that outranks the published documentation: the
# live journey sent it a `filter` argument and Linear refused the whole document with
# `Unknown argument "filter" on field "Query.projectStatuses". Did you mean "after"?`. So
# that connection is not filterable and `ProjectStatusFilter`, which nothing now sends, is
# gone with it; the plugin matches the status name locally instead.
#
# `ProjectRelationCreateInput` is re-observed and pinned 2026-09-04 from the published
# schema, which now declares `anchorType: String!` and `relatedAnchorType: String!`
# alongside the three fields this file carried before. Linear added them under both `main`
# and this branch, and the live journey's project-relation write failed on the first with
# `Field "anchorType" of required type "String!" was not provided.` Both are bare `String!`
# there and Linear enumerates their values nowhere introspection reaches; `start`, `end`
# and `milestone` are the values, and the two this source sends are the whole-project
# anchors. The optional `id`, `projectMilestoneId` and `relatedProjectMilestoneId` that
# input also declares stay out, as every unsent field does.
#
# Those three values, and the one value `type` takes, were confirmed on 2026-09-04 from
# Linear's validator rather than derived: refusing an undocumented value it answers
# `anchorType must be one of the following values: start, end, milestone` and `type must be
# one of the following values: dependency`. Neither enumeration can be pinned as a GraphQL
# enum here, because Linear does not declare one — the fields really are `String!`, and the
# check that validates this file against the plugin's documents can only hold them to that.
# What holds the values is the plugin's own tests; `README.md` records the whole probe,
# including which pair of anchors means which direction.
#
# The team side of `WorkflowStateFilter` is corrected on 2026-09-04 from the real API's own
# answer, which is the second time that authority has outranked the documentation this file
# is otherwise derived from. This file carried the filter as an invented `IdComparator {
# id: IdEquality { eq: String } }`, and the plugin's state lookup declared `$team: String!`
# to match it. Linear refused the whole document with HTTP 400 and
# `Variable "$team" of type "String!" used in position expecting type "ID".`, which names
# both the real shape and why `String!` cannot stand there: a variable is usable at a
# location only when its type is the location's type or that type's non-null form, and
# `String` is not `ID`. So the real names and the real scalar are pinned —
# `WorkflowStateFilter.team` is a `NullableTeamFilter`, whose `id` is an `IDComparator`,
# whose `eq` is an `ID` — and `ISSUE_STATE` declares `$team: ID!`, which is the non-null
# form of the location's own type and is therefore accepted.
# Only the read/write contract exercised by this plugin is retained.
type Query {
  viewer: User!
  issue(id: String!): Issue
  issues(first: Int!, after: String, filter: IssueFilter): IssueConnection!
  project(id: String!): Project
  projects(first: Int!, after: String, filter: ProjectFilter): ProjectConnection!
  document(id: String!): Document!
  documents(first: Int, after: String, filter: DocumentFilter): DocumentConnection!
  issueLabels(first: Int, after: String, filter: IssueLabelFilter): IssueLabelConnection!
  projectLabels(filter: ProjectLabelFilter): ProjectLabelConnection!
  teams(filter: TeamFilter): TeamConnection!
  workflowStates(filter: WorkflowStateFilter): WorkflowStateConnection!
  projectStatuses(after: String): ProjectStatusConnection!
}
type Mutation {
  issueCreate(input: IssueCreateInput!): IssuePayload!
  issueUpdate(id: String!, input: IssueUpdateInput!): IssuePayload!
  projectCreate(input: ProjectCreateInput!): ProjectPayload!
  projectUpdate(id: String!, input: ProjectUpdateInput!): ProjectPayload!
  issueRelationCreate(input: IssueRelationCreateInput!): IssueRelationPayload!
  projectRelationCreate(input: ProjectRelationCreateInput!): ProjectRelationPayload!
  issueRelationDelete(id: String!): DeletePayload!
  projectRelationDelete(id: String!): DeletePayload!
  issueDelete(id: String!): DeletePayload!
  projectDelete(id: String!): ProjectArchivePayload!
  documentCreate(input: DocumentCreateInput!): DocumentPayload!
  documentUpdate(id: String!, input: DocumentUpdateInput!): DocumentPayload!
  documentDelete(id: String!): DocumentArchivePayload!
}

type User {
  id: ID!
}
type Issue {
  id: ID!
  title: String!
  description: String
  url: String
  createdAt: DateTime
  updatedAt: DateTime
  archivedAt: DateTime
  state: WorkflowState!
  labels: IssueLabelConnection!
  project: Project
  relations(first: Int!, after: String): IssueRelationConnection!
  inverseRelations(first: Int!, after: String): IssueRelationConnection!
}
type Project {
  id: ID!
  name: String!
  description: String
  url: String
  createdAt: DateTime
  updatedAt: DateTime
  archivedAt: DateTime
  status: ProjectStatus!
  labels: ProjectLabelConnection!
  relations(first: Int!, after: String): ProjectRelationConnection!
  inverseRelations(first: Int!, after: String): ProjectRelationConnection!
}
# Linear's own first-class document. It carries no `labels` field and no status: the only
# types of the published schema with `labels` are Issue, Project, Team, Initiative and
# Organization, which is why this source reports a document's labels as none rather than
# standing a slot up beside a first-class type.
type Document {
  id: ID!
  title: String!
  content: String
  url: String!
  createdAt: DateTime!
  updatedAt: DateTime!
  archivedAt: DateTime
  project: Project
}
type WorkflowState {
  id: ID!
  name: String!
  type: String!
}
type ProjectStatus {
  id: ID!
  name: String!
  type: String!
}
type IssueLabel {
  id: ID!
  name: String!
  color: String
}
type ProjectLabel {
  id: ID!
  name: String!
  color: String
}
type IssueConnection {
  nodes: [Issue!]!
  pageInfo: PageInfo!
}
type ProjectConnection {
  nodes: [Project!]!
  pageInfo: PageInfo!
}
type IssueLabelConnection {
  nodes: [IssueLabel!]!
  pageInfo: PageInfo!
}
type ProjectLabelConnection {
  nodes: [ProjectLabel!]!
  pageInfo: PageInfo!
}
type DocumentConnection {
  nodes: [Document!]!
  pageInfo: PageInfo!
}
type IssueRelationConnection {
  nodes: [IssueRelation!]!
  pageInfo: PageInfo!
}
type ProjectRelationConnection {
  nodes: [ProjectRelation!]!
  pageInfo: PageInfo!
}
type IssueRelation {
  id: ID!
  type: String!
  issue: Issue
  relatedIssue: Issue
}
type ProjectRelation {
  id: ID!
  type: String!
  project: Project
  relatedProject: Project
}
type Team {
  id: ID!
}
type TeamConnection {
  nodes: [Team!]!
}
type WorkflowStateConnection {
  nodes: [WorkflowState!]!
}
type ProjectStatusConnection {
  nodes: [ProjectStatus!]!
}
type IssuePayload {
  success: Boolean!
  issue: Issue
}
type ProjectPayload {
  success: Boolean!
  project: Project
}
type IssueRelationPayload {
  success: Boolean!
  issueRelation: IssueRelation
}
type ProjectRelationPayload {
  success: Boolean!
  projectRelation: ProjectRelation
}
type DocumentPayload {
  success: Boolean!
  document: Document!
}
type DeletePayload {
  success: Boolean!
}
type DocumentArchivePayload {
  success: Boolean!
}
type ProjectArchivePayload {
  success: Boolean!
}
type PageInfo {
  hasNextPage: Boolean!
  endCursor: String
}
# `and` and `or` are members of every one of Linear's filter inputs, and this source uses
# `or` to spell "carries at least one of these labels" — see `StringComparator` below for
# why that cannot be one list operator.
# The two read filters below carried nothing but `and`/`or` until 2026-09-04, and that
# emptiness is what let two wrong members reach `projects(filter:)` for months: a filter is
# built at runtime and passed whole as `$filter`, so it appears nowhere in a document and
# the two checks that read documents could not see it. They are pinned member by member
# now, from Linear's real schema, and `every_variables_object_this_source_sends_conforms_to_the_pinned_schema`
# walks the objects this source really builds against them.
#
# **`IssueFilter` and `ProjectFilter` are different types, and the members they do not
# share are the point.** A project has no `team` — Linear refused one with
# `Field "team" is not defined by type "ProjectFilter". Did you mean "lead"?` — it has the
# teams it is accessible from, under `accessibleTeams`. And a project's status is not an
# issue's state: `ProjectFilter.state` exists and is a bare `StringComparator` over
# something else, while the counterpart of `IssueFilter.state` is `ProjectFilter.status`.
input IssueFilter {
  team: TeamFilter
  labels: IssueLabelCollectionFilter
  state: WorkflowStateFilter
  project: NullableProjectFilter
  and: [IssueFilter!]
  or: [IssueFilter!]
}
# `state` is the one member here this source does not send, and it is pinned because it is
# the trap: it exists, it is spelled exactly like the member of `IssueFilter` that carries
# an issue's status, and it is a bare `StringComparator` over something else entirely. With
# it absent, sending the issue's `{state:{type:{in:[…]}}}` here would be reported as a
# member Linear does not have, which is untrue; with it present the report is what is
# actually wrong — a `StringComparator` has no `type`.
input ProjectFilter {
  id: EntityIdentifierIDComparator
  accessibleTeams: TeamCollectionFilter
  labels: ProjectLabelCollectionFilter
  state: StringComparator
  status: ProjectStatusFilter
  and: [ProjectFilter!]
  or: [ProjectFilter!]
}
# `DocumentFilter.project` is a `ProjectFilter` rather than the `NullableProjectFilter`
# `IssueFilter.project` is, so it carries no `null:` member and Linear cannot be asked for
# the documents belonging to no project. That predicate is the one this source applies to
# a fetched page itself.
input DocumentFilter {
  project: ProjectFilter
  and: [DocumentFilter!]
}
# `null:` is the whole reason `IssueFilter.project` reaches this rather than `ProjectFilter`:
# it is what asks for the issues belonging to no project at all.
input NullableProjectFilter {
  id: EntityIdentifierIDComparator
  null: Boolean
  and: [NullableProjectFilter!]
  or: [NullableProjectFilter!]
}
input TeamCollectionFilter {
  some: TeamFilter
  every: TeamFilter
}
input TeamFilter {
  key: StringComparator
}
input IssueLabelCollectionFilter {
  some: IssueLabelFilter
  every: IssueLabelFilter
}
# Not a mirror of the collection above, and the difference is Linear's: `some` here takes
# the *collection* filter again where the issue side takes the plain one. Both carry the
# `name` this source sends, so one spelling satisfies each, which is why it is written once
# and pinned twice.
input ProjectLabelCollectionFilter {
  name: StringComparator
  some: ProjectLabelCollectionFilter
  every: ProjectLabelFilter
}
input WorkflowStateFilter {
  name: StringComparator
  type: StringComparator
  team: NullableTeamFilter
}
# The project counterpart of `WorkflowStateFilter`, and its `type` is a `StringComparator`
# although the `ProjectStatus.type` field it filters is the `ProjectStatusType` enum —
# `backlog`, `planned`, `started`, `paused`, `completed`, `canceled`. Neither `planned` nor
# `paused` is a workflow-state type and neither `unstarted` nor `triage` is a project-status
# one, so the two vocabularies are not interchangeable however alike the two filters look.
input ProjectStatusFilter {
  type: StringComparator
}
input ProjectLabelFilter {
  name: StringComparator
}
input IssueLabelFilter {
  name: StringComparator
}
# Every member here is one this source sends. What is recorded rather than sent is the
# absence: `StringComparator` has no case-insensitive *list* operator. Linear said so
# itself, refusing `inIgnoreCase` with HTTP 400 — `Field "inIgnoreCase" is not defined by
# type "StringComparator". Did you mean "eqIgnoreCase" or "neqIgnoreCase"?` — which is
# where the two case-insensitive members below come from. `in` is the case-*sensitive* list
# operator, which is why the two status narrowings use it over a fixed vocabulary and the
# label predicates, over names a user typed, do not.
input StringComparator {
  eqIgnoreCase: String
  neqIgnoreCase: String
  in: [String!]
}
input NullableTeamFilter {
  id: IDComparator
}
input IDComparator {
  eq: ID
}
# A project id is compared with this rather than with `IDComparator`; the member this
# source sends is spelled the same in both, and the types are not.
input EntityIdentifierIDComparator {
  eq: ID
}
input IssueCreateInput {
  teamId: String!
  title: String!
  description: String
  stateId: String!
  labelIds: [String!]!
  projectId: String
}
input IssueUpdateInput {
  title: String!
  description: String
  stateId: String!
  labelIds: [String!]!
  projectId: String
}
input ProjectCreateInput {
  teamIds: [String!]!
  name: String!
  description: String
  statusId: String!
  labelIds: [String!]!
}
input ProjectUpdateInput {
  name: String!
  description: String
  statusId: String!
  labelIds: [String!]!
}
# Nullability as published: `title` alone is required of a create, and every member of an
# update is optional.
input DocumentCreateInput {
  title: String!
  content: String
  projectId: String
  teamId: String
}
input DocumentUpdateInput {
  title: String
  content: String
  projectId: String
}
input IssueRelationCreateInput {
  issueId: String!
  relatedIssueId: String!
  type: String!
}
input ProjectRelationCreateInput {
  projectId: String!
  relatedProjectId: String!
  type: String!
  anchorType: String!
  relatedAnchorType: String!
}
scalar DateTime