|
@@ -29,6 +29,18 @@ namespace ClankerSaddle.Services {
|
|
|
public const string GOAL_BLOCK_PLAN = "plan";
|
|
public const string GOAL_BLOCK_PLAN = "plan";
|
|
|
public const string GOAL_BLOCK_COMPLETION = "completion";
|
|
public const string GOAL_BLOCK_COMPLETION = "completion";
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Whether a run-task block row is THE completion review. The review is
|
|
|
|
|
+ * raised from two paths — the web's completion raise (kind
|
|
|
|
|
+ * {@link GOAL_BLOCK_COMPLETION}) and the daemon's done-park (kind
|
|
|
|
|
+ * "review") — which adopt each other so only one row ever exists;
|
|
|
|
|
+ * every completion-verdict/narrative surface matches both kinds.
|
|
|
|
|
+ */
|
|
|
|
|
+ private static bool is_completion_row(TaskBlock block) {
|
|
|
|
|
+ return block.block_type == GOAL_BLOCK_COMPLETION
|
|
|
|
|
+ || block.block_type == "review";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
/** Task kinds (the `tasks.kind` wire values). */
|
|
/** Task kinds (the `tasks.kind` wire values). */
|
|
|
public const string TASK_KIND_TASK = "task";
|
|
public const string TASK_KIND_TASK = "task";
|
|
|
public const string TASK_KIND_GOAL_RUN = "goal_run";
|
|
public const string TASK_KIND_GOAL_RUN = "goal_run";
|
|
@@ -419,7 +431,10 @@ namespace ClankerSaddle.Services {
|
|
|
throw new Error(Quark.from_string("clanker-saddle"), 85,
|
|
throw new Error(Quark.from_string("clanker-saddle"), 85,
|
|
|
@"The goal is already $(((!)goal).status) — its completion review is closed");
|
|
@"The goal is already $(((!)goal).status) — its completion review is closed");
|
|
|
}
|
|
}
|
|
|
- // Idempotent on the pending report (see submit_plan).
|
|
|
|
|
|
|
+ // Idempotent on the pending report (see submit_plan): the
|
|
|
|
|
+ // raise adopts an unanswered review-family row (the daemon's
|
|
|
|
|
+ // done-park "review" block IS this review) — one row,
|
|
|
|
|
+ // whichever path landed first.
|
|
|
TaskBlock? raised = yield raise_run_block(((!)goal), "Goal completion report",
|
|
TaskBlock? raised = yield raise_run_block(((!)goal), "Goal completion report",
|
|
|
GOAL_BLOCK_COMPLETION, null, report, true);
|
|
GOAL_BLOCK_COMPLETION, null, report, true);
|
|
|
if (raised == null) {
|
|
if (raised == null) {
|
|
@@ -548,7 +563,7 @@ namespace ClankerSaddle.Services {
|
|
|
} else {
|
|
} else {
|
|
|
((!)goal).status = GOAL_PLANNING;
|
|
((!)goal).status = GOAL_PLANNING;
|
|
|
}
|
|
}
|
|
|
- } else if (((!)block).block_type == GOAL_BLOCK_COMPLETION) {
|
|
|
|
|
|
|
+ } else if (is_completion_row(((!)block))) {
|
|
|
if (answer == NOTICE_REVIEW_APPROVED) {
|
|
if (answer == NOTICE_REVIEW_APPROVED) {
|
|
|
// Plan 15 C4: the operator-confirmed hard gate —
|
|
// Plan 15 C4: the operator-confirmed hard gate —
|
|
|
// refuse while origin is behind (live flags only).
|
|
// refuse while origin is behind (live flags only).
|
|
@@ -563,7 +578,7 @@ namespace ClankerSaddle.Services {
|
|
|
|
|
|
|
|
yield tasks.answer_block(block_id, answer, answered_by);
|
|
yield tasks.answer_block(block_id, answer, answered_by);
|
|
|
|
|
|
|
|
- if (((!)block).block_type == GOAL_BLOCK_COMPLETION
|
|
|
|
|
|
|
+ if (is_completion_row(((!)block))
|
|
|
&& answer == NOTICE_REVIEW_APPROVED) {
|
|
&& answer == NOTICE_REVIEW_APPROVED) {
|
|
|
// Approving the completion closes the goal agent's
|
|
// Approving the completion closes the goal agent's
|
|
|
// own task: the run is finished, its saddle frees.
|
|
// own task: the run is finished, its saddle frees.
|
|
@@ -601,7 +616,19 @@ namespace ClankerSaddle.Services {
|
|
|
goal.id.to_string(), question);
|
|
goal.id.to_string(), question);
|
|
|
return null;
|
|
return null;
|
|
|
}
|
|
}
|
|
|
|
|
+ // Review-family cross-dedupe: the completion review is raised
|
|
|
|
|
+ // from two paths (the web's completion kind, the daemon's
|
|
|
|
|
+ // done-park "review" kind, different question strings) — an
|
|
|
|
|
+ // unanswered row of EITHER kind IS the pending review; adopt
|
|
|
|
|
+ // it rather than raising a twin.
|
|
|
|
|
+ bool review_family = block_type == GOAL_BLOCK_COMPLETION
|
|
|
|
|
+ || block_type == "review";
|
|
|
foreach (var existing in yield tasks.list_blocks(((!)run).id)) {
|
|
foreach (var existing in yield tasks.list_blocks(((!)run).id)) {
|
|
|
|
|
+ if (review_family && existing.answer == null
|
|
|
|
|
+ && (existing.block_type == GOAL_BLOCK_COMPLETION
|
|
|
|
|
+ || existing.block_type == "review")) {
|
|
|
|
|
+ return existing;
|
|
|
|
|
+ }
|
|
|
bool same = dedupe_on_type
|
|
bool same = dedupe_on_type
|
|
|
? existing.block_type == block_type : existing.question == question;
|
|
? existing.block_type == block_type : existing.question == question;
|
|
|
if (same && existing.answer == null) {
|
|
if (same && existing.answer == null) {
|
|
@@ -742,7 +769,7 @@ namespace ClankerSaddle.Services {
|
|
|
rows.add(new NarrativeRow() {
|
|
rows.add(new NarrativeRow() {
|
|
|
kind = "plan-submitted", at = block.created_at,
|
|
kind = "plan-submitted", at = block.created_at,
|
|
|
text = @"Plan revision #$(block.id) submitted for review" });
|
|
text = @"Plan revision #$(block.id) submitted for review" });
|
|
|
- } else if (block.block_type == GOAL_BLOCK_COMPLETION) {
|
|
|
|
|
|
|
+ } else if (is_completion_row(block)) {
|
|
|
rows.add(new NarrativeRow() {
|
|
rows.add(new NarrativeRow() {
|
|
|
kind = "completion-submitted", at = block.created_at,
|
|
kind = "completion-submitted", at = block.created_at,
|
|
|
text = "Completion report submitted for review" });
|
|
text = "Completion report submitted for review" });
|
|
@@ -761,7 +788,7 @@ namespace ClankerSaddle.Services {
|
|
|
text = approved
|
|
text = approved
|
|
|
? @"Plan revision #$(block.id) approved — the goal is active"
|
|
? @"Plan revision #$(block.id) approved — the goal is active"
|
|
|
: @"Plan revision #$(block.id) rejected with feedback — the agent revises" });
|
|
: @"Plan revision #$(block.id) rejected with feedback — the agent revises" });
|
|
|
- } else if (block.block_type == GOAL_BLOCK_COMPLETION) {
|
|
|
|
|
|
|
+ } else if (is_completion_row(block)) {
|
|
|
bool approved = block.answer == NOTICE_REVIEW_APPROVED;
|
|
bool approved = block.answer == NOTICE_REVIEW_APPROVED;
|
|
|
rows.add(new NarrativeRow() {
|
|
rows.add(new NarrativeRow() {
|
|
|
kind = approved ? "goal-approved" : "completion-rejected",
|
|
kind = approved ? "goal-approved" : "completion-rejected",
|