@humanbased/crosscheck 1.2.0-beta.2 → 1.2.0-beta.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/crosscheck.config.example.yml +42 -0
  2. package/dist/__tests__/can-write-verdict.test.d.ts +2 -0
  3. package/dist/__tests__/can-write-verdict.test.d.ts.map +1 -0
  4. package/dist/__tests__/can-write-verdict.test.js +31 -0
  5. package/dist/__tests__/can-write-verdict.test.js.map +1 -0
  6. package/dist/__tests__/linear-client.test.d.ts +2 -0
  7. package/dist/__tests__/linear-client.test.d.ts.map +1 -0
  8. package/dist/__tests__/linear-client.test.js +120 -0
  9. package/dist/__tests__/linear-client.test.js.map +1 -0
  10. package/dist/__tests__/linear-comment.test.d.ts +2 -0
  11. package/dist/__tests__/linear-comment.test.d.ts.map +1 -0
  12. package/dist/__tests__/linear-comment.test.js +151 -0
  13. package/dist/__tests__/linear-comment.test.js.map +1 -0
  14. package/dist/__tests__/linear-identity.test.d.ts +2 -0
  15. package/dist/__tests__/linear-identity.test.d.ts.map +1 -0
  16. package/dist/__tests__/linear-identity.test.js +253 -0
  17. package/dist/__tests__/linear-identity.test.js.map +1 -0
  18. package/dist/__tests__/linear-notify.test.d.ts +2 -0
  19. package/dist/__tests__/linear-notify.test.d.ts.map +1 -0
  20. package/dist/__tests__/linear-notify.test.js +144 -0
  21. package/dist/__tests__/linear-notify.test.js.map +1 -0
  22. package/dist/__tests__/linear-ref.test.d.ts +2 -0
  23. package/dist/__tests__/linear-ref.test.d.ts.map +1 -0
  24. package/dist/__tests__/linear-ref.test.js +261 -0
  25. package/dist/__tests__/linear-ref.test.js.map +1 -0
  26. package/dist/__tests__/linear-verify.test.d.ts +2 -0
  27. package/dist/__tests__/linear-verify.test.d.ts.map +1 -0
  28. package/dist/__tests__/linear-verify.test.js +113 -0
  29. package/dist/__tests__/linear-verify.test.js.map +1 -0
  30. package/dist/__tests__/linear-worker.test.d.ts +2 -0
  31. package/dist/__tests__/linear-worker.test.d.ts.map +1 -0
  32. package/dist/__tests__/linear-worker.test.js +83 -0
  33. package/dist/__tests__/linear-worker.test.js.map +1 -0
  34. package/dist/__tests__/linear-write-possible.test.d.ts +2 -0
  35. package/dist/__tests__/linear-write-possible.test.d.ts.map +1 -0
  36. package/dist/__tests__/linear-write-possible.test.js +30 -0
  37. package/dist/__tests__/linear-write-possible.test.js.map +1 -0
  38. package/dist/__tests__/optimize.test.js +1 -0
  39. package/dist/__tests__/optimize.test.js.map +1 -1
  40. package/dist/__tests__/runner.test.js +29 -1
  41. package/dist/__tests__/runner.test.js.map +1 -1
  42. package/dist/commands/review.d.ts.map +1 -1
  43. package/dist/commands/review.js +48 -1
  44. package/dist/commands/review.js.map +1 -1
  45. package/dist/commands/run.d.ts.map +1 -1
  46. package/dist/commands/run.js +23 -3
  47. package/dist/commands/run.js.map +1 -1
  48. package/dist/commands/status.d.ts.map +1 -1
  49. package/dist/commands/status.js +40 -1
  50. package/dist/commands/status.js.map +1 -1
  51. package/dist/commands/watch.d.ts.map +1 -1
  52. package/dist/commands/watch.js +52 -6
  53. package/dist/commands/watch.js.map +1 -1
  54. package/dist/config/loader.d.ts +3 -1
  55. package/dist/config/loader.d.ts.map +1 -1
  56. package/dist/config/loader.js +13 -0
  57. package/dist/config/loader.js.map +1 -1
  58. package/dist/config/schema.d.ts +208 -0
  59. package/dist/config/schema.d.ts.map +1 -1
  60. package/dist/config/schema.js +39 -0
  61. package/dist/config/schema.js.map +1 -1
  62. package/dist/issues/ticket-ref.d.ts.map +1 -1
  63. package/dist/issues/ticket-ref.js +6 -5
  64. package/dist/issues/ticket-ref.js.map +1 -1
  65. package/dist/lib/clone.d.ts.map +1 -1
  66. package/dist/lib/clone.js +6 -2
  67. package/dist/lib/clone.js.map +1 -1
  68. package/dist/lib/runner.d.ts +12 -0
  69. package/dist/lib/runner.d.ts.map +1 -1
  70. package/dist/lib/runner.js +81 -23
  71. package/dist/lib/runner.js.map +1 -1
  72. package/dist/lib/workflow.d.ts +9 -0
  73. package/dist/lib/workflow.d.ts.map +1 -1
  74. package/dist/lib/workflow.js +20 -0
  75. package/dist/lib/workflow.js.map +1 -1
  76. package/dist/linear/client.d.ts +18 -0
  77. package/dist/linear/client.d.ts.map +1 -0
  78. package/dist/linear/client.js +67 -0
  79. package/dist/linear/client.js.map +1 -0
  80. package/dist/linear/comment.d.ts +20 -0
  81. package/dist/linear/comment.d.ts.map +1 -0
  82. package/dist/linear/comment.js +53 -0
  83. package/dist/linear/comment.js.map +1 -0
  84. package/dist/linear/identity.d.ts +47 -0
  85. package/dist/linear/identity.d.ts.map +1 -0
  86. package/dist/linear/identity.js +151 -0
  87. package/dist/linear/identity.js.map +1 -0
  88. package/dist/linear/notify.d.ts +35 -0
  89. package/dist/linear/notify.d.ts.map +1 -0
  90. package/dist/linear/notify.js +65 -0
  91. package/dist/linear/notify.js.map +1 -0
  92. package/dist/linear/ref.d.ts +13 -0
  93. package/dist/linear/ref.d.ts.map +1 -0
  94. package/dist/linear/ref.js +90 -0
  95. package/dist/linear/ref.js.map +1 -0
  96. package/dist/linear/verify.d.ts +21 -0
  97. package/dist/linear/verify.d.ts.map +1 -0
  98. package/dist/linear/verify.js +65 -0
  99. package/dist/linear/verify.js.map +1 -0
  100. package/docs/linear-identity-contract.md +133 -0
  101. package/docs/linear-identity.md +224 -0
  102. package/get-started.md +39 -0
  103. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/linear/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ export interface LinearCommentInput {
2
+ /** `🤖 {actor} · {product}` — must lead the comment (IN-2260 step 3). */
3
+ signature: string;
4
+ verdict: string | null;
5
+ reviewer: string;
6
+ origin: string;
7
+ model: string;
8
+ prUrl: string;
9
+ prTitle: string;
10
+ sha?: string;
11
+ summary?: string;
12
+ service?: string;
13
+ /** Workflow step this verdict came from — a recheck must not read as a review. */
14
+ stepType?: string;
15
+ round?: number;
16
+ }
17
+ export declare function buildLinearCommentBody(input: LinearCommentInput): string;
18
+ export declare function normalizeVerdict(verdict: string | null): string;
19
+ export declare function shouldPostToLinear(verdict: string | null, commentOn: readonly string[]): boolean;
20
+ //# sourceMappingURL=comment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comment.d.ts","sourceRoot":"","sources":["../../src/linear/comment.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,kBAAkB;IACjC,yEAAyE;IACzE,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,kFAAkF;IAClF,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAsBD,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,kBAAkB,GAAG,MAAM,CAyBxE;AAKD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAE/D;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,EAAE,SAAS,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAGhG"}
@@ -0,0 +1,53 @@
1
+ // Body construction for the comment crosscheck posts back to a PR's Linear issue.
2
+ import { buildAnnotation } from '../lib/annotation.js';
3
+ const DEFAULT_SERVICE = 'crosscheck';
4
+ // The visible line must agree with the annotation. Saying "review" on a recheck
5
+ // made the human-readable text contradict the type=recheck sitting right below it.
6
+ // Returns the whole connective phrase, including the preposition — "fix of" is
7
+ // not English, so the label owns it rather than the template appending one.
8
+ function stepLabel(stepType) {
9
+ switch (stepType) {
10
+ case 'recheck': return 'recheck of';
11
+ case 'fix': return 'fix for';
12
+ case 'conflict-resolve': return 'conflict resolution for';
13
+ default: return 'review of';
14
+ }
15
+ }
16
+ // A `]` in the PR title would terminate the markdown link label early.
17
+ function escapeLinkLabel(text) {
18
+ return text.replace(/([[\]])/g, '\\$1');
19
+ }
20
+ export function buildLinearCommentBody(input) {
21
+ const verdict = input.verdict ?? 'UNKNOWN';
22
+ const service = input.service ?? DEFAULT_SERVICE;
23
+ const sections = [
24
+ input.signature,
25
+ '',
26
+ `**${verdict}** — ${input.reviewer} ${stepLabel(input.stepType)} [${escapeLinkLabel(input.prTitle)}](${input.prUrl})`,
27
+ ];
28
+ if (input.summary)
29
+ sections.push('', input.summary);
30
+ // Same annotation schema as the GitHub comment, so one parser reads both.
31
+ sections.push('', buildAnnotation({
32
+ origin: input.origin,
33
+ reviewer: input.reviewer,
34
+ model: input.model,
35
+ type: input.stepType ?? 'review',
36
+ round: input.round ?? 1,
37
+ verdict,
38
+ service,
39
+ ...(input.sha && { sha: input.sha }),
40
+ }));
41
+ return sections.join('\n');
42
+ }
43
+ // parseVerdict yields the human spelling `NEEDS WORK`, while config and the
44
+ // annotation schema both use `NEEDS_WORK`. Comparing them raw silently dropped
45
+ // every NEEDS WORK verdict — the most common actionable one.
46
+ export function normalizeVerdict(verdict) {
47
+ return (verdict ?? 'UNKNOWN').trim().toUpperCase().replace(/\s+/g, '_');
48
+ }
49
+ export function shouldPostToLinear(verdict, commentOn) {
50
+ const wanted = normalizeVerdict(verdict);
51
+ return commentOn.some(v => normalizeVerdict(v) === wanted);
52
+ }
53
+ //# sourceMappingURL=comment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comment.js","sourceRoot":"","sources":["../../src/linear/comment.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAElF,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAoBtD,MAAM,eAAe,GAAG,YAAY,CAAA;AAEpC,gFAAgF;AAChF,mFAAmF;AACnF,+EAA+E;AAC/E,4EAA4E;AAC5E,SAAS,SAAS,CAAC,QAA4B;IAC7C,QAAQ,QAAQ,EAAE,CAAC;QACjB,KAAK,SAAS,CAAC,CAAC,OAAO,YAAY,CAAA;QACnC,KAAK,KAAK,CAAC,CAAC,OAAO,SAAS,CAAA;QAC5B,KAAK,kBAAkB,CAAC,CAAC,OAAO,yBAAyB,CAAA;QACzD,OAAO,CAAC,CAAC,OAAO,WAAW,CAAA;IAC7B,CAAC;AACH,CAAC;AAED,uEAAuE;AACvE,SAAS,eAAe,CAAC,IAAY;IACnC,OAAO,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,MAAM,CAAC,CAAA;AACzC,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,KAAyB;IAC9D,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,IAAI,SAAS,CAAA;IAC1C,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,IAAI,eAAe,CAAA;IAEhD,MAAM,QAAQ,GAAG;QACf,KAAK,CAAC,SAAS;QACf,EAAE;QACF,KAAK,OAAO,QAAQ,KAAK,CAAC,QAAQ,IAAI,SAAS,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,eAAe,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,KAAK,CAAC,KAAK,GAAG;KACtH,CAAA;IAED,IAAI,KAAK,CAAC,OAAO;QAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,CAAC,OAAO,CAAC,CAAA;IAEnD,0EAA0E;IAC1E,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,eAAe,CAAC;QAChC,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,IAAI,EAAE,KAAK,CAAC,QAAQ,IAAI,QAAQ;QAChC,KAAK,EAAE,KAAK,CAAC,KAAK,IAAI,CAAC;QACvB,OAAO;QACP,OAAO;QACP,GAAG,CAAC,KAAK,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC;KACrC,CAAC,CAAC,CAAA;IAEH,OAAO,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AAC5B,CAAC;AAED,4EAA4E;AAC5E,+EAA+E;AAC/E,6DAA6D;AAC7D,MAAM,UAAU,gBAAgB,CAAC,OAAsB;IACrD,OAAO,CAAC,OAAO,IAAI,SAAS,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;AACzE,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,OAAsB,EAAE,SAA4B;IACrF,MAAM,MAAM,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAA;IACxC,OAAO,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,gBAAgB,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,CAAA;AAC5D,CAAC"}
@@ -0,0 +1,47 @@
1
+ import type { LinearConfig } from '../config/schema.js';
2
+ /** Injectable fetch, so tests never touch the network. */
3
+ export type FetchLike = (url: string, init: RequestInit) => Promise<Response>;
4
+ export type LinearAuthMode = 'api_key' | 'client_credentials';
5
+ /** Credentials read from the environment. Resolved in config/loader.ts — never here. */
6
+ export interface LinearCredentials {
7
+ apiKey?: string;
8
+ clientId?: string;
9
+ clientSecret?: string;
10
+ }
11
+ export interface ResolvedLinearAuth {
12
+ mode: LinearAuthMode;
13
+ /** Bare API key (T0) or minted app token (T1). Never log this. */
14
+ token: string;
15
+ /** T1 tokens need an `Authorization: Bearer` prefix; T0 keys are sent bare. */
16
+ bearer: boolean;
17
+ actor: string;
18
+ /** Rendered `🤖 {actor} · {product}` line. Leads every write in both tiers. */
19
+ signature: string;
20
+ /** Set in T1 only — Linear attributes the write to this display name. */
21
+ createAsUser?: string;
22
+ /** Retained so withWorker() can re-render the signature for a derived actor. */
23
+ signatureTemplate: string;
24
+ product: string;
25
+ }
26
+ export interface MintOptions {
27
+ fetchImpl?: FetchLike;
28
+ }
29
+ export interface ResolveOptions extends MintOptions {
30
+ /** Product name used in the signature template. */
31
+ product?: string;
32
+ }
33
+ export declare class LinearConfigError extends Error {
34
+ readonly userError = true;
35
+ constructor(message: string);
36
+ }
37
+ export declare function isLinearConfigError(err: unknown): boolean;
38
+ export declare function renderSignature(template: string, actor: string, product: string): string;
39
+ export declare function normalizeScopes(scopes: string): string;
40
+ export declare function mintAppToken(creds: {
41
+ clientId: string;
42
+ clientSecret: string;
43
+ scopes: string;
44
+ }, opts?: MintOptions): Promise<string>;
45
+ export declare function resolveLinearAuth(config: LinearConfig, creds: LinearCredentials, opts?: ResolveOptions): Promise<ResolvedLinearAuth>;
46
+ export declare function withWorker(auth: ResolvedLinearAuth, worker: string): ResolvedLinearAuth;
47
+ //# sourceMappingURL=identity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../../src/linear/identity.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAIvD,0DAA0D;AAC1D,MAAM,MAAM,SAAS,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;AAE7E,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,oBAAoB,CAAA;AAE7D,wFAAwF;AACxF,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,cAAc,CAAA;IACpB,kEAAkE;IAClE,KAAK,EAAE,MAAM,CAAA;IACb,+EAA+E;IAC/E,MAAM,EAAE,OAAO,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;IACb,+EAA+E;IAC/E,SAAS,EAAE,MAAM,CAAA;IACjB,yEAAyE;IACzE,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,gFAAgF;IAChF,iBAAiB,EAAE,MAAM,CAAA;IACzB,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,WAAW;IAC1B,SAAS,CAAC,EAAE,SAAS,CAAA;CACtB;AAED,MAAM,WAAW,cAAe,SAAQ,WAAW;IACjD,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAOD,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,QAAQ,CAAC,SAAS,QAAO;gBACb,OAAO,EAAE,MAAM;CAI5B;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAEzD;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAExF;AAKD,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED,wBAAsB,YAAY,CAChC,KAAK,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EACjE,IAAI,GAAE,WAAgB,GACrB,OAAO,CAAC,MAAM,CAAC,CA8CjB;AAED,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,iBAAiB,EACxB,IAAI,GAAE,cAAmB,GACxB,OAAO,CAAC,kBAAkB,CAAC,CAwD7B;AAWD,wBAAgB,UAAU,CAAC,IAAI,EAAE,kBAAkB,EAAE,MAAM,EAAE,MAAM,GAAG,kBAAkB,CAWvF"}
@@ -0,0 +1,151 @@
1
+ // Tiered Linear identity (IN-2267 / IN-2269).
2
+ //
3
+ // T0 api_key — operator supplies a personal/workspace key. Writes carry a
4
+ // `🤖 {actor} · {product}` signature line. Works everywhere,
5
+ // weakest attribution.
6
+ // T1 client_credentials — operator creates an OAuth app in their own workspace and
7
+ // enables the Client credentials toggle. We mint a Bearer app
8
+ // token per run and pass `createAsUser` on every write, so
9
+ // Linear renders them as the app (botActor), not a human.
10
+ //
11
+ // Hard requirements this module enforces:
12
+ // * Secrets travel in a POST body — never on argv, never in a URL, never logged.
13
+ // * A configured-but-failing T1 mint ABORTS. It must never silently downgrade to
14
+ // api_key, because that would re-attribute agent writes to a human account.
15
+ const TOKEN_ENDPOINT = 'https://api.linear.app/oauth/token';
16
+ const DEFAULT_PRODUCT = 'crosscheck';
17
+ // Misconfiguration (missing env var, rejected credentials) is a user error, so it
18
+ // must surface as exit 1 — the CLI contract reserves 2 for unexpected failures.
19
+ // commands/review.ts already exits 1 here; the workflow path has to agree.
20
+ export class LinearConfigError extends Error {
21
+ userError = true;
22
+ constructor(message) {
23
+ super(message);
24
+ this.name = 'LinearConfigError';
25
+ }
26
+ }
27
+ export function isLinearConfigError(err) {
28
+ return err instanceof LinearConfigError;
29
+ }
30
+ export function renderSignature(template, actor, product) {
31
+ return template.replaceAll('{actor}', actor).replaceAll('{product}', product);
32
+ }
33
+ // Linear's token endpoint documents scope as a COMMA-separated list, but OAuth 2.0
34
+ // itself specifies space-separated, so operators write it both ways. Accept either
35
+ // and always send commas.
36
+ export function normalizeScopes(scopes) {
37
+ return scopes.split(/[\s,]+/).filter(Boolean).join(',');
38
+ }
39
+ export async function mintAppToken(creds, opts = {}) {
40
+ const doFetch = opts.fetchImpl ?? globalThis.fetch;
41
+ // URLSearchParams keeps the secret in the request body. Nothing here reaches argv.
42
+ const body = new URLSearchParams({
43
+ grant_type: 'client_credentials',
44
+ client_id: creds.clientId,
45
+ client_secret: creds.clientSecret,
46
+ scope: normalizeScopes(creds.scopes),
47
+ });
48
+ let response;
49
+ try {
50
+ response = await doFetch(TOKEN_ENDPOINT, {
51
+ method: 'POST',
52
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
53
+ body: body.toString(),
54
+ });
55
+ }
56
+ catch (err) {
57
+ // Transport failure — Linear is unreachable. Not a configuration problem, so
58
+ // this stays a plain Error and classifies as an unexpected failure (exit 2).
59
+ throw new Error(`Linear token mint failed: ${err instanceof Error ? err.message : String(err)}`);
60
+ }
61
+ const text = await response.text();
62
+ if (!response.ok) {
63
+ // 4xx means Linear rejected these credentials or scopes — the operator can fix
64
+ // that. 5xx is an outage, and 429 is rate limiting: both are transient and must
65
+ // not be blamed on the config.
66
+ const message = `Linear token mint rejected (HTTP ${response.status})`;
67
+ const transient = response.status >= 500 || response.status === 429;
68
+ throw transient ? new Error(message) : new LinearConfigError(message);
69
+ }
70
+ let parsed;
71
+ try {
72
+ parsed = JSON.parse(text);
73
+ }
74
+ catch {
75
+ throw new Error(`Linear token mint returned a non-JSON response (HTTP ${response.status})`);
76
+ }
77
+ const token = parsed.access_token;
78
+ if (typeof token !== 'string' || token.length === 0) {
79
+ throw new Error('Linear token mint returned no access_token');
80
+ }
81
+ return token;
82
+ }
83
+ export async function resolveLinearAuth(config, creds, opts = {}) {
84
+ const product = opts.product ?? DEFAULT_PRODUCT;
85
+ const { actor, signature: template } = config.identity;
86
+ const signature = renderSignature(template, actor, product);
87
+ if (config.auth.mode === 'client_credentials') {
88
+ const missing = [];
89
+ if (!creds.clientId)
90
+ missing.push(config.auth.client_id_env);
91
+ if (!creds.clientSecret)
92
+ missing.push(config.auth.client_secret_env);
93
+ if (missing.length > 0) {
94
+ // Abort rather than fall back — see the module header.
95
+ throw new LinearConfigError(`Linear auth mode is client_credentials but ${missing.join(' and ')} ` +
96
+ `${missing.length > 1 ? 'are' : 'is'} not set. ` +
97
+ 'Set them, or switch linear.auth.mode to api_key.');
98
+ }
99
+ let token;
100
+ try {
101
+ token = await mintAppToken({ clientId: creds.clientId, clientSecret: creds.clientSecret, scopes: config.auth.scopes }, opts);
102
+ }
103
+ catch (err) {
104
+ // Preserve the classification the mint chose: a rejected credential stays a
105
+ // config error (exit 1), an outage stays unexpected (exit 2). Both abort —
106
+ // neither falls back to api_key.
107
+ // Name the variables to check. A bare "HTTP 401" leaves an operator with
108
+ // custom env names guessing which credential to rotate. Names only — never
109
+ // the values.
110
+ const message = `Linear client_credentials token mint failed — aborting rather than falling back ` +
111
+ `to an API key (that would attribute agent writes to a human). ` +
112
+ `Check ${config.auth.client_id_env} and ${config.auth.client_secret_env}. ` +
113
+ `${err instanceof Error ? err.message : String(err)}`;
114
+ throw isLinearConfigError(err) ? new LinearConfigError(message) : new Error(message);
115
+ }
116
+ return {
117
+ mode: 'client_credentials', token, bearer: true, actor, signature,
118
+ createAsUser: actor, signatureTemplate: template, product,
119
+ };
120
+ }
121
+ if (!creds.apiKey) {
122
+ throw new LinearConfigError(`Linear is enabled but ${config.auth.api_key_env} is not set. ` +
123
+ `Set it, or disable the linear block in your config.`);
124
+ }
125
+ return {
126
+ mode: 'api_key', token: creds.apiKey, bearer: false, actor, signature,
127
+ signatureTemplate: template, product,
128
+ };
129
+ }
130
+ // Derives a step-scoped identity: `crosscheck` → `crosscheck/review`.
131
+ //
132
+ // The epic calls for per-worker display names (its example is `symphony/worker-3`)
133
+ // so a fleet of agents does not collapse into one indistinguishable bot. crosscheck's
134
+ // natural axis is the workflow step that produced the write.
135
+ //
136
+ // T1 re-points createAsUser, which is what Linear actually renders. T0 has no
137
+ // createAsUser, so the suffix only reaches the signature line — still an improvement,
138
+ // since that is T0's whole attribution mechanism.
139
+ export function withWorker(auth, worker) {
140
+ const trimmed = worker.trim();
141
+ if (!trimmed)
142
+ return auth;
143
+ const actor = `${auth.actor}/${trimmed}`;
144
+ return {
145
+ ...auth,
146
+ actor,
147
+ signature: renderSignature(auth.signatureTemplate, actor, auth.product),
148
+ ...(auth.createAsUser !== undefined && { createAsUser: actor }),
149
+ };
150
+ }
151
+ //# sourceMappingURL=identity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity.js","sourceRoot":"","sources":["../../src/linear/identity.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ import { type LinearRequestOptions } from './client.js';
2
+ import type { ResolvedLinearAuth } from './identity.js';
3
+ import type { LinearConfig } from '../config/schema.js';
4
+ export type LinearNotifyStatus = 'posted' | 'skipped' | 'failed';
5
+ export interface LinearNotifyResult {
6
+ status: LinearNotifyStatus;
7
+ /** Machine-readable reason for skipped/failed. */
8
+ reason?: string;
9
+ identifier?: string;
10
+ url?: string;
11
+ }
12
+ export interface LinearNotifyParams {
13
+ auth: ResolvedLinearAuth;
14
+ config: LinearConfig;
15
+ pr: {
16
+ branch?: string;
17
+ title: string;
18
+ body?: string;
19
+ url: string;
20
+ sha?: string;
21
+ };
22
+ verdict: string | null;
23
+ reviewer: string;
24
+ origin: string;
25
+ model: string;
26
+ summary?: string;
27
+ service?: string;
28
+ /** Workflow step that produced this verdict. Defaults to 'review'. */
29
+ stepType?: string;
30
+ /** 1-based workflow round. Defaults to 1. */
31
+ round?: number;
32
+ }
33
+ export declare function issueBelongsToWorkspace(issueUrl: string, workspace: string): boolean;
34
+ export declare function notifyLinear(params: LinearNotifyParams, opts?: LinearRequestOptions): Promise<LinearNotifyResult>;
35
+ //# sourceMappingURL=notify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notify.d.ts","sourceRoot":"","sources":["../../src/linear/notify.ts"],"names":[],"mappings":"AASA,OAAO,EAA4C,KAAK,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAEjG,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAA;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAEvD,MAAM,MAAM,kBAAkB,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAA;AAEhE,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,kBAAkB,CAAA;IAC1B,kDAAkD;IAClD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,kBAAkB,CAAA;IACxB,MAAM,EAAE,YAAY,CAAA;IACpB,EAAE,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAChF,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,sEAAsE;IACtE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,6CAA6C;IAC7C,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAGD,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAQpF;AAED,wBAAsB,YAAY,CAChC,MAAM,EAAE,kBAAkB,EAC1B,IAAI,GAAE,oBAAyB,GAC9B,OAAO,CAAC,kBAAkB,CAAC,CA6C7B"}
@@ -0,0 +1,65 @@
1
+ // Orchestrates the one Linear write crosscheck makes: mirroring a review verdict
2
+ // onto the Linear issue the PR belongs to.
3
+ //
4
+ // This never throws. Auth is resolved up front (fail-fast, before the expensive
5
+ // review runs), so anything that goes wrong here is a per-write problem on a review
6
+ // that already succeeded and already posted to GitHub. The caller logs the returned
7
+ // status — failures are reported, never swallowed.
8
+ import { buildLinearCommentBody, shouldPostToLinear } from './comment.js';
9
+ import { findIssueByIdentifier, postLinearComment } from './client.js';
10
+ import { extractLinearRef } from './ref.js';
11
+ // Linear issue URLs are https://linear.app/<workspace>/issue/<ID>/<slug>.
12
+ export function issueBelongsToWorkspace(issueUrl, workspace) {
13
+ try {
14
+ const segments = new URL(issueUrl).pathname.split('/').filter(Boolean);
15
+ return segments[0]?.toLowerCase() === workspace.toLowerCase();
16
+ }
17
+ catch {
18
+ // Unparseable URL from the API — fail closed rather than post to the wrong place.
19
+ return false;
20
+ }
21
+ }
22
+ export async function notifyLinear(params, opts = {}) {
23
+ const { auth, config, pr } = params;
24
+ if (!shouldPostToLinear(params.verdict, config.comment_on)) {
25
+ return { status: 'skipped', reason: 'verdict-not-configured' };
26
+ }
27
+ const ref = extractLinearRef({ branch: pr.branch, title: pr.title, body: pr.body }, config.team_keys);
28
+ if (!ref)
29
+ return { status: 'skipped', reason: 'no-issue-ref' };
30
+ try {
31
+ const issue = await findIssueByIdentifier(auth, ref.id, opts);
32
+ if (!issue)
33
+ return { status: 'skipped', reason: 'issue-not-found', identifier: ref.id };
34
+ // An explicit URL names a workspace, and identifiers repeat across workspaces.
35
+ // If the credentials resolve a different workspace, this is a same-numbered
36
+ // issue somewhere else — never the one the PR pointed at.
37
+ if (ref.workspace && !issueBelongsToWorkspace(issue.url, ref.workspace)) {
38
+ return { status: 'skipped', reason: 'workspace-mismatch', identifier: ref.id };
39
+ }
40
+ const body = buildLinearCommentBody({
41
+ signature: auth.signature,
42
+ verdict: params.verdict,
43
+ reviewer: params.reviewer,
44
+ origin: params.origin,
45
+ model: params.model,
46
+ prUrl: pr.url,
47
+ prTitle: pr.title,
48
+ ...(pr.sha && { sha: pr.sha }),
49
+ ...(params.stepType && { stepType: params.stepType }),
50
+ ...(params.round !== undefined && { round: params.round }),
51
+ ...(params.summary && { summary: params.summary }),
52
+ ...(params.service && { service: params.service }),
53
+ });
54
+ const comment = await postLinearComment(auth, issue.id, body, opts);
55
+ return { status: 'posted', identifier: issue.identifier, url: comment.url };
56
+ }
57
+ catch (err) {
58
+ return {
59
+ status: 'failed',
60
+ reason: err instanceof Error ? err.message : String(err),
61
+ identifier: ref.id,
62
+ };
63
+ }
64
+ }
65
+ //# sourceMappingURL=notify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notify.js","sourceRoot":"","sources":["../../src/linear/notify.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import { type TicketRef } from '../issues/ticket-ref.js';
2
+ export interface LinearIssueRef extends TicketRef {
3
+ source: 'branch' | 'title' | 'body';
4
+ /** Workspace slug, when the ref came from an explicit URL. Bare refs have none. */
5
+ workspace?: string;
6
+ }
7
+ export interface PRMetadata {
8
+ branch?: string | null;
9
+ title?: string | null;
10
+ body?: string | null;
11
+ }
12
+ export declare function extractLinearRef(pr: PRMetadata, teamKeys: readonly string[]): LinearIssueRef | null;
13
+ //# sourceMappingURL=ref.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ref.d.ts","sourceRoot":"","sources":["../../src/linear/ref.ts"],"names":[],"mappings":"AAYA,OAAO,EAAoC,KAAK,SAAS,EAAE,MAAM,yBAAyB,CAAA;AAE1F,MAAM,WAAW,cAAe,SAAQ,SAAS;IAC/C,MAAM,EAAE,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAA;IACnC,mFAAmF;IACnF,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACrB;AAiED,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,cAAc,GAAG,IAAI,CAanG"}
@@ -0,0 +1,90 @@
1
+ // Write-side ref resolution: which Linear issue should crosscheck comment on?
2
+ //
3
+ // Extraction primitives live in src/issues/ticket-ref.ts and are reused here —
4
+ // this module only adds the two things the write path needs on top:
5
+ //
6
+ // 1. linear.app issue URLs, which are unambiguous and so need no configuration.
7
+ // 2. A stricter policy for bare identifiers: they are only honoured for team keys
8
+ // the operator has configured. Enrichment can afford to match `UTF-8` and let
9
+ // the lookup return nothing, because a failed read is a no-op. A failed *write*
10
+ // would post a review comment onto an unrelated issue, so the write path
11
+ // requires the operator to name their team keys first.
12
+ import { extractTicketRefs, parseTicketId } from '../issues/ticket-ref.js';
13
+ // The identifier occupies one whole path segment. Lookaheads kept getting this
14
+ // wrong in both directions: requiring `/` or end-of-path missed `IN-42.` at the
15
+ // end of a sentence, and allowing any non-alphanumeric accepted `IN-123-typo` as
16
+ // `IN-123`. Take the segment, strip trailing sentence punctuation, and require
17
+ // parseTicketId to match it whole.
18
+ const ISSUE_SEGMENTS = /^\/([^/]+)\/issue\/([^/]+)/i;
19
+ // Leading punctuation is safe to allow — but only characters that cannot be part
20
+ // of a hostname, so `notlinear.app` still cannot match. Missing `(linear.app/...)`
21
+ // made it fall through to the bare-ID extractor, which returns no workspace and
22
+ // so silently loses the workspace-mismatch protection.
23
+ const BARE_URL = /(?:^|[\s([{<"'`,;])linear\.app\/([^/\s]+)\/issue\/([^/\s]+)/gi;
24
+ // Absolute URLs are parsed and their hostname compared exactly. A boundary check
25
+ // on the text is not enough: `https://evil.example/linear.app/acme/issue/IN-1`
26
+ // satisfies any "character before linear.app" rule while pointing somewhere else.
27
+ const ABSOLUTE_URL = /\bhttps?:\/\/[^\s<>()"'`]+/gi;
28
+ /** Trailing sentence punctuation is never part of an identifier. */
29
+ function stripTrailingPunctuation(segment) {
30
+ return segment.replace(/[.,;:!?)\]}>'"]+$/, '');
31
+ }
32
+ function fromUrl(text) {
33
+ for (const match of text.matchAll(ABSOLUTE_URL)) {
34
+ let parsed;
35
+ try {
36
+ parsed = new URL(match[0]);
37
+ }
38
+ catch {
39
+ continue;
40
+ }
41
+ if (parsed.hostname.toLowerCase() !== 'linear.app')
42
+ continue;
43
+ const segments = parsed.pathname.match(ISSUE_SEGMENTS);
44
+ if (!segments)
45
+ continue;
46
+ const ref = parseTicketId(stripTrailingPunctuation(segments[2]));
47
+ if (ref)
48
+ return withWorkspace(ref, segments[1]);
49
+ }
50
+ // Iterate, like the absolute-URL path above: a malformed candidate earlier in
51
+ // the text must not stop a valid one later from resolving.
52
+ for (const bare of text.matchAll(BARE_URL)) {
53
+ const ref = parseTicketId(stripTrailingPunctuation(bare[2]));
54
+ if (ref)
55
+ return withWorkspace(ref, bare[1]);
56
+ }
57
+ return null;
58
+ }
59
+ // An explicit URL names a workspace. Identifiers are only unique within one, so
60
+ // dropping the slug lets a URL for workspace A resolve against credentials for
61
+ // workspace B and comment on B's same-numbered issue.
62
+ function withWorkspace(ref, workspace) {
63
+ return { ...ref, workspace: workspace.toLowerCase(), source: 'body' };
64
+ }
65
+ function matchField(text, teamKeys) {
66
+ const url = fromUrl(text);
67
+ if (url)
68
+ return url;
69
+ // Bare identifiers only for configured keys — see the module header.
70
+ if (teamKeys.length === 0)
71
+ return null;
72
+ const bare = extractTicketRefs({ title: text }, [...teamKeys])[0];
73
+ return bare ? { ...bare, source: 'body' } : null;
74
+ }
75
+ export function extractLinearRef(pr, teamKeys) {
76
+ const fields = [
77
+ [pr.branch, 'branch'],
78
+ [pr.title, 'title'],
79
+ [pr.body, 'body'],
80
+ ];
81
+ for (const [text, source] of fields) {
82
+ if (!text)
83
+ continue;
84
+ const ref = matchField(text, teamKeys);
85
+ if (ref)
86
+ return { ...ref, source };
87
+ }
88
+ return null;
89
+ }
90
+ //# sourceMappingURL=ref.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ref.js","sourceRoot":"","sources":["../../src/linear/ref.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import { type LinearAuthMode, type LinearCredentials, type ResolveOptions } from './identity.js';
2
+ import { type LinearRequestOptions } from './client.js';
3
+ import type { LinearConfig } from '../config/schema.js';
4
+ export interface LinearIdentityReport {
5
+ ok: boolean;
6
+ mode: LinearAuthMode;
7
+ /** Actor name writes will carry, including any per-step suffix the caller applies. */
8
+ actor: string;
9
+ organization?: string;
10
+ /**
11
+ * 'app' — writes render as the OAuth application (botActor). The goal state.
12
+ * 'user' — writes attribute to a human account. What IN-2271 is eliminating.
13
+ */
14
+ attribution: 'app' | 'user';
15
+ /** In api_key mode, the human account writes currently attribute to. */
16
+ attributesTo?: string;
17
+ /** Populated when ok is false. Never contains credential material. */
18
+ error?: string;
19
+ }
20
+ export declare function verifyLinearIdentity(config: LinearConfig, creds: LinearCredentials, opts?: ResolveOptions & LinearRequestOptions): Promise<LinearIdentityReport>;
21
+ //# sourceMappingURL=verify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../../src/linear/verify.ts"],"names":[],"mappings":"AAWA,OAAO,EAAqB,KAAK,cAAc,EAAE,KAAK,iBAAiB,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAA;AACnH,OAAO,EAAiB,KAAK,oBAAoB,EAAE,MAAM,aAAa,CAAA;AACtE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAEvD,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,OAAO,CAAA;IACX,IAAI,EAAE,cAAc,CAAA;IACpB,sFAAsF;IACtF,KAAK,EAAE,MAAM,CAAA;IACb,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;;;OAGG;IACH,WAAW,EAAE,KAAK,GAAG,MAAM,CAAA;IAC3B,wEAAwE;IACxE,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,sEAAsE;IACtE,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAQD,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,iBAAiB,EACxB,IAAI,GAAE,cAAc,GAAG,oBAAyB,GAC/C,OAAO,CAAC,oBAAoB,CAAC,CAkD/B"}
@@ -0,0 +1,65 @@
1
+ // Answers the one question a deployment cutover needs answered (IN-2271):
2
+ // when this daemon writes to Linear, whose name is on it?
3
+ //
4
+ // The acceptance criterion for switching Humanbased's deployments to the HB Agent
5
+ // Gateway is "zero writes from symphony/crosscheck attribute to a human account".
6
+ // That is checkable ahead of time — resolve the identity, confirm the token is
7
+ // accepted, and report what the write would render as.
8
+ //
9
+ // Never throws. A verification that fails is a report with ok: false, because the
10
+ // caller is a status command that must still print the rest of its output.
11
+ import { resolveLinearAuth } from './identity.js';
12
+ import { linearGraphQL } from './client.js';
13
+ const ORG_QUERY = `query { organization { name } }`;
14
+ const VIEWER_QUERY = `query { viewer { name email } organization { name } }`;
15
+ export async function verifyLinearIdentity(config, creds, opts = {}) {
16
+ const mode = config.auth.mode;
17
+ const attribution = mode === 'client_credentials' ? 'app' : 'user';
18
+ let auth;
19
+ try {
20
+ auth = await resolveLinearAuth(config, creds, opts);
21
+ }
22
+ catch (err) {
23
+ return {
24
+ ok: false,
25
+ mode,
26
+ actor: config.identity.actor,
27
+ attribution,
28
+ error: err instanceof Error ? err.message : String(err),
29
+ };
30
+ }
31
+ try {
32
+ if (auth.mode === 'client_credentials') {
33
+ // An app token has no viewer — it acts as itself. Reaching the org at all is
34
+ // proof the minted token is accepted.
35
+ const data = await linearGraphQL(auth, ORG_QUERY, {}, opts);
36
+ return {
37
+ ok: true,
38
+ mode: auth.mode,
39
+ actor: auth.actor,
40
+ attribution: 'app',
41
+ ...(data.organization && { organization: data.organization.name }),
42
+ };
43
+ }
44
+ const data = await linearGraphQL(auth, VIEWER_QUERY, {}, opts);
45
+ const who = data.viewer?.email ?? data.viewer?.name ?? undefined;
46
+ return {
47
+ ok: true,
48
+ mode: auth.mode,
49
+ actor: auth.actor,
50
+ attribution: 'user',
51
+ ...(data.organization && { organization: data.organization.name }),
52
+ ...(who && { attributesTo: who }),
53
+ };
54
+ }
55
+ catch (err) {
56
+ return {
57
+ ok: false,
58
+ mode: auth.mode,
59
+ actor: auth.actor,
60
+ attribution,
61
+ error: err instanceof Error ? err.message : String(err),
62
+ };
63
+ }
64
+ }
65
+ //# sourceMappingURL=verify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify.js","sourceRoot":"","sources":["../../src/linear/verify.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,0DAA0D;AAC1D,EAAE;AACF,kFAAkF;AAClF,kFAAkF;AAClF,+EAA+E;AAC/E,uDAAuD;AACvD,EAAE;AACF,kFAAkF;AAClF,2EAA2E;AAE3E,OAAO,EAAE,iBAAiB,EAAoE,MAAM,eAAe,CAAA;AACnH,OAAO,EAAE,aAAa,EAA6B,MAAM,aAAa,CAAA;AAoBtE,MAAM,SAAS,GAAG,iCAAiC,CAAA;AACnD,MAAM,YAAY,GAAG,uDAAuD,CAAA;AAK5E,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,MAAoB,EACpB,KAAwB,EACxB,OAA8C,EAAE;IAEhD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAA;IAC7B,MAAM,WAAW,GAAG,IAAI,KAAK,oBAAoB,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAA;IAElE,IAAI,IAAI,CAAA;IACR,IAAI,CAAC;QACH,IAAI,GAAG,MAAM,iBAAiB,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,CAAA;IACrD,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,OAAO;YACL,EAAE,EAAE,KAAK;YACT,IAAI;YACJ,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,KAAK;YAC5B,WAAW;YACX,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;SACxD,CAAA;IACH,CAAC;IAED,IAAI,CAAC;QACH,IAAI,IAAI,CAAC,IAAI,KAAK,oBAAoB,EAAE,CAAC;YACvC,6EAA6E;YAC7E,sCAAsC;YACtC,MAAM,IAAI,GAAG,MAAM,aAAa,CAAc,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,IAAI,CAAC,CAAA;YACxE,OAAO;gBACL,EAAE,EAAE,IAAI;gBACR,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,WAAW,EAAE,KAAK;gBAClB,GAAG,CAAC,IAAI,CAAC,YAAY,IAAI,EAAE,YAAY,EAAE,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC;aACnE,CAAA;QACH,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,aAAa,CAAiB,IAAI,EAAE,YAAY,EAAE,EAAE,EAAE,IAAI,CAAC,CAAA;QAC9E,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,IAAI,IAAI,SAAS,CAAA;QAChE,OAAO;YACL,EAAE,EAAE,IAAI;YACR,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,WAAW,EAAE,MAAM;YACnB,GAAG,CAAC,IAAI,CAAC,YAAY,IAAI,EAAE,YAAY,EAAE,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC;YAClE,GAAG,CAAC,GAAG,IAAI,EAAE,YAAY,EAAE,GAAG,EAAE,CAAC;SAClC,CAAA;IACH,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,OAAO;YACL,EAAE,EAAE,KAAK;YACT,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,WAAW;YACX,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;SACxD,CAAA;IACH,CAAC;AACH,CAAC"}