| <!doctype html> |
| <html lang="en"> |
| <head> |
| <meta charset="utf-8"/> |
| <meta name="viewport" content="width=device-width,initial-scale=1"/> |
| <title>Consent / Revocation Propagation Simulator v0.0.4</title> |
| <meta name="description" content="A HIR/OAM living-boundary simulator for testing whether consent survives handoff and whether revocation reaches downstream branches."/> |
| <style> |
| :root{ |
| --bg:#070a0f; --panel:#111827; --panel2:#0b111b; --ink:#f7f0df; --muted:#aeb8c8; |
| --gold:#f0c96a; --blue:#80b7ff; --green:#7adf98; --orange:#ffb35d; --red:#ff6b6b; |
| --purple:#c9a7ff; --cyan:#58e6d9; --line:#2d3a50; --soft:rgba(255,255,255,.045); |
| } |
| *{box-sizing:border-box} |
| body{ |
| margin:0; |
| font-family:Inter,system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif; |
| color:var(--ink); |
| background: |
| radial-gradient(circle at 12% 8%,rgba(240,201,106,.16),transparent 30%), |
| radial-gradient(circle at 84% 8%,rgba(128,183,255,.13),transparent 34%), |
| radial-gradient(circle at 50% 98%,rgba(88,230,217,.08),transparent 42%), |
| linear-gradient(180deg,#070a0f 0%,#0d121b 54%,#07090d 100%); |
| line-height:1.55; |
| } |
| .wrap{width:min(1220px,calc(100% - 32px));margin:0 auto} |
| header{padding:56px 0 28px;border-bottom:1px solid var(--line)} |
| section{padding:26px 0} |
| .eyebrow{font-size:12px;text-transform:uppercase;letter-spacing:.16em;color:var(--gold);font-weight:950} |
| h1{font-size:clamp(38px,7vw,78px);letter-spacing:-.06em;line-height:.94;margin:12px 0} |
| h2{font-size:clamp(26px,4vw,42px);letter-spacing:-.04em;line-height:1;margin:0 0 12px} |
| h3{margin:18px 0 8px} |
| .subtitle{font-size:clamp(18px,2.5vw,28px);color:#dce6f4;max-width:1040px} |
| .card{ |
| background:linear-gradient(135deg,rgba(17,24,39,.96),rgba(14,21,34,.92)); |
| border:1px solid var(--line); border-radius:24px; padding:22px; box-shadow:0 18px 45px rgba(0,0,0,.35) |
| } |
| .hero-grid{display:grid;grid-template-columns:1.08fr .92fr;gap:18px;margin-top:24px} |
| .grid{display:grid;grid-template-columns:.92fr 1.08fr;gap:18px;align-items:start} |
| .dual{display:grid;grid-template-columns:1fr 1fr;gap:16px} |
| .muted{color:var(--muted)}.gold{color:var(--gold)}.green{color:var(--green)}.red{color:var(--red)}.blue{color:var(--blue)}.orange{color:var(--orange)} |
| .equation,textarea{ |
| font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; |
| background:#070a0f; color:#f4d88e; border:1px solid #39475f; border-radius:16px; padding:14px; width:100%; |
| } |
| textarea{min-height:90px;color:#dfe7f3;resize:vertical} |
| .pillrow{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px} |
| .pill,.btn{ |
| border:1px solid #3a4a63;background:#101722;color:#dce6f4;border-radius:999px; |
| padding:8px 11px;font-size:12px;font-weight:850;cursor:pointer;text-decoration:none; |
| } |
| .btn.primary{border-color:rgba(240,201,106,.72);color:var(--gold)} |
| .btn:hover,.pill:hover{border-color:var(--gold)} |
| select{ |
| width:100%;padding:12px;border-radius:14px;background:#080c13;color:var(--ink);border:1px solid #334057 |
| } |
| .control{margin:13px 0} |
| .control label{display:flex;justify-content:space-between;font-weight:780;color:#e7edf7} |
| input[type=range]{width:100%;accent-color:var(--gold)} |
| .toggle{display:flex;align-items:center;justify-content:space-between;gap:14px;background:#0b111b;border:1px solid #31405a;border-radius:16px;padding:12px 14px;margin:10px 0} |
| .toggle span{font-weight:820}.toggle small{display:block;color:var(--muted);font-weight:500} |
| input[type=checkbox]{width:22px;height:22px;accent-color:var(--gold)} |
| .state-label{font-size:12px;text-transform:uppercase;letter-spacing:.14em;color:var(--muted);font-weight:900} |
| .state{font-size:clamp(27px,4vw,46px);line-height:.94;letter-spacing:-.05em;font-weight:950;margin:7px 0 10px} |
| .consent .state{color:var(--blue)} .revocation .state{color:var(--gold)} |
| .score-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;margin-top:12px} |
| .score{background:var(--soft);border:1px solid rgba(255,255,255,.09);border-radius:16px;padding:12px} |
| .score .label{font-size:10px;text-transform:uppercase;letter-spacing:.1em;color:var(--muted);font-weight:900} |
| .score .value{font-size:27px;font-weight:950;line-height:1;margin-top:5px} |
| .readbox{border-left:4px solid var(--gold);padding-left:14px;margin-top:14px;color:#dfe7f3} |
| .path{ |
| position:relative;overflow:hidden; |
| background:linear-gradient(180deg,rgba(255,255,255,.045),rgba(255,255,255,.025)); |
| border:1px solid rgba(255,255,255,.09);border-radius:22px;padding:18px |
| } |
| .path:before{ |
| content:"";position:absolute;inset:auto -20% 0 -20%;height:2px; |
| background:linear-gradient(90deg,transparent,var(--gold),transparent);opacity:.35 |
| } |
| .consent{border-color:rgba(128,183,255,.35)} |
| .revocation{border-color:rgba(240,201,106,.35)} |
| .flags{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px} |
| .flag{border:1px solid #44556f;background:#0c121c;border-radius:999px;padding:7px 10px;font-size:12px;font-weight:850} |
| .hot{border-color:rgba(255,107,107,.45);color:#ffd0d0;background:rgba(255,107,107,.10)} |
| .warn{border-color:rgba(255,179,93,.45);color:#ffe0bd;background:rgba(255,179,93,.10)} |
| .good{border-color:rgba(122,223,152,.45);color:#c9ffd8;background:rgba(122,223,152,.10)} |
| .info{border-color:rgba(128,183,255,.45);color:#cfe4ff;background:rgba(128,183,255,.10)} |
| .compare{display:grid;grid-template-columns:1fr auto 1fr;gap:12px;align-items:center} |
| .arrow{font-size:42px;color:var(--gold);font-weight:950;text-align:center} |
| .route-card{background:#0b111b;border:1px solid var(--line);border-radius:18px;padding:16px} |
| .route-card b{color:var(--gold)}.route-card p{color:var(--muted);font-size:13px} |
| .taxonomy{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px} |
| footer{border-top:1px solid var(--line);padding:24px 0 40px;color:var(--muted)} |
| @media(max-width:900px){.hero-grid,.grid,.dual,.compare,.taxonomy,.score-grid{grid-template-columns:1fr}.arrow{transform:rotate(90deg)}} |
|
|
| details.logicBox{ |
| border:1px solid var(--line); |
| border-radius:20px; |
| background:rgba(255,255,255,.035); |
| padding:14px 16px; |
| margin:14px 0; |
| } |
| details.logicBox summary{ |
| cursor:pointer; |
| color:var(--gold); |
| font-weight:950; |
| letter-spacing:-.01em; |
| } |
| .logicGrid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin-top:12px} |
| .logicMini{ |
| border:1px solid rgba(255,255,255,.09); |
| background:#0b111b; |
| border-radius:16px; |
| padding:12px; |
| color:#dfe7f3; |
| } |
| .logicMini b{color:var(--gold)} |
| .routeProofTable{width:100%;border-collapse:collapse;margin-top:14px;font-size:13px;overflow:hidden;border-radius:14px} |
| .routeProofTable th,.routeProofTable td{border:1px solid var(--line);padding:10px;text-align:left;vertical-align:top} |
| .routeProofTable th{background:rgba(240,201,106,.10);color:var(--gold)} |
| .routeProofTable td{background:rgba(255,255,255,.025);color:#dfe7f3} |
| @media(max-width:900px){.logicGrid{grid-template-columns:1fr}.routeProofTable{font-size:12px}} |
|
|
|
|
| .sensitivityBox{ |
| margin-top:14px; |
| border:1px solid rgba(240,201,106,.24); |
| background:linear-gradient(135deg,rgba(240,201,106,.06),rgba(128,183,255,.04)); |
| border-radius:20px; |
| padding:14px; |
| } |
| .sensitivityGrid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px;margin-top:10px} |
| .senseCard{ |
| background:#0b111b; |
| border:1px solid rgba(255,255,255,.09); |
| border-radius:16px; |
| padding:12px; |
| } |
| .senseCard .miniLabel{font-size:10px;text-transform:uppercase;letter-spacing:.10em;color:var(--muted);font-weight:900} |
| .senseCard .miniValue{font-size:20px;font-weight:950;margin-top:4px} |
| .senseStable{color:var(--green)} |
| .senseFragile{color:var(--orange)} |
| .senseHot{color:var(--red)} |
| .senseLocked{color:var(--gold)} |
| @media(max-width:900px){.sensitivityGrid{grid-template-columns:1fr}} |
|
|
|
|
| .driverTrace{ |
| margin-top:12px; |
| border:1px solid rgba(255,255,255,.09); |
| background:#0b111b; |
| border-radius:16px; |
| padding:12px; |
| } |
| .driverTrace h4{ |
| margin:0 0 8px; |
| font-size:13px; |
| color:var(--gold); |
| letter-spacing:.02em; |
| } |
| .driverCols{display:grid;grid-template-columns:1fr 1fr;gap:10px} |
| .driverList{ |
| margin:0; |
| padding-left:18px; |
| color:#dfe7f3; |
| font-size:12px; |
| line-height:1.45; |
| } |
| .driverList li{margin:4px 0} |
| .driverNone{color:var(--muted);font-size:12px;margin:0} |
| @media(max-width:900px){.driverCols{grid-template-columns:1fr}} |
|
|
| </style> |
| </head> |
| <body> |
| <header> |
| <div class="wrap"> |
| <div class="eyebrow">Consent / Revocation Propagation Simulator v0.0.4 — Sensitivity Drivers + Flip Trace Patch</div> |
| <h1>Did the boundary survive the handoff?</h1> |
| <p class="subtitle">Consent is not a checkbox. It is a living boundary that has to travel. Revocation is not erasure. It is a boundary delta that must reach every affected branch.</p> |
|
|
| <div class="hero-grid"> |
| <div class="card"> |
| <h2>A consent boundary is only real if revocation can reach the branches.</h2> |
| <p>Most systems ask: did the user consent?</p> |
| <p>This simulator asks: consent to what, for whom, for how long, under what scope, with what revocation path, and can that revocation reach every branch?</p> |
| <p><b class="gold">This simulator does not provide legal advice, HIPAA compliance certification, privacy-law analysis, or clinical authorization.</b> It maps whether a consent boundary remains structurally intact under handoff, downstream reuse, retention, and revocation pressure.</p> |
| </div> |
| <div class="card"> |
| <h2>Dual boundary equations</h2> |
| <div class="equation">S_consent = A_authorization × B_scope − P_revocation_drift</div> |
| <div class="equation">S_revocation = R_notice × C_chain × E_enforcement − P_residue</div> |
| <p class="muted">The first path tests whether consent carried with scope intact. The second path tests whether a revocation or scope change can propagate through downstream branches.</p><p class="muted"><b>v0.0.4 patch:</b> sensitivity drivers, flip traces, and top route-change contributors. Route logic, thresholds, presets, and shareable state unchanged.</p> |
| </div> |
| </div> |
| </div> |
| </header> |
|
|
| <main> |
| <section> |
| <div class="wrap card"> |
| <h2>Explain this to me as…</h2> |
| <div class="pillrow"> |
| <button class="btn primary" data-mode="plain">Plain words</button> |
| <button class="btn" data-mode="patient">Patient / EHR</button> |
| <button class="btn" data-mode="platform">Platform / data reuse</button> |
| <button class="btn" data-mode="agent">AI agent</button> |
| <button class="btn" data-mode="system">System map</button> |
| </div> |
| <p id="translation" class="readbox">This tool checks whether a consent boundary stayed clear, scoped, current, revocable, and enforceable as it moved through downstream systems.</p> |
| </div> |
| </section> |
|
|
| <section> |
| <div class="wrap card"> |
| <h2>Consent event → handoff → downstream branches</h2> |
| <div class="compare"> |
| <div> |
| <h3>Original Consent / Boundary</h3> |
| <textarea id="originalConsent">Consent is granted for a specific purpose, recipient, duration, data/action boundary, and visible revocation path.</textarea> |
| </div> |
| <div class="arrow">→</div> |
| <div> |
| <h3>Downstream Use / Revocation Event</h3> |
| <textarea id="downstreamEvent">The boundary may travel cleanly, drift into a new scope, be partially revoked, or require downstream quarantine until branches confirm compliance.</textarea> |
| </div> |
| </div> |
| <p class="muted">v0.0.1 does not automatically analyze text. These fields preserve scenario context for the export report while sliders model boundary and propagation conditions.</p> |
| </div> |
| </section> |
|
|
| <section> |
| <div class="wrap card"> |
| <h2>Simulator</h2> |
| <p class="muted"><b>v0.0.1 route note:</b> repair, revocation, quarantine, and human review override ordinary consent-active routing.</p> |
| <div class="grid"> |
| <div> |
| <label for="preset"><b>Scenario preset</b></label> |
| <select id="preset"></select> |
|
|
| <h3>Authorization / consent side</h3> |
| <div class="control"><label>Explicit Authorization <span id="explicitAuthorizationVal"></span></label><input id="explicitAuthorization" type="range" min="0" max="1" step="0.01"></div> |
| <div class="control"><label>Informed Understanding <span id="informedUnderstandingVal"></span></label><input id="informedUnderstanding" type="range" min="0" max="1" step="0.01"></div> |
| <div class="control"><label>Current / Not Expired <span id="currentConsentVal"></span></label><input id="currentConsent" type="range" min="0" max="1" step="0.01"></div> |
| <div class="control"><label>Identity / Authority Match <span id="identityAuthorityVal"></span></label><input id="identityAuthority" type="range" min="0" max="1" step="0.01"></div> |
| <div class="control"><label>Consent Record Available <span id="consentRecordVal"></span></label><input id="consentRecord" type="range" min="0" max="1" step="0.01"></div> |
|
|
| <h3>Scope side</h3> |
| <div class="control"><label>Purpose Clarity <span id="purposeClarityVal"></span></label><input id="purposeClarity" type="range" min="0" max="1" step="0.01"></div> |
| <div class="control"><label>Recipient Clarity <span id="recipientClarityVal"></span></label><input id="recipientClarity" type="range" min="0" max="1" step="0.01"></div> |
| <div class="control"><label>Data / Action Boundary <span id="dataActionBoundaryVal"></span></label><input id="dataActionBoundary" type="range" min="0" max="1" step="0.01"></div> |
| <div class="control"><label>Duration Clarity <span id="durationClarityVal"></span></label><input id="durationClarity" type="range" min="0" max="1" step="0.01"></div> |
| <div class="control"><label>Revocation Path Visible <span id="revocationPathVisibleVal"></span></label><input id="revocationPathVisible" type="range" min="0" max="1" step="0.01"></div> |
|
|
| <h3>Propagation pressure side</h3> |
| <div class="control"><label>Downstream Branch Count <span id="downstreamBranchCountVal"></span></label><input id="downstreamBranchCount" type="range" min="0" max="1" step="0.01"></div> |
| <div class="control"><label>Transfer Opacity <span id="transferOpacityVal"></span></label><input id="transferOpacity" type="range" min="0" max="1" step="0.01"></div> |
| <div class="control"><label>Retention Friction <span id="retentionFrictionVal"></span></label><input id="retentionFriction" type="range" min="0" max="1" step="0.01"></div> |
| <div class="control"><label>Automation Lag <span id="automationLagVal"></span></label><input id="automationLag" type="range" min="0" max="1" step="0.01"></div> |
| <div class="control"><label>Institutional / Commercial Incentive <span id="institutionalIncentiveVal"></span></label><input id="institutionalIncentive" type="range" min="0" max="1" step="0.01"></div> |
|
|
| <h3>Revocation propagation side</h3> |
| <div class="control"><label>Revocation Notice Clarity <span id="revocationNoticeVal"></span></label><input id="revocationNotice" type="range" min="0" max="1" step="0.01"></div> |
| <div class="control"><label>Downstream Chain Visibility <span id="chainVisibilityVal"></span></label><input id="chainVisibility" type="range" min="0" max="1" step="0.01"></div> |
| <div class="control"><label>Enforcement Ability <span id="enforcementAbilityVal"></span></label><input id="enforcementAbility" type="range" min="0" max="1" step="0.01"></div> |
| <div class="control"><label>Confirmation Returned <span id="confirmationReturnedVal"></span></label><input id="confirmationReturned" type="range" min="0" max="1" step="0.01"></div> |
| <div class="control"><label>Residual Copy / Cache Risk <span id="residualCopyRiskVal"></span></label><input id="residualCopyRisk" type="range" min="0" max="1" step="0.01"></div> |
|
|
| <h3>Route gates</h3> |
| <label class="toggle"><span>Consent Currently Granted<small>Authorization exists right now for the evaluated scope.</small></span><input id="consentGranted" type="checkbox"></label> |
| <label class="toggle"><span>Partial Revocation Present<small>The person narrowed consent rather than fully revoking it.</small></span><input id="partialRevocation" type="checkbox"></label> |
| <label class="toggle"><span>Full Revocation Present<small>Consent has been withdrawn for the affected scope.</small></span><input id="fullRevocation" type="checkbox"></label> |
| <label class="toggle"><span>Downstream Sharing Occurred<small>Consent/data/action moved beyond the first receiver.</small></span><input id="downstreamSharing" type="checkbox"></label> |
| <label class="toggle"><span>Prior Consent Harm Present<small>A prior boundary was violated, erased, ignored, or misrepresented.</small></span><input id="priorConsentHarm" type="checkbox"></label> |
| <label class="toggle"><span>Sensitive / High-Stakes Context<small>Health, education, family support, safety, identity, money, legal, or similarly high-impact context.</small></span><input id="sensitiveContext" type="checkbox"></label> |
| </div> |
|
|
| <div> |
| <div class="dual"> |
| <div class="path consent"> |
| <div class="state-label">Consent Carry State<br><small style="font-size:13px;opacity:.7">scope survival / handoff legitimacy</small></div> |
| <div id="consentState" class="state">BOUNDED CONSENT</div> |
| <p id="consentMeaning" class="muted"></p> |
| <div class="score-grid"> |
| <div class="score"><div class="label">Authorization Support</div><div id="authorizationSupport" class="value"></div></div> |
| <div class="score"><div class="label">Scope Support</div><div id="scopeSupport" class="value"></div></div> |
| <div class="score"><div class="label">Consent Stability</div><div id="consentStability" class="value"></div></div> |
| <div class="score"><div class="label">Drift Pressure</div><div id="revocationDrift" class="value"></div></div> |
| </div> |
| <div class="readbox"><b>Read:</b><p id="consentRead"></p><b>Why this route?</b><p id="consentWhy"></p><b>Action:</b><p id="consentAction"></p></div> |
| <h3>Consent Flags</h3><div id="consentFlags" class="flags"></div> |
| </div> |
|
|
| <div class="path revocation"> |
| <div class="state-label">Revocation Propagation State<br><small style="font-size:13px;opacity:.7">boundary delta / downstream enforcement</small></div> |
| <div id="revocationState" class="state">REVOCATION PROPAGATING</div> |
| <p id="revocationMeaning" class="muted"></p> |
| <div class="score-grid"> |
| <div class="score"><div class="label">Revocation Support</div><div id="revocationSupport" class="value"></div></div> |
| <div class="score"><div class="label">Residue Pressure</div><div id="residuePressure" class="value"></div></div> |
| <div class="score"><div class="label">Propagation Stability</div><div id="revocationStability" class="value"></div></div> |
| <div class="score"><div class="label">Boundary Delta Strength</div><div id="boundaryDeltaStrength" class="value"></div></div> |
| </div> |
| <div class="readbox"><b>Read:</b><p id="revocationRead"></p><b>Why this route?</b><p id="revocationWhy"></p><b>Action:</b><p id="revocationAction"></p></div> |
| <h3>Revocation Flags</h3><div id="revocationFlags" class="flags"></div> |
| </div> |
| </div> |
|
|
| <div class="pillrow"> |
| <button id="exportReport" class="btn primary">Generate boundary report</button> |
| <button id="copyReport" class="btn">Copy report</button> |
| <button id="copyScenarioLink" class="btn">Copy scenario link</button> |
| <button id="runSensitivity" class="btn">Test route stability ±10%</button> |
| </div> |
|
|
| <div id="sensitivityBox" class="sensitivityBox"> |
| <b class="gold">Route stability sensitivity</b> |
| <p class="muted" id="sensitivitySummary">Click “Test route stability ±10%” to see whether the current routes are stable, threshold-adjacent, fragile, or override-locked under small slider perturbations.</p> |
| <div class="sensitivityGrid"> |
| <div class="senseCard"><div class="miniLabel">Consent Route</div><div id="senseConsentRoute" class="miniValue">—</div></div> |
| <div class="senseCard"><div class="miniLabel">Revocation Route</div><div id="senseRevocationRoute" class="miniValue">—</div></div> |
| <div class="senseCard"><div class="miniLabel">Overall Stability</div><div id="senseOverall" class="miniValue">—</div></div> |
| </div> |
| <div class="driverTrace"> |
| <h4>Top sensitivity drivers / flip trace</h4> |
| <div class="driverCols"> |
| <div> |
| <b class="blue">Consent route flips</b> |
| <ol id="consentDriverList" class="driverList"><li>No sensitivity run yet.</li></ol> |
| </div> |
| <div> |
| <b class="gold">Revocation route flips</b> |
| <ol id="revocationDriverList" class="driverList"><li>No sensitivity run yet.</li></ol> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| </div> |
| </div> |
| </div> |
| </section> |
|
|
| <section><div class="wrap card"><h2>Exportable consent-boundary report</h2><textarea id="report" placeholder="Click Generate boundary report..."></textarea></div></section> |
|
|
| <section><div class="wrap card"> |
| <h2>How the model works</h2> |
| <div class="equation">S_consent = A_authorization × B_scope − P_revocation_drift</div> |
| <div class="equation">S_revocation = R_notice × C_chain × E_enforcement − P_residue</div> |
| <p class="readbox"> |
| Consent granted is not enough. Consent carried is the real test.<br><br> |
| Revocation received is not enough. Revocation propagated is the real test.<br><br> |
| <b>Revocation propagates as a boundary delta, not erasure.</b> History and audit spine remain preserved, but downstream authorization is downgraded, stopped, quarantined, or re-proofed according to the affected scope. |
| </p> |
| <p class="muted" style="margin-top:16px;"> |
| <b>Where:</b><br> |
| A_authorization = explicit authorization, informed understanding, currency, identity/authority match, and consent record availability.<br> |
| B_scope = purpose clarity, recipient clarity, data/action boundary, duration clarity, and revocation-path visibility.<br> |
| P_revocation_drift = downstream branch count, transfer opacity, retention friction, automation lag, and institutional/commercial incentive.<br> |
| R_notice = revocation notice clarity.<br> |
| C_chain = downstream chain visibility.<br> |
| E_enforcement = enforcement ability and confirmation returned.<br> |
| P_residue = residual copy/cache risk plus downstream drift pressure. |
| </p> |
| </div></section> |
|
|
|
|
| <section><div class="wrap card"> |
| <h2>Route transparency / math disclosure</h2> |
| <p class="readbox"> |
| <b>Why this matters:</b><br> |
| Consent granted is not enough. Consent carried is the real test.<br> |
| Revocation received is not enough. Revocation propagated is the real test.<br> |
| Revocation propagates as a boundary delta, not erasure. |
| </p> |
|
|
| <details class="logicBox" open> |
| <summary>Show route logic / show the math</summary> |
| <div class="logicGrid"> |
| <div class="logicMini"> |
| <b>Consent carry path</b><br> |
| A_authorization = 0.25×Explicit Authorization + 0.20×Informed Understanding + 0.18×Current Consent + 0.20×Identity/Authority Match + 0.17×Consent Record<br><br> |
| B_scope = 0.24×Purpose Clarity + 0.20×Recipient Clarity + 0.22×Data/Action Boundary + 0.16×Duration Clarity + 0.18×Revocation Path Visible<br><br> |
| S_consent = A_authorization × B_scope − P_revocation_drift |
| </div> |
| <div class="logicMini"> |
| <b>Revocation propagation path</b><br> |
| P_revocation_drift = average downstream branch count, transfer opacity, retention friction, automation lag, and institutional/commercial incentive.<br><br> |
| S_revocation = R_notice × C_chain × E_enforcement − P_residue<br><br> |
| P_residue = residual copy/cache risk plus retention, opacity, and automation residue. |
| </div> |
| <div class="logicMini"> |
| <b>Override order</b><br> |
| Repair debt overrides ordinary consent evaluation. Full revocation overrides active consent. Downstream quarantine overrides convenience when branches cannot be verified. Sensitive/high-stakes contexts can require human review. |
| </div> |
| <div class="logicMini"> |
| <b>Boundary rule</b><br> |
| Revocation does not erase the audit spine. It travels forward as a boundary delta: stop, downgrade, quarantine, re-proof, retain with limits, or repair according to affected scope. |
| </div> |
| </div> |
|
|
| <table class="routeProofTable"> |
| <thead> |
| <tr><th>Preset</th><th>Consent Carry Route</th><th>Revocation Route</th><th>Consent Stability</th><th>Revocation Stability</th></tr> |
| </thead> |
| <tbody> |
| <tr><td>Clean Bounded Consent</td><td>CONSENT_ACTIVE</td><td>REVOCATION_READY</td><td>0.58</td><td>0.29</td></tr> |
| <tr><td>Ambiguous App Permission</td><td>SCOPE_MISMATCH</td><td>NO_REVOCATION_ACTIVE</td><td>-0.44</td><td>-0.59</td></tr> |
| <tr><td>Patient EHR Consent Revocation</td><td>REVOKED_SCOPE</td><td>DOWNSTREAM_QUARANTINE</td><td>-0.14</td><td>-0.47</td></tr> |
| <tr><td>Health Data Partial Revocation</td><td>PARTIAL_REVOCATION</td><td>HUMAN_REVIEW_REQUIRED</td><td>-0.01</td><td>-0.30</td></tr> |
| <tr><td>Caregiver / Proxy Boundary Drift</td><td>HUMAN_REVIEW_REQUIRED</td><td>NO_REVOCATION_ACTIVE</td><td>-0.17</td><td>-0.35</td></tr> |
| <tr><td>AI Agent Tool Permission Drift</td><td>SCOPE_CLARIFICATION_REQUIRED</td><td>NO_REVOCATION_ACTIVE</td><td>-0.34</td><td>-0.47</td></tr> |
| <tr><td>Public Platform Data Reuse</td><td>RETENTION_LIMIT_REQUIRED</td><td>NO_REVOCATION_ACTIVE</td><td>-0.63</td><td>-0.76</td></tr> |
| <tr><td>Revocation Lost Downstream</td><td>REVOKED_SCOPE</td><td>DOWNSTREAM_QUARANTINE</td><td>-0.38</td><td>-0.78</td></tr> |
| <tr><td>Prior Consent Boundary Harm</td><td>REPAIR_REQUIRED</td><td>REPAIR_REQUIRED</td><td>-0.52</td><td>-0.71</td></tr> |
| </tbody> |
| </table> |
| </details> |
| </div></section> |
|
|
|
|
|
|
| <section><div class="wrap card"> |
| <h2>Route stability / sensitivity</h2> |
| <p class="readbox"> |
| A boundary route can be stable, fragile, threshold-adjacent, or override-locked. |
| This patch perturbs each slider by ±10% and checks whether the recommended consent or revocation routes change. |
| v0.0.4 also records the flip trace: which slider moved, in which direction, and which route it changed from/to. |
| </p> |
| <div class="logicGrid"> |
| <div class="logicMini"><b>Stable Route</b><br>The same route survives small pressure changes. The boundary diagnosis is not near a threshold.</div> |
| <div class="logicMini"><b>Threshold Adjacent</b><br>A few small slider shifts change the route. The case is close enough to a boundary that review or re-proof may be prudent.</div> |
| <div class="logicMini"><b>Fragile Route</b><br>Many small slider shifts change the route. The boundary state is unstable and should not be treated as cleanly resolved.</div> |
| <div class="logicMini"><b>Override Locked</b><br>A hard gate such as repair debt, full revocation, or partial revocation is active. Slider softness does not override the boundary gate.</div> |
| </div> |
| </div></section> |
|
|
|
|
| <section><div class="wrap card"> |
| <h2>Route taxonomy</h2> |
| <div class="taxonomy" id="taxonomy"></div> |
| </div></section> |
|
|
| <section><div class="wrap card"> |
| <h2>Defensive boundary</h2> |
| <p class="readbox"> |
| This simulator maps consent-boundary degradation for review, repair, education, and defensive design. It does not provide instructions for bypassing consent, evading revocation, retaining data improperly, or exploiting downstream copies. |
| </p> |
| </div></section> |
| </main> |
|
|
| <footer> |
| <div class="wrap"> |
| <strong>Consent / Revocation Propagation Simulator v0.0.4</strong><br/> |
| Sensitivity Drivers + Flip Trace Patch. Boundary: not legal advice, not HIPAA compliance certification, not privacy-law analysis, not clinical authorization. |
| </div> |
| </footer> |
|
|
| <script> |
| const sliders=[ |
| "explicitAuthorization","informedUnderstanding","currentConsent","identityAuthority","consentRecord", |
| "purposeClarity","recipientClarity","dataActionBoundary","durationClarity","revocationPathVisible", |
| "downstreamBranchCount","transferOpacity","retentionFriction","automationLag","institutionalIncentive", |
| "revocationNotice","chainVisibility","enforcementAbility","confirmationReturned","residualCopyRisk" |
| ]; |
| const toggles=["consentGranted","partialRevocation","fullRevocation","downstreamSharing","priorConsentHarm","sensitiveContext"]; |
| function el(id){return document.getElementById(id)} function val(id){return parseFloat(el(id).value)} function avg(a){return a.reduce((x,y)=>x+y,0)/a.length} function r(n){return Number(n).toFixed(2)} |
| |
| const consentRoutes={ |
| CONSENT_ACTIVE:["CONSENT ACTIVE","Consent is clear, current, scoped, traceable, and enforceable for the evaluated use.","The boundary is intact for this scope. Consent can carry with audit spine attached.","Allow use inside the stated scope. Keep revocation path visible."], |
| BOUNDED_CONSENT:["BOUNDED CONSENT","Consent exists, but only inside a narrow or explicitly bounded scope.","The boundary can carry, but it cannot expand beyond its stated purpose, recipient, duration, or action limits.","Allow only the bounded use. Do not reuse or extend without re-proof."], |
| SCOPE_CLARIFICATION_REQUIRED:["SCOPE CLARIFICATION REQUIRED","The system cannot evaluate consent because purpose, recipient, duration, or action boundary is unclear.","Honesty requires naming the actual scope before consent can be treated as valid.","Clarify scope before use, transfer, automation, or reuse."], |
| SCOPE_MISMATCH:["SCOPE MISMATCH","The proposed use exceeds or differs from the consented scope.","OAM detected: a boundary is being stretched beyond what was authorized.","Reject or narrow the use. Return to the consented scope or request renewed authorization."], |
| REPROOF_REQUIRED:["REPROOF REQUIRED","Consent may exist, but it is stale, ambiguous, transferred, sensitive, or high-stakes enough to require renewed authorization.","Integrity blocks silent inheritance across scope, time, stakeholder, or context drift.","Pause use and request re-proof before proceeding."], |
| PARTIAL_REVOCATION:["PARTIAL REVOCATION","The person narrowed consent rather than fully revoking it.","Respect preserves the remaining allowed scope while downgrading or blocking the revoked branch.","Update the boundary. Allow only what remains explicitly authorized."], |
| REVOKED_SCOPE:["REVOKED SCOPE","Consent has been withdrawn for the affected scope. Continued use cannot inherit authorization.","Revocation propagates as a boundary delta, not erasure. Prior history remains, but forward authorization is downgraded.","Stop affected use. Preserve audit trail. Propagate boundary delta downstream."], |
| DOWNSTREAM_QUARANTINE:["DOWNSTREAM QUARANTINE","Downstream branches are affected but cannot be verified.","The boundary update cannot be trusted to have reached every branch.","Quarantine affected branches until chain visibility, enforcement, and confirmation improve."], |
| RETENTION_LIMIT_REQUIRED:["RETENTION LIMIT REQUIRED","Use may be bounded, but retention, cache, archive, or deletion handling is not clear enough.","Respect requires visible retention limits when data/action residue can persist after the original use.","Set retention limits, downgrade copies, mark archives, or request human review."], |
| REPAIR_REQUIRED:["REPAIR REQUIRED","A prior consent boundary was violated, erased, ignored, or misrepresented.","Repair debt overrides ordinary consent evaluation.","Reopen for repair. Name the boundary harm, preserve affected party agency, correct downstream records, and define prevention steps."], |
| HUMAN_REVIEW_REQUIRED:["HUMAN REVIEW REQUIRED","The context is sensitive or high-stakes and automated routing is not enough.","Respect requires accountable human review before the boundary is inherited, reused, or downgraded.","Pause automation. Route to qualified human review with scope and revocation history visible."] |
| }; |
| |
| const revocationRoutes={ |
| NO_REVOCATION_ACTIVE:["NO REVOCATION ACTIVE","No current revocation event is being evaluated.","Consent status is evaluated through the carry path unless a revocation, partial revocation, or boundary harm is present.","Keep revocation path visible and ready."], |
| REVOCATION_READY:["REVOCATION READY","The system has enough notice, chain visibility, enforcement, and confirmation to propagate a future revocation.","The boundary has a usable return path if consent changes.","Proceed within scope. Keep receipt and enforcement path auditable."], |
| PARTIAL_REVOCATION_PROPAGATING:["PARTIAL REVOCATION PROPAGATING","A narrowed boundary is moving downstream.","The original authorization is not erased; the allowed scope is rewritten and must reach affected branches.","Propagate the narrowed scope and confirm branch compliance."], |
| REVOCATION_PROPAGATING:["REVOCATION PROPAGATING","Revocation has been expressed and is moving downstream, but not all branches have confirmed compliance.","The boundary delta is in motion, but enforcement is incomplete.","Continue propagation. Hold affected uses until confirmation returns."], |
| REVOKED_SCOPE_CONFIRMED:["REVOKED SCOPE CONFIRMED","The affected scope has been revoked and downstream confirmation is strong enough.","The boundary delta reached the known chain and forward use is downgraded or stopped.","Keep the audit spine and enforce the revoked scope going forward."], |
| DOWNSTREAM_QUARANTINE:["DOWNSTREAM QUARANTINE","Downstream branches are affected but cannot be verified.","The revocation may not have reached all copies, caches, partners, or automated branches.","Quarantine affected downstream use until visibility and enforcement are proven."], |
| RETENTION_LIMIT_REQUIRED:["RETENTION LIMIT REQUIRED","Retention residue remains too high for clean revocation propagation.","The boundary delta reached the system, but copies/caches/archives still require scoped handling.","Define deletion, downgrade, archive, or legal/operational retention handling."], |
| REPAIR_REQUIRED:["REPAIR REQUIRED","A prior boundary harm or revocation failure requires repair before ordinary propagation.","Revocation failure has become a repair loop, not merely a status update.","Name the harm, notify affected parties where appropriate, correct records, and prevent recurrence."], |
| HUMAN_REVIEW_REQUIRED:["HUMAN REVIEW REQUIRED","The revocation context is sensitive, ambiguous, or high-stakes enough to require human review.","The boundary should not be resolved by automation alone.","Route for human review with chain, scope, retention, and affected branches visible."] |
| }; |
| |
| const routeTriggers={ |
| CONSENT_ACTIVE:"Trigger: no revocation/harm gates, high authorization support, high scope support, low drift pressure, and consent currently granted.", |
| BOUNDED_CONSENT:"Trigger: consent support is adequate but scope is not strong enough for broad inheritance.", |
| SCOPE_CLARIFICATION_REQUIRED:"Trigger: purpose, recipient, data/action boundary, duration, or revocation path is below clarity threshold.", |
| SCOPE_MISMATCH:"Trigger: consent exists, but scope support is weak while drift/reuse pressure is high.", |
| REPROOF_REQUIRED:"Trigger: sensitive/high-stakes context, stale consent, transferred use, weak record, or high uncertainty requires renewed authorization.", |
| PARTIAL_REVOCATION:"Trigger: partial revocation gate is active.", |
| REVOKED_SCOPE:"Trigger: full revocation gate is active.", |
| DOWNSTREAM_QUARANTINE:"Trigger: downstream sharing occurred and chain visibility/enforcement/confirmation are too weak.", |
| RETENTION_LIMIT_REQUIRED:"Trigger: retention friction or residual copy/cache risk is high.", |
| REPAIR_REQUIRED:"Trigger: prior consent boundary harm is present.", |
| HUMAN_REVIEW_REQUIRED:"Trigger: sensitive or high-stakes context with weak consent, scope, or propagation support.", |
| NO_REVOCATION_ACTIVE:"Trigger: no partial or full revocation is active.", |
| REVOCATION_READY:"Trigger: revocation path, chain visibility, enforcement, and confirmation are strong enough for future propagation.", |
| PARTIAL_REVOCATION_PROPAGATING:"Trigger: partial revocation is active and downstream handling is in progress.", |
| REVOCATION_PROPAGATING:"Trigger: full revocation is active, but not all downstream confirmations have returned.", |
| REVOKED_SCOPE_CONFIRMED:"Trigger: full revocation is active with strong chain visibility, enforcement, confirmation, and manageable residue pressure." |
| }; |
| |
| const flagDefs={ |
| RepairDebt:"A prior consent boundary was violated, ignored, erased, or misrepresented.", |
| FullRevocation:"Consent has been withdrawn for the affected scope.", |
| PartialRevocation:"Consent has been narrowed; only remaining explicitly authorized scope can continue.", |
| DownstreamOpacity:"Downstream branches, partners, copies, or automations are not visible enough.", |
| ScopeAmbiguity:"Purpose, recipient, action/data boundary, duration, or revocation path is unclear.", |
| PurposeMismatch:"Purpose clarity is too weak for the proposed use.", |
| RecipientUnclear:"Recipient clarity is weak.", |
| RevocationPathMissing:"The route for withdrawing or narrowing consent is not visible enough.", |
| ConsentStale:"Consent is not current enough to carry safely.", |
| RecordWeak:"Consent record or provenance is weak.", |
| RetentionResidue:"Retention friction or residual copy/cache risk is high.", |
| AutomationLag:"Automation lag could let old authorization continue after boundary change.", |
| SensitiveContext:"High-stakes context requires extra caution or human review.", |
| ReproofNeeded:"Renewed authorization is needed before scope inheritance.", |
| QuarantineNeeded:"Affected downstream branches should be quarantined until status is verified.", |
| HumanReview:"Automation is insufficient for this boundary decision.", |
| ConsentClear:"Consent boundary appears clear for the evaluated scope.", |
| RevocationReady:"Revocation path appears ready if consent changes." |
| }; |
| |
| const presets={ |
| clean_bounded:["Clean Bounded Consent",{explicitAuthorization:.92,informedUnderstanding:.88,currentConsent:.90,identityAuthority:.90,consentRecord:.86,purposeClarity:.88,recipientClarity:.84,dataActionBoundary:.86,durationClarity:.82,revocationPathVisible:.86,downstreamBranchCount:.20,transferOpacity:.18,retentionFriction:.22,automationLag:.14,institutionalIncentive:.20,revocationNotice:.80,chainVisibility:.78,enforcementAbility:.80,confirmationReturned:.76,residualCopyRisk:.20,consentGranted:true,partialRevocation:false,fullRevocation:false,downstreamSharing:false,priorConsentHarm:false,sensitiveContext:false}], |
| ambiguous_app:["Ambiguous App Permission",{explicitAuthorization:.62,informedUnderstanding:.42,currentConsent:.74,identityAuthority:.76,consentRecord:.54,purposeClarity:.34,recipientClarity:.32,dataActionBoundary:.40,durationClarity:.36,revocationPathVisible:.30,downstreamBranchCount:.66,transferOpacity:.72,retentionFriction:.62,automationLag:.52,institutionalIncentive:.74,revocationNotice:.42,chainVisibility:.30,enforcementAbility:.34,confirmationReturned:.28,residualCopyRisk:.64,consentGranted:true,partialRevocation:false,fullRevocation:false,downstreamSharing:true,priorConsentHarm:false,sensitiveContext:false}], |
| patient_ehr_revocation:["Patient EHR Consent Revocation",{explicitAuthorization:.74,informedUnderstanding:.70,currentConsent:.54,identityAuthority:.84,consentRecord:.82,purposeClarity:.68,recipientClarity:.62,dataActionBoundary:.58,durationClarity:.56,revocationPathVisible:.72,downstreamBranchCount:.72,transferOpacity:.58,retentionFriction:.78,automationLag:.50,institutionalIncentive:.42,revocationNotice:.86,chainVisibility:.52,enforcementAbility:.60,confirmationReturned:.42,residualCopyRisk:.76,consentGranted:false,partialRevocation:false,fullRevocation:true,downstreamSharing:true,priorConsentHarm:false,sensitiveContext:true}], |
| health_partial_revocation:["Health Data Partial Revocation",{explicitAuthorization:.80,informedUnderstanding:.76,currentConsent:.62,identityAuthority:.86,consentRecord:.84,purposeClarity:.74,recipientClarity:.68,dataActionBoundary:.66,durationClarity:.60,revocationPathVisible:.78,downstreamBranchCount:.68,transferOpacity:.50,retentionFriction:.64,automationLag:.46,institutionalIncentive:.48,revocationNotice:.82,chainVisibility:.58,enforcementAbility:.64,confirmationReturned:.50,residualCopyRisk:.60,consentGranted:true,partialRevocation:true,fullRevocation:false,downstreamSharing:true,priorConsentHarm:false,sensitiveContext:true}], |
| caregiver_proxy_drift:["Caregiver / Proxy Boundary Drift",{explicitAuthorization:.54,informedUnderstanding:.50,currentConsent:.66,identityAuthority:.38,consentRecord:.48,purposeClarity:.56,recipientClarity:.50,dataActionBoundary:.44,durationClarity:.44,revocationPathVisible:.42,downstreamBranchCount:.42,transferOpacity:.50,retentionFriction:.44,automationLag:.34,institutionalIncentive:.38,revocationNotice:.42,chainVisibility:.46,enforcementAbility:.48,confirmationReturned:.40,residualCopyRisk:.44,consentGranted:true,partialRevocation:false,fullRevocation:false,downstreamSharing:false,priorConsentHarm:false,sensitiveContext:true}], |
| ai_agent_permission_drift:["AI Agent Tool Permission Drift",{explicitAuthorization:.68,informedUnderstanding:.58,currentConsent:.46,identityAuthority:.70,consentRecord:.64,purposeClarity:.44,recipientClarity:.58,dataActionBoundary:.36,durationClarity:.32,revocationPathVisible:.46,downstreamBranchCount:.54,transferOpacity:.62,retentionFriction:.46,automationLag:.82,institutionalIncentive:.58,revocationNotice:.52,chainVisibility:.48,enforcementAbility:.42,confirmationReturned:.30,residualCopyRisk:.50,consentGranted:true,partialRevocation:false,fullRevocation:false,downstreamSharing:true,priorConsentHarm:false,sensitiveContext:false}], |
| platform_reuse:["Public Platform Data Reuse",{explicitAuthorization:.58,informedUnderstanding:.38,currentConsent:.64,identityAuthority:.70,consentRecord:.46,purposeClarity:.30,recipientClarity:.28,dataActionBoundary:.32,durationClarity:.26,revocationPathVisible:.24,downstreamBranchCount:.86,transferOpacity:.82,retentionFriction:.76,automationLag:.62,institutionalIncentive:.88,revocationNotice:.36,chainVisibility:.22,enforcementAbility:.28,confirmationReturned:.16,residualCopyRisk:.82,consentGranted:true,partialRevocation:false,fullRevocation:false,downstreamSharing:true,priorConsentHarm:false,sensitiveContext:false}], |
| revocation_lost_downstream:["Revocation Lost Downstream",{explicitAuthorization:.70,informedUnderstanding:.66,currentConsent:.40,identityAuthority:.80,consentRecord:.76,purposeClarity:.64,recipientClarity:.56,dataActionBoundary:.58,durationClarity:.50,revocationPathVisible:.70,downstreamBranchCount:.88,transferOpacity:.84,retentionFriction:.82,automationLag:.74,institutionalIncentive:.64,revocationNotice:.88,chainVisibility:.24,enforcementAbility:.30,confirmationReturned:.14,residualCopyRisk:.86,consentGranted:false,partialRevocation:false,fullRevocation:true,downstreamSharing:true,priorConsentHarm:false,sensitiveContext:false}], |
| prior_boundary_harm:["Prior Consent Boundary Harm",{explicitAuthorization:.46,informedUnderstanding:.40,currentConsent:.36,identityAuthority:.62,consentRecord:.32,purposeClarity:.34,recipientClarity:.36,dataActionBoundary:.30,durationClarity:.28,revocationPathVisible:.22,downstreamBranchCount:.62,transferOpacity:.70,retentionFriction:.74,automationLag:.62,institutionalIncentive:.58,revocationNotice:.42,chainVisibility:.30,enforcementAbility:.28,confirmationReturned:.18,residualCopyRisk:.78,consentGranted:false,partialRevocation:false,fullRevocation:false,downstreamSharing:true,priorConsentHarm:true,sensitiveContext:true}] |
| }; |
| |
| function compute(){ |
| const explicitAuthorization=val("explicitAuthorization"), informedUnderstanding=val("informedUnderstanding"), currentConsent=val("currentConsent"), identityAuthority=val("identityAuthority"), consentRecord=val("consentRecord"); |
| const purposeClarity=val("purposeClarity"), recipientClarity=val("recipientClarity"), dataActionBoundary=val("dataActionBoundary"), durationClarity=val("durationClarity"), revocationPathVisible=val("revocationPathVisible"); |
| const downstreamBranchCount=val("downstreamBranchCount"), transferOpacity=val("transferOpacity"), retentionFriction=val("retentionFriction"), automationLag=val("automationLag"), institutionalIncentive=val("institutionalIncentive"); |
| const revocationNotice=val("revocationNotice"), chainVisibility=val("chainVisibility"), enforcementAbility=val("enforcementAbility"), confirmationReturned=val("confirmationReturned"), residualCopyRisk=val("residualCopyRisk"); |
| const consentGranted=el("consentGranted").checked, partialRevocation=el("partialRevocation").checked, fullRevocation=el("fullRevocation").checked, downstreamSharing=el("downstreamSharing").checked, priorConsentHarm=el("priorConsentHarm").checked, sensitiveContext=el("sensitiveContext").checked; |
| |
| const aAuthorization=.25*explicitAuthorization+.20*informedUnderstanding+.18*currentConsent+.20*identityAuthority+.17*consentRecord; |
| const bScope=.24*purposeClarity+.20*recipientClarity+.22*dataActionBoundary+.16*durationClarity+.18*revocationPathVisible; |
| const revocationDrift=avg([downstreamBranchCount,transferOpacity,retentionFriction,automationLag,institutionalIncentive]); |
| const authorizationSupport=aAuthorization; |
| const scopeSupport=bScope; |
| const consentSupport=aAuthorization*bScope; |
| const consentStability=consentSupport-revocationDrift; |
| |
| const residuePressure=.50*residualCopyRisk+.20*retentionFriction+.15*transferOpacity+.15*automationLag; |
| const revocationSupport=revocationNotice*chainVisibility*((enforcementAbility+confirmationReturned)/2); |
| const revocationStability=revocationSupport-residuePressure; |
| const boundaryDeltaStrength=fullRevocation||partialRevocation ? revocationNotice*((chainVisibility+enforcementAbility+confirmationReturned)/3) : revocationPathVisible*((chainVisibility+enforcementAbility)/2); |
| |
| let consentFlags=[]; |
| if(priorConsentHarm) consentFlags.push(["RepairDebt","hot"]); |
| if(fullRevocation) consentFlags.push(["FullRevocation","hot"]); |
| if(partialRevocation) consentFlags.push(["PartialRevocation","warn"]); |
| if(downstreamSharing && (chainVisibility<.50 || transferOpacity>.65)) consentFlags.push(["DownstreamOpacity","warn"]); |
| if(bScope<.50) consentFlags.push(["ScopeAmbiguity","warn"]); |
| if(purposeClarity<.45) consentFlags.push(["PurposeMismatch","warn"]); |
| if(recipientClarity<.45) consentFlags.push(["RecipientUnclear","warn"]); |
| if(revocationPathVisible<.45) consentFlags.push(["RevocationPathMissing","warn"]); |
| if(currentConsent<.50) consentFlags.push(["ConsentStale","warn"]); |
| if(consentRecord<.50) consentFlags.push(["RecordWeak","warn"]); |
| if(retentionFriction>.68 || residualCopyRisk>.68) consentFlags.push(["RetentionResidue","warn"]); |
| if(automationLag>.70) consentFlags.push(["AutomationLag","warn"]); |
| if(sensitiveContext) consentFlags.push(["SensitiveContext","info"]); |
| |
| let revocationFlags=[]; |
| if(priorConsentHarm) revocationFlags.push(["RepairDebt","hot"]); |
| if(fullRevocation) revocationFlags.push(["FullRevocation","hot"]); |
| if(partialRevocation) revocationFlags.push(["PartialRevocation","warn"]); |
| if(downstreamSharing && (chainVisibility<.50 || transferOpacity>.65)) revocationFlags.push(["DownstreamOpacity","warn"]); |
| if(retentionFriction>.68 || residualCopyRisk>.68) revocationFlags.push(["RetentionResidue","warn"]); |
| if(automationLag>.70) revocationFlags.push(["AutomationLag","warn"]); |
| if(sensitiveContext) revocationFlags.push(["SensitiveContext","info"]); |
| |
| let consentRoute="BOUNDED_CONSENT"; |
| if(priorConsentHarm) consentRoute="REPAIR_REQUIRED"; |
| else if(fullRevocation) consentRoute="REVOKED_SCOPE"; |
| else if(downstreamSharing && (chainVisibility<.35 || enforcementAbility<.35 || confirmationReturned<.25) && (fullRevocation || partialRevocation || revocationNotice>.70)) consentRoute="DOWNSTREAM_QUARANTINE"; |
| else if(partialRevocation) consentRoute="PARTIAL_REVOCATION"; |
| else if((retentionFriction>.76 || residualCopyRisk>.76) && downstreamSharing) consentRoute="RETENTION_LIMIT_REQUIRED"; |
| else if(sensitiveContext && (consentSupport<.52 || bScope<.58 || aAuthorization<.58)) consentRoute="HUMAN_REVIEW_REQUIRED"; |
| else if(consentGranted && bScope<.42 && revocationDrift>.55) consentRoute="SCOPE_MISMATCH"; |
| else if(consentGranted && (bScope<.50 || purposeClarity<.45 || recipientClarity<.45 || dataActionBoundary<.45 || durationClarity<.38 || revocationPathVisible<.40)) consentRoute="SCOPE_CLARIFICATION_REQUIRED"; |
| else if(!consentGranted || currentConsent<.50 || consentRecord<.45 || (sensitiveContext && consentSupport<.66)) consentRoute="REPROOF_REQUIRED"; |
| else if(consentSupport>=.70 && revocationDrift<.38 && consentGranted) consentRoute="CONSENT_ACTIVE"; |
| else consentRoute="BOUNDED_CONSENT"; |
| |
| let revocationRoute="NO_REVOCATION_ACTIVE"; |
| if(priorConsentHarm) revocationRoute="REPAIR_REQUIRED"; |
| else if(fullRevocation && revocationSupport>=.55 && residuePressure<.45 && confirmationReturned>=.65) revocationRoute="REVOKED_SCOPE_CONFIRMED"; |
| else if(fullRevocation && downstreamSharing && (chainVisibility<.45 || enforcementAbility<.45 || confirmationReturned<.45)) revocationRoute="DOWNSTREAM_QUARANTINE"; |
| else if((fullRevocation || partialRevocation) && (retentionFriction>.76 || residualCopyRisk>.76)) revocationRoute="RETENTION_LIMIT_REQUIRED"; |
| else if(sensitiveContext && (fullRevocation || partialRevocation) && (revocationSupport<.50 || residuePressure>.55)) revocationRoute="HUMAN_REVIEW_REQUIRED"; |
| else if(partialRevocation) revocationRoute="PARTIAL_REVOCATION_PROPAGATING"; |
| else if(fullRevocation) revocationRoute="REVOCATION_PROPAGATING"; |
| else if(revocationPathVisible>=.72 && chainVisibility>=.65 && enforcementAbility>=.65 && confirmationReturned>=.55 && residuePressure<.50) revocationRoute="REVOCATION_READY"; |
| else revocationRoute="NO_REVOCATION_ACTIVE"; |
| |
| if(!consentFlags.length) consentFlags.push(["ConsentClear","good"]); |
| if(!revocationFlags.length) revocationFlags.push(["RevocationReady","good"]); |
| if(consentRoute==="REPROOF_REQUIRED") consentFlags.push(["ReproofNeeded","warn"]); |
| if(consentRoute==="DOWNSTREAM_QUARANTINE" || revocationRoute==="DOWNSTREAM_QUARANTINE") { consentFlags.push(["QuarantineNeeded","hot"]); revocationFlags.push(["QuarantineNeeded","hot"]); } |
| if(consentRoute==="HUMAN_REVIEW_REQUIRED" || revocationRoute==="HUMAN_REVIEW_REQUIRED") { consentFlags.push(["HumanReview","warn"]); revocationFlags.push(["HumanReview","warn"]); } |
| |
| return {explicitAuthorization,informedUnderstanding,currentConsent,identityAuthority,consentRecord,purposeClarity,recipientClarity,dataActionBoundary,durationClarity,revocationPathVisible,downstreamBranchCount,transferOpacity,retentionFriction,automationLag,institutionalIncentive,revocationNotice,chainVisibility,enforcementAbility,confirmationReturned,residualCopyRisk,consentGranted,partialRevocation,fullRevocation,downstreamSharing,priorConsentHarm,sensitiveContext,aAuthorization,bScope,authorizationSupport,scopeSupport,revocationDrift,consentSupport,consentStability,residuePressure,revocationSupport,revocationStability,boundaryDeltaStrength,consentRoute,revocationRoute,consentFlags,revocationFlags}; |
| } |
| |
| function routeWhy(c){ |
| let consentWhy=""; |
| if(c.consentRoute==="REPAIR_REQUIRED") consentWhy="REPAIR_REQUIRED triggered because prior consent boundary harm is present. Repair debt overrides ordinary consent evaluation."; |
| else if(c.consentRoute==="REVOKED_SCOPE") consentWhy="REVOKED_SCOPE triggered because full revocation is active for the affected scope. Forward use cannot inherit authorization."; |
| else if(c.consentRoute==="DOWNSTREAM_QUARANTINE") consentWhy="DOWNSTREAM_QUARANTINE triggered because revocation or boundary change affects downstream branches that cannot be verified."; |
| else if(c.consentRoute==="PARTIAL_REVOCATION") consentWhy="PARTIAL_REVOCATION triggered because consent has been narrowed. Remaining use must fit the new boundary."; |
| else if(c.consentRoute==="RETENTION_LIMIT_REQUIRED") consentWhy="RETENTION_LIMIT_REQUIRED triggered because retention friction or residual copy/cache risk is high enough to require scoped handling."; |
| else if(c.consentRoute==="HUMAN_REVIEW_REQUIRED") consentWhy="HUMAN_REVIEW_REQUIRED triggered because sensitive/high-stakes context combines with weak consent or scope support."; |
| else if(c.consentRoute==="SCOPE_MISMATCH") consentWhy="SCOPE_MISMATCH triggered because proposed use pressure exceeds the clarity of the consented scope."; |
| else if(c.consentRoute==="SCOPE_CLARIFICATION_REQUIRED") consentWhy="SCOPE_CLARIFICATION_REQUIRED triggered because purpose, recipient, duration, action boundary, or revocation path is unclear."; |
| else if(c.consentRoute==="REPROOF_REQUIRED") consentWhy="REPROOF_REQUIRED triggered because consent is stale, absent, weakly recorded, transferred, or high-stakes enough to require renewed authorization."; |
| else if(c.consentRoute==="CONSENT_ACTIVE") consentWhy="CONSENT_ACTIVE triggered because authorization support and scope support are high while drift pressure is low."; |
| else consentWhy="BOUNDED_CONSENT triggered because consent exists, but only inside a limited scope that should not silently expand."; |
| |
| let revocationWhy=""; |
| if(c.revocationRoute==="REPAIR_REQUIRED") revocationWhy="REPAIR_REQUIRED triggered because a prior revocation or consent-boundary failure requires repair before ordinary propagation."; |
| else if(c.revocationRoute==="REVOKED_SCOPE_CONFIRMED") revocationWhy="REVOKED_SCOPE_CONFIRMED triggered because revocation is active and downstream confirmation is strong enough."; |
| else if(c.revocationRoute==="DOWNSTREAM_QUARANTINE") revocationWhy="DOWNSTREAM_QUARANTINE triggered because affected downstream branches cannot be verified."; |
| else if(c.revocationRoute==="RETENTION_LIMIT_REQUIRED") revocationWhy="RETENTION_LIMIT_REQUIRED triggered because retained copies, caches, archives, or operational/legal residue require scoped handling."; |
| else if(c.revocationRoute==="HUMAN_REVIEW_REQUIRED") revocationWhy="HUMAN_REVIEW_REQUIRED triggered because sensitive or high-stakes revocation cannot be completed by automation alone."; |
| else if(c.revocationRoute==="PARTIAL_REVOCATION_PROPAGATING") revocationWhy="PARTIAL_REVOCATION_PROPAGATING triggered because narrowed consent is moving downstream."; |
| else if(c.revocationRoute==="REVOCATION_PROPAGATING") revocationWhy="REVOCATION_PROPAGATING triggered because full revocation is active but not yet fully confirmed across the downstream chain."; |
| else if(c.revocationRoute==="REVOCATION_READY") revocationWhy="REVOCATION_READY triggered because notice, chain visibility, enforcement, and confirmation support are strong enough for future revocation."; |
| else revocationWhy="NO_REVOCATION_ACTIVE triggered because no partial or full revocation is currently active."; |
| return {consentWhy,revocationWhy}; |
| } |
| |
| function renderFlags(id, flags){ |
| el(id).innerHTML=""; |
| flags.forEach(([name,cls])=>{let s=document.createElement("span");s.className="flag "+cls;s.textContent=name;el(id).appendChild(s)}); |
| } |
| |
| function update(){ |
| sliders.forEach(id=>el(id+"Val").textContent=r(val(id))); |
| const c=compute(), consent=consentRoutes[c.consentRoute], rev=revocationRoutes[c.revocationRoute], why=routeWhy(c); |
| el("consentState").textContent=consent[0]; el("consentMeaning").textContent=consent[1]; el("consentRead").textContent=consent[2]; el("consentWhy").textContent=why.consentWhy; el("consentAction").textContent=consent[3]; |
| el("authorizationSupport").textContent=r(c.authorizationSupport); el("scopeSupport").textContent=r(c.scopeSupport); el("consentStability").textContent=r(c.consentStability); el("revocationDrift").textContent=r(c.revocationDrift); renderFlags("consentFlags",c.consentFlags); |
| el("revocationState").textContent=rev[0]; el("revocationMeaning").textContent=rev[1]; el("revocationRead").textContent=rev[2]; el("revocationWhy").textContent=why.revocationWhy; el("revocationAction").textContent=rev[3]; |
| el("revocationSupport").textContent=r(c.revocationSupport); el("residuePressure").textContent=r(c.residuePressure); el("revocationStability").textContent=r(c.revocationStability); el("boundaryDeltaStrength").textContent=r(c.boundaryDeltaStrength); renderFlags("revocationFlags",c.revocationFlags); |
| if(urlSyncReady) writeUrlState(); |
| } |
| |
| function applyPreset(k){ |
| const p=presets[k][1]; |
| Object.entries(p).forEach(([key,v])=>{if(typeof v==="boolean")el(key).checked=v;else el(key).value=v}); |
| update(); |
| } |
| |
| function report(){ |
| const c=compute(), consent=consentRoutes[c.consentRoute], rev=revocationRoutes[c.revocationRoute], why=routeWhy(c), preset=presets[el("preset").value]?.[0]||"Manual exploration"; |
| el("report").value=`Consent / Revocation Propagation Simulator v0.0.4 — Living Boundary Report |
| |
| Scenario: |
| ${preset} |
| |
| Boundary: |
| This simulator does not provide legal advice, HIPAA compliance certification, privacy-law analysis, or clinical authorization. It maps whether a consent boundary remains structurally intact under handoff, downstream reuse, retention, and revocation pressure. |
| |
| Original Consent / Boundary: |
| ${el("originalConsent").value} |
| |
| Downstream Use / Revocation Event: |
| ${el("downstreamEvent").value} |
| |
| Consent Carry State: |
| - Recommended State: ${consent[0]} |
| - A_authorization / Authorization Support: ${r(c.authorizationSupport)} |
| - B_scope / Scope Support: ${r(c.scopeSupport)} |
| - P_revocation_drift / Drift Pressure: ${r(c.revocationDrift)} |
| - Consent Stability: ${r(c.consentStability)} |
| - Flags: |
| ${c.consentFlags.map(f=>"- "+f[0]+": "+(flagDefs[f[0]]||"No definition available.")).join("\n")} |
| - Read: ${consent[2]} |
| - Why this route: ${why.consentWhy} |
| - Action: ${consent[3]} |
| |
| Revocation Propagation State: |
| - Recommended State: ${rev[0]} |
| - Revocation Support: ${r(c.revocationSupport)} |
| - Residue Pressure: ${r(c.residuePressure)} |
| - Propagation Stability: ${r(c.revocationStability)} |
| - Boundary Delta Strength: ${r(c.boundaryDeltaStrength)} |
| - Flags: |
| ${c.revocationFlags.map(f=>"- "+f[0]+": "+(flagDefs[f[0]]||"No definition available.")).join("\n")} |
| - Read: ${rev[2]} |
| - Why this route: ${why.revocationWhy} |
| - Action: ${rev[3]} |
| |
| Core equations: |
| S_consent = A_authorization × B_scope − P_revocation_drift |
| S_revocation = R_notice × C_chain × E_enforcement − P_residue |
| |
| Route transparency note: |
| Repair, revocation, downstream quarantine, retention limits, and human review can override ordinary consent-active routing. The route is the product; scores support the route. |
| |
| Sensitivity Analysis: |
| ${lastSensitivity ? `- Overall: ${lastSensitivity.overall}\n- Consent route stability: ${lastSensitivity.consentStabilityLabel}\n- Revocation route stability: ${lastSensitivity.revocationStabilityLabel}\n- Consent route changes under ±10% perturbation: ${lastSensitivity.consentChanges}\n- Revocation route changes under ±10% perturbation: ${lastSensitivity.revocationChanges}\n- Top consent sensitivity drivers: ${driverSummary(lastSensitivity.consentFlips||[])}\n- Top revocation sensitivity drivers: ${driverSummary(lastSensitivity.revocationFlips||[])}\n- Consent flip trace: ${flipTraceSummary(lastSensitivity.consentFlips||[])}\n- Revocation flip trace: ${flipTraceSummary(lastSensitivity.revocationFlips||[])}` : "Not run for this report."} |
| |
| Shareable State: |
| ${encodeStateToURL()} |
| |
| Public thesis: |
| Consent granted is not enough. Consent carried is the real test. |
| Revocation received is not enough. Revocation propagated is the real test. |
| Revocation propagates as a boundary delta, not erasure.`; |
| } |
| |
| function renderTaxonomy(){ |
| const box=el("taxonomy"); |
| Object.entries(consentRoutes).forEach(([k,v])=>{let d=document.createElement("div");d.className="route-card";d.innerHTML=`<b>${v[0]}</b><p>${v[1]}</p><p><b>Trigger:</b> ${routeTriggers[k]||"Route condition depends on boundary state."}</p><p><b>Action:</b> ${v[3]}</p>`;box.appendChild(d)}); |
| Object.entries(revocationRoutes).forEach(([k,v])=>{let d=document.createElement("div");d.className="route-card";d.innerHTML=`<b>${v[0]}</b><p>${v[1]}</p><p><b>Trigger:</b> ${routeTriggers[k]||"Route condition depends on propagation state."}</p><p><b>Action:</b> ${v[3]}</p>`;box.appendChild(d)}); |
| } |
| |
| const translations={ |
| plain:"This tool checks whether a consent boundary stayed clear, scoped, current, revocable, and enforceable as it moved through downstream systems.", |
| patient:"Patient / EHR read: consent must preserve patient agency as records, AI summaries, caregiver/proxy reports, research reuse, and downstream recipients change. Revocation is a boundary update that must reach affected branches without erasing the audit spine.", |
| platform:"Platform / data reuse read: clicking yes once is not enough if the data later moves to partners, caches, training sets, ads, summaries, exports, or archives. Scope and revocation have to travel with the data.", |
| agent:"AI agent read: permission for one task does not automatically authorize every later tool call. If task scope changes, the agent should re-proof authorization instead of inheriting stale permission.", |
| system:"System map: Patient/User Intent → Consent Field → System Handoff → Downstream Receiver → Use / Retention / Deletion / Downgrade / Repair." |
| }; |
| |
| let urlSyncReady=false; |
| let lastSensitivity=null; |
| function getScenarioState(){ |
| const state={preset:el("preset").value}; |
| sliders.forEach(id=>{state[id]=parseFloat(el(id).value);}); |
| toggles.forEach(id=>{state[id]=el(id).checked;}); |
| state.originalConsent=el("originalConsent").value; |
| state.downstreamEvent=el("downstreamEvent").value; |
| return state; |
| } |
| function applyScenarioState(state){ |
| if(state.preset && presets[state.preset]) el("preset").value=state.preset; |
| sliders.forEach(id=>{if(state[id]!==undefined && !Number.isNaN(parseFloat(state[id]))) el(id).value=Math.max(0,Math.min(1,parseFloat(state[id])));}); |
| toggles.forEach(id=>{if(state[id]!==undefined) el(id).checked=(state[id]===true || state[id]==="1" || state[id]==="true");}); |
| if(state.originalConsent!==undefined) el("originalConsent").value=state.originalConsent; |
| if(state.downstreamEvent!==undefined) el("downstreamEvent").value=state.downstreamEvent; |
| update(); |
| } |
| function encodeStateToURL(){ |
| const params=new URLSearchParams(); |
| const state=getScenarioState(); |
| Object.entries(state).forEach(([key,value])=>{ |
| if(typeof value==="boolean") params.set(key,value?"1":"0"); |
| else if(typeof value==="number") params.set(key,value.toFixed(3)); |
| else if(typeof value==="string") params.set(key,value); |
| }); |
| const url=new URL(window.location.href); |
| url.hash=params.toString(); |
| return url.toString(); |
| } |
| function loadStateFromURL(){ |
| const hash=window.location.hash.substring(1); |
| if(!hash) return false; |
| const params=new URLSearchParams(hash); |
| const state={}; |
| for(const [key,value] of params.entries()){ |
| if(toggles.includes(key)) state[key]=value==="1"; |
| else if(sliders.includes(key)) state[key]=parseFloat(value); |
| else state[key]=value; |
| } |
| if(Object.keys(state).length){applyScenarioState(state);return true;} |
| return false; |
| } |
| function writeUrlState(){ |
| if(!urlSyncReady) return; |
| history.replaceState(null,"",encodeStateToURL()); |
| } |
| |
| |
| function getCurrentStateObject(){ |
| const state={}; |
| sliders.forEach(id=>{state[id]=val(id);}); |
| toggles.forEach(id=>{state[id]=el(id).checked;}); |
| return state; |
| } |
| function clamp01(x){return Math.max(0,Math.min(1,x));} |
| function computeFromState(p){ |
| const explicitAuthorization=p.explicitAuthorization, informedUnderstanding=p.informedUnderstanding, currentConsent=p.currentConsent, identityAuthority=p.identityAuthority, consentRecord=p.consentRecord; |
| const purposeClarity=p.purposeClarity, recipientClarity=p.recipientClarity, dataActionBoundary=p.dataActionBoundary, durationClarity=p.durationClarity, revocationPathVisible=p.revocationPathVisible; |
| const downstreamBranchCount=p.downstreamBranchCount, transferOpacity=p.transferOpacity, retentionFriction=p.retentionFriction, automationLag=p.automationLag, institutionalIncentive=p.institutionalIncentive; |
| const revocationNotice=p.revocationNotice, chainVisibility=p.chainVisibility, enforcementAbility=p.enforcementAbility, confirmationReturned=p.confirmationReturned, residualCopyRisk=p.residualCopyRisk; |
| const consentGranted=p.consentGranted, partialRevocation=p.partialRevocation, fullRevocation=p.fullRevocation, downstreamSharing=p.downstreamSharing, priorConsentHarm=p.priorConsentHarm, sensitiveContext=p.sensitiveContext; |
| |
| const aAuthorization=.25*explicitAuthorization+.20*informedUnderstanding+.18*currentConsent+.20*identityAuthority+.17*consentRecord; |
| const bScope=.24*purposeClarity+.20*recipientClarity+.22*dataActionBoundary+.16*durationClarity+.18*revocationPathVisible; |
| const revocationDrift=avg([downstreamBranchCount,transferOpacity,retentionFriction,automationLag,institutionalIncentive]); |
| const consentSupport=aAuthorization*bScope; |
| const consentStability=consentSupport-revocationDrift; |
| |
| const residuePressure=.50*residualCopyRisk+.20*retentionFriction+.15*transferOpacity+.15*automationLag; |
| const revocationSupport=revocationNotice*chainVisibility*((enforcementAbility+confirmationReturned)/2); |
| const revocationStability=revocationSupport-residuePressure; |
| |
| let consentRoute="BOUNDED_CONSENT"; |
| if(priorConsentHarm) consentRoute="REPAIR_REQUIRED"; |
| else if(fullRevocation) consentRoute="REVOKED_SCOPE"; |
| else if(downstreamSharing && (chainVisibility<.35 || enforcementAbility<.35 || confirmationReturned<.25) && (fullRevocation || partialRevocation || revocationNotice>.70)) consentRoute="DOWNSTREAM_QUARANTINE"; |
| else if(partialRevocation) consentRoute="PARTIAL_REVOCATION"; |
| else if((retentionFriction>.76 || residualCopyRisk>.76) && downstreamSharing) consentRoute="RETENTION_LIMIT_REQUIRED"; |
| else if(sensitiveContext && (consentSupport<.52 || bScope<.58 || aAuthorization<.58)) consentRoute="HUMAN_REVIEW_REQUIRED"; |
| else if(consentGranted && bScope<.42 && revocationDrift>.55) consentRoute="SCOPE_MISMATCH"; |
| else if(consentGranted && (bScope<.50 || purposeClarity<.45 || recipientClarity<.45 || dataActionBoundary<.45 || durationClarity<.38 || revocationPathVisible<.40)) consentRoute="SCOPE_CLARIFICATION_REQUIRED"; |
| else if(!consentGranted || currentConsent<.50 || consentRecord<.45 || (sensitiveContext && consentSupport<.66)) consentRoute="REPROOF_REQUIRED"; |
| else if(consentSupport>=.70 && revocationDrift<.38 && consentGranted) consentRoute="CONSENT_ACTIVE"; |
| else consentRoute="BOUNDED_CONSENT"; |
| |
| let revocationRoute="NO_REVOCATION_ACTIVE"; |
| if(priorConsentHarm) revocationRoute="REPAIR_REQUIRED"; |
| else if(fullRevocation && revocationSupport>=.55 && residuePressure<.45 && confirmationReturned>=.65) revocationRoute="REVOKED_SCOPE_CONFIRMED"; |
| else if(fullRevocation && downstreamSharing && (chainVisibility<.45 || enforcementAbility<.45 || confirmationReturned<.45)) revocationRoute="DOWNSTREAM_QUARANTINE"; |
| else if((fullRevocation || partialRevocation) && (retentionFriction>.76 || residualCopyRisk>.76)) revocationRoute="RETENTION_LIMIT_REQUIRED"; |
| else if(sensitiveContext && (fullRevocation || partialRevocation) && (revocationSupport<.50 || residuePressure>.55)) revocationRoute="HUMAN_REVIEW_REQUIRED"; |
| else if(partialRevocation) revocationRoute="PARTIAL_REVOCATION_PROPAGATING"; |
| else if(fullRevocation) revocationRoute="REVOCATION_PROPAGATING"; |
| else if(revocationPathVisible>=.72 && chainVisibility>=.65 && enforcementAbility>=.65 && confirmationReturned>=.55 && residuePressure<.50) revocationRoute="REVOCATION_READY"; |
| else revocationRoute="NO_REVOCATION_ACTIVE"; |
| return {consentRoute,revocationRoute,consentStability,revocationStability}; |
| } |
| function classifyStability(baseRoute, changedCount, hardGate){ |
| if(hardGate) return "OVERRIDE_LOCKED"; |
| if(changedCount===0) return "STABLE_ROUTE"; |
| if(changedCount<=4) return "THRESHOLD_ADJACENT"; |
| return "FRAGILE_ROUTE"; |
| } |
| function stabilityClass(label){ |
| if(label==="STABLE_ROUTE") return "senseStable"; |
| if(label==="OVERRIDE_LOCKED") return "senseLocked"; |
| if(label==="FRAGILE_ROUTE") return "senseHot"; |
| return "senseFragile"; |
| } |
| |
| const sliderDisplayNames={ |
| explicitAuthorization:"Explicit Authorization", |
| informedUnderstanding:"Informed Understanding", |
| currentConsent:"Current / Not Expired", |
| identityAuthority:"Identity / Authority Match", |
| consentRecord:"Consent Record Available", |
| purposeClarity:"Purpose Clarity", |
| recipientClarity:"Recipient Clarity", |
| dataActionBoundary:"Data / Action Boundary", |
| durationClarity:"Duration Clarity", |
| revocationPathVisible:"Revocation Path Visible", |
| downstreamBranchCount:"Downstream Branch Count", |
| transferOpacity:"Transfer Opacity", |
| retentionFriction:"Retention Friction", |
| automationLag:"Automation Lag", |
| institutionalIncentive:"Institutional / Commercial Incentive", |
| revocationNotice:"Revocation Notice Clarity", |
| chainVisibility:"Downstream Chain Visibility", |
| enforcementAbility:"Enforcement Ability", |
| confirmationReturned:"Confirmation Returned", |
| residualCopyRisk:"Residual Copy / Cache Risk" |
| }; |
| function driverName(id){return sliderDisplayNames[id]||id;} |
| function rankDrivers(flips){ |
| const counts={}; |
| flips.forEach(f=>{counts[f.slider]=(counts[f.slider]||0)+1;}); |
| return Object.entries(counts).sort((a,b)=>b[1]-a[1]).map(([slider,count])=>({slider,count,label:driverName(slider)})); |
| } |
| function formatFlip(f){ |
| return `${driverName(f.slider)} ${f.direction}: ${f.from} → ${f.to}`; |
| } |
| function renderDriverList(id, flips){ |
| const list=el(id); |
| if(!flips.length){ |
| list.innerHTML='<li>No route flips under ±10% slider perturbation.</li>'; |
| return; |
| } |
| const ranked=rankDrivers(flips); |
| const topLabels=ranked.slice(0,3).map(d=>`${d.label} (${d.count})`).join("; "); |
| const trace=flips.slice(0,8).map(f=>`<li>${formatFlip(f)}</li>`).join(""); |
| list.innerHTML=`<li><b>Top drivers:</b> ${topLabels}</li>${trace}`; |
| } |
| function driverSummary(flips){ |
| if(!flips.length) return "None"; |
| const ranked=rankDrivers(flips); |
| return ranked.slice(0,5).map(d=>`${d.label} (${d.count})`).join("; "); |
| } |
| function flipTraceSummary(flips){ |
| if(!flips.length) return "None"; |
| return flips.slice(0,10).map(formatFlip).join(" | "); |
| } |
| |
| function runSensitivityAnalysis(){ |
| const baseState=getCurrentStateObject(); |
| const base=computeFromState(baseState); |
| let consentFlips=[], revocationFlips=[]; |
| sliders.forEach(id=>{ |
| [-0.10,0.10].forEach(delta=>{ |
| const p={...baseState}; |
| p[id]=clamp01(p[id]+delta); |
| const out=computeFromState(p); |
| const direction=delta>0?"+10%":"-10%"; |
| if(out.consentRoute!==base.consentRoute){ |
| consentFlips.push({slider:id,direction,from:base.consentRoute,to:out.consentRoute,consentStability:out.consentStability}); |
| } |
| if(out.revocationRoute!==base.revocationRoute){ |
| revocationFlips.push({slider:id,direction,from:base.revocationRoute,to:out.revocationRoute,revocationStability:out.revocationStability}); |
| } |
| }); |
| }); |
| const hardConsentGate=baseState.priorConsentHarm || baseState.fullRevocation || baseState.partialRevocation; |
| const hardRevocationGate=baseState.priorConsentHarm || baseState.fullRevocation || baseState.partialRevocation; |
| const consentStabilityLabel=classifyStability(base.consentRoute,consentFlips.length,hardConsentGate); |
| const revocationStabilityLabel=classifyStability(base.revocationRoute,revocationFlips.length,hardRevocationGate); |
| let overall="STABLE_ROUTE"; |
| if(consentStabilityLabel==="OVERRIDE_LOCKED" || revocationStabilityLabel==="OVERRIDE_LOCKED") overall="OVERRIDE_LOCKED"; |
| else if(consentStabilityLabel==="FRAGILE_ROUTE" || revocationStabilityLabel==="FRAGILE_ROUTE") overall="FRAGILE_ROUTE"; |
| else if(consentStabilityLabel==="THRESHOLD_ADJACENT" || revocationStabilityLabel==="THRESHOLD_ADJACENT") overall="THRESHOLD_ADJACENT"; |
| |
| lastSensitivity={ |
| baseConsentRoute:base.consentRoute, |
| baseRevocationRoute:base.revocationRoute, |
| consentStabilityLabel, |
| revocationStabilityLabel, |
| overall, |
| consentChanges:consentFlips.length, |
| revocationChanges:revocationFlips.length, |
| consentFlips, |
| revocationFlips, |
| topConsentDrivers:rankDrivers(consentFlips), |
| topRevocationDrivers:rankDrivers(revocationFlips) |
| }; |
| renderSensitivity(lastSensitivity); |
| return lastSensitivity; |
| } |
| function renderSensitivity(s){ |
| const overallText=s.overall.replaceAll("_"," "); |
| el("senseConsentRoute").textContent=s.consentStabilityLabel.replaceAll("_"," "); |
| el("senseRevocationRoute").textContent=s.revocationStabilityLabel.replaceAll("_"," "); |
| el("senseOverall").textContent=overallText; |
| el("senseConsentRoute").className="miniValue "+stabilityClass(s.consentStabilityLabel); |
| el("senseRevocationRoute").className="miniValue "+stabilityClass(s.revocationStabilityLabel); |
| el("senseOverall").className="miniValue "+stabilityClass(s.overall); |
| renderDriverList("consentDriverList",s.consentFlips||[]); |
| renderDriverList("revocationDriverList",s.revocationFlips||[]); |
| let detail=`Base routes: ${s.baseConsentRoute} / ${s.baseRevocationRoute}. `; |
| if(s.overall==="OVERRIDE_LOCKED") detail+="A hard boundary gate is active, so small slider perturbations do not override the main route. Flip trace still shows which pressures would matter if gates were cleared."; |
| else if(s.overall==="STABLE_ROUTE") detail+="No ±10% single-slider perturbation changed either route."; |
| else if(s.overall==="THRESHOLD_ADJACENT") detail+="A small number of ±10% perturbations changed a route; this case is near a decision threshold."; |
| else detail+="Multiple ±10% perturbations changed routes; this boundary state is fragile and should not be treated as cleanly resolved."; |
| detail+=` Consent route changes: ${s.consentChanges}. Revocation route changes: ${s.revocationChanges}.`; |
| if((s.consentFlips&&s.consentFlips.length)||(s.revocationFlips&&s.revocationFlips.length)){ |
| detail+=` Top drivers — Consent: ${driverSummary(s.consentFlips||[])}. Revocation: ${driverSummary(s.revocationFlips||[])}.`; |
| } |
| el("sensitivitySummary").textContent=detail; |
| } |
| |
| |
| document.addEventListener("DOMContentLoaded",()=>{ |
| Object.entries(presets).forEach(([k,p])=>{let o=document.createElement("option");o.value=k;o.textContent=p[0];el("preset").appendChild(o)}); |
| sliders.forEach(id=>el(id).addEventListener("input",update)); toggles.forEach(id=>el(id).addEventListener("change",update)); |
| el("preset").addEventListener("change",e=>applyPreset(e.target.value)); |
| el("exportReport").addEventListener("click",report); |
| el("copyReport").addEventListener("click",()=>{if(!el("report").value)report();navigator.clipboard?.writeText(el("report").value)}); |
| el("copyScenarioLink").addEventListener("click",()=>{const link=encodeStateToURL();navigator.clipboard?.writeText(link);const btn=el("copyScenarioLink");btn.textContent="Copied scenario link";setTimeout(()=>btn.textContent="Copy scenario link",1300);}); |
| el("runSensitivity").addEventListener("click",runSensitivityAnalysis); |
| document.querySelectorAll("[data-mode]").forEach(btn=>btn.addEventListener("click",()=>{document.querySelectorAll("[data-mode]").forEach(b=>b.classList.remove("primary"));btn.classList.add("primary");el("translation").textContent=translations[btn.dataset.mode]})); |
| renderTaxonomy(); |
| if(!loadStateFromURL()) applyPreset("patient_ehr_revocation"); |
| urlSyncReady=true; |
| update(); |
| }); |
| </script> |
| </body> |
| </html> |
|
|