@classytic/arc 2.25.0 → 2.30.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 (92) hide show
  1. package/README.md +2 -0
  2. package/dist/{BaseController--M8EMzt0.mjs → BaseController-B5DwufbB.mjs} +15 -11
  3. package/dist/EventTransport-C6YA8xY9.mjs +103 -0
  4. package/dist/{HookSystem-C1vB_Gc9.mjs → HookSystem-WkIougo6.mjs} +15 -35
  5. package/dist/applyPermissionResult-Cf6zUCvb.mjs +257 -0
  6. package/dist/audit/index.d.mts +2 -2
  7. package/dist/audit/index.mjs +1 -1
  8. package/dist/auth/index.d.mts +2 -2
  9. package/dist/auth/index.mjs +1 -1
  10. package/dist/{buildHandler-Dtwd8fdz.mjs → buildHandler-BYS3Vkfz.mjs} +32 -6
  11. package/dist/cleanup/index.d.mts +313 -0
  12. package/dist/cleanup/index.mjs +1267 -0
  13. package/dist/cli/commands/describe.d.mts +1 -1
  14. package/dist/cli/commands/docs.mjs +1 -1
  15. package/dist/core/index.d.mts +2 -2
  16. package/dist/core/index.mjs +5 -3
  17. package/dist/core-poalJLvp.mjs +175 -0
  18. package/dist/{createActionRouter-B6GvAuAX.mjs → createActionRouter-BxuywVRB.mjs} +2 -2
  19. package/dist/{createAggregationRouter-wzwFa_dW.mjs → createAggregationRouter-BiY-lGdY.mjs} +7 -20
  20. package/dist/{preloadResources-DfyngIN9.mjs → createApp-CXrKFciH.mjs} +25 -76
  21. package/dist/defineAggregation-ufWjUwml.mjs +47 -0
  22. package/dist/{core-EUNu0u07.mjs → defineResource-CxWqDCAy.mjs} +36 -261
  23. package/dist/docs/index.d.mts +1 -1
  24. package/dist/docs/index.mjs +1 -1
  25. package/dist/{eventPlugin-DrQ_FesK.mjs → eventPlugin-D2NNR4py.mjs} +2 -103
  26. package/dist/events/index.d.mts +28 -2
  27. package/dist/events/index.mjs +24 -343
  28. package/dist/factory/index.d.mts +4 -3
  29. package/dist/factory/index.mjs +3 -1
  30. package/dist/{fastify-augmentation-dPNBVeIj.d.mts → fastify-augmentation-0WhPnUH7.d.mts} +3 -2
  31. package/dist/{applyPermissionResult-Tw4iXtlf.mjs → fields-CQ_7KQWw.mjs} +1 -97
  32. package/dist/hooks/index.d.mts +1 -1
  33. package/dist/hooks/index.mjs +1 -1
  34. package/dist/idempotency/index.mjs +1 -1
  35. package/dist/index-CCzVR-N3.d.mts +107 -0
  36. package/dist/{index-BMnbzvtO.d.mts → index-Ch16aAah.d.mts} +201 -21
  37. package/dist/{index-f4PTrk3B.d.mts → index-Cx7bro4R.d.mts} +4 -4
  38. package/dist/{index-DPRH0Cme.d.mts → index-Q5gzEIWY.d.mts} +97 -66
  39. package/dist/index.d.mts +6 -6
  40. package/dist/index.mjs +8 -6
  41. package/dist/integrations/index.d.mts +1 -1
  42. package/dist/integrations/mcp/index.d.mts +6 -6
  43. package/dist/integrations/mcp/index.mjs +1 -1
  44. package/dist/integrations/mcp/testing.d.mts +1 -1
  45. package/dist/integrations/mcp/testing.mjs +1 -1
  46. package/dist/{mcpPlugin-Cb7olw0j.mjs → mcpPlugin-nRzm5g2s.mjs} +36 -43
  47. package/dist/middleware/index.d.mts +1 -1
  48. package/dist/migrations/index.d.mts +0 -3
  49. package/dist/migrations/index.mjs +0 -3
  50. package/dist/module-_-M7C5ES.mjs +29 -0
  51. package/dist/{openapi-DrLtog_B.mjs → openapi-B8Vkbxbn.mjs} +1 -1
  52. package/dist/permissions/index.d.mts +3 -3
  53. package/dist/permissions/index.mjs +4 -3
  54. package/dist/{permissions-BrtiBKGs.mjs → permissions-Bfu_8y1U.mjs} +318 -289
  55. package/dist/pipeline/index.d.mts +1 -1
  56. package/dist/{pipeline-BN5G05rd.mjs → pipeline-BJnpkY9i.mjs} +4 -18
  57. package/dist/plugins/index.d.mts +4 -4
  58. package/dist/plugins/index.mjs +2 -2
  59. package/dist/plugins/tracing-entry.mjs +1 -1
  60. package/dist/{preloadResources-0oe-JSSS.d.mts → preloadResources-DCfa2egz.d.mts} +3 -2
  61. package/dist/preloadResources-DSY5HGpS.mjs +45 -0
  62. package/dist/presets/filesUpload.d.mts +2 -2
  63. package/dist/presets/filesUpload.mjs +1 -1
  64. package/dist/presets/index.d.mts +14 -2
  65. package/dist/presets/index.mjs +1 -1
  66. package/dist/presets/multiTenant.d.mts +1 -1
  67. package/dist/presets/search.d.mts +2 -2
  68. package/dist/presets/search.mjs +1 -1
  69. package/dist/{presets-DwFDVKP0.mjs → presets-CJOEXLuk.mjs} +25 -9
  70. package/dist/projection-UBDgfMwu.mjs +17 -0
  71. package/dist/{realtime-Cuw_deA-.mjs → realtime-B9nmIY5V.mjs} +2 -1
  72. package/dist/{registerRoute-v0Wb6Plt.mjs → registerRoute-JkxIEBJq.mjs} +3 -1
  73. package/dist/registry/index.d.mts +2 -2
  74. package/dist/registry/index.mjs +1 -1
  75. package/dist/{registry-vcOCtPij.mjs → registry-BYXUUg21.mjs} +16 -1
  76. package/dist/repositoryFilter-Dz8k3S4P.mjs +44 -0
  77. package/dist/subscribe-helpers-DfzsivM6.mjs +339 -0
  78. package/dist/testing/cleanupStoreContract.d.mts +10 -0
  79. package/dist/testing/cleanupStoreContract.mjs +375 -0
  80. package/dist/testing/index.d.mts +34 -5
  81. package/dist/testing/index.mjs +316 -211
  82. package/dist/testing/outboxStoreContract.d.mts +13 -0
  83. package/dist/testing/outboxStoreContract.mjs +247 -0
  84. package/dist/types/index.d.mts +3 -3
  85. package/dist/{types-QFGc3Cjv.d.mts → types-Bmmn-fO0.d.mts} +1 -1
  86. package/dist/types-C_ZZRcD6.d.mts +450 -0
  87. package/dist/{types-Dmw-6Qeo.d.mts → types-ChvvVNP8.d.mts} +93 -88
  88. package/dist/{index-KUoxjIq1.d.mts → types-D_n8L6VY.d.mts} +33 -106
  89. package/dist/usage/index.d.mts +1 -1
  90. package/dist/usage/index.mjs +0 -0
  91. package/dist/utils/index.d.mts +2 -5
  92. package/package.json +539 -524
@@ -1,3 +1,5 @@
1
+ import { t as toRepositoryFilter } from "./repositoryFilter-Dz8k3S4P.mjs";
2
+ import { o as conjoinPolicyFilters } from "./applyPermissionResult-Cf6zUCvb.mjs";
1
3
  import { i as compileAggRequest, t as adapterSupportsAggregate } from "./validate-Bk44F2yT.mjs";
2
4
  //#region src/core/aggregation/buildHandler.ts
3
5
  /**
@@ -18,13 +20,13 @@ async function executeAggregation(normalized, deps, ctx) {
18
20
  const { repo } = deps;
19
21
  const config = normalized.base;
20
22
  const aggregationName = normalized.name;
21
- const { query, tenantOptions } = ctx;
23
+ const { query, tenantOptions, policyFilter } = ctx;
22
24
  const guardError = checkRequestGuards(query, config);
23
25
  if (guardError) return {
24
26
  status: 400,
25
27
  body: guardError
26
28
  };
27
- const aggReq = compileAggRequest(normalized, extractCallerFilter(query), tenantOptions, query);
29
+ const aggReq = compileAggRequest(normalized, conjoinPolicyFilters(extractCallerFilter(query), policyFilter), tenantOptions, query);
28
30
  if (config.materialized) {
29
31
  const matCtx = {
30
32
  filter: aggReq.filter,
@@ -51,9 +53,13 @@ async function executeAggregation(normalized, deps, ctx) {
51
53
  }
52
54
  }
53
55
  };
56
+ const aggReqForRepo = aggReq.filter && typeof aggReq.filter === "object" ? {
57
+ ...aggReq,
58
+ filter: toRepositoryFilter(aggReq.filter)
59
+ } : aggReq;
54
60
  let result;
55
61
  try {
56
- result = await repo.aggregate(aggReq, tenantOptions);
62
+ result = await repo.aggregate(aggReqForRepo, tenantOptions);
57
63
  } catch (err) {
58
64
  return mapAggregateError(err, aggregationName);
59
65
  }
@@ -95,9 +101,13 @@ async function executeAggregation(normalized, deps, ctx) {
95
101
  function buildAggregationHandler(normalized, deps) {
96
102
  const { buildOptions } = deps;
97
103
  return async (request, reply) => {
104
+ const query = request.query ?? {};
105
+ const tenantOptions = buildOptions(request);
106
+ const policyFilter = request._policyFilters;
98
107
  const result = await executeAggregation(normalized, deps, {
99
- query: request.query ?? {},
100
- tenantOptions: buildOptions(request)
108
+ query,
109
+ tenantOptions,
110
+ policyFilter
101
111
  });
102
112
  reply.status(result.status);
103
113
  if (result.headers) for (const [k, v] of Object.entries(result.headers)) reply.header(k, v);
@@ -199,6 +209,20 @@ const SHORTHAND_RANGE_OPS = /* @__PURE__ */ new Set([
199
209
  "lt",
200
210
  "lte"
201
211
  ]);
212
+ /**
213
+ * Operators whose operand MUST be a list. A URL carries `priority[in]=high,urgent`
214
+ * as ONE string, and `$in` against a non-array is invalid — MongoDB rejects it,
215
+ * and the portable Filter IR would spread the string into characters. Split it
216
+ * the same way `ArcQueryParser` does for the CRUD list route, which is the
217
+ * contract this expansion claims to match.
218
+ */
219
+ const SHORTHAND_LIST_OPS = /* @__PURE__ */ new Set(["in", "nin"]);
220
+ /** Normalize a list-operator operand to an array (URL string → split on commas). */
221
+ function toOperandList(value) {
222
+ if (Array.isArray(value)) return value;
223
+ if (typeof value === "string") return value.split(",").map((v) => v.trim()).filter((v) => v.length > 0);
224
+ return [value];
225
+ }
202
226
  const ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?)?$/;
203
227
  function tryCoerceDate(v) {
204
228
  if (typeof v !== "string" || !ISO_DATE_RE.test(v)) return v;
@@ -222,7 +246,9 @@ function expandShorthandOperators(value) {
222
246
  for (const [op, opVal] of Object.entries(nested)) {
223
247
  const mongoOp = OPERATOR_SHORTHAND[op];
224
248
  if (!mongoOp) continue;
225
- expanded[mongoOp] = SHORTHAND_RANGE_OPS.has(op) ? tryCoerceDate(opVal) : opVal;
249
+ if (SHORTHAND_RANGE_OPS.has(op)) expanded[mongoOp] = tryCoerceDate(opVal);
250
+ else if (SHORTHAND_LIST_OPS.has(op)) expanded[mongoOp] = toOperandList(opVal);
251
+ else expanded[mongoOp] = opVal;
226
252
  }
227
253
  return expanded;
228
254
  }
@@ -0,0 +1,313 @@
1
+ import { L as RequestWithExtras } from "../index-Q5gzEIWY.mjs";
2
+ import { A as CleanupWriteFence, C as CleanupResult, D as CleanupRunStore, E as CleanupRunStatus, F as PurgeStrategyKind, I as PurgeVerificationSummary, L as VerificationCheck, M as PurgeActor, N as PurgeEvidence, O as CleanupRunTransitionPatch, P as PurgeResourceResult, R as VerificationResult, S as CleanupRecipe, T as CleanupRunCreateResult, _ as CleanupPermissions, a as CleanupEvidenceStore, b as CleanupPlanItem, c as CleanupInput, d as CleanupLease, f as CleanupLimits, g as CleanupPermissionCheck, h as CleanupOutcomeStatus, i as CleanupContext, j as DEFAULT_CLEANUP_LIMITS, k as CleanupStepResult, l as CleanupJob, m as CleanupManifest, n as CLEANUP_TERMINAL_STATUSES, o as CleanupExecutionContext, p as CleanupLogger, r as CleanupCancelRequest, s as CleanupFinalizationPayload, t as Availability, u as CleanupJobQueue, v as CleanupPlan, w as CleanupRun, x as CleanupProgressSummary, y as CleanupPlanDraft } from "../types-C_ZZRcD6.mjs";
3
+ import { t as ArcModule } from "../types-D_n8L6VY.mjs";
4
+ import { PurgeStrategyKind as PurgeStrategyKind$1 } from "@classytic/primitives/retention";
5
+ import { canonicalJson } from "@classytic/primitives/canonical";
6
+ import { CleanupStep } from "@classytic/repo-core/cleanup";
7
+ //#region src/cleanup/compose.d.ts
8
+ /** Metadata + steps for {@link recipeFromSteps}. */
9
+ interface RecipeFromStepsInput {
10
+ /** Stable machine id, unique in the registry (e.g. `'cleanup.rebuild-projections'`). */
11
+ readonly id: string;
12
+ /** Plain-language label for the UI. */
13
+ readonly label: string;
14
+ /**
15
+ * Whether this recipe removes data. Defaults to `true` iff ANY step is
16
+ * destructive (a pure-rebuild recipe is non-destructive).
17
+ */
18
+ readonly destructive?: boolean;
19
+ /** Recipe/plan-schema version (persisted on the run). Defaults to `'1'`. */
20
+ readonly version?: string;
21
+ /**
22
+ * Strategy the run's evidence records. Defaults to `'hard'` when any step is
23
+ * destructive, `'soft'` otherwise (pure rebuilds remove no source data).
24
+ */
25
+ readonly evidenceStrategy?: PurgeStrategyKind$1;
26
+ /** Exact confirmation phrase; defaults (in the service) to the recipe id. */
27
+ readonly confirmationPhrase?: string;
28
+ /** Extra top-level retains beyond per-step `retained` lines. */
29
+ readonly retains?: readonly string[];
30
+ /** Extra top-level warnings beyond per-step warnings. */
31
+ readonly warnings?: readonly string[];
32
+ /**
33
+ * Recipe-level availability gate — go-live boundary, feature flag, etc.
34
+ * STEPS never decide availability (that is host policy). Defaults to always
35
+ * available.
36
+ */
37
+ available?(ctx: CleanupContext): Promise<Availability>;
38
+ /** Provider steps in EXECUTION ORDER. */
39
+ readonly steps: readonly CleanupStep[];
40
+ }
41
+ declare function recipeFromSteps(input: RecipeFromStepsInput): CleanupRecipe;
42
+ //#endregion
43
+ //#region src/cleanup/errors.d.ts
44
+ /**
45
+ * Data Cleanup Center — stable, typed errors.
46
+ *
47
+ * Every failure the framework raises carries a stable machine `code` (so
48
+ * SDK/UI can branch) and an HTTP `status` (so the Arc resource maps cleanly).
49
+ * The codes are part of the public contract — do not rename.
50
+ */
51
+ type CleanupErrorCode =
52
+ /** No recipe registered under the requested id. */
53
+ "CLEANUP_UNKNOWN_RECIPE" |
54
+ /** Two recipes share an id at registry construction. */
55
+ "CLEANUP_DUPLICATE_RECIPE" |
56
+ /** The recipe's `available()` returned false. */
57
+ "CLEANUP_RECIPE_UNAVAILABLE" |
58
+ /** Execute called without the exact confirmation phrase. */
59
+ "CLEANUP_CONFIRMATION_REQUIRED" |
60
+ /** Execute called with an empty reason. */
61
+ "CLEANUP_REASON_REQUIRED" |
62
+ /** The re-planned digest differs from the client's — a new preview is required. */
63
+ "CLEANUP_PLAN_CHANGED" |
64
+ /** The plan carries unresolved blockers — a hard stop (§4.4). */
65
+ "CLEANUP_BLOCKED" |
66
+ /** The plan exceeds a framework size limit (items / results / reason / depth). */
67
+ "CLEANUP_PLAN_TOO_LARGE" |
68
+ /** A destructive run is already in progress (single-run fence, §8). */
69
+ "CLEANUP_ALREADY_RUNNING" |
70
+ /** The run id does not exist. */
71
+ "CLEANUP_RUN_NOT_FOUND" |
72
+ /** The action isn't valid for the run's current status (lost a CAS, etc.). */
73
+ "CLEANUP_INVALID_ACTION" |
74
+ /** No authenticated actor could be resolved for a destructive action (fail-closed). */
75
+ "CLEANUP_ACTOR_REQUIRED" |
76
+ /** Caller lacks permission for the operation. */
77
+ "CLEANUP_FORBIDDEN";
78
+ /**
79
+ * A framework error carrying BOTH `status` (read directly in code/tests) and
80
+ * `statusCode` (the Fastify convention arc's global error handler recognizes,
81
+ * step 4 of its classifier — a numeric `statusCode` + a separatored domain
82
+ * `code` maps to that exact status + code on the wire, never `arc.internal_error`).
83
+ */
84
+ declare class CleanupError extends Error {
85
+ readonly code: CleanupErrorCode;
86
+ readonly status: number;
87
+ /** Alias of {@link status} — the property arc's error handler classifies on. */
88
+ readonly statusCode: number;
89
+ readonly meta?: Record<string, unknown>;
90
+ constructor(code: CleanupErrorCode, status: number, message: string, meta?: Record<string, unknown>);
91
+ }
92
+ /**
93
+ * Internal control-flow signal thrown by `throwIfCancelled()` when a run's
94
+ * durable `cancelRequested` flag is set. NOT a wire error — `processRun`
95
+ * catches it and CAS-transitions the run to `cancelled`.
96
+ */
97
+ declare class CleanupCancelled extends Error {
98
+ readonly runId: string;
99
+ constructor(runId: string);
100
+ }
101
+ declare const CleanupErrors: {
102
+ readonly unknownRecipe: (id: string) => CleanupError;
103
+ readonly duplicateRecipe: (id: string) => CleanupError;
104
+ readonly unavailable: (id: string, reason: string) => CleanupError;
105
+ readonly confirmationRequired: (phrase: string) => CleanupError;
106
+ readonly reasonRequired: () => CleanupError;
107
+ readonly blocked: (blockers: readonly string[]) => CleanupError;
108
+ readonly planTooLarge: (detail: string) => CleanupError;
109
+ readonly actorRequired: () => CleanupError;
110
+ readonly planChanged: (expected: string, actual: string) => CleanupError;
111
+ readonly alreadyRunning: (runId: string) => CleanupError;
112
+ readonly runNotFound: (id: string) => CleanupError;
113
+ readonly invalidAction: (action: string, status: string) => CleanupError;
114
+ readonly forbidden: (operation: string) => CleanupError;
115
+ };
116
+ //#endregion
117
+ //#region src/cleanup/memory.d.ts
118
+ /**
119
+ * In-memory {@link CleanupRunStore}. Single-process only — see the module
120
+ * docblock for the production caveat.
121
+ */
122
+ declare class MemoryCleanupRunStore implements CleanupRunStore {
123
+ #private;
124
+ /** Live run state, keyed by run id. Exposed for tests/inspection. */
125
+ readonly runs: Map<string, CleanupRun>;
126
+ constructor(now?: () => Date);
127
+ createIfPermitted(run: CleanupRun): Promise<CleanupRunCreateResult>;
128
+ get(id: string): Promise<CleanupRun | null>;
129
+ claim(id: string, lease: CleanupLease): Promise<CleanupRun | null>;
130
+ compareAndTransition(id: string, expected: readonly CleanupRunStatus[], to: CleanupRunStatus, patch?: CleanupRunTransitionPatch, leaseToken?: string): Promise<CleanupRun | null>;
131
+ reArmIfPermitted(id: string, patch: CleanupRunTransitionPatch): Promise<CleanupRun | null>;
132
+ requestCancel(id: string, request?: CleanupCancelRequest): Promise<CleanupRun | null>;
133
+ saveProgress(id: string, progress: CleanupProgressSummary, lease?: CleanupLease): Promise<void>;
134
+ }
135
+ /**
136
+ * In-memory {@link CleanupEvidenceStore} — idempotent by
137
+ * `evidence.operationId`, exactly as the port requires.
138
+ */
139
+ declare class MemoryCleanupEvidenceStore implements CleanupEvidenceStore {
140
+ #private;
141
+ readonly evidence: PurgeEvidence[];
142
+ readonly manifests: CleanupManifest[];
143
+ finalize(input: {
144
+ evidence: PurgeEvidence;
145
+ manifest: CleanupManifest;
146
+ }): Promise<void>;
147
+ }
148
+ /**
149
+ * A MANUAL in-memory {@link CleanupJobQueue}: it records enqueued jobs and runs
150
+ * nothing on its own, so a test drives the worker deterministically
151
+ * (`for (const job of queue.jobs) await service.processRun(job.runId)`).
152
+ *
153
+ * Note this is NOT the service's default queue — omitting `jobQueue` entirely
154
+ * gives you a microtask-deferred in-process queue that DOES auto-run
155
+ * `processRun` off the request path.
156
+ */
157
+ declare class MemoryCleanupJobQueue implements CleanupJobQueue {
158
+ readonly jobs: CleanupJob[];
159
+ enqueue(job: CleanupJob): Promise<void>;
160
+ /** Take everything enqueued so far, clearing the buffer. */
161
+ drain(): CleanupJob[];
162
+ }
163
+ //#endregion
164
+ //#region src/cleanup/registry.d.ts
165
+ /** UI/SDK-facing description of one recipe (no methods). */
166
+ interface CleanupRecipeInfo {
167
+ readonly id: string;
168
+ readonly label: string;
169
+ readonly destructive: boolean;
170
+ }
171
+ interface CleanupRegistry {
172
+ /** Get a recipe by id, or throw `CLEANUP_UNKNOWN_RECIPE`. */
173
+ get(id: string): CleanupRecipe;
174
+ /** Get a recipe by id, or `undefined`. */
175
+ find(id: string): CleanupRecipe | undefined;
176
+ /** Every registered recipe. */
177
+ all(): readonly CleanupRecipe[];
178
+ /** Introspection payload for UI/SDK recipe cards. */
179
+ introspect(): readonly CleanupRecipeInfo[];
180
+ }
181
+ /**
182
+ * Build a registry from a recipe list. Throws `CLEANUP_DUPLICATE_RECIPE` at
183
+ * construction if two recipes share an id — a boot-time failure, never a
184
+ * silent last-wins overwrite.
185
+ */
186
+ declare function createCleanupRegistry(recipes: readonly CleanupRecipe[]): CleanupRegistry;
187
+ //#endregion
188
+ //#region src/cleanup/service.d.ts
189
+ interface CleanupServiceDeps {
190
+ registry: CleanupRegistry;
191
+ runStore: CleanupRunStore;
192
+ evidenceStore: CleanupEvidenceStore;
193
+ /** Optional write fence (§8). No-op when absent. */
194
+ writeFence?: CleanupWriteFence | undefined;
195
+ /**
196
+ * Durable job queue (§8). Defaults to a microtask-deferred in-process queue
197
+ * that calls `processRun` off the request path (single-process only). Inject
198
+ * BullMQ / SQS / repo-backed for cross-process durability + restart recovery.
199
+ */
200
+ jobQueue?: CleanupJobQueue | undefined;
201
+ /** Framework size caps. Defaults to {@link DEFAULT_CLEANUP_LIMITS}. */
202
+ limits?: Partial<CleanupLimits> | undefined;
203
+ /**
204
+ * Exclusive worker-lease duration. A crashed executor's run becomes
205
+ * claimable again this long after its last heartbeat (each persisted
206
+ * progress write renews the lease). Default 5 minutes.
207
+ */
208
+ leaseMs?: number | undefined;
209
+ /**
210
+ * Minimum interval between PERSISTED progress writes (and their piggybacked
211
+ * cancellation reads). `0` (default) persists every chunk. Failed steps are
212
+ * always persisted immediately. Keep well below `leaseMs` — persisted
213
+ * progress is also the lease heartbeat.
214
+ */
215
+ progressThrottleMs?: number | undefined;
216
+ /** Optional logger for release-failure / recovery diagnostics. */
217
+ logger?: CleanupContext["logger"];
218
+ /** Id generator — defaults to `crypto.randomUUID`. */
219
+ generateId?: () => string;
220
+ /** Clock — defaults to `() => new Date()`. */
221
+ now?: () => Date;
222
+ }
223
+ interface PreviewInput extends CleanupInput {
224
+ recipeId: string;
225
+ actor: PurgeActor;
226
+ ambient?: Readonly<Record<string, unknown>> | undefined;
227
+ }
228
+ interface ExecuteInput extends PreviewInput {
229
+ /** The digest the operator confirmed against — must match the re-plan. */
230
+ planDigest: string;
231
+ /** Non-empty justification, persisted on the run + evidence. */
232
+ reason: string;
233
+ /** For a destructive recipe: the exact confirmation phrase. */
234
+ confirmation?: string | undefined;
235
+ }
236
+ interface CancelInput {
237
+ /** Who requested the cancel (audit — persisted on the run + evidence). */
238
+ actor?: PurgeActor | undefined;
239
+ /** Why (audit). */
240
+ reason?: string | undefined;
241
+ }
242
+ interface CleanupService {
243
+ preview(input: PreviewInput): Promise<CleanupPlan>;
244
+ execute(input: ExecuteInput): Promise<CleanupRun>;
245
+ /** Worker entrypoint — run the persisted, enqueued operation to completion. */
246
+ processRun(runId: string): Promise<void>;
247
+ getRun(id: string): Promise<CleanupRun>;
248
+ cancel(id: string, input?: CancelInput): Promise<CleanupRun>;
249
+ retry(id: string): Promise<CleanupRun>;
250
+ }
251
+ /** Admission key serializing every destructive run behind one slot. */
252
+ declare const GLOBAL_DESTRUCTIVE_KEY = "global-destructive";
253
+ declare function createCleanupService(deps: CleanupServiceDeps): CleanupService;
254
+ //#endregion
255
+ //#region src/cleanup/module.d.ts
256
+ interface DataCleanupModuleDeps {
257
+ /** The recipes this deployment exposes. Ids must be unique (boot-checked). */
258
+ recipes: readonly CleanupRecipe[];
259
+ /** Durable run store — host-owned persistence (atomic create + CAS transitions). */
260
+ runStore: CleanupRunStore;
261
+ /** Durable evidence + manifest store — host-owned, idempotent by operationId. */
262
+ evidenceStore: CleanupEvidenceStore;
263
+ /** Permission checks for view / execute / manage. */
264
+ permissions: CleanupPermissions;
265
+ /** Optional write fence (§8). */
266
+ writeFence?: CleanupWriteFence | undefined;
267
+ /** Optional durable job queue (§8) — defaults to microtask-deferred in-process. */
268
+ jobQueue?: CleanupJobQueue | undefined;
269
+ /** Framework size caps. */
270
+ limits?: Partial<CleanupLimits> | undefined;
271
+ /** Exclusive worker-lease duration (ms). Default 5 minutes. */
272
+ leaseMs?: number | undefined;
273
+ /** Min interval between persisted progress writes (ms). Default 0 (every chunk). */
274
+ progressThrottleMs?: number | undefined;
275
+ /** Route prefix. Default `/governance/data-cleanup`. */
276
+ prefix?: string | undefined;
277
+ /** Module name. Default `data-cleanup`. */
278
+ moduleName?: string | undefined;
279
+ /** App resource names this module supersedes. */
280
+ owns?: readonly string[] | undefined;
281
+ /**
282
+ * Derive the acting party from the request. Default reads `getUserId(req.scope)`
283
+ * (falling back to `req.user.id`) and FAILS CLOSED — a destructive governance
284
+ * action with no resolvable authenticated actor throws `CLEANUP_ACTOR_REQUIRED`
285
+ * (401) rather than attributing the purge to `user:unknown`.
286
+ */
287
+ resolveActor?: (req: RequestWithExtras) => PurgeActor;
288
+ /**
289
+ * Capture the request's serializable ambient scope (branch/company target) so
290
+ * the worker rebuilds the exact operation context. Must be JSON-serializable.
291
+ */
292
+ resolveAmbient?: (req: RequestWithExtras) => Readonly<Record<string, unknown>> | undefined;
293
+ /** Injected id generator (tests). */
294
+ generateId?: () => string;
295
+ /** Injected clock (tests). */
296
+ now?: () => Date;
297
+ }
298
+ /**
299
+ * Build the Data Cleanup Center Arc module. The module's public export is the
300
+ * live {@link CleanupService} (so other modules / tests can drive it directly).
301
+ */
302
+ declare function createDataCleanupModule(deps: DataCleanupModuleDeps): ArcModule<CleanupService>;
303
+ //#endregion
304
+ //#region src/cleanup/plan-digest.d.ts
305
+ /** `sha256(canonical(planMaterial))` as a hex digest. */
306
+ declare function computePlanDigest(plan: Omit<CleanupPlan, "digest">): string;
307
+ /**
308
+ * Integrity checksum of a completed manifest (§5). Excludes the digest field
309
+ * itself. Dates (e.g. `completedAt`) participate via the strict canonicalizer.
310
+ */
311
+ declare function computeManifestDigest(manifest: Record<string, unknown>): string;
312
+ //#endregion
313
+ export { type Availability, CLEANUP_TERMINAL_STATUSES, type CancelInput, type CleanupCancelRequest, CleanupCancelled, type CleanupContext, CleanupError, type CleanupErrorCode, CleanupErrors, type CleanupEvidenceStore, type CleanupExecutionContext, type CleanupFinalizationPayload, type CleanupInput, type CleanupJob, type CleanupJobQueue, type CleanupLease, type CleanupLimits, type CleanupLogger, type CleanupManifest, type CleanupOutcomeStatus, type CleanupPermissionCheck, type CleanupPermissions, type CleanupPlan, type CleanupPlanDraft, type CleanupPlanItem, type CleanupProgressSummary, type CleanupRecipe, type CleanupRecipeInfo, type CleanupRegistry, type CleanupResult, type CleanupRun, type CleanupRunCreateResult, type CleanupRunStatus, type CleanupRunStore, type CleanupRunTransitionPatch, type CleanupService, type CleanupServiceDeps, type CleanupStepResult, type CleanupWriteFence, DEFAULT_CLEANUP_LIMITS, type DataCleanupModuleDeps, type ExecuteInput, GLOBAL_DESTRUCTIVE_KEY, MemoryCleanupEvidenceStore, MemoryCleanupJobQueue, MemoryCleanupRunStore, type PreviewInput, type PurgeActor, type PurgeEvidence, type PurgeResourceResult, type PurgeStrategyKind, type PurgeVerificationSummary, type RecipeFromStepsInput, type VerificationCheck, type VerificationResult, canonicalJson, computeManifestDigest, computePlanDigest, createCleanupRegistry, createCleanupService, createDataCleanupModule, recipeFromSteps };