@fitlab-ai/agent-infra 0.9.6 → 0.9.7

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 (127) hide show
  1. package/dist/bin/internal-cli.js +5 -0
  2. package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +45 -4
  3. package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +162 -0
  4. package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +244 -0
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +370 -0
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +21 -5
  7. package/dist/lib/defaults.json +10 -2
  8. package/dist/lib/git/workflow.js +30 -3
  9. package/dist/lib/internal/codex-lifecycle.js +69 -1
  10. package/dist/lib/internal/codex-sandbox-controller.js +106 -0
  11. package/dist/lib/internal/task-orchestration.js +37 -5
  12. package/dist/lib/platform/context.js +6 -4
  13. package/dist/lib/platform/github-client.js +55 -2
  14. package/dist/lib/process-data/checkpoint.js +319 -0
  15. package/dist/lib/process-data/index.js +128 -60
  16. package/dist/lib/process-data/materialize.js +84 -0
  17. package/dist/lib/process-data/normalize.js +34 -1
  18. package/dist/lib/process-data/reconcile.js +55 -1
  19. package/dist/lib/process-data/sources.js +422 -1
  20. package/dist/lib/process-data/store.js +341 -11
  21. package/dist/lib/sandbox/commands/create.js +103 -51
  22. package/dist/lib/sandbox/commands/rm.js +64 -15
  23. package/dist/lib/sandbox/commands/start.js +4 -3
  24. package/dist/lib/sandbox/control/container-identity.js +64 -0
  25. package/dist/lib/sandbox/control/executor.js +13 -7
  26. package/dist/lib/sandbox/control/lifecycle.js +238 -29
  27. package/dist/lib/sandbox/control/protocol.js +7 -0
  28. package/dist/lib/sandbox/control/server.js +202 -26
  29. package/dist/lib/sandbox/control/state.js +25 -15
  30. package/dist/lib/sandbox/control/timing.js +8 -0
  31. package/dist/lib/sandbox/mounts.js +5 -13
  32. package/dist/lib/sandbox/recovery.js +59 -42
  33. package/dist/lib/sandbox/workspace-view.js +57 -40
  34. package/dist/lib/server/daemon.js +1 -1
  35. package/dist/lib/server/process-control.js +11 -1
  36. package/dist/lib/server/process-state.js +57 -20
  37. package/dist/lib/task/artifact-lifecycle.js +82 -11
  38. package/dist/lib/task/artifact-receipts.js +128 -0
  39. package/dist/lib/task/codex-orchestration.js +81 -6
  40. package/dist/lib/task/commands/validate.js +1 -1
  41. package/dist/lib/task/create.js +10 -2
  42. package/dist/lib/task/delegation-receipts.js +84 -2
  43. package/dist/lib/task/events.js +139 -2
  44. package/dist/lib/task/ledger.js +13 -1
  45. package/dist/lib/task/orchestration.js +235 -14
  46. package/dist/lib/task/sections.js +5 -1
  47. package/dist/lib/task/task-execution-lock.js +5 -4
  48. package/dist/lib/task/verification-engine.js +40 -8
  49. package/dist/lifecycle-build-manifest.json +6 -0
  50. package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +60 -3
  51. package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +227 -0
  52. package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +321 -0
  53. package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +26 -0
  54. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +453 -0
  55. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +33 -4
  56. package/lib/defaults.json +10 -2
  57. package/lib/git/workflow.ts +25 -2
  58. package/lib/internal/codex-lifecycle.ts +68 -1
  59. package/lib/internal/codex-sandbox-controller.ts +109 -0
  60. package/lib/internal/task-orchestration.ts +31 -4
  61. package/lib/platform/context.ts +8 -5
  62. package/lib/platform/github-client.ts +61 -3
  63. package/lib/process-data/checkpoint.ts +335 -0
  64. package/lib/process-data/index.ts +112 -60
  65. package/lib/process-data/materialize.ts +83 -0
  66. package/lib/process-data/normalize.ts +29 -1
  67. package/lib/process-data/reconcile.ts +64 -2
  68. package/lib/process-data/sources.ts +449 -2
  69. package/lib/process-data/store.ts +304 -11
  70. package/lib/process-data/types.ts +124 -2
  71. package/lib/sandbox/commands/create.ts +56 -7
  72. package/lib/sandbox/commands/rm.ts +64 -14
  73. package/lib/sandbox/commands/start.ts +4 -3
  74. package/lib/sandbox/control/container-identity.ts +93 -0
  75. package/lib/sandbox/control/executor.ts +14 -10
  76. package/lib/sandbox/control/lifecycle.ts +264 -33
  77. package/lib/sandbox/control/protocol.ts +31 -5
  78. package/lib/sandbox/control/server.ts +184 -24
  79. package/lib/sandbox/control/state.ts +34 -17
  80. package/lib/sandbox/control/timing.ts +12 -0
  81. package/lib/sandbox/mounts.ts +5 -15
  82. package/lib/sandbox/recovery.ts +71 -42
  83. package/lib/sandbox/workspace-view.ts +61 -40
  84. package/lib/server/daemon.ts +1 -1
  85. package/lib/server/process-control.ts +13 -3
  86. package/lib/server/process-state.ts +82 -27
  87. package/lib/task/artifact-lifecycle.ts +73 -10
  88. package/lib/task/artifact-receipts.ts +161 -0
  89. package/lib/task/codex-orchestration.ts +91 -5
  90. package/lib/task/commands/validate.ts +1 -1
  91. package/lib/task/create.ts +11 -2
  92. package/lib/task/delegation-receipts.ts +160 -8
  93. package/lib/task/events.ts +122 -1
  94. package/lib/task/ledger.ts +16 -1
  95. package/lib/task/orchestration.ts +356 -17
  96. package/lib/task/sections.ts +6 -2
  97. package/lib/task/task-execution-lock.ts +11 -9
  98. package/lib/task/verification-engine.ts +51 -16
  99. package/package.json +1 -1
  100. package/templates/.agents/README.en.md +7 -0
  101. package/templates/.agents/README.zh-CN.md +5 -0
  102. package/templates/.agents/hooks/lifecycle-delegation.js +25 -1
  103. package/templates/.agents/rules/lifecycle-orchestration.en.md +8 -2
  104. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +8 -2
  105. package/templates/.agents/rules/pr-checks-commands.github.en.md +1 -1
  106. package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +1 -1
  107. package/templates/.agents/rules/review-handshake.en.md +3 -1
  108. package/templates/.agents/rules/review-handshake.zh-CN.md +3 -1
  109. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +3 -1
  110. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +3 -1
  111. package/templates/.agents/skills/complete-task/SKILL.en.md +4 -0
  112. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +4 -0
  113. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +6 -6
  114. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +6 -6
  115. package/templates/.agents/skills/run-manual-validation/config/verify.en.json +14 -3
  116. package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +14 -3
  117. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +67 -0
  118. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +67 -0
  119. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +25 -0
  120. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +25 -0
  121. package/templates/.agents/skills/run-task/SKILL.en.md +9 -7
  122. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +9 -7
  123. package/templates/.agents/skills/run-task/reference/host-validation.en.md +2 -0
  124. package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +2 -0
  125. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +75 -3
  126. package/templates/.agents/workspace/README.en.md +0 -26
  127. package/templates/.agents/workspace/README.zh-CN.md +0 -26
@@ -280,6 +280,43 @@ function projectGitHubItem(item) {
280
280
  }
281
281
  return output;
282
282
  }
283
+ function projectGitHubTimelineItem(item) {
284
+ if (!item || typeof item !== 'object' || Array.isArray(item))
285
+ throw new Error('GitHub timeline item must be an object');
286
+ const source = item;
287
+ if (typeof source.event !== 'string' || source.event.length === 0)
288
+ throw new Error('GitHub timeline event type is missing');
289
+ const output = {};
290
+ for (const field of [
291
+ 'id', 'node_id', 'event', 'created_at', 'updated_at', 'submitted_at', 'commit_id', 'sha', 'state',
292
+ 'lock_reason', 'ref', 'ref_type'
293
+ ]) {
294
+ if (source[field] !== undefined)
295
+ output[field] = jsonClone(source[field]);
296
+ }
297
+ for (const field of ['actor', 'user', 'assignee', 'assigner', 'reviewer', 'requested_reviewer']) {
298
+ const projected = projectFields(source[field], ['id', 'login', 'name']);
299
+ if (projected)
300
+ output[field] = projected;
301
+ }
302
+ const nestedFields = {
303
+ label: ['id', 'name', 'color'],
304
+ milestone: ['id', 'number', 'title', 'state'],
305
+ rename: ['from', 'to'],
306
+ source: ['id', 'node_id', 'number', 'title', 'ref', 'sha'],
307
+ dismissed_review: ['state', 'review_id', 'dismissal_commit_id'],
308
+ project_card: ['id', 'project_id', 'column_name']
309
+ };
310
+ for (const [field, fields] of Object.entries(nestedFields)) {
311
+ const projected = projectFields(source[field], fields);
312
+ if (projected)
313
+ output[field] = projected;
314
+ }
315
+ const body = projectSensitiveText(source.body);
316
+ if (body !== undefined)
317
+ output.body = body;
318
+ return output;
319
+ }
283
320
  function collectGitHubObjects(repoRoot, client = createGitHubClient()) {
284
321
  const repoResult = client.json(['repo', 'view', '--json', 'nameWithOwner'], { cwd: repoRoot });
285
322
  if (!repoResult.ok)
@@ -341,5 +378,389 @@ function collectGitHubObjects(repoRoot, client = createGitHubClient()) {
341
378
  }
342
379
  return { ok: true, value: { repository, objects, endpoints: evidence } };
343
380
  }
344
- export { collectGitHubObjects, collectLocalObjects, fetchRestCollection, projectGitHubItem };
381
+ function resolveGitHubRepository(repoRoot, client = createGitHubClient()) {
382
+ const result = client.json(['repo', 'view', '--json', 'nameWithOwner'], { cwd: repoRoot });
383
+ if (!result.ok)
384
+ return { ok: false, error: result.error };
385
+ if (!result.value.nameWithOwner || !/^[^/]+\/[^/]+$/.test(result.value.nameWithOwner)) {
386
+ return { ok: false, error: { code: 'INVALID_PLATFORM_RESPONSE', message: 'GitHub repository identity is missing' } };
387
+ }
388
+ return { ok: true, value: result.value.nameWithOwner };
389
+ }
390
+ function truncateSecond(date) {
391
+ return new Date(Math.floor(date.getTime() / 1000) * 1000).toISOString();
392
+ }
393
+ function validDate(value) {
394
+ if (!value)
395
+ return null;
396
+ const parsed = new Date(value);
397
+ return Number.isFinite(parsed.getTime()) ? parsed : null;
398
+ }
399
+ function queryEndpoint(endpoint, page, perPage, queryMode, queryAfter) {
400
+ const parsed = new URL(endpoint, 'https://api.github.invalid/');
401
+ parsed.searchParams.set('per_page', String(perPage));
402
+ parsed.searchParams.set('page', String(page));
403
+ if (queryMode === 'strict-since' && queryAfter)
404
+ parsed.searchParams.set('since', queryAfter);
405
+ return `${parsed.pathname.replace(/^\//, '')}${parsed.search}`;
406
+ }
407
+ function resourceIdentity(prefix, item) {
408
+ if (!item || typeof item !== 'object' || Array.isArray(item))
409
+ return '';
410
+ const value = item;
411
+ if (typeof value.id === 'number' || typeof value.id === 'string')
412
+ return `${prefix}:${String(value.id)}`;
413
+ if (typeof value.node_id === 'string')
414
+ return `${prefix}:${value.node_id}`;
415
+ if (typeof value.sha === 'string')
416
+ return `${prefix}:${value.sha}`;
417
+ if (typeof value.number === 'number')
418
+ return `${prefix}:${value.number}`;
419
+ return '';
420
+ }
421
+ function issueOrPullIdentity(item) {
422
+ if (!item || typeof item !== 'object' || Array.isArray(item))
423
+ return '';
424
+ const value = item;
425
+ const prefix = value.pull_request ? 'pr' : 'issue';
426
+ return resourceIdentity(prefix, item);
427
+ }
428
+ function isPullRequestIssue(item) {
429
+ return Boolean(item && typeof item === 'object' && !Array.isArray(item) && item.pull_request);
430
+ }
431
+ function itemEventTime(item) {
432
+ if (!item || typeof item !== 'object' || Array.isArray(item))
433
+ return undefined;
434
+ const value = item;
435
+ for (const key of ['updated_at', 'created_at', 'submitted_at']) {
436
+ if (typeof value[key] === 'string')
437
+ return value[key];
438
+ }
439
+ const commit = value.commit;
440
+ const author = commit?.author;
441
+ const committer = commit?.committer;
442
+ if (typeof author?.date === 'string')
443
+ return author.date;
444
+ if (typeof committer?.date === 'string')
445
+ return committer.date;
446
+ return undefined;
447
+ }
448
+ function timelineIdentity(item, parentNumber) {
449
+ if (!item || typeof item !== 'object' || Array.isArray(item))
450
+ return '';
451
+ const value = item;
452
+ if (typeof value.id === 'number' || typeof value.id === 'string')
453
+ return `timeline:${String(value.id)}`;
454
+ const actor = value.actor;
455
+ const target = value.issue;
456
+ const tuple = [parentNumber, value.event, value.created_at ?? value.updated_at, actor?.id ?? actor?.login, target?.id ?? value.commit_id];
457
+ return tuple.every((part) => part !== undefined && part !== null && String(part) !== '') ? `timeline:${tuple.join(':')}` : '';
458
+ }
459
+ function descriptor(pathname, queryMode, prefix, identity, options = {}) {
460
+ return {
461
+ path: pathname,
462
+ queryMode,
463
+ eventTime: itemEventTime,
464
+ identity: identity ?? ((item) => resourceIdentity(prefix, item)),
465
+ ...options
466
+ };
467
+ }
468
+ function responseDateOrError(value) {
469
+ const date = validDate(value);
470
+ if (!date)
471
+ return { ok: false, error: { code: 'OBSERVATION_BOUNDARY_INVALID', message: 'GitHub response Date is missing or invalid' } };
472
+ return { ok: true, value: { date, iso: date.toISOString() } };
473
+ }
474
+ function collectBoundaryCollection(client, endpoint, preflight, fromInclusive, watermark, reconciliation, addResource, deferred, responseDates) {
475
+ const perPage = 100;
476
+ const queryMode = reconciliation === 'full' ? 'full-enumeration' : (fromInclusive ? endpoint.queryMode : 'full-enumeration');
477
+ const queryAfter = queryMode === 'strict-since' && fromInclusive
478
+ ? new Date(fromInclusive.getTime() - 1000).toISOString()
479
+ : null;
480
+ const pages = [];
481
+ const objects = [];
482
+ const identities = new Set();
483
+ let previousHash = null;
484
+ for (let page = 1;; page += 1) {
485
+ const url = queryEndpoint(endpoint.path, page, perPage, queryMode, queryAfter);
486
+ if (!client.jsonWithMetadata) {
487
+ return { ok: false, error: { code: 'PLATFORM_METADATA_UNAVAILABLE', message: 'GitHub response metadata boundary is unavailable' } };
488
+ }
489
+ const response = client.jsonWithMetadata(['api', url]);
490
+ if (!response.ok)
491
+ return { ok: false, error: response.error };
492
+ const dateResult = responseDateOrError(response.value.metadata.date);
493
+ if (!dateResult.ok)
494
+ return dateResult;
495
+ if (dateResult.value.date.getTime() < preflight.getTime()) {
496
+ return { ok: false, error: { code: 'OBSERVATION_BOUNDARY_INVALID', message: `Response Date precedes preflight for ${endpoint.path}` } };
497
+ }
498
+ responseDates.push(dateResult.value.iso);
499
+ const actualUrl = response.value.metadata.requestUrl;
500
+ if (queryAfter && (!actualUrl.includes(`since=${encodeURIComponent(queryAfter)}`) && !actualUrl.includes(`since=${queryAfter}`))) {
501
+ return { ok: false, error: { code: 'QUERY_BOUNDARY_INVALID', message: `GitHub request did not include since=${queryAfter}` } };
502
+ }
503
+ if (!Array.isArray(response.value.value) || response.value.value.length > perPage) {
504
+ return { ok: false, error: { code: 'INVALID_PLATFORM_RESPONSE', message: `GitHub page ${page} is not a valid array page` } };
505
+ }
506
+ const selected = response.value.value.map((item) => endpoint.path.includes('/timeline')
507
+ ? projectGitHubTimelineItem(item)
508
+ : projectGitHubItem(item));
509
+ const bytes = canonicalJsonBytes(selected);
510
+ const pageHash = sha256(bytes);
511
+ if (previousHash === pageHash && selected.length > 0) {
512
+ return { ok: false, error: { code: 'PAGINATION_UNSTABLE', message: `GitHub page ${page} repeats the previous page` } };
513
+ }
514
+ let overlapItemCount = 0;
515
+ let acceptedItemCount = 0;
516
+ let deferredItemCount = 0;
517
+ for (let index = 0; index < response.value.value.length; index += 1) {
518
+ const raw = response.value.value[index];
519
+ const projected = selected[index];
520
+ const identity = endpoint.identity(raw);
521
+ const eventTime = endpoint.eventTime(raw);
522
+ if (!identity)
523
+ return { ok: false, error: { code: endpoint.path.includes('/timeline') ? 'TIMELINE_IDENTITY_UNAVAILABLE' : 'RESOURCE_IDENTITY_UNAVAILABLE', message: `Stable identity is missing for ${endpoint.path}` } };
524
+ if (!eventTime || !validDate(eventTime))
525
+ return { ok: false, error: { code: 'RESOURCE_TIME_UNAVAILABLE', message: `Event time is missing for ${identity}` } };
526
+ if (identities.has(identity))
527
+ return { ok: false, error: { code: 'PAGINATION_UNSTABLE', message: `duplicate identity on ${endpoint.path}` } };
528
+ identities.add(identity);
529
+ const observed = validDate(eventTime);
530
+ if (fromInclusive && observed.getTime() < fromInclusive.getTime()) {
531
+ overlapItemCount += 1;
532
+ }
533
+ else if (observed.getTime() >= watermark.getTime()) {
534
+ deferredItemCount += 1;
535
+ if (endpoint.includeResource?.(raw) !== false)
536
+ deferred.add(identity);
537
+ }
538
+ else {
539
+ acceptedItemCount += 1;
540
+ if (endpoint.includeResource?.(raw) === false)
541
+ continue;
542
+ const resourceBytes = canonicalJsonBytes(projected);
543
+ const rawRecord = raw && typeof raw === 'object' && !Array.isArray(raw) ? raw : {};
544
+ const routeNumber = typeof rawRecord.number === 'number' ? rawRecord.number : undefined;
545
+ const added = addResource({
546
+ sourceKind: 'github-rest',
547
+ sourceIdentity: identity,
548
+ resourceIdentity: identity,
549
+ sha256: sha256(resourceBytes),
550
+ bytes: resourceBytes.length,
551
+ content: resourceBytes.toString('utf8'),
552
+ disposition: { state: 'included' },
553
+ role: 'resource',
554
+ eventTime,
555
+ ...(routeNumber !== undefined ? { routeNumber } : {}),
556
+ ...(endpoint.parentIdentity ? { parentIdentity: endpoint.parentIdentity } : {}),
557
+ pageSha256: pageHash
558
+ }, identity, eventTime);
559
+ if (!added.ok)
560
+ return added;
561
+ }
562
+ }
563
+ const pageEvidence = {
564
+ index: page,
565
+ itemCount: response.value.value.length,
566
+ canonicalSha256: pageHash,
567
+ queryMode,
568
+ ...(queryAfter ? { requestedSince: queryAfter } : {}),
569
+ responseDate: dateResult.value.iso,
570
+ overlapItemCount,
571
+ acceptedItemCount,
572
+ deferredItemCount
573
+ };
574
+ pages.push(pageEvidence);
575
+ objects.push({
576
+ sourceKind: 'github-rest',
577
+ sourceIdentity: `${endpoint.path}#page=${page}`,
578
+ sha256: pageHash,
579
+ bytes: bytes.length,
580
+ content: bytes.toString('utf8'),
581
+ disposition: { state: 'included' },
582
+ role: 'page-evidence',
583
+ endpoint: endpoint.path,
584
+ page,
585
+ queryMode,
586
+ ...(queryAfter ? { requestedSince: queryAfter } : {}),
587
+ responseDate: dateResult.value.iso
588
+ });
589
+ previousHash = pageHash;
590
+ const links = response.value.metadata.links.join(' ');
591
+ if (response.value.value.length < perPage) {
592
+ if (/rel=["']next["']/.test(links))
593
+ return { ok: false, error: { code: 'PAGINATION_UNSTABLE', message: `short page ${page} has a next link` } };
594
+ break;
595
+ }
596
+ }
597
+ const accepted = pages.reduce((sum, page) => sum + (page.acceptedItemCount ?? 0), 0);
598
+ const observed = pages.reduce((sum, page) => sum + page.itemCount, 0);
599
+ if (endpoint.maxItems !== undefined && observed >= endpoint.maxItems) {
600
+ return { ok: false, error: { code: 'PLATFORM_LIMIT_REACHED', message: `GitHub endpoint ${endpoint.path} reached its documented ${endpoint.maxItems}-item limit` } };
601
+ }
602
+ const evidence = {
603
+ endpoint: endpoint.path,
604
+ requestCount: pages.length,
605
+ dataPageCount: pages.filter((page) => page.itemCount > 0).length,
606
+ itemCount: accepted,
607
+ termination: 'short-page',
608
+ pages,
609
+ queryMode,
610
+ ...(queryAfter ? { requestedSince: queryAfter } : {})
611
+ };
612
+ // The caller adds these after all resources are known so page evidence stays separate from resource hashes.
613
+ for (const object of objects) {
614
+ const added = addResource(object, object.sourceIdentity, object.eventTime ?? watermark.toISOString());
615
+ if (!added.ok)
616
+ return added;
617
+ }
618
+ return { ok: true, value: evidence };
619
+ }
620
+ function collectGitHubBoundary(repoRoot, options = {}) {
621
+ const client = options.client ?? createGitHubClient();
622
+ const repoResult = client.json(['repo', 'view', '--json', 'nameWithOwner'], { cwd: repoRoot });
623
+ if (!repoResult.ok)
624
+ return { ok: false, error: repoResult.error };
625
+ const repository = repoResult.value.nameWithOwner;
626
+ if (!repository || !/^[^/]+\/[^/]+$/.test(repository) || !client.jsonWithMetadata) {
627
+ return { ok: false, error: { code: 'INVALID_PLATFORM_RESPONSE', message: 'GitHub repository identity or response metadata is missing' } };
628
+ }
629
+ const preflightResult = client.jsonWithMetadata(['api', `repos/${repository}`]);
630
+ if (!preflightResult.ok)
631
+ return { ok: false, error: preflightResult.error };
632
+ const preflightDate = responseDateOrError(preflightResult.value.metadata.date);
633
+ if (!preflightDate.ok)
634
+ return preflightDate;
635
+ const watermark = new Date(truncateSecond(preflightDate.value.date));
636
+ const fromInclusive = options.fromInclusive ? validDate(options.fromInclusive) : null;
637
+ if (options.fromInclusive && !fromInclusive)
638
+ return { ok: false, error: { code: 'CHECKPOINT_INVALID', message: 'Checkpoint watermark is invalid' } };
639
+ if (fromInclusive && watermark.getTime() <= fromInclusive.getTime()) {
640
+ return { ok: false, error: { code: 'OBSERVATION_BOUNDARY_INVALID', message: 'Observation cutoff is not after checkpoint watermark' } };
641
+ }
642
+ const reconciliation = options.reconciliation ?? 'incremental';
643
+ const allObjects = [];
644
+ const resources = new Map();
645
+ const pageObjects = [];
646
+ const endpoints = [];
647
+ const deferred = new Set();
648
+ const unavailable = new Set();
649
+ const responseDates = [preflightDate.value.iso];
650
+ const addResource = (object, identity) => {
651
+ if (object.role === 'page-evidence') {
652
+ pageObjects.push(object);
653
+ return { ok: true, value: undefined };
654
+ }
655
+ if (identity) {
656
+ const existing = resources.get(identity);
657
+ if (existing && (existing.sha256 !== object.sha256 || existing.parentIdentity !== object.parentIdentity)) {
658
+ return { ok: false, error: { code: 'RESOURCE_IDENTITY_COLLISION', message: `Conflicting GitHub resources share identity ${identity}` } };
659
+ }
660
+ if (!existing)
661
+ resources.set(identity, object);
662
+ }
663
+ return { ok: true, value: undefined };
664
+ };
665
+ const collect = (endpoint) => {
666
+ const result = collectBoundaryCollection(client, endpoint, preflightDate.value.date, fromInclusive, watermark, reconciliation, addResource, deferred, responseDates);
667
+ if (!result.ok)
668
+ return result;
669
+ endpoints.push(result.value);
670
+ return { ok: true, value: undefined };
671
+ };
672
+ const issueEndpoint = `repos/${repository}/issues?state=all&sort=created&direction=asc`;
673
+ const pullEndpoint = `repos/${repository}/pulls?state=all&sort=created&direction=asc`;
674
+ for (const endpoint of [
675
+ descriptor(issueEndpoint, 'strict-since', 'issue', issueOrPullIdentity, { includeResource: (item) => !isPullRequestIssue(item) }),
676
+ descriptor(pullEndpoint, 'full-enumeration', 'pr')
677
+ ]) {
678
+ const result = collect(endpoint);
679
+ if (!result.ok)
680
+ return result;
681
+ }
682
+ const issueNumbers = new Set();
683
+ const pullNumbers = new Set();
684
+ const issueRoots = new Map();
685
+ const pullRoots = new Map();
686
+ for (const resource of resources.values()) {
687
+ if (resource.routeNumber === undefined || !resource.resourceIdentity)
688
+ continue;
689
+ if (resource.resourceIdentity.startsWith('pr:')) {
690
+ const number = String(resource.routeNumber);
691
+ pullNumbers.add(number);
692
+ pullRoots.set(number, resource);
693
+ }
694
+ else if (resource.resourceIdentity.startsWith('issue:')) {
695
+ const number = String(resource.routeNumber);
696
+ issueNumbers.add(number);
697
+ issueRoots.set(number, resource);
698
+ }
699
+ }
700
+ for (const number of issueNumbers) {
701
+ const parentIdentity = issueRoots.get(number)?.resourceIdentity;
702
+ const result = collect(descriptor(`repos/${repository}/issues/${number}/comments`, 'strict-since', 'comment', undefined, { parentIdentity }));
703
+ if (!result.ok)
704
+ return result;
705
+ const timeline = collect(descriptor(`repos/${repository}/issues/${number}/timeline`, 'full-enumeration', 'timeline', (item) => timelineIdentity(item, number), { parentIdentity }));
706
+ if (!timeline.ok)
707
+ return timeline;
708
+ }
709
+ for (const number of pullNumbers) {
710
+ const expected = pullRoots.get(number);
711
+ let expectedHead;
712
+ if (expected?.content) {
713
+ try {
714
+ expectedHead = JSON.parse(expected.content).head?.sha;
715
+ }
716
+ catch {
717
+ expectedHead = undefined;
718
+ }
719
+ }
720
+ const current = client.jsonWithMetadata(['api', `repos/${repository}/pulls/${number}`]);
721
+ if (!current.ok)
722
+ return { ok: false, error: current.error };
723
+ const currentDate = responseDateOrError(current.value.metadata.date);
724
+ if (!currentDate.ok)
725
+ return currentDate;
726
+ if (currentDate.value.date.getTime() < preflightDate.value.date.getTime()) {
727
+ return { ok: false, error: { code: 'OBSERVATION_BOUNDARY_INVALID', message: `Response Date precedes preflight for pull request ${number}` } };
728
+ }
729
+ responseDates.push(currentDate.value.iso);
730
+ const currentProjected = projectGitHubItem(current.value.value);
731
+ const currentHead = currentProjected.head?.sha;
732
+ if (expectedHead && currentHead !== expectedHead) {
733
+ return { ok: false, error: { code: 'SOURCE_DRIFT', message: `Pull request ${number} head changed during capture` } };
734
+ }
735
+ const parentIdentity = expected?.resourceIdentity;
736
+ const conversationComments = collect(descriptor(`repos/${repository}/issues/${number}/comments`, 'strict-since', 'comment', undefined, { parentIdentity }));
737
+ if (!conversationComments.ok)
738
+ return conversationComments;
739
+ for (const suffix of ['comments', 'reviews', 'commits']) {
740
+ const mode = suffix === 'reviews' || suffix === 'commits' ? 'full-enumeration' : 'strict-since';
741
+ const prefix = suffix === 'comments' ? 'review-comment' : suffix === 'reviews' ? 'review' : 'commit';
742
+ const result = collect(descriptor(`repos/${repository}/pulls/${number}/${suffix}`, mode, prefix, undefined, { parentIdentity, ...(suffix === 'commits' ? { maxItems: 250 } : {}) }));
743
+ if (!result.ok)
744
+ return result;
745
+ }
746
+ const timeline = collect(descriptor(`repos/${repository}/issues/${number}/timeline`, 'full-enumeration', 'timeline', (item) => timelineIdentity(item, number), { parentIdentity }));
747
+ if (!timeline.ok)
748
+ return timeline;
749
+ }
750
+ allObjects.push(...pageObjects, ...resources.values());
751
+ return {
752
+ ok: true,
753
+ value: {
754
+ repository,
755
+ preflightDate: preflightDate.value.iso,
756
+ watermark: watermark.toISOString(),
757
+ objects: allObjects,
758
+ endpoints,
759
+ responseDates: [...new Set(responseDates)].sort(),
760
+ deferred: [...deferred].sort(),
761
+ unavailable: [...unavailable].sort()
762
+ }
763
+ };
764
+ }
765
+ export { collectGitHubBoundary, collectGitHubObjects, collectLocalObjects, fetchRestCollection, projectGitHubItem, projectGitHubTimelineItem, resolveGitHubRepository };
345
766
  //# sourceMappingURL=sources.js.map