@teamscala/service-boot 0.23.291 → 0.23.292

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.
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  },
7
7
  "name": "@teamscala/service-boot",
8
8
  "type": "module",
9
- "version": "0.23.291",
9
+ "version": "0.23.292",
10
10
  "sideEffects": false,
11
11
  "files": [
12
12
  "src"
@@ -501,10 +501,31 @@ async function reconcileTaskList(
501
501
  laneSession: string,
502
502
  list: TaskListItem[],
503
503
  source?: string,
504
+ pane?: string | null,
504
505
  ): Promise<{ task_id: string; created: number; updated: number; deleted: number; total: number }> {
505
- const task = await resolveLaneTask(prisma, laneSession);
506
+ // BIND ON DEMAND — this path used to `resolveLaneTask` and THROW, which is the
507
+ // exact failure `ensureLaneTask` was written to make impossible (see its
508
+ // comment above, and the operator ruling it cites: "ALL lanes can comply,
509
+ // there is no reason why a tab cant comply"). The backstop existed and this
510
+ // caller did not use it, so a lane whose SessionStart bindLane never ran — or
511
+ // ran while the endpoint was failing — could never sync a task list again.
512
+ // Not degraded: permanently, with a 404 on every attempt and no path to
513
+ // self-recover.
514
+ //
515
+ // Measured 2026-07-30: a live lane reported ~9 consecutive failures over
516
+ // hours and correctly diagnosed its own lane-compliance flag as a false
517
+ // positive it was structurally unable to clear. The gate was telling it to
518
+ // keep a task list while the only endpoint that records one refused it.
519
+ //
520
+ // A lane calling this HAS everything the bind needs — its session, its pane,
521
+ // its list — so requiring a prior call is a sequencing demand with no
522
+ // informational content. `friction-is-a-stop-condition`: the compliant path
523
+ // must exist, or the rule is unenforceable against the lane it binds.
524
+ const task = await ensureLaneTask(prisma, laneSession, pane ?? null);
506
525
  if (!task) {
507
- throw new Error(`No task bound to session ${laneSession}. Call taskSync.bindLane first (SessionStart adapter).`);
526
+ throw new Error(
527
+ `Could not bind a task for session ${laneSession} — the create failed and no existing task was found.`,
528
+ );
508
529
  }
509
530
  const existing = await prisma.work_items.findMany({
510
531
  where: { parent_id: task.id, kind: "subtask" },
@@ -701,8 +722,13 @@ export const taskSyncRouter = {
701
722
  const { ORPCError } = await import("@orpc/server");
702
723
  throw new ORPCError("BAD_REQUEST", { message: "upsertFromTaskList requires lane_session" });
703
724
  }
725
+ // The pane rides in the header the callers already send (`x-lane-pane`),
726
+ // and it is what lets the on-demand bind link the new task to the lane's
727
+ // run. Absent, the bind still succeeds — it just cannot link the run — so
728
+ // this is passed opportunistically rather than required.
729
+ const headerPane = (context as Context).headers?.get("x-lane-pane") ?? null;
704
730
  try {
705
- const res = await reconcileTaskList(prisma, session, list, body.source);
731
+ const res = await reconcileTaskList(prisma, session, list, body.source, headerPane);
706
732
  log.info("[task-sync] upsertFromTaskList reconciled", { session, ...res });
707
733
  return res;
708
734
  } catch (err) {