mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-09-03 22:25:30 +00:00
fix(networking): hold unsafe network deletions and stabilize aggregate reads (#1850)
* fix(networking): hold unsafe network deletions and stabilize aggregate reads Networking audit hardening: - Fail closed when deleting unlabeled external networks while any stack fails to render or the Docker runtime is unreachable: declarations cannot be verified, so the backend 409s with a typed code and the inventory table holds the delete affordance instead of letting the confirm dialog surprise the operator. - Bound concurrent compose renders during network delete verification with the shared render semaphore and mapWithConcurrency. - Add a short-TTL memo for the node networking aggregate keyed by node and request variant, invalidated eagerly on stack, exposure-intent, dossier, and network mutations, with stale-on-error serves flagged as degradedCache and surfaced in the overview. - Resolve node-scoped stack edit permissions against the active node in the findings action list. - Developer-mode debug logs for aggregate serving and delete-guard outcomes. Tests: cache unit suite, hardening integration suite, and component coverage for the permission threading and delete-affordance holds. * chore(networking): add missing EOF newline in aggregate cache module
This commit is contained in:
@@ -8,6 +8,7 @@ import { applySuppressions } from '../utils/suppression-filter';
|
||||
import { SENCHO_ROLLBACK_HOLD_SQL_LIKE } from '../utils/senchoRollbackHold';
|
||||
import type { AuditStatsInput } from './AuditAnomalyService';
|
||||
import { EXPOSURE_INTENTS, type ExposureIntent } from './network/types';
|
||||
import { invalidateNodeNetworkingAggregate } from './network/networkingAggregateCache';
|
||||
import { HIGH_EPSS_THRESHOLD } from './securityPosture';
|
||||
import type { BackendScheduledAction } from './scheduledActionRegistry';
|
||||
import { stackPatternMatches } from '../helpers/stackPattern';
|
||||
@@ -3852,11 +3853,13 @@ export class DatabaseService {
|
||||
now,
|
||||
now
|
||||
);
|
||||
invalidateNodeNetworkingAggregate(nodeId);
|
||||
return this.getStackDossier(nodeId, stackName) as StackDossier;
|
||||
}
|
||||
|
||||
public deleteStackDossier(nodeId: number, stackName: string): void {
|
||||
this.db.prepare('DELETE FROM stack_dossiers WHERE node_id = ? AND stack_name = ?').run(nodeId, stackName);
|
||||
invalidateNodeNetworkingAggregate(nodeId);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3947,16 +3950,19 @@ export class DatabaseService {
|
||||
updated_at = excluded.updated_at,
|
||||
updated_by = excluded.updated_by`
|
||||
).run(nodeId, stackName, service, intent, Date.now(), updatedBy);
|
||||
invalidateNodeNetworkingAggregate(nodeId);
|
||||
}
|
||||
|
||||
/** Clear one intent row, leaving that scope unset; consumers treat a service with no row as inheriting the stack intent. */
|
||||
public deleteStackExposureIntent(nodeId: number, stackName: string, service: string): void {
|
||||
this.db.prepare('DELETE FROM stack_exposure_intent WHERE node_id = ? AND stack_name = ? AND service = ?').run(nodeId, stackName, service);
|
||||
invalidateNodeNetworkingAggregate(nodeId);
|
||||
}
|
||||
|
||||
/** Clear every intent row for a stack (used when the stack is deleted). */
|
||||
public deleteStackExposureIntents(nodeId: number, stackName: string): void {
|
||||
this.db.prepare('DELETE FROM stack_exposure_intent WHERE node_id = ? AND stack_name = ?').run(nodeId, stackName);
|
||||
invalidateNodeNetworkingAggregate(nodeId);
|
||||
}
|
||||
|
||||
// --- Stack Exposure (Compose reachability descriptor) ---
|
||||
|
||||
Reference in New Issue
Block a user