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-export-delivery-state.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: HR Workspace Export Delivery State Quality

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

permissions:
contents: read

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

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

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

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

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

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

.hr-export-actions {
display: flex;
flex-wrap: wrap;
gap: var(--orgmetra-space-sm);
}

.hr-export-confirm,
.hr-export-publish {
min-height: 44px;
padding: var(--orgmetra-space-sm) var(--orgmetra-space-md);
border: 0;
border-radius: var(--orgmetra-radius-sm);
font: inherit;
cursor: pointer;
}

.hr-export-confirm {
background: var(--orgmetra-surface-page);
color: var(--orgmetra-text-primary);
}

.hr-export-publish {
background: var(--orgmetra-action-review);
color: #fff;
}

.hr-export-confirm:disabled,
.hr-export-publish:disabled {
cursor: not-allowed;
opacity: 0.62;
}

.hr-export-confirm:focus-visible,
.hr-export-publish:focus-visible {
outline: 3px solid var(--orgmetra-focus-ring);
outline-offset: 3px;
}
101 changes: 101 additions & 0 deletions apps/hr-workspace/hr-export-delivery-state.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
const EXPORT_DELIVERY_STATES = Object.freeze({
review: Object.freeze({
ariaBusy: 'false',
ariaLive: 'polite',
role: 'status',
confirmDisabled: false,
publishDisabled: true,
interactionState: 'high-risk-confirmation',
label: 'Review one-time HR export',
message: 'Confirm the approved purpose, field scope, destination class, and reviewed evidence before any delivery attempt.',
nextAction: 'Confirm the reviewed scope to prepare one audited one-time delivery attempt.',
}),
ready: Object.freeze({
ariaBusy: 'false',
ariaLive: 'polite',
role: 'status',
confirmDisabled: true,
publishDisabled: false,
interactionState: 'default',
label: 'Reviewed export scope confirmed',
message: 'UI confirmation is complete. The backend must still revalidate authorization and reviewed export evidence before delivery.',
nextAction: 'Start one audited one-time delivery attempt.',
}),
publishing: Object.freeze({
ariaBusy: 'true',
ariaLive: 'polite',
role: 'status',
confirmDisabled: true,
publishDisabled: true,
interactionState: 'loading',
label: 'One-time HR export delivery in progress',
message: 'Orgmetra is waiting for the authoritative delivery result. A second delivery attempt is disabled.',
nextAction: 'Wait for the current one-time delivery attempt to finish.',
}),
delivered: Object.freeze({
ariaBusy: 'false',
ariaLive: 'polite',
role: 'status',
confirmDisabled: true,
publishDisabled: true,
interactionState: 'read-only',
label: 'One-time HR export delivered',
message: 'Delivery evidence is read-only. The completed export must not be published again.',
nextAction: 'Review the immutable delivery receipt. Do not send the export again.',
}),
indeterminate: Object.freeze({
ariaBusy: 'false',
ariaLive: 'assertive',
role: 'alert',
confirmDisabled: true,
publishDisabled: true,
interactionState: 'error',
label: 'Delivery outcome needs reconciliation',
message: 'The delivery may already have completed. Automatic republication is disabled while the existing delivery reference is reconciled.',
nextAction: 'Do not send again. Reconcile the existing delivery reference before any further action.',
}),
denied: Object.freeze({
ariaBusy: 'false',
ariaLive: 'assertive',
role: 'alert',
confirmDisabled: true,
publishDisabled: true,
interactionState: 'permission-denied',
label: 'HR export delivery not authorized',
message: 'The reviewed purpose or authorization is not sufficient for this one-time export delivery.',
nextAction: 'Resolve purpose-bound authorization and start a new reviewed export only after approval.',
}),
});

function requireExactExportDeliveryState(state) {
if (typeof state !== 'string') {
throw new TypeError('HR export delivery state must be an exact built-in string');
}
if (!Object.hasOwn(EXPORT_DELIVERY_STATES, state)) {
throw new TypeError(`unsupported HR export delivery state: ${state}`);
}
return EXPORT_DELIVERY_STATES[state];
}

/**
* Return value-minimized interaction semantics for one governed HR export delivery state.
* The model contains no HR payload values, credentials, identifiers, or delivery secrets.
* @param {string} state Governed export delivery state.
* @returns {Readonly<object>} Immutable accessibility and next-action semantics.
*/
export function exportDeliveryViewModel(state) {
return requireExactExportDeliveryState(state);
}

/**
* Render one Storybook proof for the one-time HR export delivery interaction.
* Only constant governed copy is emitted; caller-controlled HR values are not accepted.
* @param {string} state Governed export delivery state.
* @returns {string} Static HTML for the existing HR Workspace Storybook fixture.
*/
export function exportDeliveryStateMarkup(state) {
const model = requireExactExportDeliveryState(state);
const confirmDisabled = model.confirmDisabled ? ' disabled' : '';
const publishDisabled = model.publishDisabled ? ' disabled' : '';
return `<section class="hr-export-delivery-state" data-figma-node-id="1:64" data-interaction-state="${model.interactionState}" aria-busy="${model.ariaBusy}">\n <p class="hr-export-status" role="${model.role}" aria-live="${model.ariaLive}"><strong>${model.label}</strong><span>${model.message}</span></p>\n <p class="hr-export-next-action"><strong>Next action</strong><span>${model.nextAction}</span></p>\n <div class="hr-export-actions">\n <button class="hr-export-confirm" type="button"${confirmDisabled}>Confirm reviewed scope</button>\n <button class="hr-export-publish" type="button"${publishDisabled}>Start one-time delivery</button>\n </div>\n</section>`;
}
13 changes: 13 additions & 0 deletions apps/hr-workspace/hr-export-delivery-state.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import './hr-export-delivery-state.css';
import { exportDeliveryStateMarkup } from './hr-export-delivery-state.js';

export default {
title: 'HR Workspace/One-time HR export delivery',
};

export const ReviewRequired = () => exportDeliveryStateMarkup('review');
export const ConfirmedReady = () => exportDeliveryStateMarkup('ready');
export const Publishing = () => exportDeliveryStateMarkup('publishing');
export const DeliveredReadOnly = () => exportDeliveryStateMarkup('delivered');
export const DeliveryIndeterminate = () => exportDeliveryStateMarkup('indeterminate');
export const PermissionDenied = () => exportDeliveryStateMarkup('denied');
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# HR Workspace one-time export delivery accessibility references

Status: active-PR research evidence for the dependency-first HR Workspace export-delivery interaction slice. This note does not claim WCAG conformance, legal compliance, production deployment, or backend authorization.

## Design evidence reviewed

- Existing Figma `Orgmetra Baseline` file `xu1ZK1zmtFcDep95R8oE9O`, Storybook Inventory node `1:64`, reviewed read-only on 2026-08-27. The published inventory explicitly requires `default / hover / focus / disabled / loading / validation-error / read-only / high-risk-confirmation` states. This slice reuses that requirement and does not create new Figma geometry.
- Parent PR #130 owns shared protected-read loading/disabled/error/read-only/focus semantics and Orgmetra design-token usage. This child adds only one-time HR export-specific high-risk confirmation, delivered-receipt, and do-not-republish ambiguity behavior.

## Standards implications

WCAG 2.2 is a W3C Recommendation. The interaction proof is designed around keyboard-operable controls with visible `:focus-visible` treatment, clear labels/instructions, explicit error/denial next actions, and programmatically exposed status changes. The high-risk export flow deliberately keeps the delivery action disabled until a distinct confirmation state, and terminal delivery/indeterminate states keep republish disabled.

WAI-ARIA 1.2 is a W3C Recommendation used here only for interaction semantics. Non-urgent progress/read-only changes use `role=status` with polite live announcements; authorization denial and ambiguous delivery outcomes use `role=alert` with assertive announcements. `aria-busy=true` is reserved for the in-progress delivery state. These semantics do not substitute for backend authorization or immutable audit evidence.

## APA 7 references

World Wide Web Consortium. (2024, December 12). *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/
47 changes: 47 additions & 0 deletions docs/traceability/hr-workspace-export-delivery-state.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# HR Workspace one-time export delivery interaction traceability

## Status

Active PR only. This interaction evidence is not shipped default-branch product truth and is not an authorization or release artifact.

Technical dependency chain: #53 → #130 → this child. Semantic export-owner dependencies remain #75 (governed export review) and #120 (audited one-time delivery). Their checks, reviews, backend authority, and persistence evidence do not transfer into this UI branch.

## Buyer risk closed

A one-time HR export is a sensitive, consequential data-egress operation. The UI must not make review, delivery, retry, or reconciliation look interchangeable. This slice makes the next safe action explicit without embedding protected HR values or credentials in component-state evidence.

| State | Interaction evidence | Safe next action |
|---|---|---|
| `review` | Figma-required high-risk confirmation; delivery disabled | Confirm the reviewed purpose/scope/destination evidence |
| `ready` | UI confirmation complete; confirmation locked; delivery handoff enabled | Start one audited one-time delivery attempt; backend still revalidates authority |
| `publishing` | `aria-busy=true`; confirmation and delivery disabled | Wait; do not duplicate the send |
| `delivered` | Read-only terminal receipt state; delivery disabled | Review immutable receipt; do not republish |
| `indeterminate` | Assertive error state; delivery disabled | Do not send again; reconcile the existing delivery reference |
| `denied` | Assertive authorization-denied state; delivery disabled | Resolve purpose-bound authorization and begin a newly reviewed export only after approval |

## Privacy and authority boundary

`hr-export-delivery-state.js` accepts only the governed state name and emits constant copy. It does not accept HR values, Person/Employment identifiers, document content, destination addresses, credentials, tokens, compensation, ratings, or model output. Storybook evidence therefore cannot itself exfiltrate protected HR payloads.

The `ready` UI state is not delivery authorization. The authoritative export service must still re-resolve the exact reviewed export packet, tenant/purpose/field scope, authorization freshness, destination class, immutable audit/outbox correlation, and one-time publication semantics defined by the export owner boundaries. An ambiguous external outcome is reconciliation-only; the UI deliberately has no republish action in `indeterminate`.

## Accessibility / Product Design mapping

Figma `Orgmetra Baseline` node `1:64` requires high-risk confirmation, loading, disabled, read-only, error, and focus states. Parent #130 owns the shared pattern. This child reuses existing Orgmetra spacing/surface/action/focus tokens and adds only workflow-specific semantics. Focusable controls use the existing `--orgmetra-focus-ring`; busy/error status is conveyed programmatically rather than by color alone.

The dedicated Storybook states are `ReviewRequired`, `ConfirmedReady`, `Publishing`, `DeliveredReadOnly`, `DeliveryIndeterminate`, and `PermissionDenied`.

## Verification

`tests/hr-workspace-export-delivery-state.test.mjs` requires:

- high-risk confirmation before delivery;
- exactly one enabled delivery handoff state;
- duplicate confirmation/delivery prevention during publishing;
- no republish action after delivered or indeterminate outcomes;
- explicit customer next actions for denial and ambiguity;
- no protected-value vocabulary in state evidence;
- exact built-in state input semantics; and
- Storybook/Figma correlation, tokenized focus treatment, and exact 100% line/branch/function coverage in the dedicated workflow.

After the technical parent integrates, this child must be retargeted to fresh `develop`. Before representing the interaction as commercial product truth, also refetch the current #75/#120 export contracts and rerun every applicable browser/accessibility/Foundation/SAST/Security/Recovery/central workflow on the resulting exact head. Predecessor or parent evidence never transfers.
Loading
Loading