@afokapu/atdd-flow 0.3.61 → 0.3.62
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 +1 -1
- package/src/tasks.ts +27 -4
package/package.json
CHANGED
package/src/tasks.ts
CHANGED
|
@@ -13,6 +13,7 @@ export type HandoffState = "executing" | "awaiting_coordinator" | "awaiting_assi
|
|
|
13
13
|
export type TaskHandoff = { state: HandoffState; evidence: string; updated_at: string; submitted_phase?: TaskPhase };
|
|
14
14
|
export type DoneWhen = { text: string; proof?: string };
|
|
15
15
|
export type GovernedBase = { coordinator: string; branch: string; commit: string };
|
|
16
|
+
export type LineageRef = { ref: string; commit: string; reason?: string };
|
|
16
17
|
/** An append-only accountability decision; prior coordinator evidence remains untouched. */
|
|
17
18
|
export type CoordinatorTransfer = {
|
|
18
19
|
from: string;
|
|
@@ -21,6 +22,8 @@ export type CoordinatorTransfer = {
|
|
|
21
22
|
/** Required when main executes the transfer; references an operator@desk message. */
|
|
22
23
|
authorization?: string;
|
|
23
24
|
exact_head: { branch: string; commit: string };
|
|
25
|
+
/** The driver ref used for the lineage check; HEAD means the recorded branch no longer resolved. */
|
|
26
|
+
lineage_ref?: LineageRef;
|
|
24
27
|
effective_at: string;
|
|
25
28
|
};
|
|
26
29
|
export type TerminalDisposition = { kind: "superseded" | "rejected"; by: string; at: string; reason: string };
|
|
@@ -117,6 +120,23 @@ async function writeTask(root: string, projectName: string, id: string, task: Ta
|
|
|
117
120
|
|
|
118
121
|
/** Records the exact parent head for new bounded topology without rewriting legacy tasks. */
|
|
119
122
|
async function governedBase(root: string, projectName: string, coordinatorAddress: string, assignee?: string): Promise<GovernedBase | undefined> {
|
|
123
|
+
return (await governedLineage(root, projectName, coordinatorAddress, assignee))?.base;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** The recorded branch when it resolves; otherwise the driver worktree HEAD; otherwise fail closed. */
|
|
127
|
+
async function driverLineageRef(driver: Seat): Promise<LineageRef> {
|
|
128
|
+
const resolveRef = (ref: string) => runOutput(["git", "-C", driver.worktree, "rev-parse", "--verify", "--quiet", `${ref}^{commit}`]);
|
|
129
|
+
try { return { ref: driver.branch, commit: await resolveRef(driver.branch) }; }
|
|
130
|
+
catch {
|
|
131
|
+
if (!await exists(driver.worktree)) {
|
|
132
|
+
throw new Error(`Driver ${driver.address} branch ${driver.branch} no longer resolves and its worktree ${driver.worktree} is missing; cannot verify lineage.`);
|
|
133
|
+
}
|
|
134
|
+
try { return { ref: "HEAD", commit: await resolveRef("HEAD"), reason: `recorded branch ${driver.branch} no longer resolves; used worktree HEAD` }; }
|
|
135
|
+
catch { throw new Error(`Driver ${driver.address} branch ${driver.branch} no longer resolves and worktree ${driver.worktree} has no valid HEAD.`); }
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function governedLineage(root: string, projectName: string, coordinatorAddress: string, assignee?: string): Promise<{ base: GovernedBase; lineage: LineageRef } | undefined> {
|
|
120
140
|
if (!assignee) return undefined;
|
|
121
141
|
const config = await project(root, projectName);
|
|
122
142
|
if (!config.repository) return undefined;
|
|
@@ -146,9 +166,10 @@ async function governedBase(root: string, projectName: string, coordinatorAddres
|
|
|
146
166
|
throw new Error(`Task coordinator ${coordinator.address} must be main or a named coordinator.`);
|
|
147
167
|
}
|
|
148
168
|
const commit = await runOutput(["git", "-C", coordinator.worktree, "rev-parse", branch]);
|
|
149
|
-
const
|
|
169
|
+
const lineage = await driverLineageRef(driver);
|
|
170
|
+
const common = await runOutput(["git", "-C", driver.worktree, "merge-base", commit, lineage.commit]);
|
|
150
171
|
if (common !== commit) throw new Error(`Driver ${driver.address} is not based on the exact coordinator head ${commit}.`);
|
|
151
|
-
return { coordinator: coordinator.address, branch, commit };
|
|
172
|
+
return { base: { coordinator: coordinator.address, branch, commit }, lineage };
|
|
152
173
|
}
|
|
153
174
|
|
|
154
175
|
/**
|
|
@@ -390,8 +411,9 @@ export async function transfer(root: string, projectName: string, id: string, ar
|
|
|
390
411
|
|
|
391
412
|
// governedBase confirms main/named-coordinator identity, exact branch head,
|
|
392
413
|
// and the driver's descendant relationship before accountability changes.
|
|
393
|
-
const
|
|
394
|
-
|
|
414
|
+
const governed = await governedLineage(root, projectName, target, task.assignee);
|
|
415
|
+
const base = governed?.base;
|
|
416
|
+
if (!governed || !base) throw new Error(`Task ${id} needs an assigned driver and governed repository before transfer.`);
|
|
395
417
|
const authorization = actor === main ? required(words(args, "--authorization"), "an owner-authorized topology plan") : undefined;
|
|
396
418
|
if (authorization) await operatorAuthorization(root, authorization, main, {
|
|
397
419
|
schema: "atdd-workflow/task-transfer-authorization/v1",
|
|
@@ -408,6 +430,7 @@ export async function transfer(root: string, projectName: string, id: string, ar
|
|
|
408
430
|
reason: required(words(args, "--reason"), "--reason"),
|
|
409
431
|
...(authorization ? { authorization } : {}),
|
|
410
432
|
exact_head: { branch: base.branch, commit: base.commit },
|
|
433
|
+
lineage_ref: governed.lineage,
|
|
411
434
|
effective_at: now(),
|
|
412
435
|
};
|
|
413
436
|
task.coordinator = target;
|