Keep item revisions and edge decisions as records, and refuse to treat a missing index as no impact. Co-authored-by: Cursor <cursoragent@cursor.com>
115 lines
3.3 KiB
Go
115 lines
3.3 KiB
Go
package traceimpact
|
|
|
|
import "fmt"
|
|
|
|
// Actor is the person trying to confirm or reject a candidate edge.
|
|
type Actor struct {
|
|
UserID int64
|
|
Creator bool
|
|
AuthorOf map[string]bool
|
|
}
|
|
|
|
// CanDecide reports whether actor may confirm or reject a model candidate
|
|
// on the downstream document. There is no separate trace_reviewer role.
|
|
func CanDecide(actor Actor, downstreamDocID string, downstreamAuthorID int64) error {
|
|
if downstreamDocID == "" {
|
|
return fmt.Errorf("traceimpact: missing downstream document")
|
|
}
|
|
if downstreamAuthorID > 0 {
|
|
if actor.UserID == downstreamAuthorID {
|
|
return nil
|
|
}
|
|
return fmt.Errorf("traceimpact: only the downstream author may decide this candidate")
|
|
}
|
|
if actor.Creator {
|
|
return nil
|
|
}
|
|
return fmt.Errorf("traceimpact: no downstream author; only the project creator may decide")
|
|
}
|
|
|
|
// DutySeat is one document's author, reviewer, and approver.
|
|
type DutySeat struct {
|
|
TaskKey string
|
|
AuthorID int64
|
|
ReviewerID int64
|
|
ApproverID int64
|
|
}
|
|
|
|
// MayRequestReopen allows the project creator or a duty holder on a selected document.
|
|
func MayRequestReopen(userID int64, creator bool, selected []string, seats []DutySeat) bool {
|
|
if creator {
|
|
return true
|
|
}
|
|
wanted := map[string]bool{}
|
|
for _, key := range selected {
|
|
wanted[key] = true
|
|
}
|
|
for _, seat := range seats {
|
|
if !wanted[seat.TaskKey] {
|
|
continue
|
|
}
|
|
if userID > 0 && (userID == seat.AuthorID || userID == seat.ReviewerID || userID == seat.ApproverID) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Suggest returns the three allowed next steps. They do not change state by themselves.
|
|
func Suggest(coverage string, hasImpact bool) []Suggestion {
|
|
switch coverage {
|
|
case ImpactFailed, ImpactIndexPending, ImpactPartial:
|
|
return []Suggestion{{
|
|
Action: "wait_index",
|
|
Reason: "索引未完成或分析失败,不能判断为无影响",
|
|
}}
|
|
}
|
|
out := []Suggestion{{
|
|
Action: ClearNoChange,
|
|
Reason: "确认这次修改不需要改其他文档",
|
|
}}
|
|
if hasImpact || coverage == ImpactPossible {
|
|
out = append(out,
|
|
Suggestion{Action: "l1_revise", Reason: "只修订受影响文档,阶段保持完成"},
|
|
Suggestion{Action: "l2_reopen", Reason: "需要重跑编制或补充文档时再打开阶段"},
|
|
)
|
|
}
|
|
return out
|
|
}
|
|
|
|
// SummaryForCoverage is the sentence shown when there is no confirmed hit.
|
|
func SummaryForCoverage(coverage string) string {
|
|
switch coverage {
|
|
case ImpactNoKnown:
|
|
return "索引已覆盖,未发现已知关联"
|
|
case ImpactIndexPending:
|
|
return "分析尚未完成,不能判断为无影响"
|
|
case ImpactPartial:
|
|
return "结果不完整,不能判断为无影响"
|
|
case ImpactFailed:
|
|
return "分析失败,不能判断为无影响"
|
|
case ImpactPossible:
|
|
return "存在未确认的可能影响"
|
|
case ImpactDependencyUnchanged:
|
|
return "已有关联,相关片段未变化"
|
|
default:
|
|
return "存在已确认影响"
|
|
}
|
|
}
|
|
|
|
// RedactItems drops statements from documents the viewer cannot see.
|
|
// Hidden items are counted and not returned, so titles cannot leak.
|
|
func RedactItems(items []Item, visible func(docID string) bool) (out []Item, hidden int) {
|
|
for _, item := range items {
|
|
if item.SourceDocID != "" && (visible == nil || !visible(item.SourceDocID)) {
|
|
hidden++
|
|
continue
|
|
}
|
|
out = append(out, item)
|
|
}
|
|
if out == nil {
|
|
out = []Item{}
|
|
}
|
|
return out, hidden
|
|
}
|