refactor(core): self-contained core utilities and 100% domain isolation (#59)

This commit is contained in:
Tyler Gillispie 2026-08-27 20:43:24 -07:00 committed by GitHub
parent 973fba0607
commit 5bbe2bc764
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 640 additions and 48 deletions

187
src/core/audit.ts Normal file
View File

@ -0,0 +1,187 @@
import { encodeBase64 } from "jsr:@std/encoding@1/base64";
import { encodeHex } from "jsr:@std/encoding@1/hex";
import { sqlWrapper } from "./db.ts";
import { buildMerkleTree, leafHash } from "./audit_merkle.ts";
import { fetchSpiffeIdentity } from "./spire_ffi.ts";
import { valkey } from "./valkey.ts";
let batcherIntervalId: number | null = null;
let isFlushing = false;
/**
* SIDE EFFECT: Asynchronously logs an audit record to the database.
* Does not block the main execution thread. Errors are logged but swallowed
* to prevent failing the core request due to a logging issue.
*/
export let auditLog = function auditLog(
userId: string | null,
action: string,
resource: string | null,
details: Record<string, unknown> | null,
ipAddress: string,
): void {
// Fire and forget
(async () => {
try {
const entryObj = {
userId,
action,
resource,
details,
ipAddress,
timestamp: Date.now(),
};
const entryStr = JSON.stringify(entryObj);
const entryBytes = new TextEncoder().encode(entryStr);
const hashBuffer = await leafHash(entryBytes);
const leafHashHex = encodeHex(hashBuffer);
await sqlWrapper.sql`
INSERT INTO audit_records (user_id, action, resource, details, ip_address, leaf_hash)
VALUES (${userId}, ${action}, ${resource}, ${
details ? JSON.stringify(details) : null
}, ${ipAddress}, ${leafHashHex})
`;
} catch (error: any) {
const msg = error?.code || error?.message || String(error);
console.error(`[Audit Logger] Failed to insert audit record: ${msg}`);
}
})();
};
export async function flush(): Promise<void> {
if (isFlushing) return;
isFlushing = true;
try {
const sthResult = await sqlWrapper.sql`
SELECT tree_size FROM audit_sths ORDER BY tree_size DESC LIMIT 1
`;
const lastTreeSize = sthResult.length > 0
? parseInt(sthResult[0].tree_size, 10)
: 0;
const allRecordsResult = await sqlWrapper.sql`
SELECT leaf_hash FROM audit_records ORDER BY created_at ASC, id ASC
`;
const currentTreeSize = allRecordsResult.length;
if (currentTreeSize > lastTreeSize) {
const leafHashes = allRecordsResult.map((row: any) => {
const hex = row.leaf_hash;
const bytes = new Uint8Array(hex.length / 2);
for (let i = 0; i < hex.length; i += 2) {
bytes[i / 2] = parseInt(hex.substring(i, i + 2), 16);
}
return bytes;
});
const rootHashBytes = await buildMerkleTree(leafHashes);
const rootHashHex = encodeHex(rootHashBytes);
let svidData;
try {
svidData = await fetchSpiffeIdentity();
} catch (_e) {
svidData = { x509_svid_key: new Uint8Array() };
}
let signatureBase64 = "";
if (svidData.x509_svid_key && svidData.x509_svid_key.length > 0) {
try {
const keyBuffer = svidData.x509_svid_key.buffer as ArrayBuffer;
const privateKey = await crypto.subtle.importKey(
"pkcs8",
keyBuffer,
{ name: "ECDSA", namedCurve: "P-256" },
true,
["sign"],
).catch(() =>
crypto.subtle.importKey(
"pkcs8",
keyBuffer,
{ name: "Ed25519" },
true,
["sign"],
)
);
const payloadBytes = new TextEncoder().encode(
JSON.stringify({
tree_size: currentTreeSize,
root_hash: rootHashHex,
}),
);
let signAlgo: any = { name: "ECDSA", hash: "SHA-256" };
if (privateKey.algorithm.name === "Ed25519") {
signAlgo = { name: "Ed25519" };
}
const signatureBytes = await crypto.subtle.sign(
signAlgo,
privateKey,
payloadBytes,
);
signatureBase64 = encodeBase64(new Uint8Array(signatureBytes));
} catch (e) {
console.warn(
"[Audit Batcher] Failed to import key or sign, using empty signature.",
e,
);
}
}
await sqlWrapper.sql`
INSERT INTO audit_sths (tree_size, root_hash, signature)
VALUES (${currentTreeSize}, ${rootHashHex}, ${signatureBase64})
`;
const payload = {
tree_size: currentTreeSize,
root_hash: rootHashHex,
signature: signatureBase64,
created_at: new Date().toISOString(),
};
const payloadStr = JSON.stringify(payload);
try {
if (typeof valkey.set === "function") {
await valkey.set("auth:audit:latest_sth", payloadStr);
}
if (typeof valkey.publish === "function") {
await valkey.publish("auth:audit:sth", payloadStr);
}
} catch (e) {
console.warn("[Audit Batcher] Valkey broadcast failed", e);
}
}
} catch (error) {
console.error("[Audit Batcher] Error during flush:", error);
} finally {
isFlushing = false;
}
}
export function startMicroBatcher(): void {
if (batcherIntervalId === null) {
batcherIntervalId = setInterval(flush, 1000) as unknown as number;
}
}
export function stopMicroBatcher(): void {
if (batcherIntervalId !== null) {
clearInterval(batcherIntervalId);
batcherIntervalId = null;
}
}
export const auditWrapper = {
get auditLog() {
return auditLog;
},
set auditLog(val: any) {
auditLog = val;
},
};

72
src/core/audit_merkle.ts Normal file
View File

@ -0,0 +1,72 @@
export async function leafHash(entry_bytes: Uint8Array): Promise<Uint8Array> {
const data = new Uint8Array(1 + entry_bytes.length);
data[0] = 0x00;
data.set(entry_bytes, 1);
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
return new Uint8Array(hashBuffer);
}
export async function nodeHash(
left: Uint8Array,
right: Uint8Array,
): Promise<Uint8Array> {
const data = new Uint8Array(1 + left.length + right.length);
data[0] = 0x01;
data.set(left, 1);
data.set(right, 1 + left.length);
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
return new Uint8Array(hashBuffer);
}
export async function buildMerkleTree(
leaves: Uint8Array[],
): Promise<Uint8Array> {
if (leaves.length === 0) {
const hashBuffer = await crypto.subtle.digest("SHA-256", new Uint8Array(0));
return new Uint8Array(hashBuffer);
}
if (leaves.length === 1) {
return leaves[0];
}
const k = Math.pow(2, Math.floor(Math.log2(leaves.length - 1)));
const leftHash = await buildMerkleTree(leaves.slice(0, k));
const rightHash = await buildMerkleTree(leaves.slice(k));
return await nodeHash(leftHash, rightHash);
}
export async function verifyInclusionProof(
leaf: Uint8Array,
proof: Uint8Array[],
index: number,
treeSize: number,
expectedRoot: Uint8Array,
): Promise<boolean> {
let currentHash = leaf;
let currentIndex = index;
let right = treeSize - 1;
for (const siblingHash of proof) {
if (currentIndex % 2 === 1) {
currentHash = await nodeHash(siblingHash, currentHash);
} else {
if (currentIndex === right) {
currentHash = await nodeHash(siblingHash, currentHash);
} else {
currentHash = await nodeHash(currentHash, siblingHash);
}
}
currentIndex = Math.floor(currentIndex / 2);
right = Math.floor(right / 2);
}
const currentHashHex = Array.from(currentHash).map((b) =>
b.toString(16).padStart(2, "0")
).join("");
const expectedRootHex = Array.from(expectedRoot).map((b) =>
b.toString(16).padStart(2, "0")
).join("");
return currentHashHex === expectedRootHex;
}

84
src/core/middleware.ts Normal file
View File

@ -0,0 +1,84 @@
import type { Context } from "jsr:@hono/hono@4";
import { getAuthenticatedUser } from "./session.ts";
import { valkey } from "./valkey.ts";
/**
* Extracts client IP from headers, checking X-Real-IP and X-Forwarded-For.
*/
export function getClientIp(c: Context): string {
const realIp = c.req.header("x-real-ip");
if (realIp) {
return realIp.trim();
}
let forwardedFor = c.req.header("x-forwarded-for");
if (forwardedFor) {
if (forwardedFor.length > 256) {
forwardedFor = forwardedFor.substring(0, 256);
}
const parts = forwardedFor.split(",");
return parts[parts.length - 1].trim();
}
return "unknown-ip";
}
export async function checkRateLimit(
key: string,
limit: number,
windowMs: number,
): Promise<boolean> {
try {
const current = await valkey.incr(key);
if (current === 1) {
await valkey.pexpire(key, windowMs);
}
return current <= limit;
} catch (err) {
console.warn("[Rate Limiter] Valkey error:", err);
return true; // Fail open on cache error
}
}
export const rateLimitWrapper = {
checkRateLimit,
async isRateLimited(
key: string,
limit: number,
windowMs: number,
): Promise<boolean> {
const allowed = await checkRateLimit(key, limit, windowMs);
return !allowed;
},
};
export const publicRateLimiter = async (
c: Context,
next: () => Promise<void>,
) => {
const ip = getClientIp(c);
const key = `ratelimit:public:${ip}`;
const allowed = await checkRateLimit(key, 10, 60000);
if (!allowed) {
return c.json({ error: "Too Many Requests" }, 429);
}
await next();
};
export const adminRateLimiter = async (
c: Context,
next: () => Promise<void>,
) => {
const auth = await getAuthenticatedUser(c);
if (!auth) {
return c.json({ error: "Missing or invalid session" }, 401);
}
const key = `ratelimit:admin:${auth.userId}`;
const allowed = await checkRateLimit(key, 60, 60000);
if (!allowed) {
return c.json({ error: "Too Many Requests" }, 429);
}
c.set("userId", auth.userId);
await next();
};

233
src/core/session.ts Normal file
View File

@ -0,0 +1,233 @@
import type { Context } from "jsr:@hono/hono@4";
import { deleteCookie } from "jsr:@hono/hono@4/cookie";
import { sqlWrapper } from "./db.ts";
import { valkey } from "./valkey.ts";
export interface AuthenticatedUser {
userId: string;
sessionId: string;
username: string;
label?: string;
isAgent?: boolean;
isPaused?: boolean;
customScopes?: string[];
}
export interface AppRecord {
id: string;
name: string;
domain?: string;
is_public?: boolean;
bypass_paths?: string[];
allowed_cidrs?: string[];
}
/**
* Extracts all session_id tokens from the Cookie and Authorization headers.
*/
export function extractAllSessionIds(c: Context): string[] {
const candidates: string[] = [];
const authHeader = c.req.header("authorization") || "";
if (authHeader.startsWith("Bearer ")) {
const bearerToken = authHeader.substring(7).trim();
if (bearerToken) candidates.push(bearerToken);
}
const cookieHeader = c.req.header("cookie") || "";
if (cookieHeader) {
const cookieMatches = [
...cookieHeader.matchAll(/(?:^|;\s*)session_id=([^;]+)/g),
]
.map((m) => decodeURIComponent(m[1].trim()))
.filter(Boolean);
candidates.push(...cookieMatches);
}
return candidates;
}
/**
* Resolves the authenticated user from Valkey cache or PostgreSQL sessions table.
*/
export async function getAuthenticatedUser(
c: Context,
): Promise<AuthenticatedUser | null> {
const sessionMatches = extractAllSessionIds(c);
if (sessionMatches.length === 0) return null;
for (let i = 0; i < sessionMatches.length; i++) {
const candidateId = sessionMatches[i];
// 1. Try Valkey cache
try {
const sessionDataStr = await valkey.get(candidateId);
if (sessionDataStr) {
const sessionData = JSON.parse(sessionDataStr);
if (sessionData && sessionData.uuid) {
if (i > 0) {
deleteCookie(c, "session_id", { path: "/" });
}
return {
userId: sessionData.uuid,
sessionId: candidateId,
username: sessionData.username || "",
label: sessionData.label,
isAgent: sessionData.isAgent,
isPaused: sessionData.is_paused,
customScopes: sessionData.customScopes,
};
}
}
} catch (_err) {
// Valkey cache miss or connection hiccup - fallback to DB
}
// 2. Fallback to PostgreSQL sessions table
try {
const nowIso = new Date().toISOString();
const session = await sqlWrapper.sql`
SELECT s.user_id, s.expires_at, s.label, s.is_agent, s.is_paused, s.custom_scopes, u.username
FROM sessions s
JOIN users u ON s.user_id = u.id
WHERE s.id = ${candidateId} AND s.expires_at > ${nowIso}
`.then((res: any) => res[0]);
if (session) {
const username = session.username || "";
try {
const ttlSeconds = Math.max(
1,
Math.floor(
(new Date(session.expires_at).getTime() - Date.now()) / 1000,
),
);
await valkey.setex(
candidateId,
ttlSeconds,
JSON.stringify({
uuid: session.user_id,
username,
label: session.label,
isAgent: session.is_agent,
is_paused: session.is_paused,
customScopes: session.custom_scopes,
}),
);
} catch (_e) {}
if (i > 0) {
deleteCookie(c, "session_id", { path: "/" });
}
return {
userId: session.user_id,
sessionId: candidateId,
username,
label: session.label,
isAgent: session.is_agent,
isPaused: session.is_paused,
customScopes: session.custom_scopes,
};
}
} catch (_err) {
// Continue to next candidate
}
}
return null;
}
/**
* Checks if a user has a global admin role.
*/
export async function isGlobalAdmin(userId: string): Promise<boolean> {
const result = await sqlWrapper.sql`
SELECT r.name FROM user_roles ur
JOIN roles r ON ur.role_id = r.id
WHERE ur.user_id = ${userId} AND r.name = 'admin'
`;
return result.length > 0;
}
/**
* Evaluates if the current user's capabilities satisfy the required scope.
*/
export function hasScope(
auth: AuthenticatedUser,
requiredScope: string,
): boolean {
if (!auth.isAgent) return true;
if (!Array.isArray(auth.customScopes)) return false;
return auth.customScopes.includes("*") ||
auth.customScopes.includes(requiredScope);
}
/**
* Helper to check if the session itself is authorized as an admin.
*/
export async function isSessionAdmin(
auth: AuthenticatedUser,
): Promise<boolean> {
const globalAdmin = await isGlobalAdmin(auth.userId);
if (!globalAdmin) return false;
if (!auth.isAgent) return true;
return Array.isArray(auth.customScopes) && auth.customScopes.includes("*");
}
/**
* Computes root cookie domain from RP_ID or host.
*/
export function getCookieDomain(customRpId?: string): string {
const rpId = customRpId || Deno.env.get("RP_ID");
if (rpId) {
if (rpId.includes("localhost") || rpId.includes("127.0.0.1")) {
return rpId;
}
return `.${rpId}`;
}
return "";
}
/**
* Hono Middleware: Blocks access if the session is a delegated agent session.
*/
export async function requirePrimarySession(
c: Context,
next: () => Promise<void>,
) {
const auth = await getAuthenticatedUser(c);
if (!auth) return c.json({ error: "Unauthorized" }, 401);
if (auth.isAgent) {
return c.json({ error: "Forbidden: Primary session required" }, 403);
}
await next();
}
/**
* Hono Middleware Factory: Requires a specific scope.
*/
export function requireScope(scope: string) {
return async (c: Context, next: () => Promise<void>) => {
const auth = await getAuthenticatedUser(c);
if (!auth) return c.json({ error: "Unauthorized" }, 401);
if (!hasScope(auth, scope)) {
return c.json({ error: "Forbidden: Insufficient scopes" }, 403);
}
await next();
};
}
/**
* Hono Middleware: Blocks access unless the session is an admin session.
*/
export async function requireAdmin(c: Context, next: () => Promise<void>) {
const auth = await getAuthenticatedUser(c);
if (!auth) return c.json({ error: "Unauthorized" }, 401);
const isAdmin = await isSessionAdmin(auth);
if (!isAdmin) {
return c.json({ error: "Forbidden: Global admin access required" }, 403);
}
await next();
}

View File

@ -1,13 +1,10 @@
import { Hono } from "jsr:@hono/hono@4";
import { encodeBase64Url } from "jsr:@std/encoding@1/base64url";
import { adminRateLimiter, getClientIp } from "../../../server/middleware.ts";
import {
getAuthenticatedUser,
requireAdmin,
} from "../../../server/auth-session.ts";
import { adminRateLimiter, getClientIp } from "../../core/middleware.ts";
import { getAuthenticatedUser, requireAdmin } from "../../core/session.ts";
import { valkey } from "../../core/valkey.ts";
import { auditWrapper } from "../../../server/audit.ts";
import { auditWrapper } from "../../core/audit.ts";
import {
assignUserGrant,

View File

@ -8,9 +8,9 @@ import {
import type { AuthenticationResponseJSON } from "jsr:@simplewebauthn/server@13";
import { valkey } from "../../core/valkey.ts";
import { getClientIp, publicRateLimiter } from "../../../server/middleware.ts";
import { extractAllSessionIds } from "../../../server/auth-session.ts";
import { auditWrapper } from "../../../server/audit.ts";
import { getClientIp, publicRateLimiter } from "../../core/middleware.ts";
import { extractAllSessionIds } from "../../core/session.ts";
import { auditWrapper } from "../../core/audit.ts";
import {
createSession,

View File

@ -7,8 +7,8 @@ import {
} from "jsr:@simplewebauthn/server@13";
import type { RegistrationResponseJSON } from "jsr:@simplewebauthn/server@13";
import { getClientIp, publicRateLimiter } from "../../../server/middleware.ts";
import { auditWrapper } from "../../../server/audit.ts";
import { getClientIp, publicRateLimiter } from "../../core/middleware.ts";
import { auditWrapper } from "../../core/audit.ts";
import {
bindPasskey,

View File

@ -8,8 +8,8 @@ import {
import type { RegistrationResponseJSON } from "jsr:@simplewebauthn/server@13";
import { valkey } from "../../core/valkey.ts";
import { getClientIp, publicRateLimiter } from "../../../server/middleware.ts";
import { auditWrapper } from "../../../server/audit.ts";
import { getClientIp, publicRateLimiter } from "../../core/middleware.ts";
import { auditWrapper } from "../../core/audit.ts";
import {
createPasskey,

View File

@ -4,13 +4,12 @@ import {
getCookieDomain,
hasScope,
isGlobalAdmin,
} from "../../../server/auth-session.ts";
} from "../../core/session.ts";
import { deleteCookie, setCookie } from "jsr:@hono/hono@4/cookie";
import { encodeHex } from "jsr:@std/encoding@1/hex";
import { getClientIp } from "../../../server/middleware.ts";
import { getClientIp, rateLimitWrapper } from "../../core/middleware.ts";
import { valkey } from "../../core/valkey.ts";
import { auditWrapper } from "../../../server/audit.ts";
import { rateLimitWrapper } from "../../../server/ratelimit.ts";
import { auditWrapper } from "../../core/audit.ts";
import { streamDatastar } from "../../core/sse_adapter.ts";
import { renderErrorToastFragment } from "../../core/error_fragments.tsx";
import * as Queries from "./queries.ts";

View File

@ -1,12 +1,9 @@
import { Hono } from "jsr:@hono/hono@4";
import { valkey } from "../../core/valkey.ts";
import { auditWrapper } from "../../../server/audit.ts";
import {
getAuthenticatedUser,
getClientIp,
hasScope,
} from "../../../server/auth-session.ts";
import { auditWrapper } from "../../core/audit.ts";
import { getClientIp } from "../../core/middleware.ts";
import { getAuthenticatedUser, hasScope } from "../../core/session.ts";
import {
delegateSession,

View File

@ -5,10 +5,7 @@ import { renderErrorToastFragment } from "../../core/error_fragments.tsx";
import {
AuthenticatedLayoutFragment,
} from "../../shared/ui/layout_fragments.tsx";
import {
getAuthenticatedUser,
hasScope,
} from "../../../server/auth-session.ts";
import { getAuthenticatedUser, hasScope } from "../../core/session.ts";
import { getAllApps } from "../admin/queries.ts";
import { getActiveSessions } from "./queries.ts";

View File

@ -2,36 +2,62 @@
## 1. Test Suite & Verification
- **`deno fmt`**: Passed (All newly created fragments, scripts, queries, routes, and styles formatted).
- **`deno task lint`**: Passed (`deno lint` and `scripts/lint_arch.ts` passed with 0 errors; all files strictly $\le 400$ lines with no banned DOM API regressions).
- **`deno task check`**: Passed across all workspace modules (`server/`, `sdk/`, `ui/`, `infra/`, `src/`).
- **`deno test -A --no-check`**: Passed (80 tests across 30 steps with 0 failures).
- **`deno fmt`**: Passed (All newly created fragments, scripts, queries, routes,
and styles formatted).
- **`deno task lint`**: Passed (`deno lint` and `scripts/lint_arch.ts` passed
with 0 errors; all files strictly $\le 400$ lines with no banned DOM API
regressions).
- **`deno task check`**: Passed across all workspace modules (`server/`, `sdk/`,
`ui/`, `infra/`, `src/`).
- **`deno test -A --no-check`**: Passed (80 tests across 30 steps with 0
failures).
## 2. Scope Implemented & Verified
1. **Events Vertical Slice (`src/features/events/`):**
- `cockpit_fragments.tsx`: `EventCockpitDeckFragment` supporting responsive Grid and Compact view modes.
- `cockpit_styles.ts`: Isolated CSS styling tokens to enforce SRP and keep component files under the 400-line limit.
- `drawer_fragments.tsx`: `WorkshopPassDrawerFragment` for minting event passes with 2-state handoff UI.
- `attendees_fragments.tsx`: `GuestDrawerAttendeesFragment` desktop slide-over and mobile bottom sheet for live attendee telemetry and session controls.
- `join_fragments.tsx`: `EventJoinPageFragment` for universal PIN and slug redemption.
- `queries.ts`: Pure PostgreSQL SQL queries for event retrieval, creation, expansion, PIN rotation, and live seat counts.
- `routes.tsx`: Complete Datastar SSE endpoint (`/api/events/:id/stream`) and interactive event actions (`/api/events/:id/rotate-pin`, `/api/events/:id/expand`, `/api/events/:id/end`, `/api/join`).
- `cockpit_fragments.tsx`: `EventCockpitDeckFragment` supporting responsive
Grid and Compact view modes.
- `cockpit_styles.ts`: Isolated CSS styling tokens to enforce SRP and keep
component files under the 400-line limit.
- `drawer_fragments.tsx`: `WorkshopPassDrawerFragment` for minting event
passes with 2-state handoff UI.
- `attendees_fragments.tsx`: `GuestDrawerAttendeesFragment` desktop
slide-over and mobile bottom sheet for live attendee telemetry and session
controls.
- `join_fragments.tsx`: `EventJoinPageFragment` for universal PIN and slug
redemption.
- `queries.ts`: Pure PostgreSQL SQL queries for event retrieval, creation,
expansion, PIN rotation, and live seat counts.
- `routes.tsx`: Complete Datastar SSE endpoint (`/api/events/:id/stream`) and
interactive event actions (`/api/events/:id/rotate-pin`,
`/api/events/:id/expand`, `/api/events/:id/end`, `/api/join`).
- `events.test.ts`: Verified public join route and error handling fragments.
2. **Sessions Vertical Slice (`src/features/sessions/`):**
- `table_fragments.tsx`: `SessionTableFragment` desktop table with Datastar reactive action triggers.
- `deck_fragments.tsx`: `SessionDeckFragment` responsive touch card deck for mobile viewports.
- `drawer_fragments.tsx`: `DirectPassDrawerFragment` for minting scoped child agent passes.
- `modal_fragments.tsx`: `ScopeModalFragment` for interactive permissions editing.
- `queries.ts`: SQL queries for active sessions, delegation, scope updating, pause/resume, and expiration extension.
- `actions_routes.ts`: Sub-router handling session delegation, scope modification, pausing, extension, and revocation.
- `routes.tsx`: UI route (`/dashboard/sessions`) and live telemetry SSE stream (`/api/sessions/stream`) with Valkey Pub/Sub revocation broadcasting.
- `sessions.test.tsx`: Verified route protection, table, deck, drawer, and modal rendering.
- `table_fragments.tsx`: `SessionTableFragment` desktop table with Datastar
reactive action triggers.
- `deck_fragments.tsx`: `SessionDeckFragment` responsive touch card deck for
mobile viewports.
- `drawer_fragments.tsx`: `DirectPassDrawerFragment` for minting scoped child
agent passes.
- `modal_fragments.tsx`: `ScopeModalFragment` for interactive permissions
editing.
- `queries.ts`: SQL queries for active sessions, delegation, scope updating,
pause/resume, and expiration extension.
- `actions_routes.ts`: Sub-router handling session delegation, scope
modification, pausing, extension, and revocation.
- `routes.tsx`: UI route (`/dashboard/sessions`) and live telemetry SSE
stream (`/api/sessions/stream`) with Valkey Pub/Sub revocation
broadcasting.
- `sessions.test.tsx`: Verified route protection, table, deck, drawer, and
modal rendering.
3. **Client Assets & Compatibility:**
- `public/sessions-scripts.js`: Pure vanilla JavaScript module handling drawer animations, copy clipboard actions, and scope modification modals with `globalThis` scoping.
- `public/sessions-scripts.js`: Pure vanilla JavaScript module handling
drawer animations, copy clipboard actions, and scope modification modals
with `globalThis` scoping.
4. **App Wiring & Dual Remote Distribution:**
- Mounted `eventsRoutes` and `sessionRoutes` in `src/main.ts`.
- Committed on `feat/phase-3-realtime-slices-and-scripts-removal` and synchronized across GitHub (`origin`) and Gitea (`gitea`).
- Committed on `feat/phase-3-realtime-slices-and-scripts-removal` and
synchronized across GitHub (`origin`) and Gitea (`gitea`).
- Created GitHub PR #58.
## 3. Decision