Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/missionmatch / frontend/src/lib/governance.ts |
| Date | Not specified in source |
| Repository | Janady13/missionmatch |
| Revision | 720d47fae476b0d38477ae4401697d372b1ea2bc |
| Branch | feature/opportunity-to-outcome |
| Path or archive member | frontend/src/lib/governance.ts |
| Original SHA-256 | 2debdc1c9f68125d8cdefb184b6a9a60feb78d357d7bd8965362d1e216feb4f4 |
| Git blob SHA | 47cc7bb4c23db518d51aa1b7a1b81f17ac009578 |
| Reading-copy SHA-256 | 2debdc1c9f68125d8cdefb184b6a9a60feb78d357d7bd8965362d1e216feb4f4 |
Recovered source transcript
Download complete source text ↓99 lines · 3.4 KB
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { request } from './api'
// THE AUTOMATED DECISION NOTICE.
//
// The write route has existed since the governance module shipped and NOTHING ever called it: production
// carried three candidate evaluations and zero notices. Part of the reason is that there was no way to ASK
// whether a notice existed, so no screen could prompt for one.
//
// What the law asks is consistent across jurisdictions even where the detail differs: tell the person before
// an automated tool assesses them, say what it assesses, and be able to prove you did. New York City Local
// Law 144 puts a ten business day floor on the notice period. The EU AI Act treats hiring as high risk from
// August 2026.
export interface NoticeStatus {
on_file: boolean
disclosed_at?: string
notice_version?: string
data_categories?: string[]
// More than one notice is normal and correct: a new disclosure version means a new notice, and the history
// is the evidence.
count: number
}
// The categories the scoring engine actually assesses, which is what the disclosure has to name. They are the
// six weighted components of the frozen engine, in its own vocabulary, because a disclosure that describes
// something other than what the tool does is not a disclosure.
export const AEDT_DATA_CATEGORIES = [
'security clearance',
'skills',
'certifications',
'customer experience',
'years of experience',
'location and availability',
]
export const governanceApi = {
noticeStatus: (candidateID: string) =>
request<NoticeStatus>(`/candidates/${encodeURIComponent(candidateID)}/notice`),
recordNotice: (candidateID: string, notice_version: string, data_categories: string[]) =>
request<{ id: string }>(`/candidates/${encodeURIComponent(candidateID)}/notice`, {
method: 'POST',
body: JSON.stringify({ notice_version, data_categories }),
}),
}
export function useNoticeStatus(candidateID: string) {
return useQuery({
queryKey: ['candidates', candidateID, 'notice'],
queryFn: () => governanceApi.noticeStatus(candidateID),
enabled: !!candidateID,
})
}
export function useRecordNotice(candidateID: string) {
const qc = useQueryClient()
return useMutation({
mutationFn: (v: { version: string; categories: string[] }) =>
governanceApi.recordNotice(candidateID, v.version, v.categories),
onSuccess: () => {
void qc.invalidateQueries({ queryKey: ['candidates', candidateID, 'notice'] })
},
})
}
// THE ACCESS HISTORY. Three dates a recruiter learns on a phone call, and the posture they imply.
export interface AccessIntelResponse {
posture: string
label: string
detail: string
months_out_of_access?: number
days_left_in_window?: number
adjudication_stale?: boolean
}
export interface AccessInput {
cv_enrolled?: boolean | null
cv_enrolled_on?: string | null
access_ended_on?: string | null
last_adjudicated_on?: string | null
}
export function useSetAccess(candidateID: string) {
const qc = useQueryClient()
return useMutation({
mutationFn: (v: AccessInput) =>
request<AccessIntelResponse>(`/candidates/${encodeURIComponent(candidateID)}/access`, {
method: 'PATCH',
body: JSON.stringify(v),
}),
onSuccess: () => {
// The posture appears on every search row, so every cached search is stale the moment it changes.
void qc.invalidateQueries({ queryKey: ['candidates'] })
},
})
}