AUR-577: scanners are registered engines
Measured before (base 85209c53, audit of 2026-10-03)
- Adding a scanner touched 12-14 files and 5 lists: the const/switch of
gate.sources(config.GateConfig.ValidateSources), the fixed pointerQualityGatesConfig.Sast, thequality_gates.sastblock ofApplyCentralPolicy,gate.OriginSAST, the dedicatedgate.SASTContributor{SectionOrigin, Issues, Reason}, theSAST*fields ofgatePipelineInputs,runSASTPass/realSemgrepRunnerincmd/aurumcode/sast_pass.go, the session'srunSAST,RankReason'sSASTReason. SastConfig.Enginewas decorative:EngineName()returned "semgrep" and the runner calledanalysis.NewRunner().Semgrepwithout reading it.- The SARIF rebuilt each finding's origin by a
rule|path|linejoin withBlockingFindingsand dropped every blocking finding no review issue carried: the Dependency-Track breach (ssor_dtrack) was in the audit and missing from the SARIF. fmt.Sprintf("%s (rule %s)", f.Message, f.RuleID)was written in three places (gate/sources.go, cmd sast_pass.go, cmd passes.go).- The Semgrep execution lived in
internal/analysis/semgrep.go, beside the embedded regex catalog and go vet.
What changed
internal/scanner(new):Scanner{Name(); Run(ctx, Request) (Report, error)},Report{Findings, Complete, Version},Finding.ToIssue(origin)(the one conversion;analysis.Findingis now an alias ofscanner.Finding),Request{Root, Trust, Options, Command}, the registry (Register,Lookup,Names,NamesIn,Categories,KnownSource) ofEngine{Scanner, Category, Origin, Validate}, and theExecutorthat runs an engine with a timeout and turns an error, a missing binary orComplete=falseinto the inconclusive reason<category or name>_{unavailable,execution_error,invalid_output,incomplete}(FailureReason), never into zero findings.internal/scanner/semgrep: the Semgrep engine (code moved frominternal/analysis/semgrep.gowith its tests, plus the PATH runner from cmd). Categorysast, originsast, optionrule_packs. The policy flags--disable-nosemand--x-ignore-semgrepignore-filesare kept underTrust: TrustPolicy.internal/scanner/enginesis the closed list of compiled engines (one blank import each);internal/configimports it, so every binary that parses a configuration holds the engines.internal/config:quality_gates.scanners: [{engine, enabled, required, fail_on, options}](scanners.go);quality_gates.sastis read as thesemgrepentry (SastConfig.AsScanner, alwaysrequired), and itsenginemust be a registered engine of categorysast.Parserefuses an unknown engine (quality_gates.scanners[<engine>].engine: unknown engine ... (registered: semgrep)), an engine declared twice (sast alias included), a badfail_onand options the engine refuses.mergeScanners(scanners_policy.go) replaces thequality_gates.sastblock ofApplyCentralPolicy: engine by engine, a required policy entry wins and the repository's entry (includingenabled: false) becomes the warning<key> do config do repositório foi ignorado: a política central decide sozinha(the same text as before forquality_gates.sast); a policy entry that is not required yields to the repository with its own named warning.FromPolicy(engine)records which effective entries are the policy's.gate.sourcesandgate.triagevalidate against skills, analysis and the registry (engine names and categories);sastremains the category of every SAST engine, and the error text keeps listingskills, analysis, sast.internal/gate:sast.gobecamescanner.gowithScan(entry, engine, section, issues, reason) andApplyScannerGate;ScannerContributor{Scans}replacesSASTContributor(pipeline namescanners);RankReasonreadsScannerReason.SASTIssues,FoldSourcesandOriginSASTare gone.cmd/aurumcode:scanner_pass.goreplacessast_pass.go:runScannersruns every enabled entry throughreviewDeps.scanners(ascanner.Executor; the injected runner of the session is now the executor),joinScannersstates each inconclusive scan. The gate phase passes the scans to the one contributor, the triage maps each scan by the engine'sAnswers. Nothing in cmd or gate names an engine.internal/render/gatefindings.go:GateSARIFFindings.
One gate finding for the audit and the SARIF (AC-005)
Result.BlockingFindings is the one record of what counted: the audit's
blocking_findings is written from it, and the SARIF now reads every origin
from it too. The SARIF still lists the review's issues (they carry the
message, the code context for the fingerprint, the assessment and the
suppression, which the gate finding does not); the match by rule, path and
line only attaches the gate finding's origin to that issue. What changed is
that a blocking finding no issue carries is no longer dropped: it becomes its
own SARIF result with its origin (ssor_dtrack, origin dtrack, message
ssor_dtrack (origem dtrack)).
Observable output
The Semgrep engine keeps the origin sast: the gate line (origem sast,
secao <policy|repo>), the inconclusive line (SAST (semgrep, origem sast,
secao repo) inconclusivo (sast_unavailable)), the limitation (SAST
(Semgrep) inconclusivo: ...), the audit and the SARIF are byte-identical
for every existing configuration, so AUR-497's block and the gate and sast
tutorials did not have to change. A new engine's origin is its name.
Divergence recorded
AC-002 says exit 2 for an unknown engine. The product exits 1 on every
configuration load error, as AUR-575 already recorded; this card keeps that
code and TestAUR577UnknownEngineStopsTheReview asserts exactly 1 with the
engine named on stderr and no report printed.
Re-anchored done acceptances (card v3 paths)
| Script | Was | Now |
|---|---|---|
| AUR-548 | sources cmd/aurumcode/sast_pass.go, internal/analysis/semgrep.go; MUT marker in internal/analysis/semgrep.go, replacement return []Finding{}, nil |
sources cmd/aurumcode/scanner_pass.go, internal/scanner/semgrep/{semgrep,engine}.go; same marker in internal/scanner/semgrep/semgrep.go, replacement return []scanner.Finding{}, nil |
| AUR-567 | internal/gate/sast.go, name, OriginSAST+", secao "+origin) -> name, origin) |
internal/gate/scanner.go, name, origin+", secao "+s.Section) -> name, s.Section) |
| AUR-575 MUT-003 | internal/gate/sast.go |
internal/gate/scanner.go (same line) |
How each AC/MUT is proven
bash tests/acceptance/AUR-577.sh all in the shared container:
AUR-577/AC-001/pass
AUR-577/AC-002/pass
AUR-577/AC-002/sast-demo/pass
AUR-577/AC-003/pass
AUR-577/AC-004/pass
AUR-577/AC-005/pass
AUR-577/MUT-001/rejected
AUR-577/MUT-002/rejected
AUR-577/all/pass
- AC-001:
TestAUR577FakeEngineReachesEverySinkByOriginregistersfakescanonly in the test binary, declares it inquality_gates.scannersand runs the realreview --base: the gate line endsorigem fakescan, secao repo, the audit'sblocking_findingsand the SARIF'sproperties.originsayfakescan.TestAUR577NoEngineSwitchInGateOrCmdparses (go/ast) every production file ofinternal/gateandcmd/aurumcode: nocase,==/!=or call argument is a registered engine name or category literal, and neither imports an engine package. - AC-002:
TestAUR577UnknownEngineRefusedAndSastIsAlias(Parse error namingquality_gates.scanners[inexistente].engine;quality_gates.sastis the semgrep entry; the same engine twice is refused;sast/semgrepare gate sources),TestAUR577UnknownEngineStopsTheReview(exit 1, no report), the legacyTestAUR548SeverityBreachFailsGate/CleanScanPasses, anddemo/tutoriais/sast/run.sh --checkafter re-recording: the sastexpected/did not change. - AC-003:
TestAUR577PolicyRequiredScannerSurvivesRepositoryDisable: the policy'srequired: trueentry runs once under sectionpolicyalthough the repository wroteenabled: false(with and without its ownrequired: false), the override is the named warning; a policy entry that is not required yields, with its own warning. MUT-002 (if declared && p.Required && false) turns it RED by assertion. - AC-004:
TestAUR577MissingBinaryAndIncompleteAreInconclusive: an engine whose binary is absent isghostscan_unavailable, one whose report isComplete: falseishalfscan_incomplete; no issues, the gate is inconclusive and fails (block by default). PlusTestAUR548AbsentSemgrepIsInconclusiveNeverClean. - AC-005:
TestAUR577AuditAndSARIFShareTheGateFindings:ssor_dtrack(origindtrack) and a semgrep finding appear in the audit and in the SARIF with the same origin. - MUT-001: the pass ignores
engineand always looks up semgrep:TestAUR577FakeEngineReachesEverySinkByOriginfails (exit 1, no fakescan finding) and the AST test fails on the literal.
Regression (shared container, in series)
go vet ./... && go test -p 1 ./...: 36 packages ok, no FAIL.
AUR-548.sh all -> AUR-548/all/pass (re-anchored mutation RED);
AUR-567.sh all -> AUR-567/all/pass (re-anchored mutation RED);
AUR-575.sh all -> AUR-575/all/pass (MUT-003/red: --- FAIL:
TestAUR575UnknownSeverityCountsAsError); AUR-556, AUR-569, AUR-570,
AUR-576, AUR-579, AUR-497 all pass; AUR-561..564 all rc 0 (last
line AC-002-MUT-001/ok). bash scripts/docs/build.sh (strict) built with
no warning.
Tutorials: image aurum-tutoriais:aur577 built from this tree, the 15
tutorials re-run in series (run.sh all, then --check): all=0 check=0
for every one. No expected/ file and no docs/tutorials block changed (no
tutorial writes a SARIF with a Dependency-Track breach); out/ (with
.imagem) is committed.