\n ${model.label}${model.message}
\n Next action${model.nextAction}
\n \n`;
+}
diff --git a/apps/hr-workspace/work-capacity-review-state.stories.js b/apps/hr-workspace/work-capacity-review-state.stories.js
new file mode 100644
index 000000000..4db8f6802
--- /dev/null
+++ b/apps/hr-workspace/work-capacity-review-state.stories.js
@@ -0,0 +1,26 @@
+import { workCapacityReviewStateMarkup } from './work-capacity-review-state.js';
+import './work-capacity-review-state.css';
+
+export default {
+ title: 'HR Workspace/Employment Work Capacity Review States',
+ parameters: {
+ design: {
+ type: 'figma',
+ url: 'Orgmetra Baseline — Storybook Inventory node 1:64',
+ },
+ },
+};
+
+function story(state) {
+ return () => workCapacityReviewStateMarkup(state);
+}
+
+export const Idle = story('idle');
+export const Loading = story('loading');
+export const HighRiskHumanReview = story('review');
+export const Recording = story('recording');
+export const RecordedReadOnly = story('recorded');
+export const PermissionDenied = story('denied');
+export const StaleEvidence = story('stale');
+export const AuthoritativeScopeBlocked = story('blocked');
+export const Error = story('error');
\ No newline at end of file
diff --git a/docs/doctoring/hr-workspace-work-capacity-review-accessibility-references.md b/docs/doctoring/hr-workspace-work-capacity-review-accessibility-references.md
new file mode 100644
index 000000000..33789b1c9
--- /dev/null
+++ b/docs/doctoring/hr-workspace-work-capacity-review-accessibility-references.md
@@ -0,0 +1,22 @@
+# HR Workspace Employment work-capacity review accessibility references
+
+Status: **active PR research/doctoring**. These references support the interaction semantics; they are not a claim of product certification, protected-`develop` availability, or compliance attestation.
+
+## Applied decisions
+
+- Use native button semantics and expose `aria-busy`, status/live-region, and alert semantics without turning the review surface into a custom application widget.
+- Keep a proposed contracted work-capacity change behind an explicit high-risk human-confirmation state. UI confirmation records review evidence only; it does not mutate Employment truth or authorize compensation, scheduling, leave, payroll, or an employment decision.
+- Disable duplicate submission while protected evidence is loading or immutable review evidence is being recorded.
+- Treat stale Employment/terms/capacity-policy evidence and authoritative-scope inconsistency as fail-closed validation states with explicit next actions.
+- Preserve a visible keyboard focus indicator and a minimum 44 CSS-pixel action height in the workflow-specific proof.
+- Keep Storybook proof data value-minimized: no worker identifiers/contact data, capacity ratios, compensation/payroll values, leave reasons, ratings/assessment values, credentials/tokens, prompts, or model output.
+
+## Primary final standards
+
+World Wide Web Consortium. (2023, October 5). *Web Content Accessibility Guidelines (WCAG) 2.2* (W3C Recommendation). https://www.w3.org/TR/WCAG22/
+
+World Wide Web Consortium. (2023, June 6). *Accessible Rich Internet Applications (WAI-ARIA) 1.2* (W3C Recommendation). https://www.w3.org/TR/wai-aria-1.2/
+
+## Current-status note
+
+WCAG 2.2 remains the completed W3C Recommendation used as this slice's normative accessibility baseline. W3C continued editorial-errata work for WCAG 2.2 in 2026 without republishing the Recommendation, so this branch does not treat draft or proposed corrections as a replacement final standard. WAI-ARIA 1.2 remains the completed Recommendation used here for roles, states, and properties. This slice claims neither WCAG/ARIA conformance certification nor broader product accessibility certification.
\ No newline at end of file
diff --git a/docs/traceability/hr-workspace-work-capacity-review-state.md b/docs/traceability/hr-workspace-work-capacity-review-state.md
new file mode 100644
index 000000000..a26ab4d73
--- /dev/null
+++ b/docs/traceability/hr-workspace-work-capacity-review-state.md
@@ -0,0 +1,40 @@
+# HR Workspace Employment work-capacity review state traceability
+
+Status: **active stacked PR**. This document records the presentation/interaction contract owned by this branch. It does not claim protected-`develop` availability, accessibility certification, or authority to mutate Employment truth.
+
+## Ownership boundary
+
+- Parent presentation contract: #130 `feat/hr-workspace-protected-read-state@b3b30058a79174000919d566fbbb1fdad80c62bf`.
+- HR Workspace product parent: #53.
+- Governed Employment work-capacity review evidence: #103. This branch does not import or duplicate that unmerged backend implementation.
+- Durable Employment work-capacity persistence: #128. This branch does not write its tables or bypass its future authoritative mutation boundary.
+- Employment leave/separation workflows remain separate governed concepts; this UI must not infer leave, scheduling, payroll, compensation, or employment-decision authority from a work-capacity review.
+- Figma correlation: `Orgmetra Baseline`, Storybook Inventory node `1:64`, freshly read on 2026-08-28. The node requires `default / hover / focus / disabled / loading / validation-error / read-only / high-risk-confirmation` states and an exact-value table alongside chart evidence where charts exist. This slice uses only the interaction-state inventory; it does not invent a parallel design system.
+
+## State-to-governance mapping
+
+| UI state | Interaction proof | Governance meaning | Required next action |
+| --- | --- | --- | --- |
+| `idle` | default | No governed evidence has been loaded. | Load fresh Employment, terms, and capacity-policy evidence. |
+| `loading` | loading + disabled | Protected evidence resolution is in progress. Duplicate action is blocked. | Wait for the governed load to complete. |
+| `review` | high-risk confirmation | Human review only. No Employment, compensation, scheduling, leave, or employment-decision mutation authority. | Confirm the reviewed scope and evidence before recording the review. |
+| `recording` | loading + disabled | Immutable review-evidence recording is in progress; duplicate submission is blocked. | Wait for recording to complete. |
+| `recorded` | read-only | Review evidence exists; no work-capacity change has been applied. | Continue only through a separately authorized authoritative work-capacity boundary after fresh validation. |
+| `denied` | permission denied | Purpose or reviewer authority is insufficient. | Correct the purpose/authority before retrying. |
+| `stale` | validation error | Employment, terms, capacity-policy, or reviewed evidence changed. | Reload authoritative evidence before reviewing again. |
+| `blocked` | validation error | Current capacity, effective date, Employment status, or reviewed policy evidence is inconsistent. | Resolve authoritative scope conflicts before retrying. |
+| `error` | error | Governed evidence/review service did not return a usable result; cached evidence is not accepted. | Verify service and authorization, then retry the governed load. |
+
+## Privacy and integrity contract
+
+The Storybook proof is deliberately value-minimized. Its immutable view-model vocabulary contains only presentation semantics (`ariaBusy`, `ariaLive`, `role`, `submitDisabled`, `interactionState`, `actionLabel`, `label`, `message`, `nextAction`). It must not carry Person/Employment/Assignment identifiers, worker names/contact data, current or proposed capacity ratios, compensation/payroll values, leave reasons, ratings/assessment scores, credentials/tokens, prompts, or model output.
+
+The state input is an exact built-in string drawn from a finite vocabulary. Caller-defined boxed/string-like runtime objects fail closed before rendering. Static proof markup is correlated to Figma node `1:64`, preserves visible focus semantics, exposes busy/live-region state, uses native button semantics, and enforces a minimum 44 CSS-pixel action height.
+
+## Verification
+
+The dedicated `HR Workspace Work Capacity Review State Quality` workflow is intentionally scoped to this presentation slice and its documentation. It checks out the exact PR head, runs Node.js 24 tests with exact 100% line/branch/function coverage, and requires a clean checkout. This focused evidence is **stack-local** only. It does not inherit #53/#130 or #103/#128 reviews/checks and does not substitute for browser/accessibility/Foundation/Recovery/SAST/Security/central required workflows after integration.
+
+## Integration order
+
+Process #53 -> #130 first. After #130 integrates, retarget this child to fresh `develop`, reconcile any parent changes without transferring predecessor evidence, and rerun every applicable exact-head browser/accessibility/Foundation/Recovery/SAST/Security/central gate. High-impact UI confirmation remains review evidence only until an authoritative backend independently revalidates tenant/scope, actor separation, effective time, policy/evidence freshness, and immutable audit/outbox requirements.
\ No newline at end of file
diff --git a/tests/hr-workspace-work-capacity-review-state.test.mjs b/tests/hr-workspace-work-capacity-review-state.test.mjs
new file mode 100644
index 000000000..2ae4c1f09
--- /dev/null
+++ b/tests/hr-workspace-work-capacity-review-state.test.mjs
@@ -0,0 +1,150 @@
+import assert from 'node:assert/strict';
+import { readFileSync } from 'node:fs';
+import test from 'node:test';
+import {
+ workCapacityReviewStateMarkup,
+ workCapacityReviewViewModel,
+} from '../apps/hr-workspace/work-capacity-review-state.js';
+
+const story = readFileSync(
+ new URL('../apps/hr-workspace/work-capacity-review-state.stories.js', import.meta.url),
+ 'utf8',
+);
+const css = readFileSync(
+ new URL('../apps/hr-workspace/work-capacity-review-state.css', import.meta.url),
+ 'utf8',
+);
+const workflow = readFileSync(
+ new URL('../.github/workflows/hr-workspace-work-capacity-review-state.yml', import.meta.url),
+ 'utf8',
+);
+
+const expectedStates = {
+ idle: ['false', 'status', false, 'default', 'Review work-capacity evidence'],
+ loading: ['true', 'status', true, 'loading', 'Loading current work-capacity evidence'],
+ review: ['false', 'status', false, 'high-risk-confirmation', 'Work-capacity change requires human confirmation'],
+ recording: ['true', 'status', true, 'loading', 'Recording work-capacity review'],
+ recorded: ['false', 'status', true, 'read-only', 'Work-capacity review recorded'],
+ denied: ['false', 'alert', false, 'permission-denied', 'Work-capacity review access denied'],
+ stale: ['false', 'alert', false, 'validation-error', 'Work-capacity evidence is stale'],
+ blocked: ['false', 'alert', false, 'validation-error', 'Work-capacity review is blocked by authoritative scope'],
+ error: ['false', 'alert', false, 'error', 'Work-capacity review unavailable'],
+};
+
+const allowedViewModelKeys = [
+ 'actionLabel',
+ 'ariaBusy',
+ 'ariaLive',
+ 'interactionState',
+ 'label',
+ 'message',
+ 'nextAction',
+ 'role',
+ 'submitDisabled',
+];
+
+test('work-capacity review states are bounded, actionable, and value-minimized', () => {
+ for (const [state, [ariaBusy, role, submitDisabled, interactionState, label]] of Object.entries(expectedStates)) {
+ const model = workCapacityReviewViewModel(state);
+ assert.equal(model.ariaBusy, ariaBusy);
+ assert.equal(model.role, role);
+ assert.equal(model.submitDisabled, submitDisabled);
+ assert.equal(model.interactionState, interactionState);
+ assert.equal(model.label, label);
+ assert.equal(model.ariaLive, role === 'alert' ? 'assertive' : 'polite');
+ assert.match(model.nextAction, /\.$/);
+ assert.deepEqual(Object.keys(model).sort(), allowedViewModelKeys);
+
+ for (const forbiddenKey of [
+ 'personRecordId',
+ 'employmentRecordId',
+ 'assignmentRecordId',
+ 'workerName',
+ 'email',
+ 'phone',
+ 'currentCapacityRatio',
+ 'proposedCapacityRatio',
+ 'compensationValue',
+ 'payrollValue',
+ 'leaveReason',
+ 'ratingValue',
+ 'assessmentScore',
+ 'credential',
+ 'token',
+ 'prompt',
+ 'modelOutput',
+ ]) {
+ assert.equal(Object.hasOwn(model, forbiddenKey), false);
+ }
+
+ const markup = workCapacityReviewStateMarkup(state);
+ assert.match(markup, /data-figma-node-id="1:64"/);
+ assert.match(markup, new RegExp(`data-interaction-state="${interactionState}"`));
+ assert.match(markup, new RegExp(`aria-busy="${ariaBusy}"`));
+ assert.match(markup, /Next action/);
+ if (submitDisabled) assert.match(markup, /