Task: Add the run configuration and address phase results by role

Table of Contents

This page documents a task in the Run a full report: scheduling, reporting and compute story. It captures the goal, current status, acceptance, and any notes or results.

1. Goal

Give the report definition a run configuration, carry it into the workflow start message, and make the workflow build the phase list that configuration names. At the same time replace the positional results[N] access with a lookup by phase role, because a second chain shifts every index after the phase it adds.

2. Status

Field Value
State DONE
Parent story Run a full report: scheduling, reporting and compute
Now Nothing.
Waiting on Nothing.
Next Nothing.
Last touched 2026-09-27

3. Acceptance

  • A definition's run configuration selects the phases that run, and a run is reproducible from the database alone.
  • No step consumer reads an earlier result by index; a missing result fails loudly instead of yielding an empty field.
  • A definition whose configuration executes every phase still runs the existing chain unchanged.

4. Plan

Four parts, in the order each depends on the one before it.

  1. Results addressed by role. A step result becomes a named pair rather than a bare JSON string: workflow_step_result carries the step's name and its response, workflow_step_results is the list of them, and find_step_result answers the payload the named step produced. The engine builds that list from every forwarded step, an empty answer included, because the list it built before skipped empty answers and so its positions were not step indices. A builder that cannot find or cannot read what it consumes throws; the engine catches that at dispatch and starts compensation, so a missing result fails the run instead of publishing a command with an empty field.
  2. The run configuration. The definition table gains pre_processing, prepared_input_key and post_processing, so the choice of phase is definition state and a run is reproducible from the database alone. The trigger validates the codes, refuses a substituting definition that names no archive, and copies the configuration into report_execution_request, the payload the engine stores and hands to build_steps.
  3. The chains. One registered definition builds either chain from that payload. execute and execute are the seven steps the definition ran before; substitute replaces the four pre-processing steps with one that resolves the named archive; ignore replaces result collection with one that records the batch and leaves it unread. Submission takes the name of whichever step filled the input role, so no consumer is told which producer ran.
  4. The substitutes. Both are reporting operations, and both answer in the result type of the phase they replace. That shared answer is what makes them indistinguishable to the step after them, and it is why the substitute requests name an existing result struct rather than a type of their own.

Deliberately not done here, and owned elsewhere in the story: the instance does not record which phases were substituted (the substituted-pipeline task adds it), retrieval is not yet separated from result collection, and resolving the prepared archive does not read it, so nothing checks that it is a valid engine input or digests it.

5. Measurements

Every number this task claims about a tree, with the command that produced it and the commit it was measured at. A number without both is not a measurement, because a clean-up record describes a tree and trunk moves under a branch: a consumer count, a gate's component count and a suite's size all change with no code changing, and the reader of a bare number cannot tell which tree it came from. Where the raw output matters, put it in an evidence/ file beside the task and name that file in the Command column.

What Value Command Commit
Step consumers that read a previous result by index 0 (was 8) =grep -rnE "(results\ prev)\[[0-9]+\]" projects/ores.reporting/api/include/ores.reporting.api/workflow projects/ores.refdata/api/include/ores.refdata.api/workflow \ wc -l= f4ffb3c216
build_command definitions carried to the named-result signature 5 grep -rln build_command projects/ --include=*.hpp --include=*.cpp f4ffb3c216
Steps the execute and execute configuration builds 7 ores.reporting.api.tests "[workflow][reporting]" f4ffb3c216
Steps the substitute and ignore configuration builds 4 ores.reporting.api.tests "[workflow][reporting]" f4ffb3c216
Chain cases in the new suite 10 cases, 31 assertions ores.reporting.api.tests "[workflow][reporting]" f4ffb3c216
ores.reporting.api.tests 64 cases, 248 assertions ores.reporting.api.tests f4ffb3c216
ores.shell.reporting.tests 108 cases, 283 assertions (was 102 and 269) ores.shell.reporting.tests f4ffb3c216
ores.workflow.core.tests 17 cases, 85 assertions ores.workflow.core.tests f4ffb3c216
Schema validator warnings 0, on the rebased tree (was 4 before main disabled the workspace SQL facet) projects/ores.sql/utility/validate_schemas.sh f4ffb3c216
Reporting codegen drift none projects/ores.codegen/scripts/check_component_drift.py --component reporting f4ffb3c216

6. Notes

6.1. Decisions

  • The run configuration is definition state: one entry per substitutable phase, with the prepared archive's location beside it rather than in the environment. A substituted run is therefore recognisable from the definition row before it runs, and an operator asking why a run produced no results reads the answer from the database.
  • The chain is a pure function of report_execution_request, because the engine rebuilds it on every dispatch and on recovery. The configuration therefore travels in the start message and not only in the definition.
  • A consumer addresses a producer by role, and the role resolves to a step name when the chain is built: submission is handed the name of whichever step filled the input role. Neither the phase nor its substitute knows about the other, and no step knows its own position.
  • The two substitutes are implemented here rather than left to the proof task. A configuration that can select a phase must select one that can run; leaving the subjects unhandled would have made a substituting definition hang. What the proof task adds is the live run: a real grid, a real archive, and the instance's record of what was substituted.
  • The DQ publish function gives a definition it publishes the execute and execute configuration. A DQ artefact describes what to report, not how much of the pipeline to run, so the artefact schema is unchanged and publishing behaves as it did.

6.2. A codegen CLI inconsistency, found while regenerating

./compass.sh codegen generate --model projects/ores.reporting/modeling/ores.reporting.operations.org --address ores.cpp.shell-command reports that it has nothing to generate, and it says the same for ores.iam.account_messages.org, a model this change does not touch. The component path does generate them: the drift gate's in-place regeneration of reporting wrote the shell commands for both new messages, and the shell suite grew from 102 to 108 cases. The two paths disagree about whether the drawer's :ores.cpp.shell-command.enabled: true override applies to an operation model, so the single-model form cannot be used to preview this facet. The generated output is right; only the preview is not, and that is not fixed here.

6.3. Applying the schema

The table change reaches the environment by recreation, which is the only migration this project has. Two seed paths write definitions and both were given the configuration explicitly: the FSM fixture in populate/reporting so the fixture states what it runs, and the DQ publish function so its choice is read where the definition is written. Neither was left to a column default, because the table has no defaults and a definition that states nothing is not reproducible.

7. 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
     

8. PRs

PR Title
#2289 [reporting] Select a report's phases from its run configuration

9. Review

Comment summary File Decision Notes
       

10. Result

A report definition now says how much of the pipeline a run executes, and the workflow builds the chain that configuration names.

The run configuration is three columns on ores_reporting_report_definitions_tbl — pre_processing, prepared_input_key and post_processing — so it is definition state and a run is reproducible from the database with no operator present. The trigger validates the codes, refuses a substituting definition that names no archive, and copies the configuration into the workflow start payload, which the engine rebuilds the chain from on every dispatch and on recovery.

Step results are addressed by the name of the step that produced them. The engine now carries an entry for every forwarded step rather than only the ones that answered, because the old list's positions were not step indices and one silent step shifted every later result. A builder that cannot find or read what it consumes throws, and the engine turns that into compensation, so a missing result fails the run instead of publishing a command with an empty field. Five build_command definitions moved to the signature; the provisioning workflow, the only other consumer that read a result by position, reads save_account by name.

One registered definition builds both chains. execute and execute are the seven steps the definition ran before; substitute replaces the four pre-processing steps with resolve_prepared_input; ignore replaces result collection with ignore_compute_results. Both substitutes are reporting operations and both answer in the result type of the phase they replace, which is what makes them indistinguishable to the step after them, and submission is given the name of whichever step filled the input role, so neither phase knows about the other.

Deferred on purpose, and owned by other tasks in the story: the instance does not record which phases were substituted, retrieval is not yet separated from result collection, and resolving a prepared archive does not read it, so nothing checks that it is a valid engine input or digests it.

The branch also carries one unrelated fix. The clean-http change stopped env_init.py generating ORES_HTTP_SERVER_JWT_SECRET without recording a new .env format version, so no checkout was ever told to regenerate and every one of them kept a line that make_mapper("HTTP_SERVER") maps onto an option no parser registers — which stops ores.http.server at its next start. Version 26 records the removal, and this checkout's .env was regenerated and the gateway restarted to confirm.

Verified locally, since CI runs no full build: the whole tree builds, the reporting, shell-reporting, workflow core and api, refdata api and reporting service suites all pass, compass lint and the structure-note link check are clean, the schema validator reports no warnings, and the reporting component has no codegen drift.

Emacs 29.3 (Org mode 9.6.15)