Repository navigation
fix(security): deduplicate imports using authorized tracker body index #1750
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -35,6 +35,7 @@ | |
| from collections.abc import Iterable, Mapping, Sequence | ||
| from typing import Any | ||
|
|
||
| from security_management.shared import fields, threads | ||
| from security_management.shell import w | ||
|
|
||
| BATCH = 6 | ||
|
|
@@ -106,3 +107,109 @@ def attribute(thread_ids: Iterable[object], hits: Sequence[Mapping[str, Any]]) - | |
| "rerun": batches(unmatched, half) if truncated and unmatched else [], | ||
| "rejected_ids": bad, | ||
| } | ||
|
|
||
|
|
||
| def attribute_index( | ||
| thread_ids: Iterable[object], | ||
| issues: Sequence[Mapping[str, Any]], | ||
| *, | ||
| body_fields: Mapping[str, str] | None = None, | ||
| limit: int = 1000, | ||
| ) -> dict[str, Any]: | ||
| """Match IDs against a bounded, complete issue-body index. | ||
|
|
||
| Never expose body contents in the result. Missing or ambiguous | ||
| authoritative fields prevent a definitive negative result when | ||
| they cannot be interpreted safely. | ||
| """ | ||
| good, bad = valid_ids(thread_ids) | ||
| if bad: | ||
| return { | ||
| "ok": False, | ||
| "refused": "thread ids no mail-source backend emits", | ||
| "rejected_ids": bad, | ||
| } | ||
|
|
||
| tracked: dict[str, list[dict[str, Any]]] = {} | ||
| undecided: list[dict[str, Any]] = [] | ||
|
|
||
| for issue in issues: | ||
| summary = {key: issue.get(key) for key in ("number", "title", "state", "url")} | ||
| if not isinstance(issue.get("body"), str): | ||
| undecided.append( | ||
| { | ||
| "number": issue.get("number"), | ||
| "reason": "missing or invalid issue body", | ||
| } | ||
| ) | ||
| continue | ||
|
|
||
| parsed = fields.read( | ||
| issue["body"], | ||
| ["mailing_thread"], | ||
| body_fields, | ||
| ) | ||
|
|
||
| if parsed["undecided"]: | ||
| undecided.append( | ||
| { | ||
| "number": issue.get("number"), | ||
| "reason": "ambiguous mailing_thread field", | ||
| } | ||
| ) | ||
| continue | ||
|
|
||
| field_value = parsed["values"].get("mailing_thread", "") | ||
| if not field_value: | ||
| continue | ||
|
|
||
| identity = threads.classify(field_value) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| if identity["needs_decision"]: | ||
| undecided.append( | ||
| { | ||
| "number": issue.get("number"), | ||
| "reason": "mailing_thread needs a decision", | ||
| } | ||
| ) | ||
| continue | ||
|
|
||
| known = { | ||
| value | ||
| for value in ( | ||
| identity["thread_id"], | ||
| identity["root_message_id"], | ||
| ) | ||
| if value | ||
| } | ||
|
|
||
| # Archive-only entries carry the PonyMail identifier in the | ||
| # /thread/<id> path, rather than in a Gmail thread note. | ||
| archive_urls = [identity["archive_url"]] | ||
| for secondary in identity["secondary_threads"]: | ||
| if secondary.get("thread_id"): | ||
| known.add(secondary["thread_id"]) | ||
| archive_urls.append(secondary.get("url")) | ||
|
|
||
| for url in archive_urls: | ||
| if not isinstance(url, str): | ||
| continue | ||
| match = re.search(r"/thread/([A-Za-z0-9_.,:=+-]{6,128})(?:[/?#]|$)", url) | ||
| if match: | ||
| known.add(match.group(1)) | ||
|
|
||
| for candidate in good: | ||
| if candidate in known: | ||
| tracked.setdefault(candidate, []).append(summary) | ||
|
|
||
| truncated = len(issues) >= limit | ||
| complete = not truncated and not undecided | ||
|
|
||
| return { | ||
| "ok": True, | ||
| "tracked": tracked, | ||
| "untracked": [candidate for candidate in good if candidate not in tracked] if complete else [], | ||
| "complete": complete, | ||
| "truncated": truncated, | ||
| "undecided": undecided, | ||
| "rejected_ids": [], | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please spell this as
uv run --project ~/.claude/magpie/vetted-ops vetted-op-read --caller security-issue-import …, as the other runnable blocks do (AGENTS.md → Local setup, golden rule): a barevetted-op-readis not on PATH and is not what the sandbox exclusion matches. Also,<saved-index-path>below is never defined — it is<workspace>/saved/security-import-index.json.