@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,317 @@
1
+ // THE GITHUB AMBIENT ENGAGEMENT SEAM (chat-surface contract §2.5) — the GitHub wiring over core's
2
+ // pure engagement policy (`packages/core/agent/channels/engagement.ts`). It gives GitHub the middle
3
+ // posture Slack, Discord and Teams already have: an @mention marks the issue or pull-request thread
4
+ // ENGAGED, a later PLAIN comment in that thread wakes the agent, and `stand_down` stores the stop.
5
+ //
6
+ // THE ANCHOR is `githubConversationKey(owner, repo, number)` — `owner/repo#N`, the same string the
7
+ // mapper already publishes as `anchorChannelId` for the stop registry. One issue or pull request IS
8
+ // the GitHub conversation, so that is the grain a person can address a stop to, and it is the grain
9
+ // engagement is kept per. The surface partition stays the REPO (`github/<owner>/<repo>`), so the
10
+ // anchor distinguishes the containers inside it.
11
+ //
12
+ // 🔴 WHICH LEG OWNS AN ENGAGED FOLLOW-UP IS THE SAME QUESTION AS WHICH LEG OWNS A MENTION, AND IT
13
+ // GETS THE SAME ANSWER. GitHub runs two legs over one webhook: the NATIVE mention session
14
+ // (`githubOnComment`) and the ambient supervisor (`ambientDelivery`). Today a mention goes to
15
+ // exactly one of them — the native leg, unless the repo is single-window and operator-vouched, where
16
+ // the native session is suppressed and the unified window answers instead. This seam follows that
17
+ // split rather than inventing a second one: on a repo where the native leg owns mentions it also
18
+ // owns engaged follow-ups, and the ambient leg drops them; on a single-window vouched repo the
19
+ // ambient leg owns them, because the native handler stands down for EVERY comment there
20
+ // (`shouldSuppressNativeMention` asks `singleWindowMentionPolicy` with `isMention` pinned true).
21
+ // Two legs answering one comment is the failure this ordering exists to prevent.
22
+ //
23
+ // 🔴 "THE WINDOW ALREADY ANSWERS EVERY COMMENT" WAS THE CLAIM THAT BROKE IT (OPE-1740/OPE-1745). It
24
+ // held only while a seat REQUIRED `ambientSupervisor`. Seats are universal since OPE-1695, so a
25
+ // seated, vouched repo on an ADDRESSED-ONLY domain answers addressed comments and nothing else — and
26
+ // an engaged follow-up there reached neither leg: the native handler stood down, and the ambient
27
+ // leg's chattiness gate refused the comment as `domain-refused` before this seam was read. Deploy
28
+ // #201 measured exactly that on `oe-e2e-addressed-github`. The ambient leg now reads the seam FIRST
29
+ // and feeds its verdict to the gate (`githubAmbientLegAdmission`, github.ts): enrolment is an
30
+ // address, and the domain opt-in governs UNINVITED talk only.
31
+ //
32
+ // 🔴 HOW FAR THE NON-MENTION ARM REACHES — PRIVATE REPOS, AND NEVER AN INLINE REVIEW COMMENT.
33
+ // `githubEngagedFollowUpReach` is the one predicate for it, and BOTH legs ask it. A plain follow-up
34
+ // wakes a native turn (a sandbox checkout, a judged spend), so it is refused on a public or
35
+ // unknown-privacy repo, and refused inside a review thread because the anchor is the pull request
36
+ // rather than the thread. An @mention is unaffected by either floor.
37
+ //
38
+ // 🔴 IT IS A STRICTER FLOOR THAN THE AMBIENT LEG'S, NOT THE SAME ONE. `classifyGitHubAmbientComment`
39
+ // step 5 admits a VOUCHED public repo (`repoPrivate !== true && !vouched ⇒ drop`); this one is
40
+ // vouch-blind and refuses every public repo. Do not "align" the two by adding the vouch here — that
41
+ // re-opens an uninvited native turn on a public repo, which is the thing this floor exists to close.
42
+ //
43
+ // 🔴 WHAT NO TEST REACHES, STATED RATHER THAN IMPLIED, AND ONE OF THE THREE IS COVERED BY NOTHING.
44
+ // `github.ts`'s `ambientDelivery` is module-private and only runs behind the route's `waitUntil`, so
45
+ // no in-repo test drives the LEG itself. Obligation by obligation:
46
+ // • THE CHATTINESS GATE reading the enrolment fact IS driven offline, over the real store and a
47
+ // real declared addressed-only domain, because `githubAmbientLegAdmission` is exported for it
48
+ // (`scripts/engaged-seam-wiring-test.ts`, the addressed-only block) — reach floors included.
49
+ // That the leg CALLS it is `J-ENGAGED-github`'s to prove: step (b)'s follow-up reaches a turn on
50
+ // that journey's repo only through this gate.
51
+ // • THE STOOD-DOWN SUPPRESSION is covered by NOTHING, and the gate above it is why. On an
52
+ // ADDRESSED-ONLY repo a stopped row resolves `hold`, never `deliver`, so the gate refuses the
53
+ // comment first and this branch never runs — `J-ENGAGED-github`'s stand-down step passes on the
54
+ // domain flag, not on the stop. The branch is reachable only on an ambient-ON repo, and no
55
+ // journey drives a `stand_down` on one. Deleting it would red nothing.
56
+ // • THE `!shouldSuppressNativeMention(singleWindow, vouched)` DROP GUARD is covered on its
57
+ // NON-FIRING side only: `J-ENGAGED-github`'s repo stands the native leg down, so a guard that
58
+ // wrongly dropped there reds the journey. Its FIRING side — a repo the native leg owns, where
59
+ // the guard is what stops two legs answering one comment — is covered by nothing.
60
+ // `scripts/github-external-policy-room-test.ts` records the same limit for its own leg.
61
+ // What IS pinned: this module's decision table
62
+ // (`packages/providers/github/scripts/github-engagement-test.ts`), the NATIVE leg's whole loop over
63
+ // the real store — mark, deliver, stop, the way back in, and the redelivery guard
64
+ // (`scripts/engaged-seam-wiring-test.ts`), and the anchor round trip through `stand_down`
65
+ // (`scripts/stand-down-anchor-test.ts` LEG 17). On the AMBIENT leg, the `J-ENGAGED-github` e2e
66
+ // journey covers exactly what the three bullets above say it covers, and no more.
67
+ import { getOperationalStore, getThreadEngagement, markThreadEngaged, resolvePartition, shouldDeliverReply, } from "@open-everywhere/core/agent/adapter-api.js";
68
+ import { buildGitHubAuth, githubConversationKey } from "./github-identity.js";
69
+ /**
70
+ * The engagement-registry anchor for one GitHub container, or `undefined` when the delivery names
71
+ * no container number.
72
+ *
73
+ * 🔴 IT IS THE ONE PREDICATE THE MAPPER AND THE SEAM SHARE. `bindGitHub` publishes exactly this as
74
+ * `engagementKey`, so `stand_down`'s mark and this seam's read address the same row. Two spellings
75
+ * of "which container this is" is how a stop comes to mark a row no gate reads.
76
+ */
77
+ export function githubEngagementAnchor(owner, repo, num) {
78
+ return githubConversationKey(owner, repo, num);
79
+ }
80
+ /** The partition one container's engagement row lives in — the REPO grain
81
+ * (`github/<owner>/<repo>`), which is what `resolvePartition` gives a bound GitHub session and
82
+ * therefore what `stand_down` writes under. It is built from THIS delivery's own facts, through the
83
+ * same `buildGitHubAuth` the wake dispatches, so the seam can never key a row the turn would not.
84
+ * Null on an unresolvable identity: the caller then keeps today's path, the fail-open posture every
85
+ * carriage leg on this surface takes. */
86
+ export function githubSeamPartition(f) {
87
+ try {
88
+ const auth = buildGitHubAuth({
89
+ owner: f.owner,
90
+ repo: f.repo,
91
+ isPrivate: f.isPrivate,
92
+ actorLogin: f.actorLogin,
93
+ });
94
+ return resolvePartition({ channel: { kind: "github" }, session: { auth: { current: auth } } });
95
+ }
96
+ catch {
97
+ return null;
98
+ }
99
+ }
100
+ /**
101
+ * May the NON-MENTION arm of the seam reach this delivery at all? Read BEFORE any registry read, by
102
+ * both legs, so the two never disagree about which comments the native leg owns.
103
+ *
104
+ * TWO FLOORS. Each refuses on an unknown rather than admitting, and each is about REACH — whether
105
+ * this kind of delivery may wake at all — never about whether the container is engaged.
106
+ *
107
+ * • 🔴 THE REPO MUST BE POSITIVELY PRIVATE. An engaged container's plain comment wakes a FULL
108
+ * native turn: a sandbox checkout and a judged spend. On a public repo that lets ANY GitHub
109
+ * account buy one, by writing in an issue somebody once @mentioned the app in — the same
110
+ * drive-by spend and prompt-injection surface `classifyGitHubAmbientComment` step 5 refuses for,
111
+ * on the louder of the two legs. 🔴 THIS FLOOR IS STRICTER THAN THAT ONE, deliberately: step 5
112
+ * admits a VOUCHED public repo, and this one refuses every public repo. A vouch widens the
113
+ * ambient supervisor's judged reply; widening an uninvited NATIVE turn to a public repo is a
114
+ * bigger step that needs its own ruling.
115
+ * • 🔴 A REVIEW THREAD IS NOT A CONTAINER OF ITS OWN. {@link githubEngagementAnchor} collapses to
116
+ * `owner/repo#N`, so every INLINE review comment on a pull request reads the row that one
117
+ * @mention anywhere on that pull request engaged — and one review leaves dozens of them.
118
+ *
119
+ * AN @MENTION IS UNAFFECTED BY BOTH, on a public repo and inside a review thread alike: it is
120
+ * answered by the caller's own mention gate, which this seam never narrows.
121
+ */
122
+ export function githubEngagedFollowUpReach(f) {
123
+ if (f.isPrivate !== true) {
124
+ return { reachable: false, reason: "the repo is public or of unknown privacy — an engaged follow-up wakes a native turn, which is private-repo only" };
125
+ }
126
+ if (f.conversationKind === "review_thread") {
127
+ return { reachable: false, reason: "an inline review comment shares its pull request's anchor — a review thread is not a container of its own" };
128
+ }
129
+ return { reachable: true, reason: "a private repo, and not an inline review comment" };
130
+ }
131
+ /**
132
+ * Resolve the seam for one comment — READ-ONLY. The mark is a separate call
133
+ * ({@link markGitHubEngaged}) so exactly one caller writes the row: two writers is how one comment
134
+ * comes to engage a container twice, once on each leg.
135
+ *
136
+ * ORDERING. A mention is settled before any read: it is always answered by the caller's own path, so
137
+ * its verdict costs no store round-trip. Only a plain comment pays for a read, and it pays for
138
+ * exactly one — the ROW, not a boolean, because the row carries both facts the decision needs (is
139
+ * this engaged, and was the agent asked to stand down here) and two reads could disagree.
140
+ *
141
+ * THE DECISION ITSELF IS CORE'S. `shouldDeliverReply` owns the disqualifier order and the reasons;
142
+ * this function only translates GitHub facts into its input. Nothing here re-states the policy.
143
+ *
144
+ * FAIL-CLOSED on a store error: `getThreadEngagement` reads a failure as `null`, so an unreadable
145
+ * registry holds rather than delivers — the agent does not barge into a conversation it cannot prove
146
+ * it was invited to.
147
+ */
148
+ export async function resolveGitHubEngagement(f, deps = {}) {
149
+ const anchor = githubEngagementAnchor(f.owner, f.repo, f.number);
150
+ if (!anchor)
151
+ return { kind: "unanchored", reason: "the delivery names no issue or pull-request number — no row to address" };
152
+ const partition = githubSeamPartition(f);
153
+ if (!partition)
154
+ return { kind: "unanchored", reason: "the delivery has no resolvable partition — no row to address" };
155
+ if (f.isMention)
156
+ return { kind: "engage", anchor };
157
+ const read = deps.getEngagement ?? ((p, key) => getThreadEngagement(p, key));
158
+ const row = await read(partition, anchor);
159
+ const decision = shouldDeliverReply({
160
+ isEngaged: row !== null,
161
+ stopped: row?.stopped ?? false,
162
+ authorIsBot: f.authorIsBot,
163
+ isAppMention: false, // settled above — a mention never reaches this branch
164
+ inThread: true, // an anchored GitHub container IS the thread engagement is kept per
165
+ });
166
+ if (decision.deliver)
167
+ return { kind: "deliver", anchor, reason: decision.reason };
168
+ return { kind: "hold", anchor, reason: decision.reason, stopped: row?.stopped === true };
169
+ }
170
+ /**
171
+ * Mark one container ENGAGED on an @mention, ONCE PER COMMENT.
172
+ *
173
+ * `participating: true` is the re-invitation: it promotes the row and CLEARS a previous stand-down,
174
+ * which is what "an @mention brings the agent back" means on every surface that wires this seam.
175
+ *
176
+ * 🔴 THE MARK IS DEDUPED, AND THE STOP IS WHY. Clearing the flag is right for a person's second
177
+ * @mention and WRONG for GitHub redelivering the first one — a redelivered webhook would quietly
178
+ * lift a stand-down nobody asked to lift. `commentId` is the comment's own id, so the guard keys on
179
+ * the thing that must happen once. It is the DURABLE layer only, unlike Teams' front: this route
180
+ * holds no in-process dedupe object, and a serverless invocation is a fresh process, so an
181
+ * in-memory layer would catch almost nothing here. FAIL-OPEN on a store error, the same posture
182
+ * every dedupe in this repository takes — a dedupe prevents a double write, not a security hole.
183
+ *
184
+ * Best-effort throughout — `markThreadEngaged` swallows a store failure, because engagement
185
+ * bookkeeping must never fail a turn. Returns the anchor it marked, or null when it marked nothing.
186
+ */
187
+ export async function markGitHubEngaged(f, commentId, deps = {}) {
188
+ const anchor = githubEngagementAnchor(f.owner, f.repo, f.number);
189
+ if (!anchor)
190
+ return null;
191
+ const partition = githubSeamPartition(f);
192
+ if (!partition)
193
+ return null;
194
+ const now = deps.now ?? (() => new Date().toISOString());
195
+ const atIso = now();
196
+ if (commentId !== undefined) {
197
+ const seen = deps.markEventSeen ?? liveMarkEngageSeen;
198
+ if (!(await seen(partition.org, `github-engage:${anchor}:${String(commentId)}`, atIso)))
199
+ return null;
200
+ }
201
+ const mark = deps.markEngaged ?? ((p, key, at) => markThreadEngaged(p, key, at, { participating: true }));
202
+ await mark(partition, anchor, atIso);
203
+ return anchor;
204
+ }
205
+ /** The durable half of the mark dedupe. FAIL-OPEN: an unreachable store answers "not seen", so a
206
+ * store outage costs a redundant mark rather than a lost engagement. */
207
+ const ENGAGE_DEDUPE_TTL_MS = 10 * 60_000;
208
+ let warnedEngageDedupe = false;
209
+ async function liveMarkEngageSeen(org, key, atIso) {
210
+ try {
211
+ return await getOperationalStore().markEventSeen(org, key, atIso, ENGAGE_DEDUPE_TTL_MS);
212
+ }
213
+ catch (err) {
214
+ if (!warnedEngageDedupe) {
215
+ warnedEngageDedupe = true;
216
+ console.warn(`[open-everywhere:github-engagement] engage dedupe unavailable — a redelivered @mention may re-mark: ${String(err)}`);
217
+ }
218
+ return true;
219
+ }
220
+ }
221
+ /**
222
+ * Is this comment MACHINE-AUTHORED — a GitHub App, a bot account, or this deployment's own reply?
223
+ *
224
+ * 🔴 ONE PREDICATE, AND IT MUST FAIL CLOSED ON AN UNRESOLVED SLUG. `githubBotMentioned` needs the
225
+ * app slug to recognise a mention at all, so before this seam a deployment with no `GITHUB_APP_SLUG`
226
+ * simply never woke natively. A plain comment now reaches the seam, and without the slug the app
227
+ * cannot tell its OWN reply from a person's — so "unknown" answers `true` and nothing is delivered,
228
+ * the same posture `classifyGitHubAmbientComment` takes with its own no-slug drop.
229
+ */
230
+ export function githubMachineAuthored(authorLogin, authorType, botSlug) {
231
+ const slug = botSlug?.trim();
232
+ if (!slug)
233
+ return true; // cannot identify self ⇒ cannot prove a human wrote this
234
+ if (authorType === "Bot")
235
+ return true;
236
+ return authorLogin !== undefined && authorLogin.toLowerCase() === `${slug}[bot]`.toLowerCase();
237
+ }
238
+ /**
239
+ * The NATIVE leg's one-line admission (`github.ts`'s mention gate): may this plain, non-mention
240
+ * comment run a turn?
241
+ *
242
+ * It exists because the native channel is where a GitHub turn is minted — the wake is the webhook
243
+ * itself, so admitting the comment through the mention gate IS the wake. The ambient leg owns the
244
+ * complementary halves above it (the STOP suppression, and dropping a comment this leg now owns), so
245
+ * this call only ever answers the delivery question, and it never writes.
246
+ *
247
+ * 🔴 THE REACH FLOORS RUN FIRST, above the registry read: a public repo and an inline review comment
248
+ * are refused whatever the row says ({@link githubEngagedFollowUpReach}). Putting them here rather
249
+ * than in {@link resolveGitHubEngagement} is deliberate — the ambient leg reads that function for the
250
+ * STOP, and a stood-down container must stay suppressed on a public repo too.
251
+ */
252
+ export async function githubEngagedAdmits(f, deps = {}) {
253
+ if (f.isMention)
254
+ return true; // the caller's own mention gate already admitted it
255
+ if (!githubEngagedFollowUpReach(f).reachable)
256
+ return false;
257
+ const verdict = await resolveGitHubEngagement(f, deps);
258
+ return verdict.kind === "deliver";
259
+ }
260
+ /**
261
+ * WHAT WOKE ONE AMBIENT-LEG DELIVERY, for the `github.dispatched` audit row (OPE-1752).
262
+ *
263
+ * 🔴 THE CLASSIFIER'S OWN WORD CANNOT ANSWER THIS, AND WRITING IT WAS THE DEFECT.
264
+ * `classifyGitHubAmbientComment` answers a narrower question — did this comment ADDRESS the agent —
265
+ * and an engaged follow-up did not, so it stays `channel-watch`. That is the right answer to its own
266
+ * question, and `channel-watch` is what makes the turn a JUDGED one. But `channel-watch` ALSO names
267
+ * unowned chatter in an un-enrolled issue, and the audit row is the one place those two must not
268
+ * read alike. This is the same promotion OPE-1510 made on Notion (`notionWakeAddressing`) and
269
+ * OPE-1578 made on Linear (`linearWakeAddressing`) — one concept, one spelling, across three
270
+ * surfaces.
271
+ *
272
+ * 🔴 THE MAPPING IS THE ROW'S ALONE, and nothing else has to be re-derived from it. The classifier's
273
+ * value reaches five places in `github.ts`, and TWO of them are behavioural: the `drop` return, the
274
+ * `githubAmbientLegAdmission` gate input, the seam facts' `isMention`, `directMention` — which is
275
+ * what the wake envelope and `singleWindowMentionPolicy` are built from — and this row. The gate
276
+ * input and `directMention` are the behavioural two, and NEITHER reads the PROMOTED value: both take
277
+ * the classifier's word directly, above this mapping. `github-supervisor.ts` derives the wake
278
+ * envelope's own `addressing`
279
+ * from `directMention` (`directMention ? "direct-mention" : "channel-watch"`), and the
280
+ * unified-window wake takes `singleWindowMentionPolicy(singleWindow, directMention, vouched)`.
281
+ * Neither reads this value. So the turn is judged the same, and the SILENCE BAR is what it always
282
+ * was, whatever this function returns.
283
+ *
284
+ * 🔴 A ROW THAT CANNOT TELL THEM APART REDS `J-ENGAGED-github` STEP (b) AGAINST A HEALTHY
285
+ * DEPLOYMENT. `gateEngagedWake` (packages/core/scripts/e2e/scenarios.ts) skips on an EMPTY dispatch
286
+ * read only, so a non-empty read carrying `channel-watch` is a hard gate failure, not an ignored
287
+ * value. Measured on deploy #203: the follow-up woke, dispatched and was ANSWERED (`reply.post`,
288
+ * 2026-09-12T20:27:25Z), and only the label disagreed. What the promotion DOES oblige is every
289
+ * reader that filtered the ambient leg by the literal `"channel-watch"` — `AMBIENT_LEG_ADDRESSINGS`
290
+ * (packages/core/scripts/e2e/engaged-rows.ts) is the pair such a reader takes.
291
+ *
292
+ * 🔴 IT REPORTS THE ENGAGED DOOR, AND THAT DOOR CARRIES A REACH FLOOR LINEAR'S DOES NOT. Linear's
293
+ * input is the bare seam verdict; GitHub's is `admission.engagedFollowUp`, which is
294
+ * `seam.kind === "deliver" && githubEngagedFollowUpReach(f).reachable`. So the promotion needs BOTH
295
+ * an enrolled container and a reachable one, and {@link githubEngagedFollowUpReach} refuses a public
296
+ * or unknown-privacy repo and an inline review comment. An engaged issue on a VOUCHED PUBLIC repo
297
+ * with ambient ON therefore still writes `channel-watch`: the classifier keeps that comment, but the
298
+ * engaged door is shut and the native leg would not have woken a turn for it either. That is the
299
+ * right answer — the row reports the door the delivery actually came through — and it is a narrower
300
+ * claim than Linear's, so do not read the two mappings as identical in reach.
301
+ *
302
+ * 🔴 IT REPORTS THE REGISTRY, NOT THE AMBIENT OPT-IN. `githubAmbientLegAdmission` resolves the seam
303
+ * for every kept comment, ABOVE the `githubAmbientEnabled` gate and independently of it, so on an
304
+ * ambient-ENABLED private repo an engaged issue's plain comment also reads `thread-follow` — the
305
+ * opt-in would have admitted it either way. The value therefore separates an engaged wake from an
306
+ * ambient one only where the domain has ambient OFF, which is the addressed-only posture the
307
+ * journeys drive.
308
+ *
309
+ * `engagedFollowUp` is the caller's own admission verdict, and it is the only door a plain comment
310
+ * has on a repo that never opted in. The `channel-watch` guard is belt-and-braces:
311
+ * `drop` returns before the row is written and can never reach here, and `direct` is an address
312
+ * rather than a follow-up.
313
+ */
314
+ export function githubWakeAddressing(addressing, engagedFollowUp) {
315
+ return addressing === "channel-watch" && engagedFollowUp === true ? "thread-follow" : addressing;
316
+ }
317
+ //# sourceMappingURL=github-engagement.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github-engagement.js","sourceRoot":"","sources":["../../../agent/channels/github-engagement.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,164 @@
1
+ import type { BindableContext, IdentityMapper, IngestedChatMessage } from "@open-everywhere/core/agent/adapter-api.js";
2
+ /** Structural match for eve's SessionAuthContext (attributes + principal). */
3
+ export interface OpenEverywhereGitHubAuth {
4
+ readonly authenticator: string;
5
+ readonly principalId: string;
6
+ readonly principalType: string;
7
+ readonly attributes: Record<string, string>;
8
+ }
9
+ export interface GitHubIdentityInput {
10
+ /** Repo owner (org/user login) — becomes the OpenEverywhere `workspace`, the org boundary. */
11
+ readonly owner: string;
12
+ /** Repo name — becomes the OpenEverywhere `surface` (the silo grain: one repo, many issues/PRs). */
13
+ readonly repo: string;
14
+ /** "owner/name" — carried for reference/logging (not a partition key on its own). */
15
+ readonly repositoryFullName?: string;
16
+ readonly repositoryId?: number;
17
+ /** Repo visibility, when positively known (off ctx.repository.private); absent → fail closed. */
18
+ readonly isPrivate?: boolean;
19
+ /** "issue" | "pull_request" | "review_thread" — picks the post-back anchor (issue vs PR number). */
20
+ readonly conversationKind?: string;
21
+ /** Issue number for the conversation, when it's an issue thread (post-back anchor). */
22
+ readonly issueNumber?: number;
23
+ /** PR number for the conversation, when it's a PR/review thread (post-back anchor). */
24
+ readonly pullRequestNumber?: number;
25
+ /**
26
+ * The ROOT comment id of the review thread this turn arrived in (OPE-958) — present only on a
27
+ * `review_thread` conversation. It is the id `read_thread` fetches the thread by, and the binding
28
+ * is the only place it can ride: `ReadAdapter.wakeAddress` is pure and synchronous and reads the
29
+ * binding, while eve carries the root on its own channel STATE, which the mapper never sees.
30
+ *
31
+ * GitHub sends `in_reply_to_id` on a REPLY and omits it on the comment that OPENED the thread, so
32
+ * the root is `in_reply_to_id ?? comment.id` and github.ts derives it there, once.
33
+ */
34
+ readonly reviewThreadRootCommentId?: number;
35
+ /** GitHub App installation id (needed to mint tokens for proactive post-back). */
36
+ readonly installationId?: number;
37
+ /** The acting GitHub user (comment author) — the OpenEverywhere principal. */
38
+ readonly actorLogin: string;
39
+ readonly actorId?: number;
40
+ /** "User" | "Bot" | "Organization" — a Bot actor maps to a `service` principal. */
41
+ readonly actorType?: string;
42
+ /**
43
+ * The actor's relationship to the repo, as GitHub reports it on the comment payload
44
+ * (`author_association`): OWNER | MEMBER | COLLABORATOR | CONTRIBUTOR |
45
+ * FIRST_TIME_CONTRIBUTOR | FIRST_TIMER | MANNEQUIN | NONE. A verifiable trust signal.
46
+ */
47
+ readonly authorAssociation?: string;
48
+ }
49
+ /** Build the auth context OpenEverywhere dispatches with — raw facts as string attributes. */
50
+ export declare function buildGitHubAuth(input: GitHubIdentityInput): OpenEverywhereGitHubAuth;
51
+ /**
52
+ * A comment @mentions the bot and should wake a turn. Faithfully mirrors eve's
53
+ * internal gate (shouldDispatchGitHubComment: isIgnoredGitHubComment +
54
+ * extractGitHubCommentTrigger), which is NOT on eve's public export surface — so we
55
+ * replicate it here (small, deterministic, and unit-tested) rather than reach into a
56
+ * package-internal path. Kept byte-consistent with eve because the channel's message
57
+ * layer still strips the mention token via eve's own extractGitHubCommentTrigger; a
58
+ * divergent gate here would fire turns eve then can't strip. FAIL CLOSED: no botName,
59
+ * a bot author, the bot's own comments, or eve's marker → no wake.
60
+ */
61
+ export declare function githubBotMentioned(body: string | undefined, botName: string | undefined, author: {
62
+ readonly login?: string;
63
+ readonly type?: string;
64
+ } | undefined): boolean;
65
+ /**
66
+ * GitHub repo privacy → visibility. FAIL CLOSED, mirroring slackVisibility/
67
+ * linearVisibility: only an explicitly PUBLIC repo (`repo_private="false"`) yields
68
+ * `public`; a private repo (`"true"`) or an unknown signal → `private`, so an
69
+ * under-identified session or a private repo never writes its learnings up to shared
70
+ * (workspace) memory. A public repo's issue/PR threads are world-readable, so treating
71
+ * them as a public surface (learnings may flow to the workspace brain across the org's
72
+ * repos) is correct; anything we can't confirm public stays surface-tier private.
73
+ */
74
+ export declare function githubVisibility(repoPrivate: string | undefined): Visibility;
75
+ /**
76
+ * GitHub actor + repo privacy → trust (the shared-memory gate). FAIL CLOSED — only a
77
+ * POSITIVE in-boundary fact yields a verdict:
78
+ * - A PRIVATE repo (only org members + invited collaborators can see it) touched by an
79
+ * org OWNER or MEMBER → `internal`. OWNER/MEMBER are GitHub's strong "part of the org
80
+ * that owns this repo" signals (`author_association`); on a private repo that means an
81
+ * insider acting inside the boundary, so shared/org memory is safe to read.
82
+ * - ANYTHING ELSE → `undefined`, so bindFrom FAILS CLOSED to `external`:
83
+ * · a PUBLIC repo — world-readable, anyone (external contributors) can comment, so it
84
+ * must never light up the org brain regardless of who this particular actor is;
85
+ * · a private repo touched by a COLLABORATOR (may be an OUTSIDE collaborator),
86
+ * CONTRIBUTOR, first-timer, mannequin, NONE, or a missing/unknown association;
87
+ * · a missing repo-privacy signal.
88
+ * The operator can still vouch for a deployment with OE_TRUST=internal — the same layer
89
+ * Slack-DM/Teams/Discord/Linear use. An ambiguous GitHub event must never reach
90
+ * the shared org brain. (Per-repo collaborator-membership enrichment — GET
91
+ * /repos/{o}/{r}/collaborators/{u}/permission — is the future layer-2 gate that could
92
+ * promote a verified internal collaborator; until then, only OWNER/MEMBER on a private
93
+ * repo is trusted.)
94
+ */
95
+ export declare function githubTrust(repoPrivate: string | undefined, authorAssociation: string | undefined): Trust | undefined;
96
+ /** Local copies of the binding's unions (kept eve-free + import-cycle-free). */
97
+ type Visibility = "public" | "private";
98
+ type Trust = "internal" | "external";
99
+ /** `author_association` values that carry a deliberate access grant on the repo — the INTERNAL set. */
100
+ export declare const GITHUB_INTERNAL_ASSOCIATIONS: readonly string[];
101
+ /** `author_association` values that carry NO access grant — the EXCLUDED (external) set. This is
102
+ * GitHub's whole documented enum minus {@link GITHUB_INTERNAL_ASSOCIATIONS}, written out rather
103
+ * than derived by negation: see {@link githubAssociationRefutes} for why the difference matters. */
104
+ export declare const GITHUB_EXTERNAL_ASSOCIATIONS: readonly string[];
105
+ /**
106
+ * Does this `author_association` POSITIVELY refute the author's membership?
107
+ *
108
+ * 🔴 IT MATCHES THE EXCLUDED SET, IT DOES NOT NEGATE THE INTERNAL ONE, and the difference is the
109
+ * whole fail-open posture. An ABSENT association (a payload shape GitHub changed, a synthesised
110
+ * event) and an UNRECOGNISED one (a value GitHub adds later) both answer `false` — "not refuted",
111
+ * which is what `IngestedChatMessage.refuted` means by absence of evidence. Negating the internal
112
+ * set would read every unknown as external and take the agent off the air on a payload change.
113
+ *
114
+ * Case-sensitive on purpose: GitHub sends these values upper-case, and accepting a lower-case
115
+ * variant would be inventing a second spelling of a vendor enum.
116
+ */
117
+ export declare function githubAssociationRefutes(authorAssociation: string | undefined): boolean;
118
+ /**
119
+ * The author grain's evidence for one GitHub comment, in the core's own vocabulary
120
+ * (`IngestedChatMessage`) so access/external-policy.ts answers the exclusion question — this bundle
121
+ * never re-derives it.
122
+ *
123
+ * A `Bot` actor grades as the machine floor `relay` and is therefore NEVER excluded, which is the
124
+ * core's own ruling (access/taint-signal.ts `nonMemberChatTrust`): a bot is below peer standing but
125
+ * it is not a person outside the organisation, and withholding every app's comments would be an
126
+ * outage. Stating it as the tier rather than as a local `if` keeps ONE place deciding.
127
+ *
128
+ * A human author carries `refuted` only when {@link githubAssociationRefutes} says so. `trust` is
129
+ * left ABSENT for a human on purpose: this runs before the per-wake trust resolution, so naming a
130
+ * tier here would be a guess, and the refutation leg needs no tier.
131
+ */
132
+ export declare function githubAuthorFacts(input: {
133
+ readonly authorAssociation?: string;
134
+ readonly actorType?: string;
135
+ }): IngestedChatMessage;
136
+ /**
137
+ * The CONVERSATION key the stop registry keys a GitHub turn by (OPE-898) — `owner/repo#number`.
138
+ *
139
+ * ONE derivation, used by BOTH sides of the affordance: {@link bindGitHub} writes it onto the anchor
140
+ * row at turn start, and the stop-comment leg (channels/github-stop.ts) rebuilds it from the webhook
141
+ * that carries the directive. Two spellings of this key would make the whole arm silently miss.
142
+ *
143
+ * It is NOT a surface key and never becomes one — the GitHub surface grain is the REPO, and this
144
+ * value names one issue or PR inside it. Returns undefined on any missing part: a partial key would
145
+ * collide across repos.
146
+ */
147
+ export declare function githubConversationKey(owner: string | undefined, repo: string | undefined, number: number | undefined): string | undefined;
148
+ /**
149
+ * The ROOT comment id of the review thread this turn arrived in, or `undefined` (OPE-958). The read
150
+ * side of {@link GitHubIdentityInput.reviewThreadRootCommentId} — this file owns the attribute
151
+ * spelling on BOTH sides, so a rename cannot leave the reader looking for a key nobody writes.
152
+ *
153
+ * It reads the AUTH ATTRIBUTES rather than the composed binding on purpose. `RawIdentity.target` is
154
+ * eve's `GitHubReceiveTarget` and throws on a member it does not know, so the root cannot ride
155
+ * there, and no other binding field names a comment.
156
+ *
157
+ * Returns the id as a STRING of digits, which is what `ReadAdapter.readThread` takes as its thread
158
+ * id. A non-numeric value is treated as absent: a review thread id that is not a comment id would
159
+ * fetch nothing, and refusing honestly beats reading an empty thread.
160
+ */
161
+ export declare function githubReviewThreadRoot(ctx: BindableContext): string | undefined;
162
+ export declare const bindGitHub: IdentityMapper;
163
+ export {};
164
+ //# sourceMappingURL=github-identity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github-identity.d.ts","sourceRoot":"","sources":["../../../agent/channels/github-identity.ts"],"names":[],"mappings":"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"}