Task: Retrieve the compute results

Table of Contents

This page documents a task in the Prove the report grid run end to end story. It captures the goal, current status, acceptance, and any notes or results.

1. Goal

collect_compute_results completes without downloading anything, and ores.reporting does not include ores.compute.api, so a finished job's results are never brought back. Make it fetch the output object and record where it landed. Parsing the results is out of scope.

2. Status

Field Value
State DONE
Parent story Prove the report grid run end to end
Now Nothing.
Waiting on Nothing.
Next Nothing.
Last touched 2026-10-21

3. Acceptance

  • After a run, the instance records the output location and the bytes can be retrieved from storage.
  • A run whose compute job failed is distinguishable from one that produced output.

4. Plan

Where it is. report_execution_handler::collect_results in ores.reporting/core/src/messaging/report_execution_handler.cpp is a pass-through: it logs, sets success and completes. Nothing downloads, and the request it decodes carries no location to download from — report_instance_id, tenant_id, correlation_id and batch_id, and nothing else.

Where the location lives. It is output_uri on compute's own result row. ores.reporting does not link ores.compute, which the Goal gives as the reason a finished job's results never come back.

The arc has one established way for a component to reach another's data: ask the owner through its operation, carrying the run token, rather than read its tables. Every gather step does it, and the isolation story made it the rule for storage. So reporting asks compute for a batch's results and never sees compute's tables.

The decision to take first. Two ways to get the location here, differing in who owns the knowledge.

  • Reporting asks compute. Compute already answers this, so nothing new is needed: compute.v1.workunits.list_by_batch_id gives the batch's workunits and compute.v1.results.list_by_workunit_id gives each one's results, and a result carries outcome, error_message and output_uri. Reporting then downloads the object and records where it landed. This is the arc's pattern, and compute's schema stays in compute.
  • The workflow carries the location. The step's request gains the output URI, because the engine already dispatches with the batch. Smaller, but the engine is the caller, so it has to have read the location from somewhere — the same read, moved earlier and hidden.

The plan is the first. The second is recorded because it is the cheaper move, and it is what a reader will think of.

Correction to the first reading of this Plan. It said the arc's pattern "needs a new subject with its own tests". It does not: the two operations above already exist, so this task adds a caller rather than a protocol.

The failed-job half. The acceptance asks that a run whose compute job failed be distinguishable from one that produced output, and a result already carries the outcome and the wrapper's error_message, so the distinction is a read rather than a new concept. A batch with a failed result is a failure of the run, not a run with no output.

The schema decision, which comes first. The acceptance says "the instance records the output location", and ores.reporting.report_instance has no field for one: it carries output_message, started_at and completed_at, and nothing naming an object. Either the instance gains a column, or the location is recorded somewhere that already exists and the acceptance is read that way. A column would have to be modelled, generated, migrated and given its row-level security before any of the above can be written, which is why this task is larger than its Goal suggests.

Out of scope. Parsing what the output holds. This task brings the bytes back and records where they are.

5. Notes

6. Test Scenarios

Manual QA scenarios (scaffolded via compass add test_scenario) that verify this task. Link new ones here as they're created; the scenario doc itself links back via its "Verifies task" field.

Scenario State Notes
     

7. PRs

PR Title
#2899 [reporting] Retrieve a run's compute results

8. Review

Comment summary File Decision Notes
       

9. Result

The step is implemented, and both acceptance bullets are met in code.

  • The reads. collect_results asks compute for the batch's workunits and then for each workunit's results, over the bus with the run token, which is how every other step in this arc reaches an owner's data. No new protocol was needed: compute.v1.workunits.list_by_batch_id and compute.v1.results.list_by_workunit_id already existed, and the Plan's first reading, that this needed a new subject with its own tests, was wrong.
  • The failed job is distinguishable. A result whose outcome is not Success fails the run and carries the wrapper's own error_message. A batch whose results name no output is a failure too, rather than a run that quietly retrieves nothing.
  • The output is fetched and its landing recorded. The object the result names is read from there and kept under this component's own key, reporting/runs/{instance}/output.tar.gz, so the instance names an object reporting owns rather than another component's, and the instance records that key in output_storage_key.

storage_paths::split_object_path turns the result's path back into the bucket and key it names, so reporting reads the object the owner named instead of reconstructing compute's key layout in a second place.

ores.reporting.core gains ores.compute.api and a run_token helper: an owner is reached over the bus, where the token rides as a delegation, while storage is reached over HTTP, which takes it as a bearer.

Verified by building and running. ores.reporting.core.lib, ores.reporting.service.exe and ores.reporting.core.tests all build. The storage-plan cases pass, 14 assertions in 5 cases, including the new output_storage_key case.

The gap. The handler itself has no test: collect_results talks to compute and storage, so covering it needs those stood up, and nothing in ores.reporting.core.tests does that today. The plan's key and the object-path split are the seams that are covered.

Emacs 29.3 (Org mode 9.6.15)