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
54 changes: 54 additions & 0 deletions .github/workflows/hr-workspace-document-retrieval-state.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
name: HR Workspace Document Retrieval State Quality

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

permissions:
contents: read

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

jobs:
document-retrieval-state:
name: Document retrieval 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 document-retrieval 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-document-retrieval-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/document-retrieval-state.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
.document-retrieval-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);
}

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

.document-retrieval-status span,
.document-retrieval-next-action span {
color: var(--orgmetra-text-muted);
}

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

.document-retrieval-state[aria-busy="true"] {
cursor: progress;
}

.document-retrieval-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;
}

.document-retrieval-submit:disabled {
cursor: wait;
opacity: 0.62;
}

.document-retrieval-submit:focus-visible {
outline: 3px solid var(--orgmetra-focus-ring);
outline-offset: 3px;
}
114 changes: 114 additions & 0 deletions apps/hr-workspace/document-retrieval-state.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
const STATE_MODELS = Object.freeze({
idle: Object.freeze({
ariaBusy: 'false',
ariaLive: 'polite',
role: 'status',
submitDisabled: false,
interactionState: 'default',
label: 'Review access purpose before retrieval',
message: 'Choose the approved purpose and confirm that this authenticated HR session is appropriate for the document read.',
nextAction: 'Start one authorization request after confirming the purpose and access scope.',
}),
authorizing: Object.freeze({
ariaBusy: 'true',
ariaLive: 'polite',
role: 'status',
submitDisabled: true,
interactionState: 'loading',
label: 'Authorizing document access',
message: 'Orgmetra is resolving the current document scope and purpose-bound authorization before any protected artifact read.',
nextAction: 'Wait for authorization to finish; do not start another retrieval.',
}),
reading: Object.freeze({
ariaBusy: 'true',
ariaLive: 'polite',
role: 'status',
submitDisabled: true,
interactionState: 'loading',
label: 'Verifying protected document',
message: 'Orgmetra is performing the bounded artifact read and verifying the expected SHA-256 before release.',
nextAction: 'Wait while the authorized artifact is verified.',
}),
auditing: Object.freeze({
ariaBusy: 'true',
ariaLive: 'polite',
role: 'status',
submitDisabled: true,
interactionState: 'loading',
label: 'Recording immutable access evidence',
message: 'The protected artifact has been verified; Orgmetra must append immutable access evidence before bytes are released.',
nextAction: 'Wait for the immutable access record to complete.',
}),
ready: Object.freeze({
ariaBusy: 'false',
ariaLive: 'polite',
role: 'status',
submitDisabled: false,
interactionState: 'read-only',
label: 'Authorized document is ready',
message: 'Artifact verification and immutable access evidence completed. This read-only handoff does not authorize export or an employment decision.',
nextAction: 'Open the authorized document only in the authenticated HR session.',
}),
denied: Object.freeze({
ariaBusy: 'false',
ariaLive: 'assertive',
role: 'alert',
submitDisabled: false,
interactionState: 'permission-denied',
label: 'Document access denied',
message: 'The current document, purpose, requester, or delivery scope was not authorized.',
nextAction: 'Review the purpose and access scope before starting a new authorization request.',
}),
stale: Object.freeze({
ariaBusy: 'false',
ariaLive: 'assertive',
role: 'alert',
submitDisabled: false,
interactionState: 'validation-error',
label: 'Authorization expired before release',
message: 'The prior authorization is no longer current, so Orgmetra did not release the protected document.',
nextAction: 'Start a new authorization request; do not reuse the expired decision.',
}),
error: Object.freeze({
ariaBusy: 'false',
ariaLive: 'assertive',
role: 'alert',
submitDisabled: false,
interactionState: 'error',
label: 'Document retrieval unavailable',
message: 'The protected artifact or immutable audit boundary did not return a usable result, and no local fallback is allowed.',
nextAction: 'Do not use a cached copy; check the protected source and audit service before trying again.',
}),
});

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

/**
* Return value-minimized accessibility semantics for one HR document retrieval state.
* The view model contains workflow guidance only and never carries protected document bytes or HR values.
* @param {string} state Governed document retrieval state.
* @returns {Readonly<object>} Immutable interaction semantics.
*/
export function documentRetrievalViewModel(state) {
return requireExactState(state);
}

/**
* Render Storybook evidence for one governed HR document retrieval state.
* No caller-controlled protected value is accepted by this renderer.
* @param {string} state Governed document retrieval state.
* @returns {string} Static HTML for the HR Workspace Storybook fixture.
*/
export function documentRetrievalStateMarkup(state) {
const model = requireExactState(state);
const disabled = model.submitDisabled ? ' disabled' : '';
return `<section class="document-retrieval-state" data-figma-node-id="1:64" data-interaction-state="${model.interactionState}" aria-busy="${model.ariaBusy}">\n <p class="document-retrieval-status" role="${model.role}" aria-live="${model.ariaLive}"><strong>${model.label}</strong><span>${model.message}</span></p>\n <p class="document-retrieval-next-action"><strong>Next action</strong><span>${model.nextAction}</span></p>\n <button class="document-retrieval-submit" type="button"${disabled}>Retrieve HR document</button>\n</section>`;
}
20 changes: 20 additions & 0 deletions apps/hr-workspace/document-retrieval-state.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import './document-retrieval-state.css';
import { documentRetrievalStateMarkup } from './document-retrieval-state.js';

export default {
title: 'HR Workspace/Document Retrieval State',
tags: ['autodocs'],
};

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

export const Idle = story('idle');
export const Authorizing = story('authorizing');
export const Reading = story('reading');
export const Auditing = story('auditing');
export const ReadyReadOnly = story('ready');
export const PermissionDenied = story('denied');
export const AuthorizationExpired = story('stale');
export const Error = story('error');
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# HR Workspace document retrieval accessibility references

Status: primary-source design references for active PR #132. These sources support interaction semantics only; they do not grant HR authorization, legal entitlement, export authority, or employment-decision authority.

## References (APA 7)

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

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

## Applied interpretation

- Progress messages that do not require immediate intervention use a polite `status` live region.
- Permission denial, expired authorization, and retrieval/audit failure use an assertive `alert` because the user's requested protected action cannot continue.
- `aria-busy=true` is bound only to authorizing, reading, and auditing states; duplicate submission is disabled while those phases are active.
- Completion is explicitly read-only and tells the HR user what to do next without implying export or employment-decision authority.
- Visible keyboard focus uses the existing Orgmetra `--orgmetra-focus-ring` token and `:focus-visible` contract inherited from the parent protected-read interaction slice.

Primary references were rechecked on 2026-08-28. WCAG 2.2 remains the final W3C Recommendation used here; ongoing WCAG 2.x errata/editorial work is not treated as a new conformance version.
33 changes: 33 additions & 0 deletions docs/traceability/hr-workspace-document-retrieval-state.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# HR Workspace document retrieval interaction-state traceability

## Status boundary

- **Protected-main truth:** `develop@9e3e4847510e1e612b48474ba42b177b8ed824df` does not contain this workflow-specific UI state contract.
- **Parent active PR:** #130 owns the shared protected-read accessibility/Storybook state semantics and Figma correlation. This child reuses that interaction vocabulary rather than creating a second generic protected-read renderer.
- **Backend active PR:** #116 owns a separate purpose-bound HR document retrieval execution boundary. This UI child does not import that unmerged package and does not claim that a UI state is authorization.
- **This active PR:** adds only value-minimized HR document retrieval interaction evidence for authorization, bounded artifact verification, immutable audit-before-release, read-only handoff, denial, expiry, and transport/audit failure.

## Buyer-facing contract

| UI state | Meaning | Safe next action | Authority boundary |
| --- | --- | --- | --- |
| `idle` | No protected operation is in flight. | Confirm purpose/scope, then start one authorization request. | No read authority exists. |
| `authorizing` | Current tenant/document/purpose scope is being authorized. | Wait; duplicate retrieval is disabled. | No artifact read is implied. |
| `reading` | A bounded protected artifact read and SHA-256 verification is in flight. | Wait for verification. | UI does not receive or persist document bytes as state evidence. |
| `auditing` | Verified artifact is waiting for immutable access evidence. | Wait for the audit append. | Bytes are not considered releasable before durable audit succeeds. |
| `ready` | Verified, audited read is ready inside the authenticated HR session. | Open only in that authenticated session. | Read-only; not export or employment-decision authority. |
| `denied` | Purpose/requester/document/delivery scope was not authorized. | Review purpose and access scope before a new authorization attempt. | Fail closed. |
| `stale` | Authorization expired before release. | Start a new authorization request. | Expired decisions are never reused. |
| `error` | Protected source or audit boundary failed. | Check protected source/audit service; do not use cached data. | No local fallback. |

All rendered state evidence is constant application copy. It does not contain document bytes/text/title, names, email/phone data, compensation, ratings, credentials/tokens, model output, or another service's application-table values.

## Design and accessibility trace

The parent #130 maps existing Figma `Orgmetra Baseline` Storybook Inventory node `1:64` to executable loading, disabled, error, read-only and focus semantics. This child adds workflow-specific stories without changing that design authority. In-progress phases use `aria-busy=true` and disable duplicate submission; denial/expiry/error use assertive alert semantics; normal progress/read-only states use polite status semantics; keyboard focus remains visible through the existing Orgmetra focus-ring token.

## Verification

`tests/hr-workspace-document-retrieval-state.test.mjs` requires all eight states, value-minimized copy, safe next-action copy, exact runtime-state rejection, Figma correlation, Storybook inventory, visible-focus styling and exact 100% line/branch/function coverage through the dedicated workflow.

Stack-local GREEN never transfers to #130, #53, #116, or protected-main integration. After dependencies integrate, retarget/revalidate against fresh `develop` and rerun every applicable browser/accessibility/Foundation/SAST/Security/Recovery/central gate on one exact head.
Loading
Loading