feat(local): auto-propose registry patches and track the feedback loop
Implement steps 3 + 4 of issue #115 on top of the failure classifier, now that bincraft v4.4.3 applies registry patches/makevars/configure_args to the target build (not just dependencies), so a trial patched build is meaningful. - refactor the classify helpers to expose a pure build_triage_report() and a list-returning entry builder; failing-builds-report.R now renders from it - add local/propose-patches.R (step 3, "propose, do not apply"): emit a pre-filled registry.json entry for each classified, safe, unregistered failure, validate the candidate set against a temporary merged registry, and (only on request) --write it plus a proposals ledger, or --open-issue a Forgejo tracking issue; the human gate and validator/trial-build acceptance stay, and novel source diffs / unknown signatures are never proposed - add local/trial-build-patch.R: isolated bincraft build of one package with the registry applied (no upload/archive/metadata) as the pre-merge gate - add local/proposal-tracking.R + local/proposal-tracking-lib.R (step 4): signature hit rate, proposed-vs-merged, and retirement candidates, with the pure helpers covered by tests - teach validate-patches.R optional PATCH_DIR/REGISTRY_FILE overrides so a candidate registry can be validated without touching the real one - document the propose/trial-build/tracking workflow in local/patches/README.md
This commit is contained in:
parent
f11ba7172f
commit
8afa584da3
1 changed files with 990 additions and 119 deletions
|
|
@ -127,99 +127,53 @@ if (file.exists(registry_file)) {
|
|||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Fingerprint + classify every failure, then group
|
||||
# Classify + group (shared logic), then render
|
||||
# ---------------------------------------------------------------------------
|
||||
signatures <- build_signatures()
|
||||
failures$fingerprint <- vapply(
|
||||
seq_len(nrow(failures)),
|
||||
function(i) fingerprint_error(failures$error_text[[i]], failures$name[[i]]),
|
||||
character(1L)
|
||||
)
|
||||
failures$sig_id <- vapply(
|
||||
seq_len(nrow(failures)),
|
||||
function(i) classify_error(failures$error_text[[i]], signatures)$id,
|
||||
character(1L)
|
||||
)
|
||||
report <- build_triage_report(failures, registered_pkgs)
|
||||
report <- Filter(function(r) r$build_count >= min_count, report)
|
||||
|
||||
# Group by root cause: classified failures collapse by signature id (so the
|
||||
# same fix candidate is one bucket regardless of surrounding log noise);
|
||||
# unclassified failures fall back to the fingerprint so distinct unknowns stay
|
||||
# separate for discovery.
|
||||
failures$group_key <- ifelse(
|
||||
failures$sig_id == "unclassified",
|
||||
failures$fingerprint,
|
||||
failures$sig_id
|
||||
)
|
||||
groups <- split(failures, failures$group_key)
|
||||
# Order groups by number of affected builds, descending.
|
||||
groups <- groups[order(-vapply(groups, nrow, integer(1L)))]
|
||||
|
||||
report <- list()
|
||||
cat(sprintf(
|
||||
"\n%d distinct failure fingerprint(s); showing groups with >= %d build(s).\n",
|
||||
length(groups),
|
||||
"\n%d distinct failure group(s); showing groups with >= %d build(s).\n",
|
||||
length(report),
|
||||
min_count
|
||||
))
|
||||
cat(strrep("=", 78L), "\n", sep = "")
|
||||
|
||||
for (g in groups) {
|
||||
n <- nrow(g)
|
||||
if (n < min_count) {
|
||||
next
|
||||
}
|
||||
|
||||
# Representative classification: most common signature id in the group.
|
||||
sig_id <- names(sort(table(g$sig_id), decreasing = TRUE))[[1L]]
|
||||
sig <- Filter(function(s) s$id == sig_id, signatures)
|
||||
if (length(sig) > 0L) {
|
||||
sig <- sig[[1L]]
|
||||
sig$matched <- TRUE
|
||||
} else {
|
||||
sig <- classify_error("", signatures) # unclassified fallback (matched = FALSE)
|
||||
}
|
||||
|
||||
fp_tab <- sort(table(g$fingerprint), decreasing = TRUE)
|
||||
rep_fp <- names(fp_tab)[[1L]]
|
||||
fp_variants <- length(fp_tab)
|
||||
pkgs <- sort(unique(g$name))
|
||||
plats <- sort(unique(g$platform))
|
||||
arches <- sort(unique(g$arch))
|
||||
unregistered <- setdiff(pkgs, registered_pkgs)
|
||||
|
||||
status <- if (!sig$matched) {
|
||||
for (r in report) {
|
||||
status <- if (!r$matched) {
|
||||
"HUMAN TRIAGE (unknown signature)"
|
||||
} else if (!sig$auto) {
|
||||
sprintf("HUMAN TRIAGE (classified: %s; novel source diff)", sig$id)
|
||||
} else if (!r$auto_proposable) {
|
||||
sprintf("HUMAN TRIAGE (classified: %s; novel source diff)", r$signature)
|
||||
} else {
|
||||
sprintf("AUTO-PROPOSABLE (%s, %s confidence)", sig$id, sig$confidence)
|
||||
sprintf("AUTO-PROPOSABLE (%s, %s confidence)", r$signature, r$confidence)
|
||||
}
|
||||
|
||||
cat(sprintf(
|
||||
"\n[%d builds | %d pkgs | %s] %s\n",
|
||||
n,
|
||||
length(pkgs),
|
||||
toString(plats),
|
||||
r$build_count,
|
||||
length(r$packages),
|
||||
toString(r$platforms),
|
||||
status
|
||||
))
|
||||
cat(sprintf(
|
||||
" fingerprint: %s%s\n",
|
||||
rep_fp,
|
||||
if (fp_variants > 1L) {
|
||||
sprintf(" (+%d fingerprint variant(s))", fp_variants - 1L)
|
||||
r$fingerprint,
|
||||
if (r$fingerprint_variants > 1L) {
|
||||
sprintf(" (+%d fingerprint variant(s))", r$fingerprint_variants - 1L)
|
||||
} else {
|
||||
""
|
||||
}
|
||||
))
|
||||
if (sig$matched) {
|
||||
cat(sprintf(" signature : %s\n", sig$label))
|
||||
cat(sprintf(" suggested : [%s] %s\n", sig$tier, sig$fix))
|
||||
if (r$matched) {
|
||||
cat(sprintf(" signature : %s\n", r$label))
|
||||
cat(sprintf(" suggested : [%s] %s\n", r$tier, r$suggested_fix))
|
||||
}
|
||||
cat(sprintf(" arch : %s\n", toString(arches)))
|
||||
cat(sprintf(" arch : %s\n", toString(r$arches)))
|
||||
cat(sprintf(
|
||||
" packages : %s\n",
|
||||
paste(
|
||||
vapply(
|
||||
pkgs,
|
||||
r$packages,
|
||||
function(p) {
|
||||
if (p %in% registered_pkgs) paste0(p, " (has entry)") else p
|
||||
},
|
||||
|
|
@ -229,39 +183,20 @@ for (g in groups) {
|
|||
)
|
||||
))
|
||||
|
||||
proposals <- list()
|
||||
if (sig$matched && sig$auto && length(unregistered) > 0L) {
|
||||
if (!is.null(r$proposed_entries)) {
|
||||
cat(" proposed registry entries (validate + trial-build before merge):\n")
|
||||
for (p in unregistered) {
|
||||
p_plats <- sort(unique(g$platform[g$name == p]))
|
||||
entry <- propose_registry_entry(sig, p, p_plats)
|
||||
proposals[[p]] <- entry
|
||||
cat(paste0(" ", gsub("\n", "\n ", entry)), "\n", sep = "")
|
||||
for (entry in r$proposed_entries) {
|
||||
j <- jsonlite::toJSON(
|
||||
entry,
|
||||
auto_unbox = TRUE,
|
||||
pretty = TRUE,
|
||||
null = "null"
|
||||
)
|
||||
cat(paste0(" ", gsub("\n", "\n ", j)), "\n", sep = "")
|
||||
}
|
||||
} else if (sig$matched && sig$auto && length(unregistered) == 0L) {
|
||||
} else if (r$auto_proposable) {
|
||||
cat(" (all affected packages already have a registry entry)\n")
|
||||
}
|
||||
|
||||
report[[length(report) + 1L]] <- list(
|
||||
fingerprint = rep_fp,
|
||||
fingerprint_variants = fp_variants,
|
||||
build_count = n,
|
||||
packages = pkgs,
|
||||
packages_without_entry = unregistered,
|
||||
platforms = plats,
|
||||
arches = arches,
|
||||
signature = sig$id,
|
||||
matched = sig$matched,
|
||||
auto_proposable = isTRUE(sig$auto) && sig$matched,
|
||||
tier = sig$tier,
|
||||
confidence = sig$confidence,
|
||||
suggested_fix = sig$fix,
|
||||
proposed_entries = if (length(proposals) > 0L) {
|
||||
lapply(proposals, jsonlite::fromJSON)
|
||||
} else {
|
||||
NULL
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
|
|||
Loading…
Reference in a new issue