@beyondwork/docx-react-component 1.0.106 → 1.0.109
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/package.json +19 -5
- package/src/api/geometry-overlay-rects.ts +5 -0
- package/src/api/package-version.ts +1 -1
- package/src/api/page-anchor-id.ts +5 -0
- package/src/api/public-types.ts +16 -9
- package/src/api/table-node-specs.ts +6 -0
- package/src/api/v3/_create.ts +2 -1
- package/src/api/v3/_page-anchor-id.ts +52 -0
- package/src/api/v3/_runtime-handle.ts +92 -1
- package/src/api/v3/ai/_audit-time.ts +5 -0
- package/src/api/v3/ai/_pe2-evidence.ts +38 -0
- package/src/api/v3/ai/attach.ts +7 -2
- package/src/api/v3/ai/replacement.ts +101 -18
- package/src/api/v3/ai/resolve.ts +2 -2
- package/src/api/v3/ai/review.ts +177 -3
- package/src/api/v3/index.ts +1 -0
- package/src/api/v3/runtime/collab.ts +462 -0
- package/src/api/v3/runtime/document.ts +503 -20
- package/src/api/v3/runtime/geometry.ts +97 -0
- package/src/api/v3/runtime/layout.ts +744 -0
- package/src/api/v3/runtime/perf-probe.ts +14 -0
- package/src/api/v3/runtime/viewport.ts +9 -8
- package/src/api/v3/ui/_types.ts +149 -55
- package/src/api/v3/ui/chrome-preset-model.ts +5 -5
- package/src/api/v3/ui/debug.ts +115 -2
- package/src/api/v3/ui/index.ts +13 -0
- package/src/api/v3/ui/overlays.ts +0 -8
- package/src/api/v3/ui/surface.ts +56 -0
- package/src/api/v3/ui/viewport.ts +22 -9
- package/src/core/commands/image-commands.ts +1 -0
- package/src/core/commands/index.ts +6 -0
- package/src/core/schema/text-schema.ts +43 -5
- package/src/core/selection/mapping.ts +8 -1
- package/src/core/selection/review-anchors.ts +5 -1
- package/src/core/state/text-transaction.ts +8 -2
- package/src/io/export/serialize-revisions.ts +149 -1
- package/src/io/normalize/normalize-text.ts +6 -0
- package/src/io/ooxml/parse-bookmark-references.ts +55 -0
- package/src/io/ooxml/parse-fields.ts +24 -2
- package/src/io/ooxml/parse-headers-footers.ts +38 -5
- package/src/io/ooxml/parse-main-document.ts +153 -9
- package/src/io/ooxml/parse-numbering.ts +20 -0
- package/src/io/ooxml/parse-revisions.ts +19 -8
- package/src/io/opc/package-reader.ts +98 -8
- package/src/model/anchor.ts +4 -3
- package/src/model/canonical-document.ts +220 -2
- package/src/model/canonical-hash.ts +221 -0
- package/src/model/canonical-layout-inputs.ts +245 -6
- package/src/model/layout/index.ts +1 -0
- package/src/model/layout/page-graph-types.ts +118 -1
- package/src/model/review/revision-types.ts +14 -3
- package/src/preservation/store.ts +20 -4
- package/src/review/README.md +1 -1
- package/src/review/store/revision-actions.ts +14 -2
- package/src/runtime/collab/event-types.ts +67 -1
- package/src/runtime/collab/runtime-collab-sync.ts +177 -5
- package/src/runtime/diagnostics/layout-guard-warning.ts +18 -0
- package/src/runtime/document-heading-outline.ts +147 -0
- package/src/runtime/document-navigation.ts +8 -243
- package/src/runtime/document-runtime.ts +240 -97
- package/src/runtime/edit-dispatch/dispatch-text-command.ts +11 -0
- package/src/runtime/formatting/layout-inputs.ts +38 -5
- package/src/runtime/formatting/numbering/geometry.ts +28 -2
- package/src/runtime/geometry/adjacent-geometry-intake.ts +835 -0
- package/src/runtime/geometry/caret-geometry.ts +5 -6
- package/src/runtime/geometry/geometry-facet.ts +60 -10
- package/src/runtime/geometry/geometry-index.ts +591 -20
- package/src/runtime/geometry/geometry-types.ts +59 -0
- package/src/runtime/geometry/hit-test.ts +11 -1
- package/src/runtime/geometry/overlay-rects.ts +5 -3
- package/src/runtime/geometry/project-anchors.ts +1 -1
- package/src/runtime/geometry/word-layout-v2-line-intake.ts +323 -0
- package/src/runtime/layout/index.ts +6 -0
- package/src/runtime/layout/layout-engine-instance.ts +6 -1
- package/src/runtime/layout/layout-engine-version.ts +181 -16
- package/src/runtime/layout/layout-facet-types.ts +6 -0
- package/src/runtime/layout/page-graph.ts +21 -4
- package/src/runtime/layout/paginated-layout-engine.ts +139 -15
- package/src/runtime/layout/project-block-fragments.ts +265 -7
- package/src/runtime/layout/public-facet.ts +78 -24
- package/src/runtime/layout/table-row-continuation-contract.ts +107 -0
- package/src/runtime/layout/table-row-split.ts +92 -35
- package/src/runtime/prerender/cache-envelope.ts +2 -2
- package/src/runtime/prerender/cache-key.ts +5 -4
- package/src/runtime/prerender/customxml-cache.ts +0 -1
- package/src/runtime/render/render-kernel.ts +1 -1
- package/src/runtime/revision-runtime.ts +112 -10
- package/src/runtime/scopes/_scope-dependencies.ts +1 -0
- package/src/runtime/scopes/action-validation.ts +22 -2
- package/src/runtime/scopes/capabilities.ts +316 -0
- package/src/runtime/scopes/compile-scope-bundle.ts +14 -0
- package/src/runtime/scopes/compiler-service.ts +108 -4
- package/src/runtime/scopes/content-control-evidence.ts +79 -0
- package/src/runtime/scopes/create-issue.ts +5 -5
- package/src/runtime/scopes/evidence.ts +91 -0
- package/src/runtime/scopes/formatting/apply.ts +2 -0
- package/src/runtime/scopes/geometry-evidence.ts +130 -0
- package/src/runtime/scopes/index.ts +54 -0
- package/src/runtime/scopes/issue-lifecycle.ts +224 -0
- package/src/runtime/scopes/layout-evidence.ts +374 -0
- package/src/runtime/scopes/multi-paragraph-refusal.ts +37 -0
- package/src/runtime/scopes/preservation-boundary.ts +7 -1
- package/src/runtime/scopes/replacement/apply.ts +97 -34
- package/src/runtime/scopes/scope-kinds/paragraph.ts +108 -12
- package/src/runtime/scopes/semantic-scope-types.ts +242 -3
- package/src/runtime/scopes/visualization.ts +28 -0
- package/src/runtime/surface-projection.ts +44 -5
- package/src/runtime/telemetry/perf-probe.ts +216 -0
- package/src/runtime/virtualized-rendering.ts +36 -1
- package/src/runtime/workflow/ai-issue-lifecycle.ts +253 -0
- package/src/runtime/workflow/coordinator.ts +39 -11
- package/src/runtime/workflow/derived-scope-resolver.ts +63 -9
- package/src/runtime/workflow/index.ts +3 -0
- package/src/runtime/workflow/overlay-lane-types.ts +58 -0
- package/src/runtime/workflow/overlay-lanes.ts +168 -10
- package/src/runtime/workflow/overlay-store.ts +2 -2
- package/src/runtime/workflow/redline-posture-calibration.ts +257 -0
- package/src/runtime/workflow/word-field-matrix-calibration.ts +231 -0
- package/src/session/_sync-legacy.ts +17 -27
- package/src/session/import/loader.ts +6 -4
- package/src/session/import/source-package-evidence.ts +186 -2
- package/src/session/index.ts +5 -6
- package/src/session/session.ts +30 -56
- package/src/session/types.ts +8 -13
- package/src/shell/session-bootstrap.ts +155 -81
- package/src/ui/WordReviewEditor.tsx +520 -12
- package/src/ui/editor-shell-view.tsx +14 -4
- package/src/ui/editor-surface-controller.tsx +5 -3
- package/src/ui/headless/selection-tool-resolver.ts +1 -2
- package/src/ui/presence-overlay-lane.ts +0 -1
- package/src/ui/ui-controller-factory.ts +7 -0
- package/src/ui-tailwind/chrome/build-context-menu-entries.ts +5 -1
- package/src/ui-tailwind/chrome/editor-action-registry.ts +105 -5
- package/src/ui-tailwind/chrome/editor-actions-to-palette.ts +7 -0
- package/src/ui-tailwind/chrome/layer-debug-contracts.ts +208 -0
- package/src/ui-tailwind/chrome/resolve-target-kind.ts +13 -0
- package/src/ui-tailwind/chrome/tw-alert-banner.tsx +11 -3
- package/src/ui-tailwind/chrome/tw-command-palette.tsx +36 -6
- package/src/ui-tailwind/chrome/tw-context-menu.tsx +6 -1
- package/src/ui-tailwind/chrome/tw-display-mode-selector.tsx +42 -109
- package/src/ui-tailwind/chrome/tw-inline-find-bar.tsx +26 -6
- package/src/ui-tailwind/chrome/tw-navigation-command-bar.tsx +328 -0
- package/src/ui-tailwind/chrome/tw-object-context-toolbar.tsx +8 -4
- package/src/ui-tailwind/chrome/tw-runtime-repl-dialog.tsx +129 -1
- package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +19 -5
- package/src/ui-tailwind/chrome/tw-selection-tool-structure.tsx +5 -1
- package/src/ui-tailwind/chrome/tw-workspace-chrome-host.tsx +28 -12
- package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +30 -3
- package/src/ui-tailwind/chrome-overlay/tw-object-selection-overlay.tsx +116 -10
- package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +223 -94
- package/src/ui-tailwind/chrome-overlay/tw-presence-overlay-lane.tsx +157 -0
- package/src/ui-tailwind/chrome-overlay/tw-review-overlay-lane-markers.tsx +259 -0
- package/src/ui-tailwind/chrome-overlay/tw-scope-card-layer.tsx +5 -2
- package/src/ui-tailwind/chrome-overlay/tw-substrate-overlay-lanes.tsx +314 -0
- package/src/ui-tailwind/debug/README.md +4 -1
- package/src/ui-tailwind/debug/layer11-consumer-readiness.ts +272 -0
- package/src/ui-tailwind/debug/layer11-word-field-matrix-evidence.ts +160 -0
- package/src/ui-tailwind/editor-surface/perf-probe.ts +14 -215
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +42 -0
- package/src/ui-tailwind/editor-surface/pm-position-map.ts +38 -2
- package/src/ui-tailwind/editor-surface/pm-schema.ts +14 -4
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +34 -5
- package/src/ui-tailwind/editor-surface/runtime-decoration-plugin.ts +9 -19
- package/src/ui-tailwind/editor-surface/surface-build-keys.ts +2 -2
- package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +145 -0
- package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +16 -11
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +8 -10
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +3 -0
- package/src/ui-tailwind/page-stack/tw-page-chrome-entry.tsx +4 -2
- package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +60 -20
- package/src/ui-tailwind/page-stack/tw-region-block-renderer.tsx +16 -11
- package/src/ui-tailwind/review/tw-health-panel.tsx +36 -17
- package/src/ui-tailwind/review/tw-review-rail.tsx +7 -4
- package/src/ui-tailwind/review-workspace/diagnostics-visibility.ts +44 -0
- package/src/ui-tailwind/review-workspace/page-shell-metrics.ts +11 -0
- package/src/ui-tailwind/review-workspace/tw-review-workspace-rail.tsx +16 -1
- package/src/ui-tailwind/review-workspace/types.ts +26 -12
- package/src/ui-tailwind/review-workspace/use-diagnostics-signal.ts +40 -11
- package/src/ui-tailwind/review-workspace/use-layout-facet-render-signal.ts +2 -1
- package/src/ui-tailwind/review-workspace/use-page-markers.ts +15 -26
- package/src/ui-tailwind/review-workspace/use-scope-card-state.ts +35 -18
- package/src/ui-tailwind/review-workspace/use-selection-toolbar-placement.ts +41 -32
- package/src/ui-tailwind/review-workspace/use-status-bar-page-facts.ts +2 -1
- package/src/ui-tailwind/review-workspace/use-workspace-side-effects.ts +2 -1
- package/src/ui-tailwind/status/tw-status-bar.tsx +6 -5
- package/src/ui-tailwind/toolbar/tw-role-action-region.tsx +52 -80
- package/src/ui-tailwind/toolbar/tw-shell-header.tsx +12 -48
- package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +9 -4
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +328 -361
- package/src/ui-tailwind/tw-review-workspace.tsx +152 -286
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scope layout evidence projection.
|
|
3
|
+
*
|
|
4
|
+
* Layer 08 only consumes layout evidence supplied by lower layers. When the
|
|
5
|
+
* seam is absent or cold, the bundle reports that explicitly instead of
|
|
6
|
+
* deriving page slices or continuation state from canonical content.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type {
|
|
10
|
+
ScopeLayoutContinuationEvidence,
|
|
11
|
+
ScopeLayoutEvidence,
|
|
12
|
+
ScopeTableFrameEvidence,
|
|
13
|
+
ScopeTableFramePageEvidence,
|
|
14
|
+
} from "./semantic-scope-types.ts";
|
|
15
|
+
|
|
16
|
+
export interface ScopeLayoutEvidenceEntry {
|
|
17
|
+
readonly status?: ScopeLayoutEvidence["status"];
|
|
18
|
+
readonly completeness?: ScopeLayoutEvidence["completeness"];
|
|
19
|
+
readonly reason?: string;
|
|
20
|
+
readonly pageSliceIds?: readonly string[];
|
|
21
|
+
readonly layoutObjectIds?: readonly string[];
|
|
22
|
+
readonly continuationState?: ScopeLayoutContinuationEvidence;
|
|
23
|
+
readonly divergenceIds?: readonly string[];
|
|
24
|
+
readonly tableFrame?: ScopeTableFrameEvidence;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ScopeLayoutEvidenceProvider {
|
|
28
|
+
getScopeLayoutEvidence(scopeId: string): ScopeLayoutEvidenceEntry | null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface ScopeRuntimeLayoutFragment {
|
|
32
|
+
readonly fragmentId: string;
|
|
33
|
+
readonly blockId: string;
|
|
34
|
+
readonly pageId: string;
|
|
35
|
+
readonly pageIndex: number;
|
|
36
|
+
readonly kind?: "whole" | "paragraph-slice" | "table-slice";
|
|
37
|
+
readonly tableRowRange?: {
|
|
38
|
+
readonly from: number;
|
|
39
|
+
readonly to: number;
|
|
40
|
+
readonly totalRows: number;
|
|
41
|
+
};
|
|
42
|
+
readonly continuation?: {
|
|
43
|
+
readonly kind: string;
|
|
44
|
+
readonly continuesFromPreviousPage?: boolean;
|
|
45
|
+
readonly continuesToNextPage?: boolean;
|
|
46
|
+
readonly repeatedHeaderRowIndexes?: readonly number[];
|
|
47
|
+
readonly splitRowCarry?: readonly {
|
|
48
|
+
readonly rowIndex: number;
|
|
49
|
+
readonly continuesFromPreviousPage: boolean;
|
|
50
|
+
readonly continuesToNextPage: boolean;
|
|
51
|
+
}[];
|
|
52
|
+
readonly verticalMergeCarry?: readonly {
|
|
53
|
+
readonly columnIndex: number;
|
|
54
|
+
readonly restartRowIndex: number;
|
|
55
|
+
}[];
|
|
56
|
+
};
|
|
57
|
+
readonly layoutObject?: {
|
|
58
|
+
readonly objectId?: string;
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface ScopeRuntimeLayoutEvidenceSource {
|
|
63
|
+
getPageCount(): number;
|
|
64
|
+
getFragmentsForPage(pageIndex: number): readonly ScopeRuntimeLayoutFragment[];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface RuntimeTableFrameLayoutEvidenceOptions {
|
|
68
|
+
readonly layout: ScopeRuntimeLayoutEvidenceSource;
|
|
69
|
+
readonly tableBlockIdsByBlockIndex?: ReadonlyMap<number, string>;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface ScopeRuntimeRenderSnapshotLike {
|
|
73
|
+
readonly surface?: {
|
|
74
|
+
readonly blocks?: readonly {
|
|
75
|
+
readonly kind?: string;
|
|
76
|
+
readonly blockId?: string;
|
|
77
|
+
}[];
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function freezeList<T>(values: readonly T[] | undefined): readonly T[] | undefined {
|
|
82
|
+
return values ? Object.freeze([...values]) : undefined;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function unique<T>(values: readonly T[]): readonly T[] {
|
|
86
|
+
return Object.freeze([...new Set(values)]);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function parseTableFamilyScopeId(scopeId: string):
|
|
90
|
+
| { readonly scopeKind: "table"; readonly blockIndex: number }
|
|
91
|
+
| { readonly scopeKind: "table-row"; readonly blockIndex: number; readonly rowIndex: number }
|
|
92
|
+
| {
|
|
93
|
+
readonly scopeKind: "table-cell";
|
|
94
|
+
readonly blockIndex: number;
|
|
95
|
+
readonly rowIndex: number;
|
|
96
|
+
readonly cellIndex: number;
|
|
97
|
+
}
|
|
98
|
+
| null {
|
|
99
|
+
const table = /^table:(\d+)$/.exec(scopeId);
|
|
100
|
+
if (table) return { scopeKind: "table", blockIndex: Number(table[1]) };
|
|
101
|
+
const row = /^row:(\d+):(\d+)$/.exec(scopeId);
|
|
102
|
+
if (row) {
|
|
103
|
+
return {
|
|
104
|
+
scopeKind: "table-row",
|
|
105
|
+
blockIndex: Number(row[1]),
|
|
106
|
+
rowIndex: Number(row[2]),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
const cell = /^cell:(\d+):(\d+):(\d+)$/.exec(scopeId);
|
|
110
|
+
if (cell) {
|
|
111
|
+
return {
|
|
112
|
+
scopeKind: "table-cell",
|
|
113
|
+
blockIndex: Number(cell[1]),
|
|
114
|
+
rowIndex: Number(cell[2]),
|
|
115
|
+
cellIndex: Number(cell[3]),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function candidateTableBlockIds(
|
|
122
|
+
blockIndex: number,
|
|
123
|
+
mapped?: ReadonlyMap<number, string>,
|
|
124
|
+
): readonly string[] {
|
|
125
|
+
const ids: string[] = [];
|
|
126
|
+
const mappedId = mapped?.get(blockIndex);
|
|
127
|
+
if (mappedId) ids.push(mappedId);
|
|
128
|
+
ids.push(`table-${blockIndex}`, `table:${blockIndex}`, `block-${blockIndex}`);
|
|
129
|
+
return unique(ids);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function rowInFragment(
|
|
133
|
+
fragment: ScopeRuntimeLayoutFragment,
|
|
134
|
+
rowIndex: number,
|
|
135
|
+
): boolean {
|
|
136
|
+
const range = fragment.tableRowRange;
|
|
137
|
+
if (range && rowIndex >= range.from && rowIndex < range.to) return true;
|
|
138
|
+
return fragment.continuation?.repeatedHeaderRowIndexes?.includes(rowIndex) === true;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function fragmentMatchesScope(
|
|
142
|
+
fragment: ScopeRuntimeLayoutFragment,
|
|
143
|
+
parsed: NonNullable<ReturnType<typeof parseTableFamilyScopeId>>,
|
|
144
|
+
): boolean {
|
|
145
|
+
if (parsed.scopeKind === "table") return true;
|
|
146
|
+
return rowInFragment(fragment, parsed.rowIndex);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function collectTableFragments(
|
|
150
|
+
layout: ScopeRuntimeLayoutEvidenceSource,
|
|
151
|
+
blockIds: readonly string[],
|
|
152
|
+
parsed: NonNullable<ReturnType<typeof parseTableFamilyScopeId>>,
|
|
153
|
+
): readonly ScopeRuntimeLayoutFragment[] {
|
|
154
|
+
const matches: ScopeRuntimeLayoutFragment[] = [];
|
|
155
|
+
const blockIdSet = new Set(blockIds);
|
|
156
|
+
for (let pageIndex = 0; pageIndex < layout.getPageCount(); pageIndex += 1) {
|
|
157
|
+
for (const fragment of layout.getFragmentsForPage(pageIndex)) {
|
|
158
|
+
if (!blockIdSet.has(fragment.blockId)) continue;
|
|
159
|
+
if (fragment.kind !== undefined && fragment.kind !== "table-slice" && fragment.kind !== "whole") {
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (!fragmentMatchesScope(fragment, parsed)) continue;
|
|
163
|
+
matches.push(fragment);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return Object.freeze(matches);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function projectTableFramePage(
|
|
170
|
+
fragment: ScopeRuntimeLayoutFragment,
|
|
171
|
+
): ScopeTableFramePageEvidence {
|
|
172
|
+
const repeated = fragment.continuation?.repeatedHeaderRowIndexes;
|
|
173
|
+
const splitRowCarry = fragment.continuation?.splitRowCarry;
|
|
174
|
+
const carry = fragment.continuation?.verticalMergeCarry;
|
|
175
|
+
return {
|
|
176
|
+
pageId: fragment.pageId,
|
|
177
|
+
pageIndex: fragment.pageIndex,
|
|
178
|
+
fragmentId: fragment.fragmentId,
|
|
179
|
+
...(fragment.tableRowRange ? { rowRange: { ...fragment.tableRowRange } } : {}),
|
|
180
|
+
...(fragment.continuation?.continuesFromPreviousPage !== undefined
|
|
181
|
+
? { continuesFromPreviousPage: fragment.continuation.continuesFromPreviousPage }
|
|
182
|
+
: {}),
|
|
183
|
+
...(fragment.continuation?.continuesToNextPage !== undefined
|
|
184
|
+
? { continuesToNextPage: fragment.continuation.continuesToNextPage }
|
|
185
|
+
: {}),
|
|
186
|
+
...(repeated ? { repeatedHeaderRowIndexes: unique(repeated) } : {}),
|
|
187
|
+
...(splitRowCarry ? { splitRowCarry: Object.freeze(splitRowCarry.map((item) => ({ ...item }))) } : {}),
|
|
188
|
+
...(carry ? { verticalMergeCarry: Object.freeze(carry.map((item) => ({ ...item }))) } : {}),
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function projectTableFrame(
|
|
193
|
+
blockId: string,
|
|
194
|
+
parsed: NonNullable<ReturnType<typeof parseTableFamilyScopeId>>,
|
|
195
|
+
fragments: readonly ScopeRuntimeLayoutFragment[],
|
|
196
|
+
): ScopeTableFrameEvidence {
|
|
197
|
+
const pageIds = unique(fragments.map((fragment) => fragment.pageId));
|
|
198
|
+
const pageSliceIds = unique(fragments.map((fragment) => fragment.fragmentId));
|
|
199
|
+
const layoutObjectIds = unique(
|
|
200
|
+
fragments
|
|
201
|
+
.map((fragment) => fragment.layoutObject?.objectId)
|
|
202
|
+
.filter((objectId): objectId is string => typeof objectId === "string" && objectId.length > 0),
|
|
203
|
+
);
|
|
204
|
+
const rowRangesByPage = Object.freeze(fragments.map(projectTableFramePage));
|
|
205
|
+
const repeatedHeaderRowIndexes = unique(
|
|
206
|
+
fragments.flatMap((fragment) => fragment.continuation?.repeatedHeaderRowIndexes ?? []),
|
|
207
|
+
);
|
|
208
|
+
const verticalMergeCarry = Object.freeze(
|
|
209
|
+
fragments
|
|
210
|
+
.flatMap((fragment) => fragment.continuation?.verticalMergeCarry ?? [])
|
|
211
|
+
.map((item) => ({ ...item })),
|
|
212
|
+
);
|
|
213
|
+
const splitRowCarry = Object.freeze(
|
|
214
|
+
fragments
|
|
215
|
+
.flatMap((fragment) => fragment.continuation?.splitRowCarry ?? [])
|
|
216
|
+
.map((item) => ({ ...item })),
|
|
217
|
+
);
|
|
218
|
+
return {
|
|
219
|
+
source: "runtime.layout.table-frame-continuation",
|
|
220
|
+
blockId,
|
|
221
|
+
scopeKind: parsed.scopeKind,
|
|
222
|
+
...(parsed.scopeKind === "table-row" || parsed.scopeKind === "table-cell"
|
|
223
|
+
? { rowIndex: parsed.rowIndex }
|
|
224
|
+
: {}),
|
|
225
|
+
...(parsed.scopeKind === "table-cell" ? { cellIndex: parsed.cellIndex } : {}),
|
|
226
|
+
...(pageIds.length > 0 ? { pageIds } : {}),
|
|
227
|
+
...(pageSliceIds.length > 0 ? { pageSliceIds } : {}),
|
|
228
|
+
...(layoutObjectIds.length > 0 ? { layoutObjectIds } : {}),
|
|
229
|
+
...(rowRangesByPage.length > 0 ? { rowRangesByPage } : {}),
|
|
230
|
+
...(repeatedHeaderRowIndexes.length > 0 ? { repeatedHeaderRowIndexes } : {}),
|
|
231
|
+
...(splitRowCarry.length > 0 ? { splitRowCarry } : {}),
|
|
232
|
+
...(verticalMergeCarry.length > 0 ? { verticalMergeCarry } : {}),
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function continuationFromTableFrame(
|
|
237
|
+
fragments: readonly ScopeRuntimeLayoutFragment[],
|
|
238
|
+
): ScopeLayoutContinuationEvidence {
|
|
239
|
+
const pageIds = unique(fragments.map((fragment) => fragment.pageId));
|
|
240
|
+
return {
|
|
241
|
+
...(pageIds.length > 0 ? { pageIds } : {}),
|
|
242
|
+
pageCount: pageIds.length,
|
|
243
|
+
crossesPageBoundary: pageIds.length > 1,
|
|
244
|
+
continuedFromPreviousPage: fragments.some(
|
|
245
|
+
(fragment) => fragment.continuation?.continuesFromPreviousPage === true,
|
|
246
|
+
),
|
|
247
|
+
continuesToNextPage: fragments.some(
|
|
248
|
+
(fragment) => fragment.continuation?.continuesToNextPage === true,
|
|
249
|
+
),
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function createRuntimeTableFrameLayoutEvidenceProvider(
|
|
254
|
+
options: RuntimeTableFrameLayoutEvidenceOptions,
|
|
255
|
+
): ScopeLayoutEvidenceProvider {
|
|
256
|
+
return {
|
|
257
|
+
getScopeLayoutEvidence(scopeId) {
|
|
258
|
+
const parsed = parseTableFamilyScopeId(scopeId);
|
|
259
|
+
if (!parsed) return null;
|
|
260
|
+
|
|
261
|
+
const blockIds = candidateTableBlockIds(
|
|
262
|
+
parsed.blockIndex,
|
|
263
|
+
options.tableBlockIdsByBlockIndex,
|
|
264
|
+
);
|
|
265
|
+
const fragments = collectTableFragments(options.layout, blockIds, parsed);
|
|
266
|
+
if (fragments.length === 0) return null;
|
|
267
|
+
|
|
268
|
+
const blockId = fragments[0]?.blockId ?? blockIds[0] ?? `table-${parsed.blockIndex}`;
|
|
269
|
+
const tableFrame = projectTableFrame(blockId, parsed, fragments);
|
|
270
|
+
const pageSliceIds = tableFrame.pageSliceIds;
|
|
271
|
+
const layoutObjectIds = tableFrame.layoutObjectIds;
|
|
272
|
+
const cellScoped = parsed.scopeKind === "table-cell";
|
|
273
|
+
return {
|
|
274
|
+
status: "available",
|
|
275
|
+
completeness: cellScoped ? "partial" : "complete",
|
|
276
|
+
reason: cellScoped
|
|
277
|
+
? "l04-table-frame-row-level-evidence"
|
|
278
|
+
: "l04-table-frame-continuation",
|
|
279
|
+
...(pageSliceIds ? { pageSliceIds } : {}),
|
|
280
|
+
...(layoutObjectIds ? { layoutObjectIds } : {}),
|
|
281
|
+
continuationState: continuationFromTableFrame(fragments),
|
|
282
|
+
tableFrame,
|
|
283
|
+
};
|
|
284
|
+
},
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export function collectTopLevelTableBlockIdsFromRenderSnapshot(
|
|
289
|
+
snapshot: ScopeRuntimeRenderSnapshotLike | null | undefined,
|
|
290
|
+
): ReadonlyMap<number, string> {
|
|
291
|
+
const out = new Map<number, string>();
|
|
292
|
+
const blocks = snapshot?.surface?.blocks ?? [];
|
|
293
|
+
blocks.forEach((block, blockIndex) => {
|
|
294
|
+
if (block.kind === "table" && typeof block.blockId === "string") {
|
|
295
|
+
out.set(blockIndex, block.blockId);
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
return out;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function cloneTableFrame(
|
|
302
|
+
tableFrame: ScopeTableFrameEvidence,
|
|
303
|
+
): ScopeTableFrameEvidence {
|
|
304
|
+
return {
|
|
305
|
+
...tableFrame,
|
|
306
|
+
...(tableFrame.pageIds ? { pageIds: freezeList(tableFrame.pageIds) } : {}),
|
|
307
|
+
...(tableFrame.pageSliceIds ? { pageSliceIds: freezeList(tableFrame.pageSliceIds) } : {}),
|
|
308
|
+
...(tableFrame.layoutObjectIds ? { layoutObjectIds: freezeList(tableFrame.layoutObjectIds) } : {}),
|
|
309
|
+
...(tableFrame.rowRangesByPage
|
|
310
|
+
? {
|
|
311
|
+
rowRangesByPage: Object.freeze(
|
|
312
|
+
tableFrame.rowRangesByPage.map((row) => ({
|
|
313
|
+
...row,
|
|
314
|
+
...(row.rowRange ? { rowRange: { ...row.rowRange } } : {}),
|
|
315
|
+
...(row.repeatedHeaderRowIndexes
|
|
316
|
+
? { repeatedHeaderRowIndexes: freezeList(row.repeatedHeaderRowIndexes) }
|
|
317
|
+
: {}),
|
|
318
|
+
...(row.splitRowCarry
|
|
319
|
+
? { splitRowCarry: Object.freeze(row.splitRowCarry.map((item) => ({ ...item }))) }
|
|
320
|
+
: {}),
|
|
321
|
+
...(row.verticalMergeCarry
|
|
322
|
+
? { verticalMergeCarry: Object.freeze(row.verticalMergeCarry.map((item) => ({ ...item }))) }
|
|
323
|
+
: {}),
|
|
324
|
+
})),
|
|
325
|
+
),
|
|
326
|
+
}
|
|
327
|
+
: {}),
|
|
328
|
+
...(tableFrame.repeatedHeaderRowIndexes
|
|
329
|
+
? { repeatedHeaderRowIndexes: freezeList(tableFrame.repeatedHeaderRowIndexes) }
|
|
330
|
+
: {}),
|
|
331
|
+
...(tableFrame.splitRowCarry
|
|
332
|
+
? { splitRowCarry: Object.freeze(tableFrame.splitRowCarry.map((item) => ({ ...item }))) }
|
|
333
|
+
: {}),
|
|
334
|
+
...(tableFrame.verticalMergeCarry
|
|
335
|
+
? { verticalMergeCarry: Object.freeze(tableFrame.verticalMergeCarry.map((item) => ({ ...item }))) }
|
|
336
|
+
: {}),
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
export function deriveScopeLayoutEvidence(
|
|
341
|
+
scopeId: string,
|
|
342
|
+
provider?: ScopeLayoutEvidenceProvider,
|
|
343
|
+
): ScopeLayoutEvidence {
|
|
344
|
+
if (!provider) {
|
|
345
|
+
return {
|
|
346
|
+
status: "unavailable",
|
|
347
|
+
completeness: "unavailable",
|
|
348
|
+
reason: "layout-evidence-provider-unavailable",
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const entry = provider.getScopeLayoutEvidence(scopeId);
|
|
353
|
+
if (!entry) {
|
|
354
|
+
return {
|
|
355
|
+
status: "requires-rehydration",
|
|
356
|
+
completeness: "requires-rehydration",
|
|
357
|
+
reason: "scope-layout-evidence-unavailable",
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const status = entry.status ?? "available";
|
|
362
|
+
return {
|
|
363
|
+
status,
|
|
364
|
+
completeness: entry.completeness ?? (status === "available" ? "complete" : status),
|
|
365
|
+
...(entry.reason ? { reason: entry.reason } : {}),
|
|
366
|
+
...(entry.pageSliceIds ? { pageSliceIds: freezeList(entry.pageSliceIds) } : {}),
|
|
367
|
+
...(entry.layoutObjectIds ? { layoutObjectIds: freezeList(entry.layoutObjectIds) } : {}),
|
|
368
|
+
...(entry.continuationState
|
|
369
|
+
? { continuationState: { ...entry.continuationState } }
|
|
370
|
+
: {}),
|
|
371
|
+
...(entry.divergenceIds ? { divergenceIds: freezeList(entry.divergenceIds) } : {}),
|
|
372
|
+
...(entry.tableFrame ? { tableFrame: cloneTableFrame(entry.tableFrame) } : {}),
|
|
373
|
+
};
|
|
374
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared KI-014 refusal facts for marker-backed multi-paragraph scopes.
|
|
3
|
+
*
|
|
4
|
+
* These blockers are evidence only: they describe why broad replacement is
|
|
5
|
+
* still disabled and which facts must exist before L08 can safely lower it.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export type MultiParagraphReplacementShape = "text" | "fragment" | "unknown";
|
|
9
|
+
|
|
10
|
+
export const MULTI_PARAGRAPH_REPLACEMENT_REFUSAL =
|
|
11
|
+
"compile-refused:scope:multi-paragraph-replace-not-implemented";
|
|
12
|
+
|
|
13
|
+
function shapeBlocker(shape: MultiParagraphReplacementShape): string {
|
|
14
|
+
switch (shape) {
|
|
15
|
+
case "text":
|
|
16
|
+
return "compile-refused:scope:multi-paragraph-text-replace-not-implemented";
|
|
17
|
+
case "fragment":
|
|
18
|
+
return "compile-refused:scope:multi-paragraph-fragment-replace-not-implemented";
|
|
19
|
+
default:
|
|
20
|
+
return "compile-refused:scope:multi-paragraph-replace-shape-not-implemented";
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function multiParagraphReplacementBlockers(
|
|
25
|
+
shape: MultiParagraphReplacementShape = "unknown",
|
|
26
|
+
): readonly string[] {
|
|
27
|
+
return Object.freeze([
|
|
28
|
+
MULTI_PARAGRAPH_REPLACEMENT_REFUSAL,
|
|
29
|
+
shapeBlocker(shape),
|
|
30
|
+
"capability:scope:block-granular-replacement-lowering-required",
|
|
31
|
+
"capability:scope:provenance:marker-backed-required",
|
|
32
|
+
"capability:scope:layout-completeness-required",
|
|
33
|
+
"capability:scope:geometry-completeness-required",
|
|
34
|
+
"capability:scope:continuation-state-required",
|
|
35
|
+
"capability:scope:preservation-verdict-required",
|
|
36
|
+
]);
|
|
37
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Layer 08 — preservation-boundary helper (Slice 4 interim).
|
|
3
3
|
*
|
|
4
4
|
* Computes whether a scope's canonical range crosses any preserve-only
|
|
5
|
-
* boundary that would be destroyed by a
|
|
5
|
+
* boundary that would be destroyed by a replacement operation.
|
|
6
6
|
* Sources consulted:
|
|
7
7
|
*
|
|
8
8
|
* - `document.preservation.opaqueFragments` — `OpaqueFragmentRecord[]`
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
import type { CanonicalDocument } from "../../model/canonical-document.ts";
|
|
30
30
|
import { findOpaqueFragmentsIntersectingRange } from "../../preservation/store.ts";
|
|
31
31
|
|
|
32
|
+
import { findContentControlsIntersectingRange } from "./content-control-evidence.ts";
|
|
32
33
|
import type { ScopePositionMap, ScopePositionRange } from "./position-map.ts";
|
|
33
34
|
|
|
34
35
|
export interface PreservationVerdict {
|
|
@@ -69,6 +70,11 @@ export function computePreservationVerdict(
|
|
|
69
70
|
);
|
|
70
71
|
}
|
|
71
72
|
|
|
73
|
+
const contentControls = findContentControlsIntersectingRange(document, range);
|
|
74
|
+
for (const control of contentControls) {
|
|
75
|
+
reasons.push(`content-control:${control.evidenceId}`);
|
|
76
|
+
}
|
|
77
|
+
|
|
72
78
|
if (positionMap) {
|
|
73
79
|
for (const [scopeId, markerRange] of positionMap.markerScopes) {
|
|
74
80
|
// A marker range strictly inside the target range would be
|
|
@@ -33,6 +33,10 @@ import { enumerateScopes } from "../enumerate-scopes.ts";
|
|
|
33
33
|
import type { EnumeratedScope } from "../enumerate-scopes.ts";
|
|
34
34
|
import { emitScopeActionAudit } from "../audit-bundle.ts";
|
|
35
35
|
import { compileReplacement } from "./compile.ts";
|
|
36
|
+
import {
|
|
37
|
+
MULTI_PARAGRAPH_REPLACEMENT_REFUSAL,
|
|
38
|
+
multiParagraphReplacementBlockers,
|
|
39
|
+
} from "../multi-paragraph-refusal.ts";
|
|
36
40
|
import type {
|
|
37
41
|
ReplacementScope,
|
|
38
42
|
RuntimeOperationPlan,
|
|
@@ -47,6 +51,7 @@ export interface ApplyScopeReplacementSink {
|
|
|
47
51
|
readonly getInteractionGuardSnapshot: () => InteractionGuardSnapshot;
|
|
48
52
|
readonly getCompatibilityReport: () => CompatibilityReport;
|
|
49
53
|
readonly applyScopeReplacement: (plan: RuntimeOperationPlan) => void;
|
|
54
|
+
readonly verifyReadback?: boolean;
|
|
50
55
|
}
|
|
51
56
|
|
|
52
57
|
export interface ApplyScopeReplacementInputs {
|
|
@@ -80,19 +85,28 @@ export interface ApplyScopeReplacementResult {
|
|
|
80
85
|
}
|
|
81
86
|
|
|
82
87
|
function documentHash(doc: CanonicalDocumentEnvelope): string {
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
88
|
+
// Structural content hash. This is intentionally stronger than length-only
|
|
89
|
+
// so same-length stale/no-op replacements do not report as successful edits.
|
|
90
|
+
return JSON.stringify(doc.content);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function compileScopeById(
|
|
94
|
+
document: CanonicalDocumentEnvelope,
|
|
95
|
+
overlay: WorkflowOverlay | undefined,
|
|
96
|
+
scopeId: string,
|
|
97
|
+
): { readonly scope: SemanticScope; readonly entry: EnumeratedScope } | null {
|
|
98
|
+
const paragraphIndexByBlockIndex = buildParagraphIndexMap(document);
|
|
99
|
+
const enumerateInputs = overlay ? { overlay } : {};
|
|
100
|
+
for (const entry of enumerateScopes(document, enumerateInputs)) {
|
|
101
|
+
if (entry.handle.scopeId !== scopeId) continue;
|
|
102
|
+
const compiled = compileScope(entry, {
|
|
103
|
+
document,
|
|
104
|
+
...(overlay ? { overlay } : {}),
|
|
105
|
+
paragraphIndexByBlockIndex,
|
|
106
|
+
});
|
|
107
|
+
if (compiled) return { scope: compiled, entry };
|
|
94
108
|
}
|
|
95
|
-
return
|
|
109
|
+
return null;
|
|
96
110
|
}
|
|
97
111
|
|
|
98
112
|
export function applyScopeReplacement(
|
|
@@ -102,27 +116,18 @@ export function applyScopeReplacement(
|
|
|
102
116
|
const docBefore = inputs.sink.getCanonicalDocument();
|
|
103
117
|
const overlay = inputs.sink.getWorkflowOverlay() ?? undefined;
|
|
104
118
|
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
document: docBefore,
|
|
113
|
-
...(overlay ? { overlay } : {}),
|
|
114
|
-
paragraphIndexByBlockIndex,
|
|
115
|
-
});
|
|
116
|
-
if (compiled) {
|
|
117
|
-
resolvedScope = compiled;
|
|
118
|
-
resolvedEnumerated = entry;
|
|
119
|
-
break;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
119
|
+
const resolved = compileScopeById(
|
|
120
|
+
docBefore,
|
|
121
|
+
overlay,
|
|
122
|
+
proposed.targetHandle.scopeId,
|
|
123
|
+
);
|
|
124
|
+
const resolvedScope = resolved?.scope ?? null;
|
|
125
|
+
const resolvedEnumerated = resolved?.entry ?? null;
|
|
122
126
|
|
|
123
127
|
if (!resolvedScope || !resolvedEnumerated) {
|
|
124
128
|
const validation: ValidationResult = {
|
|
125
129
|
safe: false,
|
|
130
|
+
posture: "hard-refusal",
|
|
126
131
|
blockedReasons: Object.freeze([
|
|
127
132
|
`scope-not-resolvable:${proposed.targetHandle.scopeId}`,
|
|
128
133
|
]),
|
|
@@ -207,15 +212,30 @@ export function applyScopeReplacement(
|
|
|
207
212
|
// `blockers[0]` / `reason` see the actionable sub-reason directly
|
|
208
213
|
// (rather than the bare `compile-refused:scope`). Grammar matches
|
|
209
214
|
// §10 `compile-refused:<kind>:<sub-reason>` (74a45eaf, 2026-04-23).
|
|
210
|
-
const
|
|
215
|
+
const blockers =
|
|
211
216
|
resolvedScope.kind === "scope"
|
|
212
|
-
?
|
|
217
|
+
? multiParagraphReplacementBlockers(
|
|
218
|
+
proposed.proposedContent.kind === "structured" ? "fragment" : "text",
|
|
219
|
+
)
|
|
220
|
+
: paragraphLike &&
|
|
221
|
+
proposed.operation === "replace" &&
|
|
222
|
+
proposed.preserve?.opaqueFragments === true
|
|
223
|
+
? [
|
|
224
|
+
`compile-refused:${resolvedScope.kind}:opaque-preserving-text-target-unavailable`,
|
|
225
|
+
]
|
|
213
226
|
: paragraphLike && proposed.operation !== "replace"
|
|
214
|
-
?
|
|
215
|
-
|
|
227
|
+
? [
|
|
228
|
+
`compile-refused:${resolvedScope.kind}:operation-not-implemented:${proposed.operation}`,
|
|
229
|
+
]
|
|
230
|
+
: [`compile-refused:${resolvedScope.kind}`];
|
|
231
|
+
const blocker =
|
|
232
|
+
resolvedScope.kind === "scope"
|
|
233
|
+
? MULTI_PARAGRAPH_REPLACEMENT_REFUSAL
|
|
234
|
+
: blockers[0] ?? `compile-refused:${resolvedScope.kind}`;
|
|
216
235
|
const refused: ValidationResult = {
|
|
217
236
|
safe: false,
|
|
218
|
-
|
|
237
|
+
posture: "hard-refusal",
|
|
238
|
+
blockedReasons: Object.freeze([...blockers]),
|
|
219
239
|
warnings: verdict.warnings,
|
|
220
240
|
};
|
|
221
241
|
// Coord-08 U5 — `reason` mirrors `blockers[0]` for symmetry. Agents
|
|
@@ -251,6 +271,49 @@ export function applyScopeReplacement(
|
|
|
251
271
|
|
|
252
272
|
const docAfter = inputs.sink.getCanonicalDocument();
|
|
253
273
|
const documentHashAfter = documentHash(docAfter);
|
|
274
|
+
const readback = compileScopeById(
|
|
275
|
+
docAfter,
|
|
276
|
+
inputs.sink.getWorkflowOverlay() ?? undefined,
|
|
277
|
+
proposed.targetHandle.scopeId,
|
|
278
|
+
);
|
|
279
|
+
const proposedText =
|
|
280
|
+
proposed.proposedContent.kind === "text"
|
|
281
|
+
? proposed.proposedContent.text ?? ""
|
|
282
|
+
: null;
|
|
283
|
+
const shouldVerifyReadback =
|
|
284
|
+
inputs.sink.verifyReadback === true &&
|
|
285
|
+
proposed.preserve?.opaqueFragments === true;
|
|
286
|
+
const readbackFailureReason =
|
|
287
|
+
shouldVerifyReadback &&
|
|
288
|
+
documentHashAfter === documentHashBefore &&
|
|
289
|
+
posture === "direct-edit" &&
|
|
290
|
+
!readback
|
|
291
|
+
? `apply-readback-unresolvable:${proposed.targetHandle.scopeId}`
|
|
292
|
+
: shouldVerifyReadback &&
|
|
293
|
+
documentHashAfter === documentHashBefore &&
|
|
294
|
+
posture === "direct-edit" &&
|
|
295
|
+
proposed.operation === "replace" &&
|
|
296
|
+
proposedText !== null &&
|
|
297
|
+
proposedText !== resolvedScope.content.text &&
|
|
298
|
+
readback?.scope.content.text === resolvedScope.content.text
|
|
299
|
+
? `apply-readback-unchanged:${proposed.targetHandle.scopeId}`
|
|
300
|
+
: undefined;
|
|
301
|
+
|
|
302
|
+
if (readbackFailureReason) {
|
|
303
|
+
return {
|
|
304
|
+
applied: false,
|
|
305
|
+
reason: readbackFailureReason,
|
|
306
|
+
validation: {
|
|
307
|
+
safe: false,
|
|
308
|
+
posture: "hard-refusal",
|
|
309
|
+
blockedReasons: Object.freeze([readbackFailureReason]),
|
|
310
|
+
warnings: verdict.warnings,
|
|
311
|
+
},
|
|
312
|
+
plan,
|
|
313
|
+
scope: readback?.scope ?? resolvedScope,
|
|
314
|
+
authoredRevisionIds: Object.freeze([]),
|
|
315
|
+
};
|
|
316
|
+
}
|
|
254
317
|
|
|
255
318
|
const authoredRevisionIds: string[] = [];
|
|
256
319
|
for (const id of Object.keys(docAfter.review.revisions ?? {})) {
|