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-position-reporting-review-state.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: HR Workspace Position Reporting Review State Quality

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

permissions:
contents: read

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

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

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

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

.position-reporting-review-state[data-interaction-state="high-risk-confirmation"] {
border-color: var(--orgmetra-action-review);
}

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

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

.position-reporting-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;
}

.position-reporting-review-submit:disabled {
cursor: wait;
opacity: 0.62;
}

.position-reporting-review-submit:focus-visible {
outline: 3px solid var(--orgmetra-focus-ring);
outline-offset: 3px;
}
86 changes: 86 additions & 0 deletions apps/hr-workspace/position-reporting-review-state.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
const STATE_MODELS = Object.freeze({
idle: Object.freeze({
ariaBusy: 'false', ariaLive: 'polite', role: 'status', submitDisabled: false,
interactionState: 'default', actionLabel: 'Load reporting evidence',
label: 'Review reporting-line evidence',
message: 'Load fresh Position and reporting-line evidence before making a human review of a proposed solid-line reporting change.',
nextAction: 'Load the current governed reporting evidence for the affected Position.',
}),
loading: Object.freeze({
ariaBusy: 'true', ariaLive: 'polite', role: 'status', submitDisabled: true,
interactionState: 'loading', actionLabel: 'Loading reporting evidence',
label: 'Loading current reporting-line evidence',
message: 'Orgmetra is resolving fresh Position and reporting-line evidence. No cached reporting relationship is accepted.',
nextAction: 'Wait for the current reporting evidence load to finish.',
}),
review: Object.freeze({
ariaBusy: 'false', ariaLive: 'polite', role: 'status', submitDisabled: false,
interactionState: 'high-risk-confirmation', actionLabel: 'Confirm human review',
label: 'Reporting-line change requires human confirmation',
message: 'This is read-only evidence for human review. It does not change the reporting line and does not authorize an employment decision.',
nextAction: 'Confirm the reviewed subordinate, manager, hierarchy, and staffable Position 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 reporting-line review',
message: 'Orgmetra is recording human review evidence for the proposed reporting-line change. Duplicate submission is disabled.',
nextAction: 'Wait for immutable reporting-line review evidence to be recorded.',
}),
recorded: Object.freeze({
ariaBusy: 'false', ariaLive: 'polite', role: 'status', submitDisabled: true,
interactionState: 'read-only', actionLabel: 'Review recorded',
label: 'Reporting-line review recorded',
message: 'The recorded review is evidence only and does not apply the reporting-line change or authorize an employment decision.',
nextAction: 'Continue only through the separately authorized authoritative reporting-line boundary after fresh hierarchy validation.',
}),
denied: Object.freeze({
ariaBusy: 'false', ariaLive: 'assertive', role: 'alert', submitDisabled: false,
interactionState: 'permission-denied', actionLabel: 'Review access',
label: 'Reporting-line review access denied',
message: 'The current purpose or reviewer authority does not permit this reporting-line 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: 'Reporting-line evidence is stale',
message: 'The Position, reporting relationship, or hierarchy evidence changed before review recording.',
nextAction: 'Reload authoritative Position and reporting evidence before reviewing again.',
}),
blocked: Object.freeze({
ariaBusy: 'false', ariaLive: 'assertive', role: 'alert', submitDisabled: false,
interactionState: 'validation-error', actionLabel: 'Review hierarchy integrity',
label: 'Reporting-line change is blocked by hierarchy integrity',
message: 'The proposed reporting line cannot proceed while cycle, duplicate-manager, self-report, or staffable Position evidence is invalid.',
nextAction: 'Resolve the cycle, duplicate manager, self-report, and staffable Position evidence before reviewing the change again.',
}),
error: Object.freeze({
ariaBusy: 'false', ariaLive: 'assertive', role: 'alert', submitDisabled: false,
interactionState: 'error', actionLabel: 'Retry governed load',
label: 'Reporting-line review unavailable',
message: 'The governed reporting evidence or immutable review service did not return a usable result. No cached reporting evidence is accepted.',
nextAction: 'Do not rely on cached reporting evidence; verify the service and authorization before retrying.',
}),
});

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

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

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

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

function story(state) {
return () => positionReportingReviewStateMarkup(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 HierarchyIntegrityBlocked = story('blocked');
export const Error = story('error');
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# HR Workspace Position reporting review accessibility references

Status: **active PR research/doctoring**. These references support the interaction semantics; they are not a claim of product certification or protected-`develop` conformance.

## Applied decisions

- Use native button semantics and expose busy/live state explicitly so assistive technology can receive state changes without turning the review surface into a custom application widget.
- Keep the proposed reporting-line change behind an explicit high-risk human-confirmation state. UI confirmation records review evidence only; it does not mutate Position reporting truth or authorize an employment decision.
- Preserve a visible keyboard focus indicator and a minimum 44 CSS-pixel action height in the workflow-specific proof.
- Denial, stale-evidence, hierarchy-integrity, and error states use assertive alert semantics and always provide a concrete next action.
- Loading and recording disable duplicate submission while preserving a polite status announcement.

## 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 here and was approved as ISO/IEC 40500:2025; this slice does not claim ISO, WCAG, or accessibility certification. WAI-ARIA 1.2 remains the completed Recommendation used for roles, states, and properties. Later draft work is not treated as the normative production baseline for this slice.
47 changes: 47 additions & 0 deletions docs/traceability/hr-workspace-position-reporting-review-state.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# HR Workspace Position reporting review state traceability

Status: **active PR only**. This document does not claim protected-`develop` availability.

## Ownership and dependency boundary

- Shared protected-read accessibility owner: PR #130, `feat/hr-workspace-protected-read-state`.
- Authoritative Position reporting snapshot owner: PR #94.
- Reporting-line human review evidence owner: PR #95. Its review evidence does not apply a reporting mutation or authorize an employment decision.
- Durable reporting persistence owner: PR #106, dependency-first under #94. Persistence must independently enforce hierarchy integrity, tenant isolation, staffable Position coverage, audit/outbox evidence, and bitemporal truth.
- Descriptive Position span-of-control evidence owner: PR #133. It is structural workforce evidence, not a target span or employment-decision rule.
- This PR owns only the HR Workspace presentation/interaction state contract. It does not duplicate #94/#95/#106/#133 source, persistence, authorization, or decision logic.
- Figma design authority: `Orgmetra Baseline`, Storybook Inventory node `1:64`; required interaction states include default, focus, disabled, loading, validation-error, read-only, and high-risk-confirmation.

## Buyer-visible contract

The UI must make the next safe action explicit while keeping reporting-line review human-confirmed and non-authorizing:

| State | Buyer-visible meaning | Safe next action |
| --- | --- | --- |
| `idle` | No governed reporting evidence loaded | Load current Position/reporting evidence |
| `loading` | Fresh reporting evidence is being resolved | Wait; duplicate action disabled |
| `review` | High-risk human confirmation is required | Confirm subordinate/manager/hierarchy/staffable evidence |
| `recording` | Immutable review evidence is being recorded | Wait; duplicate submission disabled |
| `recorded` | Review evidence exists; reporting mutation has not occurred | Continue only through the authoritative reporting-line boundary |
| `denied` | Purpose/reviewer authority is insufficient | Review access purpose and reviewer authority |
| `stale` | Position/reporting evidence changed | Reload authoritative evidence |
| `blocked` | Cycle, duplicate-manager, self-report, or staffable-Position integrity is invalid | Resolve authoritative hierarchy integrity |
| `error` | Governed reporting/review service is unusable | Verify service and authorization; do not rely on cached evidence |

`review` and `recorded` must never imply that a reporting-line change has been applied. They do not authorize an employment decision.

## Privacy and data minimization

State evidence contains only bounded interaction semantics. It must not contain Person, Employment, or Assignment identifiers, worker names/contact information, compensation, ratings, assessment values, credentials/tokens, prompts, or model output.

## Verification

The dedicated `HR Workspace Position Reporting Review State Quality` workflow:

1. checks out the exact PR head;
2. runs Node.js 24 tests with 100% line, branch, and function coverage thresholds;
3. validates Figma correlation and Storybook state inventory;
4. verifies fail-closed runtime state handling and non-authorizing high-risk review semantics; and
5. requires a clean checkout.

Focused child evidence is stack-local. After #130 integrates, this child must be retargeted to fresh `develop` and all applicable browser/accessibility/Foundation/Recovery/SAST/Security/central controls must execute again. Parent checks and reviews do not transfer.
Loading
Loading