LyJNLtcV1-%qZgVdM{z5pot6yJ+qQhIwcrU|Q4S%E!
zTF80(sU*@T^yh#wAJz|9MkG2_#}3QA28P?{xYg2AUgZNJj)y<+
zp>rF*hq(w1A)y8A?>MQc-S=suh@YwnNVSmEgN101{KD0RJ$CJ>%5xiTc~`!Y;7(Dn
zyA7j1k}#ZsTN@IHnWWOx>tP6G+&Q|BA|wf%_vaiaDM~q)t8*{XxH65D{{AM=<#jcd
z$c6mNrl_DV)mnc)0IV3M2UyEP9B)8K7u+u{OcfnT22xI1E1zKAE#UE+ZJfZjfxWwX
zgImqT_$7m_pgi?qBlzH_?;YW8!isc3E0G&3kNXN(K%rZB1Z}1M)yGL4^R0eVPbn+E
zgqhlcO}A|!-{1^!Z{#PV7g&G7Rb^=bTc|N7P^<1|rsDfS81O)O)ksrn6&oud{)oqx
z6$SD)%lJ=o1jwaUAbA+4>F%;>Bl(q4t?k`=)U*9lq5wFePUmKlF>D5kln?w2cBh;2
ziYhgSCG*iRtUnaWqOU`YuvG7qD`$S_A|BYWSc__U`v=x5wOLH)t)h@Js&Xzgi;X$G
zC)}i%6IQNc$!fW)4RZ}r)A#!GjA+AQUfXi^aG7`4RW#W4+9Ra=WtLCdh_W;OD^1N9
z)}977@^kdW80Sx=iU6h3&z=l-<{dMA3s)Fez&2K!M3wqLOva_7h_x*V3P@FpeF)
z1$=sFlz#<{hH4eFu_Fm4^G4dh5>uf2e7nm842ssDb&jRkYRFO%vFCo>G)n
zEG0!t4nOZokV@L#;M(3YfQC4lNu`MZXs>-`fDVZQynkzgUPFsH64Y+|k>zsa@Y7WU
z`jn}D1+jz+t7S;nUNX!?e9_$pwAPC$o@w7*RX50Oj(?X*$SQ$V%mL6r1r(a8*ECyD
z*gQ;HZhAm6KpGSTopdWh1p_`N?fU!p#Oxh_ACP-AGbsxZ1O^KESod~YNsVdlN9hE@
zW)2~5nrsh%iiGtRO{||0i+F2j&m9#`dc6BRz7{wR17l->3Irsia`>celDCzQ;eTNO
z=i>GB!7v59|A9e*L46%n`qOwLM9#hkEF_3
z*|yun>A8A6XIx6ti(!c}c;11R&YPDah1ah0V)Va)x|jleEsV6FH)czV1J-X&^2B)G
ze(HUrrSCc-x#G3sJ-FjC~i%9a%D3{HfIq?
zqg~LW0wA;_=!$P8d@Nagl!$E!gYvVVvLfPT9EjpfbMAn`7tLI}wPrQbDY~?vZV57V
zAke9`|4S(basE4Q%7JDW2MGhL;7;}&eN;wRYTC__OaX60HtCl_vV79Et-l*YyHhrzL3V3~xC31BC&G@7KKYSMl=>XD4
zlLz$6P6!C^SCT2}@S=lo2G^8C$kzG$13nl9|AX=jiwJ|@2Qc6(ben1E^rvrn>x}rb
zov)gdd*t+-ejBB%%7y)Sdt0s68o-Ns^O;se+*nlEjMZY8&2
z4cgVqjwM!5sw!UOKw}X*X$krf$tixz7KovoSx7<9Gn+7SP^xN+j4WuYBf^=a~4i;0!
z7QMpWp6}=kCpKXmEeb`_lm6RQzt+eMc6qXC$+k&a_
zq*_!mhgw#yG}@$-Et$bPYpayF`=)#GOMknRR4G!^I)R=G2oPn6o^)JII`|~}q$X6&
z3DGb&$xfY(v`n3=z1=CJ=L==MP{wsQqV@0ZvEW7jv*EXNMyZ7OqbzNoi2T0U!8jw9
zEWJC3tQ`$%GbhRgAq`MPgFsjyd@DOij48|U+$hdN9e5xjVXneC#yG*cH<;Hg#ju6*
z$SD98uU!I>RMHLZ3l_Tf>~-<&LEhG~c(0Y(!q*AYV0Yv33ZFLAqw
zyd%%{W$}==zQa3yhA1c-b*#tq7xEh>yVuv)@A{s8cG$fU15VJbG`SY%f%t@<-rJ%0
zD3p`@rg;NEr700HTxS+X$O3{=cT@|$$L-^9hg?`0sbDmcj5L{ui<}%|;){sP=K6|*
zZeB|?JG?$#vQvYNkrr{^x-kWN`=SU7KIj-6TfnmArn*)-t0mu7k`#Dkv^R9CIu${;
z;^+xcSoy5fK%lc}>aDF)kz6XJwrX<39sPJLk)1ZnDMY>9q}F6z4`7qC)FQlly?;5j
zTij;TE)5<<$I)h?f^}c8+z*LnUI_R|EVvbCy5s30oE&zD3vVFEhbe0SL7Iw7tA)3mYdJ#`LjtpOQ;avo&kT&k0@LzvXelrtY`Mq+pQ6
zv!Q){yA1GxotgElL(bF!Wt%V`cwDKtX)wlM0%Dru5AhCBS6Xime_*p;mA=7ux2v%tq
z_)$igXP^d`02~BT`;o2*4@OPL@!HLP#7*qV{e>#<`#nv|e!>xEbM}UgLFk2<)%)^R
zOr~>3Un_SU-_aybddf}}E2}=c<7QX4csKfQnZZ-=1weRpdHVJt`$xh>3nk)YFH0}M
zbg(mhKC-1CnuH9};%z(uoP0wJ?qD{pomT{*g(lgb8|zq<4ds;9)-e5YHMA0(FxWB7
zL8&^*zgP~}`MUN=OW5#^y3#Ir^CLjM)+qJeDJW1zM6&-gG-U3+Sf)(KPo9E(7
z%gwJSqJV)EY=Ai7#WtcK_;pLalvMyH?eh*lvoQ-8f*xPsH*DXHu(s&1WoHPsX5YD!
z7&+>wEJ`uTauT9gkJvpa>q2zCY`PKTI%>PVr}I%K5DU;I~9!eKE{OpMs@8vlY#zf$4LRoPNZ%RHs3
zaeHtvUV!y%;8ydMcOK}J9Dl5@>s?gnb?Rtc=_pbf)mN(DG0K(lvjNQX=d_qfpEksH
z?L2HvqcJ5&~ereJ96CpiWT$o)_XP34X0xbZJ<;GJfsbA#u4sD_oIHlRPd0@1nwX9N=LyQ!q
zB}|3m>BQA^WqS>6`g8Wyi^Itlbxdc2NhBa_`ITxeDIm%Blk3LaWnqBbt-dQ^IKqqf
zJQCot2TA$)csPuvXLS>&RqFV7`MEq&u>^8{)3{$r)*h7orPfnT;NyU-!l
zk&W$!So0r!k9f5YyDOvN_-XMps7O(YIAbB1vOM%Bxjx^=%LS;NQ#6#uF@K7EPl=pX
zQ6SUp^g9^m9|#DCHOl8W3~Zv-j&KP~Gq=VqKnVydR;;~miT2ca<3C~3n`Ro1N5e!S
zI#k(SdejmZMuASn!NYlJZ_1N&&t-blOBe2hMpHRSfRe0??IhC}gP#B+`qOE~sl@%gSQcUW
zwQ$aCl-3B-u6(k)t($
zoF-vokAuNKs;~JFgzq
zqMWLQFEJ>TfRaSa6Qw^zCT*cfxjJK{>W{Y89bqchnp~X^rpNNTT4G~Lvph=j9HHN`
zI%TKqrD0FbOKC9cJx=E=0V0D1Jsg*RdHCy?;H}sChH#0T9Qkc5dJS0Qp#Q|M_w%gO
zf5mWwy6=>W<3`|!4M&r1CK@^V$H)$vE9o29X6J8E0TV=;0V9gfe)-CREPPHRXvVEx
z!706#zI2n;SR(_|0K|+
z@0HJ3bKRDuVP1n?APNy0;&z?}N!88r(pyob(yXe*7T4`pQC!4`0x?uMQf9IzPD*35
z^RG*Ixdb<hjF+&ew_OWhy@2Ve@pK224;HYUJygLfX4pQsC{b4*3SFcZ7Bqrs8zC
zir2#`KV6f>SkEVvK9m)>nsxq;Fdo_d0$yYNrZ!`JkZU$0L~r@lO*@p}Kz%M{5_(CH
zVbxE}{OGig*(hY+06J}aTyQNFsg5~%`qJ^cdS)6GAATbhk?LyM#;TN=617e=Tp*8;
zactT_^N0S-h0l#0Y@s6djFxj}ip%q1LDAWAFp|!W2NMH-RB*=m)2>Wm(b&g{gJy=G
zd7lV3#LV2z_?9C19eijedf`7e+F>!?q_r4mJn3_%85|y)_T=0o_uNbe?t&BZ!q
zZ$GpZ{nroU{IpIB)3p^Scp?oc5hg~b-pgj(v#%?mhG$9oP`m^oJ~IDV3)`|G+f{9I
z__|)+q|@c4;k*$QWKb5aDr)2DaMktFr(e)~Yr{!I_To-@0&^^0lUFWQ?NKWdZftx^
zWHG0$L}+Q!pA!?BibErvaQoyn=8#?c`#gz*D|_iHTw}7a&v>S1Hym81B=m=TKXJ?Z
z)NN*pTzz|Z)vuzh-pvwI6Cr-S3TT!-MkUZyXXG*DnnT+KQuxQ?(G+tQNLI8=7-L$X
z=^9>AnU;=Dz#NSnZhZY|-8f~dpN6mNftqd0t-<1niL?#h9$x$LudNTrqFnt&<%
z=ZTvn_|fjcTND1{)d6OA=bXZM#fr!VYQMes7EfFNiF>`I_EJ+T;@J3bIH2iX@8_0$tS7m;MhP@ipJNH#pJT
zLW9l~tGXKdF~m}vBGxE+TXgWb?b|J-QBx1A4S%7huza#ruj|YVaslH
zjlnc8;?Ja@%)?7~ZUm*7^j;db^sobT`ulOJ;kpulNpCO07e9rj$9)^)y3VD8-
z2RXC3LD4s^^f%YMc*1Un?RzY3^HL}HyEQOA$$X-jKq!}fi$8YOQv56woS=Ud4^37#B3dF-VEfvx1&J8R+G18d*Qa5mzzQSm^&J{1m_Q_ffiVdW(COBJJKtYaAM&BZmLoEoLM~fnQ=7^e4F#&Q^^%oUHOz^(HRispUAZq%_ygVk
pksK)x>E3%2`UhWN+!xlq58iq+Y;}DIt_5_45b8VAe=HmF{{Z-)J=g#M
literal 0
HcmV?d00001
diff --git a/project-access-denial-appeal-guard/reports/manifest.json b/project-access-denial-appeal-guard/reports/manifest.json
new file mode 100644
index 00000000..10d55a6d
--- /dev/null
+++ b/project-access-denial-appeal-guard/reports/manifest.json
@@ -0,0 +1,14 @@
+{
+ "generatedAt": "2026-06-01T10:51:17.059Z",
+ "module": "project-access-denial-appeal-guard",
+ "cleanStatus": "READY",
+ "riskyStatus": "HOLD",
+ "riskyFindings": 14,
+ "artifacts": [
+ "clean-audit.json",
+ "risky-audit.json",
+ "risky-review.md",
+ "summary.svg",
+ "demo.mp4"
+ ]
+}
diff --git a/project-access-denial-appeal-guard/reports/risky-audit.json b/project-access-denial-appeal-guard/reports/risky-audit.json
new file mode 100644
index 00000000..47ddc6ca
--- /dev/null
+++ b/project-access-denial-appeal-guard/reports/risky-audit.json
@@ -0,0 +1,266 @@
+{
+ "generatedAt": "2026-06-01T10:51:17.052Z",
+ "status": "HOLD",
+ "summary": "Hold final access decisions: 12 critical or high denial/appeal governance blocker(s) need remediation.",
+ "findingCounts": {
+ "critical": 3,
+ "high": 9,
+ "warning": 2
+ },
+ "findings": [
+ {
+ "code": "APPROVAL_WITH_MISSING_RESTRICTED_EVIDENCE",
+ "severity": "critical",
+ "message": "AR-2002 approves restricted human-data access while evidence is missing: duaActive, irbApproval.",
+ "evidence": "Restricted access appeals cannot override training, DUA, or IRB prerequisites.",
+ "path": "accessRequests[0].appeal.evidence",
+ "remediation": "Hold approval until all restricted-data prerequisites are present and current.",
+ "owner": "data steward",
+ "requestId": "AR-2002"
+ },
+ {
+ "code": "PRIVATE_APPEAL_NOTE_EXPOSED",
+ "severity": "critical",
+ "message": "AR-2002 appeal includes private notes without requester-safe redaction.",
+ "evidence": "Appeal responses must not reveal private collaborator notes or hidden institutional risk comments.",
+ "path": "accessRequests[0].appeal.redactedForRequester",
+ "remediation": "Redact private notes and publish only requester-safe appeal rationale.",
+ "owner": "privacy reviewer",
+ "requestId": "AR-2002"
+ },
+ {
+ "code": "PRIVATE_DENIAL_NOTE_EXPOSED",
+ "severity": "critical",
+ "message": "AR-2002 has private denial notes without requester-safe redaction.",
+ "evidence": "Denied-access notices must not leak hidden reviewer comments, protected collaborator details, or institutional security notes.",
+ "path": "accessRequests[0].denial.redactedForRequester",
+ "remediation": "Redact private notes and expose only a requester-safe reason code plus remediation checklist.",
+ "owner": "privacy reviewer",
+ "requestId": "AR-2002"
+ },
+ {
+ "code": "APPEAL_REVIEWER_NOT_INDEPENDENT",
+ "severity": "high",
+ "message": "AR-2002 appeal is reviewed by the same actor who denied access.",
+ "evidence": "Appeals require independence from the original denial decision.",
+ "path": "accessRequests[0].appeal.reviewerId",
+ "remediation": "Assign an independent data steward, institution admin, or ethics reviewer.",
+ "owner": "workspace admin",
+ "requestId": "AR-2002"
+ },
+ {
+ "code": "APPEAL_REVIEWER_ROLE_NOT_ELIGIBLE",
+ "severity": "high",
+ "message": "AR-2002 appeal reviewer role viewer is not eligible.",
+ "evidence": "Eligible appeal reviewer roles: data-steward, institution-admin, ethics-reviewer.",
+ "path": "accessRequests[0].appeal.reviewerRole",
+ "remediation": "Assign a reviewer with an eligible governance role or document a policy exception.",
+ "owner": "workspace admin",
+ "requestId": "AR-2002"
+ },
+ {
+ "code": "APPEAL_WINDOW_EXPIRED",
+ "severity": "high",
+ "message": "AR-2002 appeal was submitted after the 14-day appeal window.",
+ "evidence": "Late appeals need explicit exception approval before a denial decision is changed.",
+ "path": "accessRequests[0].appeal.submittedAt",
+ "remediation": "Route to institution-admin exception review or keep the denial final with a clear late-appeal receipt.",
+ "owner": "institution admin",
+ "requestId": "AR-2002"
+ },
+ {
+ "code": "BROAD_RESTRICTED_SCOPE_REQUEST",
+ "severity": "high",
+ "message": "AR-2002 requests broad access to a non-public project.",
+ "evidence": "Restricted projects should appeal access at the narrowest object and permission scope that supports the research need.",
+ "path": "accessRequests[0].requestedObjects",
+ "remediation": "Replace all-project scope with specific datasets, notebooks, manuscripts, or review threads and least-privilege permissions.",
+ "owner": "workspace admin",
+ "requestId": "AR-2002"
+ },
+ {
+ "code": "DENIAL_RATIONALE_TOO_THIN",
+ "severity": "high",
+ "message": "AR-2002 has an insufficient denial rationale.",
+ "evidence": "The user-facing decision needs enough context to be fair, appealable, and auditable.",
+ "path": "accessRequests[0].denial.rationale",
+ "remediation": "Document the missing requirement, affected object scope, and remediation path.",
+ "owner": "workspace admin",
+ "requestId": "AR-2002"
+ },
+ {
+ "code": "DENIAL_REASON_CODE_MISSING",
+ "severity": "high",
+ "message": "AR-2002 has no denial reason code.",
+ "evidence": "Researchers need a concrete denial basis before they can appeal or remediate access gaps.",
+ "path": "accessRequests[0].denial.reasonCode",
+ "remediation": "Add a stable denial reason code such as TRAINING_EXPIRED, DUA_MISSING, IRB_SCOPE_MISMATCH, or OBJECT_SCOPE_TOO_BROAD.",
+ "owner": "workspace admin",
+ "requestId": "AR-2002"
+ },
+ {
+ "code": "REQUIRED_APPEAL_EVIDENCE_MISSING",
+ "severity": "high",
+ "message": "AR-2002 is missing required evidence duaActive for restricted_human access.",
+ "evidence": "Appeal outcomes must prove the requester satisfied the project data-access prerequisites.",
+ "path": "accessRequests[0].appeal.evidence.duaActive",
+ "remediation": "Attach valid duaActive evidence or keep access denied with a remediation checklist.",
+ "owner": "data steward",
+ "requestId": "AR-2002"
+ },
+ {
+ "code": "REQUIRED_APPEAL_EVIDENCE_MISSING",
+ "severity": "high",
+ "message": "AR-2002 is missing required evidence irbApproval for restricted_human access.",
+ "evidence": "Appeal outcomes must prove the requester satisfied the project data-access prerequisites.",
+ "path": "accessRequests[0].appeal.evidence.irbApproval",
+ "remediation": "Attach valid irbApproval evidence or keep access denied with a remediation checklist.",
+ "owner": "data steward",
+ "requestId": "AR-2002"
+ },
+ {
+ "code": "USER_FACING_RECEIPT_MISSING",
+ "severity": "high",
+ "message": "AR-2002 has no requester-facing denial/appeal receipt.",
+ "evidence": "Researchers need a stable notice showing the decision, appeal path, and remediation checklist.",
+ "path": "accessRequests[0].auditReceipt.userFacingNotice",
+ "remediation": "Generate a requester-safe audit receipt with reason code, appeal deadline, outcome, and evidence checklist.",
+ "owner": "workspace admin",
+ "requestId": "AR-2002"
+ },
+ {
+ "code": "APPEAL_OUTCOME_NOT_IN_RECEIPT",
+ "severity": "warning",
+ "message": "AR-2002 appeal outcome is not included in the user receipt.",
+ "evidence": "The requester-facing receipt should close the loop after an appeal is reviewed.",
+ "path": "accessRequests[0].auditReceipt.fields",
+ "remediation": "Include appealOutcome and reviewerRole in the final appeal receipt.",
+ "owner": "workspace admin",
+ "requestId": "AR-2002"
+ },
+ {
+ "code": "AUDIT_RECEIPT_FIELDS_MISSING",
+ "severity": "warning",
+ "message": "AR-2002 audit receipt is missing fields: reasonCode, appealDeadline, evidenceChecklist.",
+ "evidence": "Complete receipts reduce support churn and make the appeal process auditable.",
+ "path": "accessRequests[0].auditReceipt.fields",
+ "remediation": "Add the missing receipt fields before finalizing the denial or appeal decision.",
+ "owner": "workspace admin",
+ "requestId": "AR-2002"
+ }
+ ],
+ "appealDecisions": [
+ {
+ "requestId": "AR-2002",
+ "requesterId": "researcher-99",
+ "projectId": "proj-human-cohort-9",
+ "classification": "restricted_human",
+ "appealState": "submitted",
+ "finalAction": "hold-decision",
+ "receiptState": "missing",
+ "findingCodes": [
+ "DENIAL_REASON_CODE_MISSING",
+ "DENIAL_RATIONALE_TOO_THIN",
+ "PRIVATE_DENIAL_NOTE_EXPOSED",
+ "APPEAL_WINDOW_EXPIRED",
+ "APPEAL_REVIEWER_NOT_INDEPENDENT",
+ "APPEAL_REVIEWER_ROLE_NOT_ELIGIBLE",
+ "PRIVATE_APPEAL_NOTE_EXPOSED",
+ "APPROVAL_WITH_MISSING_RESTRICTED_EVIDENCE",
+ "REQUIRED_APPEAL_EVIDENCE_MISSING",
+ "REQUIRED_APPEAL_EVIDENCE_MISSING",
+ "BROAD_RESTRICTED_SCOPE_REQUEST",
+ "USER_FACING_RECEIPT_MISSING",
+ "AUDIT_RECEIPT_FIELDS_MISSING",
+ "APPEAL_OUTCOME_NOT_IN_RECEIPT"
+ ]
+ }
+ ],
+ "remediationActions": [
+ {
+ "code": "APPROVAL_WITH_MISSING_RESTRICTED_EVIDENCE",
+ "requestId": "AR-2002",
+ "owner": "data steward",
+ "action": "Hold approval until all restricted-data prerequisites are present and current."
+ },
+ {
+ "code": "PRIVATE_APPEAL_NOTE_EXPOSED",
+ "requestId": "AR-2002",
+ "owner": "privacy reviewer",
+ "action": "Redact private notes and publish only requester-safe appeal rationale."
+ },
+ {
+ "code": "PRIVATE_DENIAL_NOTE_EXPOSED",
+ "requestId": "AR-2002",
+ "owner": "privacy reviewer",
+ "action": "Redact private notes and expose only a requester-safe reason code plus remediation checklist."
+ },
+ {
+ "code": "APPEAL_REVIEWER_NOT_INDEPENDENT",
+ "requestId": "AR-2002",
+ "owner": "workspace admin",
+ "action": "Assign an independent data steward, institution admin, or ethics reviewer."
+ },
+ {
+ "code": "APPEAL_REVIEWER_ROLE_NOT_ELIGIBLE",
+ "requestId": "AR-2002",
+ "owner": "workspace admin",
+ "action": "Assign a reviewer with an eligible governance role or document a policy exception."
+ },
+ {
+ "code": "APPEAL_WINDOW_EXPIRED",
+ "requestId": "AR-2002",
+ "owner": "institution admin",
+ "action": "Route to institution-admin exception review or keep the denial final with a clear late-appeal receipt."
+ },
+ {
+ "code": "BROAD_RESTRICTED_SCOPE_REQUEST",
+ "requestId": "AR-2002",
+ "owner": "workspace admin",
+ "action": "Replace all-project scope with specific datasets, notebooks, manuscripts, or review threads and least-privilege permissions."
+ },
+ {
+ "code": "DENIAL_RATIONALE_TOO_THIN",
+ "requestId": "AR-2002",
+ "owner": "workspace admin",
+ "action": "Document the missing requirement, affected object scope, and remediation path."
+ },
+ {
+ "code": "DENIAL_REASON_CODE_MISSING",
+ "requestId": "AR-2002",
+ "owner": "workspace admin",
+ "action": "Add a stable denial reason code such as TRAINING_EXPIRED, DUA_MISSING, IRB_SCOPE_MISMATCH, or OBJECT_SCOPE_TOO_BROAD."
+ },
+ {
+ "code": "REQUIRED_APPEAL_EVIDENCE_MISSING",
+ "requestId": "AR-2002",
+ "owner": "data steward",
+ "action": "Attach valid duaActive evidence or keep access denied with a remediation checklist."
+ },
+ {
+ "code": "REQUIRED_APPEAL_EVIDENCE_MISSING",
+ "requestId": "AR-2002",
+ "owner": "data steward",
+ "action": "Attach valid irbApproval evidence or keep access denied with a remediation checklist."
+ },
+ {
+ "code": "USER_FACING_RECEIPT_MISSING",
+ "requestId": "AR-2002",
+ "owner": "workspace admin",
+ "action": "Generate a requester-safe audit receipt with reason code, appeal deadline, outcome, and evidence checklist."
+ },
+ {
+ "code": "APPEAL_OUTCOME_NOT_IN_RECEIPT",
+ "requestId": "AR-2002",
+ "owner": "workspace admin",
+ "action": "Include appealOutcome and reviewerRole in the final appeal receipt."
+ },
+ {
+ "code": "AUDIT_RECEIPT_FIELDS_MISSING",
+ "requestId": "AR-2002",
+ "owner": "workspace admin",
+ "action": "Add the missing receipt fields before finalizing the denial or appeal decision."
+ }
+ ],
+ "fingerprint": "fde2d21c50c6dd89"
+}
diff --git a/project-access-denial-appeal-guard/reports/risky-review.md b/project-access-denial-appeal-guard/reports/risky-review.md
new file mode 100644
index 00000000..1d3dc39c
--- /dev/null
+++ b/project-access-denial-appeal-guard/reports/risky-review.md
@@ -0,0 +1,60 @@
+# Project Access Denial and Appeal Guard
+
+Packet: risky-access-appeals
+Status: HOLD
+Fingerprint: fde2d21c50c6dd89
+
+## Summary
+
+Hold final access decisions: 12 critical or high denial/appeal governance blocker(s) need remediation.
+
+## Appeal Decisions
+
+- AR-2002: hold-decision; 14 finding(s)
+ - Requester: researcher-99; project proj-human-cohort-9
+ - Appeal state: submitted; receipt missing
+
+## Findings
+
+- CRITICAL APPROVAL_WITH_MISSING_RESTRICTED_EVIDENCE: AR-2002 approves restricted human-data access while evidence is missing: duaActive, irbApproval.
+ - Evidence: Restricted access appeals cannot override training, DUA, or IRB prerequisites.
+ - Remediation: Hold approval until all restricted-data prerequisites are present and current.
+- CRITICAL PRIVATE_APPEAL_NOTE_EXPOSED: AR-2002 appeal includes private notes without requester-safe redaction.
+ - Evidence: Appeal responses must not reveal private collaborator notes or hidden institutional risk comments.
+ - Remediation: Redact private notes and publish only requester-safe appeal rationale.
+- CRITICAL PRIVATE_DENIAL_NOTE_EXPOSED: AR-2002 has private denial notes without requester-safe redaction.
+ - Evidence: Denied-access notices must not leak hidden reviewer comments, protected collaborator details, or institutional security notes.
+ - Remediation: Redact private notes and expose only a requester-safe reason code plus remediation checklist.
+- HIGH APPEAL_REVIEWER_NOT_INDEPENDENT: AR-2002 appeal is reviewed by the same actor who denied access.
+ - Evidence: Appeals require independence from the original denial decision.
+ - Remediation: Assign an independent data steward, institution admin, or ethics reviewer.
+- HIGH APPEAL_REVIEWER_ROLE_NOT_ELIGIBLE: AR-2002 appeal reviewer role viewer is not eligible.
+ - Evidence: Eligible appeal reviewer roles: data-steward, institution-admin, ethics-reviewer.
+ - Remediation: Assign a reviewer with an eligible governance role or document a policy exception.
+- HIGH APPEAL_WINDOW_EXPIRED: AR-2002 appeal was submitted after the 14-day appeal window.
+ - Evidence: Late appeals need explicit exception approval before a denial decision is changed.
+ - Remediation: Route to institution-admin exception review or keep the denial final with a clear late-appeal receipt.
+- HIGH BROAD_RESTRICTED_SCOPE_REQUEST: AR-2002 requests broad access to a non-public project.
+ - Evidence: Restricted projects should appeal access at the narrowest object and permission scope that supports the research need.
+ - Remediation: Replace all-project scope with specific datasets, notebooks, manuscripts, or review threads and least-privilege permissions.
+- HIGH DENIAL_RATIONALE_TOO_THIN: AR-2002 has an insufficient denial rationale.
+ - Evidence: The user-facing decision needs enough context to be fair, appealable, and auditable.
+ - Remediation: Document the missing requirement, affected object scope, and remediation path.
+- HIGH DENIAL_REASON_CODE_MISSING: AR-2002 has no denial reason code.
+ - Evidence: Researchers need a concrete denial basis before they can appeal or remediate access gaps.
+ - Remediation: Add a stable denial reason code such as TRAINING_EXPIRED, DUA_MISSING, IRB_SCOPE_MISMATCH, or OBJECT_SCOPE_TOO_BROAD.
+- HIGH REQUIRED_APPEAL_EVIDENCE_MISSING: AR-2002 is missing required evidence duaActive for restricted_human access.
+ - Evidence: Appeal outcomes must prove the requester satisfied the project data-access prerequisites.
+ - Remediation: Attach valid duaActive evidence or keep access denied with a remediation checklist.
+- HIGH REQUIRED_APPEAL_EVIDENCE_MISSING: AR-2002 is missing required evidence irbApproval for restricted_human access.
+ - Evidence: Appeal outcomes must prove the requester satisfied the project data-access prerequisites.
+ - Remediation: Attach valid irbApproval evidence or keep access denied with a remediation checklist.
+- HIGH USER_FACING_RECEIPT_MISSING: AR-2002 has no requester-facing denial/appeal receipt.
+ - Evidence: Researchers need a stable notice showing the decision, appeal path, and remediation checklist.
+ - Remediation: Generate a requester-safe audit receipt with reason code, appeal deadline, outcome, and evidence checklist.
+- WARNING APPEAL_OUTCOME_NOT_IN_RECEIPT: AR-2002 appeal outcome is not included in the user receipt.
+ - Evidence: The requester-facing receipt should close the loop after an appeal is reviewed.
+ - Remediation: Include appealOutcome and reviewerRole in the final appeal receipt.
+- WARNING AUDIT_RECEIPT_FIELDS_MISSING: AR-2002 audit receipt is missing fields: reasonCode, appealDeadline, evidenceChecklist.
+ - Evidence: Complete receipts reduce support churn and make the appeal process auditable.
+ - Remediation: Add the missing receipt fields before finalizing the denial or appeal decision.
diff --git a/project-access-denial-appeal-guard/reports/summary.svg b/project-access-denial-appeal-guard/reports/summary.svg
new file mode 100644
index 00000000..82e4286e
--- /dev/null
+++ b/project-access-denial-appeal-guard/reports/summary.svg
@@ -0,0 +1,13 @@
+
\ No newline at end of file
diff --git a/project-access-denial-appeal-guard/sample-data.js b/project-access-denial-appeal-guard/sample-data.js
new file mode 100644
index 00000000..53793de8
--- /dev/null
+++ b/project-access-denial-appeal-guard/sample-data.js
@@ -0,0 +1,121 @@
+"use strict";
+
+const cleanPacket = {
+ id: "clean-access-appeals",
+ accessRequests: [
+ {
+ id: "AR-1001",
+ requester: {
+ id: "researcher-42",
+ institution: "Northeast Medical Lab",
+ orcidVerified: true,
+ mfaFresh: true
+ },
+ project: {
+ id: "proj-human-cohort-7",
+ classification: "restricted_human",
+ visibility: "institutional-only"
+ },
+ requestedObjects: [
+ {
+ id: "dataset-redacted-v2",
+ path: "data/redacted-cohort-v2.csv",
+ permission: "download",
+ classification: "restricted_human"
+ },
+ {
+ id: "notebook-analysis",
+ path: "notebooks/reproduce.ipynb",
+ permission: "read",
+ classification: "institutional_only"
+ }
+ ],
+ denial: {
+ deniedAt: "2026-05-20T12:00:00.000Z",
+ deniedBy: "steward-a",
+ reasonCode: "IRB_SCOPE_REVIEW_REQUIRED",
+ rationale: "The requested cohort export needs an IRB scope check before download access can be granted.",
+ outcome: "deny-with-remediation",
+ privateNotes: "Internal reviewer note: contact PI before export.",
+ redactedForRequester: true
+ },
+ appeal: {
+ submittedAt: "2026-05-24T09:00:00.000Z",
+ reviewerId: "ethics-reviewer-b",
+ reviewerRole: "ethics-reviewer",
+ decision: "uphold-denial",
+ rationale: "Appeal reviewed. Access remains denied until the amended IRB letter is attached.",
+ redactedForRequester: true,
+ evidence: {
+ trainingValid: true,
+ duaActive: true,
+ irbApproval: true
+ }
+ },
+ auditReceipt: {
+ userFacingNotice: true,
+ fields: ["reasonCode", "appealDeadline", "evidenceChecklist", "decisionTimestamp", "appealOutcome", "reviewerRole"]
+ }
+ }
+ ]
+};
+
+const riskyPacket = {
+ id: "risky-access-appeals",
+ accessRequests: [
+ {
+ id: "AR-2002",
+ requester: {
+ id: "researcher-99",
+ institution: "External Partner Lab",
+ orcidVerified: false,
+ mfaFresh: false
+ },
+ project: {
+ id: "proj-human-cohort-9",
+ classification: "restricted_human",
+ visibility: "invitation-only"
+ },
+ requestedObjects: [
+ {
+ id: "all-project",
+ path: "*",
+ scope: "all-project",
+ permission: "download",
+ classification: "restricted_human"
+ }
+ ],
+ denial: {
+ deniedAt: "2026-05-01T12:00:00.000Z",
+ deniedBy: "steward-a",
+ reasonCode: "",
+ rationale: "No.",
+ privateNotes: "Private: reviewer suspects a competing project conflict.",
+ redactedForRequester: false
+ },
+ appeal: {
+ submittedAt: "2026-05-25T09:00:00.000Z",
+ reviewerId: "steward-a",
+ reviewerRole: "viewer",
+ decision: "approve",
+ rationale: "Approved after appeal.",
+ privateNotes: "Internal note: sponsor did not respond.",
+ redactedForRequester: false,
+ evidence: {
+ trainingValid: true,
+ duaActive: false,
+ irbApproval: false
+ }
+ },
+ auditReceipt: {
+ userFacingNotice: false,
+ fields: ["decisionTimestamp"]
+ }
+ }
+ ]
+};
+
+module.exports = {
+ cleanPacket,
+ riskyPacket
+};
diff --git a/project-access-denial-appeal-guard/test.js b/project-access-denial-appeal-guard/test.js
new file mode 100644
index 00000000..3de48def
--- /dev/null
+++ b/project-access-denial-appeal-guard/test.js
@@ -0,0 +1,36 @@
+"use strict";
+
+const assert = require("node:assert/strict");
+const {
+ evaluateAccessDenialAppeals,
+ renderMarkdownReport,
+ renderSvgSummary
+} = require("./index");
+const { cleanPacket, riskyPacket } = require("./sample-data");
+
+const clean = evaluateAccessDenialAppeals(cleanPacket, { now: "2026-06-01T11:00:00.000Z" });
+assert.equal(clean.status, "READY");
+assert.equal(clean.findings.length, 0);
+assert.equal(clean.appealDecisions.length, 1);
+assert.equal(clean.appealDecisions[0].finalAction, "uphold-denial");
+
+const risky = evaluateAccessDenialAppeals(riskyPacket, { now: "2026-06-01T11:00:00.000Z" });
+assert.equal(risky.status, "HOLD");
+assert.ok(risky.findings.some((finding) => finding.code === "PRIVATE_DENIAL_NOTE_EXPOSED"));
+assert.ok(risky.findings.some((finding) => finding.code === "PRIVATE_APPEAL_NOTE_EXPOSED"));
+assert.ok(risky.findings.some((finding) => finding.code === "APPEAL_WINDOW_EXPIRED"));
+assert.ok(risky.findings.some((finding) => finding.code === "APPEAL_REVIEWER_NOT_INDEPENDENT"));
+assert.ok(risky.findings.some((finding) => finding.code === "APPROVAL_WITH_MISSING_RESTRICTED_EVIDENCE"));
+assert.equal(risky.appealDecisions[0].finalAction, "hold-decision");
+
+const markdown = renderMarkdownReport(risky, riskyPacket);
+assert.match(markdown, /Project Access Denial and Appeal Guard/);
+assert.match(markdown, /PRIVATE_DENIAL_NOTE_EXPOSED/);
+
+const svg = renderSvgSummary(risky);
+assert.match(svg, /