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
36 changes: 36 additions & 0 deletions enterprise-dashboard-cohort-privacy-guard/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Enterprise Dashboard Cohort Privacy Guard

This module adds a focused Enterprise Tooling slice for issue #19: privacy review for organization-wide admin dashboard metrics before they are shown or exported.

It evaluates synthetic institutional dashboard widgets for:

- small-cohort suppression with k-anonymity thresholds
- named contributor heatmap re-identification risk
- private or invitation-only project label masking
- cross-lab and department rollup minimums
- direct identifier blocking
- sensitive initiative tag masking
- raw CSV, webhook, and external BI export blocks when suppressed rows are present

The module is dependency-free and uses only synthetic fixtures. It does not call live analytics systems, identity providers, export services, billing systems, or third-party APIs.

## Run

```bash
npm run check
npm test
npm run demo
```

`npm run demo` writes reviewer artifacts under `reports/`:

- `dashboard-privacy-packet.json`
- `dashboard-privacy-report.md`
- `summary.svg`
- `demo.mp4`

## Scope

This is intentionally distinct from broad admin dashboards, export pipelines, webhook replay ledgers, identity provisioning drift, retention/legal hold, data residency, SLA, secret rotation, quotas, API change governance, connector certification, incident response, funder exports, model governance, initiative tags, policy exceptions, IRB consent, SCIM deprovisioning, repository deposit reconciliation, notification escalation, cost allocation, LMS passback, payload redaction, and vendor DPA review.

It covers the privacy trust boundary for aggregated dashboard metrics: what can be safely displayed or exported to institutional admins without exposing small groups, private project activity, or direct identifiers.
35 changes: 35 additions & 0 deletions enterprise-dashboard-cohort-privacy-guard/acceptance-notes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Acceptance Notes

The reviewer can verify this bounty slice without any external account or service.

## Expected Synthetic Result

The demo fixture produces:

- 4 dashboard widgets reviewed
- 8 dashboard rows reviewed
- 1 widget held for privacy review
- 3 widgets released only after masking or rollup
- 1 suppressed row
- 3 masked or rolled-up rows
- 1 raw export block

The critical case is `heatmap-private-oncology`: it is a private, recent, named-contributor activity row with a cohort smaller than the policy threshold and direct identifiers. The guard suppresses it, blocks raw exports, and emits admin remediation actions.

## Validation

```bash
npm run check
npm test
npm run demo
```

Optional video metadata check:

```bash
ffprobe -v error -select_streams v:0 -show_entries stream=codec_name,width,height,duration -show_entries format=size,duration -of default=noprint_wrappers=1 reports/demo.mp4
```

## Boundaries

This module does not implement a production dashboard UI, data warehouse, live webhook delivery, identity sync, access control, billing, or export adapter. It provides a deterministic privacy gate and reviewer packet for the enterprise dashboard trust boundary.
93 changes: 93 additions & 0 deletions enterprise-dashboard-cohort-privacy-guard/demo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
const fs = require("node:fs")
const path = require("node:path")
const { spawnSync } = require("node:child_process")
const { evaluateDashboardPrivacy } = require("./index")
const { dashboards, organization, privacyPolicy } = require("./sample-data")

const reportsDir = path.join(__dirname, "reports")
fs.mkdirSync(reportsDir, { recursive: true })

const packet = evaluateDashboardPrivacy({
asOf: "2026-05-23T00:00:00.000Z",
organization,
dashboards,
policy: privacyPolicy,
})

fs.writeFileSync(
path.join(reportsDir, "dashboard-privacy-packet.json"),
`${JSON.stringify(packet, null, 2)}\n`,
)

const markdown = [
"# Enterprise Dashboard Cohort Privacy Report",
"",
`Organization: ${packet.organization.name}`,
`Widgets reviewed: ${packet.summary.totalWidgets}`,
`Rows reviewed: ${packet.summary.totalRows}`,
`Hold for privacy review: ${packet.summary.hold_for_privacy_review}`,
`Masked ready: ${packet.summary.masked_ready}`,
`Suppressed rows: ${packet.summary.suppressedRows}`,
`Masked or rolled-up rows: ${packet.summary.maskedRows}`,
`Blocked raw exports: ${packet.summary.blockedRawExports}`,
`Audit digest: \`${packet.audit.digest}\``,
"",
"## Dashboard Decisions",
...packet.dashboards.flatMap((dashboard) => [
"",
`### ${dashboard.title}`,
`- Status: ${dashboard.status}`,
`- Rows reviewed: ${dashboard.reviewedRows}`,
`- Suppressed rows: ${dashboard.suppressedRows}`,
`- Masked rows: ${dashboard.maskedRows}`,
`- Findings: ${dashboard.findings.map((finding) => finding.code).join(", ") || "none"}`,
`- Reviewer note: ${dashboard.reviewerNotes[0]}`,
]),
"",
"## Admin Actions",
...packet.adminActions.map((action) => (
`- ${action.dashboardId}/${action.rowId}: ${action.action} (${action.reasons.join(", ")})`
)),
"",
]

fs.writeFileSync(path.join(reportsDir, "dashboard-privacy-report.md"), markdown.join("\n"))

const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="960" height="540" viewBox="0 0 960 540">
<rect width="960" height="540" fill="#0f172a"/>
<text x="48" y="76" fill="#f8fafc" font-family="Arial" font-size="34" font-weight="700">Enterprise Dashboard Cohort Privacy Guard</text>
<text x="48" y="120" fill="#cbd5e1" font-family="Arial" font-size="18">Small-cohort suppression and export masking for institutional analytics</text>
<rect x="48" y="168" width="250" height="152" rx="12" fill="#991b1b"/>
<text x="76" y="230" fill="#fee2e2" font-family="Arial" font-size="56" font-weight="700">${packet.summary.hold_for_privacy_review}</text>
<text x="76" y="270" fill="#fecaca" font-family="Arial" font-size="22">privacy holds</text>
<rect x="355" y="168" width="250" height="152" rx="12" fill="#0369a1"/>
<text x="383" y="230" fill="#e0f2fe" font-family="Arial" font-size="56" font-weight="700">${packet.summary.masked_ready}</text>
<text x="383" y="270" fill="#bae6fd" font-family="Arial" font-size="22">masked ready</text>
<rect x="662" y="168" width="250" height="152" rx="12" fill="#166534"/>
<text x="690" y="230" fill="#dcfce7" font-family="Arial" font-size="56" font-weight="700">${packet.summary.totalRows}</text>
<text x="690" y="270" fill="#bbf7d0" font-family="Arial" font-size="22">rows reviewed</text>
<text x="48" y="382" fill="#e2e8f0" font-family="Arial" font-size="20">Controls: k-anonymity, named-contributor suppression, private project masking, rollups, raw export blocks.</text>
<text x="48" y="424" fill="#94a3b8" font-family="Arial" font-size="16">Digest ${packet.audit.digest.slice(0, 28)}...</text>
</svg>
`

fs.writeFileSync(path.join(reportsDir, "summary.svg"), svg)

const ffmpeg = spawnSync("ffmpeg", [
"-y",
"-f",
"lavfi",
"-i",
"color=c=0x0f172a:s=960x540:d=5:r=15",
"-vf",
"drawbox=x=48:y=168:w=250:h=152:color=0x991b1b@1:t=fill,drawbox=x=355:y=168:w=250:h=152:color=0x0369a1@1:t=fill,drawbox=x=662:y=168:w=250:h=152:color=0x166534@1:t=fill,drawbox=x=48:y=365:w=864:h=18:color=0x14b8a6@1:t=fill",
"-pix_fmt",
"yuv420p",
path.join(reportsDir, "demo.mp4"),
], { stdio: "ignore" })

if (ffmpeg.status !== 0) {
console.warn("ffmpeg video generation failed; JSON, Markdown, and SVG reports were still generated.")
}

console.log(`Wrote enterprise dashboard privacy artifacts to ${reportsDir}`)
Loading