@outerlayer/cli 0.15.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +11 -11
  3. package/dist/{agent-setup-CN2HUBEJ.js → agent-setup-T624CWPD.js} +8 -7
  4. package/dist/artifact-link-cmd-2F47LT7F.js +24 -0
  5. package/dist/build-info.json +1 -1
  6. package/dist/{check-2JSSXCKJ.js → check-5XE7XMC4.js} +22 -19
  7. package/dist/{chunk-HFKKFTXU.js → chunk-2MNPHL23.js} +19 -11
  8. package/dist/{chunk-YDSMROTB.js → chunk-3NSNIIBE.js} +8 -4
  9. package/dist/{chunk-FOJGZYFC.js → chunk-42ZI52JW.js} +1 -1
  10. package/dist/{chunk-NPQWKGUA.js → chunk-6Y5RUTHO.js} +1 -1
  11. package/dist/{chunk-PQUM37BK.js → chunk-BWQ66M6N.js} +1 -1
  12. package/dist/chunk-C5Y5ZJJZ.js +88 -0
  13. package/dist/{chunk-ASOSIKA4.js → chunk-G5NPRL2K.js} +27 -6
  14. package/dist/{chunk-QLTYTPYU.js → chunk-HGECYFEI.js} +64 -7
  15. package/dist/{chunk-SXWS25JC.js → chunk-KG5ECZGJ.js} +6 -6
  16. package/dist/{chunk-F7GBYX2P.js → chunk-LWSJQJC6.js} +18 -4
  17. package/dist/{chunk-UUDDPNRJ.js → chunk-MAK5LYRF.js} +33 -10
  18. package/dist/{chunk-MTF3PHVZ.js → chunk-NBGDBTO2.js} +3 -3
  19. package/dist/{chunk-KFRM23WD.js → chunk-NNRTPG7X.js} +2 -2
  20. package/dist/{chunk-MSASO5O4.js → chunk-NWQTTSBB.js} +1 -1
  21. package/dist/{chunk-2M63GG56.js → chunk-OHVJA253.js} +1 -1
  22. package/dist/{chunk-Z3NOIFDR.js → chunk-ONKJUM6R.js} +1 -1
  23. package/dist/{chunk-T5VPP6H7.js → chunk-OZGHL5Y5.js} +1 -1
  24. package/dist/{chunk-ZA5CHAFY.js → chunk-POJMEEJJ.js} +21 -4
  25. package/dist/{chunk-JR6ERP3M.js → chunk-S3NYLI2P.js} +3 -3
  26. package/dist/{chunk-HXLMZOKU.js → chunk-VQYBXPI7.js} +4 -4
  27. package/dist/{chunk-LCEXHCHD.js → chunk-WTE3MQML.js} +1 -1
  28. package/dist/{chunk-NXUH742G.js → chunk-WVUIRTWA.js} +10 -8
  29. package/dist/{chunk-JJEGKEFV.js → chunk-YU3VKZ5F.js} +1 -1
  30. package/dist/{chunk-3NLOVYJQ.js → chunk-YZ5F4PIS.js} +134 -38
  31. package/dist/{cli-RGGXQTWI.js → cli-A76AXELQ.js} +90 -79
  32. package/dist/{command-ETUAEXP2.js → command-R2QCVX55.js} +10 -9
  33. package/dist/{connect-cmd-JUAW6MU4.js → connect-cmd-JBKAQNSF.js} +1 -1
  34. package/dist/{context-adopt-PJUMW2EZ.js → context-adopt-NFHCLVUB.js} +5 -4
  35. package/dist/{context-materialize-FDHYKNDT.js → context-materialize-IV2TUUFO.js} +5 -4
  36. package/dist/{doctor-CORFPYY6.js → doctor-4TAIARIG.js} +15 -13
  37. package/dist/{emit-artifact-cmd-FOGNJXVR.js → emit-artifact-cmd-MPZVR56N.js} +4 -4
  38. package/dist/{emit-cmd-ZEYWLC2C.js → emit-cmd-IJ3GVRFD.js} +4 -3
  39. package/dist/{emit-criteria-cmd-HBP6XS5C.js → emit-criteria-cmd-EFGYI23V.js} +2 -2
  40. package/dist/{emit-finding-cmd-DLI4MKMN.js → emit-finding-cmd-5GF3V4LR.js} +160 -47
  41. package/dist/{emit-result-cmd-MUSL7GQA.js → emit-result-cmd-I4OADKMD.js} +4 -4
  42. package/dist/hidden-prompt-UXBFMQC5.js +68 -0
  43. package/dist/{hook-fast-WYQD6D7T.js → hook-fast-R4FVSZ2H.js} +1 -1
  44. package/dist/{import-capture-cmd-6QVCOIHK.js → import-capture-cmd-EQ35EARA.js} +3 -3
  45. package/dist/{import-ruler-cmd-KLRGRBCP.js → import-ruler-cmd-BGAZXCQM.js} +1 -1
  46. package/dist/index.js +3 -3
  47. package/dist/{init-OPB7HHXO.js → init-6XPDRDTC.js} +12 -11
  48. package/dist/{init-MIC37V6J.js → init-PWU5YVL7.js} +54 -29
  49. package/dist/{init-cmd-JR3RKNFB.js → init-cmd-XAVO7HYZ.js} +11 -10
  50. package/dist/{init-update-O5YYFGF4.js → init-update-CVCWD6VS.js} +10 -9
  51. package/dist/{loop-QNSM5GXS.js → loop-FK3LPMMZ.js} +19 -18
  52. package/dist/{machine-IT6GG7FF.js → machine-SQXEBAM2.js} +10 -9
  53. package/dist/{runner-env-XTC53CHN.js → runner-env-EN7YE7QX.js} +1 -1
  54. package/dist/runner-key-6SLBI7PP.js +174 -0
  55. package/dist/{session-sender-4M247XSV.js → session-sender-FO3LCREV.js} +3 -3
  56. package/dist/{starter-pack-IB4UUIX3.js → starter-pack-KF66HGZR.js} +3 -3
  57. package/dist/{status-7ZX57OFJ.js → status-7VNQN5PW.js} +13 -12
  58. package/dist/{sync-cmd-UWD5JOFU.js → sync-cmd-GVPPFZ3G.js} +2 -2
  59. package/dist/{watch-DBNB7R6X.js → watch-23HSAEI6.js} +2 -2
  60. package/dist/{work-claim-cmd-A6IS6CQ7.js → work-claim-cmd-LKL3GHFQ.js} +5 -5
  61. package/dist/{work-cmd-EQSW5DTD.js → work-cmd-Z2WXLZPH.js} +3 -3
  62. package/dist/{work-comment-cmd-7VLKI7FN.js → work-comment-cmd-AWHP56M2.js} +3 -3
  63. package/dist/{work-open-pr-cmd-JEVD4HMQ.js → work-open-pr-cmd-IOM4AVQQ.js} +4 -4
  64. package/dist/{work-pr-cmd-L77K32WK.js → work-pr-cmd-HNEUIEN7.js} +3 -3
  65. package/package.json +1 -1
  66. package/skill-pack/maintained/{amend → outerlayer-amend}/SKILL.md +4 -2
  67. package/skill-pack/maintained/{outerlayer → outerlayer-cli}/SKILL.md +5 -3
  68. package/skill-pack/maintained/{emitting-evidence → outerlayer-evidence}/SKILL.md +5 -3
  69. package/skill-pack/maintained/{reporting-findings → outerlayer-findings}/SKILL.md +26 -30
  70. package/skill-pack/shipped.json +20 -4
  71. package/skill-pack/template/build/SKILL.md +7 -1
  72. package/skill-pack/template/build/references/agent-briefs.md +7 -1
  73. package/skill-pack/template/build/references/evidence.md +1 -1
  74. package/skill-pack/template/build/references/release.md +14 -12
  75. package/skill-pack/template/build/references/review-briefs.md +6 -0
  76. package/skill-pack/template/build/scripts/criterion-citations.mjs +8 -3
  77. package/skill-pack/template/build/scripts/record-criteria.mjs +8 -2
  78. package/skill-pack/template/spec/SKILL.md +2 -0
  79. package/skill-pack/template/writing-specs/SKILL.md +1 -1
  80. package/skill-pack/validators/acceptance-criteria.yaml +6 -5
  81. package/skill-pack/validators/code-review.yaml +6 -5
  82. package/dist/runner-key-7SSNMUBQ.js +0 -154
  83. /package/skill-pack/maintained/{outerlayer → outerlayer-cli}/references/open-pull-request.md +0 -0
  84. /package/skill-pack/maintained/{emitting-evidence → outerlayer-evidence}/references/agents-snippet.md +0 -0
@@ -1,11 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- import { readPin, pinExists, readBuildHold, readContextManifest, manifestFilesPresent, writeDegraded, writeSourceCache, SOURCE_CACHE_TTL_RETRY_MS, SOURCE_CACHE_TTL_SUCCESS_MS, isLastCheckFresh, readDegraded, touchLastCheck, clearDegraded, writeContextManifest, writePinIfChanged, readSourceCache, isSourceCacheFresh } from './chunk-LCEXHCHD.js';
3
+ import { readPin, pinExists, readBuildHold, readContextManifest, manifestFilesPresent, writeDegraded, writeSourceCache, SOURCE_CACHE_TTL_RETRY_MS, SOURCE_CACHE_TTL_SUCCESS_MS, touchLastCheck, isLastCheckFresh, readDegraded, clearDegraded, writeContextManifest, writePinIfChanged, readSourceCache, isSourceCacheFresh } from './chunk-WTE3MQML.js';
4
+ import { removeRetiredSkills } from './chunk-C5Y5ZJJZ.js';
4
5
  import { appendContextProvenance } from './chunk-ZJSNLVCE.js';
5
6
  import { defaultFetch } from './chunk-YO4PSLVB.js';
6
7
  import { withTokenHeader } from './chunk-BYT5O5RV.js';
7
- import { loadMaintainedFiles, writeMaintainedFiles } from './chunk-ZA5CHAFY.js';
8
- import { normalizeRepoName, parseGoverns, CONTEXT_SOURCE_CONFIG_PATH, parseOuterlayerConfig, classifyTree, emitTree } from './chunk-2M63GG56.js';
8
+ import { loadMaintainedFiles, writeMaintainedFiles } from './chunk-POJMEEJJ.js';
9
+ import { normalizeRepoName, parseGoverns, CONTEXT_SOURCE_CONFIG_PATH, parseOuterlayerConfig, classifyTree, emitTree } from './chunk-OHVJA253.js';
9
10
  import { resolveGatewayCredentials } from './chunk-PLMTLGYH.js';
10
11
  import { ensureContextExcludes, ensurePreCommitGuardHook } from './chunk-XCCLFVXM.js';
11
12
  import { homeFromEnv } from './chunk-VNVZDWO3.js';
@@ -187,7 +188,7 @@ async function materializeContext(deps) {
187
188
  if (ownRepoName === void 0) {
188
189
  return selfOutcome();
189
190
  }
190
- const designation = await resolveDesignation({ git, fetchImpl, home, cloud: deps.cloud, now, gitCommonDir, ownRepoName, repoRoot });
191
+ const designation = await resolveDesignation({ git, fetchImpl, home, cloud: deps.cloud, now, gitCommonDir, ownRepoName, repoRoot, lookup: deps.designationLookup ?? { timeoutMs: 2e3, attempts: 1 } });
191
192
  const answered = { designation: "answered" };
192
193
  if (designation.status === "self") {
193
194
  return { ...selfOutcome(), ...answered };
@@ -203,11 +204,12 @@ async function materializeContext(deps) {
203
204
  writeDegraded(gitCommonDir, { at: new Date(now()).toISOString(), kind: "designation-unverified", message });
204
205
  }
205
206
  writeSourceCache(gitCommonDir, { checkedAt: new Date(now()).toISOString(), status: "self", ttlMs: SOURCE_CACHE_TTL_RETRY_MS });
206
- return { status: "self", sha: null, verification: "none", designation: "unknown", ...designation.authRejected ? { authRejected: true } : {} };
207
+ return { status: "self", sha: null, verification: "none", designation: "unknown", ...designation.authRejected ? { authRejected: true } : {}, lookupFailure: designation.lookupFailure };
207
208
  }
208
209
  let repository;
209
210
  let ref;
210
211
  let unverifiedIdentity;
212
+ let storedSha;
211
213
  let preFetched;
212
214
  let unreadableCandidates = [];
213
215
  if (designation.status === "candidates") {
@@ -272,9 +274,10 @@ async function materializeContext(deps) {
272
274
  repository = designation.repository;
273
275
  ref = designation.ref;
274
276
  unverifiedIdentity = designation.unverifiedIdentity;
277
+ storedSha = designation.sha;
275
278
  }
276
279
  const manifest = readContextManifest(gitCommonDir);
277
- const evidenced = hasGovernedEvidence(hasPin, manifest);
280
+ const evidenced = hasGovernedEvidence(hasPin, manifest) || storedSha !== void 0 && !unverifiedIdentity;
278
281
  const clearOrFlagIdentity = () => {
279
282
  if (unverifiedIdentity) {
280
283
  writeDegraded(gitCommonDir, {
@@ -294,7 +297,30 @@ async function materializeContext(deps) {
294
297
  sha = preFetched.sha;
295
298
  cacheRepoDir = preFetched.cacheRepoDir;
296
299
  } else {
297
- if (isLastCheckFresh(gitCommonDir, now()) && manifest !== void 0 && pin?.sha === manifest.sha && manifestFilesPresent(repoRoot, manifest)) {
300
+ if (storedSha !== void 0 && manifest !== void 0 && manifest.sha === storedSha && pin?.sha === storedSha && manifestFilesPresent(repoRoot, manifest)) {
301
+ touchLastCheck(gitCommonDir, now());
302
+ clearOrFlagIdentity();
303
+ ensureContextExcludes(gitCommonDir);
304
+ recordMaintainedOverlay(repoRoot, gitCommonDir, manifest);
305
+ const guardMessage2 = guardNotInstalledMessage(ensurePreCommitGuardHook(repoRoot, gitCommonDir));
306
+ writeSourceCache(gitCommonDir, {
307
+ checkedAt: new Date(now()).toISOString(),
308
+ status: "governed",
309
+ repository,
310
+ ref,
311
+ sha: storedSha,
312
+ ttlMs: SOURCE_CACHE_TTL_SUCCESS_MS
313
+ });
314
+ const verification = unverifiedIdentity ? "unverified" : "verified";
315
+ record(storedSha, verification, repository, ref);
316
+ return {
317
+ status: "debounced",
318
+ sha: storedSha,
319
+ verification,
320
+ ...guardMessage2 ? { message: guardMessage2 } : {}
321
+ };
322
+ }
323
+ if (storedSha === void 0 && isLastCheckFresh(gitCommonDir, now()) && manifest !== void 0 && pin?.sha === manifest.sha && manifestFilesPresent(repoRoot, manifest)) {
298
324
  const degraded = readDegraded(gitCommonDir);
299
325
  const verification = degraded ? "unverified" : "verified";
300
326
  ensureContextExcludes(gitCommonDir);
@@ -320,8 +346,8 @@ async function materializeContext(deps) {
320
346
  }
321
347
  const remoteUrl = deps.resolveControlPlaneUrl ? deps.resolveControlPlaneUrl(repository, ref, repoRoot) : buildControlPlaneUrl(git, repoRoot, repository);
322
348
  const cacheHost = parseRemoteHost(remoteUrl).host || "unknown-host";
323
- const lsRemote = git(["ls-remote", remoteUrl, ref], { cwd: repoRoot, timeoutMs: 2e3 });
324
- if (lsRemote.ok) {
349
+ const lsRemote = storedSha === void 0 ? git(["ls-remote", remoteUrl, ref], { cwd: repoRoot, timeoutMs: 2e3 }) : void 0;
350
+ if (lsRemote?.ok) {
325
351
  const remoteSha = parseLsRemoteSha(lsRemote.stdout.toString("utf8"));
326
352
  if (remoteSha && manifest !== void 0 && manifest.sha === remoteSha && pin?.sha === remoteSha && manifestFilesPresent(repoRoot, manifest)) {
327
353
  touchLastCheck(gitCommonDir, now());
@@ -340,13 +366,21 @@ async function materializeContext(deps) {
340
366
  }
341
367
  }
342
368
  cacheRepoDir = ensureBareCache(git, home, repository, cacheHost);
343
- const localRef = `refs/outerlayer/${sanitizeRefName(ref)}`;
344
- const fetchResult = git(["fetch", "--depth", "1", remoteUrl, `+${ref}:${localRef}`], { cwd: cacheRepoDir, timeoutMs: 1e4 });
369
+ const localRef = storedSha === void 0 ? `refs/outerlayer/${sanitizeRefName(ref)}` : `refs/outerlayer/sha-${sanitizeRefName(storedSha)}`;
370
+ const wanted = storedSha === void 0 ? `+${ref}:${localRef}` : `+${storedSha}:${localRef}`;
371
+ const access = storedSha === void 0 ? void 0 : await deps.controlPlaneAccess?.([{ repository, ref }]);
372
+ const token = access !== void 0 && normalizeRepoName(access.repository) === normalizeRepoName(repository) ? access.token : void 0;
373
+ const fetchResult = git(["fetch", "--depth", "1", remoteUrl, wanted], {
374
+ cwd: cacheRepoDir,
375
+ timeoutMs: 1e4,
376
+ ...token === void 0 ? {} : { env: (base) => withTokenHeader(base, remoteUrl, token) }
377
+ });
378
+ const commitNote = (message) => storedSha === void 0 ? message : `could not fetch commit \`${storedSha.slice(0, 7)}\` of ${repository}; ${message}`;
345
379
  if (!fetchResult.ok) {
346
380
  const kind = classifyGitFailure(fetchResult.stderr);
347
381
  const lastGood = manifest?.sha ?? null;
348
382
  const verification = lastGood ? "unverified" : "none";
349
- const doctorMessage = kind === "no-access" ? noAccessMessage(lastGood) : unreachableMessage(lastGood);
383
+ const doctorMessage = commitNote(kind === "no-access" ? noAccessMessage(lastGood) : unreachableMessage(lastGood));
350
384
  writeDegraded(gitCommonDir, { at: new Date(now()).toISOString(), kind, message: doctorMessage, repository, ref, lastGoodSha: lastGood ?? void 0 });
351
385
  if (evidenced) {
352
386
  record(lastGood, verification, repository, ref);
@@ -355,10 +389,10 @@ async function materializeContext(deps) {
355
389
  return { status: kind, sha: null, verification: "none" };
356
390
  }
357
391
  const fetchedSha = gitText(git, cacheRepoDir, ["rev-parse", localRef], 2e3);
358
- if (fetchedSha === void 0) {
392
+ if (fetchedSha === void 0 || storedSha !== void 0 && fetchedSha !== storedSha) {
359
393
  const lastGood = manifest?.sha ?? null;
360
394
  const verification = lastGood ? "unverified" : "none";
361
- const doctorMessage = unreachableMessage(lastGood);
395
+ const doctorMessage = commitNote(unreachableMessage(lastGood));
362
396
  writeDegraded(gitCommonDir, { at: new Date(now()).toISOString(), kind: "unreachable", message: doctorMessage, repository, ref, lastGoodSha: lastGood ?? void 0 });
363
397
  if (evidenced) {
364
398
  record(lastGood, verification, repository, ref);
@@ -367,10 +401,12 @@ async function materializeContext(deps) {
367
401
  return { status: "unreachable", sha: null, verification: "none" };
368
402
  }
369
403
  sha = fetchedSha;
370
- const configContent = readBlobAtPath(git, cacheRepoDir, sha, CONTEXT_SOURCE_CONFIG_PATH);
371
- const parsedGoverns = configContent === void 0 ? { governs: [] } : parseGoverns(configContent);
372
- const governsList = "error" in parsedGoverns ? [] : parsedGoverns.governs;
373
- const governed = governsList.some((r) => normalizeRepoName(r) === normalizeRepoName(ownRepoName));
404
+ const governed = storedSha !== void 0 || (() => {
405
+ const configContent = readBlobAtPath(git, cacheRepoDir, sha, CONTEXT_SOURCE_CONFIG_PATH);
406
+ const parsedGoverns = configContent === void 0 ? { governs: [] } : parseGoverns(configContent);
407
+ const governsList = "error" in parsedGoverns ? [] : parsedGoverns.governs;
408
+ return governsList.some((r) => normalizeRepoName(r) === normalizeRepoName(ownRepoName));
409
+ })();
374
410
  if (!governed) {
375
411
  writeSourceCache(gitCommonDir, { checkedAt: new Date(now()).toISOString(), status: "self", ttlMs: SOURCE_CACHE_TTL_SUCCESS_MS });
376
412
  if (hasPin) {
@@ -409,13 +445,15 @@ async function materializeContext(deps) {
409
445
  return partialWrite();
410
446
  }
411
447
  let overlayPaths = [];
448
+ let retiredPaths = /* @__PURE__ */ new Set();
412
449
  let overlayFailed = false;
413
450
  try {
451
+ retiredPaths = new Set(removeRetiredSkills(repoRoot, (rel) => hasSymlinkComponent(repoRoot, rel)));
414
452
  overlayPaths = overlayMaintainedSkills(repoRoot, (rel) => hasSymlinkComponent(repoRoot, rel));
415
453
  } catch {
416
454
  overlayFailed = true;
417
455
  }
418
- const writtenPaths = [.../* @__PURE__ */ new Set([...writeResult.writtenPaths, ...overlayPaths])];
456
+ const writtenPaths = [.../* @__PURE__ */ new Set([...writeResult.writtenPaths, ...overlayPaths])].filter((path) => !retiredPaths.has(path));
419
457
  const deleteResult = manifest ? deleteRemovedFiles(repoRoot, manifest.files, writtenPaths) : { skipped: [] };
420
458
  if (overlayFailed && deleteResult.skipped.length === 0) {
421
459
  writeContextManifest(gitCommonDir, { schemaVersion: 1, sha, files: writtenPaths, materializedAt: new Date(now()).toISOString() });
@@ -446,7 +484,14 @@ async function materializeContext(deps) {
446
484
  writePinIfChanged(repoRoot, sha);
447
485
  touchLastCheck(gitCommonDir, now());
448
486
  clearOrFlagIdentity();
449
- writeSourceCache(gitCommonDir, { checkedAt: new Date(now()).toISOString(), status: "governed", repository, ref, ttlMs: SOURCE_CACHE_TTL_SUCCESS_MS });
487
+ writeSourceCache(gitCommonDir, {
488
+ checkedAt: new Date(now()).toISOString(),
489
+ status: "governed",
490
+ repository,
491
+ ref,
492
+ ...storedSha === void 0 ? {} : { sha: storedSha },
493
+ ttlMs: SOURCE_CACHE_TTL_SUCCESS_MS
494
+ });
450
495
  record(sha, "verified", repository, ref);
451
496
  return {
452
497
  status: "materialized",
@@ -458,21 +503,26 @@ async function materializeContext(deps) {
458
503
  }
459
504
  function recordMaintainedOverlay(repoRoot, gitCommonDir, manifest) {
460
505
  let overlayPaths;
506
+ let retired;
461
507
  try {
508
+ retired = new Set(removeRetiredSkills(repoRoot, (rel) => hasSymlinkComponent(repoRoot, rel)));
462
509
  overlayPaths = overlayMaintainedSkills(repoRoot, (rel) => hasSymlinkComponent(repoRoot, rel));
463
510
  } catch {
464
511
  return;
465
512
  }
466
513
  const known = new Set(manifest.files);
467
514
  const added = overlayPaths.filter((path) => !known.has(path));
468
- if (added.length > 0) writeContextManifest(gitCommonDir, { ...manifest, files: [...manifest.files, ...added] });
515
+ if (added.length > 0 || retired.size > 0) {
516
+ writeContextManifest(gitCommonDir, { ...manifest, files: [...manifest.files.filter((path) => !retired.has(path)), ...added] });
517
+ }
469
518
  }
470
519
  async function resolveDesignation(ctx) {
471
520
  const cache = readSourceCache(ctx.gitCommonDir);
472
- if (cache && isSourceCacheFresh(cache, ctx.now())) {
521
+ if (cache && isSourceCacheFresh(cache, ctx.now()) && !(cache.status === "governed" && cache.sha !== void 0)) {
473
522
  return cache.status === "governed" && cache.repository && cache.ref ? { status: "governed", repository: cache.repository, ref: cache.ref } : { status: "self" };
474
523
  }
475
524
  let authRejected;
525
+ let lookupFailure;
476
526
  const destination = resolveGatewayCredentials({
477
527
  home: ctx.home,
478
528
  env: {},
@@ -480,15 +530,27 @@ async function resolveDesignation(ctx) {
480
530
  ...ctx.cloud === void 0 ? {} : { url: ctx.cloud.url, apiKey: ctx.cloud.apiKey, appId: ctx.cloud.appId }
481
531
  });
482
532
  if (destination.ok) {
483
- const sources = await fetchContextSource(
533
+ const lookup = await fetchContextSource(
484
534
  { url: destination.url, apiKey: destination.credential, appId: destination.appId },
485
535
  ctx.ownRepoName,
486
536
  ctx.fetchImpl,
487
- () => {
488
- authRejected = designationAuthRejectedMessage(destination.credential);
489
- }
537
+ ctx.lookup
490
538
  );
491
- if (sources !== void 0) {
539
+ if (lookup.kind === "rejected") authRejected = designationAuthRejectedMessage(destination.credential);
540
+ if (lookup.kind === "failed") lookupFailure = lookup.failure;
541
+ if (lookup.kind === "answered") {
542
+ if (lookup.source !== void 0) {
543
+ if (lookup.source === null) {
544
+ writeSourceCache(ctx.gitCommonDir, {
545
+ checkedAt: new Date(ctx.now()).toISOString(),
546
+ status: "self",
547
+ ttlMs: SOURCE_CACHE_TTL_SUCCESS_MS
548
+ });
549
+ return { status: "self" };
550
+ }
551
+ return { status: "governed", repository: lookup.source.repository, ref: lookup.source.ref, sha: lookup.source.sha };
552
+ }
553
+ const sources = lookup.sources ?? [];
492
554
  if (sources.length === 0) {
493
555
  writeSourceCache(ctx.gitCommonDir, {
494
556
  checkedAt: new Date(ctx.now()).toISOString(),
@@ -501,32 +563,66 @@ async function resolveDesignation(ctx) {
501
563
  }
502
564
  }
503
565
  if (cache) {
504
- return cache.status === "governed" && cache.repository && cache.ref ? { status: "governed", repository: cache.repository, ref: cache.ref, unverifiedIdentity: true } : { status: "self" };
566
+ return cache.status === "governed" && cache.repository && cache.ref ? {
567
+ status: "governed",
568
+ repository: cache.repository,
569
+ ref: cache.ref,
570
+ unverifiedIdentity: true,
571
+ ...cache.sha === void 0 ? {} : { sha: cache.sha }
572
+ } : { status: "self" };
573
+ }
574
+ return { status: "unknown", authRejected, lookupFailure };
575
+ }
576
+ async function fetchContextSource(config, ownRepoName, fetchImpl, lookup) {
577
+ const url = `${config.url.replace(/\/$/, "")}/v1/context/source?repo=${encodeURIComponent(ownRepoName)}`;
578
+ for (let attempt = 1; ; attempt++) {
579
+ const outcome = await askContextSource(url, config, fetchImpl, lookup.timeoutMs);
580
+ if (outcome.kind !== "failed" || !isRetryable(outcome.failure) || attempt >= lookup.attempts) return outcome;
505
581
  }
506
- return { status: "unknown", authRejected };
507
582
  }
508
- async function fetchContextSource(config, ownRepoName, fetchImpl, onAuthRejected) {
583
+ function isRetryable(failure) {
584
+ return failure.kind !== "status" || failure.status >= 500;
585
+ }
586
+ async function askContextSource(url, config, fetchImpl, timeoutMs) {
509
587
  const controller = new AbortController();
510
- const timer = setTimeout(() => controller.abort(), 2e3);
588
+ let timedOut = false;
589
+ const timer = setTimeout(() => {
590
+ timedOut = true;
591
+ controller.abort();
592
+ }, timeoutMs);
511
593
  try {
512
- const url = `${config.url.replace(/\/$/, "")}/v1/context/source?repo=${encodeURIComponent(ownRepoName)}`;
513
594
  const res = await fetchImpl(url, {
514
595
  headers: { Authorization: `Bearer ${config.apiKey}`, "x-outerlayer-app-id": config.appId },
515
596
  signal: controller.signal
516
597
  });
517
598
  if (res.status === 401 || res.status === 403) {
518
- onAuthRejected();
519
- return void 0;
599
+ return { kind: "rejected" };
520
600
  }
521
- if (!res.ok) return void 0;
601
+ if (!res.ok) return { kind: "failed", failure: { kind: "status", status: res.status } };
522
602
  const body = await res.json();
523
- return body.data?.sources;
524
- } catch {
525
- return void 0;
603
+ const data = body.data;
604
+ const source = validSource(data?.source);
605
+ if (data === void 0 || data === null || source === void 0 && !Array.isArray(data.sources)) {
606
+ return { kind: "failed", failure: { kind: "network", message: "the answer named no sources" } };
607
+ }
608
+ return { kind: "answered", source, sources: data.sources };
609
+ } catch (err) {
610
+ return {
611
+ kind: "failed",
612
+ failure: timedOut ? { kind: "timeout", timeoutMs } : { kind: "network", message: err instanceof Error ? err.message : String(err) }
613
+ };
526
614
  } finally {
527
615
  clearTimeout(timer);
528
616
  }
529
617
  }
618
+ function validSource(source) {
619
+ if (source === null) return null;
620
+ const candidate = source;
621
+ if (typeof candidate?.repository === "string" && typeof candidate.ref === "string" && typeof candidate.sha === "string") {
622
+ return { repository: candidate.repository, ref: candidate.ref, sha: candidate.sha };
623
+ }
624
+ return void 0;
625
+ }
530
626
  function resolveGitCommonDir(git, repoRoot) {
531
627
  const abs = gitText(git, repoRoot, ["rev-parse", "--path-format=absolute", "--git-common-dir"], 2e3);
532
628
  if (abs !== void 0) return abs;