From fbeddab51d07f19753fc057e22992674590ccff3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 14:02:28 -0700 Subject: [PATCH 1/4] test(ui): define Position reporting review states --- ...kspace-position-reporting-review-state.yml | 54 ++++++++ ...e-position-reporting-review-state.test.mjs | 128 ++++++++++++++++++ 2 files changed, 182 insertions(+) create mode 100644 .github/workflows/hr-workspace-position-reporting-review-state.yml create mode 100644 tests/hr-workspace-position-reporting-review-state.test.mjs diff --git a/.github/workflows/hr-workspace-position-reporting-review-state.yml b/.github/workflows/hr-workspace-position-reporting-review-state.yml new file mode 100644 index 000000000..d38edb764 --- /dev/null +++ b/.github/workflows/hr-workspace-position-reporting-review-state.yml @@ -0,0 +1,54 @@ +name: HR Workspace Position Reporting Review State Quality + +on: + pull_request: + branches: + - 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)" diff --git a/tests/hr-workspace-position-reporting-review-state.test.mjs b/tests/hr-workspace-position-reporting-review-state.test.mjs new file mode 100644 index 000000000..9a1e31887 --- /dev/null +++ b/tests/hr-workspace-position-reporting-review-state.test.mjs @@ -0,0 +1,128 @@ +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import test from 'node:test'; +import { + positionReportingReviewStateMarkup, + positionReportingReviewViewModel, +} from '../apps/hr-workspace/position-reporting-review-state.js'; + +const story = readFileSync( + new URL('../apps/hr-workspace/position-reporting-review-state.stories.js', import.meta.url), + 'utf8', +); +const css = readFileSync( + new URL('../apps/hr-workspace/position-reporting-review-state.css', import.meta.url), + 'utf8', +); + +const expectedStates = { + idle: ['false', 'status', false, 'default', 'Review reporting-line evidence'], + loading: ['true', 'status', true, 'loading', 'Loading current reporting-line evidence'], + review: ['false', 'status', false, 'high-risk-confirmation', 'Reporting-line change requires human confirmation'], + recording: ['true', 'status', true, 'loading', 'Recording reporting-line review'], + recorded: ['false', 'status', true, 'read-only', 'Reporting-line review recorded'], + denied: ['false', 'alert', false, 'permission-denied', 'Reporting-line review access denied'], + stale: ['false', 'alert', false, 'validation-error', 'Reporting-line evidence is stale'], + blocked: ['false', 'alert', false, 'validation-error', 'Reporting-line change is blocked by hierarchy integrity'], + error: ['false', 'alert', false, 'error', 'Reporting-line review unavailable'], +}; + +const allowedViewModelKeys = [ + 'actionLabel', + 'ariaBusy', + 'ariaLive', + 'interactionState', + 'label', + 'message', + 'nextAction', + 'role', + 'submitDisabled', +]; + +test('reporting-line review states are bounded, actionable, and value-minimized', () => { + for (const [state, [ariaBusy, role, submitDisabled, interactionState, label]] of Object.entries(expectedStates)) { + const model = positionReportingReviewViewModel(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', + 'compensationValue', + 'ratingValue', + 'assessmentScore', + 'credential', + 'token', + 'prompt', + 'modelOutput', + ]) { + assert.equal(Object.hasOwn(model, forbiddenKey), false); + } + + const markup = positionReportingReviewStateMarkup(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 reporting mutation or employment-decision authority', () => { + const review = positionReportingReviewViewModel('review'); + assert.match(review.message, /human review/i); + assert.match(review.message, /does not change the reporting line/i); + assert.match(review.message, /does not authorize an employment decision/i); + + const recorded = positionReportingReviewViewModel('recorded'); + assert.equal(recorded.submitDisabled, true); + assert.match(recorded.message, /evidence only/i); + assert.match(recorded.message, /does not apply the reporting-line change/i); + assert.match(recorded.nextAction, /authoritative reporting-line boundary/i); +}); + +test('denial, stale evidence, hierarchy conflict, and failure explain the next safe action', () => { + assert.match(positionReportingReviewViewModel('denied').nextAction, /access purpose and reviewer authority/i); + assert.match(positionReportingReviewViewModel('stale').nextAction, /Reload authoritative Position and reporting evidence/i); + assert.match(positionReportingReviewViewModel('blocked').nextAction, /cycle, duplicate manager, self-report, and staffable Position evidence/i); + assert.match(positionReportingReviewViewModel('error').nextAction, /Do not rely on cached reporting evidence/i); +}); + +test('unsupported runtime input fails closed before rendering', () => { + assert.throws(() => positionReportingReviewViewModel('approved'), /unsupported reporting-line review state/); + assert.throws(() => positionReportingReviewViewModel(new String('review')), /exact built-in string/); + assert.throws(() => positionReportingReviewStateMarkup(Symbol('recorded')), /exact built-in string/); +}); + +test('Storybook and CSS cover high-risk reporting-line review accessibility states', () => { + for (const storyName of [ + 'Idle', + 'Loading', + 'HighRiskHumanReview', + 'Recording', + 'RecordedReadOnly', + 'PermissionDenied', + 'StaleEvidence', + 'HierarchyIntegrityBlocked', + 'Error', + ]) { + assert.match(story, new RegExp(`export const ${storyName}`)); + } + assert.match(story, /positionReportingReviewStateMarkup/); + 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/); +}); From b34e82fe36530525b4cbcb38e439f94b90d8cc89 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 14:04:29 -0700 Subject: [PATCH 2/4] feat(ui): implement governed Position reporting review states --- .../position-reporting-review-state.css | 58 +++++++++++++ .../position-reporting-review-state.js | 86 +++++++++++++++++++ ...position-reporting-review-state.stories.js | 26 ++++++ ...porting-review-accessibility-references.md | 21 +++++ ...rkspace-position-reporting-review-state.md | 47 ++++++++++ 5 files changed, 238 insertions(+) create mode 100644 apps/hr-workspace/position-reporting-review-state.css create mode 100644 apps/hr-workspace/position-reporting-review-state.js create mode 100644 apps/hr-workspace/position-reporting-review-state.stories.js create mode 100644 docs/doctoring/hr-workspace-position-reporting-review-accessibility-references.md create mode 100644 docs/traceability/hr-workspace-position-reporting-review-state.md diff --git a/apps/hr-workspace/position-reporting-review-state.css b/apps/hr-workspace/position-reporting-review-state.css new file mode 100644 index 000000000..e8c2588a2 --- /dev/null +++ b/apps/hr-workspace/position-reporting-review-state.css @@ -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; +} diff --git a/apps/hr-workspace/position-reporting-review-state.js b/apps/hr-workspace/position-reporting-review-state.js new file mode 100644 index 000000000..cd7afee04 --- /dev/null +++ b/apps/hr-workspace/position-reporting-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 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 = STATE_MODELS[value]; + 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 `
\n

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

\n

Next action${model.nextAction}

\n \n
`; +} diff --git a/apps/hr-workspace/position-reporting-review-state.stories.js b/apps/hr-workspace/position-reporting-review-state.stories.js new file mode 100644 index 000000000..6a9dde46e --- /dev/null +++ b/apps/hr-workspace/position-reporting-review-state.stories.js @@ -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'); diff --git a/docs/doctoring/hr-workspace-position-reporting-review-accessibility-references.md b/docs/doctoring/hr-workspace-position-reporting-review-accessibility-references.md new file mode 100644 index 000000000..c7111af7e --- /dev/null +++ b/docs/doctoring/hr-workspace-position-reporting-review-accessibility-references.md @@ -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. diff --git a/docs/traceability/hr-workspace-position-reporting-review-state.md b/docs/traceability/hr-workspace-position-reporting-review-state.md new file mode 100644 index 000000000..dd7d8aaca --- /dev/null +++ b/docs/traceability/hr-workspace-position-reporting-review-state.md @@ -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. From 56fb4698250bdfc085eac69973b2058585f5e94e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 02:37:52 +0900 Subject: [PATCH 3/4] fix: harden position reporting review state boundary --- apps/hr-workspace/position-reporting-review-state.js | 2 +- ...-workspace-position-reporting-review-state.test.mjs | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/apps/hr-workspace/position-reporting-review-state.js b/apps/hr-workspace/position-reporting-review-state.js index cd7afee04..7b60b43b2 100644 --- a/apps/hr-workspace/position-reporting-review-state.js +++ b/apps/hr-workspace/position-reporting-review-state.js @@ -68,7 +68,7 @@ function requireExactState(value) { if (typeof value !== 'string') { throw new TypeError('reporting-line 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 reporting-line review state: ${value}`); return model; } diff --git a/tests/hr-workspace-position-reporting-review-state.test.mjs b/tests/hr-workspace-position-reporting-review-state.test.mjs index 9a1e31887..000ee3368 100644 --- a/tests/hr-workspace-position-reporting-review-state.test.mjs +++ b/tests/hr-workspace-position-reporting-review-state.test.mjs @@ -101,6 +101,16 @@ test('denial, stale evidence, hierarchy conflict, and failure explain the next s test('unsupported runtime input fails closed before rendering', () => { assert.throws(() => positionReportingReviewViewModel('approved'), /unsupported reporting-line review state/); + for (const inheritedState of ['constructor', 'toString', '__proto__']) { + assert.throws( + () => positionReportingReviewViewModel(inheritedState), + /unsupported reporting-line review state/, + ); + assert.throws( + () => positionReportingReviewStateMarkup(inheritedState), + /unsupported reporting-line review state/, + ); + } assert.throws(() => positionReportingReviewViewModel(new String('review')), /exact built-in string/); assert.throws(() => positionReportingReviewStateMarkup(Symbol('recorded')), /exact built-in string/); }); From 0538821f810db7344fdcc0cc46d7ef75def43c87 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 06:33:11 +0900 Subject: [PATCH 4/4] fix(ui): rerun reporting gate on develop --- .../hr-workspace-position-reporting-review-state.yml | 1 + .../hr-workspace-position-reporting-review-state.test.mjs | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/.github/workflows/hr-workspace-position-reporting-review-state.yml b/.github/workflows/hr-workspace-position-reporting-review-state.yml index d38edb764..c1cabe57c 100644 --- a/.github/workflows/hr-workspace-position-reporting-review-state.yml +++ b/.github/workflows/hr-workspace-position-reporting-review-state.yml @@ -3,6 +3,7 @@ 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" diff --git a/tests/hr-workspace-position-reporting-review-state.test.mjs b/tests/hr-workspace-position-reporting-review-state.test.mjs index 000ee3368..eda9ae8dd 100644 --- a/tests/hr-workspace-position-reporting-review-state.test.mjs +++ b/tests/hr-workspace-position-reporting-review-state.test.mjs @@ -14,6 +14,10 @@ const css = readFileSync( new URL('../apps/hr-workspace/position-reporting-review-state.css', import.meta.url), 'utf8', ); +const workflow = readFileSync( + new URL('../.github/workflows/hr-workspace-position-reporting-review-state.yml', import.meta.url), + 'utf8', +); const expectedStates = { idle: ['false', 'status', false, 'default', 'Review reporting-line evidence'], @@ -136,3 +140,7 @@ test('Storybook and CSS cover high-risk reporting-line review accessibility stat 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/); +});