fix(research): fix AttributeError in suggest_objectives_from_chains#176
Merged
PurpleCHOIms merged 1 commit intoMay 9, 2026
Conversation
ChainStep stores flat string fields (node_kind, node_label, etc.) with no .node object and no .props dict. Chain similarly stores flat strings crown_jewel_label and entrypoint_label rather than objects with a .label attribute. The function was crashing with AttributeError any time plan_chains() returned at least one chain with steps. - Replace step.node.kind → step.node_kind - Replace step.node.props.get(...) calls — mitre/severity are not carried by ChainStep; remove that enrichment and default mitre to [] and opsec to "standard" - Replace chain.crown_jewel.label → chain.crown_jewel_label - Replace chain.entrypoint.label → chain.entrypoint_label Fixes PurpleAILAB#165
PurpleCHOIms
added a commit
that referenced
this pull request
May 12, 2026
Merge upstream main (48 commits behind) into the long-running benchmark branch so the OCI loop runs against the current Decepticon code, not a stale fork. Resolves a single conflict in ``decepticon/middleware/opplan.py`` introduced by upstream PR #184 (``Refactor middleware tools and harden OPPLAN persistence``), which moved the OPPLAN ``@tool`` definitions out of the middleware module and into the new ``decepticon/tools/opplan.py``. Resolution - Drop the duplicate ``@tool`` definitions on the benchmark side (~970 lines) — they now live behind ``build_opplan_tools(backend)`` in ``decepticon/tools/opplan.py``. - Preserve the benchmark-only recon-first guard in ``OPPLANMiddleware.after_model`` (added by 13ff3b3 / be918cf / 08a98eb / d211c4e) — intercepts ``task('exploit'|'postexploit', ...)`` dispatches when neither an OPPLAN recon objective nor on-disk evidence (``recon/SUMMARY.md`` or ``findings/FIND-*.md``) is present. - Re-add the ``from pathlib import Path`` import that the auto-merge dropped (now needed only by the surviving guard, since the file- writing tools moved out). Verification - ``uv run ruff check decepticon/middleware/opplan.py`` — clean - ``uv run ruff format --check decepticon/middleware/opplan.py`` — clean - ``uv run pytest tests/unit/middleware/test_opplan_hierarchy.py tests/unit/middleware/test_opplan_persistence.py`` — 34 passed - File shrinks from 1451 → 483 lines, diff vs origin/main is exactly the import line and the recon-first guard block. Other upstream changes (LiteLLM OAuth refactor #187, workspace_path reducer #183, launcher slug #182, research fix #176, AD index fix #177, LLM kwargs typing #179) merged automatically without conflict.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #165
suggest_objectives_from_chainscrashes withAttributeErrorany timeplan_chains()returns chains with steps, because the function accessed members that don't exist on theChainStepandChaindataclasses:step.node.props.get("mitre")ChainStep— no.nodeattributestep.node.props.get("severity")ChainStepstep.node.kindstep.node_kind(flatstr)chain.crown_jewel.labelchain.crown_jewel_label(flatstr)chain.entrypoint.labelchain.entrypoint_label(flatstr)ChainStepstores flat string fields (node_id,node_label,node_kind,edge_kind,hop_cost).Chainstorescrown_jewel_labelandentrypoint_labelas flat strings — not objects with a.labelattribute.Changes
ChainStep;mitredefaults to[]andopsecto"standard"(these can be enriched in a follow-up ifChainis extended to carry fullNode.props)Test plan
uv run ruff check decepticon/tools/research/tools.py— passesuv run basedpyright decepticon/tools/research/tools.py— 0 errorsuv run pytest tests/unit/research/test_tools.py— 10/10 passed