Skip to content
Open
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
23 changes: 23 additions & 0 deletions repository-access-review-gate/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Repository Access Review Gate

This self-contained slice adds a release-time access review for scientific project repositories.
It checks whether collaborators, reviewer links, restricted datasets, and artifact licenses are safe before a tagged repository export is made public.

The module is intentionally dependency-free and uses synthetic data only.

## What it produces

- `releaseStatus`: `ready` or `hold`.
- `releaseHolds`: deterministic blockers for stale external reviewers, expired data-use approvals, privileged role drift, secret-link exposure, and license/access mismatch.
- `accessMatrix`: per-collaborator keep/revoke/downgrade decisions.
- `reviewerChecklist`: action-ready remediation items for repository owners and data stewards.
- `auditDigest`: SHA-256 digest over the packet for reviewer evidence.

## Run locally

```bash
node repository-access-review-gate/test.js
node repository-access-review-gate/demo.js
```

The demo writes JSON, Markdown, SVG, and MP4 artifacts to `repository-access-review-gate/demo-output/`.
7 changes: 7 additions & 0 deletions repository-access-review-gate/acceptance-notes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Acceptance Notes

- The module stays self-contained under `repository-access-review-gate/`.
- No credentials, network calls, or live user data are used.
- Tests cover both a blocked release and a ready release with a warning-only temporary access case.
- Demo artifacts include reviewer-friendly JSON, Markdown, SVG, and a short MP4.
- The implementation is intentionally separate from prior issue #10 submissions such as repository ledgers, release engines, schema migration, merge queue governance, and environment drift gates.
Binary file not shown.
13 changes: 13 additions & 0 deletions repository-access-review-gate/demo-output/demo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
107 changes: 107 additions & 0 deletions repository-access-review-gate/demo-output/release-hold-packet.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
{
"packetType": "repository-access-review-gate",
"repositoryId": "repo-sci-artery-flow-042",
"releaseTag": "v2.1.0",
"releaseStatus": "hold",
"generatedAt": "2026-05-20T12:00:00Z",
"releaseHolds": [
{
"code": "external_reviewer_expired",
"severity": "blocker",
"owner": "u-ext-17",
"evidence": "External Reviewer 17 review access expired on 2026-05-01T00:00:00Z",
"requiredAction": "Revoke reviewer access or issue a fresh time-boxed invite before release."
},
{
"code": "license_access_mismatch",
"severity": "blocker",
"owner": "data-steward",
"evidence": "data/restricted-waveforms.parquet is restricted-dua but v2.1.0 targets public export.",
"requiredAction": "Block public export or replace the artifact with a license-compatible derivative."
},
{
"code": "privileged_role_without_release_scope",
"severity": "blocker",
"owner": "u-admin-2",
"evidence": "Samir Dev is admin but is not approved for v2.1.0.",
"requiredAction": "Downgrade the role or record release-scope approval from a repository owner."
},
{
"code": "restricted_dataset_approval_expired",
"severity": "blocker",
"owner": "u-ext-17",
"evidence": "External Reviewer 17 has restricted-data download access with an expired DUA approval.",
"requiredAction": "Remove restricted-data access until the data-use approval is renewed."
},
{
"code": "secret_link_exposure",
"severity": "blocker",
"owner": "security-review",
"evidence": "1 artifact link(s) expose secret-bearing URLs in the release packet.",
"requiredAction": "Replace secret links with scoped repository object grants before export."
}
],
"reviewerChecklist": [
{
"owner": "u-ext-17",
"requiredAction": "Revoke reviewer access or issue a fresh time-boxed invite before release.",
"evidence": "External Reviewer 17 review access expired on 2026-05-01T00:00:00Z"
},
{
"owner": "data-steward",
"requiredAction": "Block public export or replace the artifact with a license-compatible derivative.",
"evidence": "data/restricted-waveforms.parquet is restricted-dua but v2.1.0 targets public export."
},
{
"owner": "u-admin-2",
"requiredAction": "Downgrade the role or record release-scope approval from a repository owner.",
"evidence": "Samir Dev is admin but is not approved for v2.1.0."
},
{
"owner": "u-ext-17",
"requiredAction": "Remove restricted-data access until the data-use approval is renewed.",
"evidence": "External Reviewer 17 has restricted-data download access with an expired DUA approval."
},
{
"owner": "security-review",
"requiredAction": "Replace secret links with scoped repository object grants before export.",
"evidence": "1 artifact link(s) expose secret-bearing URLs in the release packet."
}
],
"accessMatrix": [
{
"userId": "u-owner-1",
"name": "Dr. Amina Park",
"role": "owner",
"scope": "repository-admin",
"releaseApproved": true,
"decision": "keep"
},
{
"userId": "u-admin-2",
"name": "Samir Dev",
"role": "admin",
"scope": "all-components",
"releaseApproved": false,
"decision": "downgrade_before_release"
},
{
"userId": "u-ext-17",
"name": "External Reviewer 17",
"role": "reviewer",
"scope": "manuscript,data",
"releaseApproved": true,
"decision": "revoke_before_release"
},
{
"userId": "u-analyst-4",
"name": "Mina Analyst",
"role": "maintainer",
"scope": "code,results",
"releaseApproved": true,
"decision": "keep"
}
],
"warnings": [],
"auditDigest": "8f8ae3250c816f296ba59f67236e29f9ccd8ae375092da4c7dc8dd59c5da9ff3"
}
15 changes: 15 additions & 0 deletions repository-access-review-gate/demo-output/reviewer-packet.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Repository access review gate demo

Repository: repo-sci-artery-flow-042
Release: v2.1.0
Status: hold
Blocking holds: 5

## Required reviewer actions
- u-ext-17: Revoke reviewer access or issue a fresh time-boxed invite before release.
- data-steward: Block public export or replace the artifact with a license-compatible derivative.
- u-admin-2: Downgrade the role or record release-scope approval from a repository owner.
- u-ext-17: Remove restricted-data access until the data-use approval is renewed.
- security-review: Replace secret links with scoped repository object grants before export.

Audit digest: 8f8ae3250c816f296ba59f67236e29f9ccd8ae375092da4c7dc8dd59c5da9ff3
81 changes: 81 additions & 0 deletions repository-access-review-gate/demo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
const fs = require('fs');
const path = require('path');
const { execFileSync } = require('child_process');
const { buildReleaseHoldPacket } = require('./index');
const { sampleRepository } = require('./sample-data');

const outputDir = path.join(__dirname, 'demo-output');
fs.mkdirSync(outputDir, { recursive: true });

const packet = buildReleaseHoldPacket(sampleRepository, {
asOf: '2026-05-20T12:00:00Z',
releaseTag: 'v2.1.0',
});

fs.writeFileSync(path.join(outputDir, 'release-hold-packet.json'), `${JSON.stringify(packet, null, 2)}\n`);

const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="720" viewBox="0 0 1280 720">
<rect width="1280" height="720" fill="#111827"/>
<rect x="70" y="70" width="1140" height="580" rx="18" fill="#f8fafc"/>
<text x="110" y="145" font-family="Arial" font-size="46" font-weight="700" fill="#0f172a">Repository Access Review Gate</text>
<text x="110" y="205" font-family="Arial" font-size="25" fill="#334155">${packet.repositoryId} / ${packet.releaseTag}</text>
<text x="110" y="285" font-family="Arial" font-size="34" font-weight="700" fill="#b91c1c">${packet.releaseHolds.length} release holds before export</text>
${packet.releaseHolds
.map(
(hold, index) =>
`<text x="130" y="${350 + index * 45}" font-family="Arial" font-size="24" fill="#1f2937">${index + 1}. ${hold.code} -> ${hold.owner}</text>`,
)
.join('\n ')}
<text x="110" y="620" font-family="Arial" font-size="22" fill="#475569">audit digest: ${packet.auditDigest.slice(0, 24)}...</text>
</svg>
`;
fs.writeFileSync(path.join(outputDir, 'demo.svg'), svg);

const markdown = [
'# Repository access review gate demo',
'',
`Repository: ${packet.repositoryId}`,
`Release: ${packet.releaseTag}`,
`Status: ${packet.releaseStatus}`,
`Blocking holds: ${packet.releaseHolds.length}`,
'',
'## Required reviewer actions',
...packet.reviewerChecklist.map((item) => `- ${item.owner}: ${item.requiredAction}`),
'',
`Audit digest: ${packet.auditDigest}`,
'',
].join('\n');
fs.writeFileSync(path.join(outputDir, 'reviewer-packet.md'), markdown);

function renderMp4() {
const videoPath = path.join(outputDir, 'demo.mp4');
const font = 'C\\:/Windows/Fonts/arial.ttf';
const text = (value) => String(value).replace(/\\/g, '\\\\').replace(/:/g, '\\:').replace(/'/g, "\\'");
const filters = [
`drawtext=fontfile='${font}':text='${text('Repository Access Review Gate')}':x=70:y=80:fontsize=44:fontcolor=white`,
`drawtext=fontfile='${font}':text='${text(`${packet.releaseHolds.length} blocking release holds detected`)}':x=70:y=160:fontsize=34:fontcolor=0xffd166`,
...packet.releaseHolds.slice(0, 5).map((hold, index) =>
`drawtext=fontfile='${font}':text='${text(`${index + 1}. ${hold.code} -> ${hold.owner}`)}':x=90:y=${240 + index * 54}:fontsize=27:fontcolor=white`,
),
`drawtext=fontfile='${font}':text='${text(`audit ${packet.auditDigest.slice(0, 20)}...`)}':x=70:y=630:fontsize=24:fontcolor=0x93c5fd`,
].join(',');

execFileSync('ffmpeg', [
'-y',
'-f',
'lavfi',
'-i',
'color=c=0x111827:s=1280x720:d=7',
'-vf',
filters,
'-c:v',
'libx264',
'-pix_fmt',
'yuv420p',
videoPath,
], { stdio: 'inherit' });
}

renderMp4();

console.log(`Wrote demo artifacts to ${outputDir}`);
Loading