Add recovered attribution evidence
ober
781536d553917ef2b477fb3f40ea3ccefb2d2c70
--- a/GAPS.md +++ b/GAPS.md @@ -331,6 +331,12 @@ Acceptance criteria: - Support known agent identity catalog from config. - Never label recovered attribution as recorded provenance. +Status: implemented with `recovered_attribution` entries derived from known +agent identity matches in author identity, commit messages, and refs/notes/ai +metadata. Each item carries source, agent, confidence, evidence, and +`recorded_provenance:false`; fixtures assert configured identities recover +without populating `recorded_attribution`. + ### G-033: Provider/tool catalog is hard-coded Known AI tools are detected through hard-coded strings. --- a/main-binary.ss +++ b/main-binary.ss @@ -27,7 +27,7 @@ (defstruct signal (name category score weight confidence reason evidence limitations)) (defstruct finding (commit parent author-name author-email time subject files additions deletions - added-lines note attribution metadata signals score verdict warnings)) + added-lines note attribution recovered-attribution metadata signals score verdict warnings)) (def raw-command-line (command-line)) (def cli-args @@ -480,6 +480,35 @@ (let* ([text (down (string-join (list author-name author-email subject body note) "\n"))] [tokens (source-lexemes (list text))]) (filter (lambda (marker) (identity-token-hit? tokens marker)) current-known-agents))) +(def (identity-hits-in text) + (let ([tokens (source-lexemes (list (down text)))]) + (filter (lambda (marker) (identity-token-hit? tokens marker)) current-known-agents))) + +(def (recovered-attribution-entry source agent confidence evidence) + (list (cons 'source source) + (cons 'agent agent) + (cons 'confidence confidence) + (cons 'recorded_provenance #f) + (cons 'evidence evidence))) + +(def (recovered-attributions-for-source source text confidence evidence) + (map (lambda (agent) + (recovered-attribution-entry source agent confidence evidence)) + (identity-hits-in text))) + +(def (recovered-attributions author-name author-email subject body note) + (append (recovered-attributions-for-source "author_identity" + (string-join (list author-name author-email) "\n") + 0.82 + "known agent identity matched author name or email") + (recovered-attributions-for-source "commit_message" + (string-join (list subject body) "\n") + 0.58 + "known agent identity matched commit subject or body") + (recovered-attributions-for-source "recorded_note_metadata" + note + 0.66 + "known agent identity matched refs/notes/ai metadata"))) (def (sig name category score weight confidence reason evidence limitations) (make-signal name category (clamp01 score) weight confidence reason evidence limitations)) @@ -1258,6 +1287,7 @@ [note (bounded-string raw-note (scan-config-max-note-bytes current-config))] [note-obj (parse-note-object note)] [attribution (note-attributions note-obj)] + [recovered (recovered-attributions author-name author-email subject body note)] [metadata (metadata-hits author-name author-email subject body note)] [eligible? (and (pair? files) (pair? lines) (or (= min-lines 0) (>= (length lines) min-lines)))] [current-baseline-features (baseline-features adds lines)] @@ -1283,7 +1313,7 @@ [v (cond [insufficient? "insufficient-evidence"] [heuristics-only? (verdict score '() "")] [else (verdict score metadata note)])]) - (list (make-finding id parent author-name author-email time subject paths adds dels (length lines) note attribution metadata signals score v + (list (make-finding id parent author-name author-email time subject paths adds dels (length lines) note attribution recovered metadata signals score v (append (warnings files lines note note-obj metadata-only? heuristics-only? min-lines file-count line-count note-count parents shallow? binary-count rename-copy-count missing-parent?) (cadr embedding-pair) (cadr provider-pair))) @@ -1363,6 +1393,7 @@ (cons 'recorded_ai_note_present (not (string-empty? (finding-note f)))) (cons 'recorded_ai_note_excerpt (if (string-empty? (finding-note f)) "" (substring (finding-note f) 0 (min 400 (string-length (finding-note f)))))) (cons 'recorded_attribution (finding-attribution f)) + (cons 'recovered_attribution (finding-recovered-attribution f)) (cons 'file_findings (map (lambda (path) (file-finding-json f path)) (finding-files f))) (cons 'unmatched_recorded_attribution (unmatched-attribution (finding-attribution f) (finding-files f))) (cons 'metadata_hits (finding-metadata f)) (cons 'signals (map signal-json (finding-signals f))) @@ -1402,12 +1433,22 @@ (def (zero-score-signals signals) (filter (lambda (s) (= (signal-score s) 0.0)) signals)) +(def (display-recovered-attributions items) + (if (pair? items) + (begin + (displayln "recovered attribution:") + (for ([item items]) + (displayln (str "- agent=" (alist-ref/default item 'agent "") + " source=" (alist-ref/default item 'source "") + " confidence=" (alist-ref/default item 'confidence 0.0) + " recorded_provenance=false")))))) (def (display-explain f) (displayln (str "commit: " (finding-commit f))) (displayln (str "verdict: " (finding-verdict f))) (displayln (str "score: " (finding-score f))) (displayln (str "author: " (finding-author-name f) " <" (finding-author-email f) ">")) (displayln (str "subject: " (finding-subject f))) + (display-recovered-attributions (finding-recovered-attribution f)) (displayln (str "thresholds: human=" (scan-config-human-threshold current-config) " ai=" (scan-config-ai-threshold current-config) " result=" (threshold-description (finding-score f)))) --- a/tests/fixture-smoke.sh +++ b/tests/fixture-smoke.sh @@ -178,6 +178,8 @@ identity_config="$identity_fixture/aigit.json" printf '{"known_agents":["mybot"]}\n' > "$identity_config" identity_custom_json=$("$root/bin/jerboa-aigit" scan "$identity_fixture" --config "$identity_config" --format json --count 1) printf '%s\n' "$identity_custom_json" | grep -q '"metadata_hits":\["mybot"\]' +printf '%s\n' "$identity_custom_json" | grep -q '"recorded_attribution":\[\]' +printf '%s\n' "$identity_custom_json" | grep -q '"recovered_attribution":\[{"source":"author_identity","agent":"mybot","confidence":0.82,"recorded_provenance":false' git -C "$shape_fixture" init -q git -C "$shape_fixture" config user.name "Human Dev"