Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions .github/workflows/hr-workspace-job-grade-review-state.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: HR Workspace Job Grade Review State Quality

on:
pull_request:
branches:
- develop
- feat/hr-workspace-protected-read-state
paths:
- "apps/hr-workspace/job-grade-review-state.js"
- "apps/hr-workspace/job-grade-review-state.css"
- "apps/hr-workspace/job-grade-review-state.stories.js"
- "tests/hr-workspace-job-grade-review-state.test.mjs"
- "docs/traceability/hr-workspace-job-grade-review-state.md"
- "docs/doctoring/hr-workspace-job-grade-review-accessibility-references.md"
- ".github/workflows/hr-workspace-job-grade-review-state.yml"
workflow_dispatch:

permissions:
contents: read

concurrency:
group: hr-workspace-job-grade-review-state-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
job-grade-review-state:
name: Job grade 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 Job grade 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-job-grade-review-state.test.mjs
- name: Require clean checkout
run: |
git diff --exit-code
test -z "$(git status --porcelain)"
54 changes: 54 additions & 0 deletions apps/hr-workspace/job-grade-review-state.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
.job-grade-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);
}

.job-grade-review-status,
.job-grade-review-next-action {
display: grid;
gap: var(--orgmetra-space-xs);
margin: 0;
}

.job-grade-review-status span,
.job-grade-review-next-action span {
color: var(--orgmetra-text-muted);
}

.job-grade-review-state[data-interaction-state="permission-denied"],
.job-grade-review-state[data-interaction-state="validation-error"],
.job-grade-review-state[data-interaction-state="error"] {
border-color: var(--orgmetra-danger);
}

.job-grade-review-state[aria-busy="true"] {
cursor: progress;
}

.job-grade-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;
}

.job-grade-review-submit:disabled {
cursor: wait;
opacity: 0.62;
}

.job-grade-review-submit:focus-visible {
outline: 3px solid var(--orgmetra-focus-ring);
outline-offset: 3px;
}
78 changes: 78 additions & 0 deletions apps/hr-workspace/job-grade-review-state.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
const STATE_MODELS = Object.freeze({
idle: Object.freeze({
ariaBusy: 'false', ariaLive: 'polite', role: 'status', submitDisabled: false,
interactionState: 'default', actionLabel: 'Load Job grade evidence',
label: 'Review Job grade evidence',
message: 'Load the authoritative Job, Job Analysis, and grade-design evidence before making a human review.',
nextAction: 'Load the current governed evidence for one Job grade review.',
}),
loading: Object.freeze({
ariaBusy: 'true', ariaLive: 'polite', role: 'status', submitDisabled: true,
interactionState: 'loading', actionLabel: 'Loading Job grade evidence',
label: 'Loading governed Job grade evidence',
message: 'Orgmetra is waiting for authoritative Job and Job Analysis evidence. No cached grade decision is used.',
nextAction: 'Wait for the current evidence load to finish.',
}),
review: Object.freeze({
ariaBusy: 'false', ariaLive: 'polite', role: 'status', submitDisabled: false,
interactionState: 'read-only', actionLabel: 'Record human review',
label: 'Job grade evidence ready for human review',
message: 'The evidence is read-only input for human review and does not authorize compensation or an employment decision.',
nextAction: 'Confirm the governed grade-design 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 human Job grade review',
message: 'Orgmetra is recording the human review evidence. Duplicate submission is disabled.',
nextAction: 'Wait for immutable review evidence to be recorded.',
}),
recorded: Object.freeze({
ariaBusy: 'false', ariaLive: 'polite', role: 'status', submitDisabled: true,
interactionState: 'read-only', actionLabel: 'Review recorded',
label: 'Human Job grade review recorded',
message: 'The recorded review is evidence only and does not authorize compensation, promotion, assignment, candidate, or employment decisions.',
nextAction: 'Return to the Job architecture queue or start a separately authorized workflow.',
}),
denied: Object.freeze({
ariaBusy: 'false', ariaLive: 'assertive', role: 'alert', submitDisabled: false,
interactionState: 'permission-denied', actionLabel: 'Review access',
label: 'Job grade review access denied',
message: 'The current purpose or reviewer authority does not permit this Job grade 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: 'Job grade evidence is stale',
message: 'The Job, Job Analysis, or grade-design evidence changed before review recording.',
nextAction: 'Reload authoritative Job and Job Analysis evidence before reviewing again.',
}),
error: Object.freeze({
ariaBusy: 'false', ariaLive: 'assertive', role: 'alert', submitDisabled: false,
interactionState: 'error', actionLabel: 'Retry governed load',
label: 'Job grade review unavailable',
message: 'The governed evidence or immutable review service did not return a usable result.',
nextAction: 'Do not rely on a cached Job grade review; verify the service and authorization before retrying.',
}),
});

function requireExactState(value) {
if (typeof value !== 'string') throw new TypeError('Job grade review state must be an exact built-in string');
if (!Object.hasOwn(STATE_MODELS, value)) {
throw new TypeError(`unsupported Job grade review state: ${value}`);
}
return STATE_MODELS[value];
}

/** Return immutable, value-minimized accessibility semantics for one Job grade review state. */
export function jobGradeReviewViewModel(state) {
return requireExactState(state);
}

/** Render one static Storybook proof without accepting caller-controlled HR values. */
export function jobGradeReviewStateMarkup(state) {
const model = requireExactState(state);
const disabled = model.submitDisabled ? ' disabled' : '';
return `<section class="job-grade-review-state" data-figma-node-id="1:64" data-interaction-state="${model.interactionState}" aria-busy="${model.ariaBusy}">\n <p class="job-grade-review-status" role="${model.role}" aria-live="${model.ariaLive}"><strong>${model.label}</strong><span>${model.message}</span></p>\n <p class="job-grade-review-next-action"><strong>Next action</strong><span>${model.nextAction}</span></p>\n <button class="job-grade-review-submit" type="button"${disabled}>${model.actionLabel}</button>\n</section>`;
}
25 changes: 25 additions & 0 deletions apps/hr-workspace/job-grade-review-state.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { jobGradeReviewStateMarkup } from './job-grade-review-state.js';
import './job-grade-review-state.css';

export default {
title: 'HR Workspace/Job Grade Review States',
parameters: {
design: {
type: 'figma',
url: 'Orgmetra Baseline — Storybook Inventory node 1:64',
},
},
};

function story(state) {
return () => jobGradeReviewStateMarkup(state);
}

export const Idle = story('idle');
export const Loading = story('loading');
export const ReadyForHumanReview = story('review');
export const Recording = story('recording');
export const RecordedReadOnly = story('recorded');
export const PermissionDenied = story('denied');
export const StaleEvidence = story('stale');
export const Error = story('error');
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# HR Workspace Job grade review accessibility references

Checked against current primary W3C publications on 2026-08-28. These references support interaction semantics only; they do not establish HR, compensation, or employment-decision authority.

## Design implications

- WAI-ARIA 1.2 defines `status` as advisory live-region information with implicit polite announcements and `alert` as assertive information that does not itself require focus. Orgmetra therefore uses `status` for ordinary progress/completion and `alert` for denial, stale evidence, and failure.
- WAI-ARIA 1.2 defines `aria-busy` as the state indicating that an element is being modified and assistive technologies may wait to expose updates. Orgmetra sets it only while loading or recording one governed review.
- WCAG 2.2 remains the current WCAG 2 Recommendation baseline. The UI preserves visible keyboard focus, a 44px minimum action target in the existing design-token system, actionable status copy, and no color-only state communication.
- The current 2026 WCAG 2 working-group activity includes proposed editorial/non-normative updates; those proposals are not treated as a replacement normative specification in this PR.

## APA 7 references

World Wide Web Consortium. (2023). *Accessible Rich Internet Applications (WAI-ARIA) 1.2* (W3C Recommendation). https://www.w3.org/TR/wai-aria-1.2/

World Wide Web Consortium. (2023). *Web Content Accessibility Guidelines (WCAG) 2.2* (W3C Recommendation). https://www.w3.org/TR/WCAG22/

## Product boundary

The Storybook evidence correlates with the existing Figma `Orgmetra Baseline` Storybook Inventory node `1:64`. It is an active-PR design/test artifact and must not be presented as integrated protected-branch runtime until its dependency stack is merged and revalidated.
29 changes: 29 additions & 0 deletions docs/traceability/hr-workspace-job-grade-review-state.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# HR Workspace Job grade review state traceability

## Truth boundary

- Protected `develop` truth remains `develop@9e3e4847510e1e612b48474ba42b177b8ed824df` for this stack's base history; this child is **active-PR evidence only** until its dependencies are integrated.
- Parent UI contract: PR #130 `feat/hr-workspace-protected-read-state@c92749cf5889a39de1ba8036742f96fd3451f459` owns shared protected-read accessibility semantics and Figma/Storybook correlation.
- Job grade governance owner: PR #101 owns `JobGradeDesignReviewPacket`; this UI does not import that unmerged package and does not claim its review is protected-branch runtime.
- Durable Job grade persistence is separately owned by dependency-first PR #109. This UI neither writes those tables nor treats persistence evidence as integrated.

## Buyer-visible requirement → evidence

| Requirement | Active-PR evidence | Safety meaning |
| --- | --- | --- |
| Load governed Job/Job Analysis/grade-design evidence before review | `loading` → `review` state model and Storybook stories | No cached or locally invented grade truth is treated as authoritative. |
| Human review remains distinct from compensation/employment authority | `review` and `recorded` messages explicitly deny compensation/employment-decision authority | Reviewing a Job grade design does not authorize pay, promotion, assignment, selection, or another employment action. |
| Prevent duplicate review submission while immutable evidence is being recorded | `recording` has `aria-busy=true` and a disabled action | One in-flight review recording is visible and duplicate activation is blocked in the interaction model. |
| Stale upstream evidence fails closed | `stale` is an assertive alert with a reload action | A changed Job/Job Analysis/grade-design context must be reloaded before another review. |
| Denied purpose/reviewer authority fails closed | `denied` is an assertive alert | The UI does not silently downgrade to a local or cached review path. |
| Service failure provides a safe next action | `error` is an assertive alert and explicitly rejects cached review evidence | Customer-facing copy guides recovery without fabricating governance evidence. |
| Storybook and Figma correlation remain discoverable | Every rendered state carries `data-figma-node-id="1:64"`; Storybook exposes eight named states | Reviewers can inspect loading, read-only, permission-denied, validation-error, error, and focus-related UI evidence. |
| Owned interaction logic has exact coverage | `.github/workflows/hr-workspace-job-grade-review-state.yml` runs Node 24 test coverage at 100% lines/branches/functions | No missing owned branch is accepted as complete evidence. |

## Data minimization

The state model carries only constant governed copy and interaction semantics. It does **not** accept or serialize Person/candidate identity, employee names, contact data, compensation or salary values, ratings, assessment results, credentials/tokens, prompts, or model output.

## Integration discipline

Keep this PR Draft while #53 and #130 remain unintegrated. After those dependencies merge, retarget this child to fresh `develop`, reconcile intervening HR Workspace changes, and rerun all applicable browser/accessibility/Foundation/SAST/Security/Recovery and central exact-head gates. No predecessor check, review, or focused stack-local GREEN result transfers across that retarget.
Loading
Loading