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
14 changes: 14 additions & 0 deletions project-role-delegation-guard/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Project Role Delegation Guard

This self-contained slice reviews temporary delegated authority before sensitive project actions such as publication, restricted-data export, archive freeze, and role transfer.

The guard checks sponsor approval, separation of duties, identity posture, object-level scope, and expiry. It produces deterministic reviewer packets so project owners can approve or block delegated actions without relying on ad hoc comments.

## Run locally

```bash
node project-role-delegation-guard/test.js
node project-role-delegation-guard/demo.js
```

Demo outputs are written to `project-role-delegation-guard/demo-output/`.
6 changes: 6 additions & 0 deletions project-role-delegation-guard/acceptance-notes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Acceptance Notes

- The module is dependency-free and uses synthetic data only.
- Tests cover blocked sensitive delegations, deterministic packet ordering, and a ready case with a near-expiry warning.
- Demo artifacts include JSON, Markdown, SVG, and MP4 output.
- The slice is intentionally separate from previous #11 submissions around broad RBAC ledgers, offboarding, access recertification, anonymous review, data-room consent, project archive handoff, profile sync, and audit anomaly monitoring.
11 changes: 11 additions & 0 deletions project-role-delegation-guard/demo-output/approval-checklist.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Delegation approval checklist

Workspace: project-neuro-catalyst
Status: hold

- del-archive-5: Narrow the object-level delegation scope before approving the action.
- del-export-7: Block the delegation until MFA, SAML, and ORCID posture requirements are satisfied.
- del-publish-2: Record sponsor approval or route the action back to the project owner.
- del-transfer-3: Route approval to an independent owner or institutional sponsor.

Audit digest: eb75b6a2db4222fa1c946655d7ea70920e1685e0ed6e5303d829ea16dc51b0ed
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
{
"packetType": "project-role-delegation-guard",
"workspaceId": "project-neuro-catalyst",
"generatedAt": "2026-05-20T12:00:00Z",
"overallStatus": "hold",
"decisions": [
{
"requestId": "del-publish-2",
"action": "publish_release",
"delegateId": "delegate-2",
"decision": "needs_sponsor",
"expiresAt": "2026-06-20T00:00:00Z"
},
{
"requestId": "del-export-7",
"action": "restricted_data_export",
"delegateId": "delegate-7",
"decision": "block",
"expiresAt": "2026-06-10T00:00:00Z"
},
{
"requestId": "del-transfer-3",
"action": "role_transfer",
"delegateId": "delegate-2",
"decision": "block",
"expiresAt": "2026-06-15T00:00:00Z"
},
{
"requestId": "del-archive-5",
"action": "archive_freeze",
"delegateId": "delegate-9",
"decision": "block",
"expiresAt": "2026-06-15T00:00:00Z"
}
],
"holds": [
{
"requestId": "del-archive-5",
"code": "delegated_scope_exceeds_action",
"owner": "delegate-9",
"severity": "blocker",
"evidence": "del-archive-5 includes excess scope(s): repository:admin.",
"requiredAction": "Narrow the object-level delegation scope before approving the action."
},
{
"requestId": "del-export-7",
"code": "identity_posture_gap",
"owner": "delegate-7",
"severity": "blocker",
"evidence": "Export Contractor is missing mfa, orcid for restricted_data_export.",
"requiredAction": "Block the delegation until MFA, SAML, and ORCID posture requirements are satisfied."
},
{
"requestId": "del-publish-2",
"code": "missing_sponsor_approval",
"owner": "owner-1",
"severity": "blocker",
"evidence": "publish_release requires sponsor approval before delegate-2 can act.",
"requiredAction": "Record sponsor approval or route the action back to the project owner."
},
{
"requestId": "del-transfer-3",
"code": "separation_of_duties_violation",
"owner": "delegate-2",
"severity": "blocker",
"evidence": "delegate-2 cannot both request/delegate and approve role_transfer.",
"requiredAction": "Route approval to an independent owner or institutional sponsor."
}
],
"warnings": [],
"approvalChecklist": [
{
"requestId": "del-archive-5",
"owner": "delegate-9",
"requiredAction": "Narrow the object-level delegation scope before approving the action.",
"evidence": "del-archive-5 includes excess scope(s): repository:admin."
},
{
"requestId": "del-export-7",
"owner": "delegate-7",
"requiredAction": "Block the delegation until MFA, SAML, and ORCID posture requirements are satisfied.",
"evidence": "Export Contractor is missing mfa, orcid for restricted_data_export."
},
{
"requestId": "del-publish-2",
"owner": "owner-1",
"requiredAction": "Record sponsor approval or route the action back to the project owner.",
"evidence": "publish_release requires sponsor approval before delegate-2 can act."
},
{
"requestId": "del-transfer-3",
"owner": "delegate-2",
"requiredAction": "Route approval to an independent owner or institutional sponsor.",
"evidence": "delegate-2 cannot both request/delegate and approve role_transfer."
}
],
"auditDigest": "eb75b6a2db4222fa1c946655d7ea70920e1685e0ed6e5303d829ea16dc51b0ed"
}
Binary file not shown.
12 changes: 12 additions & 0 deletions project-role-delegation-guard/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.
74 changes: 74 additions & 0 deletions project-role-delegation-guard/demo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
const fs = require('fs');
const path = require('path');
const { execFileSync } = require('child_process');
const { buildDelegationReviewPacket } = require('./index');
const { sampleWorkspace } = require('./sample-data');

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

const packet = buildDelegationReviewPacket(sampleWorkspace, {
asOf: '2026-05-20T12:00:00Z',
});

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

const rows = packet.holds
.map((hold, index) => `<text x="120" y="${320 + index * 48}" font-family="Arial" font-size="23" fill="#263238">${index + 1}. ${hold.requestId}: ${hold.code}</text>`)
.join('\n ');

fs.writeFileSync(path.join(outputDir, 'demo.svg'), `<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="720">
<rect width="1280" height="720" fill="#0b1320"/>
<rect x="70" y="70" width="1140" height="580" rx="18" fill="#ffffff"/>
<text x="110" y="145" font-family="Arial" font-size="45" font-weight="700" fill="#102a43">Project Role Delegation Guard</text>
<text x="110" y="205" font-family="Arial" font-size="25" fill="#486581">${packet.workspaceId}</text>
<text x="110" y="275" font-family="Arial" font-size="34" font-weight="700" fill="#b42318">${packet.holds.length} sensitive delegation holds</text>
${rows}
<text x="110" y="620" font-family="Arial" font-size="22" fill="#627d98">audit digest: ${packet.auditDigest.slice(0, 24)}...</text>
</svg>
`);

fs.writeFileSync(path.join(outputDir, 'approval-checklist.md'), [
'# Delegation approval checklist',
'',
`Workspace: ${packet.workspaceId}`,
`Status: ${packet.overallStatus}`,
'',
...packet.approvalChecklist.map((item) => `- ${item.requestId}: ${item.requiredAction}`),
'',
`Audit digest: ${packet.auditDigest}`,
'',
].join('\n'));

function renderMp4() {
const videoPath = path.join(outputDir, 'demo.mp4');
const font = 'C\\:/Windows/Fonts/arial.ttf';
const escapeText = (value) => String(value).replace(/\\/g, '\\\\').replace(/:/g, '\\:').replace(/'/g, "\\'");
const filters = [
`drawtext=fontfile='${font}':text='${escapeText('Project Role Delegation Guard')}':x=70:y=80:fontsize=44:fontcolor=white`,
`drawtext=fontfile='${font}':text='${escapeText(`${packet.holds.length} approval blockers found`)}':x=70:y=155:fontsize=34:fontcolor=0xffd166`,
...packet.holds.map((hold, index) =>
`drawtext=fontfile='${font}':text='${escapeText(`${hold.requestId}: ${hold.code}`)}':x=90:y=${235 + index * 58}:fontsize=28:fontcolor=white`,
),
`drawtext=fontfile='${font}':text='${escapeText(`audit ${packet.auditDigest.slice(0, 20)}...`)}':x=70:y=630:fontsize=24:fontcolor=0x93c5fd`,
].join(',');

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

renderMp4();

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