From d495cd585a78358f10a780941747b4fed704c6bc Mon Sep 17 00:00:00 2001 From: JB Date: Sat, 25 Jul 2026 13:28:26 -0400 Subject: [PATCH] feat(isac): grant agents baseline data contribution access --- docs/DESIGN_SYSTEM.md | 10 ++++++++++ docs/architecture.md | 8 ++++++++ src/app.css | 2 ++ src/lib/server/auth/authorization.ts | 4 +++- src/routes/admin/audit/+page.server.ts | 1 + src/routes/admin/audit/+page.svelte | 3 ++- src/routes/admin/roles/+page.svelte | 1 + src/routes/admin/users/[id]/+page.svelte | 1 + 8 files changed, 28 insertions(+), 2 deletions(-) diff --git a/docs/DESIGN_SYSTEM.md b/docs/DESIGN_SYSTEM.md index 589e2f1..c14c58e 100644 --- a/docs/DESIGN_SYSTEM.md +++ b/docs/DESIGN_SYSTEM.md @@ -531,3 +531,13 @@ data-navigation items. Proposal lifecycle states use text as well as color: pending is orange/actionable, approved is green/configured, and rejected or failed is red/archived. Review decisions always use the standard ISAC confirmation surface and must never apply a proposal from a table-row click alone. + +Audit History may use a five-column KPI filter rail when all durable operational +domains need first-class visibility. It retains the standard compact KPI height +and collapses through the shared dashboard breakpoints; do not increase card +height or create a second row solely to accommodate the fifth domain. + +Inherent permissions that cannot be removed through role or user assignment use +the compact blue information rail above the affected access controls. Always +name the baseline and enumerate its capabilities so operators do not mistake an +empty role assignment for an account with no effective access. diff --git a/docs/architecture.md b/docs/architecture.md index 5b66650..6cca7fb 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -128,6 +128,14 @@ Data Approver. Data Editor retains direct-write access for trusted operators; Data Approver combines direct-write access with queue review. New ISAC data modules must use the shared moderation interception path and audit both the final mutation and proposal lifecycle. + +Every authenticated, active registered agent receives `isac.access`, +`data.view`, and `data.propose` as baseline permissions in the authorization +layer. This baseline is intentionally implicit rather than a removable role +assignment, so it applies consistently to existing users and every account +creation method. Registered agents can inspect live source data and submit +moderated proposals, but cannot directly mutate canonical records, review the +queue, or access unrelated ISAC operations without additional permissions. 7. A successfully reverted event cannot be reverted again. Unsupported destructive identity operations, account deletion, incomplete diff --git a/src/app.css b/src/app.css index 8f0609f..47fcba0 100644 --- a/src/app.css +++ b/src/app.css @@ -117,6 +117,8 @@ .kpi-filters{height:auto}.kpi-filters>a{position:relative;min-height:70px;padding:11px 13px;background:#111415;border:1px solid #2d3233;border-top:2px solid #3a4041;display:grid;color:inherit;cursor:pointer}.kpi-filters>a:hover,.kpi-filters>a:focus-visible{border-color:#8a672b;border-top-color:var(--shd-orange);background:#171a1b}.kpi-filters>a.active{border-color:#8a672b;border-top-color:var(--shd-orange);background:#211a0e;box-shadow:inset 0 -2px #f6a821}.kpi-filters>a:active{transform:translateY(1px);filter:brightness(.88)}.kpi-filters>a span{font-size:7px;letter-spacing:1.3px;color:#737a7c}.kpi-filters>a strong{font-size:23px;line-height:1.1;margin:3px 0}.kpi-filters>a small{font-size:7px;color:#555d5f}.kpi-filters>a.active span,.kpi-filters>a.active strong{color:var(--shd-orange)} .role-kpis{grid-template-columns:repeat(5,minmax(105px,1fr))}.role-identity{grid-template-columns:minmax(220px,.7fr) minmax(320px,1.3fr)!important}.permission-catalogue{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:6px;margin-top:8px}.permission-catalogue label{min-height:54px;display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:9px;padding:8px 10px;border:1px solid #303536;background:#0d1011;cursor:pointer}.permission-catalogue label:hover,.permission-catalogue label:focus-within{border-color:#8a672b;background:#151819}.permission-catalogue label:active{transform:translateY(1px);filter:brightness(.88)}.permission-catalogue input{width:auto;margin:0;accent-color:var(--shd-orange)}.permission-catalogue span{display:grid;gap:4px}.permission-catalogue b{color:#d7d9d5;font-size:9px}.permission-catalogue small{color:#626a6c;font-size:7px;line-height:1.35}.permission-catalogue em{color:#596164;font-size:6px;font-style:normal;font-weight:900;letter-spacing:.7px}.permission-catalogue label:has(input:checked){border-color:#6e5226;background:#211a0e}.permission-catalogue label:has(input:checked) em{color:var(--shd-orange)}.role-row{min-width:960px;grid-template-columns:1.1fr .55fr 2fr .65fr .3fr}.role-permission-summary{display:flex!important;flex-wrap:wrap;gap:3px}.role-permission-summary b{padding:3px 5px;border:1px solid #3a4840;color:#75c08c;font-size:6px;letter-spacing:.5px}@media(max-width:1000px){.role-kpis{grid-template-columns:repeat(3,minmax(105px,1fr))}}@media(max-width:700px){.permission-catalogue,.role-identity{grid-template-columns:1fr!important}.role-kpis{grid-template-columns:1fr 1fr}} .audit-toolbar>div:last-child{display:block}.audit-filter{height:auto;display:grid;grid-template-columns:minmax(220px,1fr) 210px 240px auto;gap:7px;margin:10px 0}.audit-filter input,.audit-filter select,.audit-filter button{height:40px;margin:0}.audit-filter button{display:inline-flex;align-items:center;justify-content:center;padding:0 14px;line-height:1}.audit-row{min-width:980px;grid-template-columns:1fr 1fr 1.3fr .85fr .35fr}.audit-entry{border-top:1px solid #272c2d}.audit-entry:first-of-type{border-top:0}.audit-entry summary{border:0;cursor:pointer;list-style:none}.audit-entry summary::-webkit-details-marker{display:none}.audit-entry summary:hover{background:#15191a}.audit-entry summary:active{transform:translateY(1px);filter:brightness(.88)}.audit-entry summary>b{justify-self:end;color:var(--shd-orange);font-size:7px;letter-spacing:.7px}.audit-entry[open] summary{background:#171a1b;box-shadow:inset 2px 0 var(--shd-orange)}.audit-comparison{display:grid;grid-template-columns:1fr 1fr;gap:1px;padding:1px;background:#303536}.audit-comparison section{min-width:0;padding:12px;background:#0b0e0f}.audit-comparison span{color:var(--shd-orange);font-size:7px;font-weight:900;letter-spacing:1px}.audit-comparison pre{max-height:320px;overflow:auto;margin:9px 0 0;color:#aeb4b5;font:10px/1.5 Consolas,monospace;white-space:pre-wrap;word-break:break-word}@media(max-width:900px){.audit-filter{grid-template-columns:1fr 1fr}.audit-comparison{grid-template-columns:1fr}}@media(max-width:560px){.audit-filter{grid-template-columns:1fr}} +.audit-kpis{grid-template-columns:repeat(5,minmax(120px,1fr))} +.access-baseline-note{display:flex;align-items:center;gap:10px;margin:9px 0;padding:8px 10px;border-left:2px solid var(--shd-info);background:#0e1518;color:#94c3d5}.access-baseline-note b{flex:0 0 auto;font-size:7px;letter-spacing:1px}.access-baseline-note span{color:#6f929f;font-size:7px}.access-baseline-note.compact{margin:0 0 8px} .audit-revert-bar{min-height:56px;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:8px 12px;border-top:1px solid #4c302d;background:#1c1111}.audit-revert-bar>span{color:#a87870;font-size:7px;letter-spacing:.8px}.audit-revert-button{min-width:174px;height:40px;padding:0 14px}.audit-revert-button>b{font-size:15px;line-height:1}.audit-revert-button:hover,.audit-revert-button:focus-visible{box-shadow:inset 3px 0 var(--shd-danger)}.audit-revert-bar.completed{border-color:#36563f;background:#101713}.audit-revert-bar.completed>span{color:var(--shd-success)}.audit-revert-modal>p{color:#969d9f;font:13px/1.6 Georgia,serif}.audit-revert-modal code{color:#d7d9d5;font:11px Consolas,monospace}.audit-revert-summary{display:grid;grid-template-columns:1fr 1fr 1.4fr;gap:7px;margin:14px 0}.audit-revert-summary>div{min-width:0;display:grid;gap:5px;padding:9px;border:1px solid #303536;background:#0d1011}.audit-revert-summary span{color:#626a6c;font-size:6px;letter-spacing:.8px}.audit-revert-summary b{overflow:hidden;text-overflow:ellipsis;color:#d7d9d5;font-size:8px;white-space:nowrap}@media(max-width:600px){.audit-revert-bar{align-items:stretch;flex-direction:column}.audit-revert-button{width:100%}.audit-revert-summary{grid-template-columns:1fr}} .metrics-auto{height:40px;display:flex!important;align-items:center;gap:7px;padding:0 10px;border:1px solid var(--shd-border);background:#0d1011;color:#747c7e;font-size:7px;font-weight:900;letter-spacing:.8px;cursor:pointer}.metrics-auto input{width:auto;margin:0;accent-color:var(--shd-orange)}.metrics-auto:has(input:checked){border-color:#6e5226;background:#211a0e;color:var(--shd-orange)}.metrics-refresh{min-width:154px}.metrics-kpis strong{font-size:18px}.metrics-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px}.metrics-panel{min-width:0;border:1px solid var(--shd-border);background:var(--shd-surface)}.metrics-panel>header{min-height:64px;display:flex;align-items:center;justify-content:space-between;gap:14px;padding:11px 13px;border-bottom:1px solid var(--shd-border);background:#0d1011}.metrics-panel h3{margin:4px 0 0;font-size:14px;letter-spacing:.5px}.metrics-panel>header>b{color:var(--shd-orange);font-size:7px;letter-spacing:1px}.metric-lines{display:grid}.metric-lines>div{min-height:40px;display:flex;align-items:center;justify-content:space-between;gap:16px;padding:8px 12px;border-top:1px solid #252a2b}.metric-lines>div:first-child{border-top:0}.metric-lines span{color:#697174;font-size:7px;letter-spacing:.9px}.metric-lines b{max-width:65%;overflow:hidden;color:#d5d8d5;font-size:9px;text-align:right;text-overflow:ellipsis;white-space:nowrap}.metrics-foot{margin:10px 0 0;padding:9px;border:1px solid #292e2f;color:#596164;font-size:6px;letter-spacing:.8px}@media(max-width:850px){.metrics-grid{grid-template-columns:1fr}}@media(max-width:560px){.metrics-auto,.metrics-refresh{width:100%}.metric-lines b{max-width:58%}} .quality-chip{display:inline-flex;width:max-content;align-items:center;padding:4px 6px;border:1px solid currentColor;background:#1115;color:var(--quality-standard);font-size:6px;letter-spacing:.7px;text-transform:uppercase}.quality-standard{color:var(--quality-standard)!important}.quality-specialized{color:var(--quality-specialized)!important}.quality-superior{color:var(--quality-superior)!important}.quality-highend{color:var(--quality-high-end)!important} diff --git a/src/lib/server/auth/authorization.ts b/src/lib/server/auth/authorization.ts index 4ace95f..cd9014b 100644 --- a/src/lib/server/auth/authorization.ts +++ b/src/lib/server/auth/authorization.ts @@ -2,6 +2,8 @@ import { getDatabase } from '$lib/server/database'; import type { AuthUser } from './session'; import { error } from '@sveltejs/kit'; +const REGISTERED_AGENT_PERMISSIONS = ['isac.access', 'data.view', 'data.propose'] as const; + export async function getPermissions(user: AuthUser | null): Promise { if (!user) return []; if (user.role === 'admin') return ['*']; @@ -16,7 +18,7 @@ export async function getPermissions(user: AuthUser | null): Promise { JOIN user_permissions ON user_permissions.permission_id=permissions.id WHERE user_permissions.user_id=? `)).all(user.id, user.id); - const permissions = rows.map((row) => String(row.name)); + const permissions = [...new Set([...REGISTERED_AGENT_PERMISSIONS, ...rows.map((row) => String(row.name))])]; return permissions.includes('system.admin') ? ['*'] : permissions; } diff --git a/src/routes/admin/audit/+page.server.ts b/src/routes/admin/audit/+page.server.ts index 4421fcd..4e4235b 100644 --- a/src/routes/admin/audit/+page.server.ts +++ b/src/routes/admin/audit/+page.server.ts @@ -70,6 +70,7 @@ export const load: PageServerLoad = async ({ cookies, url }) => { (await database.prepare('SELECT action,COUNT(*) AS count FROM audit_log GROUP BY action ORDER BY action')).all(), (await database.prepare(`SELECT COUNT(*) AS total, SUM(CASE WHEN entity_type='game_data' THEN 1 ELSE 0 END) AS game_data, + SUM(CASE WHEN entity_type='data_moderation' THEN 1 ELSE 0 END) AS data_moderation, SUM(CASE WHEN entity_type IN ('user','user_access','user_moderation','identity') THEN 1 ELSE 0 END) AS users, SUM(CASE WHEN entity_type IN ('role','role_management') THEN 1 ELSE 0 END) AS access FROM audit_log`)).get() diff --git a/src/routes/admin/audit/+page.svelte b/src/routes/admin/audit/+page.svelte index ff5069e..d35c33c 100644 --- a/src/routes/admin/audit/+page.svelte +++ b/src/routes/admin/audit/+page.svelte @@ -20,9 +20,10 @@ Audit history — ISAC

SYSTEM OVERSIGHT

AUDIT HISTORY

Immutable operator activity, target records, and before/after change evidence.

- diff --git a/src/routes/admin/roles/+page.svelte b/src/routes/admin/roles/+page.svelte index 9957a84..9fbec39 100644 --- a/src/routes/admin/roles/+page.svelte +++ b/src/routes/admin/roles/+page.svelte @@ -17,6 +17,7 @@ Roles & permissions — ISAC

ACCESS POLICY REGISTRY

ROLES & PERMISSIONS

Compose reusable access roles from discrete SHD.build capabilities.

+

REGISTERED AGENT BASELINEEvery active account inherently receives isac.access, data.view, and data.propose. Roles add elevated capabilities and cannot remove this baseline.

ASSIGNED ROLESPERMISSION BUNDLES
{roleIds.length} SELECTED
{#each data.roles as role}{/each}
+

REGISTERED AGENT BASELINEisac.access · data.view · data.propose are inherent and are not removable here.

DIRECT PERMISSIONSUSER-SPECIFIC CAPABILITIES
{permissionIds.length} SELECTED
{#each data.permissions as permission}{/each}
{#if form?.accessError}

{form.accessError}

{/if}{#if form?.accessSaved}

Roles and direct permissions updated.

{/if}