@prenta/core 5.5.0 → 5.7.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 (109) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/ai/governance.d.ts +6 -0
  3. package/dist/ai/governance.d.ts.map +1 -1
  4. package/dist/ai/governance.js +19 -0
  5. package/dist/ai/governance.js.map +1 -1
  6. package/dist/api/handlers.d.ts.map +1 -1
  7. package/dist/api/handlers.js +2 -0
  8. package/dist/api/handlers.js.map +1 -1
  9. package/dist/api/routes/redirects.d.ts.map +1 -1
  10. package/dist/api/routes/redirects.js +89 -93
  11. package/dist/api/routes/redirects.js.map +1 -1
  12. package/dist/api/routes/seo-autopilot.d.ts +12 -0
  13. package/dist/api/routes/seo-autopilot.d.ts.map +1 -0
  14. package/dist/api/routes/seo-autopilot.js +257 -0
  15. package/dist/api/routes/seo-autopilot.js.map +1 -0
  16. package/dist/api/routes/seo.d.ts.map +1 -1
  17. package/dist/api/routes/seo.js +195 -360
  18. package/dist/api/routes/seo.js.map +1 -1
  19. package/dist/db/model-types.d.ts +26 -0
  20. package/dist/db/model-types.d.ts.map +1 -1
  21. package/dist/generated/browser.d.ts +8 -0
  22. package/dist/generated/client.d.ts +8 -0
  23. package/dist/generated/internal/class.d.ts +11 -0
  24. package/dist/generated/internal/class.js +4 -4
  25. package/dist/generated/internal/prismaNamespace.d.ts +120 -1
  26. package/dist/generated/internal/prismaNamespace.js +42 -0
  27. package/dist/generated/internal/prismaNamespaceBrowser.d.ts +44 -0
  28. package/dist/generated/internal/prismaNamespaceBrowser.js +42 -0
  29. package/dist/generated/models/SeoAutoApply.d.ts +1495 -0
  30. package/dist/generated/models/SeoAutoApply.js +1 -0
  31. package/dist/generated/models.d.ts +1 -0
  32. package/dist/redirects/recovery.d.ts +15 -9
  33. package/dist/redirects/recovery.d.ts.map +1 -1
  34. package/dist/redirects/recovery.js +15 -61
  35. package/dist/redirects/recovery.js.map +1 -1
  36. package/dist/redirects/suggestion-accept.d.ts +29 -0
  37. package/dist/redirects/suggestion-accept.d.ts.map +1 -0
  38. package/dist/redirects/suggestion-accept.js +108 -0
  39. package/dist/redirects/suggestion-accept.js.map +1 -0
  40. package/dist/seo/audit-runner.d.ts.map +1 -1
  41. package/dist/seo/audit-runner.js +0 -2
  42. package/dist/seo/audit-runner.js.map +1 -1
  43. package/dist/seo/autopilot-gate.d.ts +101 -0
  44. package/dist/seo/autopilot-gate.d.ts.map +1 -0
  45. package/dist/seo/autopilot-gate.js +272 -0
  46. package/dist/seo/autopilot-gate.js.map +1 -0
  47. package/dist/seo/autopilot-ledger.d.ts +74 -0
  48. package/dist/seo/autopilot-ledger.d.ts.map +1 -0
  49. package/dist/seo/autopilot-ledger.js +118 -0
  50. package/dist/seo/autopilot-ledger.js.map +1 -0
  51. package/dist/seo/autopilot-outcome.d.ts +93 -0
  52. package/dist/seo/autopilot-outcome.d.ts.map +1 -0
  53. package/dist/seo/autopilot-outcome.js +206 -0
  54. package/dist/seo/autopilot-outcome.js.map +1 -0
  55. package/dist/seo/autopilot-policy.d.ts +169 -0
  56. package/dist/seo/autopilot-policy.d.ts.map +1 -0
  57. package/dist/seo/autopilot-policy.js +267 -0
  58. package/dist/seo/autopilot-policy.js.map +1 -0
  59. package/dist/seo/autopilot-revert.d.ts +40 -0
  60. package/dist/seo/autopilot-revert.d.ts.map +1 -0
  61. package/dist/seo/autopilot-revert.js +155 -0
  62. package/dist/seo/autopilot-revert.js.map +1 -0
  63. package/dist/seo/autopilot.d.ts +91 -0
  64. package/dist/seo/autopilot.d.ts.map +1 -0
  65. package/dist/seo/autopilot.js +1060 -0
  66. package/dist/seo/autopilot.js.map +1 -0
  67. package/dist/seo/config-store.d.ts +35 -2
  68. package/dist/seo/config-store.d.ts.map +1 -1
  69. package/dist/seo/config-store.js +58 -1
  70. package/dist/seo/config-store.js.map +1 -1
  71. package/dist/seo/deep-equal.d.ts +12 -0
  72. package/dist/seo/deep-equal.d.ts.map +1 -0
  73. package/dist/seo/deep-equal.js +42 -0
  74. package/dist/seo/deep-equal.js.map +1 -0
  75. package/dist/seo/digest.d.ts +27 -0
  76. package/dist/seo/digest.d.ts.map +1 -1
  77. package/dist/seo/digest.js +77 -1
  78. package/dist/seo/digest.js.map +1 -1
  79. package/dist/seo/index.d.ts +20 -2
  80. package/dist/seo/index.d.ts.map +1 -1
  81. package/dist/seo/index.js +12 -1
  82. package/dist/seo/index.js.map +1 -1
  83. package/dist/seo/issue-fix-ai.d.ts +40 -1
  84. package/dist/seo/issue-fix-ai.d.ts.map +1 -1
  85. package/dist/seo/issue-fix-ai.js +53 -1
  86. package/dist/seo/issue-fix-ai.js.map +1 -1
  87. package/dist/seo/issue-fix-apply.d.ts +104 -0
  88. package/dist/seo/issue-fix-apply.d.ts.map +1 -0
  89. package/dist/seo/issue-fix-apply.js +406 -0
  90. package/dist/seo/issue-fix-apply.js.map +1 -0
  91. package/dist/seo/issue-fix.d.ts +9 -0
  92. package/dist/seo/issue-fix.d.ts.map +1 -1
  93. package/dist/seo/issue-fix.js +1 -0
  94. package/dist/seo/issue-fix.js.map +1 -1
  95. package/dist/seo/proposals-bulk-apply.d.ts +52 -0
  96. package/dist/seo/proposals-bulk-apply.d.ts.map +1 -0
  97. package/dist/seo/proposals-bulk-apply.js +183 -0
  98. package/dist/seo/proposals-bulk-apply.js.map +1 -0
  99. package/dist/seo/proposals-generate.d.ts +45 -0
  100. package/dist/seo/proposals-generate.d.ts.map +1 -0
  101. package/dist/seo/proposals-generate.js +144 -0
  102. package/dist/seo/proposals-generate.js.map +1 -0
  103. package/dist/seo/proposals.d.ts +125 -0
  104. package/dist/seo/proposals.d.ts.map +1 -0
  105. package/dist/seo/proposals.js +270 -0
  106. package/dist/seo/proposals.js.map +1 -0
  107. package/package.json +1 -1
  108. package/prisma/migrations/0017_seo_auto_applies/migration.sql +35 -0
  109. package/prisma/schema.prisma +43 -0
@@ -0,0 +1,272 @@
1
+ import { META_DESC_MAX, META_TITLE_MAX } from './issue-fix.js';
2
+ import { AUTOPILOT_DESC_MIN, } from './autopilot-policy.js';
3
+ import { ISSUE_FIX_FEATURE_KEY, REDIRECT_FEATURE_KEY } from './proposals.js';
4
+ export function featureKeyFor(kind) {
5
+ switch (kind) {
6
+ case 'issue-fix':
7
+ return ISSUE_FIX_FEATURE_KEY;
8
+ case 'redirect':
9
+ return REDIRECT_FEATURE_KEY;
10
+ default: {
11
+ const _exhaustive = kind;
12
+ return _exhaustive;
13
+ }
14
+ }
15
+ }
16
+ export function normalizeDuplicateValue(value) {
17
+ if (typeof value !== 'string')
18
+ return null;
19
+ const v = value.trim().replace(/\s+/g, ' ').toLowerCase();
20
+ return v.length > 0 ? v : null;
21
+ }
22
+ /**
23
+ * Build the duplicate index from one row per entity.
24
+ *
25
+ * `rows[].metaTitle` MUST be the RENDERED meta title — the same form the AI title fixer puts in
26
+ * `changes[].after` (raw title through the site title template). The orchestrator renders each
27
+ * document's stored title with the site template before indexing. `rows[].metaDescription` is
28
+ * used as stored (no template applies).
29
+ */
30
+ export function buildDuplicateIndex(rows) {
31
+ const titles = new Map();
32
+ const descriptions = new Map();
33
+ const add = (map, raw, id) => {
34
+ const key = normalizeDuplicateValue(raw);
35
+ if (!key)
36
+ return;
37
+ const set = map.get(key) ?? new Set();
38
+ set.add(id);
39
+ map.set(key, set);
40
+ };
41
+ for (const r of rows) {
42
+ add(titles, r.metaTitle, r.entityId);
43
+ add(descriptions, r.metaDescription, r.entityId);
44
+ }
45
+ return { titles, descriptions };
46
+ }
47
+ function escapeRegExp(s) {
48
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
49
+ }
50
+ /**
51
+ * Word-boundary, case-insensitive — the fixer's matcher, not the scorer's substring one.
52
+ *
53
+ * Boundaries are Unicode-aware lookarounds rather than `\b`: `\b` only exists between a `\w`
54
+ * and a non-`\w` character, so a term with a non-word edge (`#1`, `100%`, `24/7`) would never
55
+ * match at string start / after a space (fail-open), and ASCII `\w` treats `é` as a boundary so
56
+ * `café` would hit inside `cafés` (false block).
57
+ */
58
+ export function bannedTermHits(text, terms) {
59
+ const hits = [];
60
+ for (const banned of terms) {
61
+ const term = banned.term.trim();
62
+ if (!term)
63
+ continue;
64
+ const re = new RegExp(String.raw `(?<![\p{L}\p{N}_])${escapeRegExp(term)}(?![\p{L}\p{N}_])`, 'iu');
65
+ if (re.test(text))
66
+ hits.push(term);
67
+ }
68
+ return hits;
69
+ }
70
+ const CHANNEL_SCORABLE_FIELDS = new Set(['metaDescription', 'ogDescription']);
71
+ const TITLE_FIELDS = new Set(['metaTitle']);
72
+ const DESC_FIELDS = new Set(['metaDescription', 'ogDescription']);
73
+ const CANONICAL_FIELDS = new Set(['canonicalUrl', 'canonical']);
74
+ const STRUCTURED_FIELDS = new Set(['structuredData']);
75
+ function validatorFailure(field, after) {
76
+ if (TITLE_FIELDS.has(field)) {
77
+ // `after` is the RENDERED title (see DuplicateIndex.titles), so this validates the rendered
78
+ // length — what actually appears in the SERP — not the raw stored title.
79
+ const len = typeof after === 'string' ? after.trim().length : 0;
80
+ if (len < 1 || len > META_TITLE_MAX)
81
+ return `${field} length ${len} not in 1..${META_TITLE_MAX}`;
82
+ return null;
83
+ }
84
+ if (DESC_FIELDS.has(field)) {
85
+ const len = typeof after === 'string' ? after.trim().length : 0;
86
+ if (len < AUTOPILOT_DESC_MIN || len > META_DESC_MAX) {
87
+ return `${field} length ${len} not in ${AUTOPILOT_DESC_MIN}..${META_DESC_MAX}`;
88
+ }
89
+ return null;
90
+ }
91
+ if (CANONICAL_FIELDS.has(field)) {
92
+ if (typeof after !== 'string')
93
+ return `${field} is not an absolute http(s) URL`;
94
+ try {
95
+ const u = new URL(after);
96
+ if (u.protocol !== 'http:' && u.protocol !== 'https:')
97
+ return `${field} is not an absolute http(s) URL`;
98
+ }
99
+ catch {
100
+ return `${field} is not an absolute http(s) URL`;
101
+ }
102
+ return null;
103
+ }
104
+ if (STRUCTURED_FIELDS.has(field)) {
105
+ let parsed = after;
106
+ if (typeof after === 'string') {
107
+ try {
108
+ parsed = JSON.parse(after);
109
+ }
110
+ catch {
111
+ return `${field} is not valid JSON`;
112
+ }
113
+ }
114
+ const rec = parsed && typeof parsed === 'object' ? parsed : null;
115
+ if (!rec || typeof rec['@type'] !== 'string')
116
+ return `${field} has no @type`;
117
+ return null;
118
+ }
119
+ return null;
120
+ }
121
+ function passesPolicy(c, ctx) {
122
+ const p = ctx.policy;
123
+ if (!p.enabled)
124
+ return { pass: false, reason: 'policy_disabled' };
125
+ if (p.pausedAt)
126
+ return { pass: false, reason: 'policy_paused' };
127
+ if (p.types[c.issueTypeKey] !== true)
128
+ return { pass: false, reason: 'type_not_enabled' };
129
+ const pausedDetail = p.pausedTypes[c.issueTypeKey];
130
+ if (typeof pausedDetail === 'string')
131
+ return { pass: false, reason: 'type_paused', detail: pausedDetail };
132
+ return null;
133
+ }
134
+ function passesGovernance(c, ctx) {
135
+ const gov = ctx.governance[c.featureKey] ?? {
136
+ approvalRequired: true,
137
+ allowBulkApply: false,
138
+ maxRequestsPerDay: undefined,
139
+ };
140
+ if (gov.approvalRequired)
141
+ return { pass: false, reason: 'governance_approval_required' };
142
+ const applied = ctx.featureApplied[c.featureKey] ?? 0;
143
+ // Quota for a non-bulk feature is 1 per run: the first passes, the rest fail. Deliberately
144
+ // NOT conditioned on `featureCandidates` — a missing/mis-keyed count must not disable the quota.
145
+ if (!gov.allowBulkApply && applied >= 1) {
146
+ return { pass: false, reason: 'governance_bulk_not_allowed' };
147
+ }
148
+ return null;
149
+ }
150
+ export function evaluateAutopilotGate(c, ctx) {
151
+ const policyFail = passesPolicy(c, ctx);
152
+ if (policyFail)
153
+ return policyFail;
154
+ const govFail = passesGovernance(c, ctx);
155
+ if (govFail)
156
+ return govFail;
157
+ if (ctx.appliedThisRun >= ctx.policy.maxPerRun)
158
+ return { pass: false, reason: 'cap_per_run' };
159
+ if (ctx.appliedToday >= ctx.policy.maxPerDay)
160
+ return { pass: false, reason: 'cap_per_day' };
161
+ if (c.stale)
162
+ return { pass: false, reason: 'stale' };
163
+ // An editor rejected this proposal for the current document state: it waits in the inbox
164
+ // (or stays dismissed); autopilot never re-applies it until the document changes.
165
+ if (c.dismissedFingerprint && c.fingerprint && c.dismissedFingerprint === c.fingerprint) {
166
+ return { pass: false, reason: 'dismissed' };
167
+ }
168
+ if (c.issueId && ctx.cooldownIssueIds.has(c.issueId))
169
+ return { pass: false, reason: 'cooldown' };
170
+ if (c.entity.id && ctx.cooldownEntityKeys.has(`${c.entity.id}:${c.issueTypeKey}`)) {
171
+ return { pass: false, reason: 'cooldown' };
172
+ }
173
+ if (ctx.planDenied)
174
+ return { pass: false, reason: 'plan_denied' };
175
+ switch (c.kind) {
176
+ case 'redirect': {
177
+ const source = c.entity.url ?? '';
178
+ const target = c.changes[0]?.after;
179
+ if (source.trim() === '' || !source.startsWith('/')) {
180
+ return {
181
+ pass: false,
182
+ reason: 'validator_failed',
183
+ detail: 'redirect source must be a /-prefixed path',
184
+ };
185
+ }
186
+ if (typeof target !== 'string' || target.trim() === '') {
187
+ return {
188
+ pass: false,
189
+ reason: 'validator_failed',
190
+ detail: 'redirect target must be a non-empty string',
191
+ };
192
+ }
193
+ if (target.trim() === source.trim()) {
194
+ return { pass: false, reason: 'validator_failed', detail: 'redirect target equals source' };
195
+ }
196
+ const min = ctx.policy.redirectMinConfidence / 100;
197
+ const conf = c.confidence ?? 0;
198
+ // Negated `>=` so NaN fails closed (`NaN < min` is false and would pass).
199
+ if (!(conf >= min)) {
200
+ return { pass: false, reason: 'redirect_low_confidence', detail: `${conf} < ${min}` };
201
+ }
202
+ if (ctx.existingRedirectSources.has(source)) {
203
+ return { pass: false, reason: 'redirect_manual_exists' };
204
+ }
205
+ return { pass: true, checks: { confidence: true, noExisting: true } };
206
+ }
207
+ case 'issue-fix': {
208
+ const checks = {};
209
+ const afterStrings = c.changes.filter((ch) => typeof ch.after === 'string');
210
+ if (c.source === 'ai' || c.source === 'ai-404-recovery') {
211
+ if (ctx.brandProfile) {
212
+ const touchesScorable = c.changes.some((ch) => CHANNEL_SCORABLE_FIELDS.has(ch.field));
213
+ if (c.brandAlignment === null) {
214
+ if (touchesScorable)
215
+ return { pass: false, reason: 'brand_unscored' };
216
+ }
217
+ else if (!(c.brandAlignment >= ctx.policy.minBrandAlignment)) {
218
+ // Negated `>=` so a NaN score fails closed.
219
+ return {
220
+ pass: false,
221
+ reason: 'brand_below_threshold',
222
+ detail: `${c.brandAlignment} < ${ctx.policy.minBrandAlignment}`,
223
+ };
224
+ }
225
+ checks.brand = true;
226
+ for (const ch of afterStrings) {
227
+ const hits = bannedTermHits(ch.after, ctx.brandProfile.bannedTerms);
228
+ if (hits.length > 0)
229
+ return { pass: false, reason: 'banned_term', detail: hits[0] };
230
+ }
231
+ checks.bannedTerms = true;
232
+ }
233
+ else {
234
+ checks.brand = true;
235
+ checks.bannedTerms = true;
236
+ }
237
+ }
238
+ for (const ch of c.changes) {
239
+ const failure = validatorFailure(ch.field, ch.after);
240
+ if (failure)
241
+ return { pass: false, reason: 'validator_failed', detail: failure };
242
+ }
243
+ checks.validators = true;
244
+ if (c.source === 'ai' || c.source === 'ai-404-recovery') {
245
+ for (const ch of afterStrings) {
246
+ // `ogDescription` is intentionally excluded: no autopilot type targets OG duplication.
247
+ const map = TITLE_FIELDS.has(ch.field)
248
+ ? ctx.duplicates.titles
249
+ : ch.field === 'metaDescription'
250
+ ? ctx.duplicates.descriptions
251
+ : null;
252
+ if (!map)
253
+ continue;
254
+ const key = normalizeDuplicateValue(ch.after);
255
+ if (!key)
256
+ continue;
257
+ const owners = map.get(key);
258
+ if (owners && [...owners].some((id) => id !== c.entity.id)) {
259
+ return { pass: false, reason: 'duplicate_value', detail: ch.field };
260
+ }
261
+ }
262
+ checks.duplicates = true;
263
+ }
264
+ return { pass: true, checks };
265
+ }
266
+ default: {
267
+ const _exhaustive = c.kind;
268
+ return _exhaustive;
269
+ }
270
+ }
271
+ }
272
+ //# sourceMappingURL=autopilot-gate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"autopilot-gate.js","sourceRoot":"","sources":["../../src/seo/autopilot-gate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * SeoAutoApply ledger helpers (spec §4). Rows are append-only; revert flips
3
+ * `status` in place. All queries tolerate a consumer schema without the
4
+ * model via the `hasModel`-style probe the callers perform.
5
+ *
6
+ * `status`, `outcome`, and `kind` are plain `String` columns in Prisma; the
7
+ * unions below are the TS narrowing every writer must go through.
8
+ */
9
+ import type { PrentaDB } from '../db/client-types.js';
10
+ import type { SeoAutoApply } from '../db/model-types.js';
11
+ export type SeoAutoApplyKind = 'issue-fix' | 'redirect';
12
+ export type SeoAutoApplyStatus = 'applied' | 'verify_failed' | 'reverted' | 'outcome_reverted';
13
+ export type SeoAutoApplyOutcome = 'pending' | 'held' | 'regressed' | 'insufficient_data' | 'skipped';
14
+ export type SeoAutoApplyRevertReason = 'verify_failed' | 'outcome_regression' | 'manual';
15
+ export interface AutoApplyGateSnapshot {
16
+ source: string;
17
+ brandAlignment: number | null;
18
+ confidence: number | null;
19
+ checks: Record<string, boolean>;
20
+ }
21
+ export interface RecordAutoApplyInput {
22
+ kind: SeoAutoApplyKind;
23
+ issueId: string | null;
24
+ issueType: string;
25
+ entityId: string | null;
26
+ collection: string | null;
27
+ path: string | null;
28
+ redirectId: string | null;
29
+ /** Prior field values for issue fixes; `null` for redirects (no prior state). */
30
+ before: Record<string, unknown> | null;
31
+ after: Record<string, unknown>;
32
+ gate: AutoApplyGateSnapshot;
33
+ status: SeoAutoApplyStatus;
34
+ appliedById: string;
35
+ versionId?: string | null;
36
+ revertReason?: SeoAutoApplyRevertReason | null;
37
+ revertedAt?: Date | null;
38
+ revertedById?: string | null;
39
+ outcome?: SeoAutoApplyOutcome;
40
+ }
41
+ export type SeoAutoApplyRow = SeoAutoApply & {
42
+ entityTitle: string | null;
43
+ entityUrl: string | null;
44
+ };
45
+ export declare function startOfDayUTC(d: Date): Date;
46
+ export declare function recordAutoApply(db: PrentaDB, input: RecordAutoApplyInput): Promise<SeoAutoApply>;
47
+ export declare function countAutoAppliesToday(db: PrentaDB, now: Date): Promise<number>;
48
+ /** Per-branch row cap for `loadCooldownKeys` (newest first, so the cap drops the oldest). */
49
+ export declare const COOLDOWN_QUERY_MAX = 1000;
50
+ /**
51
+ * Issues / entities autopilot must leave alone for `AUTOPILOT_COOLDOWN_DAYS`:
52
+ * anything reverted in the window (on `revertedAt`) plus anything that failed
53
+ * verification in the window (on `appliedAt`). Verify-failed applies are
54
+ * cooled down so a deterministically failing fix does not trip the breaker
55
+ * every run. The two branches are queried separately so each is ordered by
56
+ * its own timestamp — a capped, unordered `OR` could drop the newest rows.
57
+ */
58
+ export declare function loadCooldownKeys(db: PrentaDB, now: Date): Promise<{
59
+ issueIds: Set<string>;
60
+ entityKeys: Set<string>;
61
+ }>;
62
+ export interface ListSeoAutoAppliesInput {
63
+ status?: SeoAutoApplyStatus;
64
+ outcome?: SeoAutoApplyOutcome;
65
+ page: number;
66
+ pageSize: number;
67
+ }
68
+ export declare function listSeoAutoApplies(db: PrentaDB, input: ListSeoAutoAppliesInput): Promise<{
69
+ docs: SeoAutoApplyRow[];
70
+ total: number;
71
+ page: number;
72
+ pageSize: number;
73
+ }>;
74
+ //# sourceMappingURL=autopilot-ledger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"autopilot-ledger.d.ts","sourceRoot":"","sources":["../../src/seo/autopilot-ledger.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,118 @@
1
+ import { documentPublicPath } from './document-path.js';
2
+ import { AUTOPILOT_COOLDOWN_DAYS } from './autopilot-policy.js';
3
+ const DAY_MS = 24 * 60 * 60 * 1000;
4
+ /** Statuses that count against the daily auto-apply budget. */
5
+ const COUNTED_STATUSES = ['applied', 'verify_failed'];
6
+ /** Statuses whose rows put an issue/entity into cooldown on `revertedAt` (see `loadCooldownKeys`). */
7
+ const REVERTED_STATUSES = ['reverted', 'outcome_reverted'];
8
+ export function startOfDayUTC(d) {
9
+ return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()));
10
+ }
11
+ export async function recordAutoApply(db, input) {
12
+ // Redirects have no path metric to watch; issue fixes with no path can't be judged either.
13
+ const outcome = input.outcome ?? (input.kind === 'redirect' || !input.path ? 'skipped' : 'pending');
14
+ return db.seoAutoApply.create({
15
+ data: {
16
+ kind: input.kind,
17
+ issueId: input.issueId,
18
+ issueType: input.issueType,
19
+ entityId: input.entityId,
20
+ collection: input.collection,
21
+ path: input.path,
22
+ redirectId: input.redirectId,
23
+ // `before` is a nullable Json column: a JS `null` maps to SQL NULL.
24
+ before: input.before ?? null,
25
+ after: input.after,
26
+ gate: input.gate,
27
+ status: input.status,
28
+ outcome,
29
+ versionId: input.versionId ?? null,
30
+ appliedById: input.appliedById,
31
+ revertReason: input.revertReason ?? null,
32
+ revertedAt: input.revertedAt ?? null,
33
+ revertedById: input.revertedById ?? null,
34
+ },
35
+ });
36
+ }
37
+ export async function countAutoAppliesToday(db, now) {
38
+ return db.seoAutoApply.count({
39
+ where: { status: { in: [...COUNTED_STATUSES] }, appliedAt: { gte: startOfDayUTC(now) } },
40
+ });
41
+ }
42
+ /** Per-branch row cap for `loadCooldownKeys` (newest first, so the cap drops the oldest). */
43
+ export const COOLDOWN_QUERY_MAX = 1000;
44
+ /**
45
+ * Issues / entities autopilot must leave alone for `AUTOPILOT_COOLDOWN_DAYS`:
46
+ * anything reverted in the window (on `revertedAt`) plus anything that failed
47
+ * verification in the window (on `appliedAt`). Verify-failed applies are
48
+ * cooled down so a deterministically failing fix does not trip the breaker
49
+ * every run. The two branches are queried separately so each is ordered by
50
+ * its own timestamp — a capped, unordered `OR` could drop the newest rows.
51
+ */
52
+ export async function loadCooldownKeys(db, now) {
53
+ const since = new Date(now.getTime() - AUTOPILOT_COOLDOWN_DAYS * DAY_MS);
54
+ const [reverted, verifyFailed] = await Promise.all([
55
+ db.seoAutoApply.findMany({
56
+ where: { status: { in: [...REVERTED_STATUSES] }, revertedAt: { gte: since } },
57
+ orderBy: { revertedAt: 'desc' },
58
+ take: COOLDOWN_QUERY_MAX,
59
+ }),
60
+ db.seoAutoApply.findMany({
61
+ where: { status: 'verify_failed', appliedAt: { gte: since } },
62
+ orderBy: { appliedAt: 'desc' },
63
+ take: COOLDOWN_QUERY_MAX,
64
+ }),
65
+ ]);
66
+ const issueIds = new Set();
67
+ const entityKeys = new Set();
68
+ for (const r of [...reverted, ...verifyFailed]) {
69
+ if (r.issueId)
70
+ issueIds.add(r.issueId);
71
+ if (r.entityId)
72
+ entityKeys.add(`${r.entityId}:${r.issueType}`);
73
+ }
74
+ return { issueIds, entityKeys };
75
+ }
76
+ export async function listSeoAutoApplies(db, input) {
77
+ const where = {};
78
+ if (input.status)
79
+ where.status = input.status;
80
+ if (input.outcome)
81
+ where.outcome = input.outcome;
82
+ const [rows, total] = await Promise.all([
83
+ db.seoAutoApply.findMany({
84
+ where,
85
+ orderBy: { appliedAt: 'desc' },
86
+ skip: (input.page - 1) * input.pageSize,
87
+ take: input.pageSize,
88
+ }),
89
+ db.seoAutoApply.count({ where }),
90
+ ]);
91
+ const ids = [...new Set(rows.map((r) => r.entityId).filter((v) => Boolean(v)))];
92
+ const docs = ids.length
93
+ ? await db.document.findMany({
94
+ where: { id: { in: ids }, deletedAt: null },
95
+ select: { id: true, title: true, slug: true, collection: true, data: true },
96
+ })
97
+ : [];
98
+ const byId = new Map(docs.map((d) => [
99
+ d.id,
100
+ {
101
+ title: typeof d.title === 'string' ? d.title : null,
102
+ url: documentPublicPath({
103
+ slug: d.slug,
104
+ data: (d.data ?? null),
105
+ }),
106
+ },
107
+ ]));
108
+ return {
109
+ docs: rows.map((r) => {
110
+ const meta = r.entityId ? byId.get(r.entityId) : undefined;
111
+ return { ...r, entityTitle: meta?.title ?? null, entityUrl: meta?.url ?? r.path };
112
+ }),
113
+ total,
114
+ page: input.page,
115
+ pageSize: input.pageSize,
116
+ };
117
+ }
118
+ //# sourceMappingURL=autopilot-ledger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"autopilot-ledger.js","sourceRoot":"","sources":["../../src/seo/autopilot-ledger.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AACvD,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAA;AAwC/D,MAAM,MAAM,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA;AAElC,+DAA+D;AAC/D,MAAM,gBAAgB,GAAkC,CAAC,SAAS,EAAE,eAAe,CAAC,CAAA;AACpF,sGAAsG;AACtG,MAAM,iBAAiB,GAAkC,CAAC,UAAU,EAAE,kBAAkB,CAAC,CAAA;AAEzF,MAAM,UAAU,aAAa,CAAC,CAAO;IACnC,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,CAAA;AAChF,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,EAAY,EACZ,KAA2B;IAE3B,2FAA2F;IAC3F,MAAM,OAAO,GACX,KAAK,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,UAAU,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAA;IACrF,OAAO,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC;QAC5B,IAAI,EAAE;YACJ,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,UAAU,EAAE,KAAK,CAAC,UAAU;YAC5B,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,UAAU,EAAE,KAAK,CAAC,UAAU;YAC5B,oEAAoE;YACpE,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,IAAI;YAC5B,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,OAAO;YACP,SAAS,EAAE,KAAK,CAAC,SAAS,IAAI,IAAI;YAClC,WAAW,EAAE,KAAK,CAAC,WAAW;YAC9B,YAAY,EAAE,KAAK,CAAC,YAAY,IAAI,IAAI;YACxC,UAAU,EAAE,KAAK,CAAC,UAAU,IAAI,IAAI;YACpC,YAAY,EAAE,KAAK,CAAC,YAAY,IAAI,IAAI;SACzC;KACF,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,EAAY,EAAE,GAAS;IACjE,OAAO,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC;QAC3B,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,CAAC,GAAG,gBAAgB,CAAC,EAAE,EAAE,SAAS,EAAE,EAAE,GAAG,EAAE,aAAa,CAAC,GAAG,CAAC,EAAE,EAAE;KACzF,CAAC,CAAA;AACJ,CAAC;AAED,6FAA6F;AAC7F,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,CAAA;AAEtC;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,EAAY,EACZ,GAAS;IAET,MAAM,KAAK,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,uBAAuB,GAAG,MAAM,CAAC,CAAA;IACxE,MAAM,CAAC,QAAQ,EAAE,YAAY,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QACjD,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC;YACvB,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,CAAC,GAAG,iBAAiB,CAAC,EAAE,EAAE,UAAU,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE;YAC7E,OAAO,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE;YAC/B,IAAI,EAAE,kBAAkB;SACzB,CAAC;QACF,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC;YACvB,KAAK,EAAE,EAAE,MAAM,EAAE,eAA4C,EAAE,SAAS,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE;YAC1F,OAAO,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE;YAC9B,IAAI,EAAE,kBAAkB;SACzB,CAAC;KACH,CAAC,CAAA;IACF,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAA;IAClC,MAAM,UAAU,GAAG,IAAI,GAAG,EAAU,CAAA;IACpC,KAAK,MAAM,CAAC,IAAI,CAAC,GAAG,QAAQ,EAAE,GAAG,YAAY,CAAC,EAAE,CAAC;QAC/C,IAAI,CAAC,CAAC,OAAO;YAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAA;QACtC,IAAI,CAAC,CAAC,QAAQ;YAAE,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,SAAS,EAAE,CAAC,CAAA;IAChE,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAA;AACjC,CAAC;AASD,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,EAAY,EACZ,KAA8B;IAE9B,MAAM,KAAK,GAA4B,EAAE,CAAA;IACzC,IAAI,KAAK,CAAC,MAAM;QAAE,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAA;IAC7C,IAAI,KAAK,CAAC,OAAO;QAAE,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAA;IAChD,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QACtC,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC;YACvB,KAAK;YACL,OAAO,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE;YAC9B,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,QAAQ;YACvC,IAAI,EAAE,KAAK,CAAC,QAAQ;SACrB,CAAC;QACF,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,CAAC;KACjC,CAAC,CAAA;IACF,MAAM,GAAG,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IAC5F,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM;QACrB,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC;YACzB,KAAK,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE;YAC3C,MAAM,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE;SAC5E,CAAC;QACJ,CAAC,CAAC,EAAE,CAAA;IACN,MAAM,IAAI,GAAG,IAAI,GAAG,CAClB,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;QACd,CAAC,CAAC,EAAE;QACJ;YACE,KAAK,EAAE,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI;YACnD,GAAG,EAAE,kBAAkB,CAAC;gBACtB,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,IAAI,CAAmC;aACzD,CAAC;SACH;KACF,CAAC,CACH,CAAA;IACD,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;YACnB,MAAM,IAAI,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;YAC1D,OAAO,EAAE,GAAG,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,KAAK,IAAI,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,IAAI,CAAC,CAAC,IAAI,EAAE,CAAA;QACnF,CAAC,CAAC;QACF,KAAK;QACL,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,QAAQ,EAAE,KAAK,CAAC,QAAQ;KACzB,CAAA;AACH,CAAC"}
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Outcome watch (spec §5): N days after an autopilot change, compare the
3
+ * page's Search Console clicks before vs after. A ≥ dropRatio fall reverts the
4
+ * change and pauses that issue type until a human resumes it.
5
+ *
6
+ * Windows are whole UTC calendar days anchored on `startOfDayUTC(appliedAt)`,
7
+ * matching how the GSC sync stores `SearchPerfDaily.date` (`@db.Date`, UTC
8
+ * midnight). GSC reports property-local days, so an apply near local midnight
9
+ * in a non-UTC property can land one calendar day off — accepted for v1.
10
+ *
11
+ * Due-ness is anchored on the newest synced GSC date, not the wall clock: a row
12
+ * is judged only once data exists through the end of its after window, so a
13
+ * stalled or disconnected sync leaves rows `pending` instead of reading an
14
+ * empty after window as a regression.
15
+ */
16
+ import type { PrentaDB } from '../db/client-types.js';
17
+ import { type AutopilotIssueType, type AutopilotOutcomeWatch, type SeoAutopilotPolicy } from './autopilot-policy.js';
18
+ import { type SeoAutoApplyOutcome } from './autopilot-ledger.js';
19
+ import { revertSeoAutoApply } from './autopilot-revert.js';
20
+ /** Upper bound on ledger rows judged per cron tick. */
21
+ export declare const OUTCOME_MAX_PER_TICK = 50;
22
+ /** A regression whose revert keeps throwing is requeued at most this many times. */
23
+ export declare const OUTCOME_MAX_REVERT_ATTEMPTS = 3;
24
+ export type OutcomeJudgement = Extract<SeoAutoApplyOutcome, 'insufficient_data' | 'regressed' | 'held'>;
25
+ /**
26
+ * Pure judgement. A zero `prior` (no ratio to take) or one below the floor is
27
+ * not enough signal; otherwise a fall to or below `prior × (1 − dropRatio)` is
28
+ * a regression.
29
+ */
30
+ export declare function evaluateOutcome(prior: number, after: number, watch: AutopilotOutcomeWatch): OutcomeJudgement;
31
+ export interface OutcomeWatchResult {
32
+ evaluated: number;
33
+ held: number;
34
+ regressed: number;
35
+ reverted: number;
36
+ insufficient: number;
37
+ conflicts: number;
38
+ /** Regressions whose revert threw (row requeued as `pending` until `OUTCOME_MAX_REVERT_ATTEMPTS`). */
39
+ revertErrors: number;
40
+ /** Due rows left `pending` because GSC data has not yet synced through their after window. */
41
+ notReady: number;
42
+ /** `true` when `deadline` was reached before every due row was judged (the rest stay `pending`). */
43
+ deadlineHit: boolean;
44
+ /** Issue types to pause, keyed to the human-readable reason to store on the policy. */
45
+ pausedTypes: Partial<Record<AutopilotIssueType, string>>;
46
+ }
47
+ export interface OutcomeWatchOptions {
48
+ /** Wall clock stamped on judged rows and in pause reasons. */
49
+ now: Date;
50
+ actor: {
51
+ userId: string;
52
+ role: string;
53
+ };
54
+ /** Injectable for tests; defaults to `revertSeoAutoApply`. */
55
+ revert?: typeof revertSeoAutoApply;
56
+ /**
57
+ * Epoch-ms deadline (compared against `clock()`): checked before each row is
58
+ * judged; once reached the loop stops and the remaining rows stay `pending`
59
+ * for the next tick. Absent = judge every due row (bounded by
60
+ * `OUTCOME_MAX_PER_TICK`).
61
+ */
62
+ deadline?: number;
63
+ /** Monotonic-ish clock for `deadline`; defaults to `Date.now`. */
64
+ clock?: () => number;
65
+ /**
66
+ * Invoked the moment a regression is decided — before the revert is
67
+ * attempted — so the caller can persist the type pause immediately rather
68
+ * than after the whole watch returns. A throw is logged and does not stop
69
+ * the tick; the pause is still reported in `pausedTypes`.
70
+ */
71
+ onPause?: (type: AutopilotIssueType, reason: string) => Promise<void> | void;
72
+ }
73
+ /**
74
+ * Judge every due, still-pending issue fix (oldest first, capped per tick).
75
+ * Due = applied at least `days + AUTOPILOT_OUTCOME_LAG_DAYS` ago *and* GSC data
76
+ * synced through the end of the after window. Prior window: `days` days ending
77
+ * the day before apply. After window: `days` days starting the day after apply.
78
+ *
79
+ * A regression reverts the row with reason `outcome_regression` (never
80
+ * forced — a human edit since is a conflict) and requests a pause of that
81
+ * issue type via `pausedTypes`; the caller persists the pause on the policy.
82
+ * A revert that throws is logged, counted in `revertErrors`, and the row is
83
+ * put back to `pending` (with `outcomeMetrics.revertAttempts`) so the next
84
+ * tick retries — up to `OUTCOME_MAX_REVERT_ATTEMPTS`, after which it stays
85
+ * `regressed` for a manual revert. The tick continues with the next row.
86
+ *
87
+ * The pause is decided (and `onPause` fired) before the revert is attempted,
88
+ * so a revert that throws — or a process killed mid-revert — never loses the
89
+ * breaker. `opts.deadline` bounds the wall clock: rows not reached stay
90
+ * `pending` and are judged next tick.
91
+ */
92
+ export declare function runOutcomeWatch(db: PrentaDB, policy: SeoAutopilotPolicy, opts: OutcomeWatchOptions): Promise<OutcomeWatchResult>;
93
+ //# sourceMappingURL=autopilot-outcome.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"autopilot-outcome.d.ts","sourceRoot":"","sources":["../../src/seo/autopilot-outcome.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AACrD,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,EAC1B,KAAK,kBAAkB,EAExB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EAAiB,KAAK,mBAAmB,EAAE,MAAM,uBAAuB,CAAA;AAC/E,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AAE1D,uDAAuD;AACvD,eAAO,MAAM,oBAAoB,KAAK,CAAA;AACtC,oFAAoF;AACpF,eAAO,MAAM,2BAA2B,IAAI,CAAA;AAK5C,MAAM,MAAM,gBAAgB,GAAG,OAAO,CACpC,mBAAmB,EACnB,mBAAmB,GAAG,WAAW,GAAG,MAAM,CAC3C,CAAA;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,qBAAqB,GAC3B,gBAAgB,CAIlB;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB,sGAAsG;IACtG,YAAY,EAAE,MAAM,CAAA;IACpB,8FAA8F;IAC9F,QAAQ,EAAE,MAAM,CAAA;IAChB,oGAAoG;IACpG,WAAW,EAAE,OAAO,CAAA;IACpB,uFAAuF;IACvF,WAAW,EAAE,OAAO,CAAC,MAAM,CAAC,kBAAkB,EAAE,MAAM,CAAC,CAAC,CAAA;CACzD;AAED,MAAM,WAAW,mBAAmB;IAClC,8DAA8D;IAC9D,GAAG,EAAE,IAAI,CAAA;IACT,KAAK,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;IACvC,8DAA8D;IAC9D,MAAM,CAAC,EAAE,OAAO,kBAAkB,CAAA;IAClC;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,kEAAkE;IAClE,KAAK,CAAC,EAAE,MAAM,MAAM,CAAA;IACpB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,kBAAkB,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;CAC7E;AAuBD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,eAAe,CACnC,EAAE,EAAE,QAAQ,EACZ,MAAM,EAAE,kBAAkB,EAC1B,IAAI,EAAE,mBAAmB,GACxB,OAAO,CAAC,kBAAkB,CAAC,CA8I7B"}