@open-everywhere/provider-github 0.0.0-stage → 0.1.0

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 (111) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +21 -0
  3. package/README.md +50 -2
  4. package/agent/channels/github-catch-up.ts +134 -0
  5. package/agent/channels/github-engagement.ts +381 -0
  6. package/agent/channels/github-identity.ts +376 -0
  7. package/agent/channels/github-installations.ts +152 -0
  8. package/agent/channels/github-message-ops.ts +62 -0
  9. package/agent/channels/github-origin-codec.ts +42 -0
  10. package/agent/channels/github-post-arm.ts +422 -0
  11. package/agent/channels/github-reaction-feedback.ts +489 -0
  12. package/agent/channels/github-reaction-scope.ts +312 -0
  13. package/agent/channels/github-read.ts +471 -0
  14. package/agent/channels/github-stop.ts +175 -0
  15. package/agent/channels/github-supervisor.ts +645 -0
  16. package/agent/channels/github-webhook-verifier.ts +56 -0
  17. package/agent/channels/github.ts +1458 -0
  18. package/agent/contributions.ts +31 -0
  19. package/agent/docs-generated.ts +15 -0
  20. package/agent/memory/memory.ts +265 -0
  21. package/agent/ops.ts +17 -0
  22. package/agent/provider-events.ts +114 -0
  23. package/agent/register.ts +130 -0
  24. package/agent/surface-parity.ts +116 -0
  25. package/dist/agent/channels/github-catch-up.d.ts +29 -0
  26. package/dist/agent/channels/github-catch-up.d.ts.map +1 -0
  27. package/dist/agent/channels/github-catch-up.js +105 -0
  28. package/dist/agent/channels/github-catch-up.js.map +1 -0
  29. package/dist/agent/channels/github-engagement.d.ts +223 -0
  30. package/dist/agent/channels/github-engagement.d.ts.map +1 -0
  31. package/dist/agent/channels/github-engagement.js +317 -0
  32. package/dist/agent/channels/github-engagement.js.map +1 -0
  33. package/dist/agent/channels/github-identity.d.ts +164 -0
  34. package/dist/agent/channels/github-identity.d.ts.map +1 -0
  35. package/dist/agent/channels/github-identity.js +295 -0
  36. package/dist/agent/channels/github-identity.js.map +1 -0
  37. package/dist/agent/channels/github-installations.d.ts +49 -0
  38. package/dist/agent/channels/github-installations.d.ts.map +1 -0
  39. package/dist/agent/channels/github-installations.js +114 -0
  40. package/dist/agent/channels/github-installations.js.map +1 -0
  41. package/dist/agent/channels/github-message-ops.d.ts +3 -0
  42. package/dist/agent/channels/github-message-ops.d.ts.map +1 -0
  43. package/dist/agent/channels/github-message-ops.js +58 -0
  44. package/dist/agent/channels/github-message-ops.js.map +1 -0
  45. package/dist/agent/channels/github-origin-codec.d.ts +7 -0
  46. package/dist/agent/channels/github-origin-codec.d.ts.map +1 -0
  47. package/dist/agent/channels/github-origin-codec.js +36 -0
  48. package/dist/agent/channels/github-origin-codec.js.map +1 -0
  49. package/dist/agent/channels/github-post-arm.d.ts +54 -0
  50. package/dist/agent/channels/github-post-arm.d.ts.map +1 -0
  51. package/dist/agent/channels/github-post-arm.js +339 -0
  52. package/dist/agent/channels/github-post-arm.js.map +1 -0
  53. package/dist/agent/channels/github-reaction-feedback.d.ts +97 -0
  54. package/dist/agent/channels/github-reaction-feedback.d.ts.map +1 -0
  55. package/dist/agent/channels/github-reaction-feedback.js +380 -0
  56. package/dist/agent/channels/github-reaction-feedback.js.map +1 -0
  57. package/dist/agent/channels/github-reaction-scope.d.ts +102 -0
  58. package/dist/agent/channels/github-reaction-scope.d.ts.map +1 -0
  59. package/dist/agent/channels/github-reaction-scope.js +296 -0
  60. package/dist/agent/channels/github-reaction-scope.js.map +1 -0
  61. package/dist/agent/channels/github-read.d.ts +135 -0
  62. package/dist/agent/channels/github-read.d.ts.map +1 -0
  63. package/dist/agent/channels/github-read.js +371 -0
  64. package/dist/agent/channels/github-read.js.map +1 -0
  65. package/dist/agent/channels/github-stop.d.ts +52 -0
  66. package/dist/agent/channels/github-stop.d.ts.map +1 -0
  67. package/dist/agent/channels/github-stop.js +145 -0
  68. package/dist/agent/channels/github-stop.js.map +1 -0
  69. package/dist/agent/channels/github-supervisor.d.ts +308 -0
  70. package/dist/agent/channels/github-supervisor.d.ts.map +1 -0
  71. package/dist/agent/channels/github-supervisor.js +339 -0
  72. package/dist/agent/channels/github-supervisor.js.map +1 -0
  73. package/dist/agent/channels/github-webhook-verifier.d.ts +5 -0
  74. package/dist/agent/channels/github-webhook-verifier.d.ts.map +1 -0
  75. package/dist/agent/channels/github-webhook-verifier.js +55 -0
  76. package/dist/agent/channels/github-webhook-verifier.js.map +1 -0
  77. package/dist/agent/channels/github.d.ts +139 -0
  78. package/dist/agent/channels/github.d.ts.map +1 -0
  79. package/dist/agent/channels/github.js +1232 -0
  80. package/dist/agent/channels/github.js.map +1 -0
  81. package/dist/agent/contributions.d.ts +12 -0
  82. package/dist/agent/contributions.d.ts.map +1 -0
  83. package/dist/agent/contributions.js +31 -0
  84. package/dist/agent/contributions.js.map +1 -0
  85. package/dist/agent/docs-generated.d.ts +3 -0
  86. package/dist/agent/docs-generated.d.ts.map +1 -0
  87. package/dist/agent/docs-generated.js +13 -0
  88. package/dist/agent/docs-generated.js.map +1 -0
  89. package/dist/agent/memory/memory.d.ts +54 -0
  90. package/dist/agent/memory/memory.d.ts.map +1 -0
  91. package/dist/agent/memory/memory.js +224 -0
  92. package/dist/agent/memory/memory.js.map +1 -0
  93. package/dist/agent/ops.d.ts +3 -0
  94. package/dist/agent/ops.d.ts.map +1 -0
  95. package/dist/agent/ops.js +16 -0
  96. package/dist/agent/ops.js.map +1 -0
  97. package/dist/agent/provider-events.d.ts +3 -0
  98. package/dist/agent/provider-events.d.ts.map +1 -0
  99. package/dist/agent/provider-events.js +112 -0
  100. package/dist/agent/provider-events.js.map +1 -0
  101. package/dist/agent/register.d.ts +4 -0
  102. package/dist/agent/register.d.ts.map +1 -0
  103. package/dist/agent/register.js +116 -0
  104. package/dist/agent/register.js.map +1 -0
  105. package/dist/agent/surface-parity.d.ts +3 -0
  106. package/dist/agent/surface-parity.d.ts.map +1 -0
  107. package/dist/agent/surface-parity.js +110 -0
  108. package/dist/agent/surface-parity.js.map +1 -0
  109. package/package.json +156 -4
  110. package/tsconfig.build.json +22 -0
  111. package/tsconfig.json +15 -0
@@ -0,0 +1,295 @@
1
+ // Pure GitHub→OpenEverywhere identity capture. The GitHub channel (github.ts) calls
2
+ // these from its `onComment` hook to stow RAW GitHub facts (repo owner/name + privacy +
3
+ // conversation anchor + actor) into the session auth context's `attributes`. The
4
+ // INTERPRETATION of those facts into visibility/trust lives here too (githubVisibility/
5
+ // githubTrust) — kept beside the capture so the mapper in agent/identity/binding.ts
6
+ // (bindGitHub) just calls them, exactly as Slack/Teams split capture from
7
+ // interpretation. This file imports no eve types, so it's trivially unit-testable.
8
+ //
9
+ // The two security-critical decisions are ONE tested place each and both FAIL CLOSED:
10
+ // - githubVisibility: "is this a world-readable (public-repo) surface whose learnings
11
+ // may flow up to shared memory?" — only an explicit public repo yields `public`.
12
+ // - githubTrust: "is this surface positively inside the org trust boundary?" — only a
13
+ // PRIVATE repo touched by an org OWNER/MEMBER yields `internal`; everything else
14
+ // (public repos, outside collaborators, missing signals) → no verdict, so bindFrom
15
+ // binds it `external`.
16
+ //
17
+ // GitHub has no 1:1 DM surface (every issue/PR/review thread is a shared conversation on
18
+ // a repo), so mode is always "channel" — bindGitHub inlines that, no helper needed.
19
+ //
20
+ // The IDENTITY MAPPER (bindGitHub) lives here too (moved out of core identity/binding.ts in
21
+ // OPE-114 stage 4, mirroring the notion bundle): registerGithubProvider() registers it via the
22
+ // registerIdentityMapper seam. The only imports are TYPE-ONLY barrel contracts (erased at
23
+ // compile), so the file stays trivially unit-testable.
24
+ /** Build the auth context OpenEverywhere dispatches with — raw facts as string attributes. */
25
+ export function buildGitHubAuth(input) {
26
+ const attributes = {
27
+ owner: input.owner,
28
+ repo: input.repo,
29
+ user_login: input.actorLogin,
30
+ };
31
+ if (input.repositoryFullName)
32
+ attributes.repository = input.repositoryFullName;
33
+ if (typeof input.repositoryId === "number")
34
+ attributes.repository_id = String(input.repositoryId);
35
+ if (typeof input.isPrivate === "boolean")
36
+ attributes.repo_private = String(input.isPrivate);
37
+ if (input.conversationKind)
38
+ attributes.conversation_kind = input.conversationKind;
39
+ if (typeof input.issueNumber === "number")
40
+ attributes.issue_number = String(input.issueNumber);
41
+ if (typeof input.pullRequestNumber === "number")
42
+ attributes.pull_request_number = String(input.pullRequestNumber);
43
+ if (typeof input.reviewThreadRootCommentId === "number")
44
+ attributes.review_thread_root_comment_id = String(input.reviewThreadRootCommentId);
45
+ if (typeof input.installationId === "number")
46
+ attributes.installation_id = String(input.installationId);
47
+ if (typeof input.actorId === "number")
48
+ attributes.user_id = String(input.actorId);
49
+ if (input.actorType)
50
+ attributes.user_type = input.actorType;
51
+ if (input.authorAssociation)
52
+ attributes.author_association = input.authorAssociation;
53
+ return {
54
+ authenticator: "github",
55
+ principalId: input.actorLogin,
56
+ principalType: input.actorType === "Bot" ? "service" : "github-user",
57
+ attributes,
58
+ };
59
+ }
60
+ /**
61
+ * A comment @mentions the bot and should wake a turn. Faithfully mirrors eve's
62
+ * internal gate (shouldDispatchGitHubComment: isIgnoredGitHubComment +
63
+ * extractGitHubCommentTrigger), which is NOT on eve's public export surface — so we
64
+ * replicate it here (small, deterministic, and unit-tested) rather than reach into a
65
+ * package-internal path. Kept byte-consistent with eve because the channel's message
66
+ * layer still strips the mention token via eve's own extractGitHubCommentTrigger; a
67
+ * divergent gate here would fire turns eve then can't strip. FAIL CLOSED: no botName,
68
+ * a bot author, the bot's own comments, or eve's marker → no wake.
69
+ */
70
+ export function githubBotMentioned(body, botName, author) {
71
+ const name = botName?.trim();
72
+ if (!name || !body)
73
+ return false;
74
+ if (body.includes("<!-- eve:github:"))
75
+ return false; // eve's own comment marker — never self-trigger
76
+ if (author?.type === "Bot")
77
+ return false; // ignore bot actors (loop guard)
78
+ if (author?.login && author.login.toLowerCase() === `${name}[bot]`.toLowerCase())
79
+ return false; // the bot itself
80
+ const escaped = name.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
81
+ // "@name" not followed by a word char — matches eve's `@${name}(?=$|[^A-Za-z0-9_-])`.
82
+ return new RegExp(`@${escaped}(?=$|[^A-Za-z0-9_-])`, "iu").test(body);
83
+ }
84
+ /**
85
+ * GitHub repo privacy → visibility. FAIL CLOSED, mirroring slackVisibility/
86
+ * linearVisibility: only an explicitly PUBLIC repo (`repo_private="false"`) yields
87
+ * `public`; a private repo (`"true"`) or an unknown signal → `private`, so an
88
+ * under-identified session or a private repo never writes its learnings up to shared
89
+ * (workspace) memory. A public repo's issue/PR threads are world-readable, so treating
90
+ * them as a public surface (learnings may flow to the workspace brain across the org's
91
+ * repos) is correct; anything we can't confirm public stays surface-tier private.
92
+ */
93
+ export function githubVisibility(repoPrivate) {
94
+ if (repoPrivate === "false")
95
+ return "public";
96
+ return "private"; // "true" or unknown → fail closed
97
+ }
98
+ /**
99
+ * GitHub actor + repo privacy → trust (the shared-memory gate). FAIL CLOSED — only a
100
+ * POSITIVE in-boundary fact yields a verdict:
101
+ * - A PRIVATE repo (only org members + invited collaborators can see it) touched by an
102
+ * org OWNER or MEMBER → `internal`. OWNER/MEMBER are GitHub's strong "part of the org
103
+ * that owns this repo" signals (`author_association`); on a private repo that means an
104
+ * insider acting inside the boundary, so shared/org memory is safe to read.
105
+ * - ANYTHING ELSE → `undefined`, so bindFrom FAILS CLOSED to `external`:
106
+ * · a PUBLIC repo — world-readable, anyone (external contributors) can comment, so it
107
+ * must never light up the org brain regardless of who this particular actor is;
108
+ * · a private repo touched by a COLLABORATOR (may be an OUTSIDE collaborator),
109
+ * CONTRIBUTOR, first-timer, mannequin, NONE, or a missing/unknown association;
110
+ * · a missing repo-privacy signal.
111
+ * The operator can still vouch for a deployment with OE_TRUST=internal — the same layer
112
+ * Slack-DM/Teams/Discord/Linear use. An ambiguous GitHub event must never reach
113
+ * the shared org brain. (Per-repo collaborator-membership enrichment — GET
114
+ * /repos/{o}/{r}/collaborators/{u}/permission — is the future layer-2 gate that could
115
+ * promote a verified internal collaborator; until then, only OWNER/MEMBER on a private
116
+ * repo is trusted.)
117
+ */
118
+ export function githubTrust(repoPrivate, authorAssociation) {
119
+ if (repoPrivate !== "true")
120
+ return undefined; // public / unknown repo → no verdict (binds external)
121
+ if (authorAssociation === "OWNER" || authorAssociation === "MEMBER")
122
+ return "internal";
123
+ return undefined; // collaborator/contributor/outside/unknown on a private repo → fail closed
124
+ }
125
+ // --- the EXTERNAL-AUTHOR grain (OPE-533) --------------------------------------------------------
126
+ //
127
+ // THE EVIDENCE IS FREE. GitHub reports `author_association` on every `issue_comment` payload, and
128
+ // {@link buildGitHubAuth} already stows it. The author grain needs no new call and no new
129
+ // installation permission — the org-membership probe (`orgs/{org}/members/{user}`) would need the
130
+ // "Members" permission, which every installed org would have to re-approve.
131
+ //
132
+ // 🔴 THE SET IS A RULING, NOT AN INFERENCE (Tommy, 2026-08-14). INTERNAL is the three values that
133
+ // each carry a DELIBERATE ACCESS GRANT on the repo: `OWNER`, `MEMBER`, `COLLABORATOR`. Every other
134
+ // value is EXCLUDED. Tommy verbatim: "contributor shouldn't be trusted" — a past merged PR is not an
135
+ // access grant. `MANNEQUIN` is an import placeholder with no live person behind it, so it is
136
+ // excluded as hygiene.
137
+ //
138
+ // 🔴 THIS IS A DIFFERENT QUESTION FROM {@link githubTrust}, AND THE TWO MUST NOT BE MERGED.
139
+ // `githubTrust` asks "may this session read the shared org brain?" and fails CLOSED, so it demands
140
+ // a PRIVATE repo AND an OWNER/MEMBER author. This asks "may this person's bytes task the agent?"
141
+ // and fails OPEN. A COLLABORATOR is internal here and still not trusted there, which is correct:
142
+ // denying service is a different act from opening the org's memory.
143
+ /** `author_association` values that carry a deliberate access grant on the repo — the INTERNAL set. */
144
+ export const GITHUB_INTERNAL_ASSOCIATIONS = ["OWNER", "MEMBER", "COLLABORATOR"];
145
+ /** `author_association` values that carry NO access grant — the EXCLUDED (external) set. This is
146
+ * GitHub's whole documented enum minus {@link GITHUB_INTERNAL_ASSOCIATIONS}, written out rather
147
+ * than derived by negation: see {@link githubAssociationRefutes} for why the difference matters. */
148
+ export const GITHUB_EXTERNAL_ASSOCIATIONS = [
149
+ "CONTRIBUTOR",
150
+ "FIRST_TIME_CONTRIBUTOR",
151
+ "FIRST_TIMER",
152
+ "NONE",
153
+ "MANNEQUIN",
154
+ ];
155
+ /**
156
+ * Does this `author_association` POSITIVELY refute the author's membership?
157
+ *
158
+ * 🔴 IT MATCHES THE EXCLUDED SET, IT DOES NOT NEGATE THE INTERNAL ONE, and the difference is the
159
+ * whole fail-open posture. An ABSENT association (a payload shape GitHub changed, a synthesised
160
+ * event) and an UNRECOGNISED one (a value GitHub adds later) both answer `false` — "not refuted",
161
+ * which is what `IngestedChatMessage.refuted` means by absence of evidence. Negating the internal
162
+ * set would read every unknown as external and take the agent off the air on a payload change.
163
+ *
164
+ * Case-sensitive on purpose: GitHub sends these values upper-case, and accepting a lower-case
165
+ * variant would be inventing a second spelling of a vendor enum.
166
+ */
167
+ export function githubAssociationRefutes(authorAssociation) {
168
+ return authorAssociation !== undefined && GITHUB_EXTERNAL_ASSOCIATIONS.includes(authorAssociation);
169
+ }
170
+ /**
171
+ * The author grain's evidence for one GitHub comment, in the core's own vocabulary
172
+ * (`IngestedChatMessage`) so access/external-policy.ts answers the exclusion question — this bundle
173
+ * never re-derives it.
174
+ *
175
+ * A `Bot` actor grades as the machine floor `relay` and is therefore NEVER excluded, which is the
176
+ * core's own ruling (access/taint-signal.ts `nonMemberChatTrust`): a bot is below peer standing but
177
+ * it is not a person outside the organisation, and withholding every app's comments would be an
178
+ * outage. Stating it as the tier rather than as a local `if` keeps ONE place deciding.
179
+ *
180
+ * A human author carries `refuted` only when {@link githubAssociationRefutes} says so. `trust` is
181
+ * left ABSENT for a human on purpose: this runs before the per-wake trust resolution, so naming a
182
+ * tier here would be a guess, and the refutation leg needs no tier.
183
+ */
184
+ export function githubAuthorFacts(input) {
185
+ if (input.actorType === "Bot")
186
+ return { trust: "relay" };
187
+ return githubAssociationRefutes(input.authorAssociation) ? { refuted: true } : {};
188
+ }
189
+ function githubAuthOf(ctx) {
190
+ return ctx.session?.auth?.current ?? ctx.session?.auth?.initiator ?? null;
191
+ }
192
+ function githubAttr(a, keys) {
193
+ if (!a)
194
+ return undefined;
195
+ for (const k of keys) {
196
+ const v = a[k];
197
+ if (typeof v === "string" && v.length > 0)
198
+ return v;
199
+ if (Array.isArray(v) && typeof v[0] === "string" && v[0].length > 0)
200
+ return v[0];
201
+ }
202
+ return undefined;
203
+ }
204
+ function githubAuthPrincipalId(ctx) {
205
+ const pid = githubAuthOf(ctx)?.principalId;
206
+ return pid && pid.length > 0 ? pid : undefined;
207
+ }
208
+ /**
209
+ * The CONVERSATION key the stop registry keys a GitHub turn by (OPE-898) — `owner/repo#number`.
210
+ *
211
+ * ONE derivation, used by BOTH sides of the affordance: {@link bindGitHub} writes it onto the anchor
212
+ * row at turn start, and the stop-comment leg (channels/github-stop.ts) rebuilds it from the webhook
213
+ * that carries the directive. Two spellings of this key would make the whole arm silently miss.
214
+ *
215
+ * It is NOT a surface key and never becomes one — the GitHub surface grain is the REPO, and this
216
+ * value names one issue or PR inside it. Returns undefined on any missing part: a partial key would
217
+ * collide across repos.
218
+ */
219
+ export function githubConversationKey(owner, repo, number) {
220
+ return owner && repo && Number.isFinite(number) ? `${owner}/${repo}#${number}` : undefined;
221
+ }
222
+ /**
223
+ * The ROOT comment id of the review thread this turn arrived in, or `undefined` (OPE-958). The read
224
+ * side of {@link GitHubIdentityInput.reviewThreadRootCommentId} — this file owns the attribute
225
+ * spelling on BOTH sides, so a rename cannot leave the reader looking for a key nobody writes.
226
+ *
227
+ * It reads the AUTH ATTRIBUTES rather than the composed binding on purpose. `RawIdentity.target` is
228
+ * eve's `GitHubReceiveTarget` and throws on a member it does not know, so the root cannot ride
229
+ * there, and no other binding field names a comment.
230
+ *
231
+ * Returns the id as a STRING of digits, which is what `ReadAdapter.readThread` takes as its thread
232
+ * id. A non-numeric value is treated as absent: a review thread id that is not a comment id would
233
+ * fetch nothing, and refusing honestly beats reading an empty thread.
234
+ */
235
+ export function githubReviewThreadRoot(ctx) {
236
+ const raw = githubAttr(githubAuthOf(ctx)?.attributes, ["review_thread_root_comment_id", "reviewThreadRootCommentId"]);
237
+ return raw !== undefined && /^\d+$/u.test(raw) ? raw : undefined;
238
+ }
239
+ export const bindGitHub = (ctx) => {
240
+ const a = githubAuthOf(ctx)?.attributes;
241
+ const owner = githubAttr(a, ["owner"]);
242
+ const repo = githubAttr(a, ["repo"]);
243
+ const repoPrivate = githubAttr(a, ["repo_private", "repoPrivate"]);
244
+ const conversationKind = githubAttr(a, ["conversation_kind", "conversationKind"]);
245
+ const issueNumber = githubAttr(a, ["issue_number", "issueNumber"]);
246
+ const pullRequestNumber = githubAttr(a, ["pull_request_number", "pullRequestNumber"]);
247
+ const installationId = githubAttr(a, ["installation_id", "installationId"]);
248
+ // Proactive post-back uses eve's GitHubReceiveTarget: { owner, repo, and EXACTLY ONE of
249
+ // issueNumber | pullRequestNumber } (supplying both throws). Pick the PR number for a
250
+ // pull_request/review_thread conversation, else the issue number. Only emitted when we
251
+ // have owner + repo + a usable anchor number.
252
+ const isPr = conversationKind === "pull_request" || conversationKind === "review_thread";
253
+ const anchor = isPr && pullRequestNumber
254
+ ? { pullRequestNumber: Number(pullRequestNumber) }
255
+ : issueNumber
256
+ ? { issueNumber: Number(issueNumber) }
257
+ : pullRequestNumber
258
+ ? { pullRequestNumber: Number(pullRequestNumber) }
259
+ : undefined;
260
+ return {
261
+ workspace: owner,
262
+ surface: repo,
263
+ mode: "channel", // GitHub has no DM analog — every issue/PR/review thread is shared
264
+ visibility: githubVisibility(repoPrivate),
265
+ trust: githubTrust(repoPrivate, githubAttr(a, ["author_association", "authorAssociation"])),
266
+ principalId: githubAuthPrincipalId(ctx) ?? githubAttr(a, ["user_login", "userLogin", "user_id", "userId"]),
267
+ // OPE-811: `installationId` rides the post-back target so the mid-turn `reply` arm mints its
268
+ // token against THIS wake's own installation instead of falling back to the owner→installation
269
+ // map. Absent when the webhook carried none — the arm's map lookup is the fallback, never a guess.
270
+ target: owner && repo && anchor ? { owner, repo, ...anchor, ...(installationId ? { installationId: Number(installationId) } : {}) } : undefined,
271
+ // THE STOP REGISTRY'S ADDRESS FOR THIS TURN (OPE-898). A GitHub conversation is ONE issue or PR
272
+ // thread, so that is what a stop directive posted into the thread can name. It cannot ride
273
+ // `target`: that object is eve's `GitHubReceiveTarget` and it throws on an unexpected member.
274
+ //
275
+ // GitHub sends NO webhook for a reaction (verified against the event list, 2026-08-20), so the
276
+ // trigger on this surface is a bare `stop` comment read at the ingress — see
277
+ // channels/github-stop.ts. The key it resolves by is this one.
278
+ ...(anchor
279
+ ? {
280
+ anchorChannelId: githubConversationKey(owner, repo, "pullRequestNumber" in anchor ? anchor.pullRequestNumber : anchor.issueNumber),
281
+ // THE ENGAGEMENT REGISTRY'S ADDRESS FOR THIS TURN (OPE-1501) — the SAME container key, and
282
+ // deliberately the same derivation rather than a second spelling of it. `stand_down` marks
283
+ // `binding.engagementKey ?? binding.threadTs`, and GitHub carries no `threadTs`, so
284
+ // without this field the verb found no anchor and refused on every GitHub turn.
285
+ //
286
+ // It is a SEPARATE field from `anchorChannelId` because the two answer different
287
+ // questions — which running turn a stop directive cancels, and which conversation row the
288
+ // agent was asked to stop following — and `channels/github-engagement.ts` reads this one
289
+ // back through `githubEngagementAnchor`, which calls the very same function.
290
+ engagementKey: githubConversationKey(owner, repo, "pullRequestNumber" in anchor ? anchor.pullRequestNumber : anchor.issueNumber),
291
+ }
292
+ : {}),
293
+ };
294
+ };
295
+ //# sourceMappingURL=github-identity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github-identity.js","sourceRoot":"","sources":["../../../agent/channels/github-identity.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ /** One installation delivery, normalized. `effect` is the whole decision; everything else is the
2
+ * row/audit payload. */
3
+ export interface GitHubInstallationEvent {
4
+ readonly effect: "learn" | "revoke";
5
+ /** The webhook's `action` — carried verbatim into the audit row (why the effect happened). */
6
+ readonly action: string;
7
+ /** `installation.account.login` — the SCM map's key. */
8
+ readonly owner: string;
9
+ readonly installationId: string;
10
+ /** `installation.account.type` — "Organization" | "User". */
11
+ readonly accountType?: string;
12
+ /** `installation.repository_selection` — "all" | "selected". */
13
+ readonly repositorySelection?: string;
14
+ /** How many repos the delivery named, when it named any (diagnostics in the audit row only —
15
+ * the repo LIST is never stored; the App's own grant is the source of truth). */
16
+ readonly repositoryCount?: number;
17
+ }
18
+ /**
19
+ * Normalize one webhook delivery into a {@link GitHubInstallationEvent}, or null when it isn't an
20
+ * installation delivery we act on. PURE — no I/O, so every branch is unit-tested.
21
+ *
22
+ * FAIL-CLOSED on an unusable payload: an unknown action, a missing `installation.id`, or a missing
23
+ * `installation.account.login` all return null (nothing learned, nothing revoked) rather than
24
+ * writing a half-formed row. An installation with no resolvable owner is unroutable anyway.
25
+ */
26
+ export declare function extractGitHubInstallationEvent(eventType: string, payload: Record<string, unknown>): GitHubInstallationEvent | null;
27
+ /** The side-effecting seams, injected so the dispatch core is testable without a store or a clock. */
28
+ export interface GitHubInstallationDeps {
29
+ readonly record: (rec: {
30
+ owner: string;
31
+ installationId: string;
32
+ accountType?: string;
33
+ repositorySelection?: string;
34
+ updatedAt: string;
35
+ }) => Promise<void>;
36
+ readonly forget: (owner: string) => Promise<boolean>;
37
+ readonly audit: (action: string, detail: Record<string, unknown>, workspace: string) => Promise<void>;
38
+ readonly now?: () => string;
39
+ readonly log?: (msg: string) => void;
40
+ }
41
+ /**
42
+ * Apply one normalized delivery: persist-or-clear the owner→installation entry, then audit it.
43
+ *
44
+ * ORDER IS DELIBERATE — the durable write happens FIRST and its failure propagates (the route's
45
+ * catch logs it), so we never audit an install we didn't actually record. The audit row is written
46
+ * after, best-effort at the appendAudit layer.
47
+ */
48
+ export declare function handleGitHubInstallationDelivery(ev: GitHubInstallationEvent, deps: GitHubInstallationDeps): Promise<void>;
49
+ //# sourceMappingURL=github-installations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github-installations.d.ts","sourceRoot":"","sources":["../../../agent/channels/github-installations.ts"],"names":[],"mappings":"AAwBA;yBACyB;AACzB,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,MAAM,EAAE,OAAO,GAAG,QAAQ,CAAC;IACpC,8FAA8F;IAC9F,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,wDAAwD;IACxD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,6DAA6D;IAC7D,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,gEAAgE;IAChE,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IACtC;sFACkF;IAClF,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;CACnC;AAoBD;;;;;;;GAOG;AACH,wBAAgB,8BAA8B,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,uBAAuB,GAAG,IAAI,CAiClI;AAED,sGAAsG;AACtG,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE;QACrB,KAAK,EAAE,MAAM,CAAC;QACd,cAAc,EAAE,MAAM,CAAC;QACvB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,mBAAmB,CAAC,EAAE,MAAM,CAAC;QAC7B,SAAS,EAAE,MAAM,CAAC;KACnB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpB,QAAQ,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACrD,QAAQ,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtG,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IAC5B,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC;AAED;;;;;;GAMG;AACH,wBAAsB,gCAAgC,CAAC,EAAE,EAAE,uBAAuB,EAAE,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CA0B/H"}
@@ -0,0 +1,114 @@
1
+ // The GitHub App's INSTALLATION leg — the eve-free, tested half of "an org installing the App
2
+ // registers itself" (OPE-118 GitHub §3; github.ts is the thin wiring, mirroring github-supervisor.ts).
3
+ //
4
+ // GitHub's App-level webhook (ONE url + ONE secret, fanning in every installation of the App across
5
+ // every account) delivers:
6
+ // • `installation` — actions `created`, `deleted`, `suspend`, `unsuspend`, `new_permissions_accepted`
7
+ // • `installation_repositories` — actions `added`, `removed` (the repo-scope change)
8
+ // Verified against docs.github.com/en/webhooks/webhook-events-and-payloads (2026-07). We map those
9
+ // onto exactly two effects, LEARN and REVOKE:
10
+ // LEARN ← created / unsuspend / new_permissions_accepted / added / removed
11
+ // REVOKE ← deleted / suspend
12
+ // `suspend` revokes because a suspended installation's tokens are refused by GitHub — keeping the
13
+ // row would advertise an installation that cannot mint, and `unsuspend` re-learns it. The two
14
+ // metadata-only actions (`new_permissions_accepted`, and both `installation_repositories` actions)
15
+ // re-LEARN rather than no-op so `repository_selection` on the row can't drift from reality.
16
+ //
17
+ // SIGNATURE: this module never verifies anything — it runs INSIDE the route, AFTER the one
18
+ // existing `gateGithubDelivery` HMAC gate (GITHUB_WEBHOOK_SECRET) that already fail-closed 401s
19
+ // every unverified delivery for the whole route. There is deliberately no second verification path.
20
+ //
21
+ // AUDIT: install/uninstall is a surface-lifecycle fact, so it takes the cross-platform `surface.*`
22
+ // action namespace (the same one slack-front/teams-front's wake_failed/wake_dropped rows use),
23
+ // not a github-only verb — an operator asking "who installed us, and when?" wants one query.
24
+ const LEARN_ACTIONS = new Set(["created", "unsuspend", "new_permissions_accepted", "added", "removed"]);
25
+ const REVOKE_ACTIONS = new Set(["deleted", "suspend"]);
26
+ function str(v) {
27
+ return typeof v === "string" && v.length > 0 ? v : undefined;
28
+ }
29
+ /** GitHub sends `installation.id` as a NUMBER; the mint API wants a path segment. Accept both
30
+ * spellings and reject anything else (never coerce an object/array into "[object Object]"). */
31
+ function idOf(v) {
32
+ if (typeof v === "number" && Number.isFinite(v))
33
+ return String(v);
34
+ return str(v);
35
+ }
36
+ function countOf(v) {
37
+ return Array.isArray(v) ? v.length : undefined;
38
+ }
39
+ /**
40
+ * Normalize one webhook delivery into a {@link GitHubInstallationEvent}, or null when it isn't an
41
+ * installation delivery we act on. PURE — no I/O, so every branch is unit-tested.
42
+ *
43
+ * FAIL-CLOSED on an unusable payload: an unknown action, a missing `installation.id`, or a missing
44
+ * `installation.account.login` all return null (nothing learned, nothing revoked) rather than
45
+ * writing a half-formed row. An installation with no resolvable owner is unroutable anyway.
46
+ */
47
+ export function extractGitHubInstallationEvent(eventType, payload) {
48
+ if (eventType !== "installation" && eventType !== "installation_repositories")
49
+ return null;
50
+ const action = str(payload["action"]);
51
+ if (!action)
52
+ return null;
53
+ const effect = LEARN_ACTIONS.has(action) ? "learn" : REVOKE_ACTIONS.has(action) ? "revoke" : undefined;
54
+ if (!effect)
55
+ return null;
56
+ const installation = payload["installation"];
57
+ if (typeof installation !== "object" || installation === null)
58
+ return null;
59
+ const inst = installation;
60
+ const installationId = idOf(inst["id"]);
61
+ if (!installationId)
62
+ return null;
63
+ const account = typeof inst["account"] === "object" && inst["account"] !== null ? inst["account"] : undefined;
64
+ const owner = str(account?.["login"]);
65
+ if (!owner)
66
+ return null;
67
+ // `repository_selection` sits on the installation for `installation`, and is re-sent at the TOP
68
+ // level of an `installation_repositories` delivery (that event's whole point is the change) —
69
+ // prefer the top-level value so a scope change is reflected, not the pre-change installation copy.
70
+ const repositorySelection = str(payload["repository_selection"]) ?? str(inst["repository_selection"]);
71
+ const repositoryCount = countOf(payload["repositories"]) ?? countOf(payload["repositories_added"]) ?? countOf(payload["repositories_removed"]);
72
+ return {
73
+ effect,
74
+ action,
75
+ owner,
76
+ installationId,
77
+ ...(str(account?.["type"]) ? { accountType: str(account?.["type"]) } : {}),
78
+ ...(repositorySelection ? { repositorySelection } : {}),
79
+ ...(repositoryCount !== undefined ? { repositoryCount } : {}),
80
+ };
81
+ }
82
+ /**
83
+ * Apply one normalized delivery: persist-or-clear the owner→installation entry, then audit it.
84
+ *
85
+ * ORDER IS DELIBERATE — the durable write happens FIRST and its failure propagates (the route's
86
+ * catch logs it), so we never audit an install we didn't actually record. The audit row is written
87
+ * after, best-effort at the appendAudit layer.
88
+ */
89
+ export async function handleGitHubInstallationDelivery(ev, deps) {
90
+ const at = deps.now ? deps.now() : new Date().toISOString();
91
+ const detail = {
92
+ action: ev.action,
93
+ installationId: ev.installationId,
94
+ ...(ev.accountType ? { accountType: ev.accountType } : {}),
95
+ ...(ev.repositorySelection ? { repositorySelection: ev.repositorySelection } : {}),
96
+ ...(ev.repositoryCount !== undefined ? { repositoryCount: ev.repositoryCount } : {}),
97
+ };
98
+ if (ev.effect === "revoke") {
99
+ const removed = await deps.forget(ev.owner);
100
+ deps.log?.(`installation ${ev.action} for ${ev.owner} — ${removed ? "discovered entry cleared" : "no discovered entry to clear"}`);
101
+ await deps.audit("surface.app_uninstalled", { ...detail, removed }, ev.owner);
102
+ return;
103
+ }
104
+ await deps.record({
105
+ owner: ev.owner,
106
+ installationId: ev.installationId,
107
+ ...(ev.accountType ? { accountType: ev.accountType } : {}),
108
+ ...(ev.repositorySelection ? { repositorySelection: ev.repositorySelection } : {}),
109
+ updatedAt: at,
110
+ });
111
+ deps.log?.(`installation ${ev.action} for ${ev.owner} → installation ${ev.installationId} registered (no env edit needed)`);
112
+ await deps.audit("surface.app_installed", detail, ev.owner);
113
+ }
114
+ //# sourceMappingURL=github-installations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github-installations.js","sourceRoot":"","sources":["../../../agent/channels/github-installations.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { SurfaceMessageOps } from "@open-everywhere/core/agent/adapter-api.js";
2
+ export declare const githubMessageOps: SurfaceMessageOps;
3
+ //# sourceMappingURL=github-message-ops.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github-message-ops.d.ts","sourceRoot":"","sources":["../../../agent/channels/github-message-ops.ts"],"names":[],"mappings":"AAkCA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,4CAA4C,CAAC;AAYpF,eAAO,MAAM,gBAAgB,EAAE,iBAe9B,CAAC"}
@@ -0,0 +1,58 @@
1
+ // github-message-ops — GitHub's SurfaceMessageOps (OPE-899): the ancillary message ops core
2
+ // features dispatch through channels/surface-message-ops.ts. Before this file, GitHub registered
3
+ // none, so the stop acknowledgement (turn-anchor.ts's `postSurfaceText`) resolved nothing on a
4
+ // GitHub turn — the stop control worked (github-stop.ts, the bare `stop` comment), it was just
5
+ // invisible: a person who pressed it saw the agent go quiet and read nothing.
6
+ //
7
+ // - postText → the SAME issue/PR-timeline comment POST the reply spine uses
8
+ // (github-post-arm.ts's `liveGitHubPost`, exported for this file — one mint/fetch
9
+ // implementation, never a second copy of it).
10
+ // - editMessage → the SAME comment PATCH the `edit_reply` verb uses (`liveGitHubEditComment`).
11
+ // - NO `postFiles` (OPE-899's registry ruling): GitHub has no API for attaching a file to a
12
+ // comment — web upload only. The optional tier exists exactly for this shape.
13
+ // - NO `setReaction`: GitHub's reaction set is the fixed eight (+1, -1, laugh, confused, heart,
14
+ // hooray, rocket, eyes) — there is no raised hand. The bare `stop` comment (github-stop.ts) IS
15
+ // GitHub's stop affordance; there is no ✋ glyph to place beside it, so this module offers no
16
+ // `setReaction` rather than faking one against the wrong emoji.
17
+ //
18
+ // `channelId` IS THE CONVERSATION KEY `owner/repo#number`, not a GitHub-specific pair invented
19
+ // here. `turn-anchor.ts`'s `turnAnchorAddress` reads it off `binding.anchorChannelId`, which
20
+ // `bindGitHub` (github-identity.ts) mints through `githubConversationKey` — the SAME key
21
+ // github-read.ts's `parseGithubContainer` already parses for the read adapter's container id. This
22
+ // file reuses that parser rather than writing a second one: two derivations of the same address
23
+ // would be exactly the "silently miss" trap github-identity.ts's own header warns about.
24
+ //
25
+ // `messageId` on `editMessage` is the COMMENT id `postText`/`liveGitHubPost` returned — GitHub's
26
+ // comment ids are unique across the whole repo, so `editMessage` needs only the owner/repo half of
27
+ // the parsed container, never the issue number.
28
+ //
29
+ // NO THREADING ON `postText`. GitHub's issue/PR-timeline comment stream is flat — there is no
30
+ // "reply to this specific comment" primitive on the endpoint `liveGitHubPost` posts through (that
31
+ // exists only for PR review comments, a different family this registry does not address) — so
32
+ // `opts?.replyToMessageId` is accepted (the interface's contract) and ignored, exactly like a
33
+ // top-level Slack post with no thread.
34
+ import { liveGitHubEditComment, liveGitHubPost } from "./github-post-arm.js";
35
+ import { parseGithubContainer } from "./github-read.js";
36
+ /** Parse the `owner/repo#number` container id, or throw the same honest, naming refusal every
37
+ * other surface-message-ops call gives on a malformed address — never a guessed target. */
38
+ function requireContainer(channelId) {
39
+ const target = parseGithubContainer(channelId);
40
+ if (!target)
41
+ throw new Error(`GitHub surface message ops: "${channelId}" is not a conversation id (expected "owner/repo#number").`);
42
+ return target;
43
+ }
44
+ export const githubMessageOps = {
45
+ platform: "github",
46
+ /** Post to the issue/PR-timeline comment stream. `opts?.replyToMessageId` is accepted and
47
+ * ignored — see the header's "NO THREADING" note. */
48
+ async postText(channelId, text) {
49
+ const { owner, repo, number } = requireContainer(channelId);
50
+ return liveGitHubPost({ platform: "github", owner, repo, issueNumber: number }, text);
51
+ },
52
+ /** PATCH the comment's body in place — the same edit `edit_reply` performs on the reply spine. */
53
+ async editMessage(channelId, messageId, text) {
54
+ const { repoFull } = requireContainer(channelId);
55
+ await liveGitHubEditComment(repoFull, messageId, text);
56
+ },
57
+ };
58
+ //# sourceMappingURL=github-message-ops.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github-message-ops.js","sourceRoot":"","sources":["../../../agent/channels/github-message-ops.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,iGAAiG;AACjG,+FAA+F;AAC/F,+FAA+F;AAC/F,8EAA8E;AAC9E,EAAE;AACF,iFAAiF;AACjF,oGAAoG;AACpG,gEAAgE;AAChE,iGAAiG;AACjG,8FAA8F;AAC9F,kFAAkF;AAClF,kGAAkG;AAClG,mGAAmG;AACnG,iGAAiG;AACjG,oEAAoE;AACpE,EAAE;AACF,+FAA+F;AAC/F,6FAA6F;AAC7F,yFAAyF;AACzF,mGAAmG;AACnG,gGAAgG;AAChG,yFAAyF;AACzF,EAAE;AACF,iGAAiG;AACjG,mGAAmG;AACnG,gDAAgD;AAChD,EAAE;AACF,8FAA8F;AAC9F,kGAAkG;AAClG,8FAA8F;AAC9F,8FAA8F;AAC9F,uCAAuC;AAGvC,OAAO,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC7E,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAExD;4FAC4F;AAC5F,SAAS,gBAAgB,CAAC,SAAiB;IACzC,MAAM,MAAM,GAAG,oBAAoB,CAAC,SAAS,CAAC,CAAC;IAC/C,IAAI,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,SAAS,4DAA4D,CAAC,CAAC;IACpI,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,CAAC,MAAM,gBAAgB,GAAsB;IACjD,QAAQ,EAAE,QAAQ;IAElB;0DACsD;IACtD,KAAK,CAAC,QAAQ,CAAC,SAAS,EAAE,IAAI;QAC5B,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,gBAAgB,CAAC,SAAS,CAAC,CAAC;QAC5D,OAAO,cAAc,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,IAAI,CAAC,CAAC;IACxF,CAAC;IAED,kGAAkG;IAClG,KAAK,CAAC,WAAW,CAAC,SAAS,EAAE,SAAS,EAAE,IAAI;QAC1C,MAAM,EAAE,QAAQ,EAAE,GAAG,gBAAgB,CAAC,SAAS,CAAC,CAAC;QACjD,MAAM,qBAAqB,CAAC,QAAQ,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;IACzD,CAAC;CACF,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { UnifiedWakeOrigin, WakeOriginCodec } from "@open-everywhere/core/agent/adapter-api.js";
2
+ type GitHubOrigin = Extract<UnifiedWakeOrigin, {
3
+ platform: "github";
4
+ }>;
5
+ export declare const githubWakeOriginCodec: WakeOriginCodec<GitHubOrigin>;
6
+ export {};
7
+ //# sourceMappingURL=github-origin-codec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github-origin-codec.d.ts","sourceRoot":"","sources":["../../../agent/channels/github-origin-codec.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,4CAA4C,CAAC;AAErG,KAAK,YAAY,GAAG,OAAO,CAAC,iBAAiB,EAAE;IAAE,QAAQ,EAAE,QAAQ,CAAA;CAAE,CAAC,CAAC;AAMvE,eAAO,MAAM,qBAAqB,EAAE,eAAe,CAAC,YAAY,CAqB/D,CAAC"}
@@ -0,0 +1,36 @@
1
+ // The GitHub UnifiedWakeOrigin codec — MOVED VERBATIM out of core's
2
+ // channels/wake-origin-codecs.ts (OPE-114 stage 4). STORED-DATA COMPATIBILITY IS THE CONTRACT:
3
+ // a GitHub origin is JSON-serialised into the durable operational store and fail-closed
4
+ // re-validated on read, so every behaviour here is frozen by core's
5
+ // scripts/wake-origin-fixtures-test.ts (run from the app root with this bundle's boot wiring —
6
+ // scripts/wake-origin-fixtures-boot.ts). Validator tolerances (typeof-only optional checks, extra
7
+ // unknown fields) are LOAD-BEARING for rows already on disk — never tighten them here.
8
+ //
9
+ // The `UnifiedWakeOrigin` TYPE union (including its github arm) stays in core — it is the wire
10
+ // schema for stored rows; only the codec BEHAVIOUR lives in the bundle, registered by
11
+ // registerGithubProvider() (../register.ts).
12
+ /** COLON-FREE by construction (a task windowId is `[^:]+` — core channels/window-key.ts): any colon
13
+ * in a raw surface id is folded to `_`, deterministically, so spawn + ingress agree on identity. */
14
+ const noColon = (s) => s.replace(/:/g, "_");
15
+ export const githubWakeOriginCodec = {
16
+ validate: (o) => typeof o.owner === "string" &&
17
+ o.owner.length > 0 &&
18
+ typeof o.repo === "string" &&
19
+ o.repo.length > 0 &&
20
+ typeof o.issueNumber === "number" &&
21
+ Number.isFinite(o.issueNumber) &&
22
+ o.issueNumber > 0 &&
23
+ (o.installationId === undefined || typeof o.installationId === "number"),
24
+ threadRef: (origin) => ({ channelKey: noColon(`github/${origin.owner}/${origin.repo}`), threadKey: String(origin.issueNumber) }),
25
+ // The container and the thread coincide (an issue IS its own thread) ⇒ always thread-scoped.
26
+ isThreadScoped: () => true,
27
+ containerId: (origin) => `${origin.owner}/${origin.repo}`,
28
+ // NO `placementRef` AND NO `carryPlacement`, AND THAT ABSENCE IS THE DECLARATION (OPE-1826). A
29
+ // GitHub reply is a comment appended to the issue timeline, and the issue IS the thread key above —
30
+ // there is no "where inside it" left to name, so two origins that pass `sameUnifiedConversation`
31
+ // cannot disagree about placement. `installationId` is not a counter-example: it selects the
32
+ // credential the post mints under, never where the post lands. The position is recorded, and held
33
+ // to account against the live registry, by the `origin-placement-carry` row in core's
34
+ // identity/surface-parity.ts.
35
+ };
36
+ //# sourceMappingURL=github-origin-codec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github-origin-codec.js","sourceRoot":"","sources":["../../../agent/channels/github-origin-codec.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,+FAA+F;AAC/F,wFAAwF;AACxF,oEAAoE;AACpE,+FAA+F;AAC/F,kGAAkG;AAClG,uFAAuF;AACvF,EAAE;AACF,+FAA+F;AAC/F,sFAAsF;AACtF,6CAA6C;AAM7C;qGACqG;AACrG,MAAM,OAAO,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;AAE5D,MAAM,CAAC,MAAM,qBAAqB,GAAkC;IAClE,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CACd,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ;QAC3B,CAAC,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC;QAClB,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ;QAC1B,CAAC,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC;QACjB,OAAO,CAAC,CAAC,WAAW,KAAK,QAAQ;QACjC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC;QAC9B,CAAC,CAAC,WAAW,GAAG,CAAC;QACjB,CAAC,CAAC,CAAC,cAAc,KAAK,SAAS,IAAI,OAAO,CAAC,CAAC,cAAc,KAAK,QAAQ,CAAC;IAC1E,SAAS,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC;IAChI,6FAA6F;IAC7F,cAAc,EAAE,GAAG,EAAE,CAAC,IAAI;IAC1B,WAAW,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,IAAI,EAAE;IACzD,+FAA+F;IAC/F,oGAAoG;IACpG,iGAAiG;IACjG,6FAA6F;IAC7F,kGAAkG;IAClG,sFAAsF;IACtF,8BAA8B;CAC/B,CAAC"}