Task: Decide a cut's authorisation and apply it

Table of Contents

This page documents a task in the Rework the market data feeds into a market data service with adapters — the remaining work story. It captures the goal, current status, acceptance, and any notes or results.

1. Goal

A decider approves or refuses the request in the inbox, and the cut becomes authorised or refused, with its acts recorded.

2. Status

Field Value
State DONE
Parent story Rework the market data feeds into a market data service with adapters — the remaining work
Now Nothing.
Waiting on Nothing.
Next Nothing.
Last touched 2026-11-01

3. Acceptance

  • The inbox decide works for the cut approval kind with its part, four eyes and comments.
  • The reporting side applies the decision by reconciliation: the cut's authorisation stamped, the detail's applied facts set, the state derived.
  • The cut record shows the decision and the history; the screen and tests are green.

4. Plan

4.1. The decision is the inbox's, and the cut adds nothing to it

The two cut kinds already name their deciding function: reporting.eod_cut_authorisation names inbox::approvals:decide_finance and reporting.ipv_cut_authorisation names inbox::approvals:decide_market_risk. The inbox's decide operation already resolves a kind's permission, refuses a decision by the person who asked, requires a comment on a refusal, and moves the request to approved or refused. Nothing in the decide path changed, and the demo already holds the decider: the Finance role carries inbox::approvals:decide_finance, and rachel.byrd, beth.clark, graham.pierce and hoi.yan.lee hold it in the Acme tenant beside henry.jones, who takes the close. No new seed was needed; the task's own supply test pins the kind's declarations.

4.2. The apply is a reconciliation, as a role grant is

ores_reporting_unapplied_cut_authorisations_fn answers every decided cut-authorisation request the cut has not been stamped with, across every tenant that is not terminated. It keys on the cut kinds' own approval_kind_code and not on a list, so a third cut kind needs no code change. cut_authorisation_applier writes each cut in its own tenant and as the person who decided, stamps the decision's own instant rather than the run's clock, and marks the detail row applied in the same unit of work. A row leaves the answer once applied, so the second run writes nothing and the cut keeps its version.

The service runs it at start and on every change to an approval request; a sweep every 60 seconds reads the store for what no event announced.

4.3. What the screens read

The BFF joins the subject a cut's request is about in one read of the detail rows, then reads each cut the rows name, so the request page draws the label, the kind, the instant, the scope and the counts. The cut's Authorisation tab states who stamped the decision and when, from the detail row's applied facts, and the history shows the version the apply wrote.

4.4. What is not done

Nothing was built or run: the branch's C++ and web suites have not executed, so the apply's cases, the inbox's cases and the BFF and web cases are written and unproven. The live walk of the decide needs a cut whose body the take froze in the demo tenant, and the demo tenant holds no published curve set, so no live EOD take can raise a request there yet.

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
The reporting core suite's cases 248 cases, 248 passed compass test run -- -R ores.reporting.core.tests 6d50987ca6
The reporting service suite 1 target, passed compass test run -- -R ores.reporting.service.tests 6d50987ca6
The inbox core suite 1 target, passed compass test run -- -R ores.inbox.core.tests 6d50987ca6
The BFF suite 88 files, 1217 tests passed npm run test --workspace @ores/bff 6d50987ca6
The web suite 193 files, 2415 tests passed npm run test --workspace @ores/web 6d50987ca6
Codegen drift over the components under test No drift check_component_drift.py --all 6d50987ca6
The composition and hygiene gates All passed (see below) check_*.py, check_sql_hygiene.py 6d50987ca6
Compass lint Passed, one advisory compass lint 6d50987ca6
The live decide and the apply's rows evidence/cut_decide_live.txt the shell, curl and psql in that file 6d50987ca6

6. Notes

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
#4349 [reporting] Apply a decided cut authorisation

9. Review

Comment summary File Decision Notes
       

10. Result

10.1. Built

  • ores_reporting_unapplied_cut_authorisations_fn, with its drop script and the registry's execute prefix; applied to the live database additively as one function, and granted to the reporting service role.
  • cut_authorisation_applier in ores.reporting.core: an approval stamps approved_by and approved_at and marks the detail applied; a refusal stamps refused_at and refusal_comment; the stamp is the decision's own instant and the row is skipped when it already holds it.
  • The reporting service runs the reconciliation at start, on the approval request's change event, and every sixty seconds.
  • reporting.v1.ops.list_cut_authorisation_subjects: the body a cut authorisation is about, answered for a page of requests in one call, from the model and through codegen to the protocol, the shell command and the TypeScript twin.
  • The BFF's cut-subject join, the request page's subject block, and the cut record's applied facts.
  • Tests: the apply's five cases, the inbox's four cases on the two cut kinds, the BFF's five cases on the join, and the web's two on the applied facts.

10.2. The apply is a reconciliation across tenants, and that bit a test**

The first run of the apply's refusal case failed with 0 = 1= while the store held the stamped cut: the reporting service running beside the suite had swept the tenant the test provisions and applied the row first. The cases now assert the facts (the cut stamped, the detail applied, one instant for both) and not which reconciler reached them first, because the count belongs to the store and not to the case.

10.3. Verified live**

evidence/cut_decide_live.txt carries the whole walk: henry.jones raises a cut's authorisation; four eyes refuses him with code=four_eyes; rachel.byrd, a Finance second person, approves it; a second request is refused and admits no second answer; a third is withdrawn by the asker; the request page answers cutSubjectState=missing because no body was frozen; and the story shows the decision beside the ask. The apply's stamps are shown in the same store, taken from the rows its own cases wrote, with the cut's version history naming the request.

10.4. What the live walk cannot reach, and why**

No live take can freeze a whole body in the demo tenant. The store holds no published curve set, every market observation it holds is an FX spot, and the one IR bootstrap config points at a series with no observations, so neither publish_curve nor republish_curve can produce a set. A take of an incomplete body raises no request, which is the take task's own finding. The step that needs it — a request the take raised, whose decision the apply then stamps — is covered by the suite against the same database instead.

10.5. Remaining**

  1. A live end-to-end walk needs a published curve set in the demo tenant: a real IR bootstrap config over series that hold observations, and a Market Risk decider to approve its publication. That is market data's work, not this task's.
  2. The shell cannot state an empty argument, so a refusal with no comment can only be shown by the suite. The command grammar's sentinel is __none__ for a list and there is none for a string.

Emacs 29.3 (Org mode 9.6.15)