From 3ccba3f74d81d869a3c825969c21b0fb85917b6c Mon Sep 17 00:00:00 2001 From: shaiananvari8 Date: Sat, 13 Jun 2026 19:00:18 -0500 Subject: [PATCH 01/10] Add enterprise token rotation guard package --- enterprise-token-rotation-guard/package.json | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 enterprise-token-rotation-guard/package.json diff --git a/enterprise-token-rotation-guard/package.json b/enterprise-token-rotation-guard/package.json new file mode 100644 index 00000000..d71d33bf --- /dev/null +++ b/enterprise-token-rotation-guard/package.json @@ -0,0 +1,10 @@ +{ + "name": "scibase-enterprise-token-rotation-guard", + "version": "1.0.0", + "type": "module", + "private": true, + "scripts": { + "test": "node --test test/*.test.js", + "demo": "node scripts/demo.js" + } +} From d81c11914083eb0a67c431543fdbe38ae5f697b4 Mon Sep 17 00:00:00 2001 From: shaiananvari8 Date: Sat, 13 Jun 2026 19:00:25 -0500 Subject: [PATCH 02/10] Document enterprise token rotation guard --- enterprise-token-rotation-guard/readme.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 enterprise-token-rotation-guard/readme.md diff --git a/enterprise-token-rotation-guard/readme.md b/enterprise-token-rotation-guard/readme.md new file mode 100644 index 00000000..23e4105d --- /dev/null +++ b/enterprise-token-rotation-guard/readme.md @@ -0,0 +1,14 @@ +# Enterprise Token Rotation Guard + +This module contributes to SCIBASE issue #19, Enterprise Tooling. + +It evaluates institutional API integrations before admins keep them active. The guard checks token rotation age, least-privilege scopes, active owner assignment, audit export recency, SSO group drift, and restricted-data approvals. + +## Local Verification + +```bash +npm test +npm run demo +``` + +The demo data is synthetic and does not contact SSO providers, institutional repositories, or production APIs. From 76cdff704d1e550c07c3a03f02eb036d66de3976 Mon Sep 17 00:00:00 2001 From: shaiananvari8 Date: Sat, 13 Jun 2026 19:01:19 -0500 Subject: [PATCH 03/10] Implement enterprise token rotation guard --- enterprise-token-rotation-guard/src/index.js | 180 +++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 enterprise-token-rotation-guard/src/index.js diff --git a/enterprise-token-rotation-guard/src/index.js b/enterprise-token-rotation-guard/src/index.js new file mode 100644 index 00000000..57496fc4 --- /dev/null +++ b/enterprise-token-rotation-guard/src/index.js @@ -0,0 +1,180 @@ +import crypto from "node:crypto"; + +const DEFAULT_POLICY = { + maxTokenAgeDays: 90, + maxAuditExportAgeDays: 7, + maxSsoGroupDrift: 3, + allowedScopes: ["project:read", "project:write", "review:read", "webhook:publish", "audit:read"] +}; + +export function evaluateEnterpriseIntegrations(packet, options = {}) { + const normalized = normalizePacket(packet); + const policy = { ...DEFAULT_POLICY, ...normalized.policy, ...(options.policy ?? {}) }; + const now = new Date(options.now ?? normalized.generatedAt); + const activeOwners = new Set(normalized.owners.filter((owner) => owner.active).map((owner) => owner.id)); + const blockers = []; + const warnings = []; + const decisions = []; + + for (const integration of normalized.integrations) { + const decision = { + integrationId: integration.id, + tenant: integration.tenant, + decision: "keep_active", + reasons: [], + requiredActions: [] + }; + const tokenAgeDays = daysBetween(new Date(integration.tokenIssuedAt), now); + const auditAgeDays = daysBetween(new Date(integration.lastAuditExportAt), now); + const overbroadScopes = integration.scopes.filter((scope) => !policy.allowedScopes.includes(scope)); + + if (!activeOwners.has(integration.ownerId)) { + hold(decision, "orphan_owner", "Assign an active institutional owner before keeping the integration active."); + blockers.push(finding("orphan_owner", integration.id, "Integration owner is missing or inactive.")); + } + + if (tokenAgeDays > policy.maxTokenAgeDays) { + hold(decision, "stale_token", "Rotate the integration token and record the rotation timestamp."); + blockers.push({ ...finding("stale_token", integration.id, "Integration token exceeds the rotation window."), tokenAgeDays }); + } + + if (overbroadScopes.length) { + hold(decision, "overbroad_scopes", "Remove scopes outside the enterprise allowlist."); + blockers.push({ ...finding("overbroad_scopes", integration.id, "Integration has scopes outside the allowlist."), overbroadScopes }); + } + + if (auditAgeDays > policy.maxAuditExportAgeDays) { + hold(decision, "stale_audit_export", "Generate a fresh audit export before the next sync."); + blockers.push({ ...finding("stale_audit_export", integration.id, "Audit export is outside the recency window."), auditAgeDays }); + } + + if (integration.ssoGroupDrift > policy.maxSsoGroupDrift) { + hold(decision, "sso_group_drift", "Reconcile SSO group membership before syncing permissions."); + blockers.push({ ...finding("sso_group_drift", integration.id, "SSO group drift exceeds policy."), ssoGroupDrift: integration.ssoGroupDrift }); + } + + if (integration.restrictedData && !integration.dpaApproved) { + hold(decision, "missing_dpa_approval", "Attach DPA approval before syncing restricted enterprise data."); + blockers.push(finding("missing_dpa_approval", integration.id, "Restricted-data integration lacks DPA approval.")); + } + + if (integration.webhookFailures >= 3) { + warnings.push({ ...finding("webhook_failure_streak", integration.id, "Webhook delivery has repeated failures."), webhookFailures: integration.webhookFailures }); + } + + decisions.push(decision); + } + + const status = blockers.length ? "hold_integration" : warnings.length ? "needs_admin_review" : "integration_ready"; + return { + status, + generatedAt: now.toISOString(), + packetId: normalized.packetId, + digest: digest({ packetId: normalized.packetId, blockers, warnings, decisions }), + counts: { + integrations: normalized.integrations.length, + owners: normalized.owners.length, + blockers: blockers.length, + warnings: warnings.length, + heldIntegrations: decisions.filter((item) => item.decision === "hold").length + }, + blockers, + warnings, + decisions, + policy + }; +} + +export function buildReviewerPacket(packet, options = {}) { + return { + title: "SCIBASE Enterprise Token Rotation Guard", + issue: "SCIBASE.AI#19", + claim: "/claim #19", + evaluation: evaluateEnterpriseIntegrations(packet, options), + reviewerChecklist: [ + "Integration tokens are inside the rotation window.", + "Scopes are limited to the enterprise allowlist.", + "Every integration has an active institutional owner.", + "Audit exports are fresh before enterprise syncs.", + "Restricted data integrations have DPA approval." + ] + }; +} + +export function renderMarkdownReport(packet, options = {}) { + const review = buildReviewerPacket(packet, options); + const { evaluation } = review; + return [ + "# Enterprise Token Rotation Guard Report", + "", + `Issue: ${review.issue}`, + `Claim marker: \`${review.claim}\``, + `Status: \`${evaluation.status}\``, + `Digest: \`${evaluation.digest}\``, + "", + "## Reviewer Checklist", + ...review.reviewerChecklist.map((item) => `- ${item}`), + "", + "## Blockers", + ...(evaluation.blockers.length ? evaluation.blockers.map((item) => `- ${item.code}: ${item.message}`) : ["- None."]), + "", + "## Integration Decisions", + ...evaluation.decisions.map((item) => `- ${item.integrationId}: ${item.decision}${item.reasons.length ? ` (${item.reasons.join(", ")})` : ""}`) + ].join("\n") + "\n"; +} + +export function renderSvgSummary(packet, options = {}) { + const review = buildReviewerPacket(packet, options); + const { evaluation } = review; + return `Enterprise Token Rotation GuardStatus: ${escapeXml(evaluation.status)} | Blockers: ${evaluation.counts.blockers}Integrations inspected: ${evaluation.counts.integrations}Held integrations: ${evaluation.counts.heldIntegrations}Digest: ${escapeXml(evaluation.digest.slice(0, 24))}Synthetic enterprise integration records only. No external API calls.\n`; +} + +export function demoPacket() { + return { + packetId: "scibase-enterprise-token-demo", + generatedAt: "2026-06-13T19:00:00.000Z", + owners: [ + { id: "owner-active", name: "Research IT Admin", active: true }, + { id: "owner-left", name: "Former Lab Admin", active: false } + ], + integrations: [ + { id: "int-dspace-sync", tenant: "North Campus", ownerId: "owner-active", tokenIssuedAt: "2026-05-15T00:00:00.000Z", scopes: ["project:read", "webhook:publish", "audit:read"], lastAuditExportAt: "2026-06-10T00:00:00.000Z", ssoGroupDrift: 1, restrictedData: false, dpaApproved: false, webhookFailures: 0 }, + { id: "int-hris-sync", tenant: "Medical Research Institute", ownerId: "owner-left", tokenIssuedAt: "2026-01-01T00:00:00.000Z", scopes: ["project:read", "admin:root"], lastAuditExportAt: "2026-05-20T00:00:00.000Z", ssoGroupDrift: 6, restrictedData: true, dpaApproved: false, webhookFailures: 4 } + ] + }; +} + +export function normalizePacket(packet) { + if (!packet || typeof packet !== "object") throw new TypeError("An enterprise integration packet object is required."); + return { + packetId: text(packet.packetId, "packetId"), + generatedAt: text(packet.generatedAt, "generatedAt"), + policy: packet.policy ?? {}, + owners: asArray(packet.owners, "owners").map((owner) => ({ + id: text(owner.id, "owner.id"), + name: text(owner.name, "owner.name"), + active: Boolean(owner.active) + })), + integrations: asArray(packet.integrations, "integrations").map((integration) => ({ + id: text(integration.id, "integration.id"), + tenant: text(integration.tenant, "integration.tenant"), + ownerId: text(integration.ownerId, "integration.ownerId"), + tokenIssuedAt: text(integration.tokenIssuedAt, "integration.tokenIssuedAt"), + scopes: asArray(integration.scopes, "integration.scopes").map((scope) => text(scope, "integration.scope")), + lastAuditExportAt: text(integration.lastAuditExportAt, "integration.lastAuditExportAt"), + ssoGroupDrift: number(integration.ssoGroupDrift, "integration.ssoGroupDrift"), + restrictedData: Boolean(integration.restrictedData), + dpaApproved: Boolean(integration.dpaApproved), + webhookFailures: number(integration.webhookFailures ?? 0, "integration.webhookFailures") + })) + }; +} + +function hold(decision, reason, requiredAction) { decision.decision = "hold"; decision.reasons.push(reason); decision.requiredActions.push(requiredAction); } +function finding(code, integrationId, message) { return { code, integrationId, message }; } +function daysBetween(start, end) { return Math.max(0, Math.floor((end.getTime() - start.getTime()) / 86400000)); } +function text(value, name) { if (typeof value !== "string" || !value.trim()) throw new TypeError(`${name} must be a non-empty string.`); return value.trim(); } +function number(value, name) { const parsed = Number(value); if (!Number.isFinite(parsed)) throw new TypeError(`${name} must be finite.`); return parsed; } +function asArray(value, name) { if (!Array.isArray(value)) throw new TypeError(`${name} must be an array.`); return value; } +function digest(value) { return crypto.createHash("sha256").update(JSON.stringify(value)).digest("hex"); } +function escapeXml(value) { return String(value).replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """); } From 10f464a50c111ca553c0baf27bbbf8218d40b479 Mon Sep 17 00:00:00 2001 From: shaiananvari8 Date: Sat, 13 Jun 2026 19:01:38 -0500 Subject: [PATCH 04/10] Add enterprise token rotation guard tests --- .../test/index.test.js | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 enterprise-token-rotation-guard/test/index.test.js diff --git a/enterprise-token-rotation-guard/test/index.test.js b/enterprise-token-rotation-guard/test/index.test.js new file mode 100644 index 00000000..400f406f --- /dev/null +++ b/enterprise-token-rotation-guard/test/index.test.js @@ -0,0 +1,59 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +import { + buildReviewerPacket, + demoPacket, + evaluateEnterpriseIntegrations, + normalizePacket, + renderMarkdownReport, + renderSvgSummary +} from "../src/index.js"; + +test("holds stale tokens, orphan owners, and overbroad scopes", () => { + const result = evaluateEnterpriseIntegrations(demoPacket(), { now: "2026-06-13T19:00:00.000Z" }); + assert.equal(result.status, "hold_integration"); + assert.ok(result.blockers.some((blocker) => blocker.code === "orphan_owner")); + assert.ok(result.blockers.some((blocker) => blocker.code === "stale_token")); + assert.ok(result.blockers.some((blocker) => blocker.code === "overbroad_scopes")); +}); + +test("holds stale audit exports, SSO drift, and missing DPA approval", () => { + const result = evaluateEnterpriseIntegrations(demoPacket(), { now: "2026-06-13T19:00:00.000Z" }); + assert.ok(result.blockers.some((blocker) => blocker.code === "stale_audit_export")); + assert.ok(result.blockers.some((blocker) => blocker.code === "sso_group_drift")); + assert.ok(result.blockers.some((blocker) => blocker.code === "missing_dpa_approval")); +}); + +test("releases clean enterprise integration packets", () => { + const packet = demoPacket(); + packet.integrations = [packet.integrations[0]]; + const result = evaluateEnterpriseIntegrations(packet, { now: "2026-06-13T19:00:00.000Z" }); + assert.equal(result.status, "integration_ready"); + assert.equal(result.counts.blockers, 0); + assert.equal(result.counts.heldIntegrations, 0); +}); + +test("warns on repeated webhook delivery failures", () => { + const packet = demoPacket(); + packet.integrations = [{ ...packet.integrations[0], webhookFailures: 3 }]; + const result = evaluateEnterpriseIntegrations(packet, { now: "2026-06-13T19:00:00.000Z" }); + assert.equal(result.status, "needs_admin_review"); + assert.ok(result.warnings.some((warning) => warning.code === "webhook_failure_streak")); +}); + +test("rejects malformed packets and renders claim artifacts", () => { + assert.equal(normalizePacket(demoPacket()).integrations.length, 2); + const broken = demoPacket(); + broken.integrations[0].scopes = "project:read"; + assert.throws(() => normalizePacket(broken), /integration\.scopes must be an array/); + + const packet = demoPacket(); + const review = buildReviewerPacket(packet, { now: packet.generatedAt }); + const markdown = renderMarkdownReport(packet, { now: packet.generatedAt }); + const svg = renderSvgSummary(packet, { now: packet.generatedAt }); + assert.equal(review.claim, "/claim #19"); + assert.match(markdown, /Enterprise Token Rotation Guard Report/); + assert.match(markdown, /`\/claim #19`/); + assert.match(svg, / Date: Sat, 13 Jun 2026 19:01:49 -0500 Subject: [PATCH 05/10] Add enterprise token rotation demo --- .../scripts/demo.js | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 enterprise-token-rotation-guard/scripts/demo.js diff --git a/enterprise-token-rotation-guard/scripts/demo.js b/enterprise-token-rotation-guard/scripts/demo.js new file mode 100644 index 00000000..17e96d3b --- /dev/null +++ b/enterprise-token-rotation-guard/scripts/demo.js @@ -0,0 +1,29 @@ +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +import { + buildReviewerPacket, + demoPacket, + renderMarkdownReport, + renderSvgSummary +} from "../src/index.js"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const moduleRoot = path.resolve(__dirname, ".."); +const reportsDir = path.join(moduleRoot, "reports"); +const packet = demoPacket(); +const reviewerPacket = buildReviewerPacket(packet, { now: packet.generatedAt }); + +fs.mkdirSync(reportsDir, { recursive: true }); +fs.writeFileSync(path.join(reportsDir, "enterprise-token-packet.json"), `${JSON.stringify(reviewerPacket, null, 2)}\n`); +fs.writeFileSync(path.join(reportsDir, "enterprise-token-report.md"), renderMarkdownReport(packet, { now: packet.generatedAt })); +fs.writeFileSync(path.join(reportsDir, "summary.svg"), renderSvgSummary(packet, { now: packet.generatedAt })); + +console.log(JSON.stringify({ + status: reviewerPacket.evaluation.status, + digest: reviewerPacket.evaluation.digest, + blockers: reviewerPacket.evaluation.counts.blockers, + heldIntegrations: reviewerPacket.evaluation.counts.heldIntegrations, + reportsDir +}, null, 2)); From d87d589d7ad522ad52b9e597e1c3b2c9ddf5f529 Mon Sep 17 00:00:00 2001 From: shaiananvari8 Date: Sat, 13 Jun 2026 19:02:08 -0500 Subject: [PATCH 06/10] Add enterprise token rotation report --- .../reports/enterprise-token-report.md | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 enterprise-token-rotation-guard/reports/enterprise-token-report.md diff --git a/enterprise-token-rotation-guard/reports/enterprise-token-report.md b/enterprise-token-rotation-guard/reports/enterprise-token-report.md new file mode 100644 index 00000000..2abc32fc --- /dev/null +++ b/enterprise-token-rotation-guard/reports/enterprise-token-report.md @@ -0,0 +1,25 @@ +# Enterprise Token Rotation Guard Report + +Issue: SCIBASE.AI#19 +Claim marker: `/claim #19` +Status: `hold_integration` +Digest: `e8c0d01bc2dfd3d313262b963fc9babcd8ab9588f6ccba24526047ed6453fb6b` + +## Reviewer Checklist +- Integration tokens are inside the rotation window. +- Scopes are limited to the enterprise allowlist. +- Every integration has an active institutional owner. +- Audit exports are fresh before enterprise syncs. +- Restricted data integrations have DPA approval. + +## Blockers +- orphan_owner: Integration owner is missing or inactive. +- stale_token: Integration token exceeds the rotation window. +- overbroad_scopes: Integration has scopes outside the allowlist. +- stale_audit_export: Audit export is outside the recency window. +- sso_group_drift: SSO group drift exceeds policy. +- missing_dpa_approval: Restricted-data integration lacks DPA approval. + +## Integration Decisions +- int-dspace-sync: keep_active +- int-hris-sync: hold (orphan_owner, stale_token, overbroad_scopes, stale_audit_export, sso_group_drift, missing_dpa_approval) From 78666a1e9e0444f8d27c76b2242950ac05097a73 Mon Sep 17 00:00:00 2001 From: shaiananvari8 Date: Sat, 13 Jun 2026 19:02:18 -0500 Subject: [PATCH 07/10] Add enterprise token rotation SVG summary --- enterprise-token-rotation-guard/reports/summary.svg | 1 + 1 file changed, 1 insertion(+) create mode 100644 enterprise-token-rotation-guard/reports/summary.svg diff --git a/enterprise-token-rotation-guard/reports/summary.svg b/enterprise-token-rotation-guard/reports/summary.svg new file mode 100644 index 00000000..14693cba --- /dev/null +++ b/enterprise-token-rotation-guard/reports/summary.svg @@ -0,0 +1 @@ +Enterprise Token Rotation GuardStatus: hold_integration | Blockers: 6Integrations inspected: 2Held integrations: 1Digest: e8c0d01bc2dfd3d313262b96Synthetic enterprise integration records only. No external API calls. From 51d2a61a11a2ff0591761e229731930b0d19ebc0 Mon Sep 17 00:00:00 2001 From: shaiananvari8 Date: Sat, 13 Jun 2026 19:02:43 -0500 Subject: [PATCH 08/10] Add enterprise token rotation packet --- .../reports/enterprise-token-packet.json | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 enterprise-token-rotation-guard/reports/enterprise-token-packet.json diff --git a/enterprise-token-rotation-guard/reports/enterprise-token-packet.json b/enterprise-token-rotation-guard/reports/enterprise-token-packet.json new file mode 100644 index 00000000..85132103 --- /dev/null +++ b/enterprise-token-rotation-guard/reports/enterprise-token-packet.json @@ -0,0 +1,113 @@ +{ + "title": "SCIBASE Enterprise Token Rotation Guard", + "issue": "SCIBASE.AI#19", + "claim": "/claim #19", + "evaluation": { + "status": "hold_integration", + "generatedAt": "2026-06-13T19:00:00.000Z", + "packetId": "scibase-enterprise-token-demo", + "digest": "e8c0d01bc2dfd3d313262b963fc9babcd8ab9588f6ccba24526047ed6453fb6b", + "counts": { + "integrations": 2, + "owners": 2, + "blockers": 6, + "warnings": 1, + "heldIntegrations": 1 + }, + "blockers": [ + { + "code": "orphan_owner", + "integrationId": "int-hris-sync", + "message": "Integration owner is missing or inactive." + }, + { + "code": "stale_token", + "integrationId": "int-hris-sync", + "message": "Integration token exceeds the rotation window.", + "tokenAgeDays": 163 + }, + { + "code": "overbroad_scopes", + "integrationId": "int-hris-sync", + "message": "Integration has scopes outside the allowlist.", + "overbroadScopes": [ + "admin:root" + ] + }, + { + "code": "stale_audit_export", + "integrationId": "int-hris-sync", + "message": "Audit export is outside the recency window.", + "auditAgeDays": 24 + }, + { + "code": "sso_group_drift", + "integrationId": "int-hris-sync", + "message": "SSO group drift exceeds policy.", + "ssoGroupDrift": 6 + }, + { + "code": "missing_dpa_approval", + "integrationId": "int-hris-sync", + "message": "Restricted-data integration lacks DPA approval." + } + ], + "warnings": [ + { + "code": "webhook_failure_streak", + "integrationId": "int-hris-sync", + "message": "Webhook delivery has repeated failures.", + "webhookFailures": 4 + } + ], + "decisions": [ + { + "integrationId": "int-dspace-sync", + "tenant": "North Campus", + "decision": "keep_active", + "reasons": [], + "requiredActions": [] + }, + { + "integrationId": "int-hris-sync", + "tenant": "Medical Research Institute", + "decision": "hold", + "reasons": [ + "orphan_owner", + "stale_token", + "overbroad_scopes", + "stale_audit_export", + "sso_group_drift", + "missing_dpa_approval" + ], + "requiredActions": [ + "Assign an active institutional owner before keeping the integration active.", + "Rotate the integration token and record the rotation timestamp.", + "Remove scopes outside the enterprise allowlist.", + "Generate a fresh audit export before the next sync.", + "Reconcile SSO group membership before syncing permissions.", + "Attach DPA approval before syncing restricted enterprise data." + ] + } + ], + "policy": { + "maxTokenAgeDays": 90, + "maxAuditExportAgeDays": 7, + "maxSsoGroupDrift": 3, + "allowedScopes": [ + "project:read", + "project:write", + "review:read", + "webhook:publish", + "audit:read" + ] + } + }, + "reviewerChecklist": [ + "Integration tokens are inside the rotation window.", + "Scopes are limited to the enterprise allowlist.", + "Every integration has an active institutional owner.", + "Audit exports are fresh before enterprise syncs.", + "Restricted data integrations have DPA approval." + ] +} From dd52d0cba093f70b66d331a844f40c3e52cc1def Mon Sep 17 00:00:00 2001 From: shaiananvari8 Date: Sun, 14 Jun 2026 23:58:47 -0500 Subject: [PATCH 09/10] Add enterprise token rotation demo transcript --- .../reports/demo-transcript.md | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 enterprise-token-rotation-guard/reports/demo-transcript.md diff --git a/enterprise-token-rotation-guard/reports/demo-transcript.md b/enterprise-token-rotation-guard/reports/demo-transcript.md new file mode 100644 index 00000000..4f75e7f6 --- /dev/null +++ b/enterprise-token-rotation-guard/reports/demo-transcript.md @@ -0,0 +1,35 @@ +# Enterprise Token Rotation Guard Demo Transcript + +Date verified: 2026-06-15 + +Commands run from `enterprise-token-rotation-guard`: + +```bash +npm test +npm run demo +``` + +Test result: + +```text +5 tests passed +0 tests failed +``` + +Demo output: + +```json +{ + "status": "hold_integration", + "digest": "e8c0d01bc2dfd3d313262b963fc9babcd8ab9588f6ccba24526047ed6453fb6b", + "blockers": 6, + "heldIntegrations": 1, + "reportsDir": "enterprise-token-rotation-guard/reports" +} +``` + +Generated reviewer artifacts: + +- `reports/enterprise-token-report.md` +- `reports/enterprise-token-packet.json` +- `reports/summary.svg` From 6ededcdb559f73afa0d8a921901d20ed4fc4bebb Mon Sep 17 00:00:00 2001 From: shaiananvari8 Date: Sun, 14 Jun 2026 23:59:44 -0500 Subject: [PATCH 10/10] Document enterprise token demo evidence --- enterprise-token-rotation-guard/readme.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/enterprise-token-rotation-guard/readme.md b/enterprise-token-rotation-guard/readme.md index 23e4105d..4b1d6520 100644 --- a/enterprise-token-rotation-guard/readme.md +++ b/enterprise-token-rotation-guard/readme.md @@ -11,4 +11,12 @@ npm test npm run demo ``` +## Demo Evidence + +The demo transcript is captured in `reports/demo-transcript.md`. The demo generates these reviewer artifacts: + +- `reports/enterprise-token-report.md` +- `reports/enterprise-token-packet.json` +- `reports/summary.svg` + The demo data is synthetic and does not contact SSO providers, institutional repositories, or production APIs.