@synoikia/core 0.1.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.
Files changed (209) hide show
  1. package/LICENSE +21 -0
  2. package/dist/app.d.ts +79 -0
  3. package/dist/app.js +155 -0
  4. package/dist/app.js.map +1 -0
  5. package/dist/approvals/links.d.ts +23 -0
  6. package/dist/approvals/links.js +49 -0
  7. package/dist/approvals/links.js.map +1 -0
  8. package/dist/approvals/service.d.ts +110 -0
  9. package/dist/approvals/service.js +182 -0
  10. package/dist/approvals/service.js.map +1 -0
  11. package/dist/audit-query.d.ts +52 -0
  12. package/dist/audit-query.js +116 -0
  13. package/dist/audit-query.js.map +1 -0
  14. package/dist/audit.d.ts +5 -0
  15. package/dist/audit.js +8 -0
  16. package/dist/audit.js.map +1 -0
  17. package/dist/auth/mcp-auth.d.ts +50 -0
  18. package/dist/auth/mcp-auth.js +160 -0
  19. package/dist/auth/mcp-auth.js.map +1 -0
  20. package/dist/auth/mcp-tokens.d.ts +57 -0
  21. package/dist/auth/mcp-tokens.js +143 -0
  22. package/dist/auth/mcp-tokens.js.map +1 -0
  23. package/dist/auth/oauth.d.ts +188 -0
  24. package/dist/auth/oauth.js +470 -0
  25. package/dist/auth/oauth.js.map +1 -0
  26. package/dist/auth/oidc.d.ts +110 -0
  27. package/dist/auth/oidc.js +221 -0
  28. package/dist/auth/oidc.js.map +1 -0
  29. package/dist/auth/sessions.d.ts +34 -0
  30. package/dist/auth/sessions.js +83 -0
  31. package/dist/auth/sessions.js.map +1 -0
  32. package/dist/auth/throttle.d.ts +31 -0
  33. package/dist/auth/throttle.js +52 -0
  34. package/dist/auth/throttle.js.map +1 -0
  35. package/dist/auth/tokens.d.ts +7 -0
  36. package/dist/auth/tokens.js +40 -0
  37. package/dist/auth/tokens.js.map +1 -0
  38. package/dist/auth/totp.d.ts +14 -0
  39. package/dist/auth/totp.js +81 -0
  40. package/dist/auth/totp.js.map +1 -0
  41. package/dist/auth/users.d.ts +70 -0
  42. package/dist/auth/users.js +296 -0
  43. package/dist/auth/users.js.map +1 -0
  44. package/dist/catalog/groups.d.ts +102 -0
  45. package/dist/catalog/groups.js +363 -0
  46. package/dist/catalog/groups.js.map +1 -0
  47. package/dist/catalog/registry.d.ts +25 -0
  48. package/dist/catalog/registry.js +80 -0
  49. package/dist/catalog/registry.js.map +1 -0
  50. package/dist/catalog/rules.d.ts +128 -0
  51. package/dist/catalog/rules.js +178 -0
  52. package/dist/catalog/rules.js.map +1 -0
  53. package/dist/catalog/sync.d.ts +29 -0
  54. package/dist/catalog/sync.js +222 -0
  55. package/dist/catalog/sync.js.map +1 -0
  56. package/dist/cli.d.ts +3 -0
  57. package/dist/cli.js +83 -0
  58. package/dist/cli.js.map +1 -0
  59. package/dist/config/env.d.ts +40 -0
  60. package/dist/config/env.js +71 -0
  61. package/dist/config/env.js.map +1 -0
  62. package/dist/crypto/index.d.ts +28 -0
  63. package/dist/crypto/index.js +88 -0
  64. package/dist/crypto/index.js.map +1 -0
  65. package/dist/crypto/rotate.d.ts +15 -0
  66. package/dist/crypto/rotate.js +61 -0
  67. package/dist/crypto/rotate.js.map +1 -0
  68. package/dist/db/index.d.ts +17 -0
  69. package/dist/db/index.js +29 -0
  70. package/dist/db/index.js.map +1 -0
  71. package/dist/db/schema.d.ts +4389 -0
  72. package/dist/db/schema.js +372 -0
  73. package/dist/db/schema.js.map +1 -0
  74. package/dist/endpoints/slug.d.ts +3 -0
  75. package/dist/endpoints/slug.js +7 -0
  76. package/dist/endpoints/slug.js.map +1 -0
  77. package/dist/errors.d.ts +16 -0
  78. package/dist/errors.js +29 -0
  79. package/dist/errors.js.map +1 -0
  80. package/dist/events.d.ts +43 -0
  81. package/dist/events.js +16 -0
  82. package/dist/events.js.map +1 -0
  83. package/dist/gate/access.d.ts +47 -0
  84. package/dist/gate/access.js +48 -0
  85. package/dist/gate/access.js.map +1 -0
  86. package/dist/gate/attestation.d.ts +23 -0
  87. package/dist/gate/attestation.js +33 -0
  88. package/dist/gate/attestation.js.map +1 -0
  89. package/dist/gate/canonical.d.ts +3 -0
  90. package/dist/gate/canonical.js +11 -0
  91. package/dist/gate/canonical.js.map +1 -0
  92. package/dist/gate/match.d.ts +67 -0
  93. package/dist/gate/match.js +133 -0
  94. package/dist/gate/match.js.map +1 -0
  95. package/dist/gate/pipeline.d.ts +61 -0
  96. package/dist/gate/pipeline.js +284 -0
  97. package/dist/gate/pipeline.js.map +1 -0
  98. package/dist/gate/preapproval.d.ts +24 -0
  99. package/dist/gate/preapproval.js +48 -0
  100. package/dist/gate/preapproval.js.map +1 -0
  101. package/dist/gate/rate-limit.d.ts +35 -0
  102. package/dist/gate/rate-limit.js +82 -0
  103. package/dist/gate/rate-limit.js.map +1 -0
  104. package/dist/gate/redact.d.ts +23 -0
  105. package/dist/gate/redact.js +87 -0
  106. package/dist/gate/redact.js.map +1 -0
  107. package/dist/http/admin/auth.d.ts +33 -0
  108. package/dist/http/admin/auth.js +338 -0
  109. package/dist/http/admin/auth.js.map +1 -0
  110. package/dist/http/admin/instances.d.ts +5 -0
  111. package/dist/http/admin/instances.js +193 -0
  112. package/dist/http/admin/instances.js.map +1 -0
  113. package/dist/http/admin/system.d.ts +5 -0
  114. package/dist/http/admin/system.js +225 -0
  115. package/dist/http/admin/system.js.map +1 -0
  116. package/dist/http/admin-app.d.ts +9 -0
  117. package/dist/http/admin-app.js +51 -0
  118. package/dist/http/admin-app.js.map +1 -0
  119. package/dist/http/common.d.ts +32 -0
  120. package/dist/http/common.js +80 -0
  121. package/dist/http/common.js.map +1 -0
  122. package/dist/http/mcp/approval-routes.d.ts +11 -0
  123. package/dist/http/mcp/approval-routes.js +117 -0
  124. package/dist/http/mcp/approval-routes.js.map +1 -0
  125. package/dist/http/mcp/endpoint.d.ts +32 -0
  126. package/dist/http/mcp/endpoint.js +293 -0
  127. package/dist/http/mcp/endpoint.js.map +1 -0
  128. package/dist/http/mcp/oauth-routes.d.ts +29 -0
  129. package/dist/http/mcp/oauth-routes.js +465 -0
  130. package/dist/http/mcp/oauth-routes.js.map +1 -0
  131. package/dist/http/mcp/pages.d.ts +63 -0
  132. package/dist/http/mcp/pages.js +200 -0
  133. package/dist/http/mcp/pages.js.map +1 -0
  134. package/dist/http/mcp-app.d.ts +7 -0
  135. package/dist/http/mcp-app.js +30 -0
  136. package/dist/http/mcp-app.js.map +1 -0
  137. package/dist/instances/connection.d.ts +37 -0
  138. package/dist/instances/connection.js +109 -0
  139. package/dist/instances/connection.js.map +1 -0
  140. package/dist/instances/manager.d.ts +313 -0
  141. package/dist/instances/manager.js +600 -0
  142. package/dist/instances/manager.js.map +1 -0
  143. package/dist/instances/settings.d.ts +42 -0
  144. package/dist/instances/settings.js +57 -0
  145. package/dist/instances/settings.js.map +1 -0
  146. package/dist/lenient.d.ts +16 -0
  147. package/dist/lenient.js +51 -0
  148. package/dist/lenient.js.map +1 -0
  149. package/dist/lock.d.ts +28 -0
  150. package/dist/lock.js +77 -0
  151. package/dist/lock.js.map +1 -0
  152. package/dist/main.d.ts +1 -0
  153. package/dist/main.js +38 -0
  154. package/dist/main.js.map +1 -0
  155. package/dist/maintenance.d.ts +10 -0
  156. package/dist/maintenance.js +48 -0
  157. package/dist/maintenance.js.map +1 -0
  158. package/dist/notify/service.d.ts +149 -0
  159. package/dist/notify/service.js +358 -0
  160. package/dist/notify/service.js.map +1 -0
  161. package/dist/plugins/discovery.d.ts +39 -0
  162. package/dist/plugins/discovery.js +157 -0
  163. package/dist/plugins/discovery.js.map +1 -0
  164. package/dist/plugins/minisign.d.ts +25 -0
  165. package/dist/plugins/minisign.js +49 -0
  166. package/dist/plugins/minisign.js.map +1 -0
  167. package/dist/plugins/process.d.ts +55 -0
  168. package/dist/plugins/process.js +162 -0
  169. package/dist/plugins/process.js.map +1 -0
  170. package/dist/plugins/repos.d.ts +198 -0
  171. package/dist/plugins/repos.js +612 -0
  172. package/dist/plugins/repos.js.map +1 -0
  173. package/dist/plugins/supervisor.d.ts +50 -0
  174. package/dist/plugins/supervisor.js +125 -0
  175. package/dist/plugins/supervisor.js.map +1 -0
  176. package/dist/runtime/index.d.ts +28 -0
  177. package/dist/runtime/index.js +248 -0
  178. package/dist/runtime/index.js.map +1 -0
  179. package/dist/sandbox/index.d.ts +53 -0
  180. package/dist/sandbox/index.js +197 -0
  181. package/dist/sandbox/index.js.map +1 -0
  182. package/dist/server.d.ts +16 -0
  183. package/dist/server.js +36 -0
  184. package/dist/server.js.map +1 -0
  185. package/dist/settings.d.ts +43 -0
  186. package/dist/settings.js +99 -0
  187. package/dist/settings.js.map +1 -0
  188. package/dist/testing/index.d.ts +73 -0
  189. package/dist/testing/index.js +174 -0
  190. package/dist/testing/index.js.map +1 -0
  191. package/dist/version.d.ts +2 -0
  192. package/dist/version.js +4 -0
  193. package/dist/version.js.map +1 -0
  194. package/drizzle/0000_init.sql +334 -0
  195. package/drizzle/0001_totp_last_step.sql +1 -0
  196. package/drizzle/0002_access_levels.sql +16 -0
  197. package/drizzle/0003_drop_exclusion_flags.sql +2 -0
  198. package/drizzle/0004_grant_instance_ids.sql +1 -0
  199. package/drizzle/0005_rule_strict_miss.sql +1 -0
  200. package/drizzle/0006_attestation_waived.sql +1 -0
  201. package/drizzle/meta/0000_snapshot.json +2318 -0
  202. package/drizzle/meta/0001_snapshot.json +2325 -0
  203. package/drizzle/meta/0002_snapshot.json +2348 -0
  204. package/drizzle/meta/0003_snapshot.json +2332 -0
  205. package/drizzle/meta/0004_snapshot.json +2339 -0
  206. package/drizzle/meta/0005_snapshot.json +2346 -0
  207. package/drizzle/meta/0006_snapshot.json +2354 -0
  208. package/drizzle/meta/_journal.json +55 -0
  209. package/package.json +58 -0
@@ -0,0 +1,178 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { and, asc, eq } from 'drizzle-orm';
3
+ import { z } from 'zod';
4
+ import { writeAudit } from '../audit.js';
5
+ import { operations, preApprovalRules } from '../db/schema.js';
6
+ import { ConflictError, NotFoundError, ValidationError } from '../errors.js';
7
+ import { MatchSchema } from '../gate/match.js';
8
+ import { resolveAccess } from './groups.js';
9
+ /**
10
+ * Pre-approval rules (design §5.2, TN §3.5): picked from the synced catalog, never free text; a
11
+ * required reason; structured match; optional rate limit and expiry. Locked operations can never be
12
+ * referenced (409), enforced here regardless of what the UI offers.
13
+ */
14
+ export const RuleInputSchema = z.object({
15
+ operationId: z.string().min(1),
16
+ match: MatchSchema.default([]),
17
+ rateLimit: z.number().int().min(1).max(100_000).nullable().optional(),
18
+ windowSeconds: z
19
+ .number()
20
+ .int()
21
+ .min(60)
22
+ .max(31 * 24 * 3600)
23
+ .nullable()
24
+ .optional(),
25
+ expiresAt: z.coerce.date().nullable().optional(),
26
+ reason: z.string().trim().min(3, 'A reason is required').max(500),
27
+ enabled: z.boolean().default(true),
28
+ });
29
+ function loadOperation(db, instanceId, operationId) {
30
+ const op = db
31
+ .select()
32
+ .from(operations)
33
+ .where(and(eq(operations.id, operationId), eq(operations.instanceId, instanceId)))
34
+ .get();
35
+ if (!op)
36
+ throw new NotFoundError('operation_not_found', 'No such operation on this endpoint');
37
+ return op;
38
+ }
39
+ /** Why a rule's match doesn't fit the operation's declared fields (design §8.3), or null if it does. */
40
+ export function matchMisfit(manifest, op, match) {
41
+ if (match.every((c) => c.field !== '$targets' && 'op' in c && c.op === 'any'))
42
+ return null;
43
+ const profile = op.matchProfile ? manifest.matchProfiles[op.matchProfile] : undefined;
44
+ if (!profile)
45
+ return new ValidationError('no_match_profile', `${op.key} has no matchable fields; use an empty match`);
46
+ for (const c of match) {
47
+ // "Any value" can name any parameter: it only widens strict matching, never narrows a condition.
48
+ if (c.field !== '$targets' && 'op' in c && c.op === 'any')
49
+ continue;
50
+ const field = profile.find((f) => f.field === c.field);
51
+ if (!field)
52
+ return new ValidationError('unknown_match_field', `${c.field} is not a matchable field of ${op.key}`);
53
+ if (c.field !== '$targets' && 'op' in c && field.op !== c.op) {
54
+ return new ValidationError('wrong_match_op', `${c.field} must use the "${field.op}" operator`);
55
+ }
56
+ }
57
+ return null;
58
+ }
59
+ function checkMatchAgainstProfile(manifest, op, match) {
60
+ const misfit = matchMisfit(manifest, op, match);
61
+ if (misfit)
62
+ throw misfit;
63
+ }
64
+ function describe(db, instanceId, rule, op) {
65
+ const access = resolveAccess(db, instanceId, op.key);
66
+ return {
67
+ ...rule,
68
+ operation: { id: op.id, key: op.key, locked: op.locked, matchProfile: op.matchProfile },
69
+ /**
70
+ * Rules only apply at level `ask`: on an unreachable operation, or one at level `write`, a rule
71
+ * never fires; the UI flags it (design §5.2.1).
72
+ */
73
+ inert: !rule.enabled ? null : !access.reachable ? access.reason : access.level === 'write' ? 'level_write' : null,
74
+ };
75
+ }
76
+ export function listRules(db, instanceId) {
77
+ return db
78
+ .select({ rule: preApprovalRules, op: operations })
79
+ .from(preApprovalRules)
80
+ .innerJoin(operations, eq(preApprovalRules.operationId, operations.id))
81
+ .where(eq(preApprovalRules.instanceId, instanceId))
82
+ .orderBy(asc(operations.key), asc(preApprovalRules.createdAt))
83
+ .all()
84
+ .map(({ rule, op }) => describe(db, instanceId, rule, op));
85
+ }
86
+ export function createRule(db, manifest, instanceId, raw, actor = {}) {
87
+ const input = RuleInputSchema.parse(raw);
88
+ const op = loadOperation(db, instanceId, input.operationId);
89
+ if (op.locked)
90
+ throw new ConflictError('operation_locked', `${op.key} is locked and can never be pre-approved`);
91
+ checkMatchAgainstProfile(manifest, op, input.match);
92
+ const id = randomUUID();
93
+ db.transaction((tx) => {
94
+ tx.insert(preApprovalRules)
95
+ .values({
96
+ id,
97
+ instanceId,
98
+ operationId: op.id,
99
+ match: input.match,
100
+ rateLimit: input.rateLimit ?? null,
101
+ windowSeconds: input.rateLimit ? (input.windowSeconds ?? 3600) : null,
102
+ expiresAt: input.expiresAt ?? null,
103
+ reason: input.reason,
104
+ enabled: input.enabled,
105
+ createdBy: actor.userId ?? null,
106
+ createdAt: new Date(),
107
+ })
108
+ .run();
109
+ writeAudit(tx, {
110
+ kind: 'config',
111
+ instanceId,
112
+ operationKey: op.key,
113
+ decision: 'rule_created',
114
+ actorKind: 'user',
115
+ actorId: actor.userId,
116
+ detail: { ruleId: id, ...input },
117
+ });
118
+ });
119
+ return listRules(db, instanceId).find((r) => r.id === id);
120
+ }
121
+ export function updateRule(db, manifest, instanceId, ruleId, raw, actor = {}) {
122
+ const before = db
123
+ .select()
124
+ .from(preApprovalRules)
125
+ .where(and(eq(preApprovalRules.id, ruleId), eq(preApprovalRules.instanceId, instanceId)))
126
+ .get();
127
+ if (!before)
128
+ throw new NotFoundError('rule_not_found', 'No such rule');
129
+ const input = RuleInputSchema.parse({ ...before, ...raw });
130
+ const op = loadOperation(db, instanceId, input.operationId);
131
+ if (op.locked)
132
+ throw new ConflictError('operation_locked', `${op.key} is locked and can never be pre-approved`);
133
+ checkMatchAgainstProfile(manifest, op, input.match);
134
+ const set = {
135
+ operationId: op.id,
136
+ match: input.match,
137
+ rateLimit: input.rateLimit ?? null,
138
+ windowSeconds: input.rateLimit ? (input.windowSeconds ?? 3600) : null,
139
+ expiresAt: input.expiresAt ?? null,
140
+ reason: input.reason,
141
+ enabled: input.enabled,
142
+ updatedAt: new Date(),
143
+ };
144
+ db.transaction((tx) => {
145
+ tx.update(preApprovalRules).set(set).where(eq(preApprovalRules.id, ruleId)).run();
146
+ writeAudit(tx, {
147
+ kind: 'config',
148
+ instanceId,
149
+ operationKey: op.key,
150
+ decision: 'rule_updated',
151
+ actorKind: 'user',
152
+ actorId: actor.userId,
153
+ detail: { ruleId, before, after: set },
154
+ });
155
+ });
156
+ return listRules(db, instanceId).find((r) => r.id === ruleId);
157
+ }
158
+ export function deleteRule(db, instanceId, ruleId, actor = {}) {
159
+ const before = db
160
+ .select()
161
+ .from(preApprovalRules)
162
+ .where(and(eq(preApprovalRules.id, ruleId), eq(preApprovalRules.instanceId, instanceId)))
163
+ .get();
164
+ if (!before)
165
+ throw new NotFoundError('rule_not_found', 'No such rule');
166
+ db.transaction((tx) => {
167
+ tx.delete(preApprovalRules).where(eq(preApprovalRules.id, ruleId)).run();
168
+ writeAudit(tx, {
169
+ kind: 'config',
170
+ instanceId,
171
+ decision: 'rule_deleted',
172
+ actorKind: 'user',
173
+ actorId: actor.userId,
174
+ detail: { rule: before },
175
+ });
176
+ });
177
+ }
178
+ //# sourceMappingURL=rules.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rules.js","sourceRoot":"","sources":["../../src/catalog/rules.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import type { Manifest } from '@synoikia/plugin-sdk';
2
+ import type { Db } from '../db/index.js';
3
+ export interface SyncSummary {
4
+ added: number;
5
+ updated: number;
6
+ staled: number;
7
+ restored: number;
8
+ newGroups: string[];
9
+ /** Writes (new, reclassified read → write, changed, or back from stale) now waiting for acknowledgement. */
10
+ pendingReview: string[];
11
+ /** Operations that became locked in this sync; their pre-approval rules were disabled. */
12
+ newlyLocked: string[];
13
+ /** Rules disabled because their operation became locked or their match no longer fits it. */
14
+ rulesDisabled: number;
15
+ }
16
+ /**
17
+ * Applies a plugin's `syncCatalog` result to an instance (design §5.2.1, §10). Plugin output is
18
+ * untrusted and re-validated here. Runs in one transaction: either the whole catalog lands or none.
19
+ *
20
+ * - Operations missing from the result are marked `stale`, never deleted (history is kept).
21
+ * - `locked` always follows the plugin seed; admin overrides survive unless the op becomes locked.
22
+ * - New writes, and reads reclassified to writes, arrive unacknowledged (quarantined). So does an
23
+ * acknowledged write whose parameters, kind, match profile or lock changed, or that returns from
24
+ * stale: the admin acknowledged what it was, not what it became.
25
+ * - Enabled pre-approval rules are re-checked against the new catalog (and the plugin's match
26
+ * profiles, when given); rules that no longer fit are disabled and audited.
27
+ * - Plugin groups are mapped through admin aliases; missing groups are created at `read`.
28
+ */
29
+ export declare function applyCatalogSync(db: Db, instanceId: string, rawResult: unknown, now?: Date, manifest?: Pick<Manifest, 'matchProfiles'>): SyncSummary;
@@ -0,0 +1,222 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { SyncCatalogResultSchema } from '@synoikia/plugin-sdk';
3
+ import { and, eq, inArray } from 'drizzle-orm';
4
+ import { writeAudit } from '../audit.js';
5
+ import { operationGroupAliases, operationGroups, operations, pluginInstances, preApprovalRules } from '../db/schema.js';
6
+ import { ValidationError } from '../errors.js';
7
+ import { MatchSchema } from '../gate/match.js';
8
+ import { matchMisfit } from './rules.js';
9
+ /** What an acknowledgement covers: a change to any of these makes the write ask again. */
10
+ const fingerprint = (op) => JSON.stringify([op.kind, op.paramsSchema ?? null, op.matchProfile ?? null, op.locked]);
11
+ const isEffectiveWrite = (op) => op.locked || op.classification === 'write';
12
+ /**
13
+ * Applies a plugin's `syncCatalog` result to an instance (design §5.2.1, §10). Plugin output is
14
+ * untrusted and re-validated here. Runs in one transaction: either the whole catalog lands or none.
15
+ *
16
+ * - Operations missing from the result are marked `stale`, never deleted (history is kept).
17
+ * - `locked` always follows the plugin seed; admin overrides survive unless the op becomes locked.
18
+ * - New writes, and reads reclassified to writes, arrive unacknowledged (quarantined). So does an
19
+ * acknowledged write whose parameters, kind, match profile or lock changed, or that returns from
20
+ * stale: the admin acknowledged what it was, not what it became.
21
+ * - Enabled pre-approval rules are re-checked against the new catalog (and the plugin's match
22
+ * profiles, when given); rules that no longer fit are disabled and audited.
23
+ * - Plugin groups are mapped through admin aliases; missing groups are created at `read`.
24
+ */
25
+ export function applyCatalogSync(db, instanceId, rawResult, now = new Date(), manifest) {
26
+ const result = SyncCatalogResultSchema.parse(rawResult);
27
+ const seen = new Set();
28
+ for (const d of result.operations) {
29
+ if (seen.has(d.key))
30
+ throw new ValidationError('duplicate_operation', `Duplicate operation key: ${d.key}`);
31
+ seen.add(d.key);
32
+ }
33
+ return db.transaction((tx) => {
34
+ const aliases = new Map(tx
35
+ .select()
36
+ .from(operationGroupAliases)
37
+ .where(eq(operationGroupAliases.instanceId, instanceId))
38
+ .all()
39
+ .map((a) => [a.pluginGroup, a.groupKey]));
40
+ const groups = new Map(tx
41
+ .select()
42
+ .from(operationGroups)
43
+ .where(eq(operationGroups.instanceId, instanceId))
44
+ .all()
45
+ .map((g) => [g.key, g]));
46
+ const existing = new Map(tx
47
+ .select()
48
+ .from(operations)
49
+ .where(eq(operations.instanceId, instanceId))
50
+ .all()
51
+ .map((o) => [o.key, o]));
52
+ const summary = {
53
+ added: 0,
54
+ updated: 0,
55
+ staled: 0,
56
+ restored: 0,
57
+ newGroups: [],
58
+ pendingReview: [],
59
+ newlyLocked: [],
60
+ rulesDisabled: 0,
61
+ };
62
+ const groupIdFor = (pluginGroup, label) => {
63
+ const key = aliases.get(pluginGroup) ?? pluginGroup;
64
+ const found = groups.get(key);
65
+ if (found)
66
+ return found.id;
67
+ const row = { id: randomUUID(), instanceId, key, label: label ?? key, level: 'read', firstSeenAt: now };
68
+ tx.insert(operationGroups).values(row).run();
69
+ groups.set(key, { ...row, levelChangedAt: null, levelChangedBy: null, stale: false });
70
+ summary.newGroups.push(key);
71
+ return row.id;
72
+ };
73
+ for (const d of result.operations) {
74
+ const prev = existing.get(d.key);
75
+ const locked = d.locked;
76
+ const override = !locked && prev?.classificationSource === 'override';
77
+ const classification = locked ? 'write' : override ? prev.classification : d.classification;
78
+ const classificationSource = locked ? 'locked' : override ? 'override' : 'inferred';
79
+ const fields = {
80
+ displayName: d.displayName ?? null,
81
+ kind: d.kind,
82
+ pluginGroup: d.group,
83
+ groupId: groupIdFor(d.group, d.groupLabel),
84
+ tag: d.tag ?? null,
85
+ classification,
86
+ classificationSource,
87
+ inferredClassification: d.classification,
88
+ inferredReason: d.classificationReason,
89
+ locked,
90
+ // A locked operation always needs the typed confirmation, whatever the plugin says.
91
+ typedConfirmation: locked || (d.typedConfirmation ?? false),
92
+ needsReview: d.needsReview,
93
+ matchProfile: d.matchProfile ?? null,
94
+ paramsSchema: d.paramsSchema ?? null,
95
+ docs: d.docs ?? null,
96
+ lastSeenAt: now,
97
+ stale: false,
98
+ };
99
+ const nowWrite = isEffectiveWrite({ classification, locked });
100
+ if (!prev) {
101
+ tx.insert(operations)
102
+ .values({
103
+ id: randomUUID(),
104
+ instanceId,
105
+ key: d.key,
106
+ ...fields,
107
+ attestationRequired: d.attestationRequired,
108
+ firstSeenAt: now,
109
+ })
110
+ .run();
111
+ summary.added++;
112
+ if (nowWrite)
113
+ summary.pendingReview.push(d.key);
114
+ continue;
115
+ }
116
+ const becameLocked = locked && !prev.locked;
117
+ const changed = fingerprint(prev) !== fingerprint(fields) || prev.stale;
118
+ const becameWrite = nowWrite && (!isEffectiveWrite(prev) || (prev.writeAcknowledged && changed));
119
+ tx.update(operations)
120
+ .set({
121
+ ...fields,
122
+ // The plugin can add the attestation requirement; only an admin can remove it (and then it stays off).
123
+ attestationRequired: prev.attestationRequired || (d.attestationRequired && !prev.attestationWaived),
124
+ ...(becameWrite ? { writeAcknowledged: false, acknowledgedAt: null, acknowledgedBy: null } : {}),
125
+ // A newly locked op starts closed again: it needs its own `ask` level to be callable.
126
+ ...(becameLocked ? { levelOverride: null } : {}),
127
+ })
128
+ .where(eq(operations.id, prev.id))
129
+ .run();
130
+ summary.updated++;
131
+ if (prev.stale)
132
+ summary.restored++;
133
+ if (becameWrite)
134
+ summary.pendingReview.push(d.key);
135
+ if (becameLocked)
136
+ summary.newlyLocked.push(prev.id);
137
+ }
138
+ const missing = [...existing.values()].filter((o) => !seen.has(o.key) && !o.stale).map((o) => o.id);
139
+ if (missing.length > 0) {
140
+ tx.update(operations).set({ stale: true }).where(inArray(operations.id, missing)).run();
141
+ summary.staled = missing.length;
142
+ }
143
+ if (summary.newlyLocked.length > 0) {
144
+ summary.rulesDisabled = tx
145
+ .update(preApprovalRules)
146
+ .set({ enabled: false, updatedAt: now })
147
+ .where(and(inArray(preApprovalRules.operationId, summary.newlyLocked), eq(preApprovalRules.enabled, true)))
148
+ .run().changes;
149
+ }
150
+ // Rules name fields of the operation's match profile; a plugin update can remove or retype them.
151
+ const misfits = [];
152
+ if (manifest) {
153
+ const enabled = tx
154
+ .select({ rule: preApprovalRules, op: operations })
155
+ .from(preApprovalRules)
156
+ .innerJoin(operations, eq(preApprovalRules.operationId, operations.id))
157
+ .where(and(eq(preApprovalRules.instanceId, instanceId), eq(preApprovalRules.enabled, true)))
158
+ .all();
159
+ for (const { rule, op } of enabled) {
160
+ const match = MatchSchema.safeParse(rule.match);
161
+ const misfit = match.success ? matchMisfit(manifest, op, match.data) : match.error;
162
+ if (!misfit)
163
+ continue;
164
+ tx.update(preApprovalRules)
165
+ .set({ enabled: false, updatedAt: now })
166
+ .where(eq(preApprovalRules.id, rule.id))
167
+ .run();
168
+ misfits.push({ ruleId: rule.id, operationKey: op.key, reason: misfit.message });
169
+ }
170
+ summary.rulesDisabled += misfits.length;
171
+ }
172
+ // A group is stale when none of its operations are live; its level is kept in case they return.
173
+ const liveGroupIds = new Set(tx
174
+ .select({ groupId: operations.groupId })
175
+ .from(operations)
176
+ .where(and(eq(operations.instanceId, instanceId), eq(operations.stale, false)))
177
+ .all()
178
+ .map((r) => r.groupId));
179
+ for (const g of groups.values()) {
180
+ const stale = !liveGroupIds.has(g.id);
181
+ if (stale !== g.stale)
182
+ tx.update(operationGroups).set({ stale }).where(eq(operationGroups.id, g.id)).run();
183
+ }
184
+ tx.update(pluginInstances)
185
+ .set({
186
+ upstreamVersion: result.upstreamVersion,
187
+ sourceRef: result.sourceRef ?? null,
188
+ lastSyncedAt: now,
189
+ lastSyncStatus: 'ok',
190
+ })
191
+ .where(eq(pluginInstances.id, instanceId))
192
+ .run();
193
+ const newlyLockedKeys = [...existing.values()].filter((o) => summary.newlyLocked.includes(o.id)).map((o) => o.key);
194
+ writeAudit(tx, {
195
+ kind: 'plugin',
196
+ instanceId,
197
+ decision: 'catalog_synced',
198
+ actorKind: 'system',
199
+ detail: { ...summary, newlyLocked: newlyLockedKeys, upstreamVersion: result.upstreamVersion },
200
+ }, now);
201
+ if (misfits.length > 0) {
202
+ writeAudit(tx, {
203
+ kind: 'config',
204
+ instanceId,
205
+ decision: 'rules_disabled_operation_changed',
206
+ actorKind: 'system',
207
+ detail: { rules: misfits },
208
+ }, now);
209
+ }
210
+ if (summary.newlyLocked.length > 0 && summary.rulesDisabled > misfits.length) {
211
+ writeAudit(tx, {
212
+ kind: 'config',
213
+ instanceId,
214
+ decision: 'rules_disabled_operation_locked',
215
+ actorKind: 'system',
216
+ detail: { operations: newlyLockedKeys, rulesDisabled: summary.rulesDisabled - misfits.length },
217
+ }, now);
218
+ }
219
+ return { ...summary, newlyLocked: newlyLockedKeys };
220
+ });
221
+ }
222
+ //# sourceMappingURL=sync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync.js","sourceRoot":"","sources":["../../src/catalog/sync.ts"],"names":[],"mappings":"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"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export declare function rotateMasterKeyCommand(env: NodeJS.ProcessEnv, log?: (line: string) => void, opts?: {
2
+ force?: boolean;
3
+ }): import("./crypto/rotate.js").RotationResult;
package/dist/cli.js ADDED
@@ -0,0 +1,83 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ import { existsSync, renameSync, rmSync, writeFileSync } from 'node:fs';
3
+ import path from 'node:path';
4
+ import { pathToFileURL } from 'node:url';
5
+ import { loadConfig } from './config/env.js';
6
+ import { loadMasterKey, MASTER_KEY_FILENAME, SecretBox } from './crypto/index.js';
7
+ import { rotateSecrets } from './crypto/rotate.js';
8
+ import { openDatabase } from './db/index.js';
9
+ import { assertServerStopped } from './lock.js';
10
+ /**
11
+ * Operator commands, run with the server stopped:
12
+ *
13
+ * node dist/cli.js rotate-master-key [--force]
14
+ *
15
+ * With `MASTER_KEY` set, the new key must be supplied as `NEW_MASTER_KEY` (so it exists before any
16
+ * data depends on it); update `MASTER_KEY` to it afterwards. With a key file, a new key is generated,
17
+ * written next to it as `master.key.new` before the database changes, then moved into place.
18
+ */
19
+ const USAGE = 'Usage: cli.js rotate-master-key [--force] (stop the server first; --force skips the running-server check)';
20
+ export function rotateMasterKeyCommand(env, log = console.log, opts = {}) {
21
+ const config = loadConfig(env);
22
+ assertServerStopped(config.DATA_DIR, opts);
23
+ const keyFile = path.join(config.DATA_DIR, MASTER_KEY_FILENAME);
24
+ if (!config.MASTER_KEY && !existsSync(keyFile))
25
+ throw new Error(`No master key: MASTER_KEY is unset and ${keyFile} does not exist`);
26
+ const current = loadMasterKey({ envKey: config.MASTER_KEY, dataDir: config.DATA_DIR });
27
+ let next;
28
+ if (env.NEW_MASTER_KEY) {
29
+ next = Buffer.from(env.NEW_MASTER_KEY.trim(), 'base64');
30
+ if (next.length !== 32)
31
+ throw new Error('NEW_MASTER_KEY must be 32 bytes of base64 (e.g. `openssl rand -base64 32`)');
32
+ }
33
+ else if (current.source === 'env') {
34
+ throw new Error('MASTER_KEY comes from the environment: set NEW_MASTER_KEY to the replacement key');
35
+ }
36
+ else {
37
+ next = randomBytes(32);
38
+ }
39
+ if (next.equals(current.key))
40
+ throw new Error('The new key is the same as the current key');
41
+ const db = openDatabase({ dataDir: config.DATA_DIR });
42
+ const pending = `${keyFile}.new`;
43
+ let committed = false;
44
+ try {
45
+ if (current.source === 'file')
46
+ writeFileSync(pending, `${next.toString('base64')}\n`, { mode: 0o600 });
47
+ const result = rotateSecrets(db, SecretBox.fromKey(current.key), SecretBox.fromKey(next));
48
+ committed = true;
49
+ // If this rename fails, master.key.new holds the only key that opens the database now.
50
+ if (current.source === 'file')
51
+ renameSync(pending, keyFile);
52
+ log(`Re-encrypted ${result.instances} endpoint connection(s), ${result.notifiers} notifier(s), ` +
53
+ `${result.users} TOTP secret(s) and ${result.oidc} OIDC secret(s); signed out ${result.sessionsCleared} session(s).`);
54
+ log(current.source === 'file'
55
+ ? `New key written to ${keyFile}.`
56
+ : 'Now set MASTER_KEY to the value of NEW_MASTER_KEY.');
57
+ return result;
58
+ }
59
+ catch (err) {
60
+ if (current.source === 'file' && !committed)
61
+ rmSync(pending, { force: true });
62
+ throw err;
63
+ }
64
+ finally {
65
+ db.$client.close();
66
+ }
67
+ }
68
+ if (import.meta.url === pathToFileURL(process.argv[1] ?? '').href) {
69
+ const [command, ...flags] = process.argv.slice(2);
70
+ try {
71
+ if (command === 'rotate-master-key' && flags.every((f) => f === '--force'))
72
+ rotateMasterKeyCommand(process.env, console.log, { force: flags.includes('--force') });
73
+ else {
74
+ console.error(USAGE);
75
+ process.exitCode = 2;
76
+ }
77
+ }
78
+ catch (err) {
79
+ console.error(`Error: ${err instanceof Error ? err.message : err}`);
80
+ process.exitCode = 1;
81
+ }
82
+ }
83
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACxE,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAClF,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAEhD;;;;;;;;GAQG;AAEH,MAAM,KAAK,GACT,6GAA6G,CAAC;AAEhH,MAAM,UAAU,sBAAsB,CACpC,GAAsB,EACtB,MAA8B,OAAO,CAAC,GAAG,EACzC,OAA4B,EAAE;IAE9B,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;IAC/B,mBAAmB,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAC3C,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,mBAAmB,CAAC,CAAC;IAChE,IAAI,CAAC,MAAM,CAAC,UAAU,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;QAC5C,MAAM,IAAI,KAAK,CAAC,0CAA0C,OAAO,iBAAiB,CAAC,CAAC;IACtF,MAAM,OAAO,GAAG,aAAa,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,UAAU,EAAE,OAAO,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;IAEvF,IAAI,IAAY,CAAC;IACjB,IAAI,GAAG,CAAC,cAAc,EAAE,CAAC;QACvB,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,EAAE,EAAE,QAAQ,CAAC,CAAC;QACxD,IAAI,IAAI,CAAC,MAAM,KAAK,EAAE;YACpB,MAAM,IAAI,KAAK,CAAC,4EAA4E,CAAC,CAAC;IAClG,CAAC;SAAM,IAAI,OAAO,CAAC,MAAM,KAAK,KAAK,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CAAC,kFAAkF,CAAC,CAAC;IACtG,CAAC;SAAM,CAAC;QACN,IAAI,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;IACzB,CAAC;IACD,IAAI,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;IAE5F,MAAM,EAAE,GAAG,YAAY,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;IACtD,MAAM,OAAO,GAAG,GAAG,OAAO,MAAM,CAAC;IACjC,IAAI,SAAS,GAAG,KAAK,CAAC;IACtB,IAAI,CAAC;QACH,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM;YAAE,aAAa,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QACvG,MAAM,MAAM,GAAG,aAAa,CAAC,EAAE,EAAE,SAAS,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QAC1F,SAAS,GAAG,IAAI,CAAC;QACjB,uFAAuF;QACvF,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM;YAAE,UAAU,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC5D,GAAG,CACD,gBAAgB,MAAM,CAAC,SAAS,4BAA4B,MAAM,CAAC,SAAS,gBAAgB;YAC1F,GAAG,MAAM,CAAC,KAAK,uBAAuB,MAAM,CAAC,IAAI,+BAA+B,MAAM,CAAC,eAAe,cAAc,CACvH,CAAC;QACF,GAAG,CACD,OAAO,CAAC,MAAM,KAAK,MAAM;YACvB,CAAC,CAAC,sBAAsB,OAAO,GAAG;YAClC,CAAC,CAAC,oDAAoD,CACzD,CAAC;QACF,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM,IAAI,CAAC,SAAS;YAAE,MAAM,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAC9E,MAAM,GAAG,CAAC;IACZ,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;IACrB,CAAC;AACH,CAAC;AAED,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAClE,MAAM,CAAC,OAAO,EAAE,GAAG,KAAK,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAClD,IAAI,CAAC;QACH,IAAI,OAAO,KAAK,mBAAmB,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC;YACxE,sBAAsB,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;aACpF,CAAC;YACJ,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YACrB,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACvB,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,UAAU,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;QACpE,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;IACvB,CAAC;AACH,CAAC"}
@@ -0,0 +1,40 @@
1
+ import { z } from 'zod';
2
+ declare const EnvSchema: z.ZodObject<{
3
+ DATA_DIR: z.ZodDefault<z.ZodString>;
4
+ MASTER_KEY: z.ZodOptional<z.ZodString>;
5
+ MCP_HOST: z.ZodDefault<z.ZodString>;
6
+ MCP_PORT: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
7
+ ADMIN_HOST: z.ZodDefault<z.ZodString>;
8
+ ADMIN_PORT: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
9
+ PUBLIC_MCP_URL: z.ZodPipe<z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>, z.ZodOptional<z.ZodURL>>;
10
+ PUBLIC_ADMIN_URL: z.ZodPipe<z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>, z.ZodOptional<z.ZodURL>>;
11
+ TRUST_PROXY: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<number, string | undefined>>;
12
+ MCP_ALLOWED_HOSTS: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string[], string | undefined>>;
13
+ CORE_PLUGINS_AUTOENABLE: z.ZodPipe<z.ZodOptional<z.ZodEnum<{
14
+ "": "";
15
+ 0: "0";
16
+ 1: "1";
17
+ true: "true";
18
+ false: "false";
19
+ }>>, z.ZodTransform<boolean, "" | "0" | "1" | "true" | "false" | undefined>>;
20
+ CORE_PLUGINS_DIR: z.ZodDefault<z.ZodString>;
21
+ ADMIN_UI_DIR: z.ZodDefault<z.ZodString>;
22
+ ADMIN_BOOTSTRAP_USERNAME: z.ZodOptional<z.ZodString>;
23
+ ADMIN_BOOTSTRAP_PASSWORD: z.ZodOptional<z.ZodString>;
24
+ ADMIN_FORCE_LOCAL_LOGIN: z.ZodPipe<z.ZodOptional<z.ZodEnum<{
25
+ "": "";
26
+ 0: "0";
27
+ 1: "1";
28
+ true: "true";
29
+ false: "false";
30
+ }>>, z.ZodTransform<boolean, "" | "0" | "1" | "true" | "false" | undefined>>;
31
+ LOG_LEVEL: z.ZodDefault<z.ZodEnum<{
32
+ error: "error";
33
+ debug: "debug";
34
+ info: "info";
35
+ warn: "warn";
36
+ }>>;
37
+ }, z.core.$strip>;
38
+ export type Config = z.infer<typeof EnvSchema>;
39
+ export declare function loadConfig(env?: NodeJS.ProcessEnv): Config;
40
+ export {};