From 02589c3bb2c801994e6163a4f902fc106a3d566a Mon Sep 17 00:00:00 2001 From: Simon Hagger Date: Sat, 22 Aug 2026 15:17:27 +0100 Subject: [PATCH] feat(desktop-main): enforce provider trust tiers in API gateway Map provider classes to trust tiers (external-http to remote-low, bundled-http to local-medium, local-high reserved for future adapters) whose ceilings constrain param/header entry counts and value chars, retry attempts, timeout, and response bytes. Operations declaring limits above their tier ceiling are blocked fail-closed with API/POLICY_VIOLATION and a correlation-ID warning log via the new gateway logger dep; compliant operations run under effective tier-clamped limits even where they declare none. Unknown providers keep the existing OPERATION_NOT_ALLOWED fail-closed path, now resolved before request validation. Existing operations remain tier-compliant unchanged. Record ADR-0010 and mark BL-048 delivered with proof. --- apps/desktop-main/src/api-gateway.spec.ts | 121 +++++++++++- apps/desktop-main/src/api-gateway.ts | 111 ++++++++--- .../desktop-main/src/api-trust-policy.spec.ts | 172 +++++++++++++++++ apps/desktop-main/src/api-trust-policy.ts | 177 ++++++++++++++++++ apps/desktop-main/src/main.ts | 6 +- docs/05-governance/backlog.md | 104 +++++----- docs/05-governance/decision-log.md | 25 +-- 7 files changed, 627 insertions(+), 89 deletions(-) create mode 100644 apps/desktop-main/src/api-trust-policy.spec.ts create mode 100644 apps/desktop-main/src/api-trust-policy.ts diff --git a/apps/desktop-main/src/api-gateway.spec.ts b/apps/desktop-main/src/api-gateway.spec.ts index 61bc78e..83fb316 100644 --- a/apps/desktop-main/src/api-gateway.spec.ts +++ b/apps/desktop-main/src/api-gateway.spec.ts @@ -1,4 +1,4 @@ -import { afterEach, describe, expect, it } from 'vitest'; +import { afterEach, describe, expect, it, vi } from 'vitest'; import type { ApiInvokeRequest, ApiOperationId, @@ -696,7 +696,7 @@ describe('invokeApiOperation', () => { 'status.github': { method: 'POST', url: 'https://api.example.com/write', - retry: { maxAttempts: 3, baseDelayMs: 1 }, + retry: { maxAttempts: 2, baseDelayMs: 1 }, }, }; let attempts = 0; @@ -751,3 +751,120 @@ describe('getApiOperationDiagnostics', () => { } }); }); + +describe('trust tier policy enforcement', () => { + const okFetch: typeof fetch = async () => + new Response(JSON.stringify({ ok: true }), { + status: 200, + headers: { 'content-type': 'application/json' }, + }); + + it('blocks operations whose declared policy exceeds the provider trust tier', async () => { + const logEvents: Array<{ + level: string; + event: string; + details?: Record; + }> = []; + const result = await invokeApiOperation(baseRequest('status.github'), { + operations: { + 'status.github': { + providerId: 'external-http', + method: 'GET', + url: 'https://api.example.com/health', + auth: { type: 'none' }, + timeoutMs: 45_000, + }, + }, + fetchFn: okFetch, + log: (level, event, details) => logEvents.push({ level, event, details }), + }); + + const error = expectFailure(result); + expect(error.code).toBe('API/POLICY_VIOLATION'); + expect(error.retryable).toBe(false); + expect(error.correlationId).toBe('corr-test'); + if (!result.ok) { + const details = result.error.details as { + trustTier?: string; + violations?: Array<{ + dimension: string; + declared: number; + ceiling: number; + }>; + }; + expect(details.trustTier).toBe('remote-low'); + expect(details.violations).toEqual([ + { dimension: 'maxTimeoutMs', declared: 45_000, ceiling: 10_000 }, + ]); + } + expect(logEvents).toHaveLength(1); + expect(logEvents[0]?.event).toBe('api.trust_policy.violation'); + expect(logEvents[0]?.details?.correlationId).toBe('corr-test'); + }); + + it('retries GET operations up to the provider trust tier ceiling', async () => { + let fetchCalls = 0; + const alwaysServerError: typeof fetch = async () => { + fetchCalls += 1; + return new Response(JSON.stringify({ error: 'server boom' }), { + status: 500, + headers: { 'content-type': 'application/json' }, + }); + }; + + const result = await invokeApiOperation(baseRequest('status.github'), { + operations: { + 'status.github': { + providerId: 'bundled-http', + method: 'GET', + url: 'https://api.example.com/health', + auth: { type: 'none' }, + retry: { maxAttempts: 3, baseDelayMs: 1 }, + }, + }, + fetchFn: alwaysServerError, + }); + + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error.code).toBe('API/SERVER_ERROR'); + } + expect(fetchCalls).toBe(3); + }); + + it('grants bundled-http operations the local-medium ceilings', async () => { + const result = await invokeApiOperation(baseRequest('status.github'), { + operations: { + 'status.github': { + providerId: 'bundled-http', + method: 'GET', + url: 'https://api.example.com/health', + auth: { type: 'none' }, + timeoutMs: 14_000, + }, + }, + fetchFn: okFetch, + }); + + expect(result.ok).toBe(true); + }); + + it('blocks operations on providers without an assigned trust tier before any network call', async () => { + const fetchSpy = vi.fn(okFetch); + const result = await invokeApiOperation(baseRequest('status.github'), { + operations: { + 'status.github': { + providerId: 'docker-local' as 'bundled-http', + method: 'GET', + url: 'https://api.example.com/health', + auth: { type: 'none' }, + }, + }, + fetchFn: fetchSpy as unknown as typeof fetch, + }); + + const error = expectFailure(result); + expect(error.code).toBe('API/OPERATION_NOT_ALLOWED'); + expect(fetchSpy).not.toHaveBeenCalled(); + }); +}); diff --git a/apps/desktop-main/src/api-gateway.ts b/apps/desktop-main/src/api-gateway.ts index 47939d7..015739f 100644 --- a/apps/desktop-main/src/api-gateway.ts +++ b/apps/desktop-main/src/api-gateway.ts @@ -12,6 +12,12 @@ import { type ApiOperationRequestPolicy, type ApiOperationRegistry, } from './api-operation-registry'; +import { + API_TRUST_TIER_POLICIES, + clampApiOperationToTier, + evaluateApiTrustTier, + resolveApiTrustTier, +} from './api-trust-policy'; const API_DEFAULT_TIMEOUT_MS = 8_000; const API_DEFAULT_MAX_RESPONSE_BYTES = 1_000_000; @@ -34,6 +40,11 @@ export const refreshDefaultApiOperationsFromEnv = () => { type InvokeApiDeps = { fetchFn?: typeof fetch; operations?: ApiOperationRegistry; + log?: ( + level: 'debug' | 'info' | 'warn' | 'error', + event: string, + details?: Record, + ) => void; }; type GetApiOperationDiagnosticsDeps = { @@ -759,6 +770,7 @@ export const invokeApiOperation = async ( ): Promise> => { const operations = deps.operations ?? defaultApiOperations; const fetchFn = deps.fetchFn ?? fetch; + const log = deps.log; const correlationId = request.correlationId; const operation = operations[request.payload.operationId]; @@ -788,7 +800,70 @@ export const invokeApiOperation = async ( ); } - const requestPolicyResult = validateRequestPolicy(request, operation); + const providerId = operation.providerId ?? 'external-http'; + const provider = executionProviders[providerId]; + if (!provider) { + return asFailure( + 'API/OPERATION_NOT_ALLOWED', + 'Requested API operation is not allowed.', + { + operationId: request.payload.operationId, + providerId, + }, + false, + correlationId, + ); + } + + const trustTier = resolveApiTrustTier(providerId); + if (!trustTier) { + log?.('warn', 'api.trust_policy.violation', { + operationId: request.payload.operationId, + correlationId, + providerId, + reason: 'provider_has_no_trust_tier', + }); + return asFailure( + 'API/POLICY_VIOLATION', + 'API operation provider has no assigned trust tier.', + { + operationId: request.payload.operationId, + providerId, + }, + false, + correlationId, + ); + } + + const tierEvaluation = evaluateApiTrustTier(operation, trustTier); + if (!tierEvaluation.ok) { + log?.('warn', 'api.trust_policy.violation', { + operationId: request.payload.operationId, + correlationId, + providerId, + trustTier, + violations: tierEvaluation.violations, + }); + return asFailure( + 'API/POLICY_VIOLATION', + 'API operation configuration exceeds its provider trust tier.', + { + operationId: request.payload.operationId, + providerId, + trustTier, + violations: tierEvaluation.violations, + }, + false, + correlationId, + ); + } + + const effectiveOperation = clampApiOperationToTier(operation, trustTier); + + const requestPolicyResult = validateRequestPolicy( + request, + effectiveOperation, + ); if (!requestPolicyResult.ok) { return requestPolicyResult; } @@ -826,34 +901,26 @@ export const invokeApiOperation = async ( state.lastStartedAt = Date.now(); try { - const providerId = operation.providerId ?? 'external-http'; - const provider = executionProviders[providerId]; - if (!provider) { - return asFailure( - 'API/OPERATION_NOT_ALLOWED', - 'Requested API operation is not allowed.', - { - operationId: request.payload.operationId, - providerId, - }, - false, - correlationId, - ); - } - + const tierPolicy = API_TRUST_TIER_POLICIES[trustTier]; + const declaredRetryAttempts = + effectiveOperation.retry?.maxAttempts ?? API_DEFAULT_RETRY_ATTEMPTS; const retryAttempts = - operation.method === 'GET' - ? Math.max( - 1, - operation.retry?.maxAttempts ?? API_DEFAULT_RETRY_ATTEMPTS, + effectiveOperation.method === 'GET' + ? Math.min( + Math.max(1, declaredRetryAttempts), + tierPolicy.maxRetryAttempts, ) : 1; const retryBaseDelayMs = - operation.retry?.baseDelayMs ?? API_DEFAULT_RETRY_BASE_DELAY_MS; + effectiveOperation.retry?.baseDelayMs ?? API_DEFAULT_RETRY_BASE_DELAY_MS; for (let attempt = 1; attempt <= retryAttempts; attempt += 1) { - const result = await provider.invoke(request, operation, fetchFn); + const result = await provider.invoke( + request, + effectiveOperation, + fetchFn, + ); if (!result.ok) { const failure = result as Extract; const errorCode = failure.error.code; diff --git a/apps/desktop-main/src/api-trust-policy.spec.ts b/apps/desktop-main/src/api-trust-policy.spec.ts new file mode 100644 index 0000000..eeb82e5 --- /dev/null +++ b/apps/desktop-main/src/api-trust-policy.spec.ts @@ -0,0 +1,172 @@ +import { describe, expect, it } from 'vitest'; +import type { ApiOperationDefinition } from './api-operation-registry'; +import { + API_TRUST_TIER_POLICIES, + clampApiOperationToTier, + evaluateApiTrustTier, + resolveApiTrustTier, +} from './api-trust-policy'; + +describe('resolveApiTrustTier', () => { + it('maps known providers to their trust tiers', () => { + expect(resolveApiTrustTier('external-http')).toBe('remote-low'); + expect(resolveApiTrustTier('bundled-http')).toBe('local-medium'); + }); + + it('returns null for unknown providers (fail closed)', () => { + expect(resolveApiTrustTier('docker-local')).toBeNull(); + expect(resolveApiTrustTier('')).toBeNull(); + }); +}); + +describe('evaluateApiTrustTier', () => { + it('accepts operations whose declared limits fit within the tier', () => { + const operation: ApiOperationDefinition = { + method: 'GET', + url: 'https://example.test/x', + timeoutMs: 8_000, + maxResponseBytes: 256_000, + retry: { maxAttempts: 2 }, + requestPolicy: { + maxParamEntries: 8, + maxHeaderEntries: 4, + maxParamValueChars: 256, + maxHeaderValueChars: 128, + }, + }; + + const result = evaluateApiTrustTier(operation, 'remote-low'); + + expect(result).toEqual({ ok: true, tier: 'remote-low' }); + }); + + it('reports violations for every declared limit above the tier ceiling', () => { + const operation: ApiOperationDefinition = { + method: 'GET', + url: 'https://example.test/x', + timeoutMs: 20_000, + maxResponseBytes: 3_000_000, + retry: { maxAttempts: 5 }, + requestPolicy: { + maxParamEntries: 40, + maxHeaderEntries: 16, + maxParamValueChars: 512, + maxHeaderValueChars: 300, + }, + }; + + const result = evaluateApiTrustTier(operation, 'remote-low'); + + if (result.ok) { + throw new Error('expected violations'); + } + const dimensions = result.violations.map((v) => v.dimension); + expect(dimensions).toEqual([ + 'maxParamEntries', + 'maxHeaderEntries', + 'maxParamValueChars', + 'maxHeaderValueChars', + 'maxRetryAttempts', + 'maxTimeoutMs', + 'maxResponseBytes', + ]); + expect(result.tier).toBe('remote-low'); + }); + + it('accepts undeclared dimensions without inventing violations', () => { + const operation: ApiOperationDefinition = { + method: 'GET', + url: 'https://example.test/x', + }; + + expect(evaluateApiTrustTier(operation, 'local-medium')).toEqual({ + ok: true, + tier: 'local-medium', + }); + }); + + it('treats declared limits equal to the ceiling as compliant', () => { + const remoteCeilings = API_TRUST_TIER_POLICIES['remote-low']; + const operation: ApiOperationDefinition = { + method: 'GET', + url: 'https://example.test/x', + timeoutMs: remoteCeilings.maxTimeoutMs, + maxResponseBytes: remoteCeilings.maxResponseBytes, + retry: { maxAttempts: remoteCeilings.maxRetryAttempts }, + requestPolicy: { + maxParamEntries: remoteCeilings.maxParamEntries, + maxHeaderEntries: remoteCeilings.maxHeaderEntries, + maxParamValueChars: remoteCeilings.maxParamValueChars, + maxHeaderValueChars: remoteCeilings.maxHeaderValueChars, + }, + }; + + expect(evaluateApiTrustTier(operation, 'remote-low')).toEqual({ + ok: true, + tier: 'remote-low', + }); + }); +}); + +describe('clampApiOperationToTier', () => { + it('applies tier ceilings to every dimension including undeclared ones', () => { + const operation: ApiOperationDefinition = { + method: 'GET', + url: 'https://example.test/x', + retry: { maxAttempts: 9, baseDelayMs: 100 }, + }; + + const clamped = clampApiOperationToTier(operation, 'local-medium'); + + expect(clamped.timeoutMs).toBe( + API_TRUST_TIER_POLICIES['local-medium'].maxTimeoutMs, + ); + expect(clamped.maxResponseBytes).toBe( + API_TRUST_TIER_POLICIES['local-medium'].maxResponseBytes, + ); + expect(clamped.retry?.maxAttempts).toBe(3); + expect(clamped.retry?.baseDelayMs).toBe(100); + expect(clamped.requestPolicy).toEqual({ + maxParamEntries: 32, + maxHeaderEntries: 16, + maxParamValueChars: 512, + maxHeaderValueChars: 512, + }); + }); + + it('keeps declared limits that are stricter than the tier ceiling', () => { + const operation: ApiOperationDefinition = { + method: 'GET', + url: 'https://example.test/x', + timeoutMs: 1_500, + requestPolicy: { + maxParamEntries: 4, + maxHeaderEntries: 2, + maxParamValueChars: 64, + maxHeaderValueChars: 64, + }, + }; + + const clamped = clampApiOperationToTier(operation, 'remote-low'); + + expect(clamped.timeoutMs).toBe(1_500); + expect(clamped.requestPolicy).toEqual({ + maxParamEntries: 4, + maxHeaderEntries: 2, + maxParamValueChars: 64, + maxHeaderValueChars: 64, + }); + }); + + it('does not mutate the source operation', () => { + const operation: ApiOperationDefinition = { + method: 'GET', + url: 'https://example.test/x', + timeoutMs: 99_000, + }; + + clampApiOperationToTier(operation, 'remote-low'); + + expect(operation.timeoutMs).toBe(99_000); + }); +}); diff --git a/apps/desktop-main/src/api-trust-policy.ts b/apps/desktop-main/src/api-trust-policy.ts new file mode 100644 index 0000000..62e11c0 --- /dev/null +++ b/apps/desktop-main/src/api-trust-policy.ts @@ -0,0 +1,177 @@ +import type { + ApiOperationDefinition, + ApiOperationProviderId, + ApiOperationRequestPolicy, +} from './api-operation-registry'; + +export type ApiTrustTierId = 'local-high' | 'local-medium' | 'remote-low'; + +export type ApiTrustTierPolicy = { + maxParamEntries: number; + maxHeaderEntries: number; + maxParamValueChars: number; + maxHeaderValueChars: number; + maxRetryAttempts: number; + maxTimeoutMs: number; + maxResponseBytes: number; +}; + +export const API_TRUST_TIER_POLICIES: Record< + ApiTrustTierId, + ApiTrustTierPolicy +> = { + 'local-high': { + maxParamEntries: 64, + maxHeaderEntries: 32, + maxParamValueChars: 1_024, + maxHeaderValueChars: 1_024, + maxRetryAttempts: 4, + maxTimeoutMs: 30_000, + maxResponseBytes: 4_000_000, + }, + 'local-medium': { + maxParamEntries: 32, + maxHeaderEntries: 16, + maxParamValueChars: 512, + maxHeaderValueChars: 512, + maxRetryAttempts: 3, + maxTimeoutMs: 15_000, + maxResponseBytes: 2_000_000, + }, + 'remote-low': { + maxParamEntries: 16, + maxHeaderEntries: 8, + maxParamValueChars: 256, + maxHeaderValueChars: 256, + maxRetryAttempts: 2, + maxTimeoutMs: 10_000, + maxResponseBytes: 1_000_000, + }, +}; + +export const PROVIDER_API_TRUST_TIERS: Record< + ApiOperationProviderId, + ApiTrustTierId +> = { + 'external-http': 'remote-low', + 'bundled-http': 'local-medium', +}; + +export type ApiTrustPolicyDimension = keyof ApiTrustTierPolicy; + +export type ApiTrustPolicyViolation = { + dimension: ApiTrustPolicyDimension; + declared: number; + ceiling: number; +}; + +export type ApiTrustTierEvaluation = + | { ok: true; tier: ApiTrustTierId } + | { + ok: false; + tier: ApiTrustTierId; + violations: ApiTrustPolicyViolation[]; + }; + +export const resolveApiTrustTier = ( + providerId: string, +): ApiTrustTierId | null => + (PROVIDER_API_TRUST_TIERS as Record)[providerId] ?? + null; + +const firstDeclaredNumber = (value: number | undefined): number | undefined => + typeof value === 'number' ? value : undefined; + +export const evaluateApiTrustTier = ( + operation: ApiOperationDefinition, + tier: ApiTrustTierId, +): ApiTrustTierEvaluation => { + const policy = API_TRUST_TIER_POLICIES[tier]; + const violations: ApiTrustPolicyViolation[] = []; + + const declaredDimensions: Array< + [ApiTrustPolicyDimension, number | undefined] + > = [ + [ + 'maxParamEntries', + firstDeclaredNumber(operation.requestPolicy?.maxParamEntries), + ], + [ + 'maxHeaderEntries', + firstDeclaredNumber(operation.requestPolicy?.maxHeaderEntries), + ], + [ + 'maxParamValueChars', + firstDeclaredNumber(operation.requestPolicy?.maxParamValueChars), + ], + [ + 'maxHeaderValueChars', + firstDeclaredNumber(operation.requestPolicy?.maxHeaderValueChars), + ], + ['maxRetryAttempts', firstDeclaredNumber(operation.retry?.maxAttempts)], + ['maxTimeoutMs', firstDeclaredNumber(operation.timeoutMs)], + ['maxResponseBytes', firstDeclaredNumber(operation.maxResponseBytes)], + ]; + + for (const [dimension, declared] of declaredDimensions) { + if (declared === undefined) { + continue; + } + const ceiling = policy[dimension]; + if (declared > ceiling) { + violations.push({ dimension, declared, ceiling }); + } + } + + return violations.length > 0 + ? { ok: false, tier, violations } + : { ok: true, tier }; +}; + +const minDefined = (value: number | undefined, ceiling: number): number => + typeof value === 'number' ? Math.min(value, ceiling) : ceiling; + +export const clampApiOperationToTier = ( + operation: ApiOperationDefinition, + tier: ApiTrustTierId, +): ApiOperationDefinition => { + const policy = API_TRUST_TIER_POLICIES[tier]; + const declaredRequestPolicy: ApiOperationRequestPolicy = + operation.requestPolicy ?? {}; + + return { + ...operation, + timeoutMs: minDefined(operation.timeoutMs, policy.maxTimeoutMs), + maxResponseBytes: minDefined( + operation.maxResponseBytes, + policy.maxResponseBytes, + ), + retry: operation.retry + ? { + ...operation.retry, + maxAttempts: minDefined( + operation.retry.maxAttempts, + policy.maxRetryAttempts, + ), + } + : operation.retry, + requestPolicy: { + maxParamEntries: minDefined( + declaredRequestPolicy.maxParamEntries, + policy.maxParamEntries, + ), + maxHeaderEntries: minDefined( + declaredRequestPolicy.maxHeaderEntries, + policy.maxHeaderEntries, + ), + maxParamValueChars: minDefined( + declaredRequestPolicy.maxParamValueChars, + policy.maxParamValueChars, + ), + maxHeaderValueChars: minDefined( + declaredRequestPolicy.maxHeaderValueChars, + policy.maxHeaderValueChars, + ), + }, + }; +}; diff --git a/apps/desktop-main/src/main.ts b/apps/desktop-main/src/main.ts index 4c6904e..2565d1c 100644 --- a/apps/desktop-main/src/main.ts +++ b/apps/desktop-main/src/main.ts @@ -471,7 +471,11 @@ const bootstrap = async () => { assertAuthorizedSender, getOidcService: () => oidcService, getStorageGateway, - invokeApiOperation: (request) => invokeApiOperation(request), + invokeApiOperation: (request) => + invokeApiOperation(request, { + log: (level, event, details) => + logEvent(level, event, undefined, details), + }), getApiOperationDiagnostics: (operationId) => getApiOperationDiagnostics(operationId), getDemoUpdater: () => demoUpdater, diff --git a/docs/05-governance/backlog.md b/docs/05-governance/backlog.md index 2ab82a3..84c07e9 100644 --- a/docs/05-governance/backlog.md +++ b/docs/05-governance/backlog.md @@ -4,58 +4,58 @@ Owner: Platform Engineering Review cadence: Weekly Last reviewed: 2026-02-15 -| ID | Title | Status | Priority | Area | Source | Owner | Notes | -| ------ | ---------------------------------------------------------------------- | -------- | -------- | ------------------------------- | ----------------------------------------- | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| BL-001 | Linux packaging strategy for desktop | Deferred | Low | Delivery + Packaging | FEEDBACK.md | Platform | Add Electron Forge Linux makers and release notes once Linux is in scope. | -| BL-002 | Enforce handshake contract-version mismatch path | Planned | Medium | IPC Contracts | FEEDBACK.md | Platform | Main process currently validates schema but does not return a dedicated mismatch error. | -| BL-003 | API operation compile-time typing hardening | Done | Medium | Platform API | Transient FR document (API, archived) | Platform | Implemented via operation type maps and typed invoke signatures across contracts/preload/desktop API (baseline delivered and extended by `BL-025`). | -| BL-004 | Enterprise proxy/TLS support matrix documentation | Deferred | Low | Security + Networking | Transient FR document (API, archived) | Platform | Document expected behaviors for proxy auth, TLS interception, and certificate errors. | -| BL-005 | Offline API queue/replay capability | Proposed | Medium | Platform API | Transient FR document (API, archived) | Platform | Current behavior is fail-fast + retry classification; queue/replay not implemented. | -| BL-006 | Storage capability-scoped authorization model | Proposed | Medium | Storage + Security | Transient FR document (Storage, archived) | Platform | Add explicit capability/role rules per storage operation. | -| BL-007 | Expanded data-classification tiers | Deferred | Low | Storage + Governance | Transient FR document (Storage, archived) | Platform | Add `public`, `secret`, and `high-value secret` tiers beyond current baseline. | -| BL-008 | Local Vault phase-2 implementation | Proposed | Medium | Storage + Security | Transient FR document (Storage, archived) | Platform | Separate higher-assurance vault capability remains unimplemented. | -| BL-009 | Storage recovery UX flows | Proposed | Medium | UX + Reliability | Transient FR document (Storage, archived) | Frontend | Build user-facing reset/repair/recovery workflow for storage failures. | -| BL-010 | Renderer structured logging adoption | Proposed | Medium | Observability | Task inbox (local, untracked) | Frontend | Apply shared structured logs in renderer with IPC correlation IDs for key user flows. | -| BL-011 | Failure UX pattern implementation | Planned | Medium | UX + Reliability | Task inbox (local, untracked) | Frontend | Implement documented toast/dialog/inline/offline patterns consistently across features. | -| BL-012 | IPC real-handler contract harness expansion | Done | Medium | Testing + IPC Contracts | Task inbox (local, untracked) | Platform | Delivered via real-handler unauthorized sender integration coverage and preload invoke timeout/correlation tests; superseded by scoped execution under `BL-023`. | -| BL-013 | OIDC auth platform (desktop PKCE + secure IPC) | Planned | High | Security + Identity | Task inbox (local, untracked) | Platform | Phased backlog and acceptance tests tracked in `docs/05-governance/oidc-auth-backlog.md`. | -| BL-014 | Remove temporary JWT-authorizer client-id audience compatibility | Planned | High | Security + Identity | OIDC integration | Platform + Security | Clerk OAuth access token currently omits `aud`; AWS authorizer temporarily allows both `YOUR_API_AUDIENCE` and OAuth client id. Remove client-id audience once Clerk emits API audience/scopes as required. | -| BL-015 | Add IdP global sign-out and token revocation flow | Done | Medium | Security + Identity | OIDC integration | Platform + Security | Delivered with explicit `local` vs `global` sign-out mode, revocation/end-session capability reporting, and renderer-safe lifecycle messaging. | -| BL-016 | Refactor desktop-main composition root and IPC modularization | Done | High | Desktop Runtime + IPC | Fresh workspace review (2026-02-13) | Platform | Completed in Sprint 1; desktop-main composition root split and handler registration modularized. | -| BL-017 | Refactor preload bridge into domain modules with shared invoke client | Done | High | Preload + IPC | Fresh workspace review (2026-02-13) | Platform | Completed in Sprint 1; preload segmented into domain APIs with shared invoke/correlation/timeout client. | -| BL-018 | Introduce reusable validated IPC handler factory in desktop-main | Done | High | IPC Contracts + Reliability | Fresh workspace review (2026-02-13) | Platform | Completed in Sprint 1; validated handler factory centralizes sender auth, schema validation, and envelope handling. | -| BL-019 | Decompose OIDC service into smaller capability-focused modules | Proposed | Medium | Security + Identity | Fresh workspace review (2026-02-13) | Platform + Security | Split sign-in flow, discovery/provider client, token lifecycle, and diagnostics concerns currently concentrated in `oidc-service.ts`. | -| BL-020 | Complete renderer i18n migration for hardcoded user-facing strings | Proposed | Medium | Frontend + I18n | Fresh workspace review (2026-02-13) | Frontend | Replace hardcoded labels/messages in renderer feature pages with translation keys and locale entries. | -| BL-021 | Consolidate renderer route/nav metadata into a single typed registry | Done | Medium | Frontend Architecture | Fresh workspace review (2026-02-13) | Frontend | Delivered by introducing a typed renderer route registry that generates both router entries and shell nav links from one source while preserving production file-replacement exclusions. | -| BL-022 | Rationalize thin shell/core/repository libraries | Proposed | Low | Architecture + Maintainability | Fresh workspace review (2026-02-13) | Platform | Either consolidate low-value wrappers or expand with meaningful domain behavior to reduce packaging overhead and clarify boundaries. | -| BL-023 | Expand IPC integration harness for preload-main real handler paths | Done | Medium | Testing + IPC Contracts | Fresh workspace review (2026-02-13) | Platform | Delivered with real-handler unauthorized sender tests and preload invoke malformed/timeout/failure correlation assertions. | -| BL-024 | Standardize structured renderer logging with shared helper adoption | Proposed | Medium | Observability | Fresh workspace review (2026-02-13) | Frontend | Apply structured logging in renderer flows with correlation IDs and redaction-safe details. | -| BL-025 | Strengthen compile-time typing for API operation contracts end-to-end | Done | Medium | Platform API + Contracts | Fresh workspace review (2026-02-13) | Platform | Delivered by introducing operation-to-request/response type maps and consuming them in preload/desktop API invoke surfaces. | -| BL-026 | Exclude lab routes/features from production bundle surface | Done | High | Frontend + Security Posture | Sprint implementation (2026-02-13) | Frontend + Platform | Production route/shell config replacement now removes lab routes/nav/toggle from production artifacts to reduce discoverability/attack surface. | -| BL-027 | Provide deterministic bundled update demo patch cycle | Done | Medium | Delivery + Update Architecture | Sprint implementation (2026-02-13) | Platform | Added local bundled feed demo (`1.0.0-demo` -> `1.0.1-demo`) with hash validation and renderer diagnostics to prove end-to-end update model independent of installer updater infra. | -| BL-028 | Enforce robust file signature validation for privileged file ingress | Done | High | Security + File Handling | Python sidecar architecture spike | Platform + Security | Delivered parity for renderer-initiated privileged ingress paths (`fs` text read, `python` PDF inspect, settings JSON imports) with fail-closed extension/signature checks and uniform rejection telemetry (`security.file_ingress_rejected`). Proof: `pnpm nx run desktop-main:test`, manual smoke log verification. | -| BL-029 | Standardize official Python runtime distribution for sidecar bundling | Done | High | Delivery + Runtime Determinism | Python sidecar packaging hardening | Platform + Security | Completed with pinned official artifact catalog + SHA256 verification, deterministic runtime bundle sync/assert flow, and reproducible staging package proof (`python-runtime:prepare-local`, `python-runtime:assert`, `build-desktop-main`, `forge:make:staging`). | -| BL-030 | Deterministic packaged Python sidecar runtime baseline | Done | High | Delivery + Runtime Determinism | Python sidecar packaging hardening | Platform + Security | Delivered deterministic runtime manifest/assert/sync flow, pinned runtime dependency install (`requirements-runtime.txt`), packaged-build bundled-runtime enforcement (no system fallback), and runtime executable diagnostics proving packaged interpreter path. | -| BL-031 | Refactor desktop-main composition root into focused runtime modules | Proposed | High | Desktop Runtime + Architecture | Fresh workspace review (2026-02-14) | Platform | Split `main.ts` orchestration from lifecycle/app-window/auth-token/python-runtime concerns into focused modules with retained behavior. Acceptance: no behavior regressions in auth/session/update/python flows. Proof: `pnpm nx run desktop-main:test`, `pnpm nx run desktop-main:build`. | -| BL-032 | Standardize IPC handler failure envelope and correlation guarantees | Done | High | IPC Contracts + Reliability | Fresh workspace review (2026-02-14) | Platform | Delivered normalized `IPC/HANDLER_FAILED` behavior in validated handler path with correlation-preserving preload/main integration assertions (real-handler throw path and preload envelope preservation tests). Proof: `pnpm nx run desktop-main:test`, `pnpm nx run desktop-preload:test`. | -| BL-033 | Centralize privileged file ingress policy across all IPC file routes | Done | High | Security + File Handling | Fresh workspace review (2026-02-14) | Platform + Security | Delivered shared ingress policy usage across file, python, and settings import IPC handlers with consistent reject semantics and structured security telemetry. Proof: `pnpm nx run desktop-main:test`, manual smoke evidence for `security.file_ingress_rejected` across channels. | -| BL-034 | Route-driven i18n asset loading manifest for renderer features | Proposed | Medium | Frontend + I18n | Fresh workspace review (2026-02-14) | Frontend | Replace manual translation path list with route/feature manifest-based loading to remove per-feature loader edits. Acceptance: adding a feature locale requires no loader code change. Proof: `pnpm i18n-check`, `pnpm nx run renderer:test`, `pnpm nx run renderer:build`. | -| BL-035 | Replace route/nav literal labels with typed i18n keys | Proposed | Medium | Frontend + I18n | Fresh workspace review (2026-02-14) | Frontend | Update route registry metadata to use translation keys instead of literals and enforce key typing for nav labels/titles. Acceptance: no hardcoded nav labels in route registry. Proof: `pnpm i18n-check`, `pnpm nx run renderer:build`. | -| BL-036 | Promote Python runtime sync to first-class Nx target dependency | Proposed | Medium | Build System + Determinism | Fresh workspace review (2026-02-14) | Platform | Model runtime sync/prepare as explicit Nx targets with `dependsOn` instead of script chaining for cache/task graph correctness. Acceptance: graph reflects runtime-prep dependency for package/build targets. Proof: `pnpm nx graph` (visual verification), `pnpm nx run desktop-main:build`, `pnpm forge:make:staging`. | -| BL-037 | Consolidate duplicated CI setup into reusable workflow primitives | Proposed | Medium | Delivery + CI Maintainability | Fresh workspace review (2026-02-14) | Platform | Extract repeated node/pnpm/install/setup patterns into reusable workflow/composite action while preserving job isolation. Acceptance: parity with current checks and no coverage loss. Proof: PR CI green on all existing jobs after refactor. | -| BL-038 | Evaluate sidecar transport hardening path (loopback HTTP vs stdio) | Proposed | Low | Security + Runtime Architecture | Fresh workspace review (2026-02-14) | Platform + Security | Produce ADR comparing current loopback model with stdio/pipe RPC model, migration cost, and risk reduction; no implementation required in first pass. Acceptance: decision record approved with explicit threat tradeoffs. Proof: decision recorded in `docs/05-governance/decision-log.md`. | -| BL-039 | Replace placeholder CODEOWNERS entries with real maintainers | Done | High | Governance + Repo Security | Independent refactor review (2026-02-15) | Platform | Completed by replacing placeholder ownership with concrete maintainer mapping in `.github/CODEOWNERS` (`@simonhagger`) and validating no placeholder handles remain. | -| BL-040 | Add contributing guide for contributor workflow and guardrails | Proposed | Medium | Developer Experience | Independent refactor review (2026-02-15) | Platform | Add a repository-level contributing guide with setup, Nx task workflow, commit/PR rules, validation matrix, and security checklist expectations. Acceptance: contributor can complete setup and produce a compliant local PR without external guidance. Proof: `pnpm docs-lint`, peer dry-run against checklist. | -| BL-041 | Publish Windows setup guide for repeatable desktop development | Proposed | Medium | Developer Experience + Delivery | Independent refactor review (2026-02-15) | Platform | Add a dedicated engineering Windows setup guide consolidating toolchain requirements and known Windows failure modes (`EBUSY`, keytar/native build, path/line-ending pitfalls). Acceptance: clean workstation setup path documented end-to-end. Proof: `pnpm docs-lint`, validated by fresh-machine walkthrough notes. | -| BL-042 | Define and enforce TypeScript coverage thresholds in CI | Proposed | Medium | Testing + Quality Gates | Independent refactor review (2026-02-15) | Platform | Establish repo-level coverage thresholds for unit/integration suites and enforce in CI without destabilizing existing pipelines. Acceptance: CI fails on threshold regressions and publishes coverage artifacts. Proof: updated test target output plus CI gate pass/fail demonstration in PR evidence. | -| BL-043 | Establish TSDoc/TypeDoc standard for shared contracts and platform API | Proposed | Medium | Documentation + Contracts | Independent refactor review (2026-02-15) | Platform | Define doc standard for public exports, generate API docs for targeted shared surfaces (`libs/shared/contracts`, `libs/platform/desktop-api`) and document upkeep policy. Acceptance: documented standard adopted and generated artifacts reproducible. Proof: docs generation command + `pnpm docs-lint`. | -| BL-044 | Expand release runbook with operator-grade procedural detail | Proposed | Low | Delivery + Operations | Independent refactor review (2026-02-15) | Platform | Enhance release docs with deterministic validation/rollback steps and expected outputs for staging/production package verification. Acceptance: release can be executed by a non-author using only runbook docs. Proof: runbook dry-run record + `pnpm docs-lint`. | -| BL-045 | Centralize typed error-code registry across contracts and handlers | Proposed | Medium | Contracts + Reliability | Independent refactor review (2026-02-15) | Platform | Create shared typed error-code registry and migrate hardcoded handler codes incrementally without breaking wire compatibility. Acceptance: new/modified handlers import from registry and tests assert stable codes. Proof: `pnpm nx run shared-contracts:test`, `pnpm nx run desktop-main:test`. | -| BL-046 | Introduce provider-agnostic external execution gateway abstraction | Done | High | Architecture + Security | Architecture discussion (2026-02-15) | Platform + Security | Completed baseline abstraction with provider-routed gateway execution (`external-http`, `bundled-http`) while preserving renderer contract shape; includes parity test proving stable response envelope across provider selection. | -| BL-047 | Add typed operation registry for gateway invocation allowlists | Done | High | Contracts + Security | Architecture discussion (2026-02-15) | Platform + Security | Completed with centralized typed operation registry and fail-closed gateway enforcement for unknown providers plus operation-level request policy limits (entry/value bounds) returning typed `API/INVALID_PARAMS` / `API/INVALID_HEADERS` envelopes. | -| BL-048 | Implement trust-tier policy engine for local and remote providers | Proposed | High | Security + Policy | Architecture discussion (2026-02-15) | Platform + Security | Add trust regimes (for example `local-high`, `local-medium`, `remote-low`) that constrain allowable operations, headers, payload sizes, and retry behavior by provider class. Acceptance: policy violations are blocked and logged with correlation IDs. Proof: `pnpm nx run desktop-main:test`, targeted integration policy tests. | -| BL-049 | Add external provider adapter baseline (Docker-local first) | Proposed | Medium | Runtime Extensibility | Architecture discussion (2026-02-15) | Platform | Implement first external adapter using localhost bridge contract (Docker-hosted service), preserving existing main-process security controls and typed failure envelopes. Acceptance: bundled and external providers pass shared operation compatibility tests. Proof: `pnpm nx run desktop-main:test`, `pnpm runtime:smoke`. | -| BL-050 | Add sensitive-operation capability gating and confirmation controls | Proposed | Medium | Security + UX | Architecture discussion (2026-02-15) | Platform + Frontend | Introduce capability/confirmation gates for high-risk operations (for example local file/system-affecting calls), including deny-by-default policy metadata in operation registry. Acceptance: gated operations require explicit enablement and reject otherwise. Proof: `pnpm nx run desktop-main:test`, `pnpm nx run renderer:test`. | +| ID | Title | Status | Priority | Area | Source | Owner | Notes | +| ------ | ---------------------------------------------------------------------- | -------- | -------- | ------------------------------- | ----------------------------------------- | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| BL-001 | Linux packaging strategy for desktop | Deferred | Low | Delivery + Packaging | FEEDBACK.md | Platform | Add Electron Forge Linux makers and release notes once Linux is in scope. | +| BL-002 | Enforce handshake contract-version mismatch path | Planned | Medium | IPC Contracts | FEEDBACK.md | Platform | Main process currently validates schema but does not return a dedicated mismatch error. | +| BL-003 | API operation compile-time typing hardening | Done | Medium | Platform API | Transient FR document (API, archived) | Platform | Implemented via operation type maps and typed invoke signatures across contracts/preload/desktop API (baseline delivered and extended by `BL-025`). | +| BL-004 | Enterprise proxy/TLS support matrix documentation | Deferred | Low | Security + Networking | Transient FR document (API, archived) | Platform | Document expected behaviors for proxy auth, TLS interception, and certificate errors. | +| BL-005 | Offline API queue/replay capability | Proposed | Medium | Platform API | Transient FR document (API, archived) | Platform | Current behavior is fail-fast + retry classification; queue/replay not implemented. | +| BL-006 | Storage capability-scoped authorization model | Proposed | Medium | Storage + Security | Transient FR document (Storage, archived) | Platform | Add explicit capability/role rules per storage operation. | +| BL-007 | Expanded data-classification tiers | Deferred | Low | Storage + Governance | Transient FR document (Storage, archived) | Platform | Add `public`, `secret`, and `high-value secret` tiers beyond current baseline. | +| BL-008 | Local Vault phase-2 implementation | Proposed | Medium | Storage + Security | Transient FR document (Storage, archived) | Platform | Separate higher-assurance vault capability remains unimplemented. | +| BL-009 | Storage recovery UX flows | Proposed | Medium | UX + Reliability | Transient FR document (Storage, archived) | Frontend | Build user-facing reset/repair/recovery workflow for storage failures. | +| BL-010 | Renderer structured logging adoption | Proposed | Medium | Observability | Task inbox (local, untracked) | Frontend | Apply shared structured logs in renderer with IPC correlation IDs for key user flows. | +| BL-011 | Failure UX pattern implementation | Planned | Medium | UX + Reliability | Task inbox (local, untracked) | Frontend | Implement documented toast/dialog/inline/offline patterns consistently across features. | +| BL-012 | IPC real-handler contract harness expansion | Done | Medium | Testing + IPC Contracts | Task inbox (local, untracked) | Platform | Delivered via real-handler unauthorized sender integration coverage and preload invoke timeout/correlation tests; superseded by scoped execution under `BL-023`. | +| BL-013 | OIDC auth platform (desktop PKCE + secure IPC) | Planned | High | Security + Identity | Task inbox (local, untracked) | Platform | Phased backlog and acceptance tests tracked in `docs/05-governance/oidc-auth-backlog.md`. | +| BL-014 | Remove temporary JWT-authorizer client-id audience compatibility | Planned | High | Security + Identity | OIDC integration | Platform + Security | Clerk OAuth access token currently omits `aud`; AWS authorizer temporarily allows both `YOUR_API_AUDIENCE` and OAuth client id. Remove client-id audience once Clerk emits API audience/scopes as required. | +| BL-015 | Add IdP global sign-out and token revocation flow | Done | Medium | Security + Identity | OIDC integration | Platform + Security | Delivered with explicit `local` vs `global` sign-out mode, revocation/end-session capability reporting, and renderer-safe lifecycle messaging. | +| BL-016 | Refactor desktop-main composition root and IPC modularization | Done | High | Desktop Runtime + IPC | Fresh workspace review (2026-02-13) | Platform | Completed in Sprint 1; desktop-main composition root split and handler registration modularized. | +| BL-017 | Refactor preload bridge into domain modules with shared invoke client | Done | High | Preload + IPC | Fresh workspace review (2026-02-13) | Platform | Completed in Sprint 1; preload segmented into domain APIs with shared invoke/correlation/timeout client. | +| BL-018 | Introduce reusable validated IPC handler factory in desktop-main | Done | High | IPC Contracts + Reliability | Fresh workspace review (2026-02-13) | Platform | Completed in Sprint 1; validated handler factory centralizes sender auth, schema validation, and envelope handling. | +| BL-019 | Decompose OIDC service into smaller capability-focused modules | Proposed | Medium | Security + Identity | Fresh workspace review (2026-02-13) | Platform + Security | Split sign-in flow, discovery/provider client, token lifecycle, and diagnostics concerns currently concentrated in `oidc-service.ts`. | +| BL-020 | Complete renderer i18n migration for hardcoded user-facing strings | Proposed | Medium | Frontend + I18n | Fresh workspace review (2026-02-13) | Frontend | Replace hardcoded labels/messages in renderer feature pages with translation keys and locale entries. | +| BL-021 | Consolidate renderer route/nav metadata into a single typed registry | Done | Medium | Frontend Architecture | Fresh workspace review (2026-02-13) | Frontend | Delivered by introducing a typed renderer route registry that generates both router entries and shell nav links from one source while preserving production file-replacement exclusions. | +| BL-022 | Rationalize thin shell/core/repository libraries | Proposed | Low | Architecture + Maintainability | Fresh workspace review (2026-02-13) | Platform | Either consolidate low-value wrappers or expand with meaningful domain behavior to reduce packaging overhead and clarify boundaries. | +| BL-023 | Expand IPC integration harness for preload-main real handler paths | Done | Medium | Testing + IPC Contracts | Fresh workspace review (2026-02-13) | Platform | Delivered with real-handler unauthorized sender tests and preload invoke malformed/timeout/failure correlation assertions. | +| BL-024 | Standardize structured renderer logging with shared helper adoption | Proposed | Medium | Observability | Fresh workspace review (2026-02-13) | Frontend | Apply structured logging in renderer flows with correlation IDs and redaction-safe details. | +| BL-025 | Strengthen compile-time typing for API operation contracts end-to-end | Done | Medium | Platform API + Contracts | Fresh workspace review (2026-02-13) | Platform | Delivered by introducing operation-to-request/response type maps and consuming them in preload/desktop API invoke surfaces. | +| BL-026 | Exclude lab routes/features from production bundle surface | Done | High | Frontend + Security Posture | Sprint implementation (2026-02-13) | Frontend + Platform | Production route/shell config replacement now removes lab routes/nav/toggle from production artifacts to reduce discoverability/attack surface. | +| BL-027 | Provide deterministic bundled update demo patch cycle | Done | Medium | Delivery + Update Architecture | Sprint implementation (2026-02-13) | Platform | Added local bundled feed demo (`1.0.0-demo` -> `1.0.1-demo`) with hash validation and renderer diagnostics to prove end-to-end update model independent of installer updater infra. | +| BL-028 | Enforce robust file signature validation for privileged file ingress | Done | High | Security + File Handling | Python sidecar architecture spike | Platform + Security | Delivered parity for renderer-initiated privileged ingress paths (`fs` text read, `python` PDF inspect, settings JSON imports) with fail-closed extension/signature checks and uniform rejection telemetry (`security.file_ingress_rejected`). Proof: `pnpm nx run desktop-main:test`, manual smoke log verification. | +| BL-029 | Standardize official Python runtime distribution for sidecar bundling | Done | High | Delivery + Runtime Determinism | Python sidecar packaging hardening | Platform + Security | Completed with pinned official artifact catalog + SHA256 verification, deterministic runtime bundle sync/assert flow, and reproducible staging package proof (`python-runtime:prepare-local`, `python-runtime:assert`, `build-desktop-main`, `forge:make:staging`). | +| BL-030 | Deterministic packaged Python sidecar runtime baseline | Done | High | Delivery + Runtime Determinism | Python sidecar packaging hardening | Platform + Security | Delivered deterministic runtime manifest/assert/sync flow, pinned runtime dependency install (`requirements-runtime.txt`), packaged-build bundled-runtime enforcement (no system fallback), and runtime executable diagnostics proving packaged interpreter path. | +| BL-031 | Refactor desktop-main composition root into focused runtime modules | Proposed | High | Desktop Runtime + Architecture | Fresh workspace review (2026-02-14) | Platform | Split `main.ts` orchestration from lifecycle/app-window/auth-token/python-runtime concerns into focused modules with retained behavior. Acceptance: no behavior regressions in auth/session/update/python flows. Proof: `pnpm nx run desktop-main:test`, `pnpm nx run desktop-main:build`. | +| BL-032 | Standardize IPC handler failure envelope and correlation guarantees | Done | High | IPC Contracts + Reliability | Fresh workspace review (2026-02-14) | Platform | Delivered normalized `IPC/HANDLER_FAILED` behavior in validated handler path with correlation-preserving preload/main integration assertions (real-handler throw path and preload envelope preservation tests). Proof: `pnpm nx run desktop-main:test`, `pnpm nx run desktop-preload:test`. | +| BL-033 | Centralize privileged file ingress policy across all IPC file routes | Done | High | Security + File Handling | Fresh workspace review (2026-02-14) | Platform + Security | Delivered shared ingress policy usage across file, python, and settings import IPC handlers with consistent reject semantics and structured security telemetry. Proof: `pnpm nx run desktop-main:test`, manual smoke evidence for `security.file_ingress_rejected` across channels. | +| BL-034 | Route-driven i18n asset loading manifest for renderer features | Proposed | Medium | Frontend + I18n | Fresh workspace review (2026-02-14) | Frontend | Replace manual translation path list with route/feature manifest-based loading to remove per-feature loader edits. Acceptance: adding a feature locale requires no loader code change. Proof: `pnpm i18n-check`, `pnpm nx run renderer:test`, `pnpm nx run renderer:build`. | +| BL-035 | Replace route/nav literal labels with typed i18n keys | Proposed | Medium | Frontend + I18n | Fresh workspace review (2026-02-14) | Frontend | Update route registry metadata to use translation keys instead of literals and enforce key typing for nav labels/titles. Acceptance: no hardcoded nav labels in route registry. Proof: `pnpm i18n-check`, `pnpm nx run renderer:build`. | +| BL-036 | Promote Python runtime sync to first-class Nx target dependency | Proposed | Medium | Build System + Determinism | Fresh workspace review (2026-02-14) | Platform | Model runtime sync/prepare as explicit Nx targets with `dependsOn` instead of script chaining for cache/task graph correctness. Acceptance: graph reflects runtime-prep dependency for package/build targets. Proof: `pnpm nx graph` (visual verification), `pnpm nx run desktop-main:build`, `pnpm forge:make:staging`. | +| BL-037 | Consolidate duplicated CI setup into reusable workflow primitives | Proposed | Medium | Delivery + CI Maintainability | Fresh workspace review (2026-02-14) | Platform | Extract repeated node/pnpm/install/setup patterns into reusable workflow/composite action while preserving job isolation. Acceptance: parity with current checks and no coverage loss. Proof: PR CI green on all existing jobs after refactor. | +| BL-038 | Evaluate sidecar transport hardening path (loopback HTTP vs stdio) | Proposed | Low | Security + Runtime Architecture | Fresh workspace review (2026-02-14) | Platform + Security | Produce ADR comparing current loopback model with stdio/pipe RPC model, migration cost, and risk reduction; no implementation required in first pass. Acceptance: decision record approved with explicit threat tradeoffs. Proof: decision recorded in `docs/05-governance/decision-log.md`. | +| BL-039 | Replace placeholder CODEOWNERS entries with real maintainers | Done | High | Governance + Repo Security | Independent refactor review (2026-02-15) | Platform | Completed by replacing placeholder ownership with concrete maintainer mapping in `.github/CODEOWNERS` (`@simonhagger`) and validating no placeholder handles remain. | +| BL-040 | Add contributing guide for contributor workflow and guardrails | Proposed | Medium | Developer Experience | Independent refactor review (2026-02-15) | Platform | Add a repository-level contributing guide with setup, Nx task workflow, commit/PR rules, validation matrix, and security checklist expectations. Acceptance: contributor can complete setup and produce a compliant local PR without external guidance. Proof: `pnpm docs-lint`, peer dry-run against checklist. | +| BL-041 | Publish Windows setup guide for repeatable desktop development | Proposed | Medium | Developer Experience + Delivery | Independent refactor review (2026-02-15) | Platform | Add a dedicated engineering Windows setup guide consolidating toolchain requirements and known Windows failure modes (`EBUSY`, keytar/native build, path/line-ending pitfalls). Acceptance: clean workstation setup path documented end-to-end. Proof: `pnpm docs-lint`, validated by fresh-machine walkthrough notes. | +| BL-042 | Define and enforce TypeScript coverage thresholds in CI | Proposed | Medium | Testing + Quality Gates | Independent refactor review (2026-02-15) | Platform | Establish repo-level coverage thresholds for unit/integration suites and enforce in CI without destabilizing existing pipelines. Acceptance: CI fails on threshold regressions and publishes coverage artifacts. Proof: updated test target output plus CI gate pass/fail demonstration in PR evidence. | +| BL-043 | Establish TSDoc/TypeDoc standard for shared contracts and platform API | Proposed | Medium | Documentation + Contracts | Independent refactor review (2026-02-15) | Platform | Define doc standard for public exports, generate API docs for targeted shared surfaces (`libs/shared/contracts`, `libs/platform/desktop-api`) and document upkeep policy. Acceptance: documented standard adopted and generated artifacts reproducible. Proof: docs generation command + `pnpm docs-lint`. | +| BL-044 | Expand release runbook with operator-grade procedural detail | Proposed | Low | Delivery + Operations | Independent refactor review (2026-02-15) | Platform | Enhance release docs with deterministic validation/rollback steps and expected outputs for staging/production package verification. Acceptance: release can be executed by a non-author using only runbook docs. Proof: runbook dry-run record + `pnpm docs-lint`. | +| BL-045 | Centralize typed error-code registry across contracts and handlers | Proposed | Medium | Contracts + Reliability | Independent refactor review (2026-02-15) | Platform | Create shared typed error-code registry and migrate hardcoded handler codes incrementally without breaking wire compatibility. Acceptance: new/modified handlers import from registry and tests assert stable codes. Proof: `pnpm nx run shared-contracts:test`, `pnpm nx run desktop-main:test`. | +| BL-046 | Introduce provider-agnostic external execution gateway abstraction | Done | High | Architecture + Security | Architecture discussion (2026-02-15) | Platform + Security | Completed baseline abstraction with provider-routed gateway execution (`external-http`, `bundled-http`) while preserving renderer contract shape; includes parity test proving stable response envelope across provider selection. | +| BL-047 | Add typed operation registry for gateway invocation allowlists | Done | High | Contracts + Security | Architecture discussion (2026-02-15) | Platform + Security | Completed with centralized typed operation registry and fail-closed gateway enforcement for unknown providers plus operation-level request policy limits (entry/value bounds) returning typed `API/INVALID_PARAMS` / `API/INVALID_HEADERS` envelopes. | +| BL-048 | Implement trust-tier policy engine for local and remote providers | Done | High | Security + Policy | Architecture discussion (2026-02-15) | Platform + Security | Delivered provider→tier mapping (`external-http`→`remote-low`, `bundled-http`→`local-medium`, `local-high` reserved for future adapters) with declared-config ceiling evaluation (fail-closed `API/POLICY_VIOLATION`), effective tier clamping of params/headers/chars/retry/timeout/response bytes, and correlation-ID violation logging via gateway logger dep. Proof: `pnpm nx run desktop-main:test` incl. trust-policy and gateway integration suites. | +| BL-049 | Add external provider adapter baseline (Docker-local first) | Proposed | Medium | Runtime Extensibility | Architecture discussion (2026-02-15) | Platform | Implement first external adapter using localhost bridge contract (Docker-hosted service), preserving existing main-process security controls and typed failure envelopes. Acceptance: bundled and external providers pass shared operation compatibility tests. Proof: `pnpm nx run desktop-main:test`, `pnpm runtime:smoke`. | +| BL-050 | Add sensitive-operation capability gating and confirmation controls | Proposed | Medium | Security + UX | Architecture discussion (2026-02-15) | Platform + Frontend | Introduce capability/confirmation gates for high-risk operations (for example local file/system-affecting calls), including deny-by-default policy metadata in operation registry. Acceptance: gated operations require explicit enablement and reject otherwise. Proof: `pnpm nx run desktop-main:test`, `pnpm nx run renderer:test`. | ## Status Definitions diff --git a/docs/05-governance/decision-log.md b/docs/05-governance/decision-log.md index e6015de..9609969 100644 --- a/docs/05-governance/decision-log.md +++ b/docs/05-governance/decision-log.md @@ -2,23 +2,24 @@ Owner: Platform Engineering Review cadence: Weekly -Last reviewed: 2026-02-13 +Last reviewed: 2026-08-22 ## Purpose Record accepted architecture/process decisions and maintain links to canonical policy documents. -| ADR | Date | Status | Summary | Canonical Reference | -| -------- | ---------- | -------- | ------------------------------------------------------------------------------------------ | ------------------------------------------------ | -| ADR-0001 | 2026-02-06 | Accepted | Nx monorepo with Angular 21 + Electron baseline | `docs/02-architecture/solution-architecture.md` | -| ADR-0002 | 2026-02-06 | Accepted | Material-first UI with controlled Carbon adapters | `docs/02-architecture/ui-system-governance.md` | -| ADR-0003 | 2026-02-06 | Accepted | Transloco runtime i18n strategy | `docs/02-architecture/a11y-and-i18n-standard.md` | -| ADR-0004 | 2026-02-06 | Accepted | Trunk-based workflow with PR-only protected main | `docs/03-engineering/git-and-pr-policy.md` | -| ADR-0005 | 2026-02-07 | Accepted | Privileged-boundary contract policy (`DesktopResult`, Zod, versioned envelopes) | `docs/02-architecture/ipc-contract-standard.md` | -| ADR-0006 | 2026-02-07 | Accepted | Electron hardening baseline with preload-only capability bridge | `docs/02-architecture/security-architecture.md` | -| ADR-0007 | 2026-02-12 | Accepted | Desktop OIDC architecture: main-process PKCE and secure token handling | `docs/05-governance/oidc-auth-backlog.md` | -| ADR-0008 | 2026-02-13 | Accepted | CI release gating includes security checklist and performance regression checks | `docs/04-delivery/ci-cd-spec.md` | -| ADR-0009 | 2026-02-13 | Accepted | Python sidecar helper model via main-process policy enforcement and tokenized file ingress | `docs/02-architecture/security-architecture.md` | +| ADR | Date | Status | Summary | Canonical Reference | +| -------- | ---------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ | +| ADR-0001 | 2026-02-06 | Accepted | Nx monorepo with Angular 21 + Electron baseline | `docs/02-architecture/solution-architecture.md` | +| ADR-0002 | 2026-02-06 | Accepted | Material-first UI with controlled Carbon adapters | `docs/02-architecture/ui-system-governance.md` | +| ADR-0003 | 2026-02-06 | Accepted | Transloco runtime i18n strategy | `docs/02-architecture/a11y-and-i18n-standard.md` | +| ADR-0004 | 2026-02-06 | Accepted | Trunk-based workflow with PR-only protected main | `docs/03-engineering/git-and-pr-policy.md` | +| ADR-0005 | 2026-02-07 | Accepted | Privileged-boundary contract policy (`DesktopResult`, Zod, versioned envelopes) | `docs/02-architecture/ipc-contract-standard.md` | +| ADR-0006 | 2026-02-07 | Accepted | Electron hardening baseline with preload-only capability bridge | `docs/02-architecture/security-architecture.md` | +| ADR-0007 | 2026-02-12 | Accepted | Desktop OIDC architecture: main-process PKCE and secure token handling | `docs/05-governance/oidc-auth-backlog.md` | +| ADR-0008 | 2026-02-13 | Accepted | CI release gating includes security checklist and performance regression checks | `docs/04-delivery/ci-cd-spec.md` | +| ADR-0009 | 2026-02-13 | Accepted | Python sidecar helper model via main-process policy enforcement and tokenized file ingress | `docs/02-architecture/security-architecture.md` | +| ADR-0010 | 2026-08-22 | Accepted | Provider trust-tier policy engine: provider classes map to `local-high` / `local-medium` / `remote-low` tiers whose ceilings (param/header entries and value chars, retry attempts, timeout, response bytes) fail-closed gate every gateway dispatch with correlation-ID logging | `apps/desktop-main/src/api-trust-policy.ts` | ## Retrospective Note