From 57338f8d96b6d1f060a40ed2080284056bbe4de2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 15:05:20 -0700 Subject: [PATCH 1/9] test(ui): pin Employment work-capacity review states --- ...kspace-work-capacity-review-state.test.mjs | 132 ++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 tests/hr-workspace-work-capacity-review-state.test.mjs 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..d0cf2e44d --- /dev/null +++ b/tests/hr-workspace-work-capacity-review-state.test.mjs @@ -0,0 +1,132 @@ +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 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, /]* disabled/); + else assert.doesNotMatch(markup, /]* disabled/); + } +}); + +test('human review never implies Employment, compensation, scheduling, or decision authority', () => { + const review = workCapacityReviewViewModel('review'); + assert.match(review.message, /human review/i); + assert.match(review.message, /does not change contracted work capacity/i); + assert.match(review.message, /does not authorize compensation, scheduling, leave, or an employment decision/i); + + const recorded = workCapacityReviewViewModel('recorded'); + assert.equal(recorded.submitDisabled, true); + assert.match(recorded.message, /evidence only/i); + assert.match(recorded.message, /does not apply the work-capacity change/i); + assert.match(recorded.nextAction, /authoritative work-capacity boundary/i); +}); + +test('denial, stale evidence, authoritative-scope conflict, and failure explain the next safe action', () => { + assert.match(workCapacityReviewViewModel('denied').nextAction, /access purpose and reviewer authority/i); + assert.match(workCapacityReviewViewModel('stale').nextAction, /Reload authoritative Employment, terms, and capacity-policy evidence/i); + assert.match(workCapacityReviewViewModel('blocked').nextAction, /current capacity, effective date, Employment status, and reviewed policy evidence/i); + assert.match(workCapacityReviewViewModel('error').nextAction, /Do not rely on cached work-capacity evidence/i); +}); + +test('unsupported runtime input fails closed before rendering', () => { + assert.throws(() => workCapacityReviewViewModel('approved'), /unsupported work-capacity review state/); + assert.throws(() => workCapacityReviewViewModel(new String('review')), /exact built-in string/); + assert.throws(() => workCapacityReviewStateMarkup(Symbol('recorded')), /exact built-in string/); +}); + +test('Storybook and CSS cover high-risk work-capacity review accessibility states', () => { + for (const storyName of [ + 'Idle', + 'Loading', + 'HighRiskHumanReview', + 'Recording', + 'RecordedReadOnly', + 'PermissionDenied', + 'StaleEvidence', + 'AuthoritativeScopeBlocked', + 'Error', + ]) { + assert.match(story, new RegExp(`export const ${storyName}`)); + } + assert.match(story, /workCapacityReviewStateMarkup/); + assert.match(css, /var\(--orgmetra-focus-ring\)/); + assert.match(css, /:focus-visible/); + assert.match(css, /\[aria-busy="true"\]/); + assert.match(css, /high-risk-confirmation/); + assert.match(css, /min-height:\s*44px/); +}); \ No newline at end of file From 92d3ff34086c54c6008bb96260ccfdc03eb3b345 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 15:05:29 -0700 Subject: [PATCH 2/9] ci(ui): require work-capacity review state evidence --- ...r-workspace-work-capacity-review-state.yml | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 .github/workflows/hr-workspace-work-capacity-review-state.yml diff --git a/.github/workflows/hr-workspace-work-capacity-review-state.yml b/.github/workflows/hr-workspace-work-capacity-review-state.yml new file mode 100644 index 000000000..aad217bc2 --- /dev/null +++ b/.github/workflows/hr-workspace-work-capacity-review-state.yml @@ -0,0 +1,54 @@ +name: HR Workspace Work Capacity Review State Quality + +on: + pull_request: + branches: + - feat/hr-workspace-protected-read-state + paths: + - "apps/hr-workspace/work-capacity-review-state.js" + - "apps/hr-workspace/work-capacity-review-state.css" + - "apps/hr-workspace/work-capacity-review-state.stories.js" + - "tests/hr-workspace-work-capacity-review-state.test.mjs" + - "docs/traceability/hr-workspace-work-capacity-review-state.md" + - "docs/doctoring/hr-workspace-work-capacity-review-accessibility-references.md" + - ".github/workflows/hr-workspace-work-capacity-review-state.yml" + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: hr-workspace-work-capacity-review-state-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + work-capacity-review-state: + name: Work-capacity review state contract + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout exact candidate + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + persist-credentials: false + - name: Prove exact candidate checkout + env: + ORGMETRA_EXPECTED_HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} + run: test "$(git rev-parse HEAD)" = "$ORGMETRA_EXPECTED_HEAD_SHA" + - name: Set up Node.js LTS + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: "24" + check-latest: false + - name: Run work-capacity review accessibility contract with exact coverage + run: >- + node --test --experimental-test-coverage + --test-coverage-lines=100 + --test-coverage-branches=100 + --test-coverage-functions=100 + tests/hr-workspace-work-capacity-review-state.test.mjs + - name: Require clean checkout + run: | + git diff --exit-code + test -z "$(git status --porcelain)" \ No newline at end of file From e49d9b2c58cb33cedf8f5d0c434dae7d247aa065 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 15:06:15 -0700 Subject: [PATCH 3/9] feat(ui): implement work-capacity review states --- .../work-capacity-review-state.js | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 apps/hr-workspace/work-capacity-review-state.js diff --git a/apps/hr-workspace/work-capacity-review-state.js b/apps/hr-workspace/work-capacity-review-state.js new file mode 100644 index 000000000..0c49228f3 --- /dev/null +++ b/apps/hr-workspace/work-capacity-review-state.js @@ -0,0 +1,86 @@ +const STATE_MODELS = Object.freeze({ + idle: Object.freeze({ + ariaBusy: 'false', ariaLive: 'polite', role: 'status', submitDisabled: false, + interactionState: 'default', actionLabel: 'Load work-capacity evidence', + label: 'Review work-capacity evidence', + message: 'Load fresh Employment, terms, and capacity-policy evidence before making a human review of a proposed contracted work-capacity change.', + nextAction: 'Load the current governed work-capacity evidence for the affected Employment.', + }), + loading: Object.freeze({ + ariaBusy: 'true', ariaLive: 'polite', role: 'status', submitDisabled: true, + interactionState: 'loading', actionLabel: 'Loading work-capacity evidence', + label: 'Loading current work-capacity evidence', + message: 'Orgmetra is resolving fresh Employment, terms, and capacity-policy evidence. No cached capacity evidence is accepted.', + nextAction: 'Wait for the current work-capacity evidence load to finish.', + }), + review: Object.freeze({ + ariaBusy: 'false', ariaLive: 'polite', role: 'status', submitDisabled: false, + interactionState: 'high-risk-confirmation', actionLabel: 'Confirm human review', + label: 'Work-capacity change requires human confirmation', + message: 'This is read-only evidence for human review. It does not change contracted work capacity and does not authorize compensation, scheduling, leave, or an employment decision.', + nextAction: 'Confirm the reviewed Employment, current capacity, proposed effective date, terms, and capacity-policy evidence before recording the human review.', + }), + recording: Object.freeze({ + ariaBusy: 'true', ariaLive: 'polite', role: 'status', submitDisabled: true, + interactionState: 'loading', actionLabel: 'Recording human review', + label: 'Recording work-capacity review', + message: 'Orgmetra is recording human review evidence for the proposed contracted work-capacity change. Duplicate submission is disabled.', + nextAction: 'Wait for immutable work-capacity review evidence to be recorded.', + }), + recorded: Object.freeze({ + ariaBusy: 'false', ariaLive: 'polite', role: 'status', submitDisabled: true, + interactionState: 'read-only', actionLabel: 'Review recorded', + label: 'Work-capacity review recorded', + message: 'The recorded review is evidence only and does not apply the work-capacity change or authorize compensation, scheduling, leave, or an employment decision.', + nextAction: 'Continue only through the separately authorized authoritative work-capacity boundary after fresh Employment and policy validation.', + }), + denied: Object.freeze({ + ariaBusy: 'false', ariaLive: 'assertive', role: 'alert', submitDisabled: false, + interactionState: 'permission-denied', actionLabel: 'Review access', + label: 'Work-capacity review access denied', + message: 'The current purpose or reviewer authority does not permit this work-capacity review.', + nextAction: 'Check the access purpose and reviewer authority before trying again.', + }), + stale: Object.freeze({ + ariaBusy: 'false', ariaLive: 'assertive', role: 'alert', submitDisabled: false, + interactionState: 'validation-error', actionLabel: 'Reload evidence', + label: 'Work-capacity evidence is stale', + message: 'The Employment, terms, capacity policy, or reviewed work-capacity evidence changed before review recording.', + nextAction: 'Reload authoritative Employment, terms, and capacity-policy evidence before reviewing again.', + }), + blocked: Object.freeze({ + ariaBusy: 'false', ariaLive: 'assertive', role: 'alert', submitDisabled: false, + interactionState: 'validation-error', actionLabel: 'Review authoritative scope', + label: 'Work-capacity review is blocked by authoritative scope', + message: 'The proposal cannot proceed while current capacity, effective-date, Employment-status, or reviewed-policy evidence is inconsistent.', + nextAction: 'Resolve the current capacity, effective date, Employment status, and reviewed policy evidence before reviewing the change again.', + }), + error: Object.freeze({ + ariaBusy: 'false', ariaLive: 'assertive', role: 'alert', submitDisabled: false, + interactionState: 'error', actionLabel: 'Retry governed load', + label: 'Work-capacity review unavailable', + message: 'The governed work-capacity evidence or immutable review service did not return a usable result. No cached work-capacity evidence is accepted.', + nextAction: 'Do not rely on cached work-capacity evidence; verify the service and authorization before retrying.', + }), +}); + +function requireExactState(value) { + if (typeof value !== 'string') { + throw new TypeError('work-capacity review state must be an exact built-in string'); + } + const model = STATE_MODELS[value]; + if (!model) throw new TypeError(`unsupported work-capacity review state: ${value}`); + return model; +} + +/** Return immutable, value-minimized accessibility semantics for one Employment work-capacity review state. */ +export function workCapacityReviewViewModel(state) { + return requireExactState(state); +} + +/** Render one static Storybook proof without accepting caller-controlled worker, capacity, or HR values. */ +export function workCapacityReviewStateMarkup(state) { + const model = requireExactState(state); + const disabled = model.submitDisabled ? ' disabled' : ''; + return `
\n

${model.label}${model.message}

\n

Next action${model.nextAction}

\n \n
`; +} \ No newline at end of file From 44297bfe33276b1c0fa14341862330a9108b3026 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 15:06:24 -0700 Subject: [PATCH 4/9] feat(ui): add work-capacity Storybook evidence --- .../work-capacity-review-state.stories.js | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 apps/hr-workspace/work-capacity-review-state.stories.js 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 From 5a14c98b03683ae7b421bd433642fd47420436da Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 15:06:34 -0700 Subject: [PATCH 5/9] feat(ui): style work-capacity review states --- .../work-capacity-review-state.css | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 apps/hr-workspace/work-capacity-review-state.css diff --git a/apps/hr-workspace/work-capacity-review-state.css b/apps/hr-workspace/work-capacity-review-state.css new file mode 100644 index 000000000..5bf22fb02 --- /dev/null +++ b/apps/hr-workspace/work-capacity-review-state.css @@ -0,0 +1,58 @@ +.work-capacity-review-state { + display: grid; + gap: var(--orgmetra-space-md); + max-width: 40rem; + padding: var(--orgmetra-space-lg); + border: 1px solid var(--orgmetra-border-subtle); + border-radius: var(--orgmetra-radius-md); + background: var(--orgmetra-surface-card); + color: var(--orgmetra-text-primary); +} + +.work-capacity-review-status, +.work-capacity-review-next-action { + display: grid; + gap: var(--orgmetra-space-xs); + margin: 0; +} + +.work-capacity-review-status span, +.work-capacity-review-next-action span { + color: var(--orgmetra-text-muted); +} + +.work-capacity-review-state[data-interaction-state="high-risk-confirmation"] { + border-color: var(--orgmetra-action-review); +} + +.work-capacity-review-state[data-interaction-state="permission-denied"], +.work-capacity-review-state[data-interaction-state="validation-error"], +.work-capacity-review-state[data-interaction-state="error"] { + border-color: var(--orgmetra-danger); +} + +.work-capacity-review-state[aria-busy="true"] { + cursor: progress; +} + +.work-capacity-review-submit { + justify-self: start; + min-height: 44px; + padding: var(--orgmetra-space-sm) var(--orgmetra-space-md); + border: 0; + border-radius: var(--orgmetra-radius-sm); + background: var(--orgmetra-action-review); + color: #fff; + font: inherit; + cursor: pointer; +} + +.work-capacity-review-submit:disabled { + cursor: wait; + opacity: 0.62; +} + +.work-capacity-review-submit:focus-visible { + outline: 3px solid var(--orgmetra-focus-ring); + outline-offset: 3px; +} \ No newline at end of file From 6466ac90d22b81b3785688c385aafc61af6a7ede Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 15:06:57 -0700 Subject: [PATCH 6/9] docs(ui): trace work-capacity review interaction --- ...hr-workspace-work-capacity-review-state.md | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 docs/traceability/hr-workspace-work-capacity-review-state.md 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 From 9e3f5c5b1c959bef37831e8cba8695504a18061f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 15:07:12 -0700 Subject: [PATCH 7/9] docs(ui): add work-capacity accessibility references --- ...apacity-review-accessibility-references.md | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 docs/doctoring/hr-workspace-work-capacity-review-accessibility-references.md 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 From b213fbcbe053fe863495a5a9c392d318c0017abe Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 02:42:06 +0900 Subject: [PATCH 8/9] fix: harden work capacity review state boundary --- apps/hr-workspace/work-capacity-review-state.js | 4 ++-- .../hr-workspace-work-capacity-review-state.test.mjs | 12 +++++++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/apps/hr-workspace/work-capacity-review-state.js b/apps/hr-workspace/work-capacity-review-state.js index 0c49228f3..d8a338a96 100644 --- a/apps/hr-workspace/work-capacity-review-state.js +++ b/apps/hr-workspace/work-capacity-review-state.js @@ -68,7 +68,7 @@ function requireExactState(value) { if (typeof value !== 'string') { throw new TypeError('work-capacity review state must be an exact built-in string'); } - const model = STATE_MODELS[value]; + const model = Object.hasOwn(STATE_MODELS, value) ? STATE_MODELS[value] : undefined; if (!model) throw new TypeError(`unsupported work-capacity review state: ${value}`); return model; } @@ -83,4 +83,4 @@ export function workCapacityReviewStateMarkup(state) { const model = requireExactState(state); const disabled = model.submitDisabled ? ' disabled' : ''; return `
\n

${model.label}${model.message}

\n

Next action${model.nextAction}

\n \n
`; -} \ 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 index d0cf2e44d..3834cca3a 100644 --- a/tests/hr-workspace-work-capacity-review-state.test.mjs +++ b/tests/hr-workspace-work-capacity-review-state.test.mjs @@ -105,6 +105,16 @@ test('denial, stale evidence, authoritative-scope conflict, and failure explain test('unsupported runtime input fails closed before rendering', () => { assert.throws(() => workCapacityReviewViewModel('approved'), /unsupported work-capacity review state/); + for (const inheritedState of ['constructor', 'toString', '__proto__']) { + assert.throws( + () => workCapacityReviewViewModel(inheritedState), + /unsupported work-capacity review state/, + ); + assert.throws( + () => workCapacityReviewStateMarkup(inheritedState), + /unsupported work-capacity review state/, + ); + } assert.throws(() => workCapacityReviewViewModel(new String('review')), /exact built-in string/); assert.throws(() => workCapacityReviewStateMarkup(Symbol('recorded')), /exact built-in string/); }); @@ -129,4 +139,4 @@ test('Storybook and CSS cover high-risk work-capacity review accessibility state assert.match(css, /\[aria-busy="true"\]/); assert.match(css, /high-risk-confirmation/); assert.match(css, /min-height:\s*44px/); -}); \ No newline at end of file +}); From 8781009710c53140cdecf1a74b34f1ef3e2e4e89 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 06:31:42 +0900 Subject: [PATCH 9/9] fix(ui): rerun work capacity gate on develop --- .../workflows/hr-workspace-work-capacity-review-state.yml | 3 ++- tests/hr-workspace-work-capacity-review-state.test.mjs | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/hr-workspace-work-capacity-review-state.yml b/.github/workflows/hr-workspace-work-capacity-review-state.yml index aad217bc2..d4609b9db 100644 --- a/.github/workflows/hr-workspace-work-capacity-review-state.yml +++ b/.github/workflows/hr-workspace-work-capacity-review-state.yml @@ -3,6 +3,7 @@ name: HR Workspace Work Capacity Review State Quality on: pull_request: branches: + - develop - feat/hr-workspace-protected-read-state paths: - "apps/hr-workspace/work-capacity-review-state.js" @@ -51,4 +52,4 @@ jobs: - name: Require clean checkout run: | git diff --exit-code - test -z "$(git status --porcelain)" \ No newline at end of file + test -z "$(git status --porcelain)" diff --git a/tests/hr-workspace-work-capacity-review-state.test.mjs b/tests/hr-workspace-work-capacity-review-state.test.mjs index 3834cca3a..2ae4c1f09 100644 --- a/tests/hr-workspace-work-capacity-review-state.test.mjs +++ b/tests/hr-workspace-work-capacity-review-state.test.mjs @@ -14,6 +14,10 @@ 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'], @@ -140,3 +144,7 @@ test('Storybook and CSS cover high-risk work-capacity review accessibility state assert.match(css, /high-risk-confirmation/); assert.match(css, /min-height:\s*44px/); }); + +test('the dedicated contract reruns on protected develop after parent integration', () => { + assert.match(workflow, /branches:\n\s+- develop\n\s+- feat\/hr-workspace-protected-read-state/); +});