@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,270 @@
1
+ import { getFeatureGovernance } from '../ai/governance.js';
2
+ import { buildFixFingerprint } from './issue-fix.js';
3
+ import { pendingFixFromMetadata, stickyFingerprintsFromMetadata, } from './issue-fix-ai.js';
4
+ import { INSERT_LINK_FIX_TYPES, buildInsertLinkFingerprint } from './issue-fix-link-insert.js';
5
+ import { getSeoOverrides, resolveSeoModuleSettings } from './config-store.js';
6
+ export const ISSUE_FIX_FEATURE_KEY = 'ai.features.seoMetaGeneration';
7
+ export const REDIRECT_FEATURE_KEY = 'ai.features.seo404Recovery';
8
+ const AUTOPILOT_APPLIED_WINDOW_MS = 7 * 24 * 60 * 60 * 1000;
9
+ /**
10
+ * Count the autopilot changes that are live right now and landed in the last
11
+ * 7 days. Only `applied` counts — `verify_failed` rows were undone at apply
12
+ * time and reverted rows are no longer live. Degrades to 0 when the ledger
13
+ * model is absent or the count fails: this is a status strip, not a ledger.
14
+ */
15
+ async function countAutopilotAppliedLast7d(db) {
16
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- optional-model guard
17
+ const d = db;
18
+ if (typeof d?.seoAutoApply?.count !== 'function')
19
+ return 0;
20
+ try {
21
+ const status = 'applied';
22
+ const n = await d.seoAutoApply.count({
23
+ where: { status, appliedAt: { gte: new Date(Date.now() - AUTOPILOT_APPLIED_WINDOW_MS) } },
24
+ });
25
+ return typeof n === 'number' && Number.isFinite(n) ? n : 0;
26
+ }
27
+ catch {
28
+ return 0;
29
+ }
30
+ }
31
+ const SEVERITY_RANK = { critical: 0, warning: 1, info: 2 };
32
+ function severityOf(value) {
33
+ return value === 'critical' || value === 'warning' || value === 'info' ? value : null;
34
+ }
35
+ function toDate(value) {
36
+ return value instanceof Date ? value : new Date(String(value));
37
+ }
38
+ /**
39
+ * Open/recurring issues (any fixability) plus the documents they reference, in
40
+ * two queries. "Reference" covers the issue entity AND, for cached insert-link
41
+ * fixes, the SOURCE document named by `pendingFix.patch.sourceEntityId` — that
42
+ * is the document the fix writes to and the one its fingerprint is built from,
43
+ * so without it `liveFingerprintFor` could never detect a stale link proposal.
44
+ */
45
+ export async function loadOpenFixableIssuesWithDocs(db) {
46
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- optional-model guard
47
+ const d = db;
48
+ const issues = d?.seoIssue?.findMany
49
+ ? await d.seoIssue.findMany({
50
+ where: { status: { in: ['open', 'recurring'] } },
51
+ orderBy: { lastDetectedAt: 'desc' },
52
+ take: 500,
53
+ })
54
+ : [];
55
+ const docIds = new Set();
56
+ for (const issue of issues) {
57
+ if (typeof issue.entityId === 'string' && issue.entityId.length > 0) {
58
+ docIds.add(issue.entityId);
59
+ }
60
+ const sourceId = pendingFixFromMetadata(issue.metadata)?.patch.sourceEntityId;
61
+ if (typeof sourceId === 'string' && sourceId.length > 0)
62
+ docIds.add(sourceId);
63
+ }
64
+ const docsById = new Map();
65
+ if (docIds.size > 0 && d?.document?.findMany) {
66
+ const docs = (await d.document.findMany({
67
+ where: { id: { in: [...docIds] }, deletedAt: null },
68
+ select: { id: true, updatedAt: true, title: true, slug: true, collection: true },
69
+ }));
70
+ for (const doc of docs) {
71
+ docsById.set(String(doc.id), {
72
+ id: String(doc.id),
73
+ updatedAt: toDate(doc.updatedAt),
74
+ title: typeof doc.title === 'string' ? doc.title : null,
75
+ slug: typeof doc.slug === 'string' ? doc.slug : null,
76
+ collection: String(doc.collection ?? ''),
77
+ });
78
+ }
79
+ }
80
+ return { issues, docsById };
81
+ }
82
+ /**
83
+ * The fingerprint a fresh suggestion for this issue would carry right now.
84
+ * Insert-link fixes are fingerprinted on the SOURCE document (the one that
85
+ * gets written), so their live value needs the cache to know the source.
86
+ * `loadOpenFixableIssuesWithDocs` loads sources alongside entities; if one is
87
+ * still missing (deleted since generation) we fall back to the cached value
88
+ * rather than guessing.
89
+ */
90
+ export function liveFingerprintFor(issue, cache, docsById) {
91
+ const entityId = typeof issue.entityId === 'string' ? issue.entityId : null;
92
+ if (!entityId)
93
+ return null;
94
+ if (cache &&
95
+ (INSERT_LINK_FIX_TYPES.has(issue.type) || cache.patch.fixStrategy === 'insert-link')) {
96
+ const sourceId = typeof cache.patch.sourceEntityId === 'string' ? cache.patch.sourceEntityId : null;
97
+ const source = sourceId ? docsById.get(sourceId) : undefined;
98
+ if (!source)
99
+ return cache.fingerprint;
100
+ return buildInsertLinkFingerprint(sourceId, typeof cache.patch.targetEntityId === 'string' ? cache.patch.targetEntityId : entityId, typeof cache.patch.anchorText === 'string' ? cache.patch.anchorText : '', source.updatedAt);
101
+ }
102
+ const doc = docsById.get(entityId);
103
+ if (!doc)
104
+ return null;
105
+ return buildFixFingerprint(entityId, issue.type, doc.updatedAt);
106
+ }
107
+ /**
108
+ * The marker `dismiss-fix` stores. After a dismissal there is no cache, so
109
+ * `selectEligibleForGeneration` compares the marker against the ENTITY-based
110
+ * live fingerprint. Insert-link caches are fingerprinted on the source
111
+ * document instead, so for them we store the entity-based value the no-cache
112
+ * path will compute; for every other type the cached fingerprint already is
113
+ * that value.
114
+ */
115
+ export function dismissalFingerprintFor(issue, cache, entityUpdatedAt) {
116
+ const isInsertLink = INSERT_LINK_FIX_TYPES.has(issue.type) || cache.patch.fixStrategy === 'insert-link';
117
+ if (!isInsertLink || !entityUpdatedAt || typeof issue.entityId !== 'string') {
118
+ return cache.fingerprint;
119
+ }
120
+ return buildFixFingerprint(issue.entityId, issue.type, entityUpdatedAt);
121
+ }
122
+ /**
123
+ * Issues batch generation should attempt: fixable, open, document present,
124
+ * no LIVE cache (a stale one counts as none), and neither sticky marker
125
+ * equals the live fingerprint. Ordered critical → warning → info.
126
+ */
127
+ export function selectEligibleForGeneration(issues, docsById) {
128
+ const out = [];
129
+ for (const issue of issues) {
130
+ if (issue.fixable !== true)
131
+ continue;
132
+ if (issue.status !== 'open' && issue.status !== 'recurring')
133
+ continue;
134
+ const cache = pendingFixFromMetadata(issue.metadata);
135
+ const live = liveFingerprintFor(issue, cache, docsById);
136
+ if (!live)
137
+ continue;
138
+ if (cache && cache.fingerprint === live)
139
+ continue;
140
+ const sticky = stickyFingerprintsFromMetadata(issue.metadata);
141
+ if (sticky.dismissed === live || sticky.generateSkipped === live)
142
+ continue;
143
+ out.push({ issue, liveFingerprint: live });
144
+ }
145
+ out.sort((a, b) => (SEVERITY_RANK[String(a.issue.severity)] ?? 99) -
146
+ (SEVERITY_RANK[String(b.issue.severity)] ?? 99));
147
+ return out;
148
+ }
149
+ function issueFixProposal(issue, cache, docsById) {
150
+ const live = liveFingerprintFor(issue, cache, docsById);
151
+ const doc = typeof issue.entityId === 'string' ? docsById.get(issue.entityId) : undefined;
152
+ return {
153
+ id: `issue:${issue.id}`,
154
+ kind: 'issue-fix',
155
+ title: String(issue.title ?? issue.type),
156
+ entity: {
157
+ type: typeof issue.entityType === 'string' ? issue.entityType : null,
158
+ id: typeof issue.entityId === 'string' ? issue.entityId : null,
159
+ title: (typeof issue.entityTitle === 'string' ? issue.entityTitle : null) ?? doc?.title ?? null,
160
+ url: typeof issue.url === 'string' ? issue.url : null,
161
+ },
162
+ changes: cache.changes,
163
+ justification: cache.justification,
164
+ source: cache.source,
165
+ confidence: null,
166
+ brandAlignment: cache.brandAlignment,
167
+ generatedAt: cache.generatedAt,
168
+ fingerprint: cache.fingerprint,
169
+ stale: live !== null && live !== cache.fingerprint,
170
+ issueType: String(issue.type),
171
+ severity: severityOf(issue.severity),
172
+ issueId: String(issue.id),
173
+ suggestionId: null,
174
+ };
175
+ }
176
+ function redirectProposal(s) {
177
+ const fromPath = String(s.fromPath ?? '');
178
+ const toPath = String(s.toPath ?? '');
179
+ return {
180
+ id: `redirect:${String(s.id)}`,
181
+ kind: 'redirect',
182
+ title: `Redirect ${fromPath} → ${toPath}`,
183
+ entity: { type: null, id: null, title: null, url: fromPath },
184
+ changes: [{ field: 'redirect', label: 'Redirect', before: fromPath, after: toPath }],
185
+ justification: typeof s.reason === 'string' ? s.reason : '',
186
+ source: 'ai-404-recovery',
187
+ // Stored as a 0-100 Int; the UI contract is a 0-1 fraction.
188
+ confidence: Number(s.confidence ?? 0) / 100,
189
+ brandAlignment: null,
190
+ generatedAt: s.createdAt ? toDate(s.createdAt).toISOString() : null,
191
+ fingerprint: null,
192
+ stale: false,
193
+ issueType: null,
194
+ severity: null,
195
+ issueId: null,
196
+ suggestionId: String(s.id),
197
+ };
198
+ }
199
+ export async function listSeoProposals(db) {
200
+ const { issues, docsById } = await loadOpenFixableIssuesWithDocs(db);
201
+ const fixes = [];
202
+ for (const issue of issues) {
203
+ const cache = pendingFixFromMetadata(issue.metadata);
204
+ if (!cache)
205
+ continue;
206
+ fixes.push(issueFixProposal(issue, cache, docsById));
207
+ }
208
+ fixes.sort((a, b) => {
209
+ const band = (SEVERITY_RANK[String(a.severity)] ?? 99) - (SEVERITY_RANK[String(b.severity)] ?? 99);
210
+ if (band !== 0)
211
+ return band;
212
+ return Number(a.stale) - Number(b.stale);
213
+ });
214
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- optional-model guard
215
+ const d = db;
216
+ const redirectRows = d?.redirectSuggestion?.findMany
217
+ ? await d.redirectSuggestion.findMany({
218
+ where: { status: 'open' },
219
+ orderBy: { confidence: 'desc' },
220
+ })
221
+ : [];
222
+ // Sort defensively: the DB already orders by confidence, but the shape of the
223
+ // payload should not depend on that (fakes in tests do not honor orderBy).
224
+ const redirects = redirectRows
225
+ .map(redirectProposal)
226
+ .sort((a, b) => (b.confidence ?? 0) - (a.confidence ?? 0));
227
+ const [issueGov, redirectGov, overrides, appliedLast7d] = await Promise.all([
228
+ getFeatureGovernance(db, ISSUE_FIX_FEATURE_KEY),
229
+ getFeatureGovernance(db, REDIRECT_FEATURE_KEY),
230
+ // `getSeoOverrides` already returns null on any read failure.
231
+ getSeoOverrides(db),
232
+ countAutopilotAppliedLast7d(db),
233
+ ]);
234
+ const settings = resolveSeoModuleSettings(overrides);
235
+ const stale = fixes.filter((p) => p.stale).length;
236
+ return {
237
+ proposals: [...fixes, ...redirects],
238
+ counts: {
239
+ issueFixes: fixes.length - stale,
240
+ redirects: redirects.length,
241
+ stale,
242
+ eligibleForGeneration: selectEligibleForGeneration(issues, docsById).length,
243
+ },
244
+ governance: {
245
+ issueFixBulk: issueGov.allowBulkApply,
246
+ redirectBulk: redirectGov.allowBulkApply,
247
+ },
248
+ autopilot: {
249
+ enabled: settings.autopilot.enabled,
250
+ pausedReason: settings.autopilot.pausedAt ? settings.autopilot.pausedReason : null,
251
+ lastRunAt: settings.autopilotLastRunAt,
252
+ appliedLast7d,
253
+ },
254
+ };
255
+ }
256
+ /**
257
+ * Cheap counts for the overview badge: every cached fix (stale ones are still
258
+ * rows in the inbox) + open redirect suggestions. Callers pass the open issues
259
+ * they already loaded so this adds at most one `count` query.
260
+ */
261
+ export async function countPendingProposals(db, openIssues) {
262
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- optional-model guard
263
+ const d = db;
264
+ const redirects = d?.redirectSuggestion?.count
265
+ ? ((await d.redirectSuggestion.count({ where: { status: 'open' } })) ?? 0)
266
+ : 0;
267
+ const issueFixes = openIssues.filter((i) => pendingFixFromMetadata(i.metadata) !== null).length;
268
+ return { issueFixes, redirects };
269
+ }
270
+ //# sourceMappingURL=proposals.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proposals.js","sourceRoot":"","sources":["../../src/seo/proposals.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prenta/core",
3
- "version": "5.5.0",
3
+ "version": "5.7.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -0,0 +1,35 @@
1
+ -- SEO Autopilot ledger (spec: 2026-09-07-auto-apply-policy-engine-design.md §4).
2
+ -- One row per unattended write. Additive; no backfill.
3
+
4
+ CREATE TABLE "prenta_seo_auto_applies" (
5
+ "id" TEXT NOT NULL,
6
+ "kind" TEXT NOT NULL,
7
+ "issueId" TEXT,
8
+ "issueType" TEXT NOT NULL,
9
+ "entityId" TEXT,
10
+ "collection" TEXT,
11
+ "path" TEXT,
12
+ "redirectId" TEXT,
13
+ "before" JSONB,
14
+ "after" JSONB NOT NULL,
15
+ "gate" JSONB NOT NULL,
16
+ "status" TEXT NOT NULL,
17
+ "outcome" TEXT NOT NULL DEFAULT 'pending',
18
+ "outcomeMetrics" JSONB,
19
+ "outcomeCheckedAt" TIMESTAMP(3),
20
+ "versionId" TEXT,
21
+ "appliedById" TEXT NOT NULL,
22
+ "appliedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
23
+ "revertedAt" TIMESTAMP(3),
24
+ "revertedById" TEXT,
25
+ "revertReason" TEXT,
26
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
27
+ "updatedAt" TIMESTAMP(3) NOT NULL,
28
+
29
+ CONSTRAINT "prenta_seo_auto_applies_pkey" PRIMARY KEY ("id")
30
+ );
31
+
32
+ CREATE INDEX "prenta_seo_auto_applies_status_appliedAt_idx" ON "prenta_seo_auto_applies"("status", "appliedAt");
33
+ CREATE INDEX "prenta_seo_auto_applies_entityId_idx" ON "prenta_seo_auto_applies"("entityId");
34
+ CREATE INDEX "prenta_seo_auto_applies_outcome_appliedAt_idx" ON "prenta_seo_auto_applies"("outcome", "appliedAt");
35
+ CREATE INDEX "prenta_seo_auto_applies_issueId_idx" ON "prenta_seo_auto_applies"("issueId");
@@ -640,6 +640,49 @@ model RedirectSuggestion {
640
640
  @@map("prenta_redirect_suggestions")
641
641
  }
642
642
 
643
+ /// One row per change SEO Autopilot wrote (spec 2026-09-07-auto-apply-policy-engine §4).
644
+ /// `before`/`after` hold the field-level diff for issue fixes, or the created
645
+ /// redirect (after) for redirects. `before` is null for redirects: autopilot
646
+ /// never overwrites an existing redirect. Rows are never deleted; revert flips `status`.
647
+ model SeoAutoApply {
648
+ id String @id @default(cuid())
649
+ // 'issue-fix' | 'redirect'
650
+ kind String
651
+ issueId String?
652
+ // AutopilotIssueType
653
+ issueType String
654
+ entityId String?
655
+ collection String?
656
+ // Public path at apply time — outcome-watch key into prenta_search_perf_daily.
657
+ path String?
658
+ redirectId String?
659
+ before Json?
660
+ after Json
661
+ // { source, brandAlignment, confidence, checks }
662
+ gate Json
663
+ // 'applied' | 'verify_failed' | 'reverted' | 'outcome_reverted'
664
+ status String
665
+ // 'pending' | 'held' | 'regressed' | 'insufficient_data' | 'skipped'
666
+ outcome String @default("pending")
667
+ outcomeMetrics Json?
668
+ outcomeCheckedAt DateTime?
669
+ versionId String?
670
+ appliedById String
671
+ appliedAt DateTime @default(now())
672
+ revertedAt DateTime?
673
+ revertedById String?
674
+ // 'verify_failed' | 'outcome_regression' | 'manual'
675
+ revertReason String?
676
+ createdAt DateTime @default(now())
677
+ updatedAt DateTime @updatedAt
678
+
679
+ @@index([status, appliedAt])
680
+ @@index([entityId])
681
+ @@index([outcome, appliedAt])
682
+ @@index([issueId])
683
+ @@map("prenta_seo_auto_applies")
684
+ }
685
+
643
686
  // A versioned snapshot of a form's field schema. Saving a form's schema in
644
687
  // the admin creates a new row here; submissions reference the version they
645
688
  // were captured against so historical entries always render with the fields