Thanks to visit codestin.com
Credit goes to github.com

Skip to content

fix: prevent operation report from returning null when link paw absent (#3048)#3279

Merged
deacon-mp merged 3 commits into
masterfrom
fix/issue-3048-operation-report-null
Mar 16, 2026
Merged

fix: prevent operation report from returning null when link paw absent (#3048)#3279
deacon-mp merged 3 commits into
masterfrom
fix/issue-3048-operation-report-null

Conversation

@deacon-mp
Copy link
Copy Markdown
Contributor

Summary

Fixes the "Null" download bug on operation reports (#3048). Three related KeyErrors in c_operation.py caused Operation.report() to silently return None, which web.json_response(None) serialised as JSON null and the UI displayed/downloaded as "Null".

Root causes:

  1. agents_steps[step.paw] KeyError in report()agents_steps is pre-populated only with paws from self.agents at call time. If an agent was removed between when the operation ran and when the report is downloaded, any link for that agent raises KeyError. Fixed with agents_steps.setdefault(step.paw, {'steps': []}).

  2. abilities_by_agent[link.paw] KeyError in _get_all_possible_abilities_by_agent() — same pattern: orphan paw not guarded. Fixed with an explicit if link.paw in abilities_by_agent check.

  3. Silent None return from except Exception — the broad except block logged the error but fell off the function end without returning anything. None propagated all the way to web.json_response(None) → JSON null → "Null" download. Fixed by adding raise so the framework returns a proper HTTP 500 with an error body.

Test plan

  • pytest tests/objects/test_operation.py -v --asyncio-mode=auto — all 27 tests pass, including the new regression test.
  • test_report_includes_steps_for_agents_not_in_host_group — constructs an operation with a chain link whose paw is not in operation.agents; asserts report() returns a non-None dict containing the orphan paw's steps.
  • pytest tests/api/v2/handlers/test_operations_api.py -v --asyncio-mode=auto — all 51 tests pass.

…absent (#3048)

Three related KeyErrors in c_operation.py could cause Operation.report()
to silently return None, which the API then serialised as JSON null and
the UI rendered as "Null":

1. `agents_steps[step.paw]` in report() raised KeyError when a link's
   paw was not in the set of operation agents built at call time (e.g.
   the agent was removed between operation run and report download).
   Fixed with `agents_steps.setdefault(step.paw, {'steps': []})`.

2. `abilities_by_agent[link.paw]` in _get_all_possible_abilities_by_agent()
   had the same pattern — orphan paw not guarded.  Fixed with an
   explicit membership check before the extend.

3. The `except Exception` block in report() logged the error but fell
   off the end of the function, returning None implicitly.  The caller
   then returned None to web.json_response(), producing the "Null"
   download.  Fixed by re-raising so the framework returns a proper 500
   with an error body instead of a silent null payload.

Adds a regression test that constructs an operation with a chain link
whose paw is not present in operation.agents and asserts report()
returns a non-None dict that includes the orphan paw's steps.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes operation report generation so downloading an operation report no longer returns JSON null (rendered as “Null”) when the operation chain contains links for agents (paws) that are no longer present in operation.agents at report-download time.

Changes:

  • Prevent KeyError in Operation.report() by safely initializing missing agents_steps entries for orphaned paws.
  • Prevent KeyError in _get_all_possible_abilities_by_agent() by guarding lookups when a link’s paw is not in the current abilities_by_agent map.
  • Add a regression test covering report generation with an orphaned paw in the chain.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
app/objects/c_operation.py Makes report generation resilient to orphaned paws and ensures exceptions don’t silently yield None.
tests/objects/test_operation.py Adds regression coverage to ensure report() returns a dict and includes orphan-paw steps.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

if step.agent_reported_time:
step_report['agent_reported_time'] = step.agent_reported_time.strftime(self.TIME_FORMAT)
agents_steps[step.paw]['steps'].append(step_report)
agents_steps.setdefault(step.paw, {'steps': []})['steps'].append(step_report)
Comment thread app/objects/c_operation.py Outdated
Comment on lines +473 to +476
if link.ability.ability_id not in self.adversary.atomic_ordering:
matching_abilities = await data_svc.locate('abilities', match=dict(ability_id=link.ability.ability_id))
abilities_by_agent[link.paw]['all_abilities'].extend(matching_abilities)
if link.paw in abilities_by_agent:
abilities_by_agent[link.paw]['all_abilities'].extend(matching_abilities)
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes operation report downloads returning JSON null when an operation contains links for agents (paws) that are no longer in operation.agents, and ensures exceptions in Operation.report() propagate properly instead of being swallowed.

Changes:

  • Prevent KeyError in Operation.report() by creating missing agents_steps entries for orphan paws.
  • Prevent KeyError in _get_all_possible_abilities_by_agent() by guarding missing paws.
  • Re-raise exceptions from Operation.report() so callers get an HTTP 500 instead of a null JSON response; add regression test for orphan paw reporting.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
tests/objects/test_operation.py Adds a regression test ensuring orphan paw links don’t cause report() to return None.
app/objects/c_operation.py Guards report generation and ability aggregation against missing agent paws; ensures exceptions propagate.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +346 to +347
logging.error('Error generating operation report (%s)' % self.name, exc_info=True)
raise
if step.agent_reported_time:
step_report['agent_reported_time'] = step.agent_reported_time.strftime(self.TIME_FORMAT)
agents_steps[step.paw]['steps'].append(step_report)
agents_steps.setdefault(step.paw, {'steps': []})['steps'].append(step_report)
Comment thread app/objects/c_operation.py Outdated
Comment on lines +475 to +476
if link.paw in abilities_by_agent:
abilities_by_agent[link.paw]['all_abilities'].extend(matching_abilities)
@deacon-mp deacon-mp requested a review from Copilot March 16, 2026 01:14
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes operation report generation so it no longer returns None (serialized as JSON null) when the operation contains chain links for paws not present in operation.agents, and adds a regression test for the scenario.

Changes:

  • Guarded report step aggregation to tolerate “orphan” paws when building report['steps'].
  • Prevented KeyError in ability aggregation by safely handling paws missing from abilities_by_agent.
  • Ensured report-generation exceptions don’t silently devolve into None by re-raising after logging; added regression test.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
tests/objects/test_operation.py Adds regression coverage for orphan-paw links to ensure report() returns a dict and includes steps for missing agents.
app/objects/c_operation.py Hardens report/ability aggregation against missing paws and stops swallowing unexpected exceptions during report generation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +629 to +633
async def test_report_includes_steps_for_agents_not_in_host_group(
self, operation_agent, operation_adversary, executor, ability, operation_link,
encoded_command, parse_datestring, file_svc, data_svc, knowledge_svc, fire_event_mock):
"""Regression test for issue #3048: a link whose paw is absent from operation.agents
must not cause report() to silently return None (i.e. download as 'Null')."""
if step.agent_reported_time:
step_report['agent_reported_time'] = step.agent_reported_time.strftime(self.TIME_FORMAT)
agents_steps[step.paw]['steps'].append(step_report)
agents_steps.setdefault(step.paw, {'steps': []})['steps'].append(step_report)
matching_abilities = await data_svc.locate('abilities', match=dict(ability_id=link.ability.ability_id))
abilities_by_agent[link.paw]['all_abilities'].extend(matching_abilities)
entry = abilities_by_agent.get(link.paw)
if entry:
@sonarqubecloud
Copy link
Copy Markdown

@deacon-mp deacon-mp merged commit b6ee9ca into master Mar 16, 2026
19 checks passed
@deacon-mp deacon-mp deleted the fix/issue-3048-operation-report-null branch March 16, 2026 01:35
fionamccrae pushed a commit that referenced this pull request Mar 16, 2026
#3048) (#3279)

* fix: prevent operation report from returning null when a link paw is absent (#3048)

Three related KeyErrors in c_operation.py could cause Operation.report()
to silently return None, which the API then serialised as JSON null and
the UI rendered as "Null":

1. `agents_steps[step.paw]` in report() raised KeyError when a link's
   paw was not in the set of operation agents built at call time (e.g.
   the agent was removed between operation run and report download).
   Fixed with `agents_steps.setdefault(step.paw, {'steps': []})`.

2. `abilities_by_agent[link.paw]` in _get_all_possible_abilities_by_agent()
   had the same pattern — orphan paw not guarded.  Fixed with an
   explicit membership check before the extend.

3. The `except Exception` block in report() logged the error but fell
   off the end of the function, returning None implicitly.  The caller
   then returned None to web.json_response(), producing the "Null"
   download.  Fixed by re-raising so the framework returns a proper 500
   with an error body instead of a silent null payload.

Adds a regression test that constructs an operation with a chain link
whose paw is not present in operation.agents and asserts report()
returns a non-None dict that includes the orphan paw's steps.

* style: fix E303 too many blank lines in test_operation.py

* refactor: simplify double dict lookup using abilities_by_agent.get()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants