@beyondwork/docx-react-component 1.0.104 → 1.0.106

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 (34) hide show
  1. package/package.json +1 -1
  2. package/src/api/public-types.ts +3 -0
  3. package/src/api/v3/_create.ts +9 -2
  4. package/src/api/v3/ai/_audit-reference.ts +28 -0
  5. package/src/api/v3/ai/_pe2-evidence.ts +419 -0
  6. package/src/api/v3/ai/attach.ts +22 -2
  7. package/src/api/v3/ai/bundle.ts +18 -6
  8. package/src/api/v3/ai/inspect.ts +12 -2
  9. package/src/api/v3/ai/replacement.ts +124 -0
  10. package/src/api/v3/index.ts +7 -0
  11. package/src/api/v3/ui/_types.ts +139 -0
  12. package/src/api/v3/ui/index.ts +9 -0
  13. package/src/api/v3/ui/overlays.ts +104 -0
  14. package/src/api/v3/ui/viewport.ts +97 -0
  15. package/src/model/layout/index.ts +3 -0
  16. package/src/model/layout/page-graph-types.ts +118 -0
  17. package/src/model/layout/runtime-page-graph-types.ts +13 -0
  18. package/src/runtime/document-runtime.ts +39 -18
  19. package/src/runtime/event-refresh-hints.ts +33 -6
  20. package/src/runtime/geometry/geometry-facet.ts +9 -1
  21. package/src/runtime/geometry/geometry-index.ts +461 -10
  22. package/src/runtime/geometry/geometry-types.ts +6 -0
  23. package/src/runtime/geometry/object-handles.ts +7 -4
  24. package/src/runtime/layout/layout-engine-instance.ts +2 -0
  25. package/src/runtime/layout/layout-engine-version.ts +36 -1
  26. package/src/runtime/layout/page-graph.ts +697 -10
  27. package/src/runtime/layout/paginated-layout-engine.ts +10 -0
  28. package/src/runtime/layout/project-block-fragments.ts +187 -8
  29. package/src/runtime/layout/public-facet.ts +236 -0
  30. package/src/runtime/prerender/graph-canonicalize.ts +14 -0
  31. package/src/runtime/workflow/index.ts +1 -0
  32. package/src/runtime/workflow/overlay-lanes.ts +228 -0
  33. package/src/ui/presence-overlay-lane.ts +131 -0
  34. package/src/ui/ui-controller-factory.ts +21 -0
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@beyondwork/docx-react-component",
3
3
  "publisher": "beyondwork",
4
- "version": "1.0.104",
4
+ "version": "1.0.106",
5
5
  "description": "Embeddable React Word (docx) editor with review, comments, tracked changes, and round-trip OOXML fidelity.",
6
6
  "type": "module",
7
7
  "sideEffects": [
@@ -177,9 +177,11 @@ export type {
177
177
  PublicBlockMeasurement,
178
178
  PublicFieldDirtinessReport,
179
179
  PublicLineBox,
180
+ PublicLayoutDivergence,
180
181
  PublicMeasurementFidelity,
181
182
  PublicNoteAllocation,
182
183
  PublicPageAnchor,
184
+ PublicPageFrame,
183
185
  PublicPageNode,
184
186
  PublicPageRegion,
185
187
  PublicPageRegions,
@@ -190,6 +192,7 @@ export type {
190
192
  PublicResolvedParagraphFormatting,
191
193
  PublicResolvedRunFormatting,
192
194
  PublicSectionNode,
195
+ PublicTwipsRect,
193
196
  // R0.5: named page-format + margin-preset catalogs
194
197
  PageFormatDefinition,
195
198
  ActivePageFormat,
@@ -41,6 +41,7 @@ import { createReviewFamily as createAiReviewFamily } from "./ai/review.ts";
41
41
  import { createEvaluateFamily } from "./ai/evaluate.ts";
42
42
  import { createStatsFamily } from "./ai/stats.ts";
43
43
  import { createOutlineFamily } from "./ai/outline.ts";
44
+ import type { AiPe2EvidenceOptions } from "./ai/_pe2-evidence.ts";
44
45
 
45
46
  import { createUiApi } from "./ui/_create.ts";
46
47
  import type { ApiV3Ui, UiControllerFactory } from "./ui/_types.ts";
@@ -108,6 +109,12 @@ export interface ApiV3 {
108
109
  */
109
110
  export interface CreateApiV3Opts {
110
111
  readonly ui?: UiControllerFactory;
112
+ /**
113
+ * Optional PE2 evidence bridge. Hosts/debug services that already have
114
+ * truth-oracle run metadata can inject it here; omitted means AI reads
115
+ * report `pe2Evidence.oracle.status: "not-wired"`.
116
+ */
117
+ readonly pe2Evidence?: AiPe2EvidenceOptions;
111
118
  }
112
119
 
113
120
  /**
@@ -119,9 +126,9 @@ export interface CreateApiV3Opts {
119
126
  */
120
127
  export function createApiV3(handle: RuntimeApiHandle, opts?: CreateApiV3Opts): ApiV3 {
121
128
  const ai: ApiV3Ai = {
122
- ...createInspectFamily(handle),
129
+ ...createInspectFamily(handle, opts?.pe2Evidence),
123
130
  ...createResolveFamily(handle),
124
- ...createBundleFamily(handle),
131
+ ...createBundleFamily(handle, opts?.pe2Evidence),
125
132
  ...createReplacementFamily(handle),
126
133
  ...createAttachFamily(handle),
127
134
  ...createExportFamily(handle),
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Compact AI-facing pointer to an emitted ScopeActionAudit.
3
+ *
4
+ * The full audit bundle remains on the `scope` telemetry channel. Mutation
5
+ * return values expose only enough provenance for agents to cite the action
6
+ * and correlate it with telemetry.
7
+ */
8
+
9
+ import type { ScopeActionAudit } from "../../../runtime/scopes/index.ts";
10
+
11
+ export interface AiActionAuditReference {
12
+ readonly actionId: string;
13
+ readonly actorId: string;
14
+ readonly origin: ScopeActionAudit["origin"];
15
+ readonly emittedAtUtc: string;
16
+ }
17
+
18
+ export function projectAuditReference(
19
+ audit: ScopeActionAudit | null | undefined,
20
+ ): AiActionAuditReference | undefined {
21
+ if (!audit) return undefined;
22
+ return {
23
+ actionId: audit.actionId,
24
+ actorId: audit.actorId,
25
+ origin: audit.origin,
26
+ emittedAtUtc: audit.emittedAtUtc,
27
+ };
28
+ }
@@ -0,0 +1,419 @@
1
+ /**
2
+ * Layer 09 PE2 evidence projection.
3
+ *
4
+ * The AI API should not leak the full runtime geometry index. Agents need a
5
+ * small, JSON-stable read model that says whether PE2 geometry evidence is
6
+ * available and, for scope reads, whether a replacement envelope exists for
7
+ * the requested scope.
8
+ */
9
+
10
+ import type { RuntimeApiHandle } from "../_runtime-handle.ts";
11
+ import type { ScopeHandle } from "../../../runtime/scopes/index.ts";
12
+ import type {
13
+ GeometryIndexCoverage,
14
+ GeometryPrecision,
15
+ GeometryRehydrationStatus,
16
+ GeometryReplacementEnvelopeEntry,
17
+ GeometrySourceIdentity,
18
+ } from "../../../runtime/geometry/index.ts";
19
+ import type { PublicLayoutDivergence } from "../../../runtime/layout/public-facet.ts";
20
+ import {
21
+ createHeaderFooterStoryKey,
22
+ createNoteStoryKey,
23
+ MAIN_STORY_KEY,
24
+ } from "../../../model/canonical-layout-inputs.ts";
25
+
26
+ export interface AiPe2GeometryCoverageEvidence {
27
+ readonly status: GeometryRehydrationStatus;
28
+ readonly pageCount: number;
29
+ readonly regionCount: number;
30
+ readonly sliceCount: number;
31
+ readonly lineCount: number;
32
+ readonly anchorCount: number;
33
+ readonly hitTargetCount: number;
34
+ readonly semanticEntryCount: number;
35
+ readonly replacementEnvelopeCount: number;
36
+ readonly objectHandleCount: number;
37
+ readonly precision: {
38
+ readonly exact: number;
39
+ readonly "within-tolerance": number;
40
+ readonly heuristic: number;
41
+ };
42
+ }
43
+
44
+ export interface AiPe2DocumentEvidence {
45
+ readonly geometry: AiPe2GeometryCoverageEvidence;
46
+ readonly layout: AiPe2LayoutEvidence;
47
+ readonly oracle: AiPe2OracleEvidence;
48
+ }
49
+
50
+ export interface AiPe2ScopeReplacementEnvelopeEvidence {
51
+ readonly status: GeometryRehydrationStatus;
52
+ readonly precision: GeometryPrecision;
53
+ readonly rectCount: number;
54
+ readonly pageIds?: readonly string[];
55
+ readonly sourceIdentity?: GeometrySourceIdentity;
56
+ }
57
+
58
+ export interface AiPe2ScopeEvidence {
59
+ readonly geometry: {
60
+ readonly coverage: AiPe2GeometryCoverageEvidence;
61
+ readonly replacementEnvelope?: AiPe2ScopeReplacementEnvelopeEvidence;
62
+ readonly reason?: "geometry-index-unavailable" | "scope-envelope-not-found";
63
+ };
64
+ readonly layout: AiPe2LayoutEvidence;
65
+ readonly oracle: AiPe2OracleEvidence;
66
+ }
67
+
68
+ export interface AiPe2LayoutDivergenceEvidence {
69
+ readonly divergenceId: string;
70
+ readonly kind: string;
71
+ readonly source: string;
72
+ readonly severity: string;
73
+ readonly message: string;
74
+ readonly regionKinds?: readonly string[];
75
+ readonly fragmentIds?: readonly string[];
76
+ }
77
+
78
+ export interface AiPe2LayoutEvidence {
79
+ readonly status: "realized" | "unavailable";
80
+ readonly pageCount: number;
81
+ readonly pageIds: readonly string[];
82
+ readonly pagesWithDivergences: readonly string[];
83
+ readonly divergenceIds: readonly string[];
84
+ readonly divergences: readonly AiPe2LayoutDivergenceEvidence[];
85
+ readonly reason?: "scope-page-evidence-not-found";
86
+ }
87
+
88
+ export type AiPe2OracleVerdict =
89
+ | "match"
90
+ | "divergent"
91
+ | "blocked"
92
+ | "oracle-incomplete"
93
+ | "runtime-projection-not-wired"
94
+ | "not-compared";
95
+
96
+ export interface AiPe2OracleEvidence {
97
+ readonly status: "available" | "not-wired";
98
+ readonly runIds: readonly string[];
99
+ readonly divergenceIds: readonly string[];
100
+ readonly verdict?: AiPe2OracleVerdict;
101
+ readonly reason?: "truth-adapter-unavailable" | "truth-adapter-error";
102
+ }
103
+
104
+ export interface AiPe2OracleEvidenceProviderInput {
105
+ readonly documentId: string;
106
+ readonly scopeId?: string;
107
+ readonly pageIds: readonly string[];
108
+ }
109
+
110
+ export type AiPe2OracleEvidenceProvider = (
111
+ input: AiPe2OracleEvidenceProviderInput,
112
+ ) => AiPe2OracleEvidence | null | undefined;
113
+
114
+ export interface AiPe2EvidenceOptions {
115
+ readonly oracle?: AiPe2OracleEvidenceProvider;
116
+ }
117
+
118
+ function copyCoverage(coverage: GeometryIndexCoverage): AiPe2GeometryCoverageEvidence {
119
+ return {
120
+ status: coverage.status,
121
+ pageCount: coverage.pageCount,
122
+ regionCount: coverage.regionCount,
123
+ sliceCount: coverage.sliceCount,
124
+ lineCount: coverage.lineCount,
125
+ anchorCount: coverage.anchorCount,
126
+ hitTargetCount: coverage.hitTargetCount,
127
+ semanticEntryCount: coverage.semanticEntryCount,
128
+ replacementEnvelopeCount: coverage.replacementEnvelopeCount,
129
+ objectHandleCount: coverage.objectHandleCount,
130
+ precision: {
131
+ exact: coverage.precision.exact,
132
+ "within-tolerance": coverage.precision["within-tolerance"],
133
+ heuristic: coverage.precision.heuristic,
134
+ },
135
+ };
136
+ }
137
+
138
+ function copyEnvelope(
139
+ envelope: GeometryReplacementEnvelopeEntry,
140
+ ): AiPe2ScopeReplacementEnvelopeEvidence {
141
+ return {
142
+ status: envelope.status,
143
+ precision: envelope.precision,
144
+ rectCount: envelope.rects.length,
145
+ ...(envelope.pageIds ? { pageIds: [...envelope.pageIds] } : {}),
146
+ ...(envelope.sourceIdentity ? { sourceIdentity: { ...envelope.sourceIdentity } } : {}),
147
+ };
148
+ }
149
+
150
+ const UNAVAILABLE_COVERAGE: AiPe2GeometryCoverageEvidence = {
151
+ status: "unavailable",
152
+ pageCount: 0,
153
+ regionCount: 0,
154
+ sliceCount: 0,
155
+ lineCount: 0,
156
+ anchorCount: 0,
157
+ hitTargetCount: 0,
158
+ semanticEntryCount: 0,
159
+ replacementEnvelopeCount: 0,
160
+ objectHandleCount: 0,
161
+ precision: { exact: 0, "within-tolerance": 0, heuristic: 0 },
162
+ };
163
+
164
+ function copyLayoutDivergence(
165
+ divergence: PublicLayoutDivergence,
166
+ ): AiPe2LayoutDivergenceEvidence {
167
+ return {
168
+ divergenceId: divergence.divergenceId,
169
+ kind: divergence.kind,
170
+ source: divergence.source,
171
+ severity: divergence.severity,
172
+ message: divergence.message,
173
+ ...(divergence.regionKinds ? { regionKinds: [...divergence.regionKinds] } : {}),
174
+ ...(divergence.fragmentIds ? { fragmentIds: [...divergence.fragmentIds] } : {}),
175
+ };
176
+ }
177
+
178
+ function projectLayoutEvidence(
179
+ runtime: RuntimeApiHandle,
180
+ pageIds?: readonly string[],
181
+ ): AiPe2LayoutEvidence {
182
+ if (typeof runtime.layout?.getPageCount !== "function") {
183
+ return {
184
+ status: "unavailable",
185
+ pageCount: 0,
186
+ pageIds: [],
187
+ pagesWithDivergences: [],
188
+ divergenceIds: [],
189
+ divergences: [],
190
+ };
191
+ }
192
+ const pageCount = runtime.layout.getPageCount();
193
+ const selectedPageIds = pageIds ? new Set(pageIds) : null;
194
+ const pageIdOut: string[] = [];
195
+ const pagesWithDivergences: string[] = [];
196
+ const divergenceIds = new Set<string>();
197
+ const divergencesById = new Map<string, AiPe2LayoutDivergenceEvidence>();
198
+
199
+ for (let pageIndex = 0; pageIndex < pageCount; pageIndex += 1) {
200
+ const page = runtime.layout.getPage(pageIndex);
201
+ if (!page) continue;
202
+ if (selectedPageIds && !selectedPageIds.has(page.pageId)) continue;
203
+ pageIdOut.push(page.pageId);
204
+
205
+ for (const divergenceId of page.frame?.divergenceIds ?? []) {
206
+ divergenceIds.add(divergenceId);
207
+ }
208
+ for (const divergence of page.divergences ?? []) {
209
+ divergenceIds.add(divergence.divergenceId);
210
+ divergencesById.set(divergence.divergenceId, copyLayoutDivergence(divergence));
211
+ }
212
+ if ((page.frame?.divergenceIds.length ?? 0) > 0 || (page.divergences?.length ?? 0) > 0) {
213
+ pagesWithDivergences.push(page.pageId);
214
+ }
215
+ }
216
+
217
+ return {
218
+ status: pageIdOut.length > 0 ? "realized" : "unavailable",
219
+ pageCount: pageIdOut.length,
220
+ pageIds: pageIdOut,
221
+ pagesWithDivergences,
222
+ divergenceIds: [...divergenceIds],
223
+ divergences: [...divergencesById.values()],
224
+ ...(selectedPageIds && pageIdOut.length === 0
225
+ ? { reason: "scope-page-evidence-not-found" as const }
226
+ : {}),
227
+ };
228
+ }
229
+
230
+ function copyOracleEvidence(evidence: AiPe2OracleEvidence): AiPe2OracleEvidence {
231
+ return {
232
+ status: evidence.status,
233
+ runIds: [...evidence.runIds],
234
+ divergenceIds: [...evidence.divergenceIds],
235
+ ...(evidence.verdict ? { verdict: evidence.verdict } : {}),
236
+ ...(evidence.reason ? { reason: evidence.reason } : {}),
237
+ };
238
+ }
239
+
240
+ function canonicalStoryKeyForHandle(handle: ScopeHandle): string {
241
+ const target = handle.storyTarget;
242
+ switch (target.kind) {
243
+ case "main":
244
+ return MAIN_STORY_KEY;
245
+ case "header":
246
+ return createHeaderFooterStoryKey({
247
+ kind: "header",
248
+ relationshipId: target.relationshipId,
249
+ variant: target.variant,
250
+ ...(target.sectionIndex !== undefined ? { sectionIndex: target.sectionIndex } : {}),
251
+ });
252
+ case "footer":
253
+ return createHeaderFooterStoryKey({
254
+ kind: "footer",
255
+ relationshipId: target.relationshipId,
256
+ variant: target.variant,
257
+ ...(target.sectionIndex !== undefined ? { sectionIndex: target.sectionIndex } : {}),
258
+ });
259
+ case "footnote":
260
+ return createNoteStoryKey("footnote", target.noteId);
261
+ case "endnote":
262
+ return createNoteStoryKey("endnote", target.noteId);
263
+ }
264
+ }
265
+
266
+ function semanticMainBlockPath(handle: ScopeHandle): string | null {
267
+ const path = handle.semanticPath;
268
+ if (path[0] !== "body") return null;
269
+ if (path[1] === "paragraph" || path[1] === "heading" || path[1] === "list-item") {
270
+ const blockIndex = path[path.length - 1];
271
+ return typeof blockIndex === "string" && /^\d+$/u.test(blockIndex)
272
+ ? `${MAIN_STORY_KEY}/block[${blockIndex}]`
273
+ : null;
274
+ }
275
+ if (path[1] === "table" && typeof path[2] === "string" && /^\d+$/u.test(path[2])) {
276
+ return `${MAIN_STORY_KEY}/block[${path[2]}]`;
277
+ }
278
+ return null;
279
+ }
280
+
281
+ function scoreEnvelopeForHandle(
282
+ envelope: GeometryReplacementEnvelopeEntry,
283
+ handle: ScopeHandle,
284
+ storyKey: string,
285
+ ): number {
286
+ if (envelope.scopeId !== handle.scopeId) return -1;
287
+ const source = envelope.sourceIdentity;
288
+ if (!source) return 0;
289
+
290
+ let score = 1;
291
+ if (source.scopeId === handle.scopeId) score += 2;
292
+ if (source.storyKey === storyKey) score += 8;
293
+ if (source.scopeKey === `${storyKey}:scope:${handle.scopeId}` ||
294
+ source.scopeKey?.startsWith(`${storyKey}:scope:${handle.scopeId}:`)) {
295
+ score += 4;
296
+ }
297
+
298
+ const blockPath = semanticMainBlockPath(handle);
299
+ if (blockPath && source.blockPath === blockPath) score += 2;
300
+
301
+ return score;
302
+ }
303
+
304
+ function findReplacementEnvelopeForHandle(
305
+ envelopes: readonly GeometryReplacementEnvelopeEntry[],
306
+ handle: ScopeHandle,
307
+ ): GeometryReplacementEnvelopeEntry | undefined {
308
+ const storyKey = canonicalStoryKeyForHandle(handle);
309
+ let best: GeometryReplacementEnvelopeEntry | undefined;
310
+ let bestScore = -1;
311
+ for (const envelope of envelopes) {
312
+ const score = scoreEnvelopeForHandle(envelope, handle, storyKey);
313
+ if (score > bestScore) {
314
+ best = envelope;
315
+ bestScore = score;
316
+ }
317
+ }
318
+ return bestScore >= 0 ? best : undefined;
319
+ }
320
+
321
+ function projectOracleEvidence(
322
+ runtime: RuntimeApiHandle,
323
+ options: AiPe2EvidenceOptions | undefined,
324
+ input: Omit<AiPe2OracleEvidenceProviderInput, "documentId">,
325
+ ): AiPe2OracleEvidence {
326
+ const provider = options?.oracle;
327
+ if (provider) {
328
+ try {
329
+ const evidence = provider({
330
+ documentId: runtime.getSessionState().documentId,
331
+ ...input,
332
+ });
333
+ if (evidence) return copyOracleEvidence(evidence);
334
+ } catch {
335
+ return {
336
+ status: "not-wired",
337
+ runIds: [],
338
+ divergenceIds: [],
339
+ reason: "truth-adapter-error",
340
+ };
341
+ }
342
+ }
343
+ return {
344
+ status: "not-wired",
345
+ runIds: [],
346
+ divergenceIds: [],
347
+ reason: "truth-adapter-unavailable",
348
+ };
349
+ }
350
+
351
+ export function projectDocumentPe2Evidence(
352
+ runtime: RuntimeApiHandle,
353
+ options?: AiPe2EvidenceOptions,
354
+ ): AiPe2DocumentEvidence {
355
+ const layout = projectLayoutEvidence(runtime);
356
+ const geometry = runtime.geometry
357
+ ? copyCoverage(runtime.geometry.getGeometryCoverage())
358
+ : UNAVAILABLE_COVERAGE;
359
+ return {
360
+ geometry,
361
+ layout,
362
+ oracle: projectOracleEvidence(runtime, options, { pageIds: layout.pageIds }),
363
+ };
364
+ }
365
+
366
+ export function projectScopePe2Evidence(
367
+ runtime: RuntimeApiHandle,
368
+ handle: ScopeHandle,
369
+ options?: AiPe2EvidenceOptions,
370
+ ): AiPe2ScopeEvidence {
371
+ const { scopeId } = handle;
372
+ if (!runtime.geometry) {
373
+ const layout = projectLayoutEvidence(runtime, []);
374
+ return {
375
+ geometry: {
376
+ coverage: UNAVAILABLE_COVERAGE,
377
+ reason: "geometry-index-unavailable",
378
+ },
379
+ layout,
380
+ oracle: projectOracleEvidence(runtime, options, { scopeId, pageIds: layout.pageIds }),
381
+ };
382
+ }
383
+ const coverage = copyCoverage(runtime.geometry.getGeometryCoverage());
384
+ const index = runtime.geometry.getGeometryIndex();
385
+ if (!index) {
386
+ const layout = projectLayoutEvidence(runtime, []);
387
+ return {
388
+ geometry: {
389
+ coverage,
390
+ reason: "geometry-index-unavailable",
391
+ },
392
+ layout,
393
+ oracle: projectOracleEvidence(runtime, options, { scopeId, pageIds: layout.pageIds }),
394
+ };
395
+ }
396
+
397
+ const envelope = findReplacementEnvelopeForHandle(index.replacementEnvelopes, handle);
398
+ if (!envelope) {
399
+ const layout = projectLayoutEvidence(runtime, []);
400
+ return {
401
+ geometry: {
402
+ coverage,
403
+ reason: "scope-envelope-not-found",
404
+ },
405
+ layout,
406
+ oracle: projectOracleEvidence(runtime, options, { scopeId, pageIds: layout.pageIds }),
407
+ };
408
+ }
409
+
410
+ const layout = projectLayoutEvidence(runtime, envelope.pageIds);
411
+ return {
412
+ geometry: {
413
+ coverage,
414
+ replacementEnvelope: copyEnvelope(envelope),
415
+ },
416
+ layout,
417
+ oracle: projectOracleEvidence(runtime, options, { scopeId, pageIds: layout.pageIds }),
418
+ };
419
+ }
@@ -37,15 +37,22 @@ import {
37
37
  emitScopeMetadataAudit,
38
38
  snapshotDocumentHash,
39
39
  } from "./_metadata-audit.ts";
40
+ import {
41
+ projectAuditReference,
42
+ type AiActionAuditReference,
43
+ } from "./_audit-reference.ts";
40
44
 
41
45
  export interface AttachExplanationInput {
42
46
  readonly scopeId: string;
43
47
  readonly explanation: string;
48
+ readonly actorId?: string;
49
+ readonly origin?: "ui" | "agent" | "host";
44
50
  }
45
51
 
46
52
  export interface AttachExplanationResult {
47
53
  readonly explanationId: string;
48
54
  readonly attached: boolean;
55
+ readonly auditReference?: AiActionAuditReference;
49
56
  /**
50
57
  * Present when `attached: false`. Mirrors the primitive's refusal
51
58
  * reason (`scope-not-resolvable:<scopeId>` post-coord-06 §13c;
@@ -85,11 +92,14 @@ export interface CreateIssueInput {
85
92
  readonly scopeId: string;
86
93
  readonly summary: string;
87
94
  readonly severity?: "info" | "warning" | "error";
95
+ readonly actorId?: string;
96
+ readonly origin?: "ui" | "agent" | "host";
88
97
  }
89
98
 
90
99
  export interface CreateIssueResult {
91
100
  readonly issueId: string;
92
101
  readonly created: boolean;
102
+ readonly auditReference?: AiActionAuditReference;
93
103
  readonly reason?: string;
94
104
  }
95
105
 
@@ -141,10 +151,13 @@ export function createAttachFamily(runtime: RuntimeApiHandle) {
141
151
  explanation: input.explanation,
142
152
  explanationId,
143
153
  });
154
+ let auditReference: AiActionAuditReference | undefined;
144
155
  if (primitiveResult.attached && preScope) {
145
- emitScopeMetadataAudit({
156
+ const audit = emitScopeMetadataAudit({
146
157
  runtime,
147
158
  actionId: attachExplanationMetadata.name,
159
+ ...(input.actorId ? { actorId: input.actorId } : {}),
160
+ ...(input.origin ? { origin: input.origin } : {}),
148
161
  scopeId: input.scopeId,
149
162
  targetScopeSnapshot: preScope,
150
163
  proposedContent: {
@@ -159,6 +172,7 @@ export function createAttachFamily(runtime: RuntimeApiHandle) {
159
172
  emittedAtUtc: new Date(0).toISOString(),
160
173
  documentHashBefore,
161
174
  });
175
+ auditReference = projectAuditReference(audit);
162
176
  }
163
177
  emitUxResponse(runtime, {
164
178
  apiFn: attachExplanationMetadata.name,
@@ -170,6 +184,7 @@ export function createAttachFamily(runtime: RuntimeApiHandle) {
170
184
  return {
171
185
  explanationId: primitiveResult.explanationId,
172
186
  attached: primitiveResult.attached,
187
+ ...(auditReference ? { auditReference } : {}),
173
188
  // §1.17 — surface primitive's reason on refusal for symmetry
174
189
  // with CreateIssueResult. The primitive returns `reason` only
175
190
  // when `attached: false`; passthrough preserves that contract.
@@ -197,10 +212,13 @@ export function createAttachFamily(runtime: RuntimeApiHandle) {
197
212
  ...(input.severity ? { severity: input.severity } : {}),
198
213
  issueId,
199
214
  });
215
+ let auditReference: AiActionAuditReference | undefined;
200
216
  if (primitiveResult.created && preScope) {
201
- emitScopeMetadataAudit({
217
+ const audit = emitScopeMetadataAudit({
202
218
  runtime,
203
219
  actionId: createIssueMetadata.name,
220
+ ...(input.actorId ? { actorId: input.actorId } : {}),
221
+ ...(input.origin ? { origin: input.origin } : {}),
204
222
  scopeId: input.scopeId,
205
223
  targetScopeSnapshot: preScope,
206
224
  proposedContent: {
@@ -216,6 +234,7 @@ export function createAttachFamily(runtime: RuntimeApiHandle) {
216
234
  emittedAtUtc: new Date(0).toISOString(),
217
235
  documentHashBefore,
218
236
  });
237
+ auditReference = projectAuditReference(audit);
219
238
  }
220
239
  emitUxResponse(runtime, {
221
240
  apiFn: createIssueMetadata.name,
@@ -227,6 +246,7 @@ export function createAttachFamily(runtime: RuntimeApiHandle) {
227
246
  const out: CreateIssueResult = {
228
247
  issueId: primitiveResult.issueId,
229
248
  created: primitiveResult.created,
249
+ ...(auditReference ? { auditReference } : {}),
230
250
  ...(primitiveResult.created ? {} : { reason: primitiveResult.reason }),
231
251
  };
232
252
  return out;
@@ -24,6 +24,11 @@ import {
24
24
  type SemanticScope,
25
25
  } from "../../../runtime/scopes/index.ts";
26
26
  import type { ApiV3FnMetadata } from "../_layer-metadata.ts";
27
+ import {
28
+ projectScopePe2Evidence,
29
+ type AiPe2EvidenceOptions,
30
+ type AiPe2ScopeEvidence,
31
+ } from "./_pe2-evidence.ts";
27
32
 
28
33
  /**
29
34
  * Handle-shaped input (architecture A3 — scope-handle-first targeting).
@@ -42,8 +47,9 @@ export interface GetScopeBundleInput {
42
47
  readonly nowUtc: string;
43
48
  }
44
49
 
45
- /** Re-export the runtime-side `ScopeBundle` so v3 consumers get the rich shape. */
46
- export type { RuntimeScopeBundle as ScopeBundle };
50
+ export type ScopeBundle = RuntimeScopeBundle & {
51
+ readonly pe2Evidence: AiPe2ScopeEvidence;
52
+ };
47
53
 
48
54
  export interface ScopeBundleNotFound {
49
55
  readonly notFound: true;
@@ -91,14 +97,17 @@ export const getScopeBundleMetadata: ApiV3FnMetadata = {
91
97
  boundedScope: "scope",
92
98
  auditCategory: "scope-bundle",
93
99
  contextPromptShape:
94
- "Bundle content + formatting + layout + geometry + workflow + replaceability for prompt context.",
100
+ "Bundle content + formatting + layout + geometry + workflow + replaceability + PE2 geometry coverage/envelope evidence for prompt context.",
95
101
  },
96
102
  stateClass: "A-canonical",
97
103
  persistsTo: "canonical",
98
104
  rwdReference: "§AI API § ai.getScopeBundle",
99
105
  };
100
106
 
101
- export function createBundleFamily(runtime: RuntimeApiHandle) {
107
+ export function createBundleFamily(
108
+ runtime: RuntimeApiHandle,
109
+ pe2Evidence?: AiPe2EvidenceOptions,
110
+ ) {
102
111
  const compiler = createScopeCompilerService(runtime);
103
112
  return {
104
113
  getScope(input: GetScopeInput): SemanticScope | null {
@@ -110,7 +119,7 @@ export function createBundleFamily(runtime: RuntimeApiHandle) {
110
119
  return compiled?.scope ?? null;
111
120
  },
112
121
 
113
- getScopeBundle(input: GetScopeBundleInput): RuntimeScopeBundle | ScopeBundleNotFound {
122
+ getScopeBundle(input: GetScopeBundleInput): ScopeBundle | ScopeBundleNotFound {
114
123
  // @endStateApi — live-with-adapter. Routes through the compiler-
115
124
  // service facade.
116
125
  //
@@ -126,7 +135,10 @@ export function createBundleFamily(runtime: RuntimeApiHandle) {
126
135
  reason: "no scope with this id in canonical document or review store",
127
136
  };
128
137
  }
129
- return bundle;
138
+ return {
139
+ ...bundle,
140
+ pe2Evidence: projectScopePe2Evidence(runtime, input.handle, pe2Evidence),
141
+ };
130
142
  },
131
143
  };
132
144
  }