valuation-red-team

v2026.09.24

Attacks a finished valuation the way a skeptical reviewer would — hunts bias and verdict-first reasoning, checks that each risk is charged exactly once, tests whether growth is paid for, interrogates terminal value, reverse-engineers what the market price already assumes, and applies the seven deadly sins of acquisition analysis. Use when reviewing a completed valuation, red-teaming or challenging assumptions before acting on a number, auditing a DCF someone else built, or checking double counting, control premiums, synergy claims, comparable sets and route constraints.

GitHub
Install command
npx skhub add lyndonkl/valuation-red-team
Markdown
SKILL.md

Valuation red team

This is the adversarial pass. Someone has produced a value per share. Your job is to find the places where that number is not supported by the work behind it, and to say so in a form the owning agent can act on.

Three rules define the role.

You raise findings. You do not edit. Every artifact has exactly one writer, and it is not you. A disagreement travels as a finding with evidence and a suggested fix. If you correct the number yourself, nobody can tell later whether the model was fixed or the critic was.

The burden of proof sits on the number. You are not required to produce a better valuation before you may reject this one. A claim with no evidence behind it is a finding, whoever made it.

Attack the input, never the output. "Value is too high" is not a finding. "Terminal return on capital sits 4 points above the terminal cost of capital and the write-up names no barrier to entry" is.

Before you start

You need the artifacts the analysis produced. At minimum: classification.json, cost-of-capital.json, forecast.json, dcf-result.json, and the prose files that carry the argument. In acquisition, restructuring and ipo modes you also need the stage artifacts for those paths.

If gate G6_valued has not passed there is no equity value to attack. Return a blocked status naming the artifact you are missing. Do not review a half-built model and grade it as if it were finished.

Read classification.json first. The route decides which attacks apply and which are irrelevant. Running the growth-reinvestment attack on a bank valued by excess return wastes both your time and the analyst's.

Step 0 — run the mechanical gate before you read anything

python3 <skills>/valuation-consistency-checks/resources/validate.py \
  --mandate mandate.json --classification classification.json \
  --capital capital.json --forecast forecast.json --dcf dcf-result.json --json

That script tests currency agreement, the terminal identities, capital weights, beta range, the growth reconciliation, the equity bridge arithmetic, value per share, tax rates and three constraint rules. It is free and it is deterministic. Start there so your reading time goes to the things a script cannot see.

Convert its output as follows.

Validator resultBecomes
ERRORa high finding, quoting the check name and the two numbers that contradict
WARNa medium finding, unless the write-up already carries a written defence
INFOnothing; do not report checks that passed
SKIPinvestigate — a missing file reads exactly like a clean pass

The SKIP lines matter more than they look. A path typo makes the gate exit 0 with almost every check unrun. Confirm each artifact you passed actually exists before you trust the exit code.

The attack sequence

Work these in order. Early attacks invalidate later ones: a route violation makes the growth arithmetic moot, so there is no point pricing the terminal value of a model that should never have been built.

#AttackDefault severityDetail
A1Route conformance against the compiled constraintshighconstraint-conformance.md
A2Bias — did the conclusion precede the analysismedium to highbias-diagnostics.md
A3Single charge per riskhighsingle-charge-register.md
A4Growth must be paid forhighthis file, plus the register
A5Terminal value plausibilityhighthis file
A6The equity bridge and the double-count registerhighsingle-charge-register.md
A7The implied-expectations attackmedium to highimplied-expectations.md
A8Comparable-set and pricing-route biasmedium to highcomparable-set-bias.md
A9The seven sins of acquisition analysishighseven-sins-audit.md
A10Narrative-to-number traceabilitymediumthis file
A11Uncertainty and the closing claimmediumthis file

A1 · Route conformance

What to check. Every rule in classification.json.constraints is a predicate over the produced artifacts. Test each one. A constraint the diagnostician compiled and nobody honored is the most expensive error in this domain, because the machinery that ran was never valid for this company.

How. The validator enforces no-fcff-valuation, require-failure-probability and no-earnings-multiple mechanically, and it needs a method field on the DCF artifact to do the first one. The remaining rules you test by reading, using the predicate table in constraint-conformance.md. That file carries all 28 rules from the routing framework with the artifact and field each one reads.

Failure looks like. A bank carrying a WACC and an enterprise value. A price-earnings multiple applied to a trough year. An optimal-debt-ratio schedule run on an insurer. A total beta paired with a diversified acquirer. An illiquidity discount applied to an IPO. A terminal value set by an exit multiple.

Severity. High, without exception. A route violation does not degrade the answer, it voids it.


A2 · Bias

What to check. Whether the number was chosen before the model was built. This is the sin that hides every other sin, because a fitted model passes consistency checks.

How. Reconstruct the chronology from the artifacts and their sequence. Compare each driver against its sector benchmark and note which direction the deviations run. Count the inputs that carry a story sentence. Run the implied-expectations solve described in A7 and see how close the base case sits to the market price. Full diagnostic set in bias-diagnostics.md.

Failure looks like. Every driver sits at the favourable end of its plausible range, and the deviations all point the same way. The value lands within a whisker of the price on a company nobody claims is fairly valued. The write-up describes assumptions as "conservative" without naming a number. A range is reported with no designated base cell.

Severity. Medium when the pattern is one-sided but each input is individually defensible. High when the chronology shows the price came first, or when a driver was demonstrably moved to reach a target.


A3 · Single charge per risk

What to check. Each risk is priced exactly once. This is the audit that pays best, because every one of its failures is the same error wearing a different costume, and each one is quietly worth a lot of value.

How. Build the register: one row per risk, one column per channel it could have entered through. Fill it by reading cost-of-capital.json for premiums, forecast.json for haircuts and probability weights, and dcf-result.json for outer adjustments and bridge discounts. Any row with two marks is a finding. The full risk list, the channels each risk can legitimately use, and the correct channel to keep are in single-charge-register.md.

Failure looks like. A country risk premium in the cost of equity, plus a nationalization scenario, plus a governance discount — three charges for overlapping risks. A failure probability applied alongside a distress-adjusted discount rate. A total beta paired with an illiquidity discount with no overlap argument. Weak governance modelled as a discount-rate bump rather than as low returns on capital and a low probability of change.

Severity. High. The routing framework carries single-charge-per-risk as a universal constraint, so a double charge is also a route violation.


A4 · Growth must be paid for

What to check. Growth is bought with reinvestment at a return. The identity is g = reinvestment rate × return on capital in every explicit year, and reinvestment rate = g / ROC in the terminal year. Growth and reinvestment are one decision. Setting them separately asserts a return on capital nobody examined.

How.

  1. The validator's growth_reconciliation check reports the widest gap between revenue growth and the growth the reinvestment supports. It warns above 2 points.
  2. Recompute marginal return on invested capital yourself from the forecast rows in dcf-result.json: the change in after-tax operating income across the forecast divided by the change in invested capital. Compare it against what the best firms in the sector earn.
  3. Check the sales-to-capital ratio in forecast.json against the industry average bundled with cost-of-capital-toolkit in resources/data. A ratio far above the industry is an explicit claim that growth is nearly free.
  4. Confirm the three matched pairs are not mixed. Operating income pairs with the reinvestment rate and return on capital. Earnings per share pairs with the retention ratio and return on equity. Crossing them is a silent error.

Failure looks like. Revenue growing much faster than the capital base with no efficiency argument and no stated stop date. Marginal return on capital above anything the sector has achieved. A sales-to-capital ratio taken from a different business. An efficiency-growth term applied in every year rather than once across a transition.

Severity. High when the terminal identity breaks or when marginal return on capital is implausible. Medium when only the forecast years drift and the write-up is silent.


A5 · Terminal value plausibility

What to check. Most of the value usually sits here. Six tests, in order of how cheap they are to run.

TestPredicateSeverity if it fails
Growth capterminal growth ≤ riskfree rate in the valuation currencyhigh
Finite perpetuityterminal cost of capital strictly above terminal growthhigh
Earned growthterminal reinvestment rate equals g / ROC exactlyhigh
Named moatterminal return on capital above terminal cost of capital only with a stated barrier and its expected lifehigh
Mature inputsbeta near 1.0, debt ratio at the industry level, country premium faded, tax at the marginal ratemedium
Horizonterminal value below roughly 90% of total value, or a forecast long enough to reach maturitymedium

How. The validator covers the first three and flags the fourth and sixth. Read forecast.json's terminal block for the fifth. Then run the reverse consistency check by hand: the embedded reinvestment rate is 1 − FCFF_terminal / after-tax EBIT_terminal, and the implied perpetual return on capital is g divided by that. Never accept a valuation whose implied perpetual return nobody looked at.

Failure looks like. A high-growth beta or debt ratio left standing in the terminal year. A country risk premium held at crisis levels forever. Terminal return above the terminal cost of capital by spreadsheet default. Terminal capital spending grown from year N instead of back-solved from the reinvestment rule.


A6 · The bridge and the double-count register

What to check. The walk from operating assets to value per share, and then the register of things that get charged or credited twice. Most disputes between competent analysts happen here rather than in the forecast.

How. The validator recomputes the bridge and the per-share division. Then work the sixteen-row double-count register in single-charge-register.md as a checklist. Each row names where the double count hides and which side to delete.

Failure looks like. Option value subtracted and diluted shares used in the same division. Minority interests carried at book. Brand value added on top of margins that already reflect the brand. Goodwill added as an asset. Cash added back while interest income is still inside the cash flows. Pension underfunding counted as debt in the weights and subtracted again in the bridge.

Severity. High for arithmetic that does not tie, and for any double count large enough to move the verdict. Medium for a double count that is real but immaterial — say so, and say how much it is worth.


A7 · The implied-expectations attack

What to check. What the market price already assumes, and whether the analyst's own drivers survive being read backwards.

How. Run the implied subcommand with the market price as the target:

python3 <skills>/dcf-valuation-engine/resources/dcf.py implied --in solve.json

The payload takes base_case (the analyst's own driver set), path (a dotted path to the driver that carries the story, such as operating_margin.end or terminal.growth_rate), target_value_per_share set to the market price, and a low/high bracket. Then locate the solved number in the sector distribution with relative-valuation-toolkit.

Procedure and worked forms are in implied-expectations.md, including the market-implied probability of management change for restructuring mandates.

Failure looks like. Two different findings, and they point in opposite directions.

  • Against the model. The analyst's own drivers imply a market share above 100%, a revenue number in year 10 that no competitor is losing, or a margin the sector has never sustained. That is a high finding.
  • Against the thesis, not the model. The price implies something extreme and the write-up never says so. The verdict then rests on an unstated disagreement with the market. That is a medium finding, and the fix is a sentence, not a number.

Some scenario always justifies any price. The question is never whether a story exists but whether it is probable.


A8 · Comparable-set and pricing-route bias

What to check. A relative valuation runs in every mode, and it is the easiest place to reverse-engineer a conclusion. A peer set assembled after the target price can justify almost any number.

How. Rebuild the peer statistics with relative-valuation-toolkit. The peer-stats subcommand reports the median, the quartiles, the skew and the count of firms dropped from the sample. The locate subcommand places a multiple inside the bundled current distribution. The regress and predict subcommands test whether the fitted equation is strong enough to act on. Then check the multiple against the forbidden list for this route, which is tabulated in comparable-set-bias.md.

Failure looks like. A peer set of six firms with the drop-outs undocumented. Means used where medians belong, on a distribution that is always right-skewed. A fixed threshold applied across years or regions. A United States regression applied to a firm that operates elsewhere. One company-wide multiple used across divisions with different economics. A price built off precedent transactions, which is a sample of overpayments.

Severity. Medium in general. High when the multiple is on the forbidden list for the route, or when the peer set was assembled after the price was known.


A9 · The seven sins of acquisition analysis

What to check. In acquisition, restructuring and ipo modes, run the seven-sin scorecard before anything else in this section. Set the prior first: acquirers usually destroy value, and the failure is structural. The burden of proof belongs on the deal.

SinThe input it corruptsTest
1 Risk transferencediscount rateis the target discounted at its own cost of equity
2 Debt subsidycost of capitalis the target's own debt capacity and cost of debt used
3 Auto-pilot controlpriceis the premium derived as restructured minus status quo
4 Elusive synergycash flows and growthdoes every claimed benefit map to one valuation input with a number and a date
5 It's all relativeprice and terminal valueis the price off precedent deals, or the terminal value an exit multiple
6 Verdict firsteverythingdid the valuation post-date the price
7 It's not my faultdeliveryis a named person accountable for the promised benefits

How. Rebuild the target's own rate with cost-of-capital-toolkit and quantify the transfer that the acquirer's rate creates. Re-run the synergy schedule with project-investment-analysis, using synergy for the value and the ceiling price, and synergy-haircut for what the post-merger evidence says will actually arrive. Confirm all four numbers exist and that the acid test was applied to the one matching the stated motive. Detail, including the four numbers and the acid test, is in seven-sins-audit.md.

Failure looks like. The acquirer's cost of capital on the target's cash flows. A fixed percentage control premium. A synergy baseline built on the status quo target rather than the restructured one, which counts control gains twice. Earnings accretion cited as a deal test, when accretion is guaranteed whenever the acquirer's price-earnings ratio is higher.

Severity. High for sins 1 through 5, because each mis-states a number. High for sin 6 when the chronology shows the price came first. Medium for sin 7.


A10 · Narrative-to-number traceability

What to check. Two counts that should both be zero: model inputs with no story sentence, and story claims with no driver. Then the routing of each claim.

How. Read narrative.md and drivers.json against forecast.json. Confirm each claim was routed exactly once. Probable claims belong in the base-year numbers and expected cash flows. Plausible claims belong in the growth rate. Possible claims belong in option value on top of the model, and nowhere else.

Where several firms chase one market, run the aggregation test. Impute each competitor's breakeven revenue in a common year, multiply by its share of revenue from that market, and sum. Implied market shares across the sector cannot exceed 100%.

Failure looks like. A market counted in the revenue path and again as option value. A driver with no sentence behind it. A claim in the prose that moves no input, which is decoration. An implied sector total above any credible market forecast.

Severity. Medium. High when a possible claim entered the cash flows and the option layer at once, because that is a double count with a number attached.


A11 · Uncertainty and the closing claim

What to check. Whether the point estimate was honestly converted into a range, and whether the gap was converted into an actionable claim.

How. Confirm the sensitivity grid varies the two drivers that actually move value for this route. Confirm each scenario row carries a likelihood label and that one cell is designated the base case. Confirm the market price is located inside the distribution rather than compared to the mean. If the analyst supplied no distribution, rebuild one with monte-carlo-valuation using simulate, and check whether the price sits near the median. A price near the median means the market is inside the uncertainty band and there is no edge to act on.

Then check the closing claim. A gap needs a closing mechanism and a horizon. Margin of safety should be widest exactly where the routing was hardest — young, distressed and emerging-market cases.

Failure looks like. A point estimate quoted to the cent. A range with no chosen cell. A gap presented as a trade with no catalyst named. A DCF value averaged with a multiple-based price into a single number.


The severity model

Three levels, and each carries a different obligation.

SeverityMeaningWhat it triggers
highan internal contradiction, a route violation, or a claim with no evidence that moves the verdictblocks the verdict; gate G7_challenged does not pass until the finding is resolved or explicitly disclosed
mediumdefensible but unstated; the model is making a claim nobody wrote downowes a written defence in the owning artifact, or a fix
lownoted for the record; does not change the number materiallynothing beyond being recorded

Use high when there is no assumption set under which both halves of the contradiction are true at once. Terminal growth above the riskfree rate is not aggressive, it is inconsistent with the rate already chosen. Use medium when the model is possible but silent. The right response there is usually a sentence in the write-up, not a change to the numbers.

Resist severity inflation. A challenge file where everything is high tells the orchestrator nothing about what to fix first, and it burns the loopback budget on cosmetics.

The loopback rule. A high finding reopens the owning stage and its dependents. The cap is two loopbacks per stage. On the third, the finding is disclosed in the report as an unresolved risk rather than looped again. Write findings knowing this: a high finding you cannot state precisely enough to fix will simply consume two re-runs and then appear in the report anyway.

The findings shape

You write two artifacts and nothing else: challenge.json and challenge.md.

challenge.json carries a findings array. Every finding has these five keys.

KeyContents
idstable within this run, F1, F2, … The orchestrator tracks it in state.json.open_findings
severityhigh, medium or low
target_stagethe stage that owns the artifact the fix belongs in
claimone sentence stating the defect, not the consequence
evidencethe artifact, the field, and the numbers that contradict
suggested_fixthe input to change, or the sentence that has to be written
{
  "findings": [
    {
      "id": "F1",
      "severity": "high",
      "target_stage": "cost-of-capital",
      "claim": "Country risk is charged twice.",
      "evidence": "cost-of-capital.json adds a 3.1% country risk premium to the cost of equity, and dcf-result.json applies a further 12% expropriation haircut to the operating asset value. The routing constraint single-charge-per-risk permits one channel.",
      "suggested_fix": "Keep the exposure-weighted premium in the cost of equity and remove the haircut, or model expropriation as an explicit probability-weighted branch and drop the premium. State which channel was chosen."
    }
  ]
}

target_stage names the stage the orchestrator dispatched, matching the keys under state.json.stages. Route each finding to the artifact's single writer, using the roster:

ArtifactOwning agent
classification.json, diagnosis.mdcompany-diagnostician
narrative.md, drivers.jsonbusiness-narrative-analyst
cleaned-financials.json, adjustments.mdfinancial-statement-analyst
cost-of-capital.json / .mdcost-of-capital-analyst
forecast.json, dcf-result.json, intrinsic.mdintrinsic-valuation-analyst
relative-result.json, relative.mdrelative-valuation-analyst
capital-structure.json, payout.json, investment.jsonthe matching corporate-finance analyst
real-options.json / .mdreal-options-analyst

A finding aimed at the wrong stage will be fixed by nobody. When a defect spans two stages, route it to the upstream one, because the downstream artifact will be rebuilt anyway.

challenge.md is the prose companion. It carries five sections in this order. The route you validated against. The validator run and its counts. The findings ranked by severity, with the argument for each. The attacks you ran that found nothing. The open questions you could not resolve from the artifacts. That fourth section matters as much as the third. It is the record that the review was systematic rather than opportunistic.

Worked findings, including the shape of a good evidence field, are in finding-examples.md.

What this pass does not do

It does not produce an alternative valuation. If you rebuild the model to test a claim, the rebuild is evidence inside a finding, not an artifact.

It does not edit forecast.json, dcf-result.json or any other agent's file. Not even to fix an obvious typo.

It does not grade the analyst. It grades the argument.

It does not report checks that passed as though they were findings. Consistency is not accuracy, and a clean validator run is a floor, not a verdict.

Discovery
Tags

No tags published for this skill.

Version
Latest version metadata

Version

v2026.09.24

Published

Sep 24, 2026

Category

Uncategorized

License

Not specified

Source path

skills/valuation-red-team

Default branch

main

Latest commit

4acc337

Tree SHA

4f0a83e