All notable changes to pwplan-core are documented here. Format follows
Keep a Changelog; this project adheres to
Semantic Versioning. The planning wire contract is versioned separately as
PLANNING_CONTRACT_VERSION.
-
Slice 4 synthetic Cash Flow Bridge UI (2026-07-05). Added the
CashflowBridgeTooltab, which uses demo/de-identified monthly-close aggregate values to callplanning.cashflowPlanningBridge,planning.cashReserveAnalysis, andplanning.budgetPacingProjection. Results show planning assumptions, cash-reserve coverage, and budget pacing status without any CSV upload, raw transaction storage, merchant/payee/account fields, household records, advisor/client notes, approvals, release state, persistence, or audit trail. Added pure validators for the three aggregate request shapes, raw-transaction-shaped key rejection, schema-v3 scenario round-trip support, preset coverage, and docs updates. Wire-contract count remains 21; UI tab count is now 19. Livenexusmcp.sitestill returned the older 23-tool list during this pass, so tests remain mocked/offline. -
Slice 3 cash-flow planning bridge contract/gateway support (2026-07-05). Added public-safe TypeScript request/result contracts,
PLANNING_TOOLSids, andplanning.*gateway methods for the three Nexus cash-flow bridge tools:cashflow_planning_bridge,cash_reserve_analysis, andbudget_pacing_projection. These are contract/gateway-only in this slice and consume derived monthly-close aggregate numbers only. Added offline dispatch tests, PII-tripwire coverage for the new methods, and contract tests that keep identity-shaped and raw-ingestion-shaped fields out of the public wire types. No UI, Monarch CSV upload, raw transaction processing, merchant/payee/account fields, household records, advisor/client notes, approvals, release state, persistence, or audit trail were added. The wire-contract count is now 21; the UI remains 18 tabs plus the separate Roth · IRMAA case contract.
- Slice 0 docs alignment (2026-07-05). Clarified the hybrid PW Cash Flow OS +
PW Planning Lab + PW Retirement Income Lab boundary:
pwplan-coreremains the public-safe UI/contract/demo shell, may only add synthetic Cash Flow OS / Planning Bridge references, and must not add Monarch CSV upload, raw transaction storage, household records, advisor/client workflow state, approvals, release state, or audit trails. Also fixed stale historical nexus-core tool-count wording inCURRENT-STATE.md.
- Audit hardening pass (2026-07-01). Scenario save/load now uses schema v2
and round-trips every current tool input (
roth_irmaa, optimize allocation, report builder, and the other post-MVP tabs included), whileloadSnapshotclears every result slot so newly loaded scenarios cannot display stale output. Roth · IRMAA validation now checks spouse birth year for all married filing statuses and bounds Medicare enrollment count to the filing household; the gateway now fails fast on unsupportedVITE_PLANNING_BACKENDvalues. Root docs were reconciled to the current Vite 8 / 18-wire-tool / 201-test state, and outstanding/future work is tracked in GitHub issues #15, #16, and #17.
- Defensive patent posture — application number filed + recorded.
NOTICEnow cites USPTO provisional #64/082,241 (PW-PROV-003, filed 2026-06-04 for the privacy-by-construction planning architecture — PII-free compute plane + two-deployment gateway), replacing the prior TODO placeholder. 12-month non-provisional / PCT conversion deadline 2027-06-04. Protocol Wealth's OIN 2.0 membership is executed. The-corepatent family is now three provisionals: pwos-core #64/034,215 + nexus-core #64/034,229 + pwplan-core #64/082,241.
-
Two new planning tools —
optimize_allocation+build_planning_report(wire contract 0.1.0, additive). Both already exist server-side in nexus-core; this is the consumer-side wiring.optimize_allocationreturns mean-variance optimal weights over the engine's real-data universe: a risk profile (conservative…aggressive) and/or explicit objective (max_sharpe, min_volatility, max_quadratic_utility, efficient_return, efficient_risk), optional asset-class subset, long-only weight bounds, and aregimeAwaretoggle that lets the live EMF regime select the objective; the result panel shows the per-asset weights (bar table), the expected return / volatility / Sharpe frontier point, and the regime + regimeNote.build_planning_reportassembles de-identified sections (kind+ optional title + findings) into an ordered report; the editor adds/removes sections and the panel renders the ordered sections, findings, and the engine's assumptions list. Adds the contract request/result types + twoPLANNING_TOOLSids, the twoplanning.*gateway methods, store inputs/result slots/setters, two structural validators (validateOptimizeAllocation+validateBuildPlanningReport: weight-bound and section-kind sanity, no quant logic), two UI tabs (OptimizeAllocationTool+BuildPlanningReportTool), and gateway + validation tests (+11). PII-free invariant held (no identity fields). No version bump (additive). 18 wire-contract tools, 17 UI tabs. -
Case contract v1.1.0 (additive). Mirror the nexus-core v1.1.0 bump in
src/contract/roth-conversion.ts(PLANNING_CASE_CONTRACT_VERSION1.0.0→1.1.0):accounts.employer_plan_aggregate(401k/403b → RMD-drag pool), the structuredYearAnalysis.aca(AcaInteraction), andDoNothingProjectionsurvivor + employer-plan fields. The Roth · IRMAA tab gains an Employer plan (401k/403b) input, and the do-nothing card now shows the surviving-spouse single-filing RMD rate (joint→single compression) + the employer-plan pool. Backward-compatible. -
Roth · IRMAA planner tab — the composite multi-year analysis (initial PlanningContract v1.0.0; current mirror is v1.1.0). A new tab that sizes a Roth conversion for a ~60-something retiree across multiple years when the binding constraint is IRMAA (Medicare surcharges), not the tax bracket. Consumes the new case contract (
src/contract/roth-conversion.ts, a UI-side mirror of@protocolwealthos/planning-contract/ the nexus-core JSON-Schema), versioned separately asPLANNING_CASE_CONTRACT_VERSION. Renders, per year: the recommended amount under fill-to-22% / fill-to-24% / just-under-the-IRMAA-tier (with a "crosses IRMAA" flag), the IRMAA cliff cost if crossed, the incremental federal + state tax, the NIIT and LTCG-stacking deltas, the breakeven rate, and the binding constraint; plus the multi-year roll-up and the do-nothing RMD-drag projection. The projected-IRMAA-with-buffer assumption is shown as a prominent fiduciary disclosure banner, not fine print. NewanalyzeRothConversiongateway method (PII-free, opaquecase_idgenerated at dispatch),validateRothIrmaavalidator, store slice, andRothIrmaaPlannerToolform + results. Components are contract-typed, so a breaking contract change is a compile error. Engine side in nexus-core (analyze_roth_conversion); the tab works once that deploys. +12 tests. -
FIRE, Risk-metrics, and Rebalance tabs — three more tools (historical milestone: 16 contract tools, 15 UI tabs at that point). Each adds a contract request/result pair, a
planning.*gateway method, a store input + result slot, a pure validator, and a UI tab. No version bump (additive tool ids). Engine side in nexus-core PR #117; the tabs work once that deploys. +16 tests (173 total).- FIRE (
planning.fire): FIRE number (spend ÷ SWR), the coast number needed today, projected balance at retirement, and years/age to financial independence with level contributions. - Risk metrics (
planning.riskMetrics): a return-series text input → annualized return/vol, Sharpe, Sortino, max drawdown, and historical VaR/CVaR (95%); reuses the sharedparseReturns. - Rebalance (
planning.rebalance): an editable target weight per shared asset class → per-asset drift + the self-financing buy/sell trade list + one-way turnover, over the same blended portfolio.
- FIRE (
-
Portfolio X-ray tab — regime-aware structural diagnostics (13th tool, 12th UI tab). A new tab that runs
planning.portfolioXrayover the shared Monte Carlo portfolio (asset classes + accounts; no separate inputs) and renders: the live regime, severity-coded findings (concentration, tax-location spread, growth posture, and the differentiator — regime sensitivity vs the live regime), plus portfolio metrics (weighted return, weighted-avg vol, portfolio λ, growth sleeve, concentration via Herfindahl/effective-holdings, account mix). ContractPortfolioXrayRequest/Result+XrayFinding+portfolioXrayinPLANNING_TOOLS/gateway; store result slot; purevalidatePortfolioXray(reusesisAllocationBalanced);scenario-iotolerance. +6 tests (157 total). No version bump (additive tool id). Engine side in nexus-core PR #116; the tab works once that deploys. -
Correlation matrix + Regime return generator tabs — the last two contract tools now have UI; all 12 tools are surfaced (11 tabs + the CMA control).
- Correlation (
planning.correlationMatrix): asset-class ids + lookback + Ledoit-Wolf shrinkage toggle → the real-data correlation matrix (rendered via the sharedMatrixTable) +asOf. - Regime paths (
planning.regimeReturnGenerator): runs over the shared Monte Carlo portfolio's asset classes (each needs a λ) → live current regime, the regime transition matrix, and thepathCacheKey(replayable in a Monte Carlo run to reuse the EMF paths — closing thepathCacheKeyloop). - Extracted
MatrixTable(a generic square-matrix renderer) fromScenarioForm's inline correlation table; reused by all three matrix views. - Store inputs/results/setters (+
PlanningToolunion → 11 UI tools), pure validators (validateCorrelation,validateRegimeGen,parseIdList),scenario-iotolerance, +11 tests (151 total). Both verified live. No version bump (these tools already existed in the contract/gateway).
- Correlation (
-
Four calculator tabs — RMD, Tax-bracket headroom / Roth-fill, Social Security claiming, and Regime-conditioned SWR — wired end to end (contract → gateway → UI). The contract + gateway now cover all 12 engine tools, and the tab bar exposes 9 (it wraps to a second row). Each is the established thin-shell pattern (pure request-shape validation, no quant):
- RMD (
planning.rmd): age + prior-year-end balance → RMD amount, distribution period, effective rate; "no RMD before 73" when it doesn't apply. - Tax-bracket headroom / Roth-fill (
planning.taxBracketHeadroom): income + filing status + target rate → marginal rate, room to the next bracket, room to fill the target rate. - Social Security claiming (
planning.socialSecurityClaiming): PIA + FRA → a benefit-by-claim-age bar chart (62–70) + breakeven ages. - Regime-conditioned SWR (
planning.regimeConditionedSwr): base rate + balance → the live regime (classified server-side), multiplier, adjusted rate, first-year withdrawal. - Contract types +
PLANNING_TOOLSentries (rmd/tax_bracket_headroom/social_security_claiming/regime_conditioned_swr), gateway methods, store inputs/results/setters (+PlanningToolunion), pure validators (validateRmd/validateBracketHeadroom/validateSocialSecurity/validateRegimeSwr), andscenario-iotolerance for the new tab pointers. - +16 tests (140 total). No version bump (additive tool ids, contract
0.1.0); the engine tools are live atnexusmcp.site(nexus-core #101).
- RMD (
-
Roth conversion + Sequence-of-returns-stress tools — wired end to end (contract → gateway → UI tabs). Two new planning tools surfaced as their own tabs:
- Roth conversion (
planning.rothConversion): convert-now vs. leave-pre-tax after-tax comparison. Form (income, filing status, conversion amount, growth, years, retirement marginal rate, pay-tax-from-conversion toggle) + a results panel showing the net benefit, the breakeven retirement rate, the incremental conversion tax + effective rate, and both terminal after-tax values. - Sequence risk (
planning.sequenceOfReturnsStress): replays one return set worst-first / best-first / as-entered. Form (initial balance, constant annual spend, comma-separated returns) + a panel showing the sequence-risk gap and each ordering's terminal balance + depletion year. RothConversionRequest/Result+SequenceOfReturnsStressRequest/Result(+SequenceOutcome) added to the contract;rothConversion+sequenceOfReturnsStressinPLANNING_TOOLS(8 tools) and the gateway.- Pure
validateRoth/validateSequenceStress/parseReturnsintool-validation.ts; store gainedrothInputs/sorInputs(+ results);scenario-iotolerates the new active-tab pointer. +13 tests (124 total). - No version bump (additive tool ids, contract
0.1.0). These call the matching nexus-core tools, which go live when nexus-core PR #100 merges and deploys.
- Roth conversion (
-
"Real data, fake clients" UI — a Load real market assumptions control on the Monte Carlo form (
ScenarioForm). It callsplanning.capitalMarketAssumptionsfor the current asset-class ids (or the engine's full default universe when there are none), replaces each asset class'sexpectedReturn/volatility/λin place — same ids, so account allocations stay valid — and stashes the engine's correlation matrix. The matrix then rides into the next simulation asMonteCarloRequest.correlations; a provenance line shows the assumptions'asOfdate, and a compact read-only correlation matrix is rendered beneath the control. Verified end-to-end against the live engine: CMA (asOf 2026-05-29) → Monte Carlo accepts the drop-inassetClasses+correlations(HTTP 200, regime-aware result). Store gained an ephemeralassumptionsslice ({ asOf, correlations }+loadingAssumptions), deliberately outsideScenarioInputs(live data, re-fetched — cleared on snapshot load) so scenario save/load is untouched. No new wire types; no quant logic. -
Capital-market-assumptions tool +
pathCacheKey— the client side of the two0.1.0additions, landing the contract at parity with the now-live 6-tool nexus-core engine (additive, no version bump — invariant #5; the engine serves0.1.0andContractMismatchErroris an exact-match check). The live smoke test (scripts/smoke-nexus.mjs) and a direct probe ofnexusmcp.site/mcp/toolsconfirmed all six tools atcontractVersion 0.1.0before any client code changed (nexus-core #82–#84). Specifically:CapitalMarketAssumptionsRequest/CapitalMarketAssumptionsResultinsrc/contract/planning.ts(§3.6) — source real asset-class returns, vols, λ and correlations from the engine; the result'sassetClasses+correlationsare drop-in for aMonteCarloRequest(the "real data, fake clients" flow).capitalMarketAssumptions: "capital_market_assumptions"added toPLANNING_TOOLS(the 6th tool).- Optional
pathCacheKey?: stringonMonteCarloRequest(§3.1) — replay aregime_return_generatorcache key so the engine reuses its EMF paths; a stale/unknown key is a cache miss (regenerate), never an error. planning.capitalMarketAssumptions(req, opts?)gateway method, samecallTooldispatch + PII tripwire + audit seam as the other five.planning-gateway.test.ts: the 6th tool added to the tool-id mapping test, plus a CMA dispatch test (asserts the drop-inassetClasses/correlations/asOfround-trip) and apathCacheKeypassthrough test. +2 tests (111 total).
-
Editable Retirement age input on the Monte Carlo form (
ScenarioForm), placed between Current age and Horizon age, so the value sent to the engine is now visible and adjustable rather than only seeded.validateScenariogained a request-shape age-ordering check —currentAge ≤ retirementAge < horizonAge, matching the glide-path tool's rules and messages (skipped when ages are absent, e.g. allocation-only callers). +5 validation tests. -
Optional
retirementAge?: numberonMonteCarloRequest(contract0.1.0, additive — no version bump; the engine is pre-first-release and already accepts and uses it, per nexus-core #83). It marks the age decumulation begins; the engine draws fromcurrentAgewhen omitted, which sinks the out-of-box demo (8%-spend-from-age-45 → ~0 success, all-zero terminals). The browser UI seeds65:ScenarioFormnow threads the store'sinputs.retirementAgeinto the request,scripts/smoke-nexus.mjssends it, andplanning-gateway.test.tsasserts it reaches the wire. (ScenarioInputs, the presets, andscenario-ioalready carried the field; only the wire contract and the request builder were missing it.) -
NEXT-PROMPT.md— committed session hand-off: the copy-paste prompt to resume the contract-additive work (add the0.1.0pathCacheKey+capital_market_assumptionsclient types) once nexus-core's 6-tool MCP server is live at0.1.0. Its step 1 verifies the engine viascripts/smoke-nexus.mjsbefore any coding. -
SPDX Apache-2.0 license headers on every first-party source file (
// SPDX-License-Identifier: Apache-2.0+ copyright), matching the rest of the-corefamily. Covers allsrc/**TS/TSX, the one CSS file,scripts/*.mjs(after the shebang), and the rootvite.config.ts/eslint.config.js. TheNOTICEpatent application number remains a<PATENT_APP_NO>placeholder.
- vitest
3.2.4→4.1.8— clears two critical Dependabot alerts (GHSA: the Vitest UI server can read/execute arbitrary files when listening; patched in4.1.0). Dev-only dependency and the repo runsvitest run(never--ui), so real exposure was low, but the bump is clean: major 3→4 with no config changes (notestblock invite.config.ts), all 124 tests pass,npm auditreports 0 vulnerabilities.
docs/nexus-core-requirements.md: pinned the two former "gaps" into contract0.1.0(now 6 tools) rather than a future bump — thecapital_market_assumptionstool (§3.6, the "real data, fake clients" capability) and the optionalpathCacheKeyfield onmonte_carlo_decumulation(§3.1). The engine is pre-first-release, so amending0.1.0is non-breaking, and a bump would break the client's exact-match version check against an engine shipping0.1.0. The contract types themselves (src/contract/planning.ts) are unchanged in this commit — they will be added in a follow-up so client and engine land at0.1.0together; the doc is the agreed spec for the nexus-core build.- Accessibility contrast: every
text-stone-400body/micro-text span (footers, empty states, table headers, captions, the remove-button glyph) raised totext-stone-500(≈4.6:1 on white) across 9 components, clearing WCAG AA for normal text and completing the accessibility-pass ROADMAP item. - Repositioned as demo / case-study tooling. This OSS repo now runs against
the public nexus-core MCP engine (
https://nexusmcp.siteby default — a fresh clone needs no.env) with de-identified / fake client data, and no longer carries the production compliance stack.src/lib/compliance.tsrewritten:assertNoPIIis now a small, always-on, dependency-free structural tripwire (findIdentityKeywalks the payload and throws on any identity-shaped key) instead of a stub gated behindVITE_COMPLIANCE_NOOP.auditCallis an explicit no-op seam (writes nothing). Addedcompliance.test.ts(9 tests).- Removed the
@protocolwealthos/pii-guard+@protocolwealthos/audit-logpeer deps, theVITE_COMPLIANCE_NOOPflag, and thecompliance-presentCI job (CI is now 7 jobs). Thepw-apigateway seam andauditCallseam are kept so the private production fork stays a low-diff sync. - Default
VITE_PLANNING_GATEWAY_URLis nowhttps://nexusmcp.site. - Docs (README, CONTRIBUTING, CLAUDE.md, NOTICE) reframed: production PII de-identification, audit logging, and pw-api integration are out of scope here and live only in a private fork integrating pwos-core. Invariant #4 reworded from "fail-closed compliance tripwire" to "structural, always-on, dep-free PII tripwire".
- Scenario save / load and built-in case-study presets, so demos can show
variations instantly.
scenario-io.ts— a pure, versioned, PII-free JSON envelope for plan inputs (Monte Carlo + glide-path + tax).assertNoPIIruns fail-closed on both serialize and load: a file that smuggled an identity-shaped key is refused with an error (checked on the raw input before field-whitelisting could silently drop it), never loaded. No browser storage (CLAUDE.md). 13 tests.scenario-presets.ts— three case studies (accumulator age 35, near-retiree age 62, crisis-stress age 70 with RMDs), each a full snapshot with allocations summing to 1. Demo numbers only, not PW capital-market assumptions. 15 tests assert each passes the real scenario / glide / tax validators and round-trips through serialize/parse. (Suite now 104.)ScenarioIO.tsx— Save (Blob download), Load (file input through the fail-closed parser), and a preset picker; store gainsloadSnapshot.
- Accessibility pass: keyboard focus-visible rings restored across inputs,
buttons, tabs, and the save/load/preset controls (WCAG 2.4.7);
aria-currentnow emits"page"/omitted instead of"false";index.htmlhead deduped and given a real meta description. (Contrast tuning ofstone-400micro-text is a tracked follow-up.) docs/nexus-core-requirements.md— consumer-side spec for the nexus-core MCP server (the 5 planning tools' request/response shapes, shared enums/objects, transport + CORS + determinism rules, the "real data, fake clients" demo capability, and the additive contract gaps to coordinate:pathCacheKey, a capital-market-assumptions endpoint). Derived from the v0.1.0 contract and the gateway/store/results consumers; handed to nexus-core to drive the server build.src/lib/planning-gateway.test.ts— offline integration test for the gateway dispatch path (fetch mocked; no network). Covers the PiiTripwireError (fail-closed before dispatch) and ContractMismatchError (version drift) paths, tool-id → path mapping for all 5 tools, contract-version injection, the audit / contract-version / subjectRef headers, verbatim non-OK error surfacing, and thepw-apibackend seam. 9 tests (suite now 76).scripts/smoke-nexus.mjs— opt-in live round-trip againstnexusmcp.siteusing the PII-free default scenario; validates the response carries the UI's load-bearing fields. Not in the gate suite and never run in CI (the public engine must not gate this repo).- Glide-path and tax-withdrawal tools, wiring the existing
planning.glidePath/planning.taxWithdrawalgateway methods to the UI. A tab bar inApp.tsxswitches between Monte Carlo, Glide path, and Tax withdrawal; each tool keeps its own input + result slot in the store, and accounts are a single shared portfolio (the contract sharesAccount[]).GlidePathTool.tsx— form (ages, start/end equity weight, shape) + an equity-weight-by-age line chart on a fixed 0–1 axis.TaxWithdrawalTool.tsx— form (year, age, gross need, other income, filing status) over the shared portfolio + a withdrawals-by-account table with total tax, effective rate, and an RMD-satisfied indicator.tool-validation.ts— pure request-shape validation (validateGlidePath,validateTaxWithdrawal); no quant logic. 10 unit tests.results-viz.tsgainsageWeightSeries(sort age→weight map) and aforcedMaxoption onseriesGeometry(fixed-top Y domain). 5 unit tests.
- Shared, extracted presentational modules to keep the three tools DRY:
format.ts(currency/pct),form-controls.tsx(Field/NumberInput/Select/ Card/IssueList/RunButton/…),charts.tsx(ChartHeading+ genericLineChart),result-shell.tsx(error/running/empty framing).ScenarioFormandResultsPanelwere refactored onto these with no behavior change. - Results visualization in
ResultsPanel: a median-balance-by-year line/area chart (age-labelled, peak annotated), a terminal-value percentile bar chart (worst-path footnote), and a regime-path strip with a legend when the engine returnsregimePathSummary. All hand-rolled inline SVG/CSS — no chart library; the whole Unreleased block keeps the bundle lean (~235 kB / ~72 kB gzip, no new deps). Charts carryrole="img"+aria-label/titlefor screen readers. results-viz.ts— pure, dependency-free geometry helpers (seriesGeometry,percentileBars,regimeRuns) that map an engine result to SVG/CSS coordinates. Presentation math only; no quant logic (thin-shell intact).results-viz.test.ts— 15 unit tests (SVG y-inversion, 0-based vs negative domain, single-point centering, flat-series safety, percentile ordering / normalization / zero + decimal-key + key-filtering, regime run-collapsing / single-regime / length).- ScenarioForm now collects the full
MonteCarloRequestshape: asset classes (id, label, expected return, volatility, λ), accounts (taxable / traditional / roth) with per-asset-class allocation weights, guaranteed income (Social Security / pension, amount, start age, COLA), and filing status. scenario-validation.ts— pure, UI-side request-shape validation (validateScenario,isAllocationBalanced,allocationSum,duplicateAssetClassIds). Enforces "allocation weights sum to 1 per account" (within a 1e-6 float tolerance), non-empty asset classes/accounts, unique non-blank asset-class ids, and no allocation to an unknown asset class. The Run button is disabled with inline reasons until the scenario is valid. This is form-shape validation only — no quant logic (thin-shell invariant intact).scenario-validation.test.ts— 15 unit tests, including float-tolerance band boundaries (just inside / just outside, both sides) and the zero-weight stale-key exemption.store/scenario.tsseedsDEFAULT_INPUTSwith a valid, balanced two-account / two-asset-class scenario so the form is runnable out of the box.
- Asset-class id whitespace mismatch:
updateAssetClassnow stores and re-keys allocations with the trimmed id, matching how validation normalizes ids. A padded id (e.g." us_equity ") no longer produces a spurious "unknown asset class" error that left the Run button stuck disabled. (Found via adversarial multi-agent review of this change.)
- Initial open-source scaffold: thin planning UI (React 19, Vite 6, Tailwind v4, TypeScript, Zustand).
- Planning wire contract v0.1.0 with five engine tools; PII-free by construction, enforced by tests.
- Backend-agnostic planning gateway targeting
nexus-mcp(open) orpw-api(private);ContractMismatchErroron version drift; opaquesubjectRefcorrelation header. - Compliance as a fail-closed PII tripwire (
assertNoPII) plus an audit-log hook; pwos-core packages declared as optional peer deps. - CI with 8 jobs; Apache-2.0 LICENSE and NOTICE (defensive patent posture; OIN membership).
- Governance and memory files: CLAUDE.md, CURRENT-STATE.md, ROADMAP.md.