@ai-dossier/sched 0.52.0 → 0.54.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.
@@ -100,6 +100,7 @@ exports.MAX_BATCH_AGENT_RESPAWNS = exports.tailMembersRefusal = void 0;
100
100
  exports.memberBranchFor = memberBranchFor;
101
101
  exports.evictMemberAndContinue = evictMemberAndContinue;
102
102
  exports.resumeBlockedGate = resumeBlockedGate;
103
+ exports.undispatchedSurvivingMembers = undispatchedSurvivingMembers;
103
104
  exports.reconcileStaleBlockedBatches = reconcileStaleBlockedBatches;
104
105
  exports.memberDispatchModeFor = memberDispatchModeFor;
105
106
  exports.runBatchTick = runBatchTick;
@@ -3141,7 +3142,18 @@ const MEMBER_RECONCILE_CHAIN = {
3141
3142
  * and the branch is exactly what the merge evidence below covers.
3142
3143
  */
3143
3144
  function everySurvivingMemberDispatched(state, batch) {
3145
+ return undispatchedSurvivingMembers(state, batch).length === 0;
3146
+ }
3147
+ /**
3148
+ * The surviving members `everySurvivingMemberDispatched` finds NOT yet
3149
+ * dispatched — the members that keep a blocked batch out of
3150
+ * `reconcileStaleBlockedBatches` (#686). Exported for `sched attach-pr`
3151
+ * (#824), which refuses to record a PR the reconcile would then never act on,
3152
+ * and names these members in the refusal.
3153
+ */
3154
+ function undispatchedSurvivingMembers(state, batch) {
3144
3155
  const evicted = new Set(batch.evictions.map((e) => e.issue));
3156
+ const undispatched = [];
3145
3157
  for (const issue of batch.members) {
3146
3158
  const entry = (0, state_1.findEntry)(state, issue);
3147
3159
  if (!entry)
@@ -3159,9 +3171,9 @@ function everySurvivingMemberDispatched(state, batch) {
3159
3171
  // completion — everything the entry can reach afterwards means the
3160
3172
  // member was dispatched and its work is on the branch.
3161
3173
  if (!DISPATCHED_MEMBER_STATUSES.has(entry.status))
3162
- return false;
3174
+ undispatched.push(issue);
3163
3175
  }
3164
- return true;
3176
+ return undispatched;
3165
3177
  }
3166
3178
  /** Slot-line statuses that prove a member was actually dispatched (see above). */
3167
3179
  const DISPATCHED_MEMBER_STATUSES = new Set([
@@ -3228,6 +3240,26 @@ function clearPrDetectAmbiguous(deps, batch, now) {
3228
3240
  */
3229
3241
  function recordPrDetectAmbiguous(deps, batch, matches, now) {
3230
3242
  const { since, ticks, announce } = advanceBatchDedupStreak(batch.pr_detect_ambiguous_reason, batch.pr_detect_ambiguous_since, batch.pr_detect_ambiguous_ticks, state_1.PR_DETECT_AMBIGUOUS_REASON, now);
3243
+ // #824 review: the lookup above ran outside the lock — an operator's
3244
+ // `sched attach-pr` (or a resume/abandon) may have landed meanwhile. Write
3245
+ // the streak (and journal it) only while the batch is STILL blocked with no
3246
+ // PR; never rewrite the marker an attach just cleared, nor tell the
3247
+ // operator to run `sched attach-pr` after they already did.
3248
+ const written = deps.store.withLock((s) => {
3249
+ const b = (0, state_1.findBatch)(s, batch.id);
3250
+ if (!b || b.status !== 'blocked' || b.pr !== null)
3251
+ return { state: s, result: false };
3252
+ return {
3253
+ state: (0, state_1.patchBatch)(s, batch.id, {
3254
+ pr_detect_ambiguous_reason: state_1.PR_DETECT_AMBIGUOUS_REASON,
3255
+ pr_detect_ambiguous_since: since,
3256
+ pr_detect_ambiguous_ticks: ticks,
3257
+ }, now, false),
3258
+ result: true,
3259
+ };
3260
+ });
3261
+ if (!written)
3262
+ return;
3231
3263
  if (announce) {
3232
3264
  journalEvent(deps, 'pr-detect-ambiguous', unit(batch.id), {
3233
3265
  reason: state_1.PR_DETECT_AMBIGUOUS_REASON,
@@ -3236,21 +3268,14 @@ function recordPrDetectAmbiguous(deps, batch, matches, now) {
3236
3268
  at: now.toISOString(),
3237
3269
  since,
3238
3270
  ticks_persisted: ticks,
3239
- // #789 review: neither "record it explicitly" (no command exists) nor
3240
- // "close the extras" (a MERGED PR cannot be closed, so it never leaves
3241
- // `gh pr list --state merged`) was ever an actionable remedy — state
3242
- // what actually resolves it instead. #824 tracks the missing verb.
3243
- detail: `${matches.length} MERGED PRs match head=${batch.branch ?? '?'} (#${matches.join(', #')}) — refusing to guess which is ours; nothing recorded. The batch still reconciles via commits-in-base/members-closed evidence if either holds; otherwise inspect and \`sched abandon --batch ${batch.id}\`, or use \`sched attach-pr\` once #824 ships it`,
3271
+ // #789 review: "close the extras" (a MERGED PR cannot be closed, so it
3272
+ // never leaves `gh pr list --state merged`) was never an actionable
3273
+ // remedy — state what actually resolves it instead: the operator names
3274
+ // the batch's own PR with `sched attach-pr` (#824), which holds it to
3275
+ // these same candidate checks.
3276
+ detail: `${matches.length} MERGED PRs match head=${batch.branch ?? '?'} (#${matches.join(', #')}) — refusing to guess which is ours; nothing recorded. The batch still reconciles via commits-in-base/members-closed evidence if either holds; otherwise inspect the candidates and record the batch's own with \`sched attach-pr --batch ${batch.id} <pr>\`, or \`sched abandon --batch ${batch.id}\``,
3244
3277
  });
3245
3278
  }
3246
- deps.store.withLock((s) => ({
3247
- state: (0, state_1.patchBatch)(s, batch.id, {
3248
- pr_detect_ambiguous_reason: state_1.PR_DETECT_AMBIGUOUS_REASON,
3249
- pr_detect_ambiguous_since: since,
3250
- pr_detect_ambiguous_ticks: ticks,
3251
- }, now, false),
3252
- result: undefined,
3253
- }));
3254
3279
  }
3255
3280
  /**
3256
3281
  * #686: reconcile a `blocked` batch whose work demonstrably shipped anyway.
@@ -3382,7 +3407,7 @@ function reconcileStaleBlockedBatches(deps, now, result, anchorCtx) {
3382
3407
  // #789 review: what the lock ACTUALLY applied, not what the outer
3383
3408
  // snapshot predicted — re-derived from `b` (read under the lock) below,
3384
3409
  // since `batch.pr` could in principle have changed between the snapshot
3385
- // above and the lock acquiring (e.g. a future `sched attach-pr`, #824).
3410
+ // above and the lock acquiring (e.g. `sched attach-pr`, #824).
3386
3411
  let effectivePr = null;
3387
3412
  let prWasRecorded = false;
3388
3413
  deps.store.withLock((s) => {