@afokapu/atdd-flow 0.3.52 → 0.3.54

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/handover.ts +24 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@afokapu/atdd-flow",
3
- "version": "0.3.52",
3
+ "version": "0.3.54",
4
4
  "description": "Filesystem-first Desk registry for projects, seats, tasks, threads, and handoffs.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/handover.ts CHANGED
@@ -286,7 +286,26 @@ function assertSinglePiWriter(raw: string) {
286
286
  if (pi.length !== 1) throw new Error("Legacy source bootstrap requires exactly one live Pi writer on the authorized source pane.");
287
287
  }
288
288
 
289
- async function verifyLegacySource(source: HandoverReplacementRequest["source"], target: HandoverReplacementRequest["target"], authorizationHash: string, dependencies: HandoverReplacementDependencies) {
289
+ type PiSource = Pick<HandoverReplacementRequest["source"], "pi_session" | "pi_session_path">;
290
+
291
+ function assertCanonicalSourceNotLive(raw: string, canonical: PiSource) {
292
+ const value = JSON.parse(raw), agents = result(value)?.agents;
293
+ if (!Array.isArray(agents)) throw new Error("Consumed Path-A replacement requires complete global Herdr agent-list evidence.");
294
+ for (const entry of agents) {
295
+ const agent = object(entry);
296
+ if (!agent) throw new Error("Consumed Path-A replacement requires complete global Herdr agent-list evidence.");
297
+ if (text(agent.agent) !== "pi") continue;
298
+ const session = object(agent.agent_session);
299
+ const sessionPath = session?.kind === "path" ? text(session.value) : undefined;
300
+ if (!sessionPath) throw new Error("Consumed Path-A replacement requires complete global Herdr agent-list evidence.");
301
+ const sessionId = basename(sessionPath).match(/_([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/i)?.[1];
302
+ if (resolve(sessionPath) === resolve(canonical.pi_session_path) || sessionId === canonical.pi_session) {
303
+ throw new Error("Consumed Path-A replacement prior canonical source is still a live Pi agent.");
304
+ }
305
+ }
306
+ }
307
+
308
+ async function verifyLegacySource(source: HandoverReplacementRequest["source"], target: HandoverReplacementRequest["target"], authorizationHash: string, dependencies: HandoverReplacementDependencies, canonical?: PiSource) {
290
309
  const command = dependencies.command ?? runOutput;
291
310
  const pane = legacyPaneObservation(await command(herdr(source.herdr_session, "pane", "get", source.pane)));
292
311
  assertLegacyPane(pane, source, target.cwd);
@@ -296,6 +315,9 @@ async function verifyLegacySource(source: HandoverReplacementRequest["source"],
296
315
  ]);
297
316
  const agent = legacyAgentName(agentRaw, source, target.cwd);
298
317
  assertSinglePiWriter(processRaw);
318
+ if (canonical && (canonical.pi_session !== source.pi_session || resolve(canonical.pi_session_path) !== resolve(source.pi_session_path))) {
319
+ assertCanonicalSourceNotLive(await command(herdr(source.herdr_session, "agent", "list")), canonical);
320
+ }
299
321
  return { authorization_hash: authorizationHash, ...source, cwd: target.cwd, agent, verified_at: dependencies.at?.() ?? now() };
300
322
  }
301
323
 
@@ -421,7 +443,6 @@ function assertCanonicalPlacement(intent: Awaited<ReturnType<typeof prepareRepla
421
443
  || resolve(request.target.cwd) !== resolve(intent.record.worktree)) {
422
444
  throw new Error("Consumed Path-A replacement does not match the unchanged canonical main runtime and placement.");
423
445
  }
424
- if (intent.handover.supersedes !== request.handover.id) throw new Error("Consumed Path-A replacement does not match the current handover lineage.");
425
446
  }
426
447
 
427
448
  function canonicalRuntimeForSupersession(intent: Awaited<ReturnType<typeof prepareReplacement>>, request: HandoverReplacementRequest) {
@@ -666,7 +687,7 @@ async function supersedeConsumedReplacement(root: string, resolved: string, file
666
687
  seat: resolved, pi_session: intent.source.pi_session, pi_session_path: intent.source.pi_session_path,
667
688
  source: { herdr_session: intent.source.herdr_session, pane: intent.source.pane }, target: intent.target,
668
689
  });
669
- const liveSource = await verifyLegacySource(intent.source, intent.target, authorization.hash, dependencies);
690
+ const liveSource = await verifyLegacySource(intent.source, intent.target, authorization.hash, dependencies, consumed.canonical);
670
691
  const request = supersedingRequest(resolved, file, intent, authorization, consumed, liveSource, dependencies);
671
692
  const manifest = supersessionManifest(resolved, file, consumed);
672
693
  const staging = await stageSupersession(file, request, consumed, manifest, dependencies);