@vercel/factory 0.0.17 → 0.0.19

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 (186) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/dist/api-contracts.d.mts +665 -715
  3. package/dist/api-contracts.mjs +91 -4
  4. package/dist/api-contracts.mjs.map +1 -1
  5. package/dist/api-task-graph.mjs +14 -5
  6. package/dist/api-task-graph.mjs.map +1 -1
  7. package/dist/api.d.mts +58 -11
  8. package/dist/api.mjs +715 -399
  9. package/dist/api.mjs.map +1 -1
  10. package/dist/approval-access.d.mts +1 -1
  11. package/dist/approval-contracts.d.mts +3 -4
  12. package/dist/approval-contracts.mjs +1 -2
  13. package/dist/approval-contracts.mjs.map +1 -1
  14. package/dist/blob/index.d.mts +1 -1
  15. package/dist/budget.mjs +20 -3
  16. package/dist/budget.mjs.map +1 -1
  17. package/dist/change-verification/brief.d.mts +1 -1
  18. package/dist/change-verification/dispatch.mjs +8 -1
  19. package/dist/change-verification/dispatch.mjs.map +1 -1
  20. package/dist/changes/eve-record-change.mjs +4 -1
  21. package/dist/changes/eve-record-change.mjs.map +1 -1
  22. package/dist/client-stream.d.mts +19 -2
  23. package/dist/client-stream.mjs +115 -53
  24. package/dist/client-stream.mjs.map +1 -1
  25. package/dist/client-transcript.d.mts +25 -2
  26. package/dist/client-transcript.mjs +95 -13
  27. package/dist/client-transcript.mjs.map +1 -1
  28. package/dist/client.d.mts +145 -19
  29. package/dist/client.mjs +89 -16
  30. package/dist/client.mjs.map +1 -1
  31. package/dist/code-review/contracts.d.mts +1 -0
  32. package/dist/code-review/eve-post-review.d.mts +4 -2
  33. package/dist/code-review/eve-post-review.mjs +8 -0
  34. package/dist/code-review/eve-post-review.mjs.map +1 -1
  35. package/dist/code-review/github-reporter.d.mts +6 -2
  36. package/dist/code-review/github-reporter.mjs +86 -35
  37. package/dist/code-review/github-reporter.mjs.map +1 -1
  38. package/dist/code-review/preparation.d.mts +2 -1
  39. package/dist/code-review/preparation.mjs +2 -1
  40. package/dist/code-review/preparation.mjs.map +1 -1
  41. package/dist/code-review/stall.mjs +11 -2
  42. package/dist/code-review/stall.mjs.map +1 -1
  43. package/dist/connectors/github.d.mts +1 -1
  44. package/dist/connectors/index.d.mts +1 -1
  45. package/dist/connectors/slack.d.mts +1 -1
  46. package/dist/delivery-metrics/metrics.d.mts +1 -1
  47. package/dist/dispatch.mjs +3 -3
  48. package/dist/dispatch.mjs.map +1 -1
  49. package/dist/eve/index.d.mts +1 -1
  50. package/dist/eve/index.mjs +8 -1
  51. package/dist/eve/index.mjs.map +1 -1
  52. package/dist/eve/task-execution.d.mts +4 -4
  53. package/dist/eve/transcript.d.mts +3 -1
  54. package/dist/eve/transcript.mjs.map +1 -1
  55. package/dist/finding-remediation/admission.d.mts +1 -1
  56. package/dist/finding-remediation/brief.d.mts +1 -1
  57. package/dist/github-app.d.mts +1 -1
  58. package/dist/github-publication.d.mts +1 -1
  59. package/dist/github.d.mts +9 -1
  60. package/dist/github.mjs +29 -11
  61. package/dist/github.mjs.map +1 -1
  62. package/dist/index.d.mts +1 -1
  63. package/dist/intake-contracts.d.mts +1 -0
  64. package/dist/intake-contracts.mjs +2 -1
  65. package/dist/intake-contracts.mjs.map +1 -1
  66. package/dist/integrations/github-issues.d.mts +64 -0
  67. package/dist/integrations/github-issues.mjs +175 -0
  68. package/dist/integrations/github-issues.mjs.map +1 -0
  69. package/dist/integrations/github.d.mts +2 -1
  70. package/dist/integrations/github.mjs +2 -1
  71. package/dist/multi-repository-changes/coordination.mjs +1 -1
  72. package/dist/planning/reconcile.mjs +15 -2
  73. package/dist/planning/reconcile.mjs.map +1 -1
  74. package/dist/postgres/index.d.mts +2 -1
  75. package/dist/postgres/index.mjs +11 -5
  76. package/dist/postgres/index.mjs.map +1 -1
  77. package/dist/postgres/indexed-codec.mjs +137 -0
  78. package/dist/postgres/indexed-codec.mjs.map +1 -0
  79. package/dist/postgres/indexed-schema.mjs +99 -0
  80. package/dist/postgres/indexed-schema.mjs.map +1 -0
  81. package/dist/postgres/indexed.d.mts +28 -0
  82. package/dist/postgres/indexed.mjs +277 -0
  83. package/dist/postgres/indexed.mjs.map +1 -0
  84. package/dist/postgres/record-predicates.mjs +23 -0
  85. package/dist/postgres/record-predicates.mjs.map +1 -0
  86. package/dist/presets/software-development/deepsec-brief.d.mts +1 -1
  87. package/dist/presets/software-development/dispatch.mjs +1 -1
  88. package/dist/presets/software-development/dispatch.mjs.map +1 -1
  89. package/dist/presets/software-development/recovery.mjs +9 -2
  90. package/dist/presets/software-development/recovery.mjs.map +1 -1
  91. package/dist/pull-requests/eve-fetch.d.mts +4 -1
  92. package/dist/pull-requests/eve-fetch.mjs +9 -0
  93. package/dist/pull-requests/eve-fetch.mjs.map +1 -1
  94. package/dist/pull-requests/github-publisher.d.mts +1 -1
  95. package/dist/pull-requests/github-publisher.mjs +19 -7
  96. package/dist/pull-requests/github-publisher.mjs.map +1 -1
  97. package/dist/repositories.mjs +20 -13
  98. package/dist/repositories.mjs.map +1 -1
  99. package/dist/sandbox/execution-run-ledger.d.mts +2 -2
  100. package/dist/sandbox/execution-run-ledger.mjs.map +1 -1
  101. package/dist/sandbox/execution.d.mts +2 -2
  102. package/dist/sandbox/execution.mjs.map +1 -1
  103. package/dist/schema/catalog.mjs +1 -1
  104. package/dist/schema/change.d.mts +1 -1
  105. package/dist/schema/communication.d.mts +2 -2
  106. package/dist/schema/factory-config.d.mts +1 -1
  107. package/dist/schema/outbox.d.mts +6 -6
  108. package/dist/schema/session-summary.d.mts +218 -0
  109. package/dist/schema/session-summary.mjs +68 -0
  110. package/dist/schema/session-summary.mjs.map +1 -0
  111. package/dist/schema/session.d.mts +39 -59
  112. package/dist/schema/session.mjs +6 -6
  113. package/dist/schema/session.mjs.map +1 -1
  114. package/dist/schema/task-graph.d.mts +24 -24
  115. package/dist/schema/transcript.d.mts +141 -3
  116. package/dist/schema/transcript.mjs +19 -3
  117. package/dist/schema/transcript.mjs.map +1 -1
  118. package/dist/schema/transitions.d.mts +1 -1
  119. package/dist/schema/transitions.mjs +1 -1
  120. package/dist/schema/transitions.mjs.map +1 -1
  121. package/dist/signals.d.mts +1 -1
  122. package/dist/stall.mjs +10 -2
  123. package/dist/stall.mjs.map +1 -1
  124. package/dist/storage.d.mts +10 -1
  125. package/dist/storage.mjs +7 -1
  126. package/dist/store/approvals.d.mts +14 -0
  127. package/dist/store/approvals.mjs +78 -0
  128. package/dist/store/approvals.mjs.map +1 -0
  129. package/dist/store/conversations.d.mts +59 -0
  130. package/dist/store/conversations.mjs +549 -0
  131. package/dist/store/conversations.mjs.map +1 -0
  132. package/dist/store/driver.d.mts +1 -1
  133. package/dist/store/driver.mjs.map +1 -1
  134. package/dist/store/engine.d.mts +38 -16
  135. package/dist/store/engine.mjs +229 -106
  136. package/dist/store/engine.mjs.map +1 -1
  137. package/dist/store/indexed-engine.d.mts +57 -0
  138. package/dist/store/indexed-engine.mjs +155 -0
  139. package/dist/store/indexed-engine.mjs.map +1 -0
  140. package/dist/store/indexed-memory.d.mts +7 -0
  141. package/dist/store/indexed-memory.mjs +200 -0
  142. package/dist/store/indexed-memory.mjs.map +1 -0
  143. package/dist/store/indexed-projections.d.mts +280 -0
  144. package/dist/store/indexed-projections.mjs +481 -0
  145. package/dist/store/indexed-projections.mjs.map +1 -0
  146. package/dist/store/indexed.d.mts +101 -0
  147. package/dist/store/indexed.mjs +19 -0
  148. package/dist/store/indexed.mjs.map +1 -0
  149. package/dist/store/iterate.d.mts +9 -0
  150. package/dist/store/iterate.mjs +23 -0
  151. package/dist/store/iterate.mjs.map +1 -0
  152. package/dist/store/query-scope.mjs +62 -0
  153. package/dist/store/query-scope.mjs.map +1 -0
  154. package/dist/store/query.d.mts +32 -0
  155. package/dist/store/query.mjs +103 -0
  156. package/dist/store/query.mjs.map +1 -0
  157. package/dist/store/session-history.mjs +24 -0
  158. package/dist/store/session-history.mjs.map +1 -0
  159. package/dist/store/slack-pr-notifications.d.mts +2 -2
  160. package/dist/store/slack-pr-notifications.mjs.map +1 -1
  161. package/dist/store/state.d.mts +26 -0
  162. package/dist/store/state.mjs +95 -0
  163. package/dist/store/state.mjs.map +1 -0
  164. package/dist/store/task-effects.d.mts +1 -1
  165. package/dist/store/task-graphs.d.mts +1 -1
  166. package/dist/store/task-messages.d.mts +1 -1
  167. package/dist/store/task-work.d.mts +1 -1
  168. package/dist/store/task-work.mjs +1 -1
  169. package/dist/store/transcripts.mjs +308 -0
  170. package/dist/store/transcripts.mjs.map +1 -0
  171. package/dist/sweep.d.mts +1 -1
  172. package/dist/sweep.mjs +12 -7
  173. package/dist/sweep.mjs.map +1 -1
  174. package/dist/task-communication.mjs +15 -1
  175. package/dist/task-communication.mjs.map +1 -1
  176. package/dist/task-protocols.d.mts +3 -3
  177. package/dist/tasks.d.mts +1 -1
  178. package/dist/vercel.d.mts +1 -1
  179. package/dist/work-triage/find-existing-work.d.mts +1 -1
  180. package/dist/work-triage/find-existing-work.mjs +12 -2
  181. package/dist/work-triage/find-existing-work.mjs.map +1 -1
  182. package/docs/index.md +4 -0
  183. package/docs/recipes/persistence-recovery.md +15 -9
  184. package/docs/recipes/postgres-indexed-engine.md +108 -0
  185. package/docs/recipes/postgres-indexed-storage.md +145 -0
  186. package/package.json +3 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"indexed.mjs","names":["delay"],"sources":["../../src/postgres/indexed.ts"],"sourcesContent":["import { recordPredicates } from \"./record-predicates\";\nimport { count, and, asc, desc, eq, gte, lt, or, inArray, sql } from \"drizzle-orm\";\nimport { z } from \"zod\";\nimport { setTimeout as delay } from \"node:timers/promises\";\nimport {\n RecordExistsError,\n RecordNotFoundError,\n VersionConflictError,\n type CollectionName,\n} from \"../store/driver\";\nimport { StoreOperationConflictError, type IndexedStore } from \"../store/indexed\";\nimport type { FactoryDatabase } from \"./index\";\nimport {\n canonicalJson,\n collectionNameSchema,\n decodeCursor,\n digest,\n encodeCursor,\n indexKeySchema,\n namespaceSchema,\n recordIdSchema,\n sortKeySchema,\n versionSchema,\n} from \"./indexed-codec\";\nimport { indexedCounts, indexedEntries, indexedOperations, indexedRecords } from \"./indexed-schema\";\nimport { compareStorageText, queryIdentity, recordQuerySchema } from \"../store/query\";\n\n/** Customer-owned transactional Postgres connection and isolated generation of Factory state. */\nexport interface PostgresIndexedStoreOptions {\n /** Drizzle database supporting interactive transactions, such as node-postgres or Neon Pool. */\n readonly db: FactoryDatabase;\n /** Separate environments and fresh cutovers must use distinct namespaces. */\n readonly namespace: string;\n}\n\n/**\n * Creates bounded Postgres persistence with atomic record, summary, count, and retry-receipt writes.\n *\n * @remarks\n * Apply `applyFactoryMigrations` explicitly using a migration role first. Runtime access needs only\n * SELECT/INSERT on all four indexed tables, UPDATE on records/counts, and DELETE on entries.\n * Use a primary connection with interactive transactions; Neon HTTP is not supported. Callers own\n * connection limits/timeouts, schema validation, authorization, and complete index memberships.\n * Records are limited to 256 KiB, summaries to 8 KiB, and memberships to 32 per record. Queries\n * fetch at most limit + 1 compact rows using a scoped B-tree seek; counts use a point read.\n * Operation receipts persist indefinitely. Retry an uncertain write with the identical operation\n * ID and intent. Pages/counts from separate calls are not a shared snapshot. This opt-in store\n * has no list/scan, automatic migration, or Blob fallback and cannot replace StoreDriver unchanged.\n */\nexport function createPostgresIndexedStore(options: PostgresIndexedStoreOptions): IndexedStore {\n return createIndexedStore(options);\n}\n\nfunction retryableTransaction(error: unknown): boolean {\n const seen = new Set<unknown>();\n let current = error;\n while (current !== null && typeof current === \"object\" && !seen.has(current)) {\n seen.add(current);\n const code: unknown = Reflect.get(current, \"code\");\n if (code === \"40001\" || code === \"40P01\") return true;\n current = Reflect.get(current, \"cause\");\n }\n return false;\n}\n\nfunction createIndexedStore(\n options: PostgresIndexedStoreOptions,\n transactional = false,\n): IndexedStore {\n const { db } = options;\n const namespace = namespaceSchema.parse(options.namespace);\n const recordWhere = (collection: CollectionName, id: string) =>\n and(\n eq(indexedRecords.namespace, namespace),\n eq(indexedRecords.collection, collectionNameSchema.parse(collection)),\n eq(indexedRecords.id, recordIdSchema.parse(id)),\n );\n const operationWhere = (operationId: string) =>\n and(eq(indexedOperations.namespace, namespace), eq(indexedOperations.operationId, operationId));\n function replay(\n receipt: { fingerprint: string; version: number },\n fingerprint: string,\n operationId: string,\n ) {\n if (receipt.fingerprint !== fingerprint) throw new StoreOperationConflictError(operationId);\n return { version: versionSchema.parse(receipt.version) };\n }\n\n const store: IndexedStore = {\n name: \"postgres\",\n async transaction(run) {\n if (transactional) return run(store);\n for (let attempt = 0; ; attempt++) {\n try {\n return await db.transaction(\n (tx) => run(createIndexedStore({ ...options, db: tx }, true)),\n { isolationLevel: \"serializable\" },\n );\n } catch (error) {\n if (attempt >= 4 || !retryableTransaction(error)) throw error;\n await delay(5 * 2 ** attempt);\n }\n }\n },\n async get(collection, id) {\n const [record] = await db\n .select({ value: indexedRecords.value, version: indexedRecords.version })\n .from(indexedRecords)\n .where(recordWhere(collection, id))\n .limit(1);\n return record ?? null;\n },\n async write(input) {\n const collection = collectionNameSchema.parse(input.collection);\n const id = recordIdSchema.parse(input.id);\n const operationId = recordIdSchema.parse(input.operationId);\n const expectedVersion = z\n .number()\n .int()\n .min(0)\n .max(Number.MAX_SAFE_INTEGER - 1)\n .parse(input.expectedVersion);\n if (input.indexes.length > 32) throw new Error(\"A record can belong to at most 32 indexes\");\n const indexes = input.indexes\n .map((entry) => ({\n index: indexKeySchema.parse(entry.index),\n sortKey: sortKeySchema.parse(entry.sortKey),\n summary: canonicalJson(entry.summary, 8 * 1024),\n ...(entry.countGroup\n ? {\n countGroup: {\n group: indexKeySchema.parse(entry.countGroup.group),\n attributes: canonicalJson(entry.countGroup.attributes, 8 * 1024),\n },\n }\n : {}),\n }))\n .sort((left, right) => (left.index < right.index ? -1 : left.index > right.index ? 1 : 0));\n if (new Set(indexes.map((entry) => entry.index)).size !== indexes.length)\n throw new Error(\"Duplicate index membership\");\n const value = canonicalJson(input.value, 256 * 1024);\n const fingerprint = digest(\n JSON.stringify({ collection, id, expectedVersion, value, indexes }),\n );\n const version = expectedVersion + 1;\n try {\n const commit = async (tx: FactoryDatabase) => {\n // The unique operation key serializes identical deliveries before record or count writes.\n const claimed = await tx\n .insert(indexedOperations)\n .values({ namespace, operationId, fingerprint, version })\n .onConflictDoNothing()\n .returning({ version: indexedOperations.version });\n if (claimed.length === 0) {\n const [receipt] = await tx\n .select()\n .from(indexedOperations)\n .where(operationWhere(operationId))\n .limit(1);\n if (!receipt) throw new Error(\"Committed operation receipt is missing\");\n return replay(receipt, fingerprint, operationId);\n }\n if (expectedVersion === 0) {\n const inserted = await tx\n .insert(indexedRecords)\n .values({ namespace, collection, id, version, value: sql`${value}::jsonb` })\n .onConflictDoNothing()\n .returning({ id: indexedRecords.id });\n if (inserted.length === 0) throw new RecordExistsError(collection, id);\n } else {\n const updated = await tx\n .update(indexedRecords)\n .set({ value: sql`${value}::jsonb`, version })\n .where(and(recordWhere(collection, id), eq(indexedRecords.version, expectedVersion)))\n .returning({ id: indexedRecords.id });\n if (updated.length === 0) {\n const [existing] = await tx\n .select({ id: indexedRecords.id })\n .from(indexedRecords)\n .where(recordWhere(collection, id))\n .limit(1);\n if (!existing) throw new RecordNotFoundError(collection, id);\n throw new VersionConflictError(collection, id, expectedVersion);\n }\n }\n const entryWhere = and(\n eq(indexedEntries.namespace, namespace),\n eq(indexedEntries.collection, collection),\n eq(indexedEntries.id, id),\n );\n const previous = await tx\n .select({ index: indexedEntries.index })\n .from(indexedEntries)\n .where(entryWhere)\n .limit(33);\n if (previous.length > 32)\n throw new Error(\"Stored index memberships exceed the write bound\");\n const oldIndexes = new Set(previous.map((entry) => entry.index));\n const newIndexes = new Set(indexes.map((entry) => entry.index));\n const retained = indexes.filter((entry) => oldIndexes.has(entry.index));\n if (retained.length) {\n const counts = await tx\n .select({\n index: indexedCounts.index,\n group: indexedCounts.group,\n attributes: indexedCounts.attributes,\n })\n .from(indexedCounts)\n .where(\n and(\n eq(indexedCounts.namespace, namespace),\n inArray(\n indexedCounts.index,\n retained.map((entry) => entry.index),\n ),\n ),\n );\n for (const entry of retained) {\n const count = counts.find((row) => row.index === entry.index);\n if (\n !count ||\n count.group !== (entry.countGroup?.group ?? null) ||\n canonicalJson(count.attributes, 8 * 1024) !==\n (entry.countGroup?.attributes ?? \"null\")\n )\n throw new Error(\"Index count attributes are immutable\");\n }\n }\n // All writers lock shared counters in the same order to avoid cross-index deadlocks.\n for (const index of [...new Set([...oldIndexes, ...newIndexes])].sort()) {\n if (oldIndexes.has(index) && newIndexes.has(index)) continue;\n if (newIndexes.has(index)) {\n const facet = indexes.find((entry) => entry.index === index)?.countGroup;\n const group = facet?.group ?? null;\n const attributes = facet ? sql`${facet.attributes}::jsonb` : sql`NULL::jsonb`;\n const increment = oldIndexes.has(index) ? 0 : 1;\n const updated = await tx\n .insert(indexedCounts)\n .values({ namespace, index, count: increment, group, attributes })\n .onConflictDoUpdate({\n target: [indexedCounts.namespace, indexedCounts.index],\n set: { count: sql`${indexedCounts.count} + ${increment}` },\n setWhere: and(\n sql`${indexedCounts.group} IS NOT DISTINCT FROM ${group}::text`,\n sql`${indexedCounts.attributes} IS NOT DISTINCT FROM ${attributes}`,\n ),\n })\n .returning({ index: indexedCounts.index });\n if (updated.length !== 1) throw new Error(\"Index count attributes are immutable\");\n } else {\n const decremented = await tx\n .update(indexedCounts)\n .set({ count: sql`${indexedCounts.count} - 1` })\n .where(\n and(\n eq(indexedCounts.namespace, namespace),\n eq(indexedCounts.index, index),\n gte(indexedCounts.count, 1),\n ),\n )\n .returning({ count: indexedCounts.count });\n if (decremented.length !== 1) throw new Error(\"Index count invariant failed\");\n }\n }\n await tx.delete(indexedEntries).where(entryWhere);\n if (indexes.length)\n await tx.insert(indexedEntries).values(\n indexes.map((entry) => ({\n namespace,\n collection,\n id,\n version,\n index: entry.index,\n sortKey: entry.sortKey,\n summary: sql`${entry.summary}::jsonb`,\n })),\n );\n return { version };\n };\n return transactional\n ? await commit(db)\n : await db.transaction(commit, { isolationLevel: \"read committed\" });\n } catch (error) {\n if (transactional) throw error;\n const [receipt] = await db\n .select()\n .from(indexedOperations)\n .where(operationWhere(operationId))\n .limit(1)\n .catch(() => []);\n if (receipt) return replay(receipt, fingerprint, operationId);\n throw error;\n }\n },\n async query(input) {\n const index = indexKeySchema.parse(input.index);\n const limit = z.number().int().min(1).max(100).parse(input.limit);\n const order = z.enum([\"asc\", \"desc\"]).parse(input.order ?? \"desc\");\n const cursor =\n input.cursor === undefined\n ? undefined\n : decodeCursor(input.cursor, namespace, index, order);\n const position = sql`(${indexedEntries.sortKey}, ${indexedEntries.collection}, ${indexedEntries.id})`;\n const boundary = cursor\n ? sql`(${cursor.sortKey}, ${cursor.collection}, ${cursor.id})`\n : undefined;\n const direction = order === \"asc\" ? asc : desc;\n const rows = await db\n .select({\n index: indexedEntries.index,\n sortKey: indexedEntries.sortKey,\n collection: indexedEntries.collection,\n id: indexedEntries.id,\n version: indexedEntries.version,\n summary: indexedEntries.summary,\n })\n .from(indexedEntries)\n .where(\n and(\n eq(indexedEntries.namespace, namespace),\n eq(indexedEntries.index, index),\n boundary\n ? order === \"asc\"\n ? sql`${position} > ${boundary}`\n : sql`${position} < ${boundary}`\n : undefined,\n ),\n )\n .orderBy(\n direction(indexedEntries.sortKey),\n direction(indexedEntries.collection),\n direction(indexedEntries.id),\n )\n .limit(limit + 1);\n const entries = rows.slice(0, limit);\n const last = entries.at(-1);\n return {\n entries,\n nextCursor:\n rows.length > limit && last\n ? encodeCursor({\n version: 1,\n namespace,\n index,\n order,\n sortKey: last.sortKey,\n collection: last.collection,\n id: last.id,\n })\n : null,\n };\n },\n async queryRecords({ sources: sourceScopes, ...input }) {\n const sources = z\n .array(\n z.strictObject({\n collection: collectionNameSchema,\n index: indexKeySchema,\n }),\n )\n .min(1)\n .max(8)\n .parse(sourceScopes)\n .sort((a, b) => compareStorageText(a.collection, b.collection));\n if (new Set(sources.map((source) => source.collection)).size !== sources.length)\n throw new Error(\"Record queries require exactly one source index per collection\");\n const query = recordQuerySchema.parse(input);\n const cursorScope = `records:${queryIdentity(JSON.stringify(sources), query)}`;\n const cursor =\n query.cursor === undefined\n ? undefined\n : decodeCursor(query.cursor, namespace, cursorScope, query.order);\n const direction = query.order === \"asc\" ? asc : desc;\n const position = sql`(${indexedEntries.sortKey}, ${indexedEntries.collection}, ${indexedEntries.id})`;\n const boundary = cursor\n ? sql`(${cursor.sortKey}, ${cursor.collection}, ${cursor.id})`\n : undefined;\n const membershipConditions = and(\n eq(indexedEntries.namespace, namespace),\n or(\n ...sources.map(({ collection, index }) =>\n and(eq(indexedEntries.collection, collection), eq(indexedEntries.index, index)),\n ),\n ),\n query.since ? gte(indexedEntries.sortKey, query.since) : undefined,\n query.until ? lt(indexedEntries.sortKey, query.until) : undefined,\n );\n const conditions = and(membershipConditions, recordPredicates(indexedRecords.value, query));\n const [{ totalCount } = {}] = query.includeTotal\n ? await db\n .select({ totalCount: count() })\n .from(indexedEntries)\n .innerJoin(\n indexedRecords,\n and(\n eq(indexedRecords.namespace, indexedEntries.namespace),\n eq(indexedRecords.collection, indexedEntries.collection),\n eq(indexedRecords.id, indexedEntries.id),\n ),\n )\n .where(conditions)\n : [];\n // Keep the ordered membership seek outside a point-sized lateral read. A flat join can\n // choose a JSON bitmap scan and sort the entire partition before applying LIMIT.\n const selectedRecord = db\n .select({ value: indexedRecords.value, version: indexedRecords.version })\n .from(indexedRecords)\n .where(\n and(\n eq(indexedRecords.namespace, indexedEntries.namespace),\n eq(indexedRecords.collection, indexedEntries.collection),\n eq(indexedRecords.id, indexedEntries.id),\n recordPredicates(indexedRecords.value, query),\n ),\n )\n .limit(1)\n .as(\"selected_record\");\n const rows = await db\n .select({\n id: indexedEntries.id,\n collection: indexedEntries.collection,\n sortKey: indexedEntries.sortKey,\n value: selectedRecord.value,\n version: selectedRecord.version,\n })\n .from(indexedEntries)\n .innerJoinLateral(selectedRecord, sql`true`)\n .where(\n and(\n membershipConditions,\n boundary\n ? query.order === \"asc\"\n ? sql`${position} > ${boundary}`\n : sql`${position} < ${boundary}`\n : undefined,\n ),\n )\n .orderBy(\n direction(indexedEntries.sortKey),\n direction(indexedEntries.collection),\n direction(indexedEntries.id),\n )\n .limit(query.limit + 1);\n const page = rows.slice(0, query.limit);\n const last = page.at(-1);\n return {\n ...(totalCount === undefined ? {} : { totalCount }),\n entries: page.map(({ collection, id, value, version }) => ({\n collection,\n id,\n record: { value, version },\n })),\n nextCursor:\n rows.length > page.length && last\n ? encodeCursor({\n version: 1,\n namespace,\n index: cursorScope,\n order: query.order,\n collection: last.collection,\n sortKey: last.sortKey,\n id: last.id,\n })\n : null,\n };\n },\n async sumCounts(group, input) {\n const query = recordQuerySchema.parse({ ...input, limit: 1 });\n const [row] = await db\n .select({ total: sql<string>`coalesce(sum(${indexedCounts.count}), 0)` })\n .from(indexedCounts)\n .where(\n and(\n eq(indexedCounts.namespace, namespace),\n eq(indexedCounts.group, indexKeySchema.parse(group)),\n recordPredicates(indexedCounts.attributes, query),\n ),\n );\n return z.coerce\n .number()\n .int()\n .nonnegative()\n .max(Number.MAX_SAFE_INTEGER)\n .parse(row?.total ?? 0);\n },\n async count(index) {\n const [row] = await db\n .select({ count: indexedCounts.count })\n .from(indexedCounts)\n .where(\n and(\n eq(indexedCounts.namespace, namespace),\n eq(indexedCounts.index, indexKeySchema.parse(index)),\n ),\n )\n .limit(1);\n return row?.count ?? 0;\n },\n };\n return store;\n}\n"],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import { canonicalJson } from "./indexed-codec.mjs";
2
+ import { and, or, sql } from "drizzle-orm";
3
+ //#region src/postgres/record-predicates.ts
4
+ function recordPredicates(value, query) {
5
+ const alternatives = query.match === void 0 ? [] : Array.isArray(query.match) ? query.match : [query.match];
6
+ const fields = query.fields?.map((filter) => {
7
+ const path = sql`ARRAY[${sql.join(filter.path.map((key) => sql`${key}`), sql`, `)}]::text[]`;
8
+ const field = sql`(${value} #> ${path})`;
9
+ if (filter.test === "exists") return sql`${field} IS NOT NULL`;
10
+ if (filter.test === "missing") return sql`${field} IS NULL`;
11
+ if (filter.test === "notEmpty") return sql`CASE WHEN jsonb_typeof(${field}) = 'array' THEN jsonb_array_length(${field}) > 0 ELSE false END`;
12
+ if (!("value" in filter)) throw new Error("Invalid field filter");
13
+ const expected = canonicalJson(filter.value, 32768);
14
+ if (filter.test === "oneOf") return sql`${expected}::jsonb @> jsonb_build_array(${field})`;
15
+ if (filter.test === "subset") return sql`${field} <@ ${expected}::jsonb`;
16
+ return filter.test === "eq" ? sql`${field} = ${expected}::jsonb` : sql`${field} IS DISTINCT FROM ${expected}::jsonb`;
17
+ });
18
+ return and(alternatives.length ? or(...alternatives.map((match) => sql`${value} @> ${canonicalJson(match, 32768)}::jsonb`)) : void 0, ...fields ?? [], query.search ? sql`to_tsvector('simple', coalesce(${value}->>'searchText', '')) @@ plainto_tsquery('simple', ${query.search})` : void 0);
19
+ }
20
+ //#endregion
21
+ export { recordPredicates };
22
+
23
+ //# sourceMappingURL=record-predicates.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"record-predicates.mjs","names":[],"sources":["../../src/postgres/record-predicates.ts"],"sourcesContent":["import { and, or, sql, type SQL, type SQLWrapper } from \"drizzle-orm\";\nimport { canonicalJson } from \"./indexed-codec\";\nimport type { FactoryRecordQuery } from \"../store/query\";\n\nexport function recordPredicates(\n value: SQLWrapper,\n query: Pick<FactoryRecordQuery, \"match\" | \"fields\" | \"search\">,\n): SQL | undefined {\n const alternatives =\n query.match === undefined ? [] : Array.isArray(query.match) ? query.match : [query.match];\n const fields = query.fields?.map((filter) => {\n const path = sql`ARRAY[${sql.join(\n filter.path.map((key) => sql`${key}`),\n sql`, `,\n )}]::text[]`;\n const field = sql`(${value} #> ${path})`;\n if (filter.test === \"exists\") return sql`${field} IS NOT NULL`;\n if (filter.test === \"missing\") return sql`${field} IS NULL`;\n if (filter.test === \"notEmpty\")\n return sql`CASE WHEN jsonb_typeof(${field}) = 'array' THEN jsonb_array_length(${field}) > 0 ELSE false END`;\n if (!(\"value\" in filter)) throw new Error(\"Invalid field filter\");\n const expected = canonicalJson(filter.value, 32 * 1024);\n if (filter.test === \"oneOf\") return sql`${expected}::jsonb @> jsonb_build_array(${field})`;\n if (filter.test === \"subset\") return sql`${field} <@ ${expected}::jsonb`;\n return filter.test === \"eq\"\n ? sql`${field} = ${expected}::jsonb`\n : sql`${field} IS DISTINCT FROM ${expected}::jsonb`;\n });\n return and(\n alternatives.length\n ? or(\n ...alternatives.map(\n (match) => sql`${value} @> ${canonicalJson(match, 32 * 1024)}::jsonb`,\n ),\n )\n : undefined,\n ...(fields ?? []),\n query.search\n ? sql`to_tsvector('simple', coalesce(${value}->>'searchText', '')) @@ plainto_tsquery('simple', ${query.search})`\n : undefined,\n );\n}\n"],"mappings":";;;AAIA,SAAgB,iBACd,OACA,OACiB;CACjB,MAAM,eACJ,MAAM,UAAU,KAAA,IAAY,CAAC,IAAI,MAAM,QAAQ,MAAM,KAAK,IAAI,MAAM,QAAQ,CAAC,MAAM,KAAK;CAC1F,MAAM,SAAS,MAAM,QAAQ,KAAK,WAAW;EAC3C,MAAM,OAAO,GAAG,SAAS,IAAI,KAC3B,OAAO,KAAK,KAAK,QAAQ,GAAG,GAAG,KAAK,GACpC,GAAG,IACL,EAAE;EACF,MAAM,QAAQ,GAAG,IAAI,MAAM,MAAM,KAAK;EACtC,IAAI,OAAO,SAAS,UAAU,OAAO,GAAG,GAAG,MAAM;EACjD,IAAI,OAAO,SAAS,WAAW,OAAO,GAAG,GAAG,MAAM;EAClD,IAAI,OAAO,SAAS,YAClB,OAAO,GAAG,0BAA0B,MAAM,sCAAsC,MAAM;EACxF,IAAI,EAAE,WAAW,SAAS,MAAM,IAAI,MAAM,sBAAsB;EAChE,MAAM,WAAW,cAAc,OAAO,OAAO,KAAS;EACtD,IAAI,OAAO,SAAS,SAAS,OAAO,GAAG,GAAG,SAAS,+BAA+B,MAAM;EACxF,IAAI,OAAO,SAAS,UAAU,OAAO,GAAG,GAAG,MAAM,MAAM,SAAS;EAChE,OAAO,OAAO,SAAS,OACnB,GAAG,GAAG,MAAM,KAAK,SAAS,WAC1B,GAAG,GAAG,MAAM,oBAAoB,SAAS;CAC/C,CAAC;CACD,OAAO,IACL,aAAa,SACT,GACE,GAAG,aAAa,KACb,UAAU,GAAG,GAAG,MAAM,MAAM,cAAc,OAAO,KAAS,EAAE,QAC/D,CACF,IACA,KAAA,GACJ,GAAI,UAAU,CAAC,GACf,MAAM,SACF,GAAG,kCAAkC,MAAM,qDAAqD,MAAM,OAAO,KAC7G,KAAA,CACN;AACF"}
@@ -1,5 +1,5 @@
1
- import { Repository } from "../../schema/repository.mjs";
2
1
  import { Task } from "../../schema/task.mjs";
2
+ import { Repository } from "../../schema/repository.mjs";
3
3
  //#region src/presets/software-development/deepsec-brief.d.ts
4
4
  /** Format the software-development preset's starting brief for a DeepSec scan. */
5
5
  declare function formatDeepsecScanBrief(task: Task, repository: Repository): string;
@@ -18,7 +18,7 @@ async function dispatchSoftwareDevelopmentWork(options) {
18
18
  for (const id of new Set(tasks.flatMap((task) => task.repositoryIds))) if (!await options.canUseRepository(id)) throw new Error(`Work repository ${id} is no longer enabled and configured`);
19
19
  for (const task of tasks.filter((task) => task.state === "queued" && task.replyTo.channel === "api")) {
20
20
  const session = await stores.sessions.get(agentSessionIdSchema.parse(task.replyTo.address));
21
- if (!session || session.route !== void 0 && session.taskId === void 0 || task.work.route?.id === "conversation" && !session.continuations?.some((entry) => entry.taskId === task.id)) throw new Error(`Task ${task.id} is waiting for its durable API session binding`);
21
+ if (!session || session.route !== void 0 && session.taskId === void 0 || task.work.route?.id === "conversation" && !await stores.sessions.getContinuation(session.id, task.id)) throw new Error(`Task ${task.id} is waiting for its durable API session binding`);
22
22
  }
23
23
  for (const task of tasks) if (task.kind === "coordination" && task.state === "running") await reconcileMultiRepositoryChangeTasks(stores, task.id);
24
24
  if (options.verificationRoute) await ensureChangeVerificationTasks({
@@ -1 +1 @@
1
- {"version":3,"file":"dispatch.mjs","names":[],"sources":["../../../src/presets/software-development/dispatch.ts"],"sourcesContent":["import { sweepSettlements } from \"../../budget\";\nimport type { AgentRouteBinding } from \"../../agent-routes\";\nimport { ensureChangeVerificationTasks } from \"../../change-verification/dispatch\";\nimport type { PresentVerificationResultInput } from \"../../change-verification/eve-tool\";\nimport type { DispatchBudget } from \"../../dispatch\";\nimport { reconcileMultiRepositoryChangeTasks } from \"../../multi-repository-changes/coordination\";\nimport type { PlannerReconcileResult } from \"../../planning/runtime\";\nimport type { MillisecondClock } from \"../../schema/common\";\nimport type { RepositoryId } from \"../../schema/id\";\nimport { agentSessionIdSchema } from \"../../schema/session\";\nimport type { Task } from \"../../schema/task\";\nimport type { FactoryStores } from \"../../store/engine\";\nimport type { WorkNotification } from \"../../store/work-notifications\";\nimport { sweepQueued, type SweepQueuedOptions, type SweepQueuedResult } from \"../../sweep\";\nimport type { TaskCommunicationReconciler } from \"../../task-communication\";\nimport {\n createSoftwareDevelopmentTaskCommunication,\n type SoftwareDevelopmentTaskCommunicationStores,\n} from \"./task-communication\";\n\nconst defaultReconcileTaskCommunications = createSoftwareDevelopmentTaskCommunication().reconcile;\n\n/** Factory capabilities composed by the supplied software-development dispatcher. */\nexport type SoftwareDevelopmentDispatchStores = Pick<\n FactoryStores,\n | \"changes\"\n | \"graphs\"\n | \"outbox\"\n | \"receipts\"\n | \"repositories\"\n | \"sessions\"\n | \"signals\"\n | \"tasks\"\n | \"work\"\n>;\n\n/** Application policies and adapters for reconciling one reference-work root graph. */\nexport interface DispatchSoftwareDevelopmentWorkOptions {\n /** Durable wakeup identifying the canonical Task graph to reconcile. */\n readonly event: WorkNotification;\n /** Provider delivery identity used to deduplicate continuation wakeups. */\n readonly deliveryId: string;\n stores: SoftwareDevelopmentDispatchStores;\n planning: {\n isPlanner(task: Task): boolean;\n reconcile(tasks: readonly Task[]): Promise<PlannerReconcileResult>;\n };\n launchers: SweepQueuedOptions[\"launchers\"];\n budget: DispatchBudget;\n verificationRoute?: AgentRouteBinding;\n /** Idempotent application presenter for recorded verification outcomes recovered by the queue. */\n presentVerificationResult?: (input: PresentVerificationResultInput) => Promise<void>;\n canUseRepository(id: RepositoryId): Promise<boolean>;\n publish(event: WorkNotification, delaySeconds?: number): Promise<void>;\n /**\n * Tasks a workflow owns and reconciles itself. The reference dispatcher skips\n * them so the two never drive the same execution.\n */\n ownedByWorkflow?(tasks: readonly Task[]): readonly Task[];\n now?: MillisecondClock;\n retryDelayMs?: number;\n maxDispatches?: number;\n plannerBatchSize?: number;\n /** Application-selected projection from task state to outbound communication. */\n reconcileCommunications?: TaskCommunicationReconciler<SoftwareDevelopmentTaskCommunicationStores>;\n}\n\n/** Queued-dispatch and planner reconciliation results for one notified root graph. */\nexport interface DispatchSoftwareDevelopmentWorkResult extends SweepQueuedResult {\n readonly planner: PlannerReconcileResult;\n}\n\n/** Reconcile one notified root graph without scanning or scheduling unrelated work. */\nexport async function dispatchSoftwareDevelopmentWork(\n options: DispatchSoftwareDevelopmentWorkOptions,\n): Promise<DispatchSoftwareDevelopmentWorkResult> {\n const { deliveryId, event, stores, planning } = options;\n const recover = async () => {\n const graph = await stores.work.recover(event.rootTaskId);\n const owned = new Set((options.ownedByWorkflow?.(graph) ?? []).map((task) => task.id));\n return graph.filter((task) => !owned.has(task.id));\n };\n let tasks = await recover();\n for (const id of new Set(tasks.flatMap((task) => task.repositoryIds))) {\n if (!(await options.canUseRepository(id)))\n throw new Error(`Work repository ${id} is no longer enabled and configured`);\n }\n // Admission may publish before the API finishes binding its session or continuation.\n for (const task of tasks.filter(\n (task) => task.state === \"queued\" && task.replyTo.channel === \"api\",\n )) {\n const session = await stores.sessions.get(agentSessionIdSchema.parse(task.replyTo.address));\n if (\n !session ||\n (session.route !== undefined && session.taskId === undefined) ||\n (task.work.route?.id === \"conversation\" &&\n !session.continuations?.some((entry) => entry.taskId === task.id))\n ) {\n throw new Error(`Task ${task.id} is waiting for its durable API session binding`);\n }\n }\n for (const task of tasks) {\n if (task.kind === \"coordination\" && task.state === \"running\")\n await reconcileMultiRepositoryChangeTasks(stores, task.id);\n }\n if (options.verificationRoute)\n await ensureChangeVerificationTasks({\n stores,\n tasks: await recover(),\n route: options.verificationRoute,\n presentResult: options.presentVerificationResult,\n });\n await sweepSettlements(stores, { tasks: await recover() });\n const planner = await planning.reconcile(await recover());\n tasks = await recover();\n const ready: Task[] = [];\n const now = (options.now ?? Date.now)();\n for (const task of tasks.filter((task) => !planning.isPlanner(task))) {\n const retryAt = Date.parse(task.updatedAt) + (options.retryDelayMs ?? 60_000);\n if (task.state === \"queued\" && task.attempt > 1 && retryAt > now) {\n await options.publish(\n { rootTaskId: event.rootTaskId, key: `retry:${task.id}:${task.attempt}` },\n Math.ceil((retryAt - now) / 1000),\n );\n } else ready.push(task);\n }\n const result = await sweepQueued(stores, {\n tasks: ready,\n launchers: options.launchers,\n budget: options.budget,\n maxDispatches: options.maxDispatches ?? 1,\n });\n await (options.reconcileCommunications ?? defaultReconcileTaskCommunications)(\n stores,\n await recover(),\n );\n // Continue bounded batches through the queue, never through the next recovery interval.\n if (\n result.skipped.some((entry) => entry.reason === \"per-sweep dispatch limit reached\") ||\n planner.dispatched.length ||\n planner.delivered >= (options.plannerBatchSize ?? 32) ||\n planner.recovered + planner.discarded >= (options.plannerBatchSize ?? 32)\n ) {\n await options.publish({ rootTaskId: event.rootTaskId, key: `continue:${deliveryId}` });\n }\n if (planner.errors.length || planner.cancellationPending?.length || result.failed.length) {\n throw new Error(\n `Work reconciliation needs retry: ${JSON.stringify({ planner: planner.errors, cancellationPending: planner.cancellationPending, failed: result.failed })}`,\n );\n }\n return { ...result, planner };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"dispatch.mjs","names":[],"sources":["../../../src/presets/software-development/dispatch.ts"],"sourcesContent":["import { sweepSettlements } from \"../../budget\";\nimport type { AgentRouteBinding } from \"../../agent-routes\";\nimport { ensureChangeVerificationTasks } from \"../../change-verification/dispatch\";\nimport type { PresentVerificationResultInput } from \"../../change-verification/eve-tool\";\nimport type { DispatchBudget } from \"../../dispatch\";\nimport { reconcileMultiRepositoryChangeTasks } from \"../../multi-repository-changes/coordination\";\nimport type { PlannerReconcileResult } from \"../../planning/runtime\";\nimport type { MillisecondClock } from \"../../schema/common\";\nimport type { RepositoryId } from \"../../schema/id\";\nimport { agentSessionIdSchema } from \"../../schema/session\";\nimport type { Task } from \"../../schema/task\";\nimport type { FactoryStores } from \"../../store/engine\";\nimport type { WorkNotification } from \"../../store/work-notifications\";\nimport { sweepQueued, type SweepQueuedOptions, type SweepQueuedResult } from \"../../sweep\";\nimport type { TaskCommunicationReconciler } from \"../../task-communication\";\nimport {\n createSoftwareDevelopmentTaskCommunication,\n type SoftwareDevelopmentTaskCommunicationStores,\n} from \"./task-communication\";\n\nconst defaultReconcileTaskCommunications = createSoftwareDevelopmentTaskCommunication().reconcile;\n\n/** Factory capabilities composed by the supplied software-development dispatcher. */\nexport type SoftwareDevelopmentDispatchStores = Pick<\n FactoryStores,\n | \"changes\"\n | \"graphs\"\n | \"outbox\"\n | \"receipts\"\n | \"repositories\"\n | \"sessions\"\n | \"signals\"\n | \"tasks\"\n | \"work\"\n>;\n\n/** Application policies and adapters for reconciling one reference-work root graph. */\nexport interface DispatchSoftwareDevelopmentWorkOptions {\n /** Durable wakeup identifying the canonical Task graph to reconcile. */\n readonly event: WorkNotification;\n /** Provider delivery identity used to deduplicate continuation wakeups. */\n readonly deliveryId: string;\n stores: SoftwareDevelopmentDispatchStores;\n planning: {\n isPlanner(task: Task): boolean;\n reconcile(tasks: readonly Task[]): Promise<PlannerReconcileResult>;\n };\n launchers: SweepQueuedOptions[\"launchers\"];\n budget: DispatchBudget;\n verificationRoute?: AgentRouteBinding;\n /** Idempotent application presenter for recorded verification outcomes recovered by the queue. */\n presentVerificationResult?: (input: PresentVerificationResultInput) => Promise<void>;\n canUseRepository(id: RepositoryId): Promise<boolean>;\n publish(event: WorkNotification, delaySeconds?: number): Promise<void>;\n /**\n * Tasks a workflow owns and reconciles itself. The reference dispatcher skips\n * them so the two never drive the same execution.\n */\n ownedByWorkflow?(tasks: readonly Task[]): readonly Task[];\n now?: MillisecondClock;\n retryDelayMs?: number;\n maxDispatches?: number;\n plannerBatchSize?: number;\n /** Application-selected projection from task state to outbound communication. */\n reconcileCommunications?: TaskCommunicationReconciler<SoftwareDevelopmentTaskCommunicationStores>;\n}\n\n/** Queued-dispatch and planner reconciliation results for one notified root graph. */\nexport interface DispatchSoftwareDevelopmentWorkResult extends SweepQueuedResult {\n readonly planner: PlannerReconcileResult;\n}\n\n/** Reconcile one notified root graph without scanning or scheduling unrelated work. */\nexport async function dispatchSoftwareDevelopmentWork(\n options: DispatchSoftwareDevelopmentWorkOptions,\n): Promise<DispatchSoftwareDevelopmentWorkResult> {\n const { deliveryId, event, stores, planning } = options;\n const recover = async () => {\n const graph = await stores.work.recover(event.rootTaskId);\n const owned = new Set((options.ownedByWorkflow?.(graph) ?? []).map((task) => task.id));\n return graph.filter((task) => !owned.has(task.id));\n };\n let tasks = await recover();\n for (const id of new Set(tasks.flatMap((task) => task.repositoryIds))) {\n if (!(await options.canUseRepository(id)))\n throw new Error(`Work repository ${id} is no longer enabled and configured`);\n }\n // Admission may publish before the API finishes binding its session or continuation.\n for (const task of tasks.filter(\n (task) => task.state === \"queued\" && task.replyTo.channel === \"api\",\n )) {\n const session = await stores.sessions.get(agentSessionIdSchema.parse(task.replyTo.address));\n if (\n !session ||\n (session.route !== undefined && session.taskId === undefined) ||\n (task.work.route?.id === \"conversation\" &&\n !(await stores.sessions.getContinuation(session.id, task.id)))\n ) {\n throw new Error(`Task ${task.id} is waiting for its durable API session binding`);\n }\n }\n for (const task of tasks) {\n if (task.kind === \"coordination\" && task.state === \"running\")\n await reconcileMultiRepositoryChangeTasks(stores, task.id);\n }\n if (options.verificationRoute)\n await ensureChangeVerificationTasks({\n stores,\n tasks: await recover(),\n route: options.verificationRoute,\n presentResult: options.presentVerificationResult,\n });\n await sweepSettlements(stores, { tasks: await recover() });\n const planner = await planning.reconcile(await recover());\n tasks = await recover();\n const ready: Task[] = [];\n const now = (options.now ?? Date.now)();\n for (const task of tasks.filter((task) => !planning.isPlanner(task))) {\n const retryAt = Date.parse(task.updatedAt) + (options.retryDelayMs ?? 60_000);\n if (task.state === \"queued\" && task.attempt > 1 && retryAt > now) {\n await options.publish(\n { rootTaskId: event.rootTaskId, key: `retry:${task.id}:${task.attempt}` },\n Math.ceil((retryAt - now) / 1000),\n );\n } else ready.push(task);\n }\n const result = await sweepQueued(stores, {\n tasks: ready,\n launchers: options.launchers,\n budget: options.budget,\n maxDispatches: options.maxDispatches ?? 1,\n });\n await (options.reconcileCommunications ?? defaultReconcileTaskCommunications)(\n stores,\n await recover(),\n );\n // Continue bounded batches through the queue, never through the next recovery interval.\n if (\n result.skipped.some((entry) => entry.reason === \"per-sweep dispatch limit reached\") ||\n planner.dispatched.length ||\n planner.delivered >= (options.plannerBatchSize ?? 32) ||\n planner.recovered + planner.discarded >= (options.plannerBatchSize ?? 32)\n ) {\n await options.publish({ rootTaskId: event.rootTaskId, key: `continue:${deliveryId}` });\n }\n if (planner.errors.length || planner.cancellationPending?.length || result.failed.length) {\n throw new Error(\n `Work reconciliation needs retry: ${JSON.stringify({ planner: planner.errors, cancellationPending: planner.cancellationPending, failed: result.failed })}`,\n );\n }\n return { ...result, planner };\n}\n"],"mappings":"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"}
@@ -52,7 +52,8 @@ function createSoftwareDevelopmentRecoveryLaunchers(options) {
52
52
  }
53
53
  launchers["conversation@1"] = async (task) => {
54
54
  if (task.work.route?.id !== "conversation" || task.work.route.version !== 1 || task.replyTo.channel !== "api") throw new Error(`Task ${task.id} is not bound to conversation@1`);
55
- if (!(await stores.sessions.get(agentSessionIdSchema.parse(task.replyTo.address)))?.continuations?.some((item) => item.taskId === task.id)) throw new Error(`Conversation binding is missing for task ${task.id}`);
55
+ const session = await stores.sessions.get(agentSessionIdSchema.parse(task.replyTo.address));
56
+ if (!session || !await stores.sessions.getContinuation(session.id, task.id)) throw new Error(`Conversation binding is missing for task ${task.id}`);
56
57
  return continueConversation(task);
57
58
  };
58
59
  return launchers;
@@ -108,7 +109,13 @@ async function verificationTaskBrief(stores, task, repository) {
108
109
  }
109
110
  async function changeIdForPullRequest(stores, repository, prUrl) {
110
111
  if (prUrl === void 0) return void 0;
111
- return (await stores.changes.list()).find((change) => change.repositoryId === repository.id && change.prUrl === prUrl)?.id;
112
+ return (await stores.changes.list({
113
+ limit: 1,
114
+ match: {
115
+ repositoryId: repository.id,
116
+ prUrl
117
+ }
118
+ })).items[0]?.id;
112
119
  }
113
120
  async function withAnsweredQuestions(stores, task, brief) {
114
121
  const answers = (await Promise.all((task.answerEventIds ?? []).map((id) => stores.answers.get(id)))).flatMap((event) => event === null ? [] : [`${event.question}\nAnswer: ${event.answer}`]);
@@ -1 +1 @@
1
- {"version":3,"file":"recovery.mjs","names":[],"sources":["../../../src/presets/software-development/recovery.ts"],"sourcesContent":["import {\n routeKey,\n type AgentRoute,\n type AgentRouteKey,\n type AgentRouteRegistry,\n} from \"../../agent-routes\";\nimport { formatChangeVerificationBrief } from \"../../change-verification/brief\";\nimport { findFindingForTask, formatFindingRemediationBrief } from \"../../finding-remediation/brief\";\nimport { normalizeFactoryReviewComment, normalizeGitHubPullRequest } from \"../../github\";\nimport type { ImageAttachment } from \"../../image-input\";\nimport { mergeConflictPayloadSchema } from \"../../merge-resolution/contracts\";\nimport type { Repository } from \"../../schema/repository\";\nimport { agentSessionIdSchema } from \"../../schema/session\";\nimport type { ExecutionReference, Task } from \"../../schema/task\";\nimport type { JsonValue } from \"../../schema/work\";\nimport type { FactoryStores } from \"../../store/engine\";\nimport type { SweepQueuedOptions, TaskLauncher } from \"../../sweep\";\nimport { formatDeepsecScanBrief } from \"./deepsec-brief\";\n\n/** Answer, Change, session, and Signal capabilities used to assemble recovery launchers. */\nexport type SoftwareDevelopmentRecoveryStores = Pick<\n FactoryStores,\n \"answers\" | \"changes\" | \"sessions\" | \"signals\"\n>;\n\n/** Identifies one replacement attempt on its exact registered software agent route. */\nexport interface StartSoftwareDevelopmentRecoveryInput {\n readonly route: AgentRoute;\n readonly task: Task;\n readonly message: string;\n readonly images?: {\n readonly owner: string;\n readonly attachments: readonly ImageAttachment[];\n };\n}\n\n/** Starts a replacement attempt using its exact registered software agent route. */\nexport type StartSoftwareDevelopmentRecovery = (\n input: StartSoftwareDevelopmentRecoveryInput,\n) => Promise<ExecutionReference>;\n\ntype RecoveryBrief = (task: Task, repository: Repository) => Promise<string>;\n\n/** Stores, route registry, and execution boundaries used to assemble recovery launchers. */\nexport interface SoftwareDevelopmentRecoveryOptions {\n stores: SoftwareDevelopmentRecoveryStores;\n routes: AgentRouteRegistry;\n start: StartSoftwareDevelopmentRecovery;\n continueConversation: (task: Task) => Promise<ExecutionReference>;\n}\n\n/** Assemble replacement-attempt launchers for the supplied software-factory routes. */\nexport function createSoftwareDevelopmentRecoveryLaunchers(\n options: SoftwareDevelopmentRecoveryOptions,\n): SweepQueuedOptions[\"launchers\"] {\n const { stores, routes, start, continueConversation } = options;\n const briefs: Readonly<Partial<Record<AgentRouteKey, RecoveryBrief>>> = {\n \"code-change@1\": async (task, repository) =>\n withAnsweredQuestions(\n stores,\n task,\n formatFindingRemediationBrief(task, repository, await findFindingForTask(stores, task)),\n ),\n \"code-review@1\": (task, repository) => codeReviewBrief(stores, task, repository),\n \"review-follow-up@1\": (task, repository) => reviewFollowUpBrief(stores, task, repository),\n \"merge-conflict@1\": (task, repository) => mergeConflictBrief(stores, task, repository),\n \"security-scan@1\": async (task, repository) => formatDeepsecScanBrief(task, repository),\n \"verification@1\": (task, repository) => verificationTaskBrief(stores, task, repository),\n };\n const launchers: Partial<Record<AgentRouteKey, TaskLauncher>> = {};\n for (const route of routes.routes) {\n const key = routeKey(route);\n const brief =\n briefs[key] ??\n ((task: Task) =>\n withAnsweredQuestions(\n stores,\n task,\n `You are assigned replacement attempt ${task.attempt} for task ${task.id}. Read the task before starting.\\n\\n${task.work.title}\\n\\nWork input:\\n${JSON.stringify(task.work.input)}`,\n ));\n launchers[key] = async (task, repository) => {\n if (!task.work.route || routeKey(task.work.route) !== key) {\n throw new Error(`Task ${task.id} is not bound to agent route ${key}`);\n }\n if (task.replyTo.channel === \"api\") {\n const session = await stores.sessions.get(agentSessionIdSchema.parse(task.replyTo.address));\n if (session === null) {\n throw new Error(`API session binding is missing for task ${task.id}`);\n }\n if (session.taskId === task.id) {\n if (session.agent !== route.agent) {\n throw new Error(`API session agent differs from route ${route.id}`);\n }\n const message = await withAnsweredQuestions(\n stores,\n task,\n `You are assigned Factory task ${task.id}. Read it before starting.\\n\\n${session.changeId === undefined ? \"\" : `Existing change: ${session.changeId}.\\n\\n`}${session.message}`,\n );\n return start({\n route,\n task,\n message,\n ...(session.attachments?.length\n ? {\n images: {\n owner: session.owner,\n attachments: session.attachments,\n },\n }\n : {}),\n });\n }\n }\n const input = task.work.input;\n const instructions =\n isJsonObject(input) && typeof input.instructions === \"string\"\n ? input.instructions\n : undefined;\n const message =\n instructions === undefined\n ? await brief(task, repository)\n : await withAnsweredQuestions(\n stores,\n task,\n `You are assigned Factory task ${task.id}. Read it before starting.\\n\\n${task.work.title}\\n\\n${instructions}\\n\\nCompletion criteria:\\n${task.work.completionCriteria.join(\"\\n\")}`,\n );\n return start({ route, task, message });\n };\n }\n launchers[\"conversation@1\"] = async (task) => {\n if (\n task.work.route?.id !== \"conversation\" ||\n task.work.route.version !== 1 ||\n task.replyTo.channel !== \"api\"\n ) {\n throw new Error(`Task ${task.id} is not bound to conversation@1`);\n }\n const session = await stores.sessions.get(agentSessionIdSchema.parse(task.replyTo.address));\n if (!session?.continuations?.some((item) => item.taskId === task.id)) {\n throw new Error(`Conversation binding is missing for task ${task.id}`);\n }\n return continueConversation(task);\n };\n return launchers;\n}\n\nfunction isJsonObject(value: JsonValue): value is { readonly [key: string]: JsonValue } {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\nasync function codeReviewBrief(\n stores: SoftwareDevelopmentRecoveryStores,\n task: Task,\n repository: Repository,\n): Promise<string> {\n const signal =\n task.origin.signalId === undefined ? null : await stores.signals.get(task.origin.signalId);\n const normalized =\n signal?.source === \"github_pr\"\n ? normalizeGitHubPullRequest({\n deliveryId: signal.deliveryId,\n event: \"pull_request\",\n payload: signal.payload,\n })\n : null;\n const changeId =\n task.changeId ??\n (await changeIdForPullRequest(stores, repository, normalized?.pullRequest.pullRequestUrl));\n const facts = [\n `You are assigned replacement attempt ${task.attempt} for code review task ${task.id}.`,\n changeId === undefined ? null : `Change under review: ${changeId}.`,\n normalized === null ? null : `Pull request: ${normalized.pullRequest.pullRequestUrl}.`,\n normalized === null ? null : `Expected head commit: ${normalized.pullRequest.headSha}.`,\n \"Read the task, fetch and review the pull request, then record exactly one review result with record_post_review.\",\n ]\n .filter((line) => line !== null)\n .join(\"\\n\\n\");\n return withAnsweredQuestions(stores, task, facts);\n}\n\nasync function reviewFollowUpBrief(\n stores: SoftwareDevelopmentRecoveryStores,\n task: Task,\n repository: Repository,\n): Promise<string> {\n const signal =\n task.origin.signalId === undefined ? null : await stores.signals.get(task.origin.signalId);\n const review =\n signal?.source === \"github_pr_comment\"\n ? normalizeFactoryReviewComment({\n event: \"pull_request_review_comment\",\n payload: signal.payload,\n })\n : null;\n const changeId =\n task.changeId ?? (await changeIdForPullRequest(stores, repository, review?.pullRequestUrl));\n const facts = [\n `You are assigned replacement attempt ${task.attempt} for factory task ${task.id}.`,\n changeId === undefined ? null : `Change under review: ${changeId}.`,\n review === null ? null : `Inline review comment: ${review.commentId}.`,\n review === null ? null : `Pull request: ${review.pullRequestUrl}.`,\n \"Read the task and originating review comment. Apply a concrete high-confidence fix, post a medium-confidence suggestion, or answer with findings, then finish the task.\",\n ]\n .filter((line) => line !== null)\n .join(\"\\n\\n\");\n return withAnsweredQuestions(stores, task, facts);\n}\n\nasync function mergeConflictBrief(\n stores: SoftwareDevelopmentRecoveryStores,\n task: Task,\n repository: Repository,\n): Promise<string> {\n const signal =\n task.origin.signalId === undefined ? null : await stores.signals.get(task.origin.signalId);\n const parsed = mergeConflictPayloadSchema.safeParse(signal?.payload);\n const facts = parsed.success\n ? [\n `You are assigned replacement attempt ${task.attempt} for merge conflict task ${task.id} in ${repository.slug}.`,\n `Pull request: ${parsed.data.prUrl}.`,\n \"Read the task, fetch the conflict with fetch_merge_conflict, resolve and test it, then publish_merge_resolution.\",\n ].join(\"\\n\\n\")\n : [\n `You are assigned replacement attempt ${task.attempt} for merge conflict task ${task.id} in ${repository.slug}.`,\n \"Read the task and recover its merge-conflict context with the dedicated tools. Do not guess a pull request identifier.\",\n ].join(\"\\n\\n\");\n return withAnsweredQuestions(stores, task, facts);\n}\n\nasync function verificationTaskBrief(\n stores: SoftwareDevelopmentRecoveryStores,\n task: Task,\n repository: Repository,\n): Promise<string> {\n if (task.changeId === undefined) throw new Error(`verification task ${task.id} has no change`);\n const change = await stores.changes.get(task.changeId);\n if (change === null) throw new Error(`change ${task.changeId} does not exist`);\n return withAnsweredQuestions(\n stores,\n task,\n formatChangeVerificationBrief(task, repository, change),\n );\n}\n\nasync function changeIdForPullRequest(\n stores: SoftwareDevelopmentRecoveryStores,\n repository: Repository,\n prUrl: string | undefined,\n): Promise<string | undefined> {\n if (prUrl === undefined) return undefined;\n return (await stores.changes.list()).find(\n (change) => change.repositoryId === repository.id && change.prUrl === prUrl,\n )?.id;\n}\n\nasync function withAnsweredQuestions(\n stores: SoftwareDevelopmentRecoveryStores,\n task: Task,\n brief: string,\n): Promise<string> {\n const answers = (\n await Promise.all((task.answerEventIds ?? []).map((id) => stores.answers.get(id)))\n ).flatMap((event) => (event === null ? [] : [`${event.question}\\nAnswer: ${event.answer}`]));\n return answers.length === 0 ? brief : `${brief}\\n\\nHuman answers:\\n${answers.join(\"\\n\\n\")}`;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"recovery.mjs","names":[],"sources":["../../../src/presets/software-development/recovery.ts"],"sourcesContent":["import {\n routeKey,\n type AgentRoute,\n type AgentRouteKey,\n type AgentRouteRegistry,\n} from \"../../agent-routes\";\nimport { formatChangeVerificationBrief } from \"../../change-verification/brief\";\nimport { findFindingForTask, formatFindingRemediationBrief } from \"../../finding-remediation/brief\";\nimport { normalizeFactoryReviewComment, normalizeGitHubPullRequest } from \"../../github\";\nimport type { ImageAttachment } from \"../../image-input\";\nimport { mergeConflictPayloadSchema } from \"../../merge-resolution/contracts\";\nimport type { Repository } from \"../../schema/repository\";\nimport { agentSessionIdSchema } from \"../../schema/session\";\nimport type { ExecutionReference, Task } from \"../../schema/task\";\nimport type { JsonValue } from \"../../schema/work\";\nimport type { FactoryStores } from \"../../store/engine\";\nimport type { SweepQueuedOptions, TaskLauncher } from \"../../sweep\";\nimport { formatDeepsecScanBrief } from \"./deepsec-brief\";\n\n/** Answer, Change, session, and Signal capabilities used to assemble recovery launchers. */\nexport type SoftwareDevelopmentRecoveryStores = Pick<\n FactoryStores,\n \"answers\" | \"changes\" | \"sessions\" | \"signals\"\n>;\n\n/** Identifies one replacement attempt on its exact registered software agent route. */\nexport interface StartSoftwareDevelopmentRecoveryInput {\n readonly route: AgentRoute;\n readonly task: Task;\n readonly message: string;\n readonly images?: {\n readonly owner: string;\n readonly attachments: readonly ImageAttachment[];\n };\n}\n\n/** Starts a replacement attempt using its exact registered software agent route. */\nexport type StartSoftwareDevelopmentRecovery = (\n input: StartSoftwareDevelopmentRecoveryInput,\n) => Promise<ExecutionReference>;\n\ntype RecoveryBrief = (task: Task, repository: Repository) => Promise<string>;\n\n/** Stores, route registry, and execution boundaries used to assemble recovery launchers. */\nexport interface SoftwareDevelopmentRecoveryOptions {\n stores: SoftwareDevelopmentRecoveryStores;\n routes: AgentRouteRegistry;\n start: StartSoftwareDevelopmentRecovery;\n continueConversation: (task: Task) => Promise<ExecutionReference>;\n}\n\n/** Assemble replacement-attempt launchers for the supplied software-factory routes. */\nexport function createSoftwareDevelopmentRecoveryLaunchers(\n options: SoftwareDevelopmentRecoveryOptions,\n): SweepQueuedOptions[\"launchers\"] {\n const { stores, routes, start, continueConversation } = options;\n const briefs: Readonly<Partial<Record<AgentRouteKey, RecoveryBrief>>> = {\n \"code-change@1\": async (task, repository) =>\n withAnsweredQuestions(\n stores,\n task,\n formatFindingRemediationBrief(task, repository, await findFindingForTask(stores, task)),\n ),\n \"code-review@1\": (task, repository) => codeReviewBrief(stores, task, repository),\n \"review-follow-up@1\": (task, repository) => reviewFollowUpBrief(stores, task, repository),\n \"merge-conflict@1\": (task, repository) => mergeConflictBrief(stores, task, repository),\n \"security-scan@1\": async (task, repository) => formatDeepsecScanBrief(task, repository),\n \"verification@1\": (task, repository) => verificationTaskBrief(stores, task, repository),\n };\n const launchers: Partial<Record<AgentRouteKey, TaskLauncher>> = {};\n for (const route of routes.routes) {\n const key = routeKey(route);\n const brief =\n briefs[key] ??\n ((task: Task) =>\n withAnsweredQuestions(\n stores,\n task,\n `You are assigned replacement attempt ${task.attempt} for task ${task.id}. Read the task before starting.\\n\\n${task.work.title}\\n\\nWork input:\\n${JSON.stringify(task.work.input)}`,\n ));\n launchers[key] = async (task, repository) => {\n if (!task.work.route || routeKey(task.work.route) !== key) {\n throw new Error(`Task ${task.id} is not bound to agent route ${key}`);\n }\n if (task.replyTo.channel === \"api\") {\n const session = await stores.sessions.get(agentSessionIdSchema.parse(task.replyTo.address));\n if (session === null) {\n throw new Error(`API session binding is missing for task ${task.id}`);\n }\n if (session.taskId === task.id) {\n if (session.agent !== route.agent) {\n throw new Error(`API session agent differs from route ${route.id}`);\n }\n const message = await withAnsweredQuestions(\n stores,\n task,\n `You are assigned Factory task ${task.id}. Read it before starting.\\n\\n${session.changeId === undefined ? \"\" : `Existing change: ${session.changeId}.\\n\\n`}${session.message}`,\n );\n return start({\n route,\n task,\n message,\n ...(session.attachments?.length\n ? {\n images: {\n owner: session.owner,\n attachments: session.attachments,\n },\n }\n : {}),\n });\n }\n }\n const input = task.work.input;\n const instructions =\n isJsonObject(input) && typeof input.instructions === \"string\"\n ? input.instructions\n : undefined;\n const message =\n instructions === undefined\n ? await brief(task, repository)\n : await withAnsweredQuestions(\n stores,\n task,\n `You are assigned Factory task ${task.id}. Read it before starting.\\n\\n${task.work.title}\\n\\n${instructions}\\n\\nCompletion criteria:\\n${task.work.completionCriteria.join(\"\\n\")}`,\n );\n return start({ route, task, message });\n };\n }\n launchers[\"conversation@1\"] = async (task) => {\n if (\n task.work.route?.id !== \"conversation\" ||\n task.work.route.version !== 1 ||\n task.replyTo.channel !== \"api\"\n ) {\n throw new Error(`Task ${task.id} is not bound to conversation@1`);\n }\n const session = await stores.sessions.get(agentSessionIdSchema.parse(task.replyTo.address));\n if (!session || !(await stores.sessions.getContinuation(session.id, task.id))) {\n throw new Error(`Conversation binding is missing for task ${task.id}`);\n }\n return continueConversation(task);\n };\n return launchers;\n}\n\nfunction isJsonObject(value: JsonValue): value is { readonly [key: string]: JsonValue } {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\nasync function codeReviewBrief(\n stores: SoftwareDevelopmentRecoveryStores,\n task: Task,\n repository: Repository,\n): Promise<string> {\n const signal =\n task.origin.signalId === undefined ? null : await stores.signals.get(task.origin.signalId);\n const normalized =\n signal?.source === \"github_pr\"\n ? normalizeGitHubPullRequest({\n deliveryId: signal.deliveryId,\n event: \"pull_request\",\n payload: signal.payload,\n })\n : null;\n const changeId =\n task.changeId ??\n (await changeIdForPullRequest(stores, repository, normalized?.pullRequest.pullRequestUrl));\n const facts = [\n `You are assigned replacement attempt ${task.attempt} for code review task ${task.id}.`,\n changeId === undefined ? null : `Change under review: ${changeId}.`,\n normalized === null ? null : `Pull request: ${normalized.pullRequest.pullRequestUrl}.`,\n normalized === null ? null : `Expected head commit: ${normalized.pullRequest.headSha}.`,\n \"Read the task, fetch and review the pull request, then record exactly one review result with record_post_review.\",\n ]\n .filter((line) => line !== null)\n .join(\"\\n\\n\");\n return withAnsweredQuestions(stores, task, facts);\n}\n\nasync function reviewFollowUpBrief(\n stores: SoftwareDevelopmentRecoveryStores,\n task: Task,\n repository: Repository,\n): Promise<string> {\n const signal =\n task.origin.signalId === undefined ? null : await stores.signals.get(task.origin.signalId);\n const review =\n signal?.source === \"github_pr_comment\"\n ? normalizeFactoryReviewComment({\n event: \"pull_request_review_comment\",\n payload: signal.payload,\n })\n : null;\n const changeId =\n task.changeId ?? (await changeIdForPullRequest(stores, repository, review?.pullRequestUrl));\n const facts = [\n `You are assigned replacement attempt ${task.attempt} for factory task ${task.id}.`,\n changeId === undefined ? null : `Change under review: ${changeId}.`,\n review === null ? null : `Inline review comment: ${review.commentId}.`,\n review === null ? null : `Pull request: ${review.pullRequestUrl}.`,\n \"Read the task and originating review comment. Apply a concrete high-confidence fix, post a medium-confidence suggestion, or answer with findings, then finish the task.\",\n ]\n .filter((line) => line !== null)\n .join(\"\\n\\n\");\n return withAnsweredQuestions(stores, task, facts);\n}\n\nasync function mergeConflictBrief(\n stores: SoftwareDevelopmentRecoveryStores,\n task: Task,\n repository: Repository,\n): Promise<string> {\n const signal =\n task.origin.signalId === undefined ? null : await stores.signals.get(task.origin.signalId);\n const parsed = mergeConflictPayloadSchema.safeParse(signal?.payload);\n const facts = parsed.success\n ? [\n `You are assigned replacement attempt ${task.attempt} for merge conflict task ${task.id} in ${repository.slug}.`,\n `Pull request: ${parsed.data.prUrl}.`,\n \"Read the task, fetch the conflict with fetch_merge_conflict, resolve and test it, then publish_merge_resolution.\",\n ].join(\"\\n\\n\")\n : [\n `You are assigned replacement attempt ${task.attempt} for merge conflict task ${task.id} in ${repository.slug}.`,\n \"Read the task and recover its merge-conflict context with the dedicated tools. Do not guess a pull request identifier.\",\n ].join(\"\\n\\n\");\n return withAnsweredQuestions(stores, task, facts);\n}\n\nasync function verificationTaskBrief(\n stores: SoftwareDevelopmentRecoveryStores,\n task: Task,\n repository: Repository,\n): Promise<string> {\n if (task.changeId === undefined) throw new Error(`verification task ${task.id} has no change`);\n const change = await stores.changes.get(task.changeId);\n if (change === null) throw new Error(`change ${task.changeId} does not exist`);\n return withAnsweredQuestions(\n stores,\n task,\n formatChangeVerificationBrief(task, repository, change),\n );\n}\n\nasync function changeIdForPullRequest(\n stores: SoftwareDevelopmentRecoveryStores,\n repository: Repository,\n prUrl: string | undefined,\n): Promise<string | undefined> {\n if (prUrl === undefined) return undefined;\n return (await stores.changes.list({ limit: 1, match: { repositoryId: repository.id, prUrl } }))\n .items[0]?.id;\n}\n\nasync function withAnsweredQuestions(\n stores: SoftwareDevelopmentRecoveryStores,\n task: Task,\n brief: string,\n): Promise<string> {\n const answers = (\n await Promise.all((task.answerEventIds ?? []).map((id) => stores.answers.get(id)))\n ).flatMap((event) => (event === null ? [] : [`${event.question}\\nAnswer: ${event.answer}`]));\n return answers.length === 0 ? brief : `${brief}\\n\\nHuman answers:\\n${answers.join(\"\\n\\n\")}`;\n}\n"],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import { Task } from "../schema/task.mjs";
1
2
  import { FactoryStores } from "../store/engine.mjs";
2
3
  import { GitHubGitTransport } from "../github-transfer.mjs";
3
4
  import { z } from "zod";
@@ -18,9 +19,11 @@ interface GitHubFetchPullRequestToolOptions {
18
19
  git: GitHubGitTransport;
19
20
  /** Path of the checkout inside the sandbox. Defaults to `repo`. */
20
21
  repoPath?: string;
22
+ /** Application revision policy; a returned reason refuses the fetched head. */
23
+ validateHead?: (task: Task, headSha: string) => Promise<string | null>;
21
24
  }
22
25
  /** Machine-readable reason a trusted pull-request fetch did not complete. */
23
- type GitHubFetchPullRequestFailureCode = "change_not_found" | "change_not_owned_by_task" | "change_not_reviewable" | "repository_not_found" | "repository_disabled" | "git_fetch_failed" | "merge_base_failed";
26
+ type GitHubFetchPullRequestFailureCode = "change_not_found" | "change_not_owned_by_task" | "change_not_reviewable" | "repository_not_found" | "repository_disabled" | "head_not_reviewable" | "git_fetch_failed" | "merge_base_failed";
24
27
  /** Bounded refusal returned when a trusted pull-request fetch cannot complete. */
25
28
  interface GitHubFetchPullRequestFailure {
26
29
  fetched: false;
@@ -131,6 +131,15 @@ function createGitHubFetchPullRequestTool(input) {
131
131
  reason: `could not determine the pull request merge base: ${(mergeBase.stderr ?? "").trim()}`
132
132
  };
133
133
  const mergeBaseSha = mergeBase.stdout.trim();
134
+ if (input.validateHead !== void 0) {
135
+ task = await requireTaskExecution(stores, ctx, taskId);
136
+ const reason = await input.validateHead(task, head.stdout.trim());
137
+ if (reason !== null) return {
138
+ fetched: false,
139
+ code: "head_not_reviewable",
140
+ reason
141
+ };
142
+ }
134
143
  return {
135
144
  fetched: true,
136
145
  prNumber: currentChange.prNumber,
@@ -1 +1 @@
1
- {"version":3,"file":"eve-fetch.mjs","names":[],"sources":["../../src/pull-requests/eve-fetch.ts"],"sourcesContent":["import {\n fetchGitHubRefs,\n quoteShellArgument,\n runSandboxCommand,\n type GitHubGitTransport,\n} from \"../github-transfer\";\nimport { defineTool, type ToolContext, type ToolDefinition } from \"eve/tools\";\nimport { z } from \"zod\";\nimport { changeIdSchema } from \"../schema/id\";\nimport type { Change } from \"../schema/change\";\nimport type { Task } from \"../schema/task\";\nimport type { FactoryStores } from \"../store/engine\";\nimport { requireTaskExecution } from \"../eve/task-execution\";\nimport { requireTaskIdForSession } from \"../eve/task-session\";\n\n/** Change, repository, and Task capabilities required by the trusted pull-request fetch tool. */\nexport type GitHubFetchPullRequestToolStores = Pick<\n FactoryStores,\n \"changes\" | \"repositories\" | \"tasks\"\n>;\n\n/** Validates the Change identity supplied to the trusted pull-request fetch tool. */\nexport const gitHubFetchPullRequestInputSchema = z.object({\n changeId: changeIdSchema.describe(\"The Change linked to your Task, returned by get_task.\"),\n});\n\n/** Change identity supplied to the trusted pull-request fetch tool. */\nexport type GitHubFetchPullRequestInput = z.infer<typeof gitHubFetchPullRequestInputSchema>;\n\n/** Stores, Git credential boundary, and checkout used to construct a pull-request fetch tool. */\nexport interface GitHubFetchPullRequestToolOptions {\n stores: GitHubFetchPullRequestToolStores;\n /** Fetches Git objects outside the agent sandbox with read-only credentials. */\n git: GitHubGitTransport;\n /** Path of the checkout inside the sandbox. Defaults to `repo`. */\n repoPath?: string;\n}\n\n/** Machine-readable reason a trusted pull-request fetch did not complete. */\nexport type GitHubFetchPullRequestFailureCode =\n | \"change_not_found\"\n | \"change_not_owned_by_task\"\n | \"change_not_reviewable\"\n | \"repository_not_found\"\n | \"repository_disabled\"\n | \"git_fetch_failed\"\n | \"merge_base_failed\";\n\n/** Bounded refusal returned when a trusted pull-request fetch cannot complete. */\nexport interface GitHubFetchPullRequestFailure {\n fetched: false;\n code: GitHubFetchPullRequestFailureCode;\n reason: string;\n}\n\n/** Exact pull-request revision and diff context produced by a trusted fetch. */\nexport interface GitHubFetchPullRequestSuccess {\n fetched: true;\n prNumber: number;\n prUrl: string;\n branch: string;\n headSha: string;\n baseBranch: string;\n baseSha: string;\n mergeBaseSha: string;\n diffCommand: string;\n}\n\n/** The refusal or exact pull-request revision and diff context produced by a trusted fetch. */\nexport type GitHubFetchPullRequestResult =\n | GitHubFetchPullRequestFailure\n | GitHubFetchPullRequestSuccess;\n\n/** Eve tool that fetches the exact pull-request revision linked to the current Task. */\nexport interface GitHubFetchPullRequestTool extends ToolDefinition<\n GitHubFetchPullRequestInput,\n GitHubFetchPullRequestResult\n> {\n execute(\n input: GitHubFetchPullRequestInput,\n ctx: ToolContext,\n ): Promise<GitHubFetchPullRequestResult>;\n}\n\nfunction authorizeTaskChange(task: Task, change: Change): GitHubFetchPullRequestFailure | null {\n if (task.changeId !== undefined && task.changeId !== change.id) {\n return {\n fetched: false,\n code: \"change_not_owned_by_task\",\n reason: `change ${change.id} is not linked to task ${task.id}`,\n };\n }\n if (!task.repositoryIds.includes(change.repositoryId)) {\n return {\n fetched: false,\n code: \"change_not_owned_by_task\",\n reason: `change ${change.id} belongs to repository ${change.repositoryId}, outside task ${task.id}'s repository scope`,\n };\n }\n return null;\n}\n\n/**\n * Create the verifier's tool for fetching the change under review. The sandbox has no\n * GitHub credential. A separate trusted transport fetches Git objects;\n * only a credential-free bundle enters the agent sandbox. The pull request number and repository come from the\n * change record, never from the agent.\n */\nexport function createGitHubFetchPullRequestTool(\n input: GitHubFetchPullRequestToolOptions,\n): GitHubFetchPullRequestTool {\n const { stores } = input;\n const repoPath = input.repoPath ?? \"repo\";\n return defineTool({\n description:\n \"Fetch the pull request and the repository's current default branch, then check out the pull request as local branch `pr`. Call this after get_task, before inspecting or testing the change. Returns an exact diff command based on their merge base; use it instead of origin/HEAD.\",\n inputSchema: gitHubFetchPullRequestInputSchema,\n async execute({ changeId }, ctx): Promise<GitHubFetchPullRequestResult> {\n const taskId = requireTaskIdForSession(ctx.session?.auth.initiator);\n let task = await requireTaskExecution(stores, ctx, taskId);\n const change = await stores.changes.get(changeId);\n if (change === null) {\n return {\n fetched: false,\n code: \"change_not_found\",\n reason: `change ${changeId} does not exist`,\n };\n }\n const authorizationFailure = authorizeTaskChange(task, change);\n if (authorizationFailure !== null) return authorizationFailure;\n if (change.state !== \"verifying\" && change.state !== \"ready\") {\n return {\n fetched: false,\n code: \"change_not_reviewable\",\n reason: `change is ${change.state}, not open for review; nothing to fetch`,\n };\n }\n const repository = await stores.repositories.get(change.repositoryId);\n if (repository === null) {\n return {\n fetched: false,\n code: \"repository_not_found\",\n reason: `repository ${change.repositoryId} does not exist`,\n };\n }\n if (!repository.enabled) {\n return {\n fetched: false,\n code: \"repository_disabled\",\n reason: `repository ${change.repositoryId} is disabled`,\n };\n }\n\n // Fetch into a remote-tracking ref (never the checked-out branch, which\n // git refuses to update), then move the local `pr` branch onto it so a\n // repeated call picks up a force-pushed head.\n const sandbox = await ctx.getSandbox();\n const currentChange = await stores.changes.get(changeId);\n if (currentChange === null) {\n return {\n fetched: false,\n code: \"change_not_found\",\n reason: `change ${changeId} does not exist`,\n };\n }\n if (currentChange.state !== \"verifying\" && currentChange.state !== \"ready\") {\n return {\n fetched: false,\n code: \"change_not_reviewable\",\n reason: `change is ${currentChange.state}, not open for review; nothing to fetch`,\n };\n }\n const currentRepository = await stores.repositories.get(currentChange.repositoryId);\n if (currentRepository === null) {\n return {\n fetched: false,\n code: \"repository_not_found\",\n reason: `repository ${currentChange.repositoryId} does not exist`,\n };\n }\n if (!currentRepository.enabled) {\n return {\n fetched: false,\n code: \"repository_disabled\",\n reason: `repository ${currentChange.repositoryId} is disabled`,\n };\n }\n task = await requireTaskExecution(stores, ctx, taskId);\n const currentAuthorizationFailure = authorizeTaskChange(task, currentChange);\n if (currentAuthorizationFailure !== null) return currentAuthorizationFailure;\n const tracking = `refs/remotes/origin/pr-${currentChange.prNumber}`;\n const baseTracking = `refs/remotes/origin/${currentRepository.defaultBranch}`;\n const git = [\"git\", \"-C\", repoPath].map(quoteShellArgument).join(\" \");\n try {\n await fetchGitHubRefs({\n sandbox,\n transport: input.git,\n repository: currentRepository.slug,\n refs: [\n { remote: `refs/heads/${currentRepository.defaultBranch}`, local: baseTracking },\n { remote: `refs/pull/${currentChange.prNumber}/head`, local: tracking },\n ],\n repositoryPath: repoPath,\n });\n await runSandboxCommand(sandbox, [\"git\", \"-C\", repoPath, \"checkout\", \"-B\", \"pr\", tracking]);\n } catch (error) {\n return {\n fetched: false,\n code: \"git_fetch_failed\",\n reason: `git fetch of pull request ${currentChange.prNumber} failed: ${error instanceof Error ? error.message : \"Git transfer failed\"}`,\n };\n }\n\n const head = await sandbox.run({ command: `${git} rev-parse HEAD` });\n const base = await sandbox.run({ command: `${git} rev-parse ${baseTracking}` });\n const mergeBase = await sandbox.run({ command: `${git} merge-base ${baseTracking} pr` });\n if (typeof mergeBase.exitCode === \"number\" && mergeBase.exitCode !== 0) {\n return {\n fetched: false,\n code: \"merge_base_failed\",\n reason: `could not determine the pull request merge base: ${(mergeBase.stderr ?? \"\").trim()}`,\n };\n }\n const mergeBaseSha = mergeBase.stdout.trim();\n return {\n fetched: true,\n prNumber: currentChange.prNumber,\n prUrl: currentChange.prUrl,\n branch: \"pr\",\n headSha: head.stdout.trim(),\n baseBranch: currentRepository.defaultBranch,\n baseSha: base.stdout.trim(),\n mergeBaseSha,\n diffCommand: `${git} diff ${mergeBaseSha}..pr`,\n };\n },\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"eve-fetch.mjs","names":[],"sources":["../../src/pull-requests/eve-fetch.ts"],"sourcesContent":["import {\n fetchGitHubRefs,\n quoteShellArgument,\n runSandboxCommand,\n type GitHubGitTransport,\n} from \"../github-transfer\";\nimport { defineTool, type ToolContext, type ToolDefinition } from \"eve/tools\";\nimport { z } from \"zod\";\nimport { changeIdSchema } from \"../schema/id\";\nimport type { Change } from \"../schema/change\";\nimport type { Task } from \"../schema/task\";\nimport type { FactoryStores } from \"../store/engine\";\nimport { requireTaskExecution } from \"../eve/task-execution\";\nimport { requireTaskIdForSession } from \"../eve/task-session\";\n\n/** Change, repository, and Task capabilities required by the trusted pull-request fetch tool. */\nexport type GitHubFetchPullRequestToolStores = Pick<\n FactoryStores,\n \"changes\" | \"repositories\" | \"tasks\"\n>;\n\n/** Validates the Change identity supplied to the trusted pull-request fetch tool. */\nexport const gitHubFetchPullRequestInputSchema = z.object({\n changeId: changeIdSchema.describe(\"The Change linked to your Task, returned by get_task.\"),\n});\n\n/** Change identity supplied to the trusted pull-request fetch tool. */\nexport type GitHubFetchPullRequestInput = z.infer<typeof gitHubFetchPullRequestInputSchema>;\n\n/** Stores, Git credential boundary, and checkout used to construct a pull-request fetch tool. */\nexport interface GitHubFetchPullRequestToolOptions {\n stores: GitHubFetchPullRequestToolStores;\n /** Fetches Git objects outside the agent sandbox with read-only credentials. */\n git: GitHubGitTransport;\n /** Path of the checkout inside the sandbox. Defaults to `repo`. */\n repoPath?: string;\n /** Application revision policy; a returned reason refuses the fetched head. */\n validateHead?: (task: Task, headSha: string) => Promise<string | null>;\n}\n\n/** Machine-readable reason a trusted pull-request fetch did not complete. */\nexport type GitHubFetchPullRequestFailureCode =\n | \"change_not_found\"\n | \"change_not_owned_by_task\"\n | \"change_not_reviewable\"\n | \"repository_not_found\"\n | \"repository_disabled\"\n | \"head_not_reviewable\"\n | \"git_fetch_failed\"\n | \"merge_base_failed\";\n\n/** Bounded refusal returned when a trusted pull-request fetch cannot complete. */\nexport interface GitHubFetchPullRequestFailure {\n fetched: false;\n code: GitHubFetchPullRequestFailureCode;\n reason: string;\n}\n\n/** Exact pull-request revision and diff context produced by a trusted fetch. */\nexport interface GitHubFetchPullRequestSuccess {\n fetched: true;\n prNumber: number;\n prUrl: string;\n branch: string;\n headSha: string;\n baseBranch: string;\n baseSha: string;\n mergeBaseSha: string;\n diffCommand: string;\n}\n\n/** The refusal or exact pull-request revision and diff context produced by a trusted fetch. */\nexport type GitHubFetchPullRequestResult =\n | GitHubFetchPullRequestFailure\n | GitHubFetchPullRequestSuccess;\n\n/** Eve tool that fetches the exact pull-request revision linked to the current Task. */\nexport interface GitHubFetchPullRequestTool extends ToolDefinition<\n GitHubFetchPullRequestInput,\n GitHubFetchPullRequestResult\n> {\n execute(\n input: GitHubFetchPullRequestInput,\n ctx: ToolContext,\n ): Promise<GitHubFetchPullRequestResult>;\n}\n\nfunction authorizeTaskChange(task: Task, change: Change): GitHubFetchPullRequestFailure | null {\n if (task.changeId !== undefined && task.changeId !== change.id) {\n return {\n fetched: false,\n code: \"change_not_owned_by_task\",\n reason: `change ${change.id} is not linked to task ${task.id}`,\n };\n }\n if (!task.repositoryIds.includes(change.repositoryId)) {\n return {\n fetched: false,\n code: \"change_not_owned_by_task\",\n reason: `change ${change.id} belongs to repository ${change.repositoryId}, outside task ${task.id}'s repository scope`,\n };\n }\n return null;\n}\n\n/**\n * Create the verifier's tool for fetching the change under review. The sandbox has no\n * GitHub credential. A separate trusted transport fetches Git objects;\n * only a credential-free bundle enters the agent sandbox. The pull request number and repository come from the\n * change record, never from the agent.\n */\nexport function createGitHubFetchPullRequestTool(\n input: GitHubFetchPullRequestToolOptions,\n): GitHubFetchPullRequestTool {\n const { stores } = input;\n const repoPath = input.repoPath ?? \"repo\";\n return defineTool({\n description:\n \"Fetch the pull request and the repository's current default branch, then check out the pull request as local branch `pr`. Call this after get_task, before inspecting or testing the change. Returns an exact diff command based on their merge base; use it instead of origin/HEAD.\",\n inputSchema: gitHubFetchPullRequestInputSchema,\n async execute({ changeId }, ctx): Promise<GitHubFetchPullRequestResult> {\n const taskId = requireTaskIdForSession(ctx.session?.auth.initiator);\n let task = await requireTaskExecution(stores, ctx, taskId);\n const change = await stores.changes.get(changeId);\n if (change === null) {\n return {\n fetched: false,\n code: \"change_not_found\",\n reason: `change ${changeId} does not exist`,\n };\n }\n const authorizationFailure = authorizeTaskChange(task, change);\n if (authorizationFailure !== null) return authorizationFailure;\n if (change.state !== \"verifying\" && change.state !== \"ready\") {\n return {\n fetched: false,\n code: \"change_not_reviewable\",\n reason: `change is ${change.state}, not open for review; nothing to fetch`,\n };\n }\n const repository = await stores.repositories.get(change.repositoryId);\n if (repository === null) {\n return {\n fetched: false,\n code: \"repository_not_found\",\n reason: `repository ${change.repositoryId} does not exist`,\n };\n }\n if (!repository.enabled) {\n return {\n fetched: false,\n code: \"repository_disabled\",\n reason: `repository ${change.repositoryId} is disabled`,\n };\n }\n\n // Fetch into a remote-tracking ref (never the checked-out branch, which\n // git refuses to update), then move the local `pr` branch onto it so a\n // repeated call picks up a force-pushed head.\n const sandbox = await ctx.getSandbox();\n const currentChange = await stores.changes.get(changeId);\n if (currentChange === null) {\n return {\n fetched: false,\n code: \"change_not_found\",\n reason: `change ${changeId} does not exist`,\n };\n }\n if (currentChange.state !== \"verifying\" && currentChange.state !== \"ready\") {\n return {\n fetched: false,\n code: \"change_not_reviewable\",\n reason: `change is ${currentChange.state}, not open for review; nothing to fetch`,\n };\n }\n const currentRepository = await stores.repositories.get(currentChange.repositoryId);\n if (currentRepository === null) {\n return {\n fetched: false,\n code: \"repository_not_found\",\n reason: `repository ${currentChange.repositoryId} does not exist`,\n };\n }\n if (!currentRepository.enabled) {\n return {\n fetched: false,\n code: \"repository_disabled\",\n reason: `repository ${currentChange.repositoryId} is disabled`,\n };\n }\n task = await requireTaskExecution(stores, ctx, taskId);\n const currentAuthorizationFailure = authorizeTaskChange(task, currentChange);\n if (currentAuthorizationFailure !== null) return currentAuthorizationFailure;\n const tracking = `refs/remotes/origin/pr-${currentChange.prNumber}`;\n const baseTracking = `refs/remotes/origin/${currentRepository.defaultBranch}`;\n const git = [\"git\", \"-C\", repoPath].map(quoteShellArgument).join(\" \");\n try {\n await fetchGitHubRefs({\n sandbox,\n transport: input.git,\n repository: currentRepository.slug,\n refs: [\n { remote: `refs/heads/${currentRepository.defaultBranch}`, local: baseTracking },\n { remote: `refs/pull/${currentChange.prNumber}/head`, local: tracking },\n ],\n repositoryPath: repoPath,\n });\n await runSandboxCommand(sandbox, [\"git\", \"-C\", repoPath, \"checkout\", \"-B\", \"pr\", tracking]);\n } catch (error) {\n return {\n fetched: false,\n code: \"git_fetch_failed\",\n reason: `git fetch of pull request ${currentChange.prNumber} failed: ${error instanceof Error ? error.message : \"Git transfer failed\"}`,\n };\n }\n\n const head = await sandbox.run({ command: `${git} rev-parse HEAD` });\n const base = await sandbox.run({ command: `${git} rev-parse ${baseTracking}` });\n const mergeBase = await sandbox.run({ command: `${git} merge-base ${baseTracking} pr` });\n if (typeof mergeBase.exitCode === \"number\" && mergeBase.exitCode !== 0) {\n return {\n fetched: false,\n code: \"merge_base_failed\",\n reason: `could not determine the pull request merge base: ${(mergeBase.stderr ?? \"\").trim()}`,\n };\n }\n const mergeBaseSha = mergeBase.stdout.trim();\n if (input.validateHead !== undefined) {\n task = await requireTaskExecution(stores, ctx, taskId);\n const reason = await input.validateHead(task, head.stdout.trim());\n if (reason !== null) return { fetched: false, code: \"head_not_reviewable\", reason };\n }\n return {\n fetched: true,\n prNumber: currentChange.prNumber,\n prUrl: currentChange.prUrl,\n branch: \"pr\",\n headSha: head.stdout.trim(),\n baseBranch: currentRepository.defaultBranch,\n baseSha: base.stdout.trim(),\n mergeBaseSha,\n diffCommand: `${git} diff ${mergeBaseSha}..pr`,\n };\n },\n });\n}\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { TaskId } from "../schema/id.mjs";
2
- import { Repository, RepositorySlug } from "../schema/repository.mjs";
3
2
  import { Change } from "../schema/change.mjs";
3
+ import { Repository, RepositorySlug } from "../schema/repository.mjs";
4
4
  import { FactoryStores } from "../store/engine.mjs";
5
5
  import { OpenPullRequestInput, PublishedPullRequest, PullRequestPublicationGateInput, PullRequestPublicationGateResult, PullRequestPublicationOwner, PushCheckpointInput, TaskPullRequest } from "./contracts.mjs";
6
6
  import { CommittedGitChange, CreatedGitHubPublicationPullRequest, GitHubPublicationClient, GitHubPublicationClientOptions, SandboxGit } from "../github-publication.mjs";
@@ -98,6 +98,15 @@ async function acceptSessionHead(sandbox, client, sha) {
98
98
  await rememberSessionHead(sandbox, sha);
99
99
  return unchanged;
100
100
  }
101
+ /** Whether the Task branch already holds the checkout's squashed commit on the same base. */
102
+ async function taskBranchMatchesCheckout(sandbox, client, headSha, base) {
103
+ const head = await client.commit({ sha: headSha });
104
+ if (head.parentShas.length !== 1 || head.parentShas[0] !== base) return false;
105
+ return head.treeSha === (await runSandboxGit({
106
+ sandbox,
107
+ args: ["rev-parse", "HEAD^{tree}"]
108
+ })).trim();
109
+ }
101
110
  function sessionPublication(pull, branch, changedFiles = 0) {
102
111
  return {
103
112
  prNumber: pull.pullRequestNumber,
@@ -294,12 +303,15 @@ function createGitHubPullRequestPublisher(options) {
294
303
  baseRef: input.linkedChange === void 0 ? "origin/HEAD" : `refs/remotes/origin/pr-${input.linkedChange.prNumber}`
295
304
  });
296
305
  if (input.linkedChange !== void 0 && change.base !== pull.head.sha) throw new Error(`pull request #${input.linkedChange.prNumber} changed since it was fetched; fetch it again`);
297
- const sha = await createCommit(client, change, `${pull.title}\n\n${pull.body ?? ""}`);
298
- await client.updateBranch({
299
- branch: pull.branch,
300
- sha,
301
- force: input.linkedChange === void 0
302
- });
306
+ const unchanged = input.linkedChange === void 0 && await taskBranchMatchesCheckout(input.sandbox, client, pull.head.sha, change.base);
307
+ if (!unchanged) {
308
+ const sha = await createCommit(client, change, `${pull.title}\n\n${pull.body ?? ""}`);
309
+ await client.updateBranch({
310
+ branch: pull.branch,
311
+ sha,
312
+ force: input.linkedChange === void 0
313
+ });
314
+ }
303
315
  let draft = pull.draft;
304
316
  if (input.checkpoint.ready && draft) {
305
317
  await client.markReady({ nodeId: pull.nodeId });
@@ -309,7 +321,7 @@ function createGitHubPullRequestPublisher(options) {
309
321
  prNumber: pull.pullRequestNumber,
310
322
  prUrl: pull.pullRequestUrl,
311
323
  branch: pull.branch,
312
- changedFiles: change.changedFiles,
324
+ changedFiles: unchanged ? 0 : change.changedFiles,
313
325
  draft
314
326
  };
315
327
  }
@@ -1 +1 @@
1
- {"version":3,"file":"github-publisher.mjs","names":[],"sources":["../../src/pull-requests/github-publisher.ts"],"sourcesContent":["import { redactedErrorMessage } from \"../errors\";\nimport {\n createGitHubPublicationClient,\n factorySessionBranch,\n factoryTaskBranch,\n GitHubPublicationApiError,\n isSafeGitRef,\n readCommittedGitChange,\n runSandboxGit,\n type CommittedGitChange,\n type CreatedGitHubPublicationPullRequest,\n type GitHubPublicationClient,\n type GitHubPublicationClientOptions,\n type GitHubPublicationPullRequest,\n type SandboxGit,\n} from \"../github-publication\";\nimport type { Change } from \"../schema/change\";\nimport type { TaskId } from \"../schema/id\";\nimport type { Repository, RepositorySlug } from \"../schema/repository\";\nimport type { AgentSessionId } from \"../schema/session\";\nimport type { FactoryStores } from \"../store/engine\";\nimport type {\n OpenPullRequestInput,\n PublishedPullRequest,\n PullRequestPublicationGateInput,\n PullRequestPublicationGateResult,\n PullRequestPublicationOwner,\n PushCheckpointInput,\n TaskPullRequest,\n} from \"./contracts\";\n\n/** Repository credential and client factories required by software publication. */\nexport interface GitHubPullRequestPublisherOptions {\n tokenForRepository(repositorySlug: RepositorySlug): Promise<string>;\n fetch?: typeof globalThis.fetch;\n createClient?: (options: GitHubPublicationClientOptions) => GitHubPublicationClient;\n /** Publishes the initial commit, branch, and pull request through an application credential boundary. */\n publishPullRequest?: (input: {\n repository: RepositorySlug;\n baseCommit: string;\n tree: CommittedGitChange[\"tree\"];\n commitMessage: string;\n branch: string;\n title: string;\n body: string;\n baseBranch: string;\n draft: boolean;\n }) => Promise<CreatedGitHubPublicationPullRequest & { commitSha: string }>;\n}\n\n/** Task- and session-aware operations for idempotent GitHub pull-request publication. */\nexport interface GitHubPullRequestPublisher {\n requireTaskPullRequest(input: RequireTaskPullRequestInput): Promise<TaskPullRequest>;\n openPullRequest(input: PublishGitHubPullRequestInput): Promise<PublishedPullRequest>;\n pushCheckpoint(input: PushGitHubCheckpointInput): Promise<PublishedPullRequest>;\n}\n\n/** Repository and Task whose owned pull request must already exist. */\nexport interface RequireTaskPullRequestInput {\n repository: Repository;\n taskId: TaskId;\n}\n\n/** Sandbox checkout and publication request used to open a GitHub pull request. */\nexport interface PublishGitHubPullRequestInput {\n sandbox: SandboxGit;\n repository: Repository;\n owner: PullRequestPublicationOwner;\n pullRequest: OpenPullRequestInput;\n}\n\n/** Sandbox checkout and optional linked Change used to publish a checkpoint. */\nexport interface PushGitHubCheckpointInput {\n sandbox: SandboxGit;\n repository: Repository;\n owner: PullRequestPublicationOwner;\n checkpoint: PushCheckpointInput;\n linkedChange?: Pick<Change, \"prNumber\" | \"prUrl\">;\n}\n\nasync function clientForRepository(\n options: GitHubPullRequestPublisherOptions,\n repository: Repository,\n): Promise<GitHubPublicationClient> {\n const token = await options.tokenForRepository(repository.slug);\n return (options.createClient ?? createGitHubPublicationClient)({\n repository: repository.slug,\n token,\n fetch: options.fetch,\n });\n}\n\nfunction repositoryOwner(repository: Repository): string {\n const owner = repository.slug.split(\"/\")[0];\n if (owner === undefined || owner === \"\") {\n throw new Error(`invalid repository slug: ${repository.slug}`);\n }\n return owner;\n}\n\nasync function findOpenPullRequest(\n client: GitHubPublicationClient,\n repository: Repository,\n taskId: TaskId,\n): Promise<GitHubPublicationPullRequest & { branch: string }> {\n const branch = factoryTaskBranch(taskId);\n const open = await client.pullRequests({\n state: \"open\",\n head: `${repositoryOwner(repository)}:${branch}`,\n });\n const pull = open.find((candidate) => candidate.head.repository === repository.slug);\n if (pull === undefined) {\n throw new Error(`no open pull request on ${branch}; call open_pull_request first`);\n }\n return { ...pull, branch };\n}\n\nasync function findLinkedPullRequest(\n client: GitHubPublicationClient,\n repository: Repository,\n change: Pick<Change, \"prNumber\" | \"prUrl\">,\n): Promise<GitHubPublicationPullRequest & { branch: string }> {\n const pull = await client.pullRequest({ pullRequestNumber: change.prNumber });\n const branch = pull.head.branch;\n if (\n pull.state !== \"open\" ||\n pull.pullRequestUrl !== change.prUrl ||\n pull.head.repository !== repository.slug ||\n !isSafeGitRef(branch)\n ) {\n throw new Error(\n `Change pull request #${change.prNumber} is not an open same-repository branch`,\n );\n }\n return { ...pull, branch };\n}\n\nasync function createCommit(\n client: GitHubPublicationClient,\n change: CommittedGitChange,\n message: string,\n parent = change.base,\n): Promise<string> {\n const baseCommit = await client.commit({ sha: change.base });\n const tree = await client.createTree({ baseTreeSha: baseCommit.treeSha, entries: change.tree });\n return client.createCommit({ message, treeSha: tree, parentShas: [parent] });\n}\n\nasync function sessionPullRequest(\n client: GitHubPublicationClient,\n repository: Repository,\n branch: string,\n): Promise<GitHubPublicationPullRequest | undefined> {\n const pulls = await client.pullRequests({\n state: \"all\",\n head: `${repositoryOwner(repository)}:${branch}`,\n });\n const pull = pulls.find(\n (candidate) =>\n candidate.head.branch === branch && candidate.head.repository === repository.slug,\n );\n if (pull !== undefined && pull.state !== \"open\") {\n throw new Error(\n \"This session's pull request is merged or closed. Start a new session for a new pull request.\",\n );\n }\n return pull;\n}\n\nasync function sessionHead(sandbox: SandboxGit): Promise<string> {\n const result = await sandbox.run({ command: \"git -C repo config --get factory.publishedHead\" });\n if (result.exitCode === 1) return \"\";\n if (result.exitCode !== undefined && result.exitCode !== 0) {\n throw new Error(\"Could not read the session's published head.\");\n }\n return result.stdout.trim();\n}\n\nasync function rememberSessionHead(sandbox: SandboxGit, sha: string): Promise<void> {\n if (!/^[0-9a-f]{40}$/u.test(sha)) throw new Error(\"GitHub returned an invalid commit SHA.\");\n await runSandboxGit({ sandbox, args: [\"config\", \"factory.publishedHead\", sha] });\n}\n\nasync function assertSessionCheckout(sandbox: SandboxGit, repository: Repository): Promise<void> {\n if (\n (await runSandboxGit({ sandbox, args: [\"remote\", \"get-url\", \"origin\"] })).trim() !==\n `https://github.com/${repository.slug}.git`\n ) {\n throw new Error(`sandbox checkout does not match session repository ${repository.slug}`);\n }\n if ((await runSandboxGit({ sandbox, args: [\"status\", \"--porcelain\"] })).trim() !== \"\") {\n throw new Error(\"Commit your changes before publishing the pull request.\");\n }\n}\n\nasync function acceptSessionHead(\n sandbox: SandboxGit,\n client: GitHubPublicationClient,\n sha: string,\n): Promise<boolean> {\n const expected = await sessionHead(sandbox);\n const commit = await client.commit({ sha });\n const unchanged =\n commit.treeSha ===\n (await runSandboxGit({ sandbox, args: [\"rev-parse\", \"HEAD^{tree}\"] })).trim();\n const recovered = unchanged && (expected === \"\" || commit.parentShas.includes(expected));\n if (expected !== sha && !recovered) {\n throw new Error(\n \"The session PR changed since checkout or publication. Refusing to overwrite newer commits; reload its checkout before continuing.\",\n );\n }\n await rememberSessionHead(sandbox, sha);\n return unchanged;\n}\n\nfunction sessionPublication(\n pull: Pick<GitHubPublicationPullRequest, \"pullRequestNumber\" | \"pullRequestUrl\" | \"draft\">,\n branch: string,\n changedFiles = 0,\n): PublishedPullRequest {\n return {\n prNumber: pull.pullRequestNumber,\n prUrl: pull.pullRequestUrl,\n branch,\n changedFiles,\n draft: pull.draft,\n };\n}\n\n/** Supplied software workflow for safe Task- and session-owned GitHub publication. */\nexport function createGitHubPullRequestPublisher(\n options: GitHubPullRequestPublisherOptions,\n): GitHubPullRequestPublisher {\n async function requireTaskPullRequest(\n input: RequireTaskPullRequestInput,\n ): Promise<TaskPullRequest> {\n const client = await clientForRepository(options, input.repository);\n const pull = await findOpenPullRequest(client, input.repository, input.taskId);\n return {\n prNumber: pull.pullRequestNumber,\n prUrl: pull.pullRequestUrl,\n branch: pull.branch,\n };\n }\n\n async function openSessionPullRequest(\n sandbox: SandboxGit,\n repository: Repository,\n sessionId: AgentSessionId,\n input: OpenPullRequestInput,\n ): Promise<PublishedPullRequest> {\n const branch = factorySessionBranch(sessionId);\n await assertSessionCheckout(sandbox, repository);\n if (options.publishPullRequest !== undefined) {\n const change = await readCommittedGitChange({ sandbox, repositorySlug: repository.slug });\n const published = await options.publishPullRequest({\n repository: repository.slug,\n baseCommit: change.base,\n tree: change.tree,\n commitMessage: change.commitMessage,\n branch,\n title: input.title,\n body: input.body,\n baseBranch: repository.defaultBranch,\n draft: true,\n });\n await rememberSessionHead(sandbox, published.commitSha);\n return {\n prNumber: published.pullRequestNumber,\n prUrl: published.pullRequestUrl,\n branch,\n changedFiles: change.changedFiles,\n draft: published.draft,\n };\n }\n const client = await clientForRepository(options, repository);\n const existing = await sessionPullRequest(client, repository, branch);\n if (existing !== undefined) {\n await acceptSessionHead(sandbox, client, existing.head.sha);\n return sessionPublication(existing, branch);\n }\n\n let sha = await client.branchHead({ branch });\n let changedFiles = 0;\n if (sha === null) {\n const change = await readCommittedGitChange({ sandbox, repositorySlug: repository.slug });\n changedFiles = change.changedFiles;\n sha = await createCommit(client, change, `${input.title}\\n\\n${input.body}`);\n try {\n await client.createBranch({ branch, sha });\n } catch (error) {\n if ((await client.branchHead({ branch })) !== sha) throw error;\n }\n await rememberSessionHead(sandbox, sha);\n } else {\n await acceptSessionHead(sandbox, client, sha);\n }\n try {\n const pull = await client.createPullRequest({\n title: input.title,\n body: input.body,\n head: branch,\n base: repository.defaultBranch,\n draft: true,\n });\n return {\n prNumber: pull.pullRequestNumber,\n prUrl: pull.pullRequestUrl,\n branch,\n changedFiles,\n draft: pull.draft,\n };\n } catch (error) {\n const created = await sessionPullRequest(client, repository, branch);\n if (created === undefined) throw error;\n return sessionPublication(created, branch, changedFiles);\n }\n }\n\n async function openPullRequest(\n input: PublishGitHubPullRequestInput,\n ): Promise<PublishedPullRequest> {\n if (typeof input.owner !== \"string\") {\n return openSessionPullRequest(\n input.sandbox,\n input.repository,\n input.owner.sessionId,\n input.pullRequest,\n );\n }\n const change = await readCommittedGitChange({\n sandbox: input.sandbox,\n repositorySlug: input.repository.slug,\n });\n if (options.publishPullRequest !== undefined) {\n const branch = factoryTaskBranch(input.owner);\n const published = await options.publishPullRequest({\n repository: input.repository.slug,\n baseCommit: change.base,\n tree: change.tree,\n commitMessage: change.commitMessage,\n branch,\n title: input.pullRequest.title,\n body: input.pullRequest.body,\n baseBranch: input.repository.defaultBranch,\n draft: true,\n });\n return {\n prNumber: published.pullRequestNumber,\n prUrl: published.pullRequestUrl,\n branch,\n changedFiles: change.changedFiles,\n draft: published.draft,\n };\n }\n const client = await clientForRepository(options, input.repository);\n const branch = factoryTaskBranch(input.owner);\n const existing = (\n await client.pullRequests({\n state: \"all\",\n head: `${repositoryOwner(input.repository)}:${branch}`,\n })\n ).find((pull) => pull.head.branch === branch && pull.head.repository === input.repository.slug);\n if (existing !== undefined) {\n if (existing.state !== \"open\")\n throw new Error(\n \"This Task's pull request is merged or closed. Start new work for a new pull request.\",\n );\n return {\n prNumber: existing.pullRequestNumber,\n prUrl: existing.pullRequestUrl,\n branch,\n changedFiles: 0,\n draft: existing.draft,\n };\n }\n const sha = await createCommit(\n client,\n change,\n `${input.pullRequest.title}\\n\\n${input.pullRequest.body}`,\n );\n await client.createBranch({ branch, sha });\n const pull = await client.createPullRequest({\n title: input.pullRequest.title,\n body: input.pullRequest.body,\n head: branch,\n base: input.repository.defaultBranch,\n draft: true,\n });\n return {\n prNumber: pull.pullRequestNumber,\n prUrl: pull.pullRequestUrl,\n branch,\n changedFiles: change.changedFiles,\n draft: pull.draft,\n };\n }\n\n async function pushSessionCheckpoint(\n sandbox: SandboxGit,\n repository: Repository,\n sessionId: AgentSessionId,\n input: PushCheckpointInput,\n ): Promise<PublishedPullRequest> {\n const branch = factorySessionBranch(sessionId);\n await assertSessionCheckout(sandbox, repository);\n const client = await clientForRepository(options, repository);\n const pull = await sessionPullRequest(client, repository, branch);\n if (pull === undefined) {\n throw new Error(`no open pull request on ${branch}; call open_pull_request first`);\n }\n const unchanged = await acceptSessionHead(sandbox, client, pull.head.sha);\n let changedFiles = 0;\n if (!unchanged) {\n const change = await readCommittedGitChange({\n sandbox,\n repositorySlug: repository.slug,\n allowEmpty: true,\n });\n changedFiles = change.changedFiles;\n const sha = await createCommit(\n client,\n change,\n `${pull.title}\\n\\n${pull.body ?? \"\"}`,\n pull.head.sha,\n );\n await client.updateBranch({ branch, sha, force: false });\n await rememberSessionHead(sandbox, sha);\n }\n const published = sessionPublication(pull, branch, changedFiles);\n if (input.ready && published.draft) {\n await client.markReady({ nodeId: pull.nodeId });\n published.draft = false;\n }\n return published;\n }\n\n async function pushCheckpoint(input: PushGitHubCheckpointInput): Promise<PublishedPullRequest> {\n if (typeof input.owner !== \"string\") {\n return pushSessionCheckpoint(\n input.sandbox,\n input.repository,\n input.owner.sessionId,\n input.checkpoint,\n );\n }\n const client = await clientForRepository(options, input.repository);\n const pull =\n input.linkedChange === undefined\n ? await findOpenPullRequest(client, input.repository, input.owner)\n : await findLinkedPullRequest(client, input.repository, input.linkedChange);\n const change = await readCommittedGitChange({\n sandbox: input.sandbox,\n repositorySlug: input.repository.slug,\n baseRef:\n input.linkedChange === undefined\n ? \"origin/HEAD\"\n : `refs/remotes/origin/pr-${input.linkedChange.prNumber}`,\n });\n if (input.linkedChange !== undefined && change.base !== pull.head.sha) {\n throw new Error(\n `pull request #${input.linkedChange.prNumber} changed since it was fetched; fetch it again`,\n );\n }\n const sha = await createCommit(client, change, `${pull.title}\\n\\n${pull.body ?? \"\"}`);\n await client.updateBranch({\n branch: pull.branch,\n sha,\n force: input.linkedChange === undefined,\n });\n let draft = pull.draft;\n if (input.checkpoint.ready && draft) {\n await client.markReady({ nodeId: pull.nodeId });\n draft = false;\n }\n return {\n prNumber: pull.pullRequestNumber,\n prUrl: pull.pullRequestUrl,\n branch: pull.branch,\n changedFiles: change.changedFiles,\n draft,\n };\n }\n\n return Object.freeze({ requireTaskPullRequest, openPullRequest, pushCheckpoint });\n}\n\n/** Store and repository lookup dependencies for the pull-request publication gate. */\nexport interface GitHubPullRequestPublicationGateOptions extends GitHubPullRequestPublisherOptions {\n stores: Pick<FactoryStores, \"tasks\" | \"changes\">;\n repositoryForTask(taskId: TaskId): Promise<Repository>;\n}\n\n/** Validates that one pull request belongs to its exact GitHub Task and Change context. */\nexport type GitHubPullRequestPublicationGate = (\n input: PullRequestPublicationGateInput,\n) => Promise<PullRequestPublicationGateResult>;\n\n/** Fail-closed gate for the pull request a supplied code-change workflow records. */\nexport function createGitHubPullRequestPublicationGate(\n options: GitHubPullRequestPublicationGateOptions,\n): GitHubPullRequestPublicationGate {\n return async (\n input: PullRequestPublicationGateInput,\n ): Promise<PullRequestPublicationGateResult> => {\n let repository: Repository;\n let linkedChange: Awaited<ReturnType<FactoryStores[\"changes\"][\"get\"]>>;\n try {\n repository = await options.repositoryForTask(input.taskId);\n if (repository.id !== input.repositoryId) {\n return {\n ok: false,\n code: \"repository_mismatch\",\n reason: \"repositoryId does not match the task repository\",\n };\n }\n const task = await options.stores.tasks.get(input.taskId);\n if (task === null) {\n return {\n ok: false,\n code: \"task_not_found\",\n reason: `task ${input.taskId} does not exist`,\n };\n }\n linkedChange =\n task.changeId === undefined ? null : await options.stores.changes.get(task.changeId);\n if (task.changeId !== undefined && linkedChange === null) {\n return {\n ok: false,\n code: \"change_not_found\",\n reason: `change ${task.changeId} does not exist`,\n };\n }\n if (\n linkedChange !== null &&\n (linkedChange.repositoryId !== repository.id ||\n linkedChange.prNumber !== input.prNumber ||\n linkedChange.prUrl !== input.prUrl)\n ) {\n return {\n ok: false,\n code: \"linked_change_mismatch\",\n reason: \"pull request does not match the Change linked to this task\",\n };\n }\n } catch (error) {\n return {\n ok: false,\n code: \"repository_lookup_failed\",\n reason: redactedErrorMessage(error),\n };\n }\n\n let client: GitHubPublicationClient;\n try {\n client = await clientForRepository(options, repository);\n } catch (error) {\n return {\n ok: false,\n code: \"authentication_failed\",\n reason: `could not authenticate to GitHub: ${redactedErrorMessage(error)}`,\n };\n }\n\n let pull: GitHubPublicationPullRequest;\n try {\n pull = await client.pullRequest({ pullRequestNumber: input.prNumber });\n } catch (error) {\n return {\n ok: false,\n code:\n error instanceof GitHubPublicationApiError && error.status === 404\n ? \"pull_request_not_found\"\n : \"pull_request_lookup_failed\",\n reason:\n error instanceof GitHubPublicationApiError && error.status === 404\n ? `pull request #${input.prNumber} does not exist on ${repository.slug}`\n : `could not verify pull request #${input.prNumber} on ${repository.slug}`,\n };\n }\n if (pull.pullRequestUrl !== input.prUrl) {\n return {\n ok: false,\n code: \"pull_request_url_mismatch\",\n reason: `prUrl does not match pull request #${input.prNumber} on ${repository.slug}`,\n };\n }\n if (pull.state !== \"open\") {\n return {\n ok: false,\n code: \"pull_request_closed\",\n reason: `pull request #${input.prNumber} is not open`,\n };\n }\n const branch = factoryTaskBranch(input.taskId);\n if (\n pull.head.repository !== repository.slug ||\n (linkedChange === null && pull.head.branch !== branch)\n ) {\n return {\n ok: false,\n code: \"pull_request_branch_mismatch\",\n reason:\n linkedChange === null\n ? `pull request #${input.prNumber} is not on this task's branch ${branch}`\n : `pull request #${input.prNumber} is not a same-repository branch`,\n };\n }\n if (pull.draft) {\n return {\n ok: false,\n code: \"pull_request_draft\",\n reason: `pull request #${input.prNumber} is still a draft; call push_checkpoint with ready: true first`,\n };\n }\n return { ok: true };\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"github-publisher.mjs","names":[],"sources":["../../src/pull-requests/github-publisher.ts"],"sourcesContent":["import { redactedErrorMessage } from \"../errors\";\nimport {\n createGitHubPublicationClient,\n factorySessionBranch,\n factoryTaskBranch,\n GitHubPublicationApiError,\n isSafeGitRef,\n readCommittedGitChange,\n runSandboxGit,\n type CommittedGitChange,\n type CreatedGitHubPublicationPullRequest,\n type GitHubPublicationClient,\n type GitHubPublicationClientOptions,\n type GitHubPublicationPullRequest,\n type SandboxGit,\n} from \"../github-publication\";\nimport type { Change } from \"../schema/change\";\nimport type { TaskId } from \"../schema/id\";\nimport type { Repository, RepositorySlug } from \"../schema/repository\";\nimport type { AgentSessionId } from \"../schema/session\";\nimport type { FactoryStores } from \"../store/engine\";\nimport type {\n OpenPullRequestInput,\n PublishedPullRequest,\n PullRequestPublicationGateInput,\n PullRequestPublicationGateResult,\n PullRequestPublicationOwner,\n PushCheckpointInput,\n TaskPullRequest,\n} from \"./contracts\";\n\n/** Repository credential and client factories required by software publication. */\nexport interface GitHubPullRequestPublisherOptions {\n tokenForRepository(repositorySlug: RepositorySlug): Promise<string>;\n fetch?: typeof globalThis.fetch;\n createClient?: (options: GitHubPublicationClientOptions) => GitHubPublicationClient;\n /** Publishes the initial commit, branch, and pull request through an application credential boundary. */\n publishPullRequest?: (input: {\n repository: RepositorySlug;\n baseCommit: string;\n tree: CommittedGitChange[\"tree\"];\n commitMessage: string;\n branch: string;\n title: string;\n body: string;\n baseBranch: string;\n draft: boolean;\n }) => Promise<CreatedGitHubPublicationPullRequest & { commitSha: string }>;\n}\n\n/** Task- and session-aware operations for idempotent GitHub pull-request publication. */\nexport interface GitHubPullRequestPublisher {\n requireTaskPullRequest(input: RequireTaskPullRequestInput): Promise<TaskPullRequest>;\n openPullRequest(input: PublishGitHubPullRequestInput): Promise<PublishedPullRequest>;\n pushCheckpoint(input: PushGitHubCheckpointInput): Promise<PublishedPullRequest>;\n}\n\n/** Repository and Task whose owned pull request must already exist. */\nexport interface RequireTaskPullRequestInput {\n repository: Repository;\n taskId: TaskId;\n}\n\n/** Sandbox checkout and publication request used to open a GitHub pull request. */\nexport interface PublishGitHubPullRequestInput {\n sandbox: SandboxGit;\n repository: Repository;\n owner: PullRequestPublicationOwner;\n pullRequest: OpenPullRequestInput;\n}\n\n/** Sandbox checkout and optional linked Change used to publish a checkpoint. */\nexport interface PushGitHubCheckpointInput {\n sandbox: SandboxGit;\n repository: Repository;\n owner: PullRequestPublicationOwner;\n checkpoint: PushCheckpointInput;\n linkedChange?: Pick<Change, \"prNumber\" | \"prUrl\">;\n}\n\nasync function clientForRepository(\n options: GitHubPullRequestPublisherOptions,\n repository: Repository,\n): Promise<GitHubPublicationClient> {\n const token = await options.tokenForRepository(repository.slug);\n return (options.createClient ?? createGitHubPublicationClient)({\n repository: repository.slug,\n token,\n fetch: options.fetch,\n });\n}\n\nfunction repositoryOwner(repository: Repository): string {\n const owner = repository.slug.split(\"/\")[0];\n if (owner === undefined || owner === \"\") {\n throw new Error(`invalid repository slug: ${repository.slug}`);\n }\n return owner;\n}\n\nasync function findOpenPullRequest(\n client: GitHubPublicationClient,\n repository: Repository,\n taskId: TaskId,\n): Promise<GitHubPublicationPullRequest & { branch: string }> {\n const branch = factoryTaskBranch(taskId);\n const open = await client.pullRequests({\n state: \"open\",\n head: `${repositoryOwner(repository)}:${branch}`,\n });\n const pull = open.find((candidate) => candidate.head.repository === repository.slug);\n if (pull === undefined) {\n throw new Error(`no open pull request on ${branch}; call open_pull_request first`);\n }\n return { ...pull, branch };\n}\n\nasync function findLinkedPullRequest(\n client: GitHubPublicationClient,\n repository: Repository,\n change: Pick<Change, \"prNumber\" | \"prUrl\">,\n): Promise<GitHubPublicationPullRequest & { branch: string }> {\n const pull = await client.pullRequest({ pullRequestNumber: change.prNumber });\n const branch = pull.head.branch;\n if (\n pull.state !== \"open\" ||\n pull.pullRequestUrl !== change.prUrl ||\n pull.head.repository !== repository.slug ||\n !isSafeGitRef(branch)\n ) {\n throw new Error(\n `Change pull request #${change.prNumber} is not an open same-repository branch`,\n );\n }\n return { ...pull, branch };\n}\n\nasync function createCommit(\n client: GitHubPublicationClient,\n change: CommittedGitChange,\n message: string,\n parent = change.base,\n): Promise<string> {\n const baseCommit = await client.commit({ sha: change.base });\n const tree = await client.createTree({ baseTreeSha: baseCommit.treeSha, entries: change.tree });\n return client.createCommit({ message, treeSha: tree, parentShas: [parent] });\n}\n\nasync function sessionPullRequest(\n client: GitHubPublicationClient,\n repository: Repository,\n branch: string,\n): Promise<GitHubPublicationPullRequest | undefined> {\n const pulls = await client.pullRequests({\n state: \"all\",\n head: `${repositoryOwner(repository)}:${branch}`,\n });\n const pull = pulls.find(\n (candidate) =>\n candidate.head.branch === branch && candidate.head.repository === repository.slug,\n );\n if (pull !== undefined && pull.state !== \"open\") {\n throw new Error(\n \"This session's pull request is merged or closed. Start a new session for a new pull request.\",\n );\n }\n return pull;\n}\n\nasync function sessionHead(sandbox: SandboxGit): Promise<string> {\n const result = await sandbox.run({ command: \"git -C repo config --get factory.publishedHead\" });\n if (result.exitCode === 1) return \"\";\n if (result.exitCode !== undefined && result.exitCode !== 0) {\n throw new Error(\"Could not read the session's published head.\");\n }\n return result.stdout.trim();\n}\n\nasync function rememberSessionHead(sandbox: SandboxGit, sha: string): Promise<void> {\n if (!/^[0-9a-f]{40}$/u.test(sha)) throw new Error(\"GitHub returned an invalid commit SHA.\");\n await runSandboxGit({ sandbox, args: [\"config\", \"factory.publishedHead\", sha] });\n}\n\nasync function assertSessionCheckout(sandbox: SandboxGit, repository: Repository): Promise<void> {\n if (\n (await runSandboxGit({ sandbox, args: [\"remote\", \"get-url\", \"origin\"] })).trim() !==\n `https://github.com/${repository.slug}.git`\n ) {\n throw new Error(`sandbox checkout does not match session repository ${repository.slug}`);\n }\n if ((await runSandboxGit({ sandbox, args: [\"status\", \"--porcelain\"] })).trim() !== \"\") {\n throw new Error(\"Commit your changes before publishing the pull request.\");\n }\n}\n\nasync function acceptSessionHead(\n sandbox: SandboxGit,\n client: GitHubPublicationClient,\n sha: string,\n): Promise<boolean> {\n const expected = await sessionHead(sandbox);\n const commit = await client.commit({ sha });\n const unchanged =\n commit.treeSha ===\n (await runSandboxGit({ sandbox, args: [\"rev-parse\", \"HEAD^{tree}\"] })).trim();\n const recovered = unchanged && (expected === \"\" || commit.parentShas.includes(expected));\n if (expected !== sha && !recovered) {\n throw new Error(\n \"The session PR changed since checkout or publication. Refusing to overwrite newer commits; reload its checkout before continuing.\",\n );\n }\n await rememberSessionHead(sandbox, sha);\n return unchanged;\n}\n\n/** Whether the Task branch already holds the checkout's squashed commit on the same base. */\nasync function taskBranchMatchesCheckout(\n sandbox: SandboxGit,\n client: GitHubPublicationClient,\n headSha: string,\n base: string,\n): Promise<boolean> {\n const head = await client.commit({ sha: headSha });\n if (head.parentShas.length !== 1 || head.parentShas[0] !== base) return false;\n return (\n head.treeSha === (await runSandboxGit({ sandbox, args: [\"rev-parse\", \"HEAD^{tree}\"] })).trim()\n );\n}\n\nfunction sessionPublication(\n pull: Pick<GitHubPublicationPullRequest, \"pullRequestNumber\" | \"pullRequestUrl\" | \"draft\">,\n branch: string,\n changedFiles = 0,\n): PublishedPullRequest {\n return {\n prNumber: pull.pullRequestNumber,\n prUrl: pull.pullRequestUrl,\n branch,\n changedFiles,\n draft: pull.draft,\n };\n}\n\n/** Supplied software workflow for safe Task- and session-owned GitHub publication. */\nexport function createGitHubPullRequestPublisher(\n options: GitHubPullRequestPublisherOptions,\n): GitHubPullRequestPublisher {\n async function requireTaskPullRequest(\n input: RequireTaskPullRequestInput,\n ): Promise<TaskPullRequest> {\n const client = await clientForRepository(options, input.repository);\n const pull = await findOpenPullRequest(client, input.repository, input.taskId);\n return {\n prNumber: pull.pullRequestNumber,\n prUrl: pull.pullRequestUrl,\n branch: pull.branch,\n };\n }\n\n async function openSessionPullRequest(\n sandbox: SandboxGit,\n repository: Repository,\n sessionId: AgentSessionId,\n input: OpenPullRequestInput,\n ): Promise<PublishedPullRequest> {\n const branch = factorySessionBranch(sessionId);\n await assertSessionCheckout(sandbox, repository);\n if (options.publishPullRequest !== undefined) {\n const change = await readCommittedGitChange({ sandbox, repositorySlug: repository.slug });\n const published = await options.publishPullRequest({\n repository: repository.slug,\n baseCommit: change.base,\n tree: change.tree,\n commitMessage: change.commitMessage,\n branch,\n title: input.title,\n body: input.body,\n baseBranch: repository.defaultBranch,\n draft: true,\n });\n await rememberSessionHead(sandbox, published.commitSha);\n return {\n prNumber: published.pullRequestNumber,\n prUrl: published.pullRequestUrl,\n branch,\n changedFiles: change.changedFiles,\n draft: published.draft,\n };\n }\n const client = await clientForRepository(options, repository);\n const existing = await sessionPullRequest(client, repository, branch);\n if (existing !== undefined) {\n await acceptSessionHead(sandbox, client, existing.head.sha);\n return sessionPublication(existing, branch);\n }\n\n let sha = await client.branchHead({ branch });\n let changedFiles = 0;\n if (sha === null) {\n const change = await readCommittedGitChange({ sandbox, repositorySlug: repository.slug });\n changedFiles = change.changedFiles;\n sha = await createCommit(client, change, `${input.title}\\n\\n${input.body}`);\n try {\n await client.createBranch({ branch, sha });\n } catch (error) {\n if ((await client.branchHead({ branch })) !== sha) throw error;\n }\n await rememberSessionHead(sandbox, sha);\n } else {\n await acceptSessionHead(sandbox, client, sha);\n }\n try {\n const pull = await client.createPullRequest({\n title: input.title,\n body: input.body,\n head: branch,\n base: repository.defaultBranch,\n draft: true,\n });\n return {\n prNumber: pull.pullRequestNumber,\n prUrl: pull.pullRequestUrl,\n branch,\n changedFiles,\n draft: pull.draft,\n };\n } catch (error) {\n const created = await sessionPullRequest(client, repository, branch);\n if (created === undefined) throw error;\n return sessionPublication(created, branch, changedFiles);\n }\n }\n\n async function openPullRequest(\n input: PublishGitHubPullRequestInput,\n ): Promise<PublishedPullRequest> {\n if (typeof input.owner !== \"string\") {\n return openSessionPullRequest(\n input.sandbox,\n input.repository,\n input.owner.sessionId,\n input.pullRequest,\n );\n }\n const change = await readCommittedGitChange({\n sandbox: input.sandbox,\n repositorySlug: input.repository.slug,\n });\n if (options.publishPullRequest !== undefined) {\n const branch = factoryTaskBranch(input.owner);\n const published = await options.publishPullRequest({\n repository: input.repository.slug,\n baseCommit: change.base,\n tree: change.tree,\n commitMessage: change.commitMessage,\n branch,\n title: input.pullRequest.title,\n body: input.pullRequest.body,\n baseBranch: input.repository.defaultBranch,\n draft: true,\n });\n return {\n prNumber: published.pullRequestNumber,\n prUrl: published.pullRequestUrl,\n branch,\n changedFiles: change.changedFiles,\n draft: published.draft,\n };\n }\n const client = await clientForRepository(options, input.repository);\n const branch = factoryTaskBranch(input.owner);\n const existing = (\n await client.pullRequests({\n state: \"all\",\n head: `${repositoryOwner(input.repository)}:${branch}`,\n })\n ).find((pull) => pull.head.branch === branch && pull.head.repository === input.repository.slug);\n if (existing !== undefined) {\n if (existing.state !== \"open\")\n throw new Error(\n \"This Task's pull request is merged or closed. Start new work for a new pull request.\",\n );\n return {\n prNumber: existing.pullRequestNumber,\n prUrl: existing.pullRequestUrl,\n branch,\n changedFiles: 0,\n draft: existing.draft,\n };\n }\n const sha = await createCommit(\n client,\n change,\n `${input.pullRequest.title}\\n\\n${input.pullRequest.body}`,\n );\n await client.createBranch({ branch, sha });\n const pull = await client.createPullRequest({\n title: input.pullRequest.title,\n body: input.pullRequest.body,\n head: branch,\n base: input.repository.defaultBranch,\n draft: true,\n });\n return {\n prNumber: pull.pullRequestNumber,\n prUrl: pull.pullRequestUrl,\n branch,\n changedFiles: change.changedFiles,\n draft: pull.draft,\n };\n }\n\n async function pushSessionCheckpoint(\n sandbox: SandboxGit,\n repository: Repository,\n sessionId: AgentSessionId,\n input: PushCheckpointInput,\n ): Promise<PublishedPullRequest> {\n const branch = factorySessionBranch(sessionId);\n await assertSessionCheckout(sandbox, repository);\n const client = await clientForRepository(options, repository);\n const pull = await sessionPullRequest(client, repository, branch);\n if (pull === undefined) {\n throw new Error(`no open pull request on ${branch}; call open_pull_request first`);\n }\n const unchanged = await acceptSessionHead(sandbox, client, pull.head.sha);\n let changedFiles = 0;\n if (!unchanged) {\n const change = await readCommittedGitChange({\n sandbox,\n repositorySlug: repository.slug,\n allowEmpty: true,\n });\n changedFiles = change.changedFiles;\n const sha = await createCommit(\n client,\n change,\n `${pull.title}\\n\\n${pull.body ?? \"\"}`,\n pull.head.sha,\n );\n await client.updateBranch({ branch, sha, force: false });\n await rememberSessionHead(sandbox, sha);\n }\n const published = sessionPublication(pull, branch, changedFiles);\n if (input.ready && published.draft) {\n await client.markReady({ nodeId: pull.nodeId });\n published.draft = false;\n }\n return published;\n }\n\n async function pushCheckpoint(input: PushGitHubCheckpointInput): Promise<PublishedPullRequest> {\n if (typeof input.owner !== \"string\") {\n return pushSessionCheckpoint(\n input.sandbox,\n input.repository,\n input.owner.sessionId,\n input.checkpoint,\n );\n }\n const client = await clientForRepository(options, input.repository);\n const pull =\n input.linkedChange === undefined\n ? await findOpenPullRequest(client, input.repository, input.owner)\n : await findLinkedPullRequest(client, input.repository, input.linkedChange);\n const change = await readCommittedGitChange({\n sandbox: input.sandbox,\n repositorySlug: input.repository.slug,\n baseRef:\n input.linkedChange === undefined\n ? \"origin/HEAD\"\n : `refs/remotes/origin/pr-${input.linkedChange.prNumber}`,\n });\n if (input.linkedChange !== undefined && change.base !== pull.head.sha) {\n throw new Error(\n `pull request #${input.linkedChange.prNumber} changed since it was fetched; fetch it again`,\n );\n }\n const unchanged =\n input.linkedChange === undefined &&\n (await taskBranchMatchesCheckout(input.sandbox, client, pull.head.sha, change.base));\n if (!unchanged) {\n const sha = await createCommit(client, change, `${pull.title}\\n\\n${pull.body ?? \"\"}`);\n await client.updateBranch({\n branch: pull.branch,\n sha,\n force: input.linkedChange === undefined,\n });\n }\n let draft = pull.draft;\n if (input.checkpoint.ready && draft) {\n await client.markReady({ nodeId: pull.nodeId });\n draft = false;\n }\n return {\n prNumber: pull.pullRequestNumber,\n prUrl: pull.pullRequestUrl,\n branch: pull.branch,\n changedFiles: unchanged ? 0 : change.changedFiles,\n draft,\n };\n }\n\n return Object.freeze({ requireTaskPullRequest, openPullRequest, pushCheckpoint });\n}\n\n/** Store and repository lookup dependencies for the pull-request publication gate. */\nexport interface GitHubPullRequestPublicationGateOptions extends GitHubPullRequestPublisherOptions {\n stores: Pick<FactoryStores, \"tasks\" | \"changes\">;\n repositoryForTask(taskId: TaskId): Promise<Repository>;\n}\n\n/** Validates that one pull request belongs to its exact GitHub Task and Change context. */\nexport type GitHubPullRequestPublicationGate = (\n input: PullRequestPublicationGateInput,\n) => Promise<PullRequestPublicationGateResult>;\n\n/** Fail-closed gate for the pull request a supplied code-change workflow records. */\nexport function createGitHubPullRequestPublicationGate(\n options: GitHubPullRequestPublicationGateOptions,\n): GitHubPullRequestPublicationGate {\n return async (\n input: PullRequestPublicationGateInput,\n ): Promise<PullRequestPublicationGateResult> => {\n let repository: Repository;\n let linkedChange: Awaited<ReturnType<FactoryStores[\"changes\"][\"get\"]>>;\n try {\n repository = await options.repositoryForTask(input.taskId);\n if (repository.id !== input.repositoryId) {\n return {\n ok: false,\n code: \"repository_mismatch\",\n reason: \"repositoryId does not match the task repository\",\n };\n }\n const task = await options.stores.tasks.get(input.taskId);\n if (task === null) {\n return {\n ok: false,\n code: \"task_not_found\",\n reason: `task ${input.taskId} does not exist`,\n };\n }\n linkedChange =\n task.changeId === undefined ? null : await options.stores.changes.get(task.changeId);\n if (task.changeId !== undefined && linkedChange === null) {\n return {\n ok: false,\n code: \"change_not_found\",\n reason: `change ${task.changeId} does not exist`,\n };\n }\n if (\n linkedChange !== null &&\n (linkedChange.repositoryId !== repository.id ||\n linkedChange.prNumber !== input.prNumber ||\n linkedChange.prUrl !== input.prUrl)\n ) {\n return {\n ok: false,\n code: \"linked_change_mismatch\",\n reason: \"pull request does not match the Change linked to this task\",\n };\n }\n } catch (error) {\n return {\n ok: false,\n code: \"repository_lookup_failed\",\n reason: redactedErrorMessage(error),\n };\n }\n\n let client: GitHubPublicationClient;\n try {\n client = await clientForRepository(options, repository);\n } catch (error) {\n return {\n ok: false,\n code: \"authentication_failed\",\n reason: `could not authenticate to GitHub: ${redactedErrorMessage(error)}`,\n };\n }\n\n let pull: GitHubPublicationPullRequest;\n try {\n pull = await client.pullRequest({ pullRequestNumber: input.prNumber });\n } catch (error) {\n return {\n ok: false,\n code:\n error instanceof GitHubPublicationApiError && error.status === 404\n ? \"pull_request_not_found\"\n : \"pull_request_lookup_failed\",\n reason:\n error instanceof GitHubPublicationApiError && error.status === 404\n ? `pull request #${input.prNumber} does not exist on ${repository.slug}`\n : `could not verify pull request #${input.prNumber} on ${repository.slug}`,\n };\n }\n if (pull.pullRequestUrl !== input.prUrl) {\n return {\n ok: false,\n code: \"pull_request_url_mismatch\",\n reason: `prUrl does not match pull request #${input.prNumber} on ${repository.slug}`,\n };\n }\n if (pull.state !== \"open\") {\n return {\n ok: false,\n code: \"pull_request_closed\",\n reason: `pull request #${input.prNumber} is not open`,\n };\n }\n const branch = factoryTaskBranch(input.taskId);\n if (\n pull.head.repository !== repository.slug ||\n (linkedChange === null && pull.head.branch !== branch)\n ) {\n return {\n ok: false,\n code: \"pull_request_branch_mismatch\",\n reason:\n linkedChange === null\n ? `pull request #${input.prNumber} is not on this task's branch ${branch}`\n : `pull request #${input.prNumber} is not a same-repository branch`,\n };\n }\n if (pull.draft) {\n return {\n ok: false,\n code: \"pull_request_draft\",\n reason: `pull request #${input.prNumber} is still a draft; call push_checkpoint with ready: true first`,\n };\n }\n return { ok: true };\n };\n}\n"],"mappings":"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"}