@celilo/cli 0.13.3 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CELILO_CORE_MODULES.md +3 -0
- package/CELILO_SUBSYSTEMS.md +71 -2
- package/docs/ALERTING.md +298 -0
- package/docs/INDEX.md +103 -0
- package/drizzle/0016_trusted_sources.sql +10 -0
- package/drizzle/0017_alerting.sql +127 -0
- package/drizzle/meta/_journal.json +15 -1
- package/package.json +3 -2
- package/schemas/system_config.json +9 -0
- package/src/cli/commands/alerts-act.ts +107 -0
- package/src/cli/commands/alerts-list.ts +62 -0
- package/src/cli/commands/alerts-poll.ts +129 -0
- package/src/cli/commands/alerts-sweep.ts +156 -0
- package/src/cli/commands/module-list.ts +50 -3
- package/src/cli/commands/monitor.ts +178 -0
- package/src/cli/commands/notify-config.ts +453 -0
- package/src/cli/commands/system-audit.ts +2 -0
- package/src/cli/commands/system-update.ts +1 -0
- package/src/cli/completion.ts +26 -0
- package/src/cli/generate-zsh-completion.ts +2 -0
- package/src/cli/index.ts +58 -0
- package/src/cli/tui/audit-state.ts +2 -0
- package/src/db/schema.ts +358 -0
- package/src/hooks/capability-loader.ts +158 -46
- package/src/hooks/capability-map-coverage.test.ts +101 -0
- package/src/manifest/schema.ts +60 -1
- package/src/services/alerting/ack.test.ts +212 -0
- package/src/services/alerting/ack.ts +119 -0
- package/src/services/alerting/builtin-monitors.test.ts +132 -0
- package/src/services/alerting/builtin-monitors.ts +84 -0
- package/src/services/alerting/builtin-source.ts +82 -0
- package/src/services/alerting/coverage-source.ts +38 -0
- package/src/services/alerting/deferral.test.ts +161 -0
- package/src/services/alerting/delivery-loop.test.ts +396 -0
- package/src/services/alerting/deploy-hooks.test.ts +125 -0
- package/src/services/alerting/deploy-hooks.ts +111 -0
- package/src/services/alerting/escalation.test.ts +207 -0
- package/src/services/alerting/escalation.ts +151 -0
- package/src/services/alerting/format.test.ts +193 -0
- package/src/services/alerting/format.ts +150 -0
- package/src/services/alerting/health-coverage.ts +81 -0
- package/src/services/alerting/inbound-poller.test.ts +298 -0
- package/src/services/alerting/inbound-poller.ts +236 -0
- package/src/services/alerting/inbound.test.ts +201 -0
- package/src/services/alerting/inbound.ts +112 -0
- package/src/services/alerting/interview-responder.test.ts +169 -0
- package/src/services/alerting/interview-responder.ts +158 -0
- package/src/services/alerting/keys.test.ts +155 -0
- package/src/services/alerting/keys.ts +190 -0
- package/src/services/alerting/monitors.ts +185 -0
- package/src/services/alerting/notification-responder.test.ts +290 -0
- package/src/services/alerting/notification-responder.ts +260 -0
- package/src/services/alerting/notifier.ts +219 -0
- package/src/services/alerting/people.ts +178 -0
- package/src/services/alerting/quiet-hours.test.ts +140 -0
- package/src/services/alerting/quiet-hours.ts +99 -0
- package/src/services/alerting/reconcile.test.ts +190 -0
- package/src/services/alerting/reconcile.ts +166 -0
- package/src/services/alerting/run-monitor.test.ts +185 -0
- package/src/services/alerting/run-monitor.ts +177 -0
- package/src/services/alerting/store.test.ts +222 -0
- package/src/services/alerting/store.ts +289 -0
- package/src/services/alerting/suppression.test.ts +228 -0
- package/src/services/alerting/suppression.ts +142 -0
- package/src/services/alerting/sweep-runner.test.ts +229 -0
- package/src/services/alerting/sweep-runner.ts +204 -0
- package/src/services/alerting/sweep.test.ts +61 -0
- package/src/services/alerting/sweep.ts +41 -0
- package/src/services/alerting/tokens.test.ts +152 -0
- package/src/services/alerting/tokens.ts +119 -0
- package/src/services/alerting/transport-loader.ts +48 -0
- package/src/services/audit/index.test.ts +1 -0
- package/src/services/audit/index.ts +3 -0
- package/src/services/audit/trusted-sources.test.ts +137 -0
- package/src/services/audit/trusted-sources.ts +124 -0
- package/src/services/audit/types.ts +2 -1
- package/src/services/firewall-reach.ts +83 -0
- package/src/services/health-runner.test.ts +50 -0
- package/src/services/health-runner.ts +116 -82
- package/src/services/module-deploy.ts +17 -0
- package/src/services/system-config-validator.test.ts +31 -1
- package/src/services/trusted-sources.test.ts +221 -0
- package/src/services/trusted-sources.ts +159 -0
- package/src/services/update/orchestrator.test.ts +1 -0
- package/src/templates/generator.ts +6 -29
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trusted-source registry — the DB-backed `TrustedSourceStore` for the
|
|
3
|
+
* `firewall` capability, and the composition of the trusted-subnet set the
|
|
4
|
+
* renderer consumes.
|
|
5
|
+
*
|
|
6
|
+
* A trusted source is an ORIGIN subnet permitted to initiate into every managed
|
|
7
|
+
* zone (the admin VPN's client range being the motivating case). It is not a
|
|
8
|
+
* port forward, so `exposeService` cannot express it — which is why the VPN's
|
|
9
|
+
* rules belonged to no module and every converge removed them.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type {
|
|
13
|
+
RegisterTrustedSourceRequest,
|
|
14
|
+
TrustedSource,
|
|
15
|
+
TrustedSourceStore,
|
|
16
|
+
} from '@celilo/capabilities';
|
|
17
|
+
import { and, eq } from 'drizzle-orm';
|
|
18
|
+
import type { DbClient } from '../db/client';
|
|
19
|
+
import { trustedSources } from '../db/schema';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Build the store bound to the module that will call through the capability.
|
|
23
|
+
* `registeredBy` is stamped here, not supplied by the provider, so a
|
|
24
|
+
* registration can never be attributed to the wrong module.
|
|
25
|
+
*/
|
|
26
|
+
export function buildTrustedSourceStore(db: DbClient, registeredBy: string): TrustedSourceStore {
|
|
27
|
+
return {
|
|
28
|
+
list(firewallIp: string): TrustedSource[] {
|
|
29
|
+
return db
|
|
30
|
+
.select()
|
|
31
|
+
.from(trustedSources)
|
|
32
|
+
.where(eq(trustedSources.firewallIp, firewallIp))
|
|
33
|
+
.all()
|
|
34
|
+
.map((r) => ({
|
|
35
|
+
subnet: r.subnet,
|
|
36
|
+
description: r.description,
|
|
37
|
+
registeredBy: r.registeredBy,
|
|
38
|
+
}));
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
add(firewallIp: string, source: RegisterTrustedSourceRequest): void {
|
|
42
|
+
// Idempotent upsert on (firewall, subnet) — matches buildPortForwardStore.
|
|
43
|
+
db.delete(trustedSources)
|
|
44
|
+
.where(
|
|
45
|
+
and(eq(trustedSources.firewallIp, firewallIp), eq(trustedSources.subnet, source.subnet)),
|
|
46
|
+
)
|
|
47
|
+
.run();
|
|
48
|
+
db.insert(trustedSources)
|
|
49
|
+
.values({
|
|
50
|
+
firewallIp,
|
|
51
|
+
subnet: source.subnet,
|
|
52
|
+
description: source.description,
|
|
53
|
+
registeredBy,
|
|
54
|
+
})
|
|
55
|
+
.run();
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
remove(firewallIp: string, subnet: string): void {
|
|
59
|
+
db.delete(trustedSources)
|
|
60
|
+
.where(and(eq(trustedSources.firewallIp, firewallIp), eq(trustedSources.subnet, subnet)))
|
|
61
|
+
.run();
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Where a trusted subnet came from — reach into every tier must be attributable. */
|
|
67
|
+
export type TrustedSubnetOrigin = 'derived-control-plane' | 'registered' | 'operator-override';
|
|
68
|
+
|
|
69
|
+
export interface TrustedSubnetEntry {
|
|
70
|
+
subnet: string;
|
|
71
|
+
origin: TrustedSubnetOrigin;
|
|
72
|
+
/** Module id for `registered`; undefined for the derived and override origins. */
|
|
73
|
+
registeredBy?: string;
|
|
74
|
+
description?: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The operator's explicit trusted sources, as a comma-separated CIDR list in
|
|
79
|
+
* system config. ADDITIVE rather than replacing the derived control-plane
|
|
80
|
+
* subnet: dropping control-plane trust would block the SSH celilo uses to run
|
|
81
|
+
* every hook and converge, so an operator cannot strand celilo's own management
|
|
82
|
+
* by naming a subnet here.
|
|
83
|
+
*/
|
|
84
|
+
export const TRUSTED_SUBNETS_CONFIG_KEY = 'firewall.trusted_subnets';
|
|
85
|
+
|
|
86
|
+
export function parseOperatorTrustedSubnets(raw: string | undefined): string[] {
|
|
87
|
+
if (!raw) return [];
|
|
88
|
+
return raw
|
|
89
|
+
.split(',')
|
|
90
|
+
.map((s) => s.trim())
|
|
91
|
+
.filter((s) => s.length > 0);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Compose the trusted-subnet set from its three origins, deduped by subnet with
|
|
96
|
+
* the FIRST origin winning (derived beats registered beats override), so the
|
|
97
|
+
* report names the strongest claim on a subnet rather than an arbitrary one.
|
|
98
|
+
*
|
|
99
|
+
* With nothing registered and no override this returns exactly the derived
|
|
100
|
+
* control-plane subnet — the property that makes composing safe to ship ahead of
|
|
101
|
+
* any module that registers.
|
|
102
|
+
*/
|
|
103
|
+
export function composeTrustedSubnets(inputs: {
|
|
104
|
+
controlPlaneSubnet?: string;
|
|
105
|
+
registered: TrustedSource[];
|
|
106
|
+
operatorOverride: string[];
|
|
107
|
+
}): TrustedSubnetEntry[] {
|
|
108
|
+
const entries: TrustedSubnetEntry[] = [];
|
|
109
|
+
const seen = new Set<string>();
|
|
110
|
+
const push = (entry: TrustedSubnetEntry): void => {
|
|
111
|
+
if (seen.has(entry.subnet)) return;
|
|
112
|
+
seen.add(entry.subnet);
|
|
113
|
+
entries.push(entry);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
if (inputs.controlPlaneSubnet) {
|
|
117
|
+
push({ subnet: inputs.controlPlaneSubnet, origin: 'derived-control-plane' });
|
|
118
|
+
}
|
|
119
|
+
for (const source of inputs.registered) {
|
|
120
|
+
push({
|
|
121
|
+
subnet: source.subnet,
|
|
122
|
+
origin: 'registered',
|
|
123
|
+
registeredBy: source.registeredBy,
|
|
124
|
+
description: source.description,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
for (const subnet of inputs.operatorOverride) {
|
|
128
|
+
push({ subnet, origin: 'operator-override' });
|
|
129
|
+
}
|
|
130
|
+
return entries;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** The registered trusted sources for one firewall. Read-only; no binding needed. */
|
|
134
|
+
export function listTrustedSourcesFor(db: DbClient, firewallIp: string): TrustedSource[] {
|
|
135
|
+
return db
|
|
136
|
+
.select()
|
|
137
|
+
.from(trustedSources)
|
|
138
|
+
.where(eq(trustedSources.firewallIp, firewallIp))
|
|
139
|
+
.all()
|
|
140
|
+
.map((r) => ({
|
|
141
|
+
subnet: r.subnet,
|
|
142
|
+
description: r.description,
|
|
143
|
+
registeredBy: r.registeredBy,
|
|
144
|
+
}));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** All registered trusted sources, across every firewall — for reporting. */
|
|
148
|
+
export function listAllTrustedSources(db: DbClient): Array<TrustedSource & { firewallIp: string }> {
|
|
149
|
+
return db
|
|
150
|
+
.select()
|
|
151
|
+
.from(trustedSources)
|
|
152
|
+
.all()
|
|
153
|
+
.map((r) => ({
|
|
154
|
+
firewallIp: r.firewallIp,
|
|
155
|
+
subnet: r.subnet,
|
|
156
|
+
description: r.description,
|
|
157
|
+
registeredBy: r.registeredBy,
|
|
158
|
+
}));
|
|
159
|
+
}
|
|
@@ -916,35 +916,12 @@ export async function generateTemplates(options: GenerateOptions): Promise<Gener
|
|
|
916
916
|
};
|
|
917
917
|
}
|
|
918
918
|
|
|
919
|
-
//
|
|
920
|
-
//
|
|
921
|
-
//
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
const derivedValue = context.selfConfig[variable.name];
|
|
926
|
-
if (derivedValue === undefined) continue;
|
|
927
|
-
|
|
928
|
-
const existing = db
|
|
929
|
-
.select()
|
|
930
|
-
.from(moduleConfigs)
|
|
931
|
-
.where(and(eq(moduleConfigs.moduleId, moduleId), eq(moduleConfigs.key, variable.name)))
|
|
932
|
-
.get();
|
|
933
|
-
|
|
934
|
-
if (!existing) {
|
|
935
|
-
// Insert-only (not upsert) — derived values only seed on first
|
|
936
|
-
// generate; subsequent runs preserve whatever the user has set.
|
|
937
|
-
// Routes through upsertModuleConfig anyway because the helper
|
|
938
|
-
// is the only path that maintains the valueJson invariant.
|
|
939
|
-
upsertModuleConfig(
|
|
940
|
-
db,
|
|
941
|
-
moduleId,
|
|
942
|
-
variable.name,
|
|
943
|
-
derivedValue as string | number | boolean | unknown[] | Record<string, unknown>,
|
|
944
|
-
);
|
|
945
|
-
}
|
|
946
|
-
}
|
|
947
|
-
}
|
|
919
|
+
// NOTE: derived variables are NOT persisted here. `buildResolutionContext`
|
|
920
|
+
// (called above) already wrote them at variables/context.ts, so the
|
|
921
|
+
// insert-only block that used to live here could never fire — it read as a
|
|
922
|
+
// second, competing persistence policy while being unreachable. The single
|
|
923
|
+
// write lives in context.ts; see openspec/changes/derived-value-recomputation
|
|
924
|
+
// for the plan to remove it there too.
|
|
948
925
|
|
|
949
926
|
// Execution: Resolve capability 'imports' variables into module_configs
|
|
950
927
|
// This makes capability-derived values available in host_vars for Ansible
|