[{"data":1,"prerenderedAt":417},["ShallowReactive",2],{"-docs-guide-campaigns":3,"-docs-guide-campaigns-surround":406},{"id":4,"title":5,"body":6,"description":396,"extension":397,"links":398,"meta":399,"navigation":400,"path":402,"seo":403,"stem":404,"__hash__":405},"docs\u002Fdocs\u002F2.guide\u002F3.campaigns.md","Campaigns",{"type":7,"value":8,"toc":385},"minimark",[9,31,36,101,105,110,123,127,156,175,186,190,200,232,239,243,250,291,302,306,313,356,359,363,381],[10,11,12,13,17,18,22,23,26,27,30],"p",{},"A ",[14,15,16],"strong",{},"campaign"," is one audit run against a specific git state: a branch, a commit, and a remote. Campaigns are created by a developer's ",[19,20,21],"code",{},"run",", by a forge webhook (PR opened\u002Fsynchronized), or as a ",[14,24,25],{},"retest"," of existing findings. Each campaign pins a ",[19,28,29],{},"config_version"," so in-flight work renders from the methodology it started with.",[32,33,35],"h2",{"id":34},"choose-a-campaign-type","Choose a campaign type",[37,38,39,55],"table",{},[40,41,42],"thead",{},[43,44,45,49,52],"tr",{},[46,47,48],"th",{},"Type",[46,50,51],{},"When to use",[46,53,54],{},"Scope",[56,57,58,72,89],"tbody",{},[43,59,60,66,69],{},[61,62,63],"td",{},[19,64,65],{},"full",[61,67,68],{},"first scan of a repo, or a periodic re-baseline",[61,70,71],{},"every bucket × every pass × every model",[43,73,74,79,82],{},[61,75,76],{},[19,77,78],{},"incremental",[61,80,81],{},"a PR \u002F branch — only what changed",[61,83,84,85,88],{},"buckets\u002Fpasses whose scope intersects ",[19,86,87],{},"changed_paths"," + risk-trigger files",[43,90,91,95,98],{},[61,92,93],{},[19,94,25],{},[61,96,97],{},"verifying a fix landed",[61,99,100],{},"a single finding's bucket + the passes whose scope matches its files",[32,102,104],{"id":103},"create-a-campaign","Create a campaign",[106,107,109],"h3",{"id":108},"from-the-agent-full","From the agent (full)",[10,111,112,115,116,119,120,122],{},[19,113,114],{},"thespider-agent run"," without ",[19,117,118],{},"--campaign"," creates a ",[19,121,65],{}," campaign from local git detect — the client detects branch\u002Fcommit\u002Fremote_url\u002Fdirty and the server records it.",[106,124,126],{"id":125},"from-the-api-any-type","From the API (any type)",[128,129,134],"pre",{"className":130,"code":131,"language":132,"meta":133,"style":133},"language-http shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","POST \u002Fv1\u002Fprojects\u002F{id}\u002Fcampaigns\n     { type, git:{branch,commit,remote_url,dirty}, base_commit?, changed_paths?, config_version? }\n     → { campaign_id, config_version_id, status, reused }\n","http","",[19,135,136,144,150],{"__ignoreMap":133},[137,138,141],"span",{"class":139,"line":140},"line",1,[137,142,143],{},"POST \u002Fv1\u002Fprojects\u002F{id}\u002Fcampaigns\n",[137,145,147],{"class":139,"line":146},2,[137,148,149],{},"     { type, git:{branch,commit,remote_url,dirty}, base_commit?, changed_paths?, config_version? }\n",[137,151,153],{"class":139,"line":152},3,[137,154,155],{},"     → { campaign_id, config_version_id, status, reused }\n",[157,158,159,160,163,164,167,168,170,171,174],"note",{},"The server has ",[14,161,162],{},"no source tree",". For incremental scans the client ",[14,165,166],{},"must"," send ",[19,169,87],{}," (file ",[14,172,173],{},"paths",", not contents). The server intersects them with pass-scope globs + risk triggers — paths travel, contents never do.",[10,176,177,178,181,182,185],{},"Dedup identity is keyed by ",[19,179,180],{},"(project, branch, commit, type)",", so re-creating the same campaign ",[14,183,184],{},"reuses"," the existing one instead of duplicating work.",[32,187,189],{"id":188},"scope-an-incremental-campaign","Scope an incremental campaign",[10,191,192,193,196,197,199],{},"Incremental targeting is decided ",[14,194,195],{},"server-side"," in campaign planning, fed by client-supplied ",[19,198,87],{},". It ports two rules verbatim from the original engine:",[201,202,203,210],"ol",{},[204,205,206,209],"li",{},[14,207,208],{},"Pass-scope glob matching"," — a pass only runs on a changed file if the pass's scope globs match it.",[204,211,212,215,216,219,220,223,224,227,228,231],{},[14,213,214],{},"Global risk triggers"," — ",[19,217,218],{},"Dockerfile",", ",[19,221,222],{},".env*",", lockfiles, ",[19,225,226],{},"*.config.*",", and similar risk-pattern files expand scope to ",[14,229,230],{},"all"," passes, regardless of the pass's own scope.",[10,233,234,235,238],{},"This is why a one-line ",[19,236,237],{},".env"," change can fan out into a broad re-scan, while a docs-only change runs nothing.",[32,240,242],{"id":241},"understand-finding-identity-across-campaigns","Understand finding identity across campaigns",[10,244,245,246,249],{},"Fingerprint is the ",[14,247,248],{},"fast path",", not the identity. Ingest resolves identity in three steps:",[201,251,252,270,285],{},[204,253,254,257,258,261,262,265,266,269],{},[14,255,256],{},"Alias-table hit"," on ",[19,259,260],{},"(project_id, fingerprint)"," → ",[14,263,264],{},"KNOWN",": record a sighting, inherit current triage status (a won't-fix on ",[19,267,268],{},"main"," stays suppressed on a PR branch).",[204,271,272,275,276,280,281,284],{},[14,273,274],{},"Miss → candidate dedupe"," project-scoped, with bounded candidates and threshold gates. If adjudicated duplicate (heuristic or AI) → insert an alias row mapping this fingerprint to the canonical finding + record a sighting. This absorbs renames and wording drift, which ",[277,278,279],"em",{},"change"," the fingerprint. For entitled orgs, AI adjudication batches the finding's eligible candidate pairs into model requests of up to ",[19,282,283],{},"AI_DEDUPE_BATCH_SIZE"," pairs (defective entries are retried individually); each pair's decision is cached individually and reused forever.",[204,286,287,290],{},[14,288,289],{},"Still no match → NEW"," finding + its birth alias row.",[10,292,293,294,297,298,301],{},"Merges repoint alias rows to the keeper and set ",[19,295,296],{},"merged_into_finding_id"," (a ",[14,299,300],{},"soft merge — never DELETE","), so a merged-away fingerprint re-imported later lands on the keeper deterministically.",[32,303,305],{"id":304},"run-a-retest","Run a retest",[10,307,308,309,312],{},"Retest is a ",[14,310,311],{},"targeted verification campaign",", not a naive re-scan. It is scoped to the finding's bucket + the passes whose scope matches its files, gated on HEAD matching the expected fix commit.",[128,314,318],{"className":315,"code":316,"language":317,"meta":133,"style":133},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","# server-created retest campaign; the agent claims it like any other work\nthespider-agent run --campaign \u003Cretest-campaign-id> --once\n","bash",[19,319,320,326],{"__ignoreMap":133},[137,321,322],{"class":139,"line":140},[137,323,325],{"class":324},"sHwdD","# server-created retest campaign; the agent claims it like any other work\n",[137,327,328,332,336,339,343,346,350,353],{"class":139,"line":146},[137,329,331],{"class":330},"sBMFI","thespider-agent",[137,333,335],{"class":334},"sfazB"," run",[137,337,338],{"class":334}," --campaign",[137,340,342],{"class":341},"sMK4o"," \u003C",[137,344,345],{"class":334},"retest-campaign-i",[137,347,349],{"class":348},"sTEyZ","d",[137,351,352],{"class":341},">",[137,354,355],{"class":334}," --once\n",[10,357,358],{},"Known nuance: fixed\u002Fregressed is decided by reappearance. Near-matches route through the dedupe layer before concluding \"fixed\" (so wording drift doesn't masquerade as a fix), and for high-severity findings you may require N consecutive clean runs. Absence only counts as evidence when the retest scope actually covered the finding — which the bucket\u002Fpass targeting guarantees.",[32,360,362],{"id":361},"next-steps","Next steps",[364,365,366,374],"ul",{},[204,367,368,373],{},[369,370,372],"a",{"href":371},"\u002Fdocs\u002Fguide\u002Ftriage","Triage"," — work with findings in the web UI.",[204,375,376,380],{},[369,377,379],{"href":378},"\u002Fdocs\u002Fguide\u002Fpr-gate","PR gate"," — turn campaigns into merge gates.",[382,383,384],"style",{},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}",{"title":133,"searchDepth":146,"depth":146,"links":386},[387,388,392,393,394,395],{"id":34,"depth":146,"text":35},{"id":103,"depth":146,"text":104,"children":389},[390,391],{"id":108,"depth":152,"text":109},{"id":125,"depth":152,"text":126},{"id":188,"depth":146,"text":189},{"id":241,"depth":146,"text":242},{"id":304,"depth":146,"text":305},{"id":361,"depth":146,"text":362},"Create and run full, incremental, and retest campaigns in TheSpider","md",null,{},{"icon":401},"i-lucide-layers","\u002Fdocs\u002Fguide\u002Fcampaigns",{"title":5,"description":396},"docs\u002F2.guide\u002F3.campaigns","1u4wt11zL8K6ke2S6YNFzD-tB3MDRHp5VyJiFJKMr3I",[407,413],{"title":408,"path":409,"stem":410,"description":411,"icon":412,"children":-1},"The agent","\u002Fdocs\u002Fguide\u002Fagent","docs\u002F2.guide\u002F2.agent","Run the TheSpider Rust agent to claim slices, run models, and gate PRs","i-lucide-terminal",{"title":372,"path":371,"stem":414,"description":415,"icon":416,"children":-1},"docs\u002F2.guide\u002F4.triage","Triage findings as a team with project-level identity and branch attribution","i-lucide-list-checks",1783955420028]