@superdoc/sdk 2.10.0-next.7 → 2.10.0-next.9

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 (53) hide show
  1. package/README.md +45 -0
  2. package/dist/action-primitives/doc-index.cjs +6 -4
  3. package/dist/action-primitives/doc-index.js +6 -4
  4. package/dist/action-primitives/engine.cjs +6 -2
  5. package/dist/action-primitives/engine.js +6 -2
  6. package/dist/action-primitives/receipt.d.ts +4 -0
  7. package/dist/action-primitives/tools/structure-insert.d.ts +1 -1
  8. package/dist/agent/actions.cjs +203 -223
  9. package/dist/agent/actions.d.ts +5 -0
  10. package/dist/agent/actions.js +203 -223
  11. package/dist/agent/catalog.cjs +15 -0
  12. package/dist/agent/catalog.js +15 -0
  13. package/dist/agent/doc-snapshot.cjs +22 -46
  14. package/dist/agent/doc-snapshot.js +22 -46
  15. package/dist/agent/execution-context.cjs +385 -0
  16. package/dist/agent/execution-context.d.ts +97 -0
  17. package/dist/agent/execution-context.js +376 -0
  18. package/dist/agent/runtime.cjs +116 -158
  19. package/dist/agent/runtime.d.ts +3 -0
  20. package/dist/agent/runtime.js +117 -159
  21. package/dist/agent/v2-preset-compat.cjs +5 -1
  22. package/dist/agent/v2-preset-compat.js +4 -1
  23. package/dist/embedded-tools.generated.cjs +5 -5
  24. package/dist/embedded-tools.generated.js +5 -5
  25. package/dist/generated/client.cjs +2 -0
  26. package/dist/generated/client.d.ts +47 -0
  27. package/dist/generated/client.js +2 -0
  28. package/dist/generated/contract.cjs +1605 -1297
  29. package/dist/generated/contract.js +1605 -1297
  30. package/dist/index.cjs +11 -0
  31. package/dist/index.d.ts +3 -1
  32. package/dist/index.js +11 -0
  33. package/dist/presets/core.cjs +1 -1
  34. package/dist/presets/core.js +1 -1
  35. package/dist/runtime/document-evidence.cjs +40 -0
  36. package/dist/runtime/document-evidence.d.ts +13 -0
  37. package/dist/runtime/document-evidence.js +30 -0
  38. package/dist/runtime/host.cjs +30 -2
  39. package/dist/runtime/host.d.ts +2 -0
  40. package/dist/runtime/host.js +30 -2
  41. package/dist/runtime/process.cjs +6 -0
  42. package/dist/runtime/process.d.ts +2 -0
  43. package/dist/runtime/process.js +6 -0
  44. package/dist/runtime/sdk-version.generated.cjs +1 -1
  45. package/dist/runtime/sdk-version.generated.d.ts +1 -1
  46. package/dist/runtime/sdk-version.generated.js +1 -1
  47. package/package.json +10 -9
  48. package/tools/catalog.json +35 -0
  49. package/tools/tools-policy.json +1 -1
  50. package/tools/tools.anthropic.json +35 -0
  51. package/tools/tools.generic.json +35 -0
  52. package/tools/tools.openai.json +35 -0
  53. package/tools/tools.vercel.json +35 -0
@@ -143,6 +143,11 @@ export const ACTION_ARG_SCHEMA = {
143
143
  },
144
144
  finds: { type: 'array', items: { type: 'string' } },
145
145
  caseSensitive: { type: 'boolean' },
146
+ evidence: {
147
+ type: 'string',
148
+ enum: ['required', 'full'],
149
+ description: 'Required evidence by default; full includes complete count summaries.',
150
+ },
146
151
  changeMode: { type: 'string', enum: ['direct', 'tracked'] },
147
152
  placement: { type: 'object', additionalProperties: true, properties: {} },
148
153
  selector: SELECTOR_SCHEMA,
@@ -387,6 +392,11 @@ export const AGENT_TOOL_DEFINITIONS = [
387
392
  additionalProperties: false,
388
393
  required: ['plan'],
389
394
  properties: {
395
+ evidence: {
396
+ type: 'string',
397
+ enum: ['required', 'full'],
398
+ description: 'Required evidence by default; full includes complete count summaries.',
399
+ },
390
400
  plan: {
391
401
  type: 'object',
392
402
  additionalProperties: false,
@@ -425,6 +435,11 @@ export const AGENT_TOOL_DEFINITIONS = [
425
435
  additionalProperties: false,
426
436
  required: ['checks'],
427
437
  properties: {
438
+ evidence: {
439
+ type: 'string',
440
+ enum: ['required', 'full'],
441
+ description: 'Required evidence by default; full includes complete count summaries.',
442
+ },
428
443
  checks: {
429
444
  type: 'array',
430
445
  minItems: 1,
@@ -159,57 +159,33 @@ async function buildDocumentSnapshot(doc, options = {}) {
159
159
  // first match so one call both LOCATES and READS the target section.
160
160
  let finds;
161
161
  if (typeof options.findText === 'string' && options.findText.trim().length > 0) {
162
- const scanFn = maybeMethod(doc, ['blocks', 'list']);
162
+ const scanFn = maybeMethod(doc, ['blocks', 'findText']);
163
163
  if (scanFn) {
164
- // Paginated scan with a hard cap: bounded bridge payloads per page, and
165
- // an explicit truncated flag instead of a silently-partial `total`.
166
- const SCAN_PAGE = 2000;
167
- const SCAN_CAP = 20000;
168
- const needle = options.findText.toLowerCase();
169
164
  const findLimit = options.findLimit != null && options.findLimit > 0 ? options.findLimit : 8;
170
- const matches = [];
171
- let scanned = 0;
172
- let truncated = false;
173
- for (let pageOffset = 0; pageOffset < SCAN_CAP; pageOffset += SCAN_PAGE) {
174
- const scanRaw = await safeCall(() => scanFn({ offset: pageOffset, limit: SCAN_PAGE, includeText: true }), null, recordError('blocks.findText'));
175
- const scanRec = asRecord(scanRaw);
176
- const scanBlocks = Array.isArray(scanRec?.blocks) ? scanRec.blocks : [];
177
- scanBlocks.forEach((b, i) => {
178
- const rec = asRecord(b) ?? {};
179
- const text = asString(rec.text);
180
- if (!text || !text.toLowerCase().includes(needle))
181
- return;
182
- matches.push({
183
- ordinal: asNumber(rec.ordinal, pageOffset + i) + 1,
184
- nodeId: asString(rec.nodeId),
185
- nodeType: asString(rec.nodeType, 'paragraph'),
186
- preview: text.slice(0, 100),
187
- index: asNumber(rec.ordinal, pageOffset + i),
188
- });
189
- });
190
- scanned += scanBlocks.length;
191
- const totalBlocksReported = explicitCount(scanRec ?? {}, 'total');
192
- if (scanBlocks.length < SCAN_PAGE)
193
- break; // last page
194
- if (totalBlocksReported != null && scanned >= totalBlocksReported)
195
- break;
196
- if (pageOffset + SCAN_PAGE >= SCAN_CAP) {
197
- truncated = true;
198
- break;
199
- }
200
- }
165
+ const scanRaw = await safeCall(() => scanFn({ text: options.findText, limit: Math.min(20000, Math.trunc(findLimit)) }), null, recordError('blocks.findText'));
166
+ const scan = asRecord(scanRaw);
167
+ const scanError = asRecord(scan?.scanError);
168
+ if (typeof scanError?.message === 'string')
169
+ recordError('blocks.findText')(scanError.message);
170
+ const matches = Array.isArray(scan?.matches) ? scan.matches : [];
201
171
  finds = {
202
172
  query: options.findText,
203
- total: matches.length,
204
- ...(truncated ? { truncated: true } : {}),
205
- scannedBlocks: scanned,
206
- matches: matches.slice(0, findLimit).map(({ index: _index, nodeId, ...m }) => ({
207
- ...m,
208
- ...(nodeId ? { nodeId } : {}),
209
- })),
173
+ total: asNumber(scan?.total),
174
+ ...(scan?.truncated === true ? { truncated: true } : {}),
175
+ scannedBlocks: asNumber(scan?.scannedBlocks),
176
+ matches: matches.map((match) => {
177
+ const rec = asRecord(match) ?? {};
178
+ const nodeId = asString(rec.nodeId);
179
+ return {
180
+ ordinal: asNumber(rec.ordinal) + 1,
181
+ ...(nodeId ? { nodeId } : {}),
182
+ nodeType: asString(rec.nodeType, 'paragraph'),
183
+ preview: asString(rec.preview),
184
+ };
185
+ }),
210
186
  };
211
- if (matches.length > 0 && options.blockOffset == null) {
212
- blockOffset = Math.max(0, matches[0].index - 2);
187
+ if (typeof scan?.firstMatchOrdinal === 'number' && options.blockOffset == null) {
188
+ blockOffset = Math.max(0, scan.firstMatchOrdinal - 2);
213
189
  blockLimit = options.blockLimit != null && options.blockLimit > 0 ? options.blockLimit : 12;
214
190
  }
215
191
  }
@@ -157,57 +157,33 @@ export async function buildDocumentSnapshot(doc, options = {}) {
157
157
  // first match so one call both LOCATES and READS the target section.
158
158
  let finds;
159
159
  if (typeof options.findText === 'string' && options.findText.trim().length > 0) {
160
- const scanFn = maybeMethod(doc, ['blocks', 'list']);
160
+ const scanFn = maybeMethod(doc, ['blocks', 'findText']);
161
161
  if (scanFn) {
162
- // Paginated scan with a hard cap: bounded bridge payloads per page, and
163
- // an explicit truncated flag instead of a silently-partial `total`.
164
- const SCAN_PAGE = 2000;
165
- const SCAN_CAP = 20000;
166
- const needle = options.findText.toLowerCase();
167
162
  const findLimit = options.findLimit != null && options.findLimit > 0 ? options.findLimit : 8;
168
- const matches = [];
169
- let scanned = 0;
170
- let truncated = false;
171
- for (let pageOffset = 0; pageOffset < SCAN_CAP; pageOffset += SCAN_PAGE) {
172
- const scanRaw = await safeCall(() => scanFn({ offset: pageOffset, limit: SCAN_PAGE, includeText: true }), null, recordError('blocks.findText'));
173
- const scanRec = asRecord(scanRaw);
174
- const scanBlocks = Array.isArray(scanRec?.blocks) ? scanRec.blocks : [];
175
- scanBlocks.forEach((b, i) => {
176
- const rec = asRecord(b) ?? {};
177
- const text = asString(rec.text);
178
- if (!text || !text.toLowerCase().includes(needle))
179
- return;
180
- matches.push({
181
- ordinal: asNumber(rec.ordinal, pageOffset + i) + 1,
182
- nodeId: asString(rec.nodeId),
183
- nodeType: asString(rec.nodeType, 'paragraph'),
184
- preview: text.slice(0, 100),
185
- index: asNumber(rec.ordinal, pageOffset + i),
186
- });
187
- });
188
- scanned += scanBlocks.length;
189
- const totalBlocksReported = explicitCount(scanRec ?? {}, 'total');
190
- if (scanBlocks.length < SCAN_PAGE)
191
- break; // last page
192
- if (totalBlocksReported != null && scanned >= totalBlocksReported)
193
- break;
194
- if (pageOffset + SCAN_PAGE >= SCAN_CAP) {
195
- truncated = true;
196
- break;
197
- }
198
- }
163
+ const scanRaw = await safeCall(() => scanFn({ text: options.findText, limit: Math.min(20000, Math.trunc(findLimit)) }), null, recordError('blocks.findText'));
164
+ const scan = asRecord(scanRaw);
165
+ const scanError = asRecord(scan?.scanError);
166
+ if (typeof scanError?.message === 'string')
167
+ recordError('blocks.findText')(scanError.message);
168
+ const matches = Array.isArray(scan?.matches) ? scan.matches : [];
199
169
  finds = {
200
170
  query: options.findText,
201
- total: matches.length,
202
- ...(truncated ? { truncated: true } : {}),
203
- scannedBlocks: scanned,
204
- matches: matches.slice(0, findLimit).map(({ index: _index, nodeId, ...m }) => ({
205
- ...m,
206
- ...(nodeId ? { nodeId } : {}),
207
- })),
171
+ total: asNumber(scan?.total),
172
+ ...(scan?.truncated === true ? { truncated: true } : {}),
173
+ scannedBlocks: asNumber(scan?.scannedBlocks),
174
+ matches: matches.map((match) => {
175
+ const rec = asRecord(match) ?? {};
176
+ const nodeId = asString(rec.nodeId);
177
+ return {
178
+ ordinal: asNumber(rec.ordinal) + 1,
179
+ ...(nodeId ? { nodeId } : {}),
180
+ nodeType: asString(rec.nodeType, 'paragraph'),
181
+ preview: asString(rec.preview),
182
+ };
183
+ }),
208
184
  };
209
- if (matches.length > 0 && options.blockOffset == null) {
210
- blockOffset = Math.max(0, matches[0].index - 2);
185
+ if (typeof scan?.firstMatchOrdinal === 'number' && options.blockOffset == null) {
186
+ blockOffset = Math.max(0, scan.firstMatchOrdinal - 2);
211
187
  blockLimit = options.blockLimit != null && options.blockLimit > 0 ? options.blockLimit : 12;
212
188
  }
213
189
  }
@@ -0,0 +1,385 @@
1
+ 'use strict';
2
+
3
+ var documentEvidence = require('../runtime/document-evidence.cjs');
4
+ var errors = require('../runtime/errors.cjs');
5
+ var docSnapshot = require('./doc-snapshot.cjs');
6
+ var operationCatalog = require('./operation-catalog.cjs');
7
+
8
+ function supportsLocalSelector(selector) {
9
+ return (selector.kind === 'nodeId' ||
10
+ selector.kind === 'textSearch' ||
11
+ selector.kind === 'placement' ||
12
+ (selector.kind === 'ordinal' &&
13
+ ['blockOrdinal', 'headingOrdinal', 'tableOrdinal'].includes(selector.ordinalKind)) ||
14
+ (selector.kind === 'relative' && supportsLocalSelector(selector.target)));
15
+ }
16
+ function localCheck(check) {
17
+ return (['revision-changed', 'revision-unchanged', 'block-text-contains', 'block-text-equals', 'table-shape'].includes(check.kind) ||
18
+ (check.kind === 'block-count-delta' && ['paragraph', 'heading', 'listItem', 'table'].includes(check.nodeType)));
19
+ }
20
+ async function readPublicRevision(doc) {
21
+ const revision = await documentEvidence.getDocumentRevisionReader(doc)?.();
22
+ if (revision !== undefined)
23
+ return revision;
24
+ const page = await doc.blocks.list({ offset: Number.MAX_SAFE_INTEGER, limit: 1 });
25
+ if (!page.revision || page.revision === 'unknown')
26
+ throw new errors.SuperDocCliError('Required document revision is unavailable.', { code: 'EVIDENCE_UNAVAILABLE' });
27
+ return page.revision;
28
+ }
29
+ class DocumentFacts {
30
+ doc;
31
+ revision;
32
+ complete;
33
+ summaryCounts;
34
+ blocks = new Map();
35
+ counts = new Map();
36
+ selections = new Map();
37
+ constructor(doc, revision, complete, summaryCounts = complete?.counts) {
38
+ this.doc = doc;
39
+ this.revision = revision;
40
+ this.complete = complete;
41
+ this.summaryCounts = summaryCounts;
42
+ }
43
+ get summary() {
44
+ return { revision: this.revision, ...(this.summaryCounts ? { counts: this.summaryCounts } : {}) };
45
+ }
46
+ async fence() {
47
+ if ((await readPublicRevision(this.doc)) !== this.revision)
48
+ throw new errors.SuperDocCliError('Document changed while acquiring execution evidence.', { code: 'REVISION_CONFLICT' });
49
+ }
50
+ async page(input) {
51
+ const result = await this.doc.blocks.list({ ...input, includeText: true });
52
+ if (result.revision !== this.revision)
53
+ throw new errors.SuperDocCliError('Document changed while resolving a target.', { code: 'REVISION_CONFLICT' });
54
+ const blocks = result.blocks.map((block) => ({
55
+ ...block,
56
+ ordinal: block.ordinal + 1,
57
+ text: block.text ?? '',
58
+ textPreview: block.textPreview ?? null,
59
+ styleId: block.styleId ?? null,
60
+ }));
61
+ for (const block of blocks)
62
+ this.blocks.set(block.nodeId, block);
63
+ return blocks;
64
+ }
65
+ async count(nodeType) {
66
+ const key = nodeType ?? '*';
67
+ if (this.counts.has(key))
68
+ return this.counts.get(key);
69
+ if (this.complete)
70
+ return nodeType
71
+ ? this.complete.blocks.filter((block) => block.nodeType === nodeType).length
72
+ : this.complete.blocks.length;
73
+ const result = await this.doc.blocks.list({
74
+ offset: Number.MAX_SAFE_INTEGER,
75
+ limit: 1,
76
+ ...(nodeType ? { nodeTypes: [nodeType] } : {}),
77
+ });
78
+ if (result.revision !== this.revision || !Number.isSafeInteger(result.total))
79
+ throw new errors.SuperDocCliError('Required count is unavailable at the execution revision.', {
80
+ code: 'EVIDENCE_UNAVAILABLE',
81
+ });
82
+ this.counts.set(key, result.total);
83
+ return result.total;
84
+ }
85
+ async block(nodeId) {
86
+ if (this.complete)
87
+ return this.complete.blocks.find((block) => block.nodeId === nodeId);
88
+ if (!this.blocks.has(nodeId))
89
+ await this.page({ nodeIds: [nodeId], limit: 1 });
90
+ return this.blocks.get(nodeId);
91
+ }
92
+ async select(selector) {
93
+ if (this.complete)
94
+ return docSnapshot.resolveSnapshotSelector(this.complete, selector);
95
+ const key = JSON.stringify(selector);
96
+ if (this.selections.has(key))
97
+ return this.selections.get(key);
98
+ let blocks = [];
99
+ if (selector.kind === 'nodeId') {
100
+ const block = await this.block(selector.nodeId);
101
+ if (block)
102
+ blocks = [block];
103
+ }
104
+ else if (selector.kind === 'textSearch') {
105
+ const terms = selector.terms.filter((term) => term.trim());
106
+ if (terms.length && selector.nodeTypes?.length !== 0)
107
+ blocks = await this.page({
108
+ textSearch: { terms: [...terms], match: selector.match, caseSensitive: selector.caseSensitive },
109
+ nodeTypes: [...(selector.nodeTypes ?? ['paragraph', 'heading', 'listItem'])],
110
+ offset: (selector.occurrence ?? 1) - 1,
111
+ limit: 1,
112
+ });
113
+ }
114
+ else if (selector.kind === 'placement') {
115
+ const offset = selector.at === 'document_start' ? 0 : (await this.count()) - 1;
116
+ if (offset >= 0)
117
+ blocks = await this.page({ offset, limit: 1 });
118
+ }
119
+ else if (selector.kind === 'ordinal') {
120
+ blocks = await this.page({
121
+ offset: selector.value - 1,
122
+ limit: 1,
123
+ ...(selector.ordinalKind === 'headingOrdinal'
124
+ ? { nodeTypes: ['heading'] }
125
+ : selector.ordinalKind === 'tableOrdinal'
126
+ ? { nodeTypes: ['table'] }
127
+ : {}),
128
+ });
129
+ }
130
+ else if (selector.kind === 'relative') {
131
+ for (const id of await this.select(selector.target)) {
132
+ const target = await this.block(id);
133
+ const offset = target ? target.ordinal - 1 + (selector.position === 'before' ? -1 : 1) : -1;
134
+ if (offset >= 0)
135
+ blocks.push(...(await this.page({ offset, limit: 1 })));
136
+ }
137
+ }
138
+ else
139
+ throw new errors.SuperDocCliError('Selector requires complete evidence.', { code: 'EVIDENCE_UNAVAILABLE' });
140
+ const ids = [...new Set(blocks.map((block) => block.nodeId))];
141
+ this.selections.set(key, ids);
142
+ return ids;
143
+ }
144
+ async target(selector) {
145
+ const ids = await this.select(selector);
146
+ if (this.complete && selector.kind === 'tableCell' && ids.length === 1) {
147
+ const cell = this.complete.tables.flatMap((table) => table.cells).find((cell) => cell.nodeId === ids[0]);
148
+ if (cell?.nodeId)
149
+ return {
150
+ nodeId: cell.nodeId,
151
+ nodeType: 'paragraph',
152
+ text: cell.text,
153
+ textPreview: cell.text,
154
+ styleId: null,
155
+ ordinal: 0,
156
+ };
157
+ }
158
+ return ids.length === 1 ? this.block(ids[0]) : undefined;
159
+ }
160
+ async tableShape(nodeId) {
161
+ if (this.complete)
162
+ return this.complete.tables.find((table) => table.nodeId === nodeId);
163
+ return this.doc.tables.get({ nodeId });
164
+ }
165
+ async completeIndex(reason, acquire) {
166
+ const value = await acquire();
167
+ await this.fence();
168
+ return { value, coverage: 'complete', reason };
169
+ }
170
+ async prepare(checks) {
171
+ for (const check of checks)
172
+ if (check.kind === 'block-count-delta')
173
+ await this.count(check.nodeType);
174
+ }
175
+ }
176
+ class ExecutionContext {
177
+ source;
178
+ requirements;
179
+ document;
180
+ executedOperations = [];
181
+ selectedTargets = [];
182
+ pre;
183
+ post;
184
+ fallbackReason;
185
+ currentRevision;
186
+ legacyRevisionOnly = false;
187
+ constructor(source, requirements = {}) {
188
+ this.source = source;
189
+ this.requirements = requirements;
190
+ if (requirements.evidence !== undefined && !['required', 'full'].includes(requirements.evidence))
191
+ throw new errors.SuperDocCliError('evidence must be required or full.', { code: 'INVALID_ARGUMENT' });
192
+ const wrap = (value, path) => new Proxy(value, {
193
+ get: (target, property, receiver) => {
194
+ const member = Reflect.get(target, property, receiver);
195
+ if (typeof property !== 'string')
196
+ return member;
197
+ const memberPath = [...path, property];
198
+ const operationId = `doc.${memberPath.join('.')}`;
199
+ if (typeof member === 'function' && operationCatalog.getOperationCatalogEntry(operationId)?.isMutating) {
200
+ return async (args = {}, options) => {
201
+ if (operationId === 'doc.format.apply' &&
202
+ documentEvidence.supportsDocumentFacts(this.source) &&
203
+ typeof args.blockId === 'string') {
204
+ const { blockId, start, end, ...rest } = args;
205
+ args = {
206
+ ...rest,
207
+ target: {
208
+ kind: 'selection',
209
+ start: { kind: 'text', blockId, offset: start },
210
+ end: { kind: 'text', blockId, offset: end },
211
+ },
212
+ };
213
+ }
214
+ if (this.legacyRevisionOnly && this.executedOperations.length)
215
+ throw new errors.SuperDocCliError('This host cannot bind revisions across multiple writes.', {
216
+ code: 'EVIDENCE_UNAVAILABLE',
217
+ });
218
+ const guarded = (documentEvidence.supportsDocumentFacts(this.source) || this.legacyRevisionOnly) &&
219
+ !['doc.history.undo', 'doc.history.redo', 'doc.plan.execute'].includes(operationId);
220
+ const revision = guarded ? (this.currentRevision ?? (await readPublicRevision(this.source))) : undefined;
221
+ const result = await Reflect.apply(member, target, guarded
222
+ ? [
223
+ { ...args, expectedRevision: args.expectedRevision ?? revision },
224
+ { ...options, expectedRevision: options?.expectedRevision ?? revision },
225
+ ]
226
+ : [args, options]);
227
+ this.executedOperations.push({ operationId, result });
228
+ if (result?.success === false)
229
+ throw new errors.SuperDocCliError('Document operation did not apply.', {
230
+ code: result.failure?.code ?? 'APPLY_FAILED',
231
+ details: result,
232
+ });
233
+ if (documentEvidence.supportsDocumentFacts(this.source)) {
234
+ const applied = documentEvidence.getMutationRevision(result);
235
+ if (!applied ||
236
+ applied.before !== this.currentRevision ||
237
+ !applied.after ||
238
+ applied.after === 'unknown')
239
+ throw new errors.SuperDocCliError('Committed operation revision is unavailable or changed.', {
240
+ code: 'EVIDENCE_UNAVAILABLE',
241
+ });
242
+ this.currentRevision = applied.after;
243
+ }
244
+ return result;
245
+ };
246
+ }
247
+ if (member && typeof member === 'object')
248
+ return wrap(member, memberPath);
249
+ return member;
250
+ },
251
+ });
252
+ this.document = wrap(source, []);
253
+ documentEvidence.forwardDocumentEvidence(source, this.document);
254
+ }
255
+ async start() {
256
+ const { evidence, selectors = [], checks = [], completeReason } = this.requirements;
257
+ const compactRevision = this.requirements.revisionOnly && evidence !== 'full' && !completeReason
258
+ ? await documentEvidence.getDocumentRevisionReader(this.source)?.()
259
+ : undefined;
260
+ this.legacyRevisionOnly = compactRevision !== undefined && !documentEvidence.supportsDocumentFacts(this.source);
261
+ this.fallbackReason =
262
+ completeReason ??
263
+ (!documentEvidence.supportsDocumentFacts(this.source) && !this.legacyRevisionOnly
264
+ ? 'host lacks typed execution facts'
265
+ : selectors.some((selector) => !supportsLocalSelector(selector))
266
+ ? 'selector requires complete index'
267
+ : checks.some((check) => !localCheck(check))
268
+ ? 'verification requires complete evidence'
269
+ : undefined);
270
+ const complete = evidence === 'full' || this.fallbackReason ? await docSnapshot.buildMutationSnapshot(this.source) : undefined;
271
+ if (complete && (complete.revision === 'unknown' || complete.diagnostics.some((item) => item.section === 'info')))
272
+ throw new errors.SuperDocCliError('Required complete evidence is unavailable.', { code: 'EVIDENCE_UNAVAILABLE' });
273
+ this.pre = new DocumentFacts(this.source, complete?.revision ?? compactRevision ?? (await readPublicRevision(this.source)), complete);
274
+ this.currentRevision = this.pre.revision;
275
+ await this.pre.prepare(checks);
276
+ return this.pre;
277
+ }
278
+ async finishRevision() {
279
+ if (this.requirements.evidence === 'full')
280
+ return this.finish();
281
+ const summary = this.pre?.complete ? await docSnapshot.buildDocumentSnapshot(this.source, { countsOnly: true }) : undefined;
282
+ if (summary && (summary.revision === 'unknown' || summary.diagnostics.some((item) => item.section === 'info')))
283
+ throw new errors.SuperDocCliError('Required summary is unavailable.', { code: 'EVIDENCE_UNAVAILABLE' });
284
+ const revision = summary?.revision ?? (await readPublicRevision(this.source));
285
+ this.assertExecutionRevision(revision);
286
+ this.post = new DocumentFacts(this.source, revision, undefined, summary?.counts);
287
+ this.currentRevision = this.post.revision;
288
+ return this.post;
289
+ }
290
+ async finish() {
291
+ const complete = this.pre?.complete ? await docSnapshot.buildMutationSnapshot(this.source) : undefined;
292
+ if (complete && (complete.revision === 'unknown' || complete.diagnostics.some((item) => item.section === 'info')))
293
+ throw new errors.SuperDocCliError('Required complete evidence is unavailable.', { code: 'EVIDENCE_UNAVAILABLE' });
294
+ const revision = complete?.revision ?? (await readPublicRevision(this.source));
295
+ this.assertExecutionRevision(revision);
296
+ this.post = new DocumentFacts(this.source, revision, complete);
297
+ this.currentRevision = this.post.revision;
298
+ return this.post;
299
+ }
300
+ assertExecutionRevision(revision) {
301
+ if (documentEvidence.supportsDocumentFacts(this.source) && this.currentRevision !== revision)
302
+ throw new errors.SuperDocCliError('Document changed outside this execution.', { code: 'REVISION_CONFLICT' });
303
+ }
304
+ receipt(intent, verification, extra = {}) {
305
+ return {
306
+ status: verification.every((check) => check.passed) ? 'ok' : 'failed',
307
+ intent,
308
+ preSnapshot: this.pre?.summary,
309
+ postSnapshot: this.post?.summary,
310
+ selectedTargets: this.selectedTargets,
311
+ executedOperations: this.executedOperations,
312
+ verification,
313
+ ...(this.fallbackReason ? { evidenceFallback: this.fallbackReason } : {}),
314
+ ...extra,
315
+ };
316
+ }
317
+ failure(intent, error) {
318
+ const err = error;
319
+ return this.receipt(intent, [], {
320
+ status: err?.code === 'AMBIGUOUS_SELECTOR' ? 'aborted' : 'failed',
321
+ errors: [
322
+ { code: err?.code ?? 'ACTION_FAILED', message: err?.message ?? String(error), recovery: { kind: 'reinspect' } },
323
+ ],
324
+ });
325
+ }
326
+ }
327
+ async function evaluateFactChecks(pre, post, checks) {
328
+ const results = [];
329
+ for (const check of checks) {
330
+ if ([
331
+ 'revision-changed',
332
+ 'revision-unchanged',
333
+ 'block-count-delta',
334
+ 'comment-count-delta',
335
+ 'tracked-change-count-delta',
336
+ ].includes(check.kind) &&
337
+ !pre) {
338
+ results.push({ check, passed: false, detail: `${check.kind} requires a baseline snapshot` });
339
+ continue;
340
+ }
341
+ if (check.kind === 'revision-changed' || check.kind === 'revision-unchanged')
342
+ results.push({
343
+ check,
344
+ passed: check.kind === 'revision-changed' ? pre.revision !== post.revision : pre.revision === post.revision,
345
+ detail: `pre=${pre.revision} post=${post.revision}`,
346
+ });
347
+ else if (check.kind === 'block-count-delta') {
348
+ const before = await pre.count(check.nodeType), after = await post.count(check.nodeType);
349
+ results.push({
350
+ check,
351
+ passed: after - before === check.delta,
352
+ detail: `pre=${before} post=${after} delta=${after - before}`,
353
+ });
354
+ }
355
+ else if (check.kind === 'block-text-contains' || check.kind === 'block-text-equals') {
356
+ const block = await post.block(check.nodeId);
357
+ results.push({
358
+ check,
359
+ passed: !!block && (check.kind === 'block-text-equals' ? block.text === check.text : block.text.includes(check.text)),
360
+ });
361
+ }
362
+ else if (check.kind === 'table-shape') {
363
+ const table = await post.tableShape(check.nodeId);
364
+ results.push({ check, passed: !!table && table.rows === check.rows && table.columns === check.columns });
365
+ }
366
+ else if (check.kind === 'comment-count-delta' || check.kind === 'tracked-change-count-delta') {
367
+ const key = check.kind === 'comment-count-delta' ? 'comments' : 'trackedChanges';
368
+ const before = pre?.complete?.counts[key], after = post.complete?.counts[key];
369
+ results.push({
370
+ check,
371
+ passed: before !== undefined && after !== undefined && after - before === check.delta,
372
+ detail: `pre=${before} post=${after}`,
373
+ });
374
+ }
375
+ else
376
+ results.push({ check, passed: false, detail: 'Check requires domain-specific evidence.' });
377
+ }
378
+ return results;
379
+ }
380
+
381
+ exports.DocumentFacts = DocumentFacts;
382
+ exports.ExecutionContext = ExecutionContext;
383
+ exports.evaluateFactChecks = evaluateFactChecks;
384
+ exports.readPublicRevision = readPublicRevision;
385
+ exports.supportsLocalSelector = supportsLocalSelector;