fix(feedback): allow restamping suspect resolutions
This commit is contained in:
parent
820f9aaf8e
commit
f00762ffdb
2 changed files with 92 additions and 11 deletions
|
|
@ -1,6 +1,5 @@
|
|||
import { _getAdapter, rowToSelfFeedback } from './sf-db-core.js';
|
||||
import { SF_STALE_STATE, SFError } from '../errors.js';
|
||||
import { logWarning } from '../workflow-logger.js';
|
||||
import { SF_STALE_STATE, SFError } from "../errors.js";
|
||||
import { _getAdapter, rowToSelfFeedback } from "./sf-db-core.js";
|
||||
|
||||
/**
|
||||
* Severity-derived default impact_score (sf-mp4rxkx0-fkt3e2). Operators can
|
||||
|
|
@ -144,7 +143,10 @@ export function linkEntries(fromId, toId, relationKind) {
|
|||
});
|
||||
return result.changes > 0;
|
||||
} catch (err) {
|
||||
const msg = err && typeof err === "object" && "message" in err ? String(err.message) : "";
|
||||
const msg =
|
||||
err && typeof err === "object" && "message" in err
|
||||
? String(err.message)
|
||||
: "";
|
||||
if (
|
||||
msg.includes("UNIQUE constraint failed") ||
|
||||
msg.includes("PRIMARY KEY constraint failed")
|
||||
|
|
@ -202,7 +204,10 @@ export function resolveSelfFeedbackEntry(entryId, resolution) {
|
|||
const existing = currentDb
|
||||
.prepare("SELECT * FROM self_feedback WHERE id = :id")
|
||||
.get({ ":id": entryId });
|
||||
if (!existing || existing["resolved_at"]) return false;
|
||||
if (!existing) return false;
|
||||
if (existing["resolved_at"] && hasCredibleResolutionEvidence(existing)) {
|
||||
return false;
|
||||
}
|
||||
const resolvedAt = resolution.resolvedAt ?? new Date().toISOString();
|
||||
const entry = {
|
||||
...rowToSelfFeedback(existing),
|
||||
|
|
@ -221,7 +226,7 @@ export function resolveSelfFeedbackEntry(entryId, resolution) {
|
|||
resolved_by_sf_version = :resolved_by_sf_version,
|
||||
resolved_evidence_json = :resolved_evidence_json,
|
||||
resolved_criteria_json = :resolved_criteria_json
|
||||
WHERE id = :id AND resolved_at IS NULL`)
|
||||
WHERE id = :id`)
|
||||
.run({
|
||||
":id": entryId,
|
||||
":full_json": JSON.stringify(entry),
|
||||
|
|
@ -237,3 +242,18 @@ export function resolveSelfFeedbackEntry(entryId, resolution) {
|
|||
});
|
||||
return result.changes > 0;
|
||||
}
|
||||
|
||||
function hasCredibleResolutionEvidence(row) {
|
||||
try {
|
||||
const parsed = row["resolved_evidence_json"]
|
||||
? JSON.parse(row["resolved_evidence_json"])
|
||||
: null;
|
||||
return (
|
||||
parsed?.kind === "agent-fix" ||
|
||||
parsed?.kind === "human-clear" ||
|
||||
parsed?.kind === "promoted-to-requirement"
|
||||
);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -153,6 +153,51 @@ test("markResolved_when_db_available_updates_sqlite_and_markdown_projection", ()
|
|||
assert.match(markdown, /Recently Resolved/);
|
||||
});
|
||||
|
||||
test("markResolved_when_existing_resolution_lacks_credible_evidence_restamps_entry", () => {
|
||||
const project = makeForgeProject();
|
||||
const result = recordSelfFeedback(
|
||||
{
|
||||
kind: "gap:suspect-resolution",
|
||||
severity: "high",
|
||||
summary: "Resolved row lacks credible evidence",
|
||||
},
|
||||
project,
|
||||
);
|
||||
assert.ok(result?.entry.id);
|
||||
const first = markResolved(
|
||||
result.entry.id,
|
||||
{
|
||||
reason: "raw SQL-like resolution",
|
||||
evidence: { kind: "auto-version-bump" },
|
||||
},
|
||||
project,
|
||||
);
|
||||
assert.equal(first, true);
|
||||
|
||||
const restamped = markResolved(
|
||||
result.entry.id,
|
||||
{
|
||||
reason: "fixed with a real agent patch",
|
||||
evidence: { kind: "agent-fix" },
|
||||
},
|
||||
project,
|
||||
);
|
||||
|
||||
assert.equal(restamped, true);
|
||||
const [entry] = readAllSelfFeedback(project);
|
||||
assert.equal(entry.resolvedReason, "fixed with a real agent patch");
|
||||
assert.equal(entry.resolvedEvidence.kind, "agent-fix");
|
||||
const idempotent = markResolved(
|
||||
result.entry.id,
|
||||
{
|
||||
reason: "second credible resolution should not mutate",
|
||||
evidence: { kind: "human-clear" },
|
||||
},
|
||||
project,
|
||||
);
|
||||
assert.equal(idempotent, false);
|
||||
});
|
||||
|
||||
test("markResolved_rejects_resolution_with_non_canonical_evidence_kind", () => {
|
||||
const project = makeForgeProject();
|
||||
const result = recordSelfFeedback(
|
||||
|
|
@ -942,7 +987,10 @@ test("markResolved_db_only_entry_resolves_without_jsonl_lookup", () => {
|
|||
// DB row is resolved
|
||||
const entries = listSelfFeedbackEntries();
|
||||
const resolved = entries.find((e) => e.id === "db-only-test-entry-1");
|
||||
assert.ok(resolved?.resolvedAt, "DB row must carry resolvedAt after resolution");
|
||||
assert.ok(
|
||||
resolved?.resolvedAt,
|
||||
"DB row must carry resolvedAt after resolution",
|
||||
);
|
||||
});
|
||||
|
||||
test("markResolved_db_only_entry_populates_jsonl_and_markdown_after_resolution", () => {
|
||||
|
|
@ -1089,7 +1137,10 @@ test("regenerateSelfFeedbackJsonl_includes_generated_comment_header", () => {
|
|||
);
|
||||
const firstLine = content.split("\n")[0];
|
||||
const meta = JSON.parse(firstLine);
|
||||
assert.ok(meta._meta, "JSONL first line must be a JSON object with _meta key");
|
||||
assert.ok(
|
||||
meta._meta,
|
||||
"JSONL first line must be a JSON object with _meta key",
|
||||
);
|
||||
assert.ok(
|
||||
meta._meta.includes("generated from .sf/sf.db"),
|
||||
"_meta must mention sf.db",
|
||||
|
|
@ -1107,8 +1158,14 @@ test("backfillSelfFeedbackJsonl_four_stuck_entries_appear_in_both_outputs", () =
|
|||
// appear as resolved in both JSONL and Markdown.
|
||||
const project = makeForgeProject();
|
||||
const stuckEntries = [
|
||||
{ id: "sf-mp5tuvdx-ibyk9b", kind: "architecture-defect:sf-print-mode-hangs" },
|
||||
{ id: "sf-mp5tp6uh-8eafni", kind: "architecture-defect:no-subagent-dispatch-observability" },
|
||||
{
|
||||
id: "sf-mp5tuvdx-ibyk9b",
|
||||
kind: "architecture-defect:sf-print-mode-hangs",
|
||||
},
|
||||
{
|
||||
id: "sf-mp5tp6uh-8eafni",
|
||||
kind: "architecture-defect:no-subagent-dispatch-observability",
|
||||
},
|
||||
{ id: "sf-mp6ed4xq-2sgx8w", kind: "runaway-loop:idle-halt" },
|
||||
{ id: "sf-mp6eitbh-9krkd9", kind: "runaway-loop:idle-halt" },
|
||||
];
|
||||
|
|
@ -1166,7 +1223,11 @@ test("backfillSelfFeedbackJsonl_four_stuck_entries_appear_in_both_outputs", () =
|
|||
return false;
|
||||
}
|
||||
});
|
||||
assert.equal(resolvedLines.length, 4, "all 4 stuck entries must appear as resolved in JSONL");
|
||||
assert.equal(
|
||||
resolvedLines.length,
|
||||
4,
|
||||
"all 4 stuck entries must appear as resolved in JSONL",
|
||||
);
|
||||
|
||||
// Markdown must show all 4 in "Recently Resolved"
|
||||
const markdown = readFileSync(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue