// Package worklist turns discovered credentials into a rotation worklist: per // credential, whether it can be auto-rotated (a driver exists) or is manual, and // the page to rotate it on. It contains NO secrets — only non-secret metadata and // links — so a worklist is safe to write to disk or print. package worklist import ( "fmt" "strings" "incredigo/internal/discover" "incredigo/internal/links" "incredigo/internal/rotate" ) // Item is one credential's rotation entry. type Item struct { Index int Credential discover.Credential Driver string // "" when no rotation driver handles it (manual) Link links.Link } // Manual reports whether this credential has no automated rotation path today. func (it Item) Manual() bool { return it.Driver == "" } // Build classifies credentials (via the rotate registry) and attaches links. // With no drivers registered every Item is Manual. func Build(creds []discover.Credential) []Item { plans := rotate.PlanAll(creds) items := make([]Item, len(plans)) for i, p := range plans { items[i] = Item{ Index: i + 1, Credential: p.Credential, Driver: p.Driver, Link: links.LinkFor(p.Credential), } } return items } // Markdown renders a worklist as a Markdown document (no secrets). func Markdown(items []Item) string { var b strings.Builder b.WriteString("# incredigo rotation worklist\n\n") fmt.Fprintf(&b, "%d credential(s). No secrets are included in this file.\n\n", len(items)) b.WriteString("| # | source | identity | rotation | change-password link |\n") b.WriteString("|---|--------|----------|----------|----------------------|\n") for _, it := range items { rot := "manual" if !it.Manual() { rot = "auto: " + it.Driver } url := it.Link.URL if url == "" { url = "— (no web page)" } fmt.Fprintf(&b, "| %d | %s | %s | %s | %s |\n", it.Index, it.Credential.Source, it.Credential.Identity, rot, url) } b.WriteString("\nGenerated by incredigo. Links are derived offline ") b.WriteString("(curated table or RFC 8615 /.well-known/change-password).\n") return b.String() }