@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.
- package/CHANGELOG.md +31 -0
- package/dist/ai/governance.d.ts +6 -0
- package/dist/ai/governance.d.ts.map +1 -1
- package/dist/ai/governance.js +19 -0
- package/dist/ai/governance.js.map +1 -1
- package/dist/api/handlers.d.ts.map +1 -1
- package/dist/api/handlers.js +2 -0
- package/dist/api/handlers.js.map +1 -1
- package/dist/api/routes/redirects.d.ts.map +1 -1
- package/dist/api/routes/redirects.js +89 -93
- package/dist/api/routes/redirects.js.map +1 -1
- package/dist/api/routes/seo-autopilot.d.ts +12 -0
- package/dist/api/routes/seo-autopilot.d.ts.map +1 -0
- package/dist/api/routes/seo-autopilot.js +257 -0
- package/dist/api/routes/seo-autopilot.js.map +1 -0
- package/dist/api/routes/seo.d.ts.map +1 -1
- package/dist/api/routes/seo.js +195 -360
- package/dist/api/routes/seo.js.map +1 -1
- package/dist/db/model-types.d.ts +26 -0
- package/dist/db/model-types.d.ts.map +1 -1
- package/dist/generated/browser.d.ts +8 -0
- package/dist/generated/client.d.ts +8 -0
- package/dist/generated/internal/class.d.ts +11 -0
- package/dist/generated/internal/class.js +4 -4
- package/dist/generated/internal/prismaNamespace.d.ts +120 -1
- package/dist/generated/internal/prismaNamespace.js +42 -0
- package/dist/generated/internal/prismaNamespaceBrowser.d.ts +44 -0
- package/dist/generated/internal/prismaNamespaceBrowser.js +42 -0
- package/dist/generated/models/SeoAutoApply.d.ts +1495 -0
- package/dist/generated/models/SeoAutoApply.js +1 -0
- package/dist/generated/models.d.ts +1 -0
- package/dist/redirects/recovery.d.ts +15 -9
- package/dist/redirects/recovery.d.ts.map +1 -1
- package/dist/redirects/recovery.js +15 -61
- package/dist/redirects/recovery.js.map +1 -1
- package/dist/redirects/suggestion-accept.d.ts +29 -0
- package/dist/redirects/suggestion-accept.d.ts.map +1 -0
- package/dist/redirects/suggestion-accept.js +108 -0
- package/dist/redirects/suggestion-accept.js.map +1 -0
- package/dist/seo/audit-runner.d.ts.map +1 -1
- package/dist/seo/audit-runner.js +0 -2
- package/dist/seo/audit-runner.js.map +1 -1
- package/dist/seo/autopilot-gate.d.ts +101 -0
- package/dist/seo/autopilot-gate.d.ts.map +1 -0
- package/dist/seo/autopilot-gate.js +272 -0
- package/dist/seo/autopilot-gate.js.map +1 -0
- package/dist/seo/autopilot-ledger.d.ts +74 -0
- package/dist/seo/autopilot-ledger.d.ts.map +1 -0
- package/dist/seo/autopilot-ledger.js +118 -0
- package/dist/seo/autopilot-ledger.js.map +1 -0
- package/dist/seo/autopilot-outcome.d.ts +93 -0
- package/dist/seo/autopilot-outcome.d.ts.map +1 -0
- package/dist/seo/autopilot-outcome.js +206 -0
- package/dist/seo/autopilot-outcome.js.map +1 -0
- package/dist/seo/autopilot-policy.d.ts +169 -0
- package/dist/seo/autopilot-policy.d.ts.map +1 -0
- package/dist/seo/autopilot-policy.js +267 -0
- package/dist/seo/autopilot-policy.js.map +1 -0
- package/dist/seo/autopilot-revert.d.ts +40 -0
- package/dist/seo/autopilot-revert.d.ts.map +1 -0
- package/dist/seo/autopilot-revert.js +155 -0
- package/dist/seo/autopilot-revert.js.map +1 -0
- package/dist/seo/autopilot.d.ts +91 -0
- package/dist/seo/autopilot.d.ts.map +1 -0
- package/dist/seo/autopilot.js +1060 -0
- package/dist/seo/autopilot.js.map +1 -0
- package/dist/seo/config-store.d.ts +35 -2
- package/dist/seo/config-store.d.ts.map +1 -1
- package/dist/seo/config-store.js +58 -1
- package/dist/seo/config-store.js.map +1 -1
- package/dist/seo/deep-equal.d.ts +12 -0
- package/dist/seo/deep-equal.d.ts.map +1 -0
- package/dist/seo/deep-equal.js +42 -0
- package/dist/seo/deep-equal.js.map +1 -0
- package/dist/seo/digest.d.ts +27 -0
- package/dist/seo/digest.d.ts.map +1 -1
- package/dist/seo/digest.js +77 -1
- package/dist/seo/digest.js.map +1 -1
- package/dist/seo/index.d.ts +20 -2
- package/dist/seo/index.d.ts.map +1 -1
- package/dist/seo/index.js +12 -1
- package/dist/seo/index.js.map +1 -1
- package/dist/seo/issue-fix-ai.d.ts +40 -1
- package/dist/seo/issue-fix-ai.d.ts.map +1 -1
- package/dist/seo/issue-fix-ai.js +53 -1
- package/dist/seo/issue-fix-ai.js.map +1 -1
- package/dist/seo/issue-fix-apply.d.ts +104 -0
- package/dist/seo/issue-fix-apply.d.ts.map +1 -0
- package/dist/seo/issue-fix-apply.js +406 -0
- package/dist/seo/issue-fix-apply.js.map +1 -0
- package/dist/seo/issue-fix.d.ts +9 -0
- package/dist/seo/issue-fix.d.ts.map +1 -1
- package/dist/seo/issue-fix.js +1 -0
- package/dist/seo/issue-fix.js.map +1 -1
- package/dist/seo/proposals-bulk-apply.d.ts +52 -0
- package/dist/seo/proposals-bulk-apply.d.ts.map +1 -0
- package/dist/seo/proposals-bulk-apply.js +183 -0
- package/dist/seo/proposals-bulk-apply.js.map +1 -0
- package/dist/seo/proposals-generate.d.ts +45 -0
- package/dist/seo/proposals-generate.d.ts.map +1 -0
- package/dist/seo/proposals-generate.js +144 -0
- package/dist/seo/proposals-generate.js.map +1 -0
- package/dist/seo/proposals.d.ts +125 -0
- package/dist/seo/proposals.d.ts.map +1 -0
- package/dist/seo/proposals.js +270 -0
- package/dist/seo/proposals.js.map +1 -0
- package/package.json +1 -1
- package/prisma/migrations/0017_seo_auto_applies/migration.sql +35 -0
- package/prisma/schema.prisma +43 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { assertBulkApplyAllowed as defaultAssertBulk } from '../ai/governance.js';
|
|
2
|
+
import { acceptRedirectSuggestion as defaultAcceptRedirect } from '../redirects/suggestion-accept.js';
|
|
3
|
+
import { applyIssueFix as defaultApplyIssue } from './issue-fix-apply.js';
|
|
4
|
+
import { pendingFixFromMetadata } from './issue-fix-ai.js';
|
|
5
|
+
import { PROPOSAL_BATCH_TIME_BUDGET_MS } from './proposals-generate.js';
|
|
6
|
+
import { ISSUE_FIX_FEATURE_KEY, REDIRECT_FEATURE_KEY } from './proposals.js';
|
|
7
|
+
export const PROPOSAL_BULK_APPLY_MAX = 50;
|
|
8
|
+
const BULK_APPLY_CONCURRENCY = 3;
|
|
9
|
+
const ADMIN_ROLES = new Set(['OWNER', 'ADMIN']);
|
|
10
|
+
const REDIRECT_MODEL_MISSING_MESSAGE = 'The "RedirectSuggestion" model is not available in your Prisma schema.';
|
|
11
|
+
function toDate(value) {
|
|
12
|
+
return value instanceof Date ? value : new Date(String(value));
|
|
13
|
+
}
|
|
14
|
+
const keyOf = (item) => `${item.kind}:${item.id}`;
|
|
15
|
+
/**
|
|
16
|
+
* Same probe as `hasModel()` in `api/route-helpers.ts` (and the local copy in
|
|
17
|
+
* `redirects/suggestion-accept.ts`): `guardedDb()` hands back a throwing Proxy
|
|
18
|
+
* for models missing from the consumer schema, so a plain truthiness check
|
|
19
|
+
* would report them present. The accept ROUTE gates this; a direct service
|
|
20
|
+
* caller must gate it itself, and the seo layer must not depend on the API layer.
|
|
21
|
+
*/
|
|
22
|
+
function hasModel(db, model) {
|
|
23
|
+
try {
|
|
24
|
+
const delegate = db[model];
|
|
25
|
+
return Boolean(delegate) && typeof delegate?.findMany === 'function';
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export async function bulkApplySeoProposals(db, input, deps = {}) {
|
|
32
|
+
const applyIssue = deps.applyIssue ?? defaultApplyIssue;
|
|
33
|
+
const acceptRedirect = deps.acceptRedirect ?? defaultAcceptRedirect;
|
|
34
|
+
const assertBulk = deps.assertBulk ?? defaultAssertBulk;
|
|
35
|
+
const now = deps.now ?? Date.now;
|
|
36
|
+
const budgetMs = deps.budgetMs ?? PROPOSAL_BATCH_TIME_BUDGET_MS;
|
|
37
|
+
const concurrency = Math.max(1, deps.concurrency ?? BULK_APPLY_CONCURRENCY);
|
|
38
|
+
const { actor } = input;
|
|
39
|
+
const startedAt = now();
|
|
40
|
+
// Dedupe by `kind:id`, keeping first-occurrence order. Results are keyed the
|
|
41
|
+
// same way, so a repeated item would run twice in one group — the second run
|
|
42
|
+
// finds the cache consumed and its `not-fixable`/`not-found` would overwrite
|
|
43
|
+
// the first run's real `applied`, and governance would count it as extra load.
|
|
44
|
+
const seen = new Set();
|
|
45
|
+
const items = input.items.filter((item) => {
|
|
46
|
+
const key = keyOf(item);
|
|
47
|
+
if (seen.has(key))
|
|
48
|
+
return false;
|
|
49
|
+
seen.add(key);
|
|
50
|
+
return true;
|
|
51
|
+
});
|
|
52
|
+
// A single item is the ordinary approve path; governance only weighs in on
|
|
53
|
+
// batches. Keys are always derived from the kinds actually present —
|
|
54
|
+
// `assertBulkApplyAllowed` passes an empty key list, so never send one.
|
|
55
|
+
if (items.length > 1) {
|
|
56
|
+
const keys = [];
|
|
57
|
+
if (items.some((i) => i.kind === 'issue-fix'))
|
|
58
|
+
keys.push(ISSUE_FIX_FEATURE_KEY);
|
|
59
|
+
if (items.some((i) => i.kind === 'redirect'))
|
|
60
|
+
keys.push(REDIRECT_FEATURE_KEY);
|
|
61
|
+
const decision = await assertBulk(db, keys, items.length);
|
|
62
|
+
if (!decision.ok)
|
|
63
|
+
return { blocked: true, reason: decision.reason };
|
|
64
|
+
}
|
|
65
|
+
const redirectModelAvailable = items.some((i) => i.kind === 'redirect')
|
|
66
|
+
? hasModel(db, 'redirectSuggestion')
|
|
67
|
+
: true;
|
|
68
|
+
// Resolve which document each issue-fix item writes (insert-link fixes
|
|
69
|
+
// write the SOURCE document) and snapshot those documents' updatedAt.
|
|
70
|
+
const issueIds = items.filter((i) => i.kind === 'issue-fix').map((i) => i.id);
|
|
71
|
+
const writeTargetByIssue = new Map();
|
|
72
|
+
if (issueIds.length > 0) {
|
|
73
|
+
const rows = await db.seoIssue.findMany({ where: { id: { in: issueIds } } });
|
|
74
|
+
for (const row of rows) {
|
|
75
|
+
const cache = pendingFixFromMetadata(row.metadata);
|
|
76
|
+
const source = cache && typeof cache.patch.sourceEntityId === 'string' ? cache.patch.sourceEntityId : null;
|
|
77
|
+
const target = source ?? row.entityId ?? null;
|
|
78
|
+
if (target)
|
|
79
|
+
writeTargetByIssue.set(row.id, target);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const snapshotByDoc = new Map();
|
|
83
|
+
const docIds = [...new Set(writeTargetByIssue.values())];
|
|
84
|
+
if (docIds.length > 0) {
|
|
85
|
+
const docs = (await db.document.findMany({
|
|
86
|
+
where: { id: { in: docIds }, deletedAt: null },
|
|
87
|
+
select: { id: true, updatedAt: true },
|
|
88
|
+
}));
|
|
89
|
+
for (const doc of docs)
|
|
90
|
+
snapshotByDoc.set(doc.id, toDate(doc.updatedAt));
|
|
91
|
+
}
|
|
92
|
+
// Group: issue fixes by write-target document; every redirect is its own group.
|
|
93
|
+
const groups = new Map();
|
|
94
|
+
for (const item of items) {
|
|
95
|
+
const key = item.kind === 'redirect'
|
|
96
|
+
? `redirect:${item.id}`
|
|
97
|
+
: `doc:${writeTargetByIssue.get(item.id) ?? `issue:${item.id}`}`;
|
|
98
|
+
const list = groups.get(key);
|
|
99
|
+
if (list)
|
|
100
|
+
list.push(item);
|
|
101
|
+
else
|
|
102
|
+
groups.set(key, [item]);
|
|
103
|
+
}
|
|
104
|
+
const resultByKey = new Map();
|
|
105
|
+
for (const item of items) {
|
|
106
|
+
resultByKey.set(keyOf(item), { kind: item.kind, id: item.id, status: 'pending' });
|
|
107
|
+
}
|
|
108
|
+
async function runItem(item) {
|
|
109
|
+
try {
|
|
110
|
+
if (item.kind === 'redirect') {
|
|
111
|
+
if (!ADMIN_ROLES.has(actor.role)) {
|
|
112
|
+
return {
|
|
113
|
+
kind: item.kind,
|
|
114
|
+
id: item.id,
|
|
115
|
+
status: 'not-allowed',
|
|
116
|
+
reason: 'Accepting redirects requires an admin role.',
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
if (!redirectModelAvailable) {
|
|
120
|
+
return {
|
|
121
|
+
kind: item.kind,
|
|
122
|
+
id: item.id,
|
|
123
|
+
status: 'error',
|
|
124
|
+
reason: REDIRECT_MODEL_MISSING_MESSAGE,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
const r = await acceptRedirect(db, {
|
|
128
|
+
suggestionId: item.id,
|
|
129
|
+
actor: { userId: actor.userId },
|
|
130
|
+
});
|
|
131
|
+
if (r.status === 'accepted')
|
|
132
|
+
return { kind: item.kind, id: item.id, status: 'applied' };
|
|
133
|
+
if (r.status === 'not-found')
|
|
134
|
+
return { kind: item.kind, id: item.id, status: 'not-found' };
|
|
135
|
+
return { kind: item.kind, id: item.id, status: r.status, reason: r.reason };
|
|
136
|
+
}
|
|
137
|
+
const target = writeTargetByIssue.get(item.id);
|
|
138
|
+
const r = await applyIssue(db, {
|
|
139
|
+
issueId: item.id,
|
|
140
|
+
fingerprint: item.fingerprint,
|
|
141
|
+
actor,
|
|
142
|
+
staleAgainst: target ? snapshotByDoc.get(target) : undefined,
|
|
143
|
+
});
|
|
144
|
+
if (r.status === 'applied')
|
|
145
|
+
return { kind: item.kind, id: item.id, status: 'applied' };
|
|
146
|
+
if (r.status === 'plan-required') {
|
|
147
|
+
return { kind: item.kind, id: item.id, status: 'plan-required', reason: r.denial.message };
|
|
148
|
+
}
|
|
149
|
+
return { kind: item.kind, id: item.id, status: r.status, reason: r.reason };
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
console.warn('[seo/proposals-bulk-apply] item apply failed', { kind: item.kind, id: item.id }, err);
|
|
153
|
+
return {
|
|
154
|
+
kind: item.kind,
|
|
155
|
+
id: item.id,
|
|
156
|
+
status: 'error',
|
|
157
|
+
reason: err instanceof Error ? err.message : 'Apply failed',
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
const groupList = [...groups.values()];
|
|
162
|
+
let cursor = 0;
|
|
163
|
+
async function worker() {
|
|
164
|
+
while (cursor < groupList.length) {
|
|
165
|
+
const group = groupList[cursor++];
|
|
166
|
+
for (const item of group) {
|
|
167
|
+
if (now() - startedAt >= budgetMs)
|
|
168
|
+
return;
|
|
169
|
+
resultByKey.set(keyOf(item), await runItem(item));
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, groupList.length) }, worker));
|
|
174
|
+
const results = items.map((item) => resultByKey.get(keyOf(item)));
|
|
175
|
+
return {
|
|
176
|
+
blocked: false,
|
|
177
|
+
results,
|
|
178
|
+
applied: results.filter((r) => r.status === 'applied').length,
|
|
179
|
+
pending: results.filter((r) => r.status === 'pending').length,
|
|
180
|
+
failed: results.filter((r) => r.status !== 'applied' && r.status !== 'pending').length,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
//# sourceMappingURL=proposals-bulk-apply.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proposals-bulk-apply.js","sourceRoot":"","sources":["../../src/seo/proposals-bulk-apply.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Batch "Generate proposals": run `suggestIssueFix` for eligible open issues
|
|
3
|
+
* and cache the results so the proposals inbox fills without opening issues
|
|
4
|
+
* one by one. Human-triggered only (route is confirm-gated, plan-gated,
|
|
5
|
+
* rate-limited). Bounded three ways — per-call cap, concurrency, wall clock —
|
|
6
|
+
* because it runs inside a 30 s function budget and spends AI tokens.
|
|
7
|
+
*
|
|
8
|
+
* AI failures are split by scope. Provider-level codes (`HALTING_AI_ERROR_CODES`)
|
|
9
|
+
* mean every further call this pass would fail too, so the batch halts and
|
|
10
|
+
* reports `haltedReason`. A per-issue `error` (bad model output for one
|
|
11
|
+
* document) is counted as failed AND stamped with `generateSkippedFingerprint`,
|
|
12
|
+
* so the same issue cannot pin the severity-sorted queue head on every pass —
|
|
13
|
+
* it is retried once the document changes, or when an editor explicitly runs
|
|
14
|
+
* `suggest-fix` (a new cache clears the marker).
|
|
15
|
+
*/
|
|
16
|
+
import type { PrentaDB } from '../db/client-types.js';
|
|
17
|
+
import { loadIssueFixContext as defaultLoadIssueFixContext } from './issue-fix-apply.js';
|
|
18
|
+
import { suggestIssueFix as defaultSuggestIssueFix } from './issue-fix.js';
|
|
19
|
+
export declare const PROPOSAL_GENERATE_BATCH_MAX = 10;
|
|
20
|
+
export declare const PROPOSAL_GENERATE_CONCURRENCY = 3;
|
|
21
|
+
export declare const PROPOSAL_BATCH_TIME_BUDGET_MS = 20000;
|
|
22
|
+
export interface GenerateProposalsResult {
|
|
23
|
+
generated: number;
|
|
24
|
+
skipped: number;
|
|
25
|
+
failed: number;
|
|
26
|
+
/** Eligible issues not processed this call (cap, budget, or early halt). */
|
|
27
|
+
remaining: number;
|
|
28
|
+
/** Set when a provider-level AI error (see `HALTING_AI_ERROR_CODES`) stopped the batch early. */
|
|
29
|
+
haltedReason: string | null;
|
|
30
|
+
}
|
|
31
|
+
export interface GenerateProposalsDeps {
|
|
32
|
+
/** Injected for tests; defaults call the real suggestIssueFix / loadIssueFixContext. */
|
|
33
|
+
suggest?: typeof defaultSuggestIssueFix;
|
|
34
|
+
loadContext?: typeof defaultLoadIssueFixContext;
|
|
35
|
+
now?: () => number;
|
|
36
|
+
max?: number;
|
|
37
|
+
concurrency?: number;
|
|
38
|
+
budgetMs?: number;
|
|
39
|
+
/** Autopilot: restrict generation to these issue types. Undefined = every fixable type. */
|
|
40
|
+
onlyTypes?: ReadonlySet<string>;
|
|
41
|
+
}
|
|
42
|
+
export declare function generateSeoProposals(db: PrentaDB, actor: {
|
|
43
|
+
userId: string;
|
|
44
|
+
}, deps?: GenerateProposalsDeps): Promise<GenerateProposalsResult>;
|
|
45
|
+
//# sourceMappingURL=proposals-generate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proposals-generate.d.ts","sourceRoot":"","sources":["../../src/seo/proposals-generate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AAErD,OAAO,EAAE,mBAAmB,IAAI,0BAA0B,EAAE,MAAM,sBAAsB,CAAA;AACxF,OAAO,EAAE,eAAe,IAAI,sBAAsB,EAAE,MAAM,gBAAgB,CAAA;AAa1E,eAAO,MAAM,2BAA2B,KAAK,CAAA;AAC7C,eAAO,MAAM,6BAA6B,IAAI,CAAA;AAC9C,eAAO,MAAM,6BAA6B,QAAS,CAAA;AAcnD,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,4EAA4E;IAC5E,SAAS,EAAE,MAAM,CAAA;IACjB,iGAAiG;IACjG,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;CAC5B;AAED,MAAM,WAAW,qBAAqB;IACpC,wFAAwF;IACxF,OAAO,CAAC,EAAE,OAAO,sBAAsB,CAAA;IACvC,WAAW,CAAC,EAAE,OAAO,0BAA0B,CAAA;IAC/C,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;IAClB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,2FAA2F;IAC3F,SAAS,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;CAChC;AAUD,wBAAsB,oBAAoB,CACxC,EAAE,EAAE,QAAQ,EACZ,KAAK,EAAE;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,EACzB,IAAI,GAAE,qBAA0B,GAC/B,OAAO,CAAC,uBAAuB,CAAC,CA2IlC"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { getAIConfig } from '../ai/config-store.js';
|
|
2
|
+
import { loadIssueFixContext as defaultLoadIssueFixContext } from './issue-fix-apply.js';
|
|
3
|
+
import { suggestIssueFix as defaultSuggestIssueFix } from './issue-fix.js';
|
|
4
|
+
import { pendingFixFromMetadata, pendingFixToMetadata, scoreFixBrandAlignment, stickyFingerprintsFromMetadata, } from './issue-fix-ai.js';
|
|
5
|
+
import { loadOpenFixableIssuesWithDocs, selectEligibleForGeneration, } from './proposals.js';
|
|
6
|
+
export const PROPOSAL_GENERATE_BATCH_MAX = 10;
|
|
7
|
+
export const PROPOSAL_GENERATE_CONCURRENCY = 3;
|
|
8
|
+
export const PROPOSAL_BATCH_TIME_BUDGET_MS = 20_000;
|
|
9
|
+
/**
|
|
10
|
+
* `aiError.code`s that describe the provider / governance state rather than
|
|
11
|
+
* one issue: every further `suggestIssueFix` call in this pass is guaranteed
|
|
12
|
+
* to fail the same way, so the batch stops instead of burning the queue.
|
|
13
|
+
*/
|
|
14
|
+
const HALTING_AI_ERROR_CODES = new Set([
|
|
15
|
+
'rate_limited',
|
|
16
|
+
'unavailable',
|
|
17
|
+
'feature_disabled',
|
|
18
|
+
'budget_exceeded',
|
|
19
|
+
]);
|
|
20
|
+
function asRecord(value) {
|
|
21
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
22
|
+
? value
|
|
23
|
+
: {};
|
|
24
|
+
}
|
|
25
|
+
export async function generateSeoProposals(db, actor, deps = {}) {
|
|
26
|
+
const suggest = deps.suggest ?? defaultSuggestIssueFix;
|
|
27
|
+
const loadContext = deps.loadContext ?? defaultLoadIssueFixContext;
|
|
28
|
+
const now = deps.now ?? Date.now;
|
|
29
|
+
const max = deps.max ?? PROPOSAL_GENERATE_BATCH_MAX;
|
|
30
|
+
const concurrency = Math.max(1, deps.concurrency ?? PROPOSAL_GENERATE_CONCURRENCY);
|
|
31
|
+
const budgetMs = deps.budgetMs ?? PROPOSAL_BATCH_TIME_BUDGET_MS;
|
|
32
|
+
const startedAt = now();
|
|
33
|
+
const { issues, docsById } = await loadOpenFixableIssuesWithDocs(db);
|
|
34
|
+
const allEligible = selectEligibleForGeneration(issues, docsById);
|
|
35
|
+
// Filter before the cap so `remaining` counts only issues this caller could
|
|
36
|
+
// ever generate (autopilot passes the policy's opted-in types).
|
|
37
|
+
const onlyTypes = deps.onlyTypes;
|
|
38
|
+
const eligible = onlyTypes
|
|
39
|
+
? allEligible.filter((e) => onlyTypes.has(String(e.issue.type)))
|
|
40
|
+
: allEligible;
|
|
41
|
+
const queue = eligible.slice(0, max);
|
|
42
|
+
// Read once per pass, not once per issue. Fail open: an unreadable AI config
|
|
43
|
+
// leaves every proposal unscored (brandAlignment: null) rather than aborting.
|
|
44
|
+
const brandVoice = await getAIConfig(db)
|
|
45
|
+
.then((c) => c.settings.brandVoice)
|
|
46
|
+
.catch((err) => {
|
|
47
|
+
console.warn('[seo/proposals-generate] AI config unavailable for brand alignment', err);
|
|
48
|
+
return undefined;
|
|
49
|
+
});
|
|
50
|
+
const result = {
|
|
51
|
+
generated: 0,
|
|
52
|
+
skipped: 0,
|
|
53
|
+
failed: 0,
|
|
54
|
+
remaining: 0,
|
|
55
|
+
haltedReason: null,
|
|
56
|
+
};
|
|
57
|
+
let processed = 0;
|
|
58
|
+
let halted = false;
|
|
59
|
+
async function writeMetadata(issueId, patch) {
|
|
60
|
+
const fresh = await db.seoIssue.findUnique({ where: { id: issueId } });
|
|
61
|
+
await db.seoIssue.update({
|
|
62
|
+
where: { id: issueId },
|
|
63
|
+
data: { metadata: { ...asRecord(fresh?.metadata), ...patch } },
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
async function processOne(item) {
|
|
67
|
+
const { issue, liveFingerprint } = item;
|
|
68
|
+
// Double-click guard: since our eligibility scan another request may have
|
|
69
|
+
// cached this issue (never spend tokens twice on one document state) or
|
|
70
|
+
// the user may have dismissed it / a prior pass marked it non-cacheable.
|
|
71
|
+
// Writing a cache would clear those sticky markers, so honour them here.
|
|
72
|
+
const fresh = await db.seoIssue.findUnique({ where: { id: issue.id } });
|
|
73
|
+
const freshCache = pendingFixFromMetadata(fresh?.metadata);
|
|
74
|
+
if (freshCache && freshCache.fingerprint === liveFingerprint)
|
|
75
|
+
return 'skipped';
|
|
76
|
+
const sticky = stickyFingerprintsFromMetadata(fresh?.metadata);
|
|
77
|
+
if (sticky.dismissed === liveFingerprint || sticky.generateSkipped === liveFingerprint) {
|
|
78
|
+
return 'skipped';
|
|
79
|
+
}
|
|
80
|
+
const loaded = await loadContext(db, issue);
|
|
81
|
+
if (!loaded.ok) {
|
|
82
|
+
await writeMetadata(issue.id, { generateSkippedFingerprint: liveFingerprint });
|
|
83
|
+
return 'skipped';
|
|
84
|
+
}
|
|
85
|
+
const outcome = await suggest({
|
|
86
|
+
type: issue.type,
|
|
87
|
+
title: issue.title,
|
|
88
|
+
recommendation: issue.recommendation,
|
|
89
|
+
entityType: issue.entityType,
|
|
90
|
+
entityId: issue.entityId,
|
|
91
|
+
}, {
|
|
92
|
+
entity: loaded.ctx.entity,
|
|
93
|
+
siteSeo: loaded.ctx.siteSeo,
|
|
94
|
+
updatedAt: loaded.ctx.updatedAt,
|
|
95
|
+
metadata: asRecord(issue.metadata),
|
|
96
|
+
}, { db, userId: actor.userId, collection: loaded.ctx.collection });
|
|
97
|
+
if (outcome.aiError && HALTING_AI_ERROR_CODES.has(outcome.aiError.code)) {
|
|
98
|
+
result.haltedReason = outcome.aiError.message;
|
|
99
|
+
return 'halted';
|
|
100
|
+
}
|
|
101
|
+
if (outcome.aiError) {
|
|
102
|
+
// Per-issue failure: remember this document state so the next pass moves
|
|
103
|
+
// on instead of retrying the same head-of-queue issue forever.
|
|
104
|
+
await writeMetadata(issue.id, { generateSkippedFingerprint: liveFingerprint });
|
|
105
|
+
return 'failed';
|
|
106
|
+
}
|
|
107
|
+
if (outcome.cacheForApply) {
|
|
108
|
+
await writeMetadata(issue.id, pendingFixToMetadata(outcome.suggestion, {
|
|
109
|
+
brandAlignment: scoreFixBrandAlignment(outcome.suggestion.changes, brandVoice),
|
|
110
|
+
}));
|
|
111
|
+
return 'generated';
|
|
112
|
+
}
|
|
113
|
+
// Nothing cacheable for this document state (deterministic fix covers it,
|
|
114
|
+
// or the type never caches). Remember that so the next pass skips it.
|
|
115
|
+
await writeMetadata(issue.id, { generateSkippedFingerprint: liveFingerprint });
|
|
116
|
+
return 'skipped';
|
|
117
|
+
}
|
|
118
|
+
let cursor = 0;
|
|
119
|
+
async function worker() {
|
|
120
|
+
while (!halted && cursor < queue.length && now() - startedAt < budgetMs) {
|
|
121
|
+
const item = queue[cursor++];
|
|
122
|
+
let outcome;
|
|
123
|
+
try {
|
|
124
|
+
outcome = await processOne(item);
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
console.warn('[seo/proposals-generate] proposal generation failed', { issueId: item.issue.id }, err);
|
|
128
|
+
outcome = 'failed';
|
|
129
|
+
}
|
|
130
|
+
processed++;
|
|
131
|
+
if (outcome === 'halted') {
|
|
132
|
+
halted = true;
|
|
133
|
+
result.failed++;
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
result[outcome]++;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, queue.length) }, worker));
|
|
141
|
+
result.remaining = eligible.length - processed;
|
|
142
|
+
return result;
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=proposals-generate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proposals-generate.js","sourceRoot":"","sources":["../../src/seo/proposals-generate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified AI proposal shape over two stores: cached issue fixes
|
|
3
|
+
* (`SeoIssue.metadata.pendingFix`) and open `RedirectSuggestion` rows.
|
|
4
|
+
* Read-only; the generate / bulk-apply services live beside this file.
|
|
5
|
+
*/
|
|
6
|
+
import type { PrentaDB } from '../db/client-types.js';
|
|
7
|
+
import { type SeoFixFieldChange } from './issue-fix.js';
|
|
8
|
+
import { type PendingFixCache } from './issue-fix-ai.js';
|
|
9
|
+
export declare const ISSUE_FIX_FEATURE_KEY = "ai.features.seoMetaGeneration";
|
|
10
|
+
export declare const REDIRECT_FEATURE_KEY = "ai.features.seo404Recovery";
|
|
11
|
+
export type SeoProposalSeverity = 'critical' | 'warning' | 'info';
|
|
12
|
+
export interface SeoProposal {
|
|
13
|
+
/** `issue:<issueId>` | `redirect:<suggestionId>` */
|
|
14
|
+
id: string;
|
|
15
|
+
kind: 'issue-fix' | 'redirect';
|
|
16
|
+
title: string;
|
|
17
|
+
entity: {
|
|
18
|
+
type: string | null;
|
|
19
|
+
id: string | null;
|
|
20
|
+
title: string | null;
|
|
21
|
+
url: string | null;
|
|
22
|
+
};
|
|
23
|
+
changes: SeoFixFieldChange[];
|
|
24
|
+
justification: string;
|
|
25
|
+
source: 'ai' | 'deterministic' | 'ai-404-recovery';
|
|
26
|
+
/** 0–1, redirects only. */
|
|
27
|
+
confidence: number | null;
|
|
28
|
+
/** 0–100, issue fixes only. */
|
|
29
|
+
brandAlignment: number | null;
|
|
30
|
+
generatedAt: string | null;
|
|
31
|
+
/** Issue fixes only; redirects are keyed by id. */
|
|
32
|
+
fingerprint: string | null;
|
|
33
|
+
/** Issue fixes: cached fingerprint ≠ live fingerprint. Redirects: always false. */
|
|
34
|
+
stale: boolean;
|
|
35
|
+
issueType: string | null;
|
|
36
|
+
severity: SeoProposalSeverity | null;
|
|
37
|
+
issueId: string | null;
|
|
38
|
+
suggestionId: string | null;
|
|
39
|
+
}
|
|
40
|
+
export interface SeoProposalsPayload {
|
|
41
|
+
proposals: SeoProposal[];
|
|
42
|
+
counts: {
|
|
43
|
+
/** Live (non-stale) issue-fix proposals. */
|
|
44
|
+
issueFixes: number;
|
|
45
|
+
redirects: number;
|
|
46
|
+
stale: number;
|
|
47
|
+
/** Open/recurring + fixable + no live proposal + not sticky-dismissed/skipped. */
|
|
48
|
+
eligibleForGeneration: number;
|
|
49
|
+
};
|
|
50
|
+
/** `allowBulkApply` per feature, so the UI can disable batch approve up front. */
|
|
51
|
+
governance: {
|
|
52
|
+
issueFixBulk: boolean;
|
|
53
|
+
redirectBulk: boolean;
|
|
54
|
+
};
|
|
55
|
+
/** Autopilot status strip for the proposals inbox. */
|
|
56
|
+
autopilot: {
|
|
57
|
+
enabled: boolean;
|
|
58
|
+
/** Set only while the engine is actually paused (`pausedAt` present). */
|
|
59
|
+
pausedReason: string | null;
|
|
60
|
+
lastRunAt: string | null;
|
|
61
|
+
/** Live autopilot changes (`status = applied`) from the last 7 days. */
|
|
62
|
+
appliedLast7d: number;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export interface ProposalDoc {
|
|
66
|
+
id: string;
|
|
67
|
+
updatedAt: Date;
|
|
68
|
+
title: string | null;
|
|
69
|
+
slug: string | null;
|
|
70
|
+
collection: string;
|
|
71
|
+
}
|
|
72
|
+
type IssueRow = Record<string, any>;
|
|
73
|
+
/**
|
|
74
|
+
* Open/recurring issues (any fixability) plus the documents they reference, in
|
|
75
|
+
* two queries. "Reference" covers the issue entity AND, for cached insert-link
|
|
76
|
+
* fixes, the SOURCE document named by `pendingFix.patch.sourceEntityId` — that
|
|
77
|
+
* is the document the fix writes to and the one its fingerprint is built from,
|
|
78
|
+
* so without it `liveFingerprintFor` could never detect a stale link proposal.
|
|
79
|
+
*/
|
|
80
|
+
export declare function loadOpenFixableIssuesWithDocs(db: PrentaDB): Promise<{
|
|
81
|
+
issues: IssueRow[];
|
|
82
|
+
docsById: Map<string, ProposalDoc>;
|
|
83
|
+
}>;
|
|
84
|
+
/**
|
|
85
|
+
* The fingerprint a fresh suggestion for this issue would carry right now.
|
|
86
|
+
* Insert-link fixes are fingerprinted on the SOURCE document (the one that
|
|
87
|
+
* gets written), so their live value needs the cache to know the source.
|
|
88
|
+
* `loadOpenFixableIssuesWithDocs` loads sources alongside entities; if one is
|
|
89
|
+
* still missing (deleted since generation) we fall back to the cached value
|
|
90
|
+
* rather than guessing.
|
|
91
|
+
*/
|
|
92
|
+
export declare function liveFingerprintFor(issue: IssueRow, cache: PendingFixCache | null, docsById: Map<string, ProposalDoc>): string | null;
|
|
93
|
+
/**
|
|
94
|
+
* The marker `dismiss-fix` stores. After a dismissal there is no cache, so
|
|
95
|
+
* `selectEligibleForGeneration` compares the marker against the ENTITY-based
|
|
96
|
+
* live fingerprint. Insert-link caches are fingerprinted on the source
|
|
97
|
+
* document instead, so for them we store the entity-based value the no-cache
|
|
98
|
+
* path will compute; for every other type the cached fingerprint already is
|
|
99
|
+
* that value.
|
|
100
|
+
*/
|
|
101
|
+
export declare function dismissalFingerprintFor(issue: IssueRow, cache: PendingFixCache, entityUpdatedAt: Date | string | null): string;
|
|
102
|
+
export interface EligibleIssue {
|
|
103
|
+
issue: IssueRow;
|
|
104
|
+
liveFingerprint: string;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Issues batch generation should attempt: fixable, open, document present,
|
|
108
|
+
* no LIVE cache (a stale one counts as none), and neither sticky marker
|
|
109
|
+
* equals the live fingerprint. Ordered critical → warning → info.
|
|
110
|
+
*/
|
|
111
|
+
export declare function selectEligibleForGeneration(issues: IssueRow[], docsById: Map<string, ProposalDoc>): EligibleIssue[];
|
|
112
|
+
export declare function listSeoProposals(db: PrentaDB): Promise<SeoProposalsPayload>;
|
|
113
|
+
/**
|
|
114
|
+
* Cheap counts for the overview badge: every cached fix (stale ones are still
|
|
115
|
+
* rows in the inbox) + open redirect suggestions. Callers pass the open issues
|
|
116
|
+
* they already loaded so this adds at most one `count` query.
|
|
117
|
+
*/
|
|
118
|
+
export declare function countPendingProposals(db: PrentaDB, openIssues: Array<{
|
|
119
|
+
metadata: unknown;
|
|
120
|
+
}>): Promise<{
|
|
121
|
+
issueFixes: number;
|
|
122
|
+
redirects: number;
|
|
123
|
+
}>;
|
|
124
|
+
export {};
|
|
125
|
+
//# sourceMappingURL=proposals.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proposals.d.ts","sourceRoot":"","sources":["../../src/seo/proposals.ts"],"names":[],"mappings":"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"}
|