@pilio/gemini-watermark-remover 1.0.44 → 1.0.46

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 (125) hide show
  1. package/LICENSE +22 -22
  2. package/README.md +426 -426
  3. package/README_zh.md +415 -415
  4. package/bin/gwr.mjs +12 -12
  5. package/dist/models/allenk-fdncnn/onnx-manifest.json +49 -49
  6. package/dist/onnxruntime/LICENSE +21 -0
  7. package/dist/onnxruntime/README.md +16 -0
  8. package/dist/onnxruntime/ThirdPartyNotices.txt +6121 -0
  9. package/dist/onnxruntime/ort-wasm-simd-threaded.asyncify.mjs +112 -112
  10. package/dist/onnxruntime/ort-wasm-simd-threaded.js +59 -59
  11. package/dist/video-app.js +15 -15
  12. package/dist/video-preview.html +599 -571
  13. package/package.json +4 -1
  14. package/skills/gemini-watermark-remover/SKILL.md +32 -32
  15. package/skills/gemini-watermark-remover/agents/openai.yaml +3 -3
  16. package/skills/gemini-watermark-remover/references/inputs-and-outputs.md +9 -9
  17. package/skills/gemini-watermark-remover/references/limitations.md +5 -5
  18. package/skills/gemini-watermark-remover/references/usage.md +23 -23
  19. package/skills/gemini-watermark-remover/scripts/run.mjs +153 -153
  20. package/src/cli/gwrCli.js +17 -17
  21. package/src/cli/gwrRemoveCommand.js +433 -433
  22. package/src/core/adaptiveDetector.js +630 -630
  23. package/src/core/allenkFdncnnDenoise.js +653 -653
  24. package/src/core/allenkFdncnnNcnnModel.js +244 -244
  25. package/src/core/allenkFdncnnOnnxExport.js +346 -346
  26. package/src/core/allenkFdncnnOnnxRuntime.js +138 -138
  27. package/src/core/allenkFdncnnReferenceRuntime.js +169 -169
  28. package/src/core/alphaGradientMask.js +165 -165
  29. package/src/core/alphaMap.js +30 -30
  30. package/src/core/blendModes.js +75 -75
  31. package/src/core/candidateEvaluation.js +299 -299
  32. package/src/core/candidateSelector.js +3736 -3736
  33. package/src/core/canvasBlob.js +26 -26
  34. package/src/core/darkOutlineContourRepair.js +323 -323
  35. package/src/core/embeddedAlphaMaps.js +102 -102
  36. package/src/core/embeddedDarkOutlineAlphaMap.js +6 -6
  37. package/src/core/embeddedOutlineAlphaMap.js +5 -5
  38. package/src/core/geminiSizeCatalog.js +580 -580
  39. package/src/core/imageWatermarkPipeline.js +329 -329
  40. package/src/core/imageWatermarkPipelineRequest.js +39 -39
  41. package/src/core/multiPassRemoval.js +93 -93
  42. package/src/core/outputResidualLocalization.js +351 -351
  43. package/src/core/pipelineAcceptedExecutor.js +364 -364
  44. package/src/core/pipelineAcceptedExecutorRequest.js +46 -46
  45. package/src/core/pipelineAcceptedFinalizationRequest.js +21 -21
  46. package/src/core/pipelineAlphaStageSpecs.js +654 -654
  47. package/src/core/pipelineAlphaTraceContract.js +58 -58
  48. package/src/core/pipelineAlphaTrial.js +306 -306
  49. package/src/core/pipelineCandidatePool.js +125 -125
  50. package/src/core/pipelineCandidateQuality.js +838 -838
  51. package/src/core/pipelineCandidateRunner.js +109 -109
  52. package/src/core/pipelineDecisionPath.js +161 -161
  53. package/src/core/pipelineDetectionCandidate.js +128 -128
  54. package/src/core/pipelineFinalization.js +99 -99
  55. package/src/core/pipelineInitialContext.js +52 -52
  56. package/src/core/pipelineInitialSelection.js +1504 -1498
  57. package/src/core/pipelineLayerContracts.js +163 -163
  58. package/src/core/pipelineMeta.js +427 -427
  59. package/src/core/pipelineMetrics.js +88 -88
  60. package/src/core/pipelinePassState.js +36 -36
  61. package/src/core/pipelineRepairGates.js +154 -154
  62. package/src/core/pipelineRepairStageSpecs.js +525 -525
  63. package/src/core/pipelineRepairTrial.js +156 -156
  64. package/src/core/pipelineResult.js +197 -197
  65. package/src/core/pipelineRuntime.js +710 -710
  66. package/src/core/pipelineRuntimeBootstrap.js +78 -78
  67. package/src/core/pipelineState.js +134 -134
  68. package/src/core/pipelineTimings.js +36 -36
  69. package/src/core/pipelineTrace.js +28 -28
  70. package/src/core/planeAlphaEvidence.js +106 -106
  71. package/src/core/previewAlphaCalibration.js +825 -825
  72. package/src/core/restorationMetrics.js +807 -807
  73. package/src/core/rowAlphaEvidence.js +59 -59
  74. package/src/core/selectionDebug.js +93 -93
  75. package/src/core/watermarkConfig.js +146 -146
  76. package/src/core/watermarkDecisionPolicy.js +163 -163
  77. package/src/core/watermarkDisplay.js +89 -89
  78. package/src/core/watermarkEngine.js +173 -173
  79. package/src/core/watermarkPresence.js +14 -14
  80. package/src/core/watermarkProcessor.js +6332 -6332
  81. package/src/core/watermarkScoring.js +197 -197
  82. package/src/core/workerClient.js +114 -114
  83. package/src/runtime/browser.d.ts +7 -7
  84. package/src/runtime/browser.js +96 -96
  85. package/src/runtime/userscript.d.ts +6 -6
  86. package/src/runtime/userscript.js +28 -28
  87. package/src/sdk/browser.d.ts +13 -13
  88. package/src/sdk/browser.js +29 -29
  89. package/src/sdk/image-data.d.ts +14 -14
  90. package/src/sdk/image-data.js +78 -78
  91. package/src/sdk/index.d.ts +191 -191
  92. package/src/sdk/index.js +8 -8
  93. package/src/sdk/node.d.ts +56 -56
  94. package/src/sdk/node.js +84 -84
  95. package/src/sdk/runtime-browser.d.ts +32 -32
  96. package/src/sdk/runtime-browser.js +1 -1
  97. package/src/sdk/runtime-userscript.d.ts +41 -41
  98. package/src/sdk/runtime-userscript.js +1 -1
  99. package/src/sdk/video.d.ts +111 -111
  100. package/src/sdk/video.js +358 -358
  101. package/src/sdk/videoProgress.js +108 -108
  102. package/src/shared/actionContextCompat.js +54 -54
  103. package/src/shared/debugFileHandoff.js +109 -109
  104. package/src/shared/domAdapter.js +279 -279
  105. package/src/shared/errorUtils.js +62 -62
  106. package/src/shared/imageProcessing.js +162 -162
  107. package/src/shared/imageSessionContext.js +98 -98
  108. package/src/shared/imageSessionStore.js +495 -495
  109. package/src/shared/originalBlob.js +79 -79
  110. package/src/shared/pageImageReplacement.js +2366 -2366
  111. package/src/userscript/actionContext.js +210 -210
  112. package/src/userscript/bridgeShared.js +72 -72
  113. package/src/userscript/clipboardHook.js +351 -351
  114. package/src/userscript/crossOriginFetch.js +84 -84
  115. package/src/userscript/downloadHook.js +1576 -1576
  116. package/src/userscript/historyBindingBootstrap.js +163 -163
  117. package/src/userscript/index.js +346 -346
  118. package/src/userscript/pageProcessBridge.js +283 -283
  119. package/src/userscript/pageProcessorRuntime.js +153 -153
  120. package/src/userscript/processBridge.js +186 -186
  121. package/src/userscript/processingRuntime.js +261 -261
  122. package/src/userscript/runtimeFlags.js +46 -46
  123. package/src/userscript/trustedTypes.js +49 -49
  124. package/src/userscript/urlUtils.js +169 -169
  125. package/src/userscript/userNotice.js +19 -19
@@ -1,427 +1,427 @@
1
- import {
2
- createAcceptedDecisionPath,
3
- createDetectionCandidateFromSelectedTrial,
4
- createRejectedDecisionPath
5
- } from './candidateEvaluation.js';
6
- import { reconcileAlphaTrialWithFinalQualitySignals } from './pipelineAlphaTrial.js';
7
-
8
- function normalizeMetaPosition(position) {
9
- if (!position) return null;
10
-
11
- const { x, y, width, height } = position;
12
- if (![x, y, width, height].every((value) => Number.isFinite(value))) {
13
- return null;
14
- }
15
-
16
- return { x, y, width, height };
17
- }
18
-
19
- function normalizeMetaConfig(config) {
20
- if (!config) return null;
21
-
22
- const { logoSize, marginRight, marginBottom } = config;
23
- if (![logoSize, marginRight, marginBottom].every((value) => Number.isFinite(value))) {
24
- return null;
25
- }
26
-
27
- return {
28
- logoSize,
29
- marginRight,
30
- marginBottom,
31
- ...(typeof config.alphaVariant === 'string' && config.alphaVariant.length > 0
32
- ? { alphaVariant: config.alphaVariant }
33
- : {})
34
- };
35
- }
36
-
37
- const QUALITY_STATUSES = new Set([
38
- 'clean',
39
- 'visible-residual',
40
- 'possible-content-damage',
41
- 'mixed'
42
- ]);
43
-
44
- function normalizeQualityStatus(status) {
45
- return QUALITY_STATUSES.has(status) ? status : null;
46
- }
47
-
48
- function normalizeSelectionConfidence(value) {
49
- return Number.isFinite(value) ? Math.max(0, Math.min(1, value)) : null;
50
- }
51
-
52
- function normalizeSelectedCandidate(candidate) {
53
- if (!candidate || typeof candidate !== 'object') return null;
54
- return {
55
- id: candidate.id ?? null,
56
- family: candidate.family ?? null,
57
- rank: Number.isFinite(candidate.rank) ? candidate.rank : null,
58
- source: candidate.source ?? null,
59
- config: normalizeMetaConfig(candidate.config),
60
- position: normalizeMetaPosition(candidate.position),
61
- alphaProfile: candidate.alphaProfile ?? null,
62
- polarity: candidate.polarity ?? null
63
- };
64
- }
65
-
66
- function normalizeCandidateSummary(summary) {
67
- if (!summary || typeof summary !== 'object') return null;
68
- return {
69
- id: summary.id ?? null,
70
- family: summary.family ?? null,
71
- rank: Number.isFinite(summary.rank) ? summary.rank : null,
72
- valid: summary.valid === true,
73
- finalScore: Number.isFinite(summary.finalScore) ? summary.finalScore : null,
74
- qualityStatus: normalizeQualityStatus(summary.qualityStatus),
75
- qualitySignals: summary.qualitySignals ?? null,
76
- error: typeof summary.error === 'string' ? summary.error : null
77
- };
78
- }
79
-
80
- function reconcileDecisionPathQuality(decisionPath, qualitySignals) {
81
- if (!decisionPath?.alphaTrial) {
82
- return decisionPath;
83
- }
84
- const alphaTrial = reconcileAlphaTrialWithFinalQualitySignals(
85
- decisionPath.alphaTrial,
86
- qualitySignals
87
- );
88
- if (alphaTrial === decisionPath.alphaTrial) return decisionPath;
89
-
90
- return {
91
- ...decisionPath,
92
- alphaTrial
93
- };
94
- }
95
-
96
- export function createWatermarkMeta({
97
- position = null,
98
- config = null,
99
- adaptiveConfidence = null,
100
- originalSpatialScore = null,
101
- originalGradientScore = null,
102
- processedSpatialScore = null,
103
- processedGradientScore = null,
104
- suppressionGain = null,
105
- residualVisibility = null,
106
- templateWarp = null,
107
- alphaGain = 1,
108
- passCount = 0,
109
- attemptedPassCount = 0,
110
- passStopReason = null,
111
- passes = null,
112
- source = 'standard',
113
- decisionTier = null,
114
- applied = true,
115
- skipReason = null,
116
- subpixelShift = null,
117
- selectionDebug = null,
118
- alphaAdjustmentStages = null,
119
- alphaMapSource = null,
120
- decisionPath = null,
121
- bestEffort = false,
122
- retryRecommended = null,
123
- qualityStatus = null,
124
- selectionConfidence = null,
125
- selectedCandidate = null,
126
- qualitySignals = null,
127
- candidateSummaries = null
128
- } = {}) {
129
- const normalizedPosition = normalizeMetaPosition(position);
130
-
131
- return {
132
- applied,
133
- skipReason: applied ? null : skipReason,
134
- size: normalizedPosition ? normalizedPosition.width : null,
135
- position: normalizedPosition,
136
- config: normalizeMetaConfig(config),
137
- detection: {
138
- adaptiveConfidence,
139
- originalSpatialScore,
140
- originalGradientScore,
141
- processedSpatialScore,
142
- processedGradientScore,
143
- suppressionGain,
144
- residualVisibility
145
- },
146
- templateWarp: templateWarp ?? null,
147
- alphaGain,
148
- passCount,
149
- attemptedPassCount,
150
- passStopReason,
151
- passes: Array.isArray(passes) ? passes : null,
152
- // decisionTier is the normalized contract used by UI and attribution.
153
- // source remains as a verbose execution trace for debugging/tests.
154
- source,
155
- decisionTier,
156
- subpixelShift: subpixelShift ?? null,
157
- selectionDebug,
158
- alphaAdjustmentStages: Array.isArray(alphaAdjustmentStages) ? alphaAdjustmentStages : null,
159
- alphaMapSource: alphaMapSource ?? null,
160
- decisionPath: decisionPath ?? null,
161
- bestEffort: bestEffort === true,
162
- retryRecommended: typeof retryRecommended === 'boolean' ? retryRecommended : null,
163
- qualityStatus: normalizeQualityStatus(qualityStatus),
164
- selectionConfidence: normalizeSelectionConfidence(selectionConfidence),
165
- selectedCandidate: normalizeSelectedCandidate(selectedCandidate),
166
- qualitySignals: qualitySignals ?? null,
167
- candidateSummaries: Array.isArray(candidateSummaries)
168
- ? candidateSummaries.map(normalizeCandidateSummary).filter(Boolean)
169
- : null
170
- };
171
- }
172
-
173
- export function createAcceptedWatermarkMeta({
174
- selectedTrial = null,
175
- selectionSource = null,
176
- position = null,
177
- config = null,
178
- adaptiveConfidence = null,
179
- originalSpatialScore = null,
180
- originalGradientScore = null,
181
- processedSpatialScore = null,
182
- processedGradientScore = null,
183
- suppressionGain = null,
184
- residualVisibility = null,
185
- templateWarp = null,
186
- alphaGain = 1,
187
- passCount = 0,
188
- attemptedPassCount = 0,
189
- passStopReason = null,
190
- passes = null,
191
- source = 'standard',
192
- decisionTier = null,
193
- subpixelShift = null,
194
- selectionDebug = null,
195
- alphaAdjustmentStages = null,
196
- alphaTrialEvents = null,
197
- alphaMapSource = null,
198
- bestEffort = false,
199
- retryRecommended = null,
200
- qualityStatus = null,
201
- selectionConfidence = null,
202
- selectedCandidate = null,
203
- qualitySignals = null,
204
- candidateSummaries = null
205
- } = {}) {
206
- const initialDecisionPath = createAcceptedDecisionPath({
207
- selectedTrial,
208
- selectionSource,
209
- source,
210
- decisionTier,
211
- config,
212
- position,
213
- adaptiveConfidence,
214
- alphaGain,
215
- alphaMapSource,
216
- templateWarp,
217
- alphaAdjustmentStages,
218
- alphaTrialEvents,
219
- originalSpatialScore,
220
- originalGradientScore,
221
- processedSpatialScore,
222
- processedGradientScore,
223
- suppressionGain,
224
- residualVisibility
225
- });
226
- const decisionPath = reconcileDecisionPathQuality(
227
- initialDecisionPath,
228
- qualitySignals
229
- );
230
-
231
- return createWatermarkMeta({
232
- position,
233
- config,
234
- adaptiveConfidence,
235
- originalSpatialScore,
236
- originalGradientScore,
237
- processedSpatialScore,
238
- processedGradientScore,
239
- suppressionGain,
240
- residualVisibility,
241
- templateWarp,
242
- alphaGain,
243
- passCount,
244
- attemptedPassCount,
245
- passStopReason,
246
- passes,
247
- source,
248
- decisionTier,
249
- applied: true,
250
- subpixelShift,
251
- alphaAdjustmentStages,
252
- alphaMapSource,
253
- selectionDebug,
254
- decisionPath,
255
- bestEffort,
256
- retryRecommended,
257
- qualityStatus,
258
- selectionConfidence,
259
- selectedCandidate,
260
- qualitySignals,
261
- candidateSummaries
262
- });
263
- }
264
-
265
- export function attachTopNSelectionMeta(meta, {
266
- qualityStatus,
267
- selectionConfidence,
268
- selectedCandidate,
269
- qualitySignals,
270
- candidateSummaries
271
- } = {}) {
272
- const normalized = createWatermarkMeta({
273
- applied: true,
274
- bestEffort: true,
275
- retryRecommended: false,
276
- qualityStatus,
277
- selectionConfidence,
278
- selectedCandidate,
279
- qualitySignals,
280
- candidateSummaries
281
- });
282
- const decisionPath = reconcileDecisionPathQuality(
283
- meta?.decisionPath,
284
- normalized.qualitySignals
285
- );
286
-
287
- return {
288
- ...meta,
289
- applied: true,
290
- skipReason: null,
291
- bestEffort: normalized.bestEffort,
292
- retryRecommended: normalized.retryRecommended,
293
- qualityStatus: normalized.qualityStatus,
294
- selectionConfidence: normalized.selectionConfidence,
295
- selectedCandidate: normalized.selectedCandidate,
296
- qualitySignals: normalized.qualitySignals,
297
- candidateSummaries: normalized.candidateSummaries,
298
- decisionPath
299
- };
300
- }
301
-
302
- export function createRejectedWatermarkMeta({
303
- reason = 'no-watermark-detected',
304
- source = 'skipped',
305
- decisionTier = 'insufficient',
306
- adaptiveConfidence = null,
307
- originalSpatialScore = null,
308
- originalGradientScore = null,
309
- selectionDebug = null
310
- } = {}) {
311
- return createWatermarkMeta({
312
- adaptiveConfidence,
313
- originalSpatialScore,
314
- originalGradientScore,
315
- processedSpatialScore: originalSpatialScore,
316
- processedGradientScore: originalGradientScore,
317
- suppressionGain: 0,
318
- alphaGain: 1,
319
- source,
320
- decisionTier,
321
- applied: false,
322
- skipReason: reason,
323
- selectionDebug,
324
- decisionPath: createRejectedDecisionPath({
325
- reason,
326
- source,
327
- decisionTier,
328
- originalSpatialScore,
329
- originalGradientScore,
330
- adaptiveConfidence
331
- })
332
- });
333
- }
334
-
335
- export function createFailClosedWatermarkMeta({
336
- selectedTrial = null,
337
- reason = 'visible-residual-unsafe-damage',
338
- evidenceClass = 'unsafe-visible-residual',
339
- position = null,
340
- config = null,
341
- adaptiveConfidence = null,
342
- originalSpatialScore = null,
343
- originalGradientScore = null,
344
- processedSpatialScore = null,
345
- processedGradientScore = null,
346
- suppressionGain = null,
347
- residualVisibility = null,
348
- templateWarp = null,
349
- alphaGain = 1,
350
- passCount = 0,
351
- attemptedPassCount = 0,
352
- passStopReason = null,
353
- passes = null,
354
- source = 'standard',
355
- decisionTier = null,
356
- subpixelShift = null,
357
- selectionDebug = null,
358
- alphaAdjustmentStages = null,
359
- alphaMapSource = null
360
- } = {}) {
361
- const resolvedPosition = position ?? selectedTrial?.position ?? null;
362
- const resolvedConfig = config ?? selectedTrial?.config ?? null;
363
- const detectionCandidate = createDetectionCandidateFromSelectedTrial({
364
- selectedTrial,
365
- source,
366
- config: resolvedConfig,
367
- position: resolvedPosition,
368
- adaptiveConfidence,
369
- decisionTier
370
- });
371
- const decisionPath = {
372
- version: 1,
373
- decision: 'reject',
374
- detectionSource: detectionCandidate.source,
375
- alphaSource: null,
376
- repairSource: null,
377
- evaluationDecision: 'rejected',
378
- blockedGate: reason,
379
- riskFlags: [],
380
- detectionCandidate,
381
- alphaTrial: null,
382
- repairTrial: null,
383
- evaluation: {
384
- pathId: `${detectionCandidate.id}->reject`,
385
- detectionId: detectionCandidate.id,
386
- alphaTrialId: null,
387
- repairTrialId: null,
388
- eligible: false,
389
- decision: 'reject',
390
- blockedGate: reason,
391
- riskFlags: [],
392
- evidenceClass,
393
- explanation: reason
394
- }
395
- };
396
-
397
- return createWatermarkMeta({
398
- position: resolvedPosition,
399
- config: resolvedConfig,
400
- adaptiveConfidence,
401
- originalSpatialScore,
402
- originalGradientScore,
403
- processedSpatialScore,
404
- processedGradientScore,
405
- suppressionGain,
406
- residualVisibility,
407
- templateWarp,
408
- alphaGain,
409
- passCount,
410
- attemptedPassCount,
411
- passStopReason,
412
- passes,
413
- source,
414
- decisionTier,
415
- applied: false,
416
- skipReason: reason,
417
- subpixelShift,
418
- alphaAdjustmentStages,
419
- alphaMapSource,
420
- selectionDebug,
421
- decisionPath
422
- });
423
- }
424
-
425
- export function createUnsafeVisibleResidualWatermarkMeta(options = {}) {
426
- return createFailClosedWatermarkMeta(options);
427
- }
1
+ import {
2
+ createAcceptedDecisionPath,
3
+ createDetectionCandidateFromSelectedTrial,
4
+ createRejectedDecisionPath
5
+ } from './candidateEvaluation.js';
6
+ import { reconcileAlphaTrialWithFinalQualitySignals } from './pipelineAlphaTrial.js';
7
+
8
+ function normalizeMetaPosition(position) {
9
+ if (!position) return null;
10
+
11
+ const { x, y, width, height } = position;
12
+ if (![x, y, width, height].every((value) => Number.isFinite(value))) {
13
+ return null;
14
+ }
15
+
16
+ return { x, y, width, height };
17
+ }
18
+
19
+ function normalizeMetaConfig(config) {
20
+ if (!config) return null;
21
+
22
+ const { logoSize, marginRight, marginBottom } = config;
23
+ if (![logoSize, marginRight, marginBottom].every((value) => Number.isFinite(value))) {
24
+ return null;
25
+ }
26
+
27
+ return {
28
+ logoSize,
29
+ marginRight,
30
+ marginBottom,
31
+ ...(typeof config.alphaVariant === 'string' && config.alphaVariant.length > 0
32
+ ? { alphaVariant: config.alphaVariant }
33
+ : {})
34
+ };
35
+ }
36
+
37
+ const QUALITY_STATUSES = new Set([
38
+ 'clean',
39
+ 'visible-residual',
40
+ 'possible-content-damage',
41
+ 'mixed'
42
+ ]);
43
+
44
+ function normalizeQualityStatus(status) {
45
+ return QUALITY_STATUSES.has(status) ? status : null;
46
+ }
47
+
48
+ function normalizeSelectionConfidence(value) {
49
+ return Number.isFinite(value) ? Math.max(0, Math.min(1, value)) : null;
50
+ }
51
+
52
+ function normalizeSelectedCandidate(candidate) {
53
+ if (!candidate || typeof candidate !== 'object') return null;
54
+ return {
55
+ id: candidate.id ?? null,
56
+ family: candidate.family ?? null,
57
+ rank: Number.isFinite(candidate.rank) ? candidate.rank : null,
58
+ source: candidate.source ?? null,
59
+ config: normalizeMetaConfig(candidate.config),
60
+ position: normalizeMetaPosition(candidate.position),
61
+ alphaProfile: candidate.alphaProfile ?? null,
62
+ polarity: candidate.polarity ?? null
63
+ };
64
+ }
65
+
66
+ function normalizeCandidateSummary(summary) {
67
+ if (!summary || typeof summary !== 'object') return null;
68
+ return {
69
+ id: summary.id ?? null,
70
+ family: summary.family ?? null,
71
+ rank: Number.isFinite(summary.rank) ? summary.rank : null,
72
+ valid: summary.valid === true,
73
+ finalScore: Number.isFinite(summary.finalScore) ? summary.finalScore : null,
74
+ qualityStatus: normalizeQualityStatus(summary.qualityStatus),
75
+ qualitySignals: summary.qualitySignals ?? null,
76
+ error: typeof summary.error === 'string' ? summary.error : null
77
+ };
78
+ }
79
+
80
+ function reconcileDecisionPathQuality(decisionPath, qualitySignals) {
81
+ if (!decisionPath?.alphaTrial) {
82
+ return decisionPath;
83
+ }
84
+ const alphaTrial = reconcileAlphaTrialWithFinalQualitySignals(
85
+ decisionPath.alphaTrial,
86
+ qualitySignals
87
+ );
88
+ if (alphaTrial === decisionPath.alphaTrial) return decisionPath;
89
+
90
+ return {
91
+ ...decisionPath,
92
+ alphaTrial
93
+ };
94
+ }
95
+
96
+ export function createWatermarkMeta({
97
+ position = null,
98
+ config = null,
99
+ adaptiveConfidence = null,
100
+ originalSpatialScore = null,
101
+ originalGradientScore = null,
102
+ processedSpatialScore = null,
103
+ processedGradientScore = null,
104
+ suppressionGain = null,
105
+ residualVisibility = null,
106
+ templateWarp = null,
107
+ alphaGain = 1,
108
+ passCount = 0,
109
+ attemptedPassCount = 0,
110
+ passStopReason = null,
111
+ passes = null,
112
+ source = 'standard',
113
+ decisionTier = null,
114
+ applied = true,
115
+ skipReason = null,
116
+ subpixelShift = null,
117
+ selectionDebug = null,
118
+ alphaAdjustmentStages = null,
119
+ alphaMapSource = null,
120
+ decisionPath = null,
121
+ bestEffort = false,
122
+ retryRecommended = null,
123
+ qualityStatus = null,
124
+ selectionConfidence = null,
125
+ selectedCandidate = null,
126
+ qualitySignals = null,
127
+ candidateSummaries = null
128
+ } = {}) {
129
+ const normalizedPosition = normalizeMetaPosition(position);
130
+
131
+ return {
132
+ applied,
133
+ skipReason: applied ? null : skipReason,
134
+ size: normalizedPosition ? normalizedPosition.width : null,
135
+ position: normalizedPosition,
136
+ config: normalizeMetaConfig(config),
137
+ detection: {
138
+ adaptiveConfidence,
139
+ originalSpatialScore,
140
+ originalGradientScore,
141
+ processedSpatialScore,
142
+ processedGradientScore,
143
+ suppressionGain,
144
+ residualVisibility
145
+ },
146
+ templateWarp: templateWarp ?? null,
147
+ alphaGain,
148
+ passCount,
149
+ attemptedPassCount,
150
+ passStopReason,
151
+ passes: Array.isArray(passes) ? passes : null,
152
+ // decisionTier is the normalized contract used by UI and attribution.
153
+ // source remains as a verbose execution trace for debugging/tests.
154
+ source,
155
+ decisionTier,
156
+ subpixelShift: subpixelShift ?? null,
157
+ selectionDebug,
158
+ alphaAdjustmentStages: Array.isArray(alphaAdjustmentStages) ? alphaAdjustmentStages : null,
159
+ alphaMapSource: alphaMapSource ?? null,
160
+ decisionPath: decisionPath ?? null,
161
+ bestEffort: bestEffort === true,
162
+ retryRecommended: typeof retryRecommended === 'boolean' ? retryRecommended : null,
163
+ qualityStatus: normalizeQualityStatus(qualityStatus),
164
+ selectionConfidence: normalizeSelectionConfidence(selectionConfidence),
165
+ selectedCandidate: normalizeSelectedCandidate(selectedCandidate),
166
+ qualitySignals: qualitySignals ?? null,
167
+ candidateSummaries: Array.isArray(candidateSummaries)
168
+ ? candidateSummaries.map(normalizeCandidateSummary).filter(Boolean)
169
+ : null
170
+ };
171
+ }
172
+
173
+ export function createAcceptedWatermarkMeta({
174
+ selectedTrial = null,
175
+ selectionSource = null,
176
+ position = null,
177
+ config = null,
178
+ adaptiveConfidence = null,
179
+ originalSpatialScore = null,
180
+ originalGradientScore = null,
181
+ processedSpatialScore = null,
182
+ processedGradientScore = null,
183
+ suppressionGain = null,
184
+ residualVisibility = null,
185
+ templateWarp = null,
186
+ alphaGain = 1,
187
+ passCount = 0,
188
+ attemptedPassCount = 0,
189
+ passStopReason = null,
190
+ passes = null,
191
+ source = 'standard',
192
+ decisionTier = null,
193
+ subpixelShift = null,
194
+ selectionDebug = null,
195
+ alphaAdjustmentStages = null,
196
+ alphaTrialEvents = null,
197
+ alphaMapSource = null,
198
+ bestEffort = false,
199
+ retryRecommended = null,
200
+ qualityStatus = null,
201
+ selectionConfidence = null,
202
+ selectedCandidate = null,
203
+ qualitySignals = null,
204
+ candidateSummaries = null
205
+ } = {}) {
206
+ const initialDecisionPath = createAcceptedDecisionPath({
207
+ selectedTrial,
208
+ selectionSource,
209
+ source,
210
+ decisionTier,
211
+ config,
212
+ position,
213
+ adaptiveConfidence,
214
+ alphaGain,
215
+ alphaMapSource,
216
+ templateWarp,
217
+ alphaAdjustmentStages,
218
+ alphaTrialEvents,
219
+ originalSpatialScore,
220
+ originalGradientScore,
221
+ processedSpatialScore,
222
+ processedGradientScore,
223
+ suppressionGain,
224
+ residualVisibility
225
+ });
226
+ const decisionPath = reconcileDecisionPathQuality(
227
+ initialDecisionPath,
228
+ qualitySignals
229
+ );
230
+
231
+ return createWatermarkMeta({
232
+ position,
233
+ config,
234
+ adaptiveConfidence,
235
+ originalSpatialScore,
236
+ originalGradientScore,
237
+ processedSpatialScore,
238
+ processedGradientScore,
239
+ suppressionGain,
240
+ residualVisibility,
241
+ templateWarp,
242
+ alphaGain,
243
+ passCount,
244
+ attemptedPassCount,
245
+ passStopReason,
246
+ passes,
247
+ source,
248
+ decisionTier,
249
+ applied: true,
250
+ subpixelShift,
251
+ alphaAdjustmentStages,
252
+ alphaMapSource,
253
+ selectionDebug,
254
+ decisionPath,
255
+ bestEffort,
256
+ retryRecommended,
257
+ qualityStatus,
258
+ selectionConfidence,
259
+ selectedCandidate,
260
+ qualitySignals,
261
+ candidateSummaries
262
+ });
263
+ }
264
+
265
+ export function attachTopNSelectionMeta(meta, {
266
+ qualityStatus,
267
+ selectionConfidence,
268
+ selectedCandidate,
269
+ qualitySignals,
270
+ candidateSummaries
271
+ } = {}) {
272
+ const normalized = createWatermarkMeta({
273
+ applied: true,
274
+ bestEffort: true,
275
+ retryRecommended: false,
276
+ qualityStatus,
277
+ selectionConfidence,
278
+ selectedCandidate,
279
+ qualitySignals,
280
+ candidateSummaries
281
+ });
282
+ const decisionPath = reconcileDecisionPathQuality(
283
+ meta?.decisionPath,
284
+ normalized.qualitySignals
285
+ );
286
+
287
+ return {
288
+ ...meta,
289
+ applied: true,
290
+ skipReason: null,
291
+ bestEffort: normalized.bestEffort,
292
+ retryRecommended: normalized.retryRecommended,
293
+ qualityStatus: normalized.qualityStatus,
294
+ selectionConfidence: normalized.selectionConfidence,
295
+ selectedCandidate: normalized.selectedCandidate,
296
+ qualitySignals: normalized.qualitySignals,
297
+ candidateSummaries: normalized.candidateSummaries,
298
+ decisionPath
299
+ };
300
+ }
301
+
302
+ export function createRejectedWatermarkMeta({
303
+ reason = 'no-watermark-detected',
304
+ source = 'skipped',
305
+ decisionTier = 'insufficient',
306
+ adaptiveConfidence = null,
307
+ originalSpatialScore = null,
308
+ originalGradientScore = null,
309
+ selectionDebug = null
310
+ } = {}) {
311
+ return createWatermarkMeta({
312
+ adaptiveConfidence,
313
+ originalSpatialScore,
314
+ originalGradientScore,
315
+ processedSpatialScore: originalSpatialScore,
316
+ processedGradientScore: originalGradientScore,
317
+ suppressionGain: 0,
318
+ alphaGain: 1,
319
+ source,
320
+ decisionTier,
321
+ applied: false,
322
+ skipReason: reason,
323
+ selectionDebug,
324
+ decisionPath: createRejectedDecisionPath({
325
+ reason,
326
+ source,
327
+ decisionTier,
328
+ originalSpatialScore,
329
+ originalGradientScore,
330
+ adaptiveConfidence
331
+ })
332
+ });
333
+ }
334
+
335
+ export function createFailClosedWatermarkMeta({
336
+ selectedTrial = null,
337
+ reason = 'visible-residual-unsafe-damage',
338
+ evidenceClass = 'unsafe-visible-residual',
339
+ position = null,
340
+ config = null,
341
+ adaptiveConfidence = null,
342
+ originalSpatialScore = null,
343
+ originalGradientScore = null,
344
+ processedSpatialScore = null,
345
+ processedGradientScore = null,
346
+ suppressionGain = null,
347
+ residualVisibility = null,
348
+ templateWarp = null,
349
+ alphaGain = 1,
350
+ passCount = 0,
351
+ attemptedPassCount = 0,
352
+ passStopReason = null,
353
+ passes = null,
354
+ source = 'standard',
355
+ decisionTier = null,
356
+ subpixelShift = null,
357
+ selectionDebug = null,
358
+ alphaAdjustmentStages = null,
359
+ alphaMapSource = null
360
+ } = {}) {
361
+ const resolvedPosition = position ?? selectedTrial?.position ?? null;
362
+ const resolvedConfig = config ?? selectedTrial?.config ?? null;
363
+ const detectionCandidate = createDetectionCandidateFromSelectedTrial({
364
+ selectedTrial,
365
+ source,
366
+ config: resolvedConfig,
367
+ position: resolvedPosition,
368
+ adaptiveConfidence,
369
+ decisionTier
370
+ });
371
+ const decisionPath = {
372
+ version: 1,
373
+ decision: 'reject',
374
+ detectionSource: detectionCandidate.source,
375
+ alphaSource: null,
376
+ repairSource: null,
377
+ evaluationDecision: 'rejected',
378
+ blockedGate: reason,
379
+ riskFlags: [],
380
+ detectionCandidate,
381
+ alphaTrial: null,
382
+ repairTrial: null,
383
+ evaluation: {
384
+ pathId: `${detectionCandidate.id}->reject`,
385
+ detectionId: detectionCandidate.id,
386
+ alphaTrialId: null,
387
+ repairTrialId: null,
388
+ eligible: false,
389
+ decision: 'reject',
390
+ blockedGate: reason,
391
+ riskFlags: [],
392
+ evidenceClass,
393
+ explanation: reason
394
+ }
395
+ };
396
+
397
+ return createWatermarkMeta({
398
+ position: resolvedPosition,
399
+ config: resolvedConfig,
400
+ adaptiveConfidence,
401
+ originalSpatialScore,
402
+ originalGradientScore,
403
+ processedSpatialScore,
404
+ processedGradientScore,
405
+ suppressionGain,
406
+ residualVisibility,
407
+ templateWarp,
408
+ alphaGain,
409
+ passCount,
410
+ attemptedPassCount,
411
+ passStopReason,
412
+ passes,
413
+ source,
414
+ decisionTier,
415
+ applied: false,
416
+ skipReason: reason,
417
+ subpixelShift,
418
+ alphaAdjustmentStages,
419
+ alphaMapSource,
420
+ selectionDebug,
421
+ decisionPath
422
+ });
423
+ }
424
+
425
+ export function createUnsafeVisibleResidualWatermarkMeta(options = {}) {
426
+ return createFailClosedWatermarkMeta(options);
427
+ }