@open-agent-toolkit/cli 0.2.65 → 0.2.66

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 (142) hide show
  1. package/assets/bundle-metadata.json +1 -1
  2. package/assets/docs/cli-utilities/configuration.md +19 -1
  3. package/assets/docs/cli-utilities/index.md +3 -0
  4. package/assets/docs/cli-utilities/remote-project-management.md +191 -0
  5. package/assets/docs/reference/cli-reference.md +12 -1
  6. package/assets/docs/reference/file-locations.md +22 -0
  7. package/assets/docs/reference/oat-directory-structure.md +30 -17
  8. package/assets/docs/reference/troubleshooting.md +44 -0
  9. package/assets/docs/workflows/skills/index.md +33 -0
  10. package/assets/public-package-versions.json +4 -4
  11. package/assets/skills/oat-doctor/SKILL.md +4 -4
  12. package/assets/skills/oat-pjm-remote/SKILL.md +101 -0
  13. package/assets/skills/oat-pjm-remote/references/external-action-protocol.md +43 -0
  14. package/dist/commands/backlog/new.d.ts +1 -0
  15. package/dist/commands/backlog/new.d.ts.map +1 -1
  16. package/dist/commands/backlog/new.js +3 -1
  17. package/dist/commands/config/index.d.ts +1 -0
  18. package/dist/commands/config/index.d.ts.map +1 -1
  19. package/dist/commands/config/index.js +232 -1
  20. package/dist/commands/pjm/doctor.d.ts +2 -0
  21. package/dist/commands/pjm/doctor.d.ts.map +1 -1
  22. package/dist/commands/pjm/doctor.js +71 -1
  23. package/dist/commands/pjm/index.d.ts +2 -0
  24. package/dist/commands/pjm/index.d.ts.map +1 -1
  25. package/dist/commands/pjm/index.js +3 -0
  26. package/dist/commands/pjm/remote/__integration__/lifecycle-harness.d.ts +60 -0
  27. package/dist/commands/pjm/remote/__integration__/lifecycle-harness.d.ts.map +1 -0
  28. package/dist/commands/pjm/remote/__integration__/lifecycle-harness.js +151 -0
  29. package/dist/commands/pjm/remote/association.d.ts +27 -0
  30. package/dist/commands/pjm/remote/association.d.ts.map +1 -0
  31. package/dist/commands/pjm/remote/association.js +68 -0
  32. package/dist/commands/pjm/remote/authority.d.ts +210 -0
  33. package/dist/commands/pjm/remote/authority.d.ts.map +1 -0
  34. package/dist/commands/pjm/remote/authority.js +341 -0
  35. package/dist/commands/pjm/remote/batch.d.ts +15 -0
  36. package/dist/commands/pjm/remote/batch.d.ts.map +1 -0
  37. package/dist/commands/pjm/remote/batch.js +126 -0
  38. package/dist/commands/pjm/remote/closeout.d.ts +69 -0
  39. package/dist/commands/pjm/remote/closeout.d.ts.map +1 -0
  40. package/dist/commands/pjm/remote/closeout.js +208 -0
  41. package/dist/commands/pjm/remote/create-binding.d.ts +62 -0
  42. package/dist/commands/pjm/remote/create-binding.d.ts.map +1 -0
  43. package/dist/commands/pjm/remote/create-binding.js +126 -0
  44. package/dist/commands/pjm/remote/credential-safety.d.ts +7 -0
  45. package/dist/commands/pjm/remote/credential-safety.d.ts.map +1 -0
  46. package/dist/commands/pjm/remote/credential-safety.js +21 -0
  47. package/dist/commands/pjm/remote/discussion.d.ts +49 -0
  48. package/dist/commands/pjm/remote/discussion.d.ts.map +1 -0
  49. package/dist/commands/pjm/remote/discussion.js +124 -0
  50. package/dist/commands/pjm/remote/doctor.d.ts +22 -0
  51. package/dist/commands/pjm/remote/doctor.d.ts.map +1 -0
  52. package/dist/commands/pjm/remote/doctor.js +380 -0
  53. package/dist/commands/pjm/remote/external-action.d.ts +208 -0
  54. package/dist/commands/pjm/remote/external-action.d.ts.map +1 -0
  55. package/dist/commands/pjm/remote/external-action.js +367 -0
  56. package/dist/commands/pjm/remote/host-execution.d.ts +28 -0
  57. package/dist/commands/pjm/remote/host-execution.d.ts.map +1 -0
  58. package/dist/commands/pjm/remote/host-execution.js +63 -0
  59. package/dist/commands/pjm/remote/index.d.ts +41 -0
  60. package/dist/commands/pjm/remote/index.d.ts.map +1 -0
  61. package/dist/commands/pjm/remote/index.js +280 -0
  62. package/dist/commands/pjm/remote/jira-adf.d.ts +35 -0
  63. package/dist/commands/pjm/remote/jira-adf.d.ts.map +1 -0
  64. package/dist/commands/pjm/remote/jira-adf.js +169 -0
  65. package/dist/commands/pjm/remote/lifecycle.d.ts +163 -0
  66. package/dist/commands/pjm/remote/lifecycle.d.ts.map +1 -0
  67. package/dist/commands/pjm/remote/lifecycle.js +224 -0
  68. package/dist/commands/pjm/remote/local-projection.d.ts +30 -0
  69. package/dist/commands/pjm/remote/local-projection.d.ts.map +1 -0
  70. package/dist/commands/pjm/remote/local-projection.js +95 -0
  71. package/dist/commands/pjm/remote/managed-markdown.d.ts +30 -0
  72. package/dist/commands/pjm/remote/managed-markdown.d.ts.map +1 -0
  73. package/dist/commands/pjm/remote/managed-markdown.js +128 -0
  74. package/dist/commands/pjm/remote/migrate.d.ts +14 -0
  75. package/dist/commands/pjm/remote/migrate.d.ts.map +1 -0
  76. package/dist/commands/pjm/remote/migrate.js +32 -0
  77. package/dist/commands/pjm/remote/operation-state.d.ts +15 -0
  78. package/dist/commands/pjm/remote/operation-state.d.ts.map +1 -0
  79. package/dist/commands/pjm/remote/operation-state.js +143 -0
  80. package/dist/commands/pjm/remote/outbound-projection-safety.d.ts +25 -0
  81. package/dist/commands/pjm/remote/outbound-projection-safety.d.ts.map +1 -0
  82. package/dist/commands/pjm/remote/outbound-projection-safety.js +99 -0
  83. package/dist/commands/pjm/remote/output.d.ts +60 -0
  84. package/dist/commands/pjm/remote/output.d.ts.map +1 -0
  85. package/dist/commands/pjm/remote/output.js +48 -0
  86. package/dist/commands/pjm/remote/preview.d.ts +88 -0
  87. package/dist/commands/pjm/remote/preview.d.ts.map +1 -0
  88. package/dist/commands/pjm/remote/preview.js +154 -0
  89. package/dist/commands/pjm/remote/provider-conformance.d.ts +13 -0
  90. package/dist/commands/pjm/remote/provider-conformance.d.ts.map +1 -0
  91. package/dist/commands/pjm/remote/provider-conformance.js +37 -0
  92. package/dist/commands/pjm/remote/provider.d.ts +57 -0
  93. package/dist/commands/pjm/remote/provider.d.ts.map +1 -0
  94. package/dist/commands/pjm/remote/provider.js +28 -0
  95. package/dist/commands/pjm/remote/providers/github-publication-safety.d.ts +46 -0
  96. package/dist/commands/pjm/remote/providers/github-publication-safety.d.ts.map +1 -0
  97. package/dist/commands/pjm/remote/providers/github-publication-safety.js +126 -0
  98. package/dist/commands/pjm/remote/providers/github.d.ts +207 -0
  99. package/dist/commands/pjm/remote/providers/github.d.ts.map +1 -0
  100. package/dist/commands/pjm/remote/providers/github.js +1456 -0
  101. package/dist/commands/pjm/remote/providers/jira.d.ts +245 -0
  102. package/dist/commands/pjm/remote/providers/jira.d.ts.map +1 -0
  103. package/dist/commands/pjm/remote/providers/jira.js +1650 -0
  104. package/dist/commands/pjm/remote/providers/linear.d.ts +182 -0
  105. package/dist/commands/pjm/remote/providers/linear.d.ts.map +1 -0
  106. package/dist/commands/pjm/remote/providers/linear.js +1289 -0
  107. package/dist/commands/pjm/remote/purpose-policy.d.ts +29 -0
  108. package/dist/commands/pjm/remote/purpose-policy.d.ts.map +1 -0
  109. package/dist/commands/pjm/remote/purpose-policy.js +118 -0
  110. package/dist/commands/pjm/remote/reconcile.d.ts +42 -0
  111. package/dist/commands/pjm/remote/reconcile.d.ts.map +1 -0
  112. package/dist/commands/pjm/remote/reconcile.js +101 -0
  113. package/dist/commands/pjm/remote/resolution.d.ts +122 -0
  114. package/dist/commands/pjm/remote/resolution.d.ts.map +1 -0
  115. package/dist/commands/pjm/remote/resolution.js +243 -0
  116. package/dist/commands/pjm/remote/schema.d.ts +9309 -0
  117. package/dist/commands/pjm/remote/schema.d.ts.map +1 -0
  118. package/dist/commands/pjm/remote/schema.js +1503 -0
  119. package/dist/commands/pjm/remote/service.d.ts +20 -0
  120. package/dist/commands/pjm/remote/service.d.ts.map +1 -0
  121. package/dist/commands/pjm/remote/service.js +5121 -0
  122. package/dist/commands/pjm/remote/shared-storage.d.ts +36 -0
  123. package/dist/commands/pjm/remote/shared-storage.d.ts.map +1 -0
  124. package/dist/commands/pjm/remote/shared-storage.js +110 -0
  125. package/dist/commands/pjm/remote/snapshot.d.ts +32 -0
  126. package/dist/commands/pjm/remote/snapshot.d.ts.map +1 -0
  127. package/dist/commands/pjm/remote/snapshot.js +140 -0
  128. package/dist/commands/pjm/remote/storage-locator.d.ts +29 -0
  129. package/dist/commands/pjm/remote/storage-locator.d.ts.map +1 -0
  130. package/dist/commands/pjm/remote/storage-locator.js +62 -0
  131. package/dist/commands/pjm/remote/store.d.ts +85 -0
  132. package/dist/commands/pjm/remote/store.d.ts.map +1 -0
  133. package/dist/commands/pjm/remote/store.js +553 -0
  134. package/dist/commands/pjm/remote/verification.d.ts +40 -0
  135. package/dist/commands/pjm/remote/verification.d.ts.map +1 -0
  136. package/dist/commands/pjm/remote/verification.js +100 -0
  137. package/dist/commands/tools/shared/pack-manifest.d.ts.map +1 -1
  138. package/dist/commands/tools/shared/pack-manifest.js +1 -0
  139. package/dist/config/oat-config.d.ts +26 -0
  140. package/dist/config/oat-config.d.ts.map +1 -1
  141. package/dist/config/oat-config.js +186 -2
  142. package/package.json +2 -2
@@ -0,0 +1,1456 @@
1
+ import { containsSensitiveContentSignal } from '../../../pjm/remote/credential-safety.js';
2
+ import { inspectManagedMarkdown, insertManagedMarkdown, replaceManagedMarkdown, } from '../../../pjm/remote/managed-markdown.js';
3
+ import { requireCurrentOutboundSafety, } from '../../../pjm/remote/outbound-projection-safety.js';
4
+ import { contextsEqual, semanticDigest, } from '../../../pjm/remote/provider.js';
5
+ import { WHOLE_FIELD_SUPPRESSION_MARKER } from '../../../pjm/remote/schema.js';
6
+ import { requireCurrentGitHubPublicationSafety, } from './github-publication-safety.js';
7
+ const URL_REFERENCE = /^https:\/\/([^/]+)\/([^/]+)\/([^/]+)\/issues\/(\d+)(?:[/?#].*)?$/;
8
+ const SHORT_REFERENCE = /^([^/\s]+)\/([^#\s]+)#(\d+)$/;
9
+ const MAX_DELETION_EVIDENCE_AGE_MS = 30_000;
10
+ export function parseGitHubIssueReference(reference) {
11
+ const value = reference.trim();
12
+ const url = URL_REFERENCE.exec(value);
13
+ if (url) {
14
+ return {
15
+ host: url[1].toLowerCase(),
16
+ owner: url[2],
17
+ name: url[3],
18
+ number: Number(url[4]),
19
+ alias: value,
20
+ };
21
+ }
22
+ const short = SHORT_REFERENCE.exec(value);
23
+ if (!short)
24
+ return null;
25
+ return {
26
+ owner: short[1],
27
+ name: short[2],
28
+ number: Number(short[3]),
29
+ alias: value,
30
+ };
31
+ }
32
+ export function normalizeGitHubIssueObservation(observation) {
33
+ assertGitHubObservation(observation);
34
+ const host = requiredContext(observation, 'host').toLowerCase();
35
+ const repositoryId = requiredContext(observation, 'repositoryId');
36
+ const nodeId = requiredString(observation.fields, 'nodeId');
37
+ const owner = requiredString(observation.fields, 'owner');
38
+ const name = requiredString(observation.fields, 'name');
39
+ const number = requiredPositiveInteger(observation.fields, 'number');
40
+ const url = requiredString(observation.fields, 'url');
41
+ const title = requiredString(observation.fields, 'title');
42
+ const description = nullableString(observation.fields, 'body');
43
+ const priority = nullableString(observation.fields, 'priority');
44
+ const status = requiredString(observation.fields, 'state');
45
+ const databaseId = requiredPositiveInteger(observation.fields, 'databaseId');
46
+ const pullRequests = uniqueStrings(observation.fields.pullRequests);
47
+ const annotations = Array.isArray(observation.fields.annotations)
48
+ ? uniqueStrings(observation.fields.annotations)
49
+ : null;
50
+ const historicalRepositoryIds = optionalStringArray(observation.fields.historicalRepositoryIds);
51
+ const currentShortAlias = `${owner}/${name}#${number}`;
52
+ const mutationEvidence = parseMutationExecutionEvidence(observation.fields.mutationEvidence);
53
+ const createProvenance = parseCreateProvenance(observation.fields.createProvenance);
54
+ return {
55
+ provider: 'github',
56
+ context: observation.context,
57
+ stableId: canonicalGitHubStableId(host, nodeId),
58
+ aliases: uniqueStrings([
59
+ currentShortAlias,
60
+ url,
61
+ ...observation.identity.aliases,
62
+ ]),
63
+ title,
64
+ description,
65
+ priority,
66
+ status,
67
+ revisionDigest: semanticDigest(observation.revision),
68
+ extensions: {
69
+ databaseId,
70
+ nodeId,
71
+ repositoryId,
72
+ historicalRepositoryIds,
73
+ pullRequests,
74
+ ...(annotations ? { annotations } : {}),
75
+ ...(mutationEvidence
76
+ ? {
77
+ capabilityEvidenceDigest: observation.capabilityEvidenceDigest,
78
+ mutationEvidence,
79
+ }
80
+ : {}),
81
+ ...(createProvenance ? { createProvenance } : {}),
82
+ },
83
+ };
84
+ }
85
+ export function validateGitHubHostCapability(expected, observed) {
86
+ const reasons = [];
87
+ if (observed.provider !== 'github')
88
+ reasons.push('provider-mismatch');
89
+ if (observed.availability === 'unavailable')
90
+ reasons.push('access-unavailable');
91
+ if (observed.availability === 'authorization-required')
92
+ reasons.push('authorization-required');
93
+ if (observed.availability === 'rate-limited')
94
+ reasons.push('rate-limited');
95
+ if (expected.context.accountId &&
96
+ observed.accountId !== expected.context.accountId) {
97
+ reasons.push('account-mismatch');
98
+ }
99
+ if (expected.context.repositoryId &&
100
+ observed.repositoryId !== expected.context.repositoryId) {
101
+ reasons.push('repository-mismatch');
102
+ }
103
+ if (!contextsEqual(expected.context, observed.context)) {
104
+ const accountOrRepositoryMismatch = reasons.some((reason) => ['account-mismatch', 'repository-mismatch'].includes(reason));
105
+ if (!accountOrRepositoryMismatch)
106
+ reasons.push('context-mismatch');
107
+ }
108
+ if (!observed.operations.includes(expected.operation)) {
109
+ reasons.push(`capability-missing:${expected.operation}`);
110
+ }
111
+ for (const field of expected.requiredFields ?? requiredFieldsForRead()) {
112
+ if (!observed.observableFields.includes(field)) {
113
+ reasons.push(`semantic-field-missing:${field}`);
114
+ }
115
+ }
116
+ if (!observed.evidenceDigest)
117
+ reasons.push('capability-evidence-missing');
118
+ return { valid: reasons.length === 0, reasons };
119
+ }
120
+ export function classifyGitHubReadObservation(input) {
121
+ if (input.action.provider !== 'github' || input.action.operation !== 'read') {
122
+ throw new Error('GitHub read classification requires a GitHub read action.');
123
+ }
124
+ if (!Number.isFinite(Date.parse(input.observedAt))) {
125
+ throw new Error('GitHub read classification requires a valid timestamp.');
126
+ }
127
+ const capability = validateGitHubHostCapability({
128
+ operation: 'read',
129
+ context: input.action.context,
130
+ requiredFields: requiredFieldsForRead(),
131
+ }, input.hostCapability);
132
+ if (!capability.valid) {
133
+ return unavailableReadResult(capability.reasons.includes('rate-limited')
134
+ ? 'temporarily-unavailable'
135
+ : 'inaccessible', capability.reasons);
136
+ }
137
+ const plannedCapabilityDigest = input.action.intent.capabilityEvidenceDigest;
138
+ const plannedStableId = input.action.intent.stableId;
139
+ const plannedNodeId = input.action.intent.stableNodeId;
140
+ if (typeof plannedCapabilityDigest !== 'string' ||
141
+ plannedCapabilityDigest !== input.hostCapability.evidenceDigest ||
142
+ typeof plannedStableId !== 'string' ||
143
+ typeof plannedNodeId !== 'string' ||
144
+ !validReadActionEvidence(input.action)) {
145
+ return unavailableReadResult('inaccessible', [
146
+ 'planned-read-evidence-missing-or-mismatched',
147
+ ]);
148
+ }
149
+ if (input.outcome === 'temporary-failure') {
150
+ return unavailableReadResult('temporarily-unavailable', [
151
+ 'temporary-host-failure',
152
+ ]);
153
+ }
154
+ if (input.outcome === 'not-found') {
155
+ const deleted = validDeletionEvidence(input.deletionEvidence, input.action, input.hostCapability.evidenceDigest, input.observedAt);
156
+ return unavailableReadResult(deleted ? 'deleted' : 'inaccessible', [
157
+ deleted ? 'authoritative-deletion' : 'absence-not-authoritative',
158
+ ]);
159
+ }
160
+ if (!input.observation) {
161
+ return unavailableReadResult('temporarily-unavailable', [
162
+ 'read-observation-missing',
163
+ ]);
164
+ }
165
+ const observedNodeId = input.observation.fields.nodeId;
166
+ const observedHost = input.observation.context.host;
167
+ if (input.observation.provider !== 'github') {
168
+ return unavailableReadResult('inaccessible', [
169
+ 'observation-provider-mismatch',
170
+ ]);
171
+ }
172
+ if (input.observation.capabilityEvidenceDigest !== plannedCapabilityDigest) {
173
+ return unavailableReadResult('inaccessible', [
174
+ 'observation-capability-mismatch',
175
+ ]);
176
+ }
177
+ if (observedHost !== input.action.context.host) {
178
+ return unavailableReadResult('inaccessible', ['observation-host-mismatch']);
179
+ }
180
+ if (input.observation.context.owner !== input.observation.fields.owner ||
181
+ input.observation.context.name !== input.observation.fields.name) {
182
+ return unavailableReadResult('inaccessible', [
183
+ 'observation-context-mismatch',
184
+ ]);
185
+ }
186
+ if (input.action.context.accountId !== undefined &&
187
+ input.observation.context.accountId !== input.action.context.accountId) {
188
+ return unavailableReadResult('inaccessible', [
189
+ 'observation-account-mismatch',
190
+ ]);
191
+ }
192
+ if (observedNodeId !== plannedNodeId ||
193
+ !observationIdentityMatches(input.observation.identity.stableId, plannedStableId, plannedNodeId)) {
194
+ return unavailableReadResult('inaccessible', [
195
+ 'observation-identity-mismatch',
196
+ ]);
197
+ }
198
+ const expectedRepositoryId = input.action.context.repositoryId;
199
+ const observedRepositoryId = input.observation.context.repositoryId;
200
+ if (expectedRepositoryId &&
201
+ observedRepositoryId !== expectedRepositoryId &&
202
+ !validTransferEvidence(input.observation.fields.transferEvidence, plannedStableId, expectedRepositoryId, observedRepositoryId, plannedCapabilityDigest, input.observedAt)) {
203
+ return unavailableReadResult('inaccessible', [
204
+ 'transfer-evidence-missing-or-invalid',
205
+ ]);
206
+ }
207
+ let issue;
208
+ try {
209
+ issue = normalizeGitHubIssueObservation(input.observation);
210
+ }
211
+ catch {
212
+ return unavailableReadResult('inaccessible', ['read-observation-invalid']);
213
+ }
214
+ const currentAlias = `${requiredString(input.observation.fields, 'owner')}/${requiredString(input.observation.fields, 'name')}#${requiredPositiveInteger(input.observation.fields, 'number')}`;
215
+ const expectedAlias = input.action.intent.currentAlias;
216
+ const classification = input.archived
217
+ ? 'archived'
218
+ : expectedRepositoryId && observedRepositoryId !== expectedRepositoryId
219
+ ? 'transferred'
220
+ : typeof expectedAlias === 'string' && currentAlias !== expectedAlias
221
+ ? 'renamed'
222
+ : 'current';
223
+ return {
224
+ classification,
225
+ issue,
226
+ freshness: {
227
+ observedAt: input.observedAt,
228
+ revisionStrength: input.observation.revision.token ? 'strong' : 'weak',
229
+ },
230
+ preservePriorEvidence: false,
231
+ reasons: [],
232
+ };
233
+ }
234
+ export function previewGitHubMutation(input) {
235
+ assertMutationIdentity(input);
236
+ const capability = validateGitHubHostCapability({ operation: input.operation, context: input.context, requiredFields: [] }, input.hostCapability);
237
+ if (!capability.valid) {
238
+ throw new Error('GitHub mutation capability is unavailable.');
239
+ }
240
+ const fieldMask = normalizeMutationFieldMask(input.operation, input.fieldMask);
241
+ const projection = buildMutationProjection(input, fieldMask);
242
+ requireCurrentOutboundSafety(projection, input.outboundSafety);
243
+ requireCurrentGitHubPublicationSafety({
244
+ context: input.context,
245
+ capabilityEvidenceDigest: input.hostCapability.evidenceDigest,
246
+ projection,
247
+ outboundSafety: input.outboundSafety,
248
+ result: input.publicationSafety,
249
+ });
250
+ const executionEvidence = {
251
+ capabilityEvidenceDigest: input.hostCapability.evidenceDigest,
252
+ projectionDigest: input.outboundSafety.projectionDigest,
253
+ outboundSafetyResultDigest: input.outboundSafety.resultDigest,
254
+ publicationSafetyResultDigest: input.publicationSafety.resultDigest,
255
+ visibilityEvidenceDigest: input.publicationSafety.preview.visibilityEvidenceDigest,
256
+ };
257
+ const previewDigest = semanticDigest({
258
+ provider: 'github',
259
+ operation: input.operation,
260
+ context: input.context,
261
+ bindingId: input.bindingId,
262
+ stableId: input.stableId ?? null,
263
+ provenance: input.provenance ?? null,
264
+ fieldMask,
265
+ projection,
266
+ postconditions: projection,
267
+ executionEvidence,
268
+ });
269
+ return { previewDigest, executionEvidence };
270
+ }
271
+ export function planGitHubMutation(input) {
272
+ const preview = previewGitHubMutation(input);
273
+ if (input.approvedPreviewDigest !== preview.previewDigest) {
274
+ throw new Error('GitHub mutation approval does not match its preview.');
275
+ }
276
+ const fieldMask = normalizeMutationFieldMask(input.operation, input.fieldMask);
277
+ const projection = buildMutationProjection(input, fieldMask);
278
+ const actionDigest = semanticDigest({
279
+ provider: 'github',
280
+ operation: input.operation,
281
+ context: input.context,
282
+ previewDigest: preview.previewDigest,
283
+ approvalDigest: input.approvedPreviewDigest,
284
+ executionEvidence: preview.executionEvidence,
285
+ });
286
+ return {
287
+ provider: 'github',
288
+ operation: input.operation,
289
+ context: input.context,
290
+ intent: {
291
+ bindingId: input.bindingId,
292
+ stableId: input.stableId ?? null,
293
+ provenance: input.provenance ?? null,
294
+ fieldMask,
295
+ projection,
296
+ postconditions: { ...projection },
297
+ capabilityEvidenceDigest: input.hostCapability.evidenceDigest,
298
+ outboundSafety: {
299
+ projectionDigest: input.outboundSafety.projectionDigest,
300
+ resultDigest: input.outboundSafety.resultDigest,
301
+ },
302
+ publicationSafety: {
303
+ resultDigest: input.publicationSafety.resultDigest,
304
+ visibilityEvidenceDigest: input.publicationSafety.preview.visibilityEvidenceDigest,
305
+ },
306
+ previewDigest: preview.previewDigest,
307
+ approvalDigest: input.approvedPreviewDigest,
308
+ actionDigest,
309
+ executionEvidence: {
310
+ ...preview.executionEvidence,
311
+ previewDigest: preview.previewDigest,
312
+ approvalDigest: input.approvedPreviewDigest,
313
+ actionDigest,
314
+ },
315
+ },
316
+ };
317
+ }
318
+ export function verifyGitHubMutationObservation(input) {
319
+ if (input.action.provider !== 'github' ||
320
+ !['create', 'update', 'transition', 'annotate'].includes(input.action.operation)) {
321
+ throw new Error('GitHub mutation verification requires a mutation action.');
322
+ }
323
+ if (input.attempt.count !== 1) {
324
+ throw new Error('GitHub mutation verification accepts exactly one attempt.');
325
+ }
326
+ if (!validMutationActionEvidence(input.action)) {
327
+ return mutationResult('uncertain', 'action-evidence-invalid');
328
+ }
329
+ const capability = validateGitHubHostCapability({
330
+ operation: input.action.operation,
331
+ context: input.action.context,
332
+ requiredFields: [],
333
+ }, input.hostCapability);
334
+ if (!capability.valid ||
335
+ input.action.intent.capabilityEvidenceDigest !==
336
+ input.hostCapability.evidenceDigest ||
337
+ input.attempt.capabilityEvidenceDigest !==
338
+ input.hostCapability.evidenceDigest) {
339
+ return mutationResult('uncertain', 'attempt-capability-mismatch');
340
+ }
341
+ if (input.attempt.outcome === 'rejected') {
342
+ return mutationResult('rejected', 'provider-rejected');
343
+ }
344
+ if (input.attempt.outcome === 'unknown') {
345
+ return mutationResult('uncertain', 'unknown-after-attempt');
346
+ }
347
+ if (!input.readback) {
348
+ return mutationResult('uncertain', 'readback-missing');
349
+ }
350
+ if (input.readback.capabilityEvidenceDigest !==
351
+ input.attempt.capabilityEvidenceDigest) {
352
+ return mutationResult('uncertain', 'readback-surface-mismatch');
353
+ }
354
+ if (input.readback.provider !== 'github' ||
355
+ !contextsEqual(input.action.context, input.readback.context)) {
356
+ return mutationResult('uncertain', 'readback-context-mismatch');
357
+ }
358
+ if (!semanticValuesEqual(input.readback.fields.mutationEvidence, input.action.intent.executionEvidence)) {
359
+ return mutationResult('uncertain', 'readback-action-evidence-mismatch');
360
+ }
361
+ if (input.action.operation === 'create' &&
362
+ !semanticValuesEqual(input.readback.fields.createProvenance, input.action.intent.provenance)) {
363
+ return mutationResult('uncertain', 'readback-create-provenance-mismatch');
364
+ }
365
+ let normalizedReadback;
366
+ try {
367
+ normalizedReadback = normalizeGitHubIssueObservation(input.readback);
368
+ }
369
+ catch {
370
+ return mutationResult('uncertain', 'readback-invalid');
371
+ }
372
+ const readbackNodeId = String(input.readback.fields.nodeId ?? '');
373
+ if (!observationIdentityMatches(input.readback.identity.stableId, normalizedReadback.stableId, readbackNodeId)) {
374
+ return mutationResult('uncertain', 'readback-identity-mismatch');
375
+ }
376
+ const stableId = input.action.intent.stableId;
377
+ if (typeof stableId === 'string' &&
378
+ stableId !== input.readback.identity.stableId &&
379
+ stableId !== normalizedReadback.stableId) {
380
+ return mutationResult('uncertain', 'readback-identity-mismatch');
381
+ }
382
+ const postconditions = parsePostconditions(input.action.intent.postconditions);
383
+ const fields = Object.entries(postconditions).map(([field, expected]) => ({
384
+ field,
385
+ status: verifyMutationField(input.readback, field, expected),
386
+ }));
387
+ const verifiedCount = fields.filter((field) => field.status === 'verified').length;
388
+ if (verifiedCount === fields.length) {
389
+ return mutationResult('verified', 'postconditions-verified', fields);
390
+ }
391
+ if (verifiedCount > 0) {
392
+ return mutationResult('partial', 'postconditions-partial', fields);
393
+ }
394
+ return mutationResult('uncertain', 'postconditions-unverified', fields);
395
+ }
396
+ export function planDuplicateSearch(input) {
397
+ const capability = validateGitHubHostCapability({
398
+ operation: 'search-duplicates',
399
+ context: input.context,
400
+ requiredFields: [],
401
+ }, input.hostCapability);
402
+ if (!capability.valid) {
403
+ throw new Error('GitHub duplicate search capability is unavailable.');
404
+ }
405
+ if (!input.provenanceToken ||
406
+ !input.reservedBindingId ||
407
+ !Number.isInteger(input.maxResults) ||
408
+ input.maxResults < 1 ||
409
+ input.maxResults > 100 ||
410
+ input.historicalAliases.length > 64 ||
411
+ input.historicalAliases.some((alias) => !alias)) {
412
+ throw new Error('GitHub duplicate search bounds are invalid.');
413
+ }
414
+ const query = {
415
+ provenanceToken: input.provenanceToken,
416
+ reservedBindingId: input.reservedBindingId,
417
+ historicalAliases: uniqueStrings(input.historicalAliases),
418
+ repository: {
419
+ host: requiredContextValue(input.context, 'host'),
420
+ repositoryId: requiredContextValue(input.context, 'repositoryId'),
421
+ owner: requiredContextValue(input.context, 'owner'),
422
+ name: requiredContextValue(input.context, 'name'),
423
+ },
424
+ };
425
+ return {
426
+ provider: 'github',
427
+ operation: 'search-duplicates',
428
+ context: input.context,
429
+ intent: {
430
+ query,
431
+ queryDigest: semanticDigest(query),
432
+ resultContract: {
433
+ maxResults: input.maxResults,
434
+ classifications: ['no-match', 'one-match', 'ambiguous'],
435
+ matchStatus: 'evidence-until-identity-and-context-verified',
436
+ },
437
+ capabilityEvidenceDigest: input.hostCapability.evidenceDigest,
438
+ },
439
+ };
440
+ }
441
+ export function validateDuplicateSearchObservation(input) {
442
+ if (input.action.provider !== 'github' ||
443
+ input.action.operation !== 'search-duplicates') {
444
+ throw new Error('GitHub duplicate validation requires a search action.');
445
+ }
446
+ if (!validDuplicateSearchAction(input.action)) {
447
+ return duplicateValidationResult(false, 'invalid', null, [
448
+ 'action-contract-invalid',
449
+ ]);
450
+ }
451
+ const capability = validateGitHubHostCapability({
452
+ operation: 'search-duplicates',
453
+ context: input.action.context,
454
+ requiredFields: [],
455
+ }, input.hostCapability);
456
+ if (!duplicateObservationAttributedToAction(input)) {
457
+ return duplicateValidationResult(false, 'invalid', null, [
458
+ 'observation-context-or-capability-mismatch',
459
+ ]);
460
+ }
461
+ const availabilityReasons = new Set([
462
+ 'access-unavailable',
463
+ 'authorization-required',
464
+ 'rate-limited',
465
+ ]);
466
+ const capabilityAttributionReasons = capability.reasons.filter((reason) => !availabilityReasons.has(reason));
467
+ if (capabilityAttributionReasons.length > 0) {
468
+ return duplicateValidationResult(false, 'invalid', null, [
469
+ ...capabilityAttributionReasons,
470
+ ]);
471
+ }
472
+ if (!capability.valid || input.observation.availability === 'unavailable') {
473
+ return duplicateValidationResult(false, 'unavailable', null, [
474
+ ...capability.reasons,
475
+ ...(input.observation.availability === 'unavailable'
476
+ ? ['search-unavailable']
477
+ : []),
478
+ ]);
479
+ }
480
+ const resultContract = recordValue(input.action.intent.resultContract, 'GitHub duplicate result contract is missing.');
481
+ const maxResults = resultContract.maxResults;
482
+ if (!Number.isInteger(maxResults) ||
483
+ Number(maxResults) < 1 ||
484
+ Number(maxResults) > 100 ||
485
+ input.observation.results.length > Number(maxResults)) {
486
+ return duplicateValidationResult(false, 'invalid', null, [
487
+ 'result-bound-exceeded',
488
+ ]);
489
+ }
490
+ if (input.observation.results.length === 0) {
491
+ return duplicateValidationResult(true, 'no-match', null, []);
492
+ }
493
+ if (input.observation.results.some((candidate) => !validDuplicateCandidateBounds(candidate))) {
494
+ return duplicateValidationResult(false, 'invalid', null, [
495
+ 'candidate-bound-invalid',
496
+ ]);
497
+ }
498
+ if (input.observation.results.length > 1) {
499
+ return duplicateValidationResult(false, 'ambiguous', null, [
500
+ 'multiple-candidates',
501
+ ]);
502
+ }
503
+ const candidate = input.observation.results[0];
504
+ const query = recordValue(input.action.intent.query, 'GitHub duplicate query contract is missing.');
505
+ const aliases = Array.isArray(query.historicalAliases)
506
+ ? query.historicalAliases.filter((alias) => typeof alias === 'string')
507
+ : [];
508
+ const matchEvidenceValid = candidate.matchedBy === 'provenance'
509
+ ? candidate.matchedProvenanceToken === query.provenanceToken
510
+ : candidate.matchedBy === 'reserved-binding'
511
+ ? candidate.matchedReservedBindingId === query.reservedBindingId
512
+ : typeof candidate.matchedAlias === 'string' &&
513
+ aliases.includes(candidate.matchedAlias) &&
514
+ candidate.aliases.includes(candidate.matchedAlias);
515
+ if (!candidate.stableId ||
516
+ !candidate.stableIdentityVerified ||
517
+ !candidate.contextVerified ||
518
+ !duplicateCandidateAttributedToAction(candidate, input) ||
519
+ !matchEvidenceValid) {
520
+ return duplicateValidationResult(false, 'ambiguous', null, [
521
+ 'candidate-not-fully-verified',
522
+ ]);
523
+ }
524
+ return duplicateValidationResult(true, 'one-verified-match', candidate.stableId, []);
525
+ }
526
+ export function planDiscussionRead(input) {
527
+ const capability = validateGitHubHostCapability({
528
+ operation: 'read-discussion',
529
+ context: input.context,
530
+ requiredFields: [],
531
+ }, input.hostCapability);
532
+ if (!capability.valid) {
533
+ throw new Error('GitHub discussion read capability is unavailable.');
534
+ }
535
+ if (!input.stableId ||
536
+ !Number.isInteger(input.limit) ||
537
+ input.limit < 1 ||
538
+ input.limit > 100 ||
539
+ !['comments', 'activity'].includes(input.evidenceKind) ||
540
+ !validDiscussionCursor(input.cursor)) {
541
+ throw new Error('GitHub discussion read bounds are invalid.');
542
+ }
543
+ return {
544
+ provider: 'github',
545
+ operation: 'read-discussion',
546
+ context: input.context,
547
+ intent: {
548
+ stableId: input.stableId,
549
+ evidenceKind: input.evidenceKind,
550
+ cursor: input.cursor,
551
+ limit: input.limit,
552
+ resultContract: {
553
+ maxItems: input.limit,
554
+ content: 'sanitized-non-persistent-evidence',
555
+ },
556
+ capabilityEvidenceDigest: input.hostCapability.evidenceDigest,
557
+ },
558
+ };
559
+ }
560
+ export function validateDiscussionReadObservation(input) {
561
+ if (input.action.provider !== 'github' ||
562
+ input.action.operation !== 'read-discussion') {
563
+ throw new Error('GitHub discussion validation requires a discussion action.');
564
+ }
565
+ if (!validDiscussionReadAction(input.action)) {
566
+ return discussionResult('invalid', null, ['action-contract-invalid']);
567
+ }
568
+ const capability = validateGitHubHostCapability({
569
+ operation: 'read-discussion',
570
+ context: input.action.context,
571
+ requiredFields: [],
572
+ }, input.hostCapability);
573
+ if (input.action.intent.capabilityEvidenceDigest !==
574
+ input.hostCapability.evidenceDigest ||
575
+ input.observation.provider !== 'github' ||
576
+ !contextsEqual(input.action.context, input.observation.context) ||
577
+ input.observation.capabilityEvidenceDigest !==
578
+ input.action.intent.capabilityEvidenceDigest ||
579
+ input.observation.stableId !== input.action.intent.stableId ||
580
+ input.observation.requestedCursor !== input.action.intent.cursor) {
581
+ return discussionResult('invalid', null, [
582
+ 'discussion-context-or-cursor-mismatch',
583
+ ]);
584
+ }
585
+ const availabilityReasons = new Set([
586
+ 'access-unavailable',
587
+ 'authorization-required',
588
+ 'rate-limited',
589
+ ]);
590
+ const capabilityAttributionReasons = capability.reasons.filter((reason) => !availabilityReasons.has(reason));
591
+ if (capabilityAttributionReasons.length > 0) {
592
+ return discussionResult('invalid', null, capabilityAttributionReasons);
593
+ }
594
+ if (input.observation.availability !== 'available') {
595
+ return discussionResult(input.observation.availability, null, [
596
+ input.observation.availability,
597
+ ]);
598
+ }
599
+ if (!capability.valid) {
600
+ return discussionResult('invalid', null, capability.reasons);
601
+ }
602
+ const contract = recordValue(input.action.intent.resultContract, 'GitHub discussion result contract is missing.');
603
+ const maxItems = Number(contract.maxItems);
604
+ if (!Number.isInteger(maxItems) ||
605
+ maxItems < 1 ||
606
+ maxItems > 100 ||
607
+ input.observation.items.length > maxItems ||
608
+ !validDiscussionCursor(input.observation.nextCursor)) {
609
+ return discussionResult('invalid', null, ['discussion-bound-exceeded']);
610
+ }
611
+ const items = [];
612
+ for (const item of input.observation.items) {
613
+ if (!item.id ||
614
+ item.id.length > 256 ||
615
+ !['comment', 'activity'].includes(item.kind) ||
616
+ typeof item.body !== 'string' ||
617
+ Buffer.byteLength(item.body, 'utf8') > 16_384 ||
618
+ !Number.isFinite(Date.parse(item.observedAt))) {
619
+ return discussionResult('invalid', null, ['discussion-item-invalid']);
620
+ }
621
+ const contentSuppressed = containsSensitiveContentSignal(item.body);
622
+ items.push({
623
+ id: item.id,
624
+ kind: item.kind,
625
+ body: contentSuppressed ? WHOLE_FIELD_SUPPRESSION_MARKER : item.body,
626
+ observedAt: item.observedAt,
627
+ contentSuppressed,
628
+ });
629
+ }
630
+ return discussionResult('page', { items, nextCursor: input.observation.nextCursor }, []);
631
+ }
632
+ export const githubAdapter = {
633
+ provider: 'github',
634
+ normalize: normalizeGitHubIssueObservation,
635
+ plan(operation, input) {
636
+ if (['create', 'update', 'transition', 'annotate'].includes(operation)) {
637
+ if (!isCompleteMutationInput(operation, input)) {
638
+ throw new Error('GitHub adapter requires complete GitHub mutation input.');
639
+ }
640
+ return planGitHubMutation(input);
641
+ }
642
+ if (operation === 'search-duplicates') {
643
+ if (!isCompleteDuplicateSearchInput(input)) {
644
+ throw new Error('GitHub adapter requires complete GitHub duplicate search input.');
645
+ }
646
+ return planDuplicateSearch(input);
647
+ }
648
+ if (operation === 'read-discussion') {
649
+ if (!isCompleteDiscussionReadInput(input)) {
650
+ throw new Error('GitHub adapter requires complete GitHub discussion read input.');
651
+ }
652
+ return planDiscussionRead(input);
653
+ }
654
+ const { context, ...intent } = input;
655
+ if (operation === 'read') {
656
+ if (!hasPinnedGitHubContext(context) ||
657
+ typeof intent.stableId !== 'string' ||
658
+ intent.stableId.length === 0 ||
659
+ typeof intent.stableNodeId !== 'string' ||
660
+ intent.stableNodeId.length === 0 ||
661
+ typeof intent.capabilityEvidenceDigest !== 'string' ||
662
+ intent.capabilityEvidenceDigest.length === 0 ||
663
+ typeof intent.stepId !== 'string' ||
664
+ intent.stepId.length === 0) {
665
+ throw new Error('GitHub read plan requires pinned identity and capability.');
666
+ }
667
+ const readIntent = {
668
+ stableId: intent.stableId,
669
+ stableNodeId: intent.stableNodeId,
670
+ currentAlias: typeof intent.currentAlias === 'string' ? intent.currentAlias : null,
671
+ capabilityEvidenceDigest: intent.capabilityEvidenceDigest,
672
+ stepId: intent.stepId,
673
+ };
674
+ return {
675
+ provider: 'github',
676
+ operation: 'read',
677
+ context,
678
+ intent: {
679
+ ...readIntent,
680
+ actionDigest: semanticDigest({
681
+ provider: 'github',
682
+ operation: 'read',
683
+ context,
684
+ ...readIntent,
685
+ }),
686
+ },
687
+ };
688
+ }
689
+ return {
690
+ provider: 'github',
691
+ operation,
692
+ context: isProviderContext(context) ? context : {},
693
+ intent,
694
+ };
695
+ },
696
+ validateObservation(action, observation) {
697
+ if (isSpecializedObservationOperation(action.operation)) {
698
+ return {
699
+ valid: false,
700
+ reasons: [specializedObservationValidatorReason(action.operation)],
701
+ };
702
+ }
703
+ if (action.provider !== 'github' ||
704
+ observation.provider !== 'github' ||
705
+ !contextsEqual(action.context, observation.context)) {
706
+ return { valid: false, reasons: ['observation-context-mismatch'] };
707
+ }
708
+ const hostCapability = parseHostCapability(observation.fields.hostCapability);
709
+ if (!hostCapability) {
710
+ return { valid: false, reasons: ['capability-evidence-missing'] };
711
+ }
712
+ const capability = validateGitHubHostCapability({
713
+ operation: action.operation,
714
+ context: action.context,
715
+ requiredFields: action.operation === 'read' ? requiredFieldsForRead() : [],
716
+ }, hostCapability);
717
+ if (!capability.valid)
718
+ return capability;
719
+ if (typeof action.intent.capabilityEvidenceDigest !== 'string' ||
720
+ observation.capabilityEvidenceDigest !==
721
+ action.intent.capabilityEvidenceDigest ||
722
+ hostCapability.evidenceDigest !== action.intent.capabilityEvidenceDigest) {
723
+ return { valid: false, reasons: ['capability-evidence-mismatch'] };
724
+ }
725
+ if (['create', 'update', 'transition', 'annotate'].includes(action.operation)) {
726
+ if (!validMutationActionEvidence(action)) {
727
+ return { valid: false, reasons: ['action-evidence-invalid'] };
728
+ }
729
+ if (!semanticValuesEqual(observation.fields.mutationEvidence, action.intent.executionEvidence)) {
730
+ return { valid: false, reasons: ['mutation-evidence-mismatch'] };
731
+ }
732
+ if (action.operation === 'create' &&
733
+ !semanticValuesEqual(observation.fields.createProvenance, action.intent.provenance)) {
734
+ return { valid: false, reasons: ['create-provenance-mismatch'] };
735
+ }
736
+ let issue;
737
+ try {
738
+ issue = normalizeGitHubIssueObservation(observation);
739
+ }
740
+ catch {
741
+ return { valid: false, reasons: ['observation-invalid'] };
742
+ }
743
+ const nodeId = String(observation.fields.nodeId ?? '');
744
+ if (!observationIdentityMatches(observation.identity.stableId, issue.stableId, nodeId) ||
745
+ (action.operation !== 'create' &&
746
+ issue.stableId !== action.intent.stableId)) {
747
+ return { valid: false, reasons: ['observation-identity-mismatch'] };
748
+ }
749
+ }
750
+ else if (action.operation === 'read') {
751
+ if (!validReadActionEvidence(action)) {
752
+ return { valid: false, reasons: ['action-evidence-invalid'] };
753
+ }
754
+ let issue;
755
+ try {
756
+ issue = normalizeGitHubIssueObservation(observation);
757
+ }
758
+ catch {
759
+ return { valid: false, reasons: ['observation-invalid'] };
760
+ }
761
+ if (issue.stableId !== action.intent.stableId ||
762
+ observation.fields.nodeId !== action.intent.stableNodeId) {
763
+ return { valid: false, reasons: ['observation-identity-mismatch'] };
764
+ }
765
+ }
766
+ return { valid: true, reasons: [] };
767
+ },
768
+ verificationFields(action) {
769
+ if (isSpecializedObservationOperation(action.operation)) {
770
+ return [specializedObservationValidationField(action.operation)];
771
+ }
772
+ return Object.keys(parsePostconditions(action.intent.postconditions));
773
+ },
774
+ verify(action, issue) {
775
+ if (isSpecializedObservationOperation(action.operation)) {
776
+ return [
777
+ {
778
+ field: specializedObservationValidationField(action.operation),
779
+ status: 'unavailable',
780
+ },
781
+ ];
782
+ }
783
+ const fields = safeVerificationFields(action);
784
+ if (!validMutationActionEvidence(action) ||
785
+ issue.provider !== 'github' ||
786
+ !contextsEqual(action.context, issue.context) ||
787
+ (action.operation !== 'create' &&
788
+ issue.stableId !== action.intent.stableId) ||
789
+ issue.extensions.capabilityEvidenceDigest !==
790
+ action.intent.capabilityEvidenceDigest ||
791
+ !semanticValuesEqual(issue.extensions.mutationEvidence, action.intent.executionEvidence) ||
792
+ (action.operation === 'create' &&
793
+ !semanticValuesEqual(issue.extensions.createProvenance, action.intent.provenance))) {
794
+ return fields.map((field) => ({
795
+ field,
796
+ status: 'unavailable',
797
+ }));
798
+ }
799
+ const postconditions = parsePostconditions(action.intent.postconditions);
800
+ return Object.entries(postconditions).map(([field, expected]) => {
801
+ const observed = field === 'description'
802
+ ? issue.description
803
+ : field === 'status'
804
+ ? issue.status
805
+ : field === 'annotation'
806
+ ? issue.extensions.annotations
807
+ : issue[field];
808
+ return {
809
+ field,
810
+ status: field === 'annotation' && Array.isArray(observed)
811
+ ? observed.includes(expected)
812
+ ? 'verified'
813
+ : 'mismatch'
814
+ : observed === expected
815
+ ? 'verified'
816
+ : 'mismatch',
817
+ };
818
+ });
819
+ },
820
+ };
821
+ function requiredFieldsForRead() {
822
+ return ['stable-identity', 'title', 'state', 'revision'];
823
+ }
824
+ function isSpecializedObservationOperation(operation) {
825
+ return ['search-duplicates', 'read-discussion'].includes(operation);
826
+ }
827
+ function specializedObservationValidatorReason(operation) {
828
+ return operation === 'search-duplicates'
829
+ ? 'duplicate-search-observation-validator-required'
830
+ : 'discussion-read-observation-validator-required';
831
+ }
832
+ function specializedObservationValidationField(operation) {
833
+ return operation === 'search-duplicates'
834
+ ? 'duplicate-search-observation-validation'
835
+ : 'discussion-read-observation-validation';
836
+ }
837
+ function unavailableReadResult(classification, reasons) {
838
+ return {
839
+ classification,
840
+ issue: null,
841
+ freshness: null,
842
+ preservePriorEvidence: true,
843
+ reasons,
844
+ };
845
+ }
846
+ function assertMutationIdentity(input) {
847
+ if (!input.bindingId)
848
+ throw new Error('GitHub mutation requires a binding ID.');
849
+ if (input.operation === 'create') {
850
+ if (input.provenance?.bindingId !== input.bindingId ||
851
+ !input.provenance.origin) {
852
+ throw new Error('GitHub create requires explicit binding provenance.');
853
+ }
854
+ }
855
+ else if (!input.stableId) {
856
+ throw new Error('GitHub mutation requires stable identity.');
857
+ }
858
+ }
859
+ function mutationResult(classification, reason, fields = []) {
860
+ return { classification, reason, fields, retryAllowed: false };
861
+ }
862
+ function parsePostconditions(value) {
863
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
864
+ throw new Error('GitHub mutation requires explicit postconditions.');
865
+ }
866
+ const entries = Object.entries(value);
867
+ const allowed = new Set([
868
+ 'title',
869
+ 'description',
870
+ 'priority',
871
+ 'status',
872
+ 'annotation',
873
+ ]);
874
+ if (entries.length === 0 ||
875
+ entries.some(([field, expected]) => !allowed.has(field) ||
876
+ (expected !== null && typeof expected !== 'string'))) {
877
+ throw new Error('GitHub mutation postconditions are unsupported.');
878
+ }
879
+ return Object.fromEntries(entries);
880
+ }
881
+ function verifyMutationField(observation, field, expected) {
882
+ const observed = field === 'description'
883
+ ? observation.fields.body
884
+ : field === 'status'
885
+ ? observation.fields.state
886
+ : field === 'annotation'
887
+ ? observation.fields.annotations
888
+ : observation.fields[field];
889
+ if (field === 'annotation') {
890
+ if (!Array.isArray(observed))
891
+ return 'unavailable';
892
+ return observed.includes(expected) ? 'verified' : 'mismatch';
893
+ }
894
+ if (observed === undefined)
895
+ return 'unavailable';
896
+ return observed === expected ? 'verified' : 'mismatch';
897
+ }
898
+ function normalizeMutationFieldMask(operation, fieldMask) {
899
+ const fieldsByOperation = {
900
+ create: ['title', 'description', 'priority'],
901
+ update: ['title', 'description', 'priority'],
902
+ transition: ['status'],
903
+ annotate: ['annotation'],
904
+ };
905
+ const ordered = fieldsByOperation[operation];
906
+ const allowed = new Set(ordered);
907
+ if (fieldMask.length === 0 ||
908
+ new Set(fieldMask).size !== fieldMask.length ||
909
+ fieldMask.some((field) => !allowed.has(field))) {
910
+ throw new Error('Unsupported GitHub mutation field.');
911
+ }
912
+ return ordered.filter((field) => fieldMask.includes(field));
913
+ }
914
+ function buildMutationProjection(input, fieldMask) {
915
+ const projectionKeys = Object.keys(input.projection);
916
+ if (projectionKeys.length !== fieldMask.length ||
917
+ projectionKeys.some((key) => !fieldMask.includes(key))) {
918
+ throw new Error('GitHub mutation projection must exactly match its field mask.');
919
+ }
920
+ const projection = {};
921
+ if (fieldMask.includes('title')) {
922
+ if (typeof input.projection.title !== 'string')
923
+ throw new Error('GitHub title projection must be a string.');
924
+ projection.title = input.projection.title;
925
+ }
926
+ if (fieldMask.includes('priority')) {
927
+ if (input.projection.priority !== null &&
928
+ !['low', 'medium', 'high', 'urgent'].includes(String(input.projection.priority))) {
929
+ throw new Error('Unsupported GitHub priority.');
930
+ }
931
+ projection.priority = input.projection.priority;
932
+ }
933
+ if (fieldMask.includes('description')) {
934
+ projection.description = buildDescriptionProjection(input);
935
+ }
936
+ if (fieldMask.includes('status')) {
937
+ if (!['open', 'closed'].includes(String(input.projection.status))) {
938
+ throw new Error('Unsupported GitHub status transition.');
939
+ }
940
+ projection.status = input.projection.status;
941
+ }
942
+ if (fieldMask.includes('annotation')) {
943
+ if (typeof input.projection.annotation !== 'string' ||
944
+ input.projection.annotation.length === 0) {
945
+ throw new Error('GitHub annotation projection must be text.');
946
+ }
947
+ projection.annotation = input.projection.annotation;
948
+ }
949
+ return projection;
950
+ }
951
+ function buildDescriptionProjection(input) {
952
+ if (input.descriptionMode === 'none') {
953
+ throw new Error('GitHub description updates are disabled by policy.');
954
+ }
955
+ const description = input.projection.description;
956
+ if (input.descriptionMode === 'replace') {
957
+ if (description !== null && typeof description !== 'string') {
958
+ throw new Error('GitHub description projection must be nullable text.');
959
+ }
960
+ return description ?? null;
961
+ }
962
+ if (typeof description !== 'string') {
963
+ throw new Error('Managed GitHub description content must be text.');
964
+ }
965
+ const currentBody = input.currentBody ?? '';
966
+ const inspection = inspectManagedMarkdown(currentBody, input.bindingId);
967
+ const update = inspection.status === 'absent'
968
+ ? insertManagedMarkdown(currentBody, input.bindingId, description)
969
+ : inspection.status === 'managed'
970
+ ? replaceManagedMarkdown(currentBody, input.bindingId, description)
971
+ : inspection;
972
+ if (update.status !== 'updated') {
973
+ throw new Error(`GitHub managed body requires choice: ${update.reason}.`);
974
+ }
975
+ return update.body;
976
+ }
977
+ function isCompleteMutationInput(operation, input) {
978
+ return (['create', 'update', 'transition', 'annotate'].includes(operation) &&
979
+ input.operation === operation &&
980
+ hasPinnedGitHubContext(input.context) &&
981
+ typeof input.bindingId === 'string' &&
982
+ Array.isArray(input.fieldMask) &&
983
+ !!input.projection &&
984
+ typeof input.projection === 'object' &&
985
+ !!input.outboundSafety &&
986
+ typeof input.outboundSafety === 'object' &&
987
+ !!input.hostCapability &&
988
+ typeof input.hostCapability === 'object' &&
989
+ !!input.publicationSafety &&
990
+ typeof input.publicationSafety === 'object' &&
991
+ typeof input.approvedPreviewDigest === 'string');
992
+ }
993
+ function isCompleteDuplicateSearchInput(input) {
994
+ return (hasPinnedGitHubContext(input.context) &&
995
+ !!parseHostCapability(input.hostCapability) &&
996
+ typeof input.provenanceToken === 'string' &&
997
+ input.provenanceToken.length > 0 &&
998
+ typeof input.reservedBindingId === 'string' &&
999
+ input.reservedBindingId.length > 0 &&
1000
+ Array.isArray(input.historicalAliases) &&
1001
+ input.historicalAliases.every((alias) => typeof alias === 'string') &&
1002
+ Number.isInteger(input.maxResults));
1003
+ }
1004
+ function isCompleteDiscussionReadInput(input) {
1005
+ return (hasPinnedGitHubContext(input.context) &&
1006
+ !!parseHostCapability(input.hostCapability) &&
1007
+ typeof input.stableId === 'string' &&
1008
+ input.stableId.length > 0 &&
1009
+ ['comments', 'activity'].includes(String(input.evidenceKind)) &&
1010
+ (input.cursor === null || typeof input.cursor === 'string') &&
1011
+ Number.isInteger(input.limit));
1012
+ }
1013
+ function validMutationActionEvidence(action) {
1014
+ try {
1015
+ const fieldMask = action.intent.fieldMask;
1016
+ const projection = recordValue(action.intent.projection, 'GitHub action projection is missing.');
1017
+ const postconditions = recordValue(action.intent.postconditions, 'GitHub action postconditions are missing.');
1018
+ const execution = recordValue(action.intent.executionEvidence, 'GitHub action execution evidence is missing.');
1019
+ if (!['create', 'update', 'transition', 'annotate'].includes(action.operation) ||
1020
+ typeof action.intent.bindingId !== 'string') {
1021
+ return false;
1022
+ }
1023
+ const operation = action.operation;
1024
+ const normalizedMask = normalizeMutationFieldMask(operation, fieldMask);
1025
+ if (!semanticValuesEqual(normalizedMask, fieldMask) ||
1026
+ !semanticValuesEqual(Object.keys(projection), fieldMask)) {
1027
+ return false;
1028
+ }
1029
+ if (operation === 'create') {
1030
+ const provenance = recordValue(action.intent.provenance, 'GitHub create provenance is missing.');
1031
+ if (action.intent.stableId !== null ||
1032
+ provenance.bindingId !== action.intent.bindingId ||
1033
+ typeof provenance.origin !== 'string' ||
1034
+ provenance.origin.length === 0) {
1035
+ return false;
1036
+ }
1037
+ }
1038
+ else if (typeof action.intent.stableId !== 'string') {
1039
+ return false;
1040
+ }
1041
+ parsePostconditions(postconditions);
1042
+ if (!Array.isArray(fieldMask) ||
1043
+ !semanticValuesEqual(projection, postconditions) ||
1044
+ action.intent.previewDigest !== action.intent.approvalDigest ||
1045
+ action.intent.previewDigest !== execution.previewDigest ||
1046
+ action.intent.approvalDigest !== execution.approvalDigest ||
1047
+ action.intent.actionDigest !== execution.actionDigest ||
1048
+ action.intent.capabilityEvidenceDigest !==
1049
+ execution.capabilityEvidenceDigest) {
1050
+ return false;
1051
+ }
1052
+ const outboundSafety = recordValue(action.intent.outboundSafety, 'GitHub outbound evidence is missing.');
1053
+ const publicationSafety = recordValue(action.intent.publicationSafety, 'GitHub publication evidence is missing.');
1054
+ if (outboundSafety.projectionDigest !== execution.projectionDigest ||
1055
+ outboundSafety.resultDigest !== execution.outboundSafetyResultDigest ||
1056
+ publicationSafety.resultDigest !==
1057
+ execution.publicationSafetyResultDigest ||
1058
+ publicationSafety.visibilityEvidenceDigest !==
1059
+ execution.visibilityEvidenceDigest ||
1060
+ semanticDigest(projection) !== execution.projectionDigest) {
1061
+ return false;
1062
+ }
1063
+ const executionBase = {
1064
+ capabilityEvidenceDigest: execution.capabilityEvidenceDigest,
1065
+ projectionDigest: execution.projectionDigest,
1066
+ outboundSafetyResultDigest: execution.outboundSafetyResultDigest,
1067
+ publicationSafetyResultDigest: execution.publicationSafetyResultDigest,
1068
+ visibilityEvidenceDigest: execution.visibilityEvidenceDigest,
1069
+ };
1070
+ const expectedPreview = semanticDigest({
1071
+ provider: 'github',
1072
+ operation: action.operation,
1073
+ context: action.context,
1074
+ bindingId: action.intent.bindingId,
1075
+ stableId: action.intent.stableId,
1076
+ provenance: action.intent.provenance,
1077
+ fieldMask,
1078
+ projection,
1079
+ postconditions,
1080
+ executionEvidence: executionBase,
1081
+ });
1082
+ if (expectedPreview !== action.intent.previewDigest)
1083
+ return false;
1084
+ return (semanticDigest({
1085
+ provider: 'github',
1086
+ operation: action.operation,
1087
+ context: action.context,
1088
+ previewDigest: action.intent.previewDigest,
1089
+ approvalDigest: action.intent.approvalDigest,
1090
+ executionEvidence: executionBase,
1091
+ }) === action.intent.actionDigest);
1092
+ }
1093
+ catch {
1094
+ return false;
1095
+ }
1096
+ }
1097
+ function validReadActionEvidence(action) {
1098
+ if (action.provider !== 'github' ||
1099
+ action.operation !== 'read' ||
1100
+ !hasPinnedGitHubContext(action.context) ||
1101
+ typeof action.intent.stableId !== 'string' ||
1102
+ action.intent.stableId.length === 0 ||
1103
+ typeof action.intent.stableNodeId !== 'string' ||
1104
+ action.intent.stableNodeId.length === 0 ||
1105
+ typeof action.intent.capabilityEvidenceDigest !== 'string' ||
1106
+ action.intent.capabilityEvidenceDigest.length === 0 ||
1107
+ typeof action.intent.stepId !== 'string' ||
1108
+ action.intent.stepId.length === 0 ||
1109
+ typeof action.intent.actionDigest !== 'string') {
1110
+ return false;
1111
+ }
1112
+ return (semanticDigest({
1113
+ provider: 'github',
1114
+ operation: 'read',
1115
+ context: action.context,
1116
+ stableId: action.intent.stableId,
1117
+ stableNodeId: action.intent.stableNodeId,
1118
+ currentAlias: typeof action.intent.currentAlias === 'string'
1119
+ ? action.intent.currentAlias
1120
+ : null,
1121
+ capabilityEvidenceDigest: action.intent.capabilityEvidenceDigest,
1122
+ stepId: action.intent.stepId,
1123
+ }) === action.intent.actionDigest);
1124
+ }
1125
+ function validDuplicateSearchAction(action) {
1126
+ try {
1127
+ const query = recordValue(action.intent.query, 'GitHub duplicate query contract is missing.');
1128
+ const repository = recordValue(query.repository, 'GitHub duplicate repository contract is missing.');
1129
+ const resultContract = recordValue(action.intent.resultContract, 'GitHub duplicate result contract is missing.');
1130
+ const aliases = query.historicalAliases;
1131
+ return (action.provider === 'github' &&
1132
+ action.operation === 'search-duplicates' &&
1133
+ hasPinnedGitHubContext(action.context) &&
1134
+ typeof query.provenanceToken === 'string' &&
1135
+ query.provenanceToken.length > 0 &&
1136
+ typeof query.reservedBindingId === 'string' &&
1137
+ query.reservedBindingId.length > 0 &&
1138
+ Array.isArray(aliases) &&
1139
+ aliases.length <= 64 &&
1140
+ aliases.every((alias) => typeof alias === 'string' && alias.length > 0) &&
1141
+ new Set(aliases).size === aliases.length &&
1142
+ semanticValuesEqual(repository, {
1143
+ host: action.context.host,
1144
+ repositoryId: action.context.repositoryId,
1145
+ owner: action.context.owner,
1146
+ name: action.context.name,
1147
+ }) &&
1148
+ action.intent.queryDigest === semanticDigest(query) &&
1149
+ typeof action.intent.capabilityEvidenceDigest === 'string' &&
1150
+ action.intent.capabilityEvidenceDigest.length > 0 &&
1151
+ Number.isInteger(resultContract.maxResults) &&
1152
+ Number(resultContract.maxResults) >= 1 &&
1153
+ Number(resultContract.maxResults) <= 100 &&
1154
+ semanticValuesEqual(resultContract.classifications, [
1155
+ 'no-match',
1156
+ 'one-match',
1157
+ 'ambiguous',
1158
+ ]) &&
1159
+ resultContract.matchStatus ===
1160
+ 'evidence-until-identity-and-context-verified');
1161
+ }
1162
+ catch {
1163
+ return false;
1164
+ }
1165
+ }
1166
+ function validDuplicateCandidateBounds(candidate) {
1167
+ return (typeof candidate.stableId === 'string' &&
1168
+ candidate.stableId.length > 0 &&
1169
+ isProviderContext(candidate.context) &&
1170
+ ['provenance', 'reserved-binding', 'alias'].includes(candidate.matchedBy) &&
1171
+ typeof candidate.stableIdentityVerified === 'boolean' &&
1172
+ typeof candidate.contextVerified === 'boolean' &&
1173
+ Array.isArray(candidate.aliases) &&
1174
+ candidate.aliases.length <= 64 &&
1175
+ candidate.aliases.every((alias) => typeof alias === 'string' && alias.length > 0) &&
1176
+ Array.isArray(candidate.historicalRepositoryIds) &&
1177
+ candidate.historicalRepositoryIds.length <= 64 &&
1178
+ candidate.historicalRepositoryIds.every((repositoryId) => typeof repositoryId === 'string' && repositoryId.length > 0));
1179
+ }
1180
+ function duplicateObservationAttributedToAction(input) {
1181
+ return (input.action.intent.capabilityEvidenceDigest ===
1182
+ input.hostCapability.evidenceDigest &&
1183
+ input.observation.provider === 'github' &&
1184
+ contextsEqual(input.action.context, input.observation.context) &&
1185
+ input.observation.capabilityEvidenceDigest ===
1186
+ input.action.intent.capabilityEvidenceDigest &&
1187
+ input.observation.queryDigest === input.action.intent.queryDigest &&
1188
+ Number.isFinite(Date.parse(input.observation.observedAt)));
1189
+ }
1190
+ function duplicateCandidateAttributedToAction(candidate, input) {
1191
+ const planned = input.action.context;
1192
+ const current = candidate.context;
1193
+ const plannedHost = planned.host;
1194
+ if (!hasPinnedGitHubContext(current) ||
1195
+ typeof plannedHost !== 'string' ||
1196
+ current.host !== plannedHost ||
1197
+ current.accountId !== planned.accountId ||
1198
+ !candidate.stableId.startsWith(`github:${plannedHost.toLowerCase()}:`) ||
1199
+ candidate.stableId === `github:${plannedHost.toLowerCase()}:`) {
1200
+ return false;
1201
+ }
1202
+ if (current.repositoryId === planned.repositoryId) {
1203
+ return current.owner === planned.owner && current.name === planned.name;
1204
+ }
1205
+ return validDuplicateTransferEvidence(candidate.transferEvidence, {
1206
+ stableId: candidate.stableId,
1207
+ fromContext: planned,
1208
+ toContext: current,
1209
+ capabilityEvidenceDigest: String(input.action.intent.capabilityEvidenceDigest),
1210
+ queryDigest: String(input.action.intent.queryDigest),
1211
+ observedAt: input.observation.observedAt,
1212
+ });
1213
+ }
1214
+ function validDuplicateTransferEvidence(value, expected) {
1215
+ if (!value ||
1216
+ value.provider !== 'github' ||
1217
+ value.stableId !== expected.stableId ||
1218
+ !contextsEqual(value.fromContext, expected.fromContext) ||
1219
+ !contextsEqual(value.toContext, expected.toContext) ||
1220
+ value.capabilityEvidenceDigest !== expected.capabilityEvidenceDigest ||
1221
+ value.queryDigest !== expected.queryDigest ||
1222
+ value.observedAt !== expected.observedAt ||
1223
+ !Number.isFinite(Date.parse(value.observedAt))) {
1224
+ return false;
1225
+ }
1226
+ const { evidenceDigest, ...content } = value;
1227
+ return evidenceDigest === semanticDigest(content);
1228
+ }
1229
+ function validDiscussionReadAction(action) {
1230
+ try {
1231
+ const contract = recordValue(action.intent.resultContract, 'GitHub discussion result contract is missing.');
1232
+ return (action.provider === 'github' &&
1233
+ action.operation === 'read-discussion' &&
1234
+ hasPinnedGitHubContext(action.context) &&
1235
+ typeof action.intent.stableId === 'string' &&
1236
+ action.intent.stableId.length > 0 &&
1237
+ ['comments', 'activity'].includes(String(action.intent.evidenceKind)) &&
1238
+ (action.intent.cursor === null ||
1239
+ typeof action.intent.cursor === 'string') &&
1240
+ validDiscussionCursor(action.intent.cursor) &&
1241
+ Number.isInteger(action.intent.limit) &&
1242
+ Number(action.intent.limit) >= 1 &&
1243
+ Number(action.intent.limit) <= 100 &&
1244
+ typeof action.intent.capabilityEvidenceDigest === 'string' &&
1245
+ action.intent.capabilityEvidenceDigest.length > 0 &&
1246
+ contract.maxItems === action.intent.limit &&
1247
+ Number.isInteger(contract.maxItems) &&
1248
+ Number(contract.maxItems) >= 1 &&
1249
+ Number(contract.maxItems) <= 100 &&
1250
+ contract.content === 'sanitized-non-persistent-evidence');
1251
+ }
1252
+ catch {
1253
+ return false;
1254
+ }
1255
+ }
1256
+ function parseMutationExecutionEvidence(value) {
1257
+ if (!value || typeof value !== 'object' || Array.isArray(value))
1258
+ return null;
1259
+ const evidence = value;
1260
+ const fields = [
1261
+ 'capabilityEvidenceDigest',
1262
+ 'projectionDigest',
1263
+ 'outboundSafetyResultDigest',
1264
+ 'publicationSafetyResultDigest',
1265
+ 'visibilityEvidenceDigest',
1266
+ 'previewDigest',
1267
+ 'approvalDigest',
1268
+ 'actionDigest',
1269
+ ];
1270
+ if (Object.keys(evidence).length !== fields.length ||
1271
+ fields.some((field) => typeof evidence[field] !== 'string' ||
1272
+ String(evidence[field]).length === 0)) {
1273
+ return null;
1274
+ }
1275
+ return Object.fromEntries(fields.map((field) => [field, String(evidence[field])]));
1276
+ }
1277
+ function parseCreateProvenance(value) {
1278
+ if (!value || typeof value !== 'object' || Array.isArray(value))
1279
+ return null;
1280
+ const provenance = value;
1281
+ if (Object.keys(provenance).length !== 2 ||
1282
+ typeof provenance.bindingId !== 'string' ||
1283
+ provenance.bindingId.length === 0 ||
1284
+ typeof provenance.origin !== 'string' ||
1285
+ provenance.origin.length === 0) {
1286
+ return null;
1287
+ }
1288
+ return {
1289
+ bindingId: provenance.bindingId,
1290
+ origin: provenance.origin,
1291
+ };
1292
+ }
1293
+ function safeVerificationFields(action) {
1294
+ try {
1295
+ return Object.keys(parsePostconditions(action.intent.postconditions));
1296
+ }
1297
+ catch {
1298
+ return [];
1299
+ }
1300
+ }
1301
+ function observationIdentityMatches(declared, canonical, nodeId) {
1302
+ return declared === canonical || declared === nodeId;
1303
+ }
1304
+ function canonicalGitHubStableId(host, nodeId) {
1305
+ return `github:${host.toLowerCase()}:${nodeId}`;
1306
+ }
1307
+ function validTransferEvidence(value, stableId, fromRepositoryId, toRepositoryId, capabilityEvidenceDigest, observedAt) {
1308
+ if (!value || typeof value !== 'object' || Array.isArray(value))
1309
+ return false;
1310
+ const evidence = value;
1311
+ if (evidence.provider !== 'github' ||
1312
+ evidence.stableId !== stableId ||
1313
+ evidence.fromRepositoryId !== fromRepositoryId ||
1314
+ evidence.toRepositoryId !== toRepositoryId ||
1315
+ evidence.capabilityEvidenceDigest !== capabilityEvidenceDigest ||
1316
+ evidence.observedAt !== observedAt) {
1317
+ return false;
1318
+ }
1319
+ const { evidenceDigest, ...content } = evidence;
1320
+ return evidenceDigest === semanticDigest(content);
1321
+ }
1322
+ function validDeletionEvidence(value, action, capabilityEvidenceDigest, currentObservedAt) {
1323
+ if (!value || value.provider !== 'github')
1324
+ return false;
1325
+ if (value.stableId !== action.intent.stableId ||
1326
+ !contextsEqual(value.context, action.context) ||
1327
+ value.capabilityEvidenceDigest !== capabilityEvidenceDigest ||
1328
+ value.actionDigest !== action.intent.actionDigest ||
1329
+ value.stepId !== action.intent.stepId ||
1330
+ !Number.isFinite(Date.parse(value.observedAt))) {
1331
+ return false;
1332
+ }
1333
+ const age = Date.parse(currentObservedAt) - Date.parse(value.observedAt);
1334
+ if (age < 0 || age > MAX_DELETION_EVIDENCE_AGE_MS)
1335
+ return false;
1336
+ return (semanticDigest({
1337
+ provider: value.provider,
1338
+ stableId: value.stableId,
1339
+ context: value.context,
1340
+ capabilityEvidenceDigest: value.capabilityEvidenceDigest,
1341
+ actionDigest: value.actionDigest,
1342
+ stepId: value.stepId,
1343
+ observedAt: value.observedAt,
1344
+ }) === value.evidenceDigest);
1345
+ }
1346
+ function semanticValuesEqual(left, right) {
1347
+ if (left === undefined || right === undefined)
1348
+ return false;
1349
+ try {
1350
+ return semanticDigest(left) === semanticDigest(right);
1351
+ }
1352
+ catch {
1353
+ return false;
1354
+ }
1355
+ }
1356
+ function isProviderContext(value) {
1357
+ return (!!value &&
1358
+ typeof value === 'object' &&
1359
+ !Array.isArray(value) &&
1360
+ Object.values(value).every((entry) => entry === undefined || typeof entry === 'string'));
1361
+ }
1362
+ function hasPinnedGitHubContext(value) {
1363
+ return (isProviderContext(value) &&
1364
+ ['host', 'accountId', 'repositoryId', 'owner', 'name'].every((key) => typeof value[key] === 'string' && value[key].length > 0));
1365
+ }
1366
+ function parseHostCapability(value) {
1367
+ if (!value || typeof value !== 'object' || Array.isArray(value))
1368
+ return null;
1369
+ const candidate = value;
1370
+ if (candidate.provider !== 'github' ||
1371
+ !isProviderContext(candidate.context) ||
1372
+ ![
1373
+ 'available',
1374
+ 'unavailable',
1375
+ 'authorization-required',
1376
+ 'rate-limited',
1377
+ ].includes(String(candidate.availability)) ||
1378
+ typeof candidate.accountId !== 'string' ||
1379
+ typeof candidate.repositoryId !== 'string' ||
1380
+ !Array.isArray(candidate.operations) ||
1381
+ !Array.isArray(candidate.observableFields) ||
1382
+ typeof candidate.evidenceDigest !== 'string') {
1383
+ return null;
1384
+ }
1385
+ return candidate;
1386
+ }
1387
+ function assertGitHubObservation(observation) {
1388
+ if (observation.provider !== 'github') {
1389
+ throw new Error('GitHub adapter requires a GitHub observation.');
1390
+ }
1391
+ }
1392
+ function requiredContext(observation, key) {
1393
+ const value = observation.context[key];
1394
+ if (!value)
1395
+ throw new Error(`GitHub context requires '${key}'.`);
1396
+ return value;
1397
+ }
1398
+ function requiredContextValue(context, key) {
1399
+ const value = context[key];
1400
+ if (!value)
1401
+ throw new Error(`GitHub context requires '${key}'.`);
1402
+ return value;
1403
+ }
1404
+ function duplicateValidationResult(accepted, classification, stableId, reasons) {
1405
+ return { accepted, classification, stableId, reasons };
1406
+ }
1407
+ function recordValue(value, message) {
1408
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
1409
+ throw new Error(message);
1410
+ }
1411
+ return value;
1412
+ }
1413
+ function validDiscussionCursor(cursor) {
1414
+ return (cursor === null ||
1415
+ (cursor.length > 0 &&
1416
+ cursor.length <= 512 &&
1417
+ [...cursor].every((character) => {
1418
+ const code = character.charCodeAt(0);
1419
+ return code >= 32 && code !== 127;
1420
+ })));
1421
+ }
1422
+ function discussionResult(classification, page, reasons) {
1423
+ return { classification, page, persistable: false, reasons };
1424
+ }
1425
+ function requiredString(fields, key) {
1426
+ const value = fields[key];
1427
+ if (typeof value !== 'string' || value.trim().length === 0) {
1428
+ throw new Error(`GitHub observation requires string field '${key}'.`);
1429
+ }
1430
+ return value;
1431
+ }
1432
+ function nullableString(fields, key) {
1433
+ const value = fields[key];
1434
+ if (value === null)
1435
+ return null;
1436
+ if (typeof value !== 'string') {
1437
+ throw new Error(`GitHub observation requires nullable string '${key}'.`);
1438
+ }
1439
+ return value;
1440
+ }
1441
+ function requiredPositiveInteger(fields, key) {
1442
+ const value = fields[key];
1443
+ if (!Number.isSafeInteger(value) || Number(value) <= 0) {
1444
+ throw new Error(`GitHub observation requires positive integer '${key}'.`);
1445
+ }
1446
+ return Number(value);
1447
+ }
1448
+ function uniqueStrings(value) {
1449
+ if (!Array.isArray(value) || value.some((item) => typeof item !== 'string')) {
1450
+ throw new Error('GitHub aliases and pull requests must be string arrays.');
1451
+ }
1452
+ return [...new Set(value)];
1453
+ }
1454
+ function optionalStringArray(value) {
1455
+ return value === undefined ? [] : uniqueStrings(value);
1456
+ }