harnery 0.38.0 → 0.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +32 -8
- package/dist/commands/artifacts.d.ts.map +1 -1
- package/dist/commands/artifacts.js +97 -6
- package/dist/commands/backup.d.ts +16 -0
- package/dist/commands/backup.d.ts.map +1 -1
- package/dist/commands/backup.js +239 -33
- package/dist/commands/browse.d.ts +8 -0
- package/dist/commands/browse.d.ts.map +1 -1
- package/dist/commands/browse.js +466 -231
- package/dist/commands/diagnostics.d.ts.map +1 -1
- package/dist/commands/diagnostics.js +6 -3
- package/dist/commands/ledger-v3.d.ts.map +1 -1
- package/dist/commands/ledger-v3.js +20 -4
- package/dist/commands/qa-run.d.ts.map +1 -1
- package/dist/commands/qa-run.js +26 -3
- package/dist/commands/qa-status.d.ts.map +1 -1
- package/dist/commands/qa-status.js +7 -2
- package/dist/commands/resources.d.ts.map +1 -1
- package/dist/commands/resources.js +23 -3
- package/dist/commands/review-pack.d.ts.map +1 -1
- package/dist/commands/review-pack.js +98 -23
- package/dist/commands/tunnel.d.ts.map +1 -1
- package/dist/commands/tunnel.js +2 -2
- package/dist/core/adapters/profiles.d.ts +1 -1
- package/dist/core/agents/canonical-emit.d.ts +3 -2
- package/dist/core/agents/canonical-emit.d.ts.map +1 -1
- package/dist/core/agents/canonical-emit.js +2 -0
- package/dist/core/agents/cli.js +13 -6
- package/dist/core/agents/live-authority-v3.d.ts.map +1 -1
- package/dist/core/agents/live-authority-v3.js +5 -3
- package/dist/core/agents/live-lifecycle-v3.d.ts +2 -2
- package/dist/core/agents/live-lifecycle-v3.d.ts.map +1 -1
- package/dist/core/agents/render/prompt-context.d.ts +7 -5
- package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
- package/dist/core/agents/render/prompt-context.js +63 -8
- package/dist/core/agents/session-finalizer-v3.d.ts +2 -2
- package/dist/core/agents/session-finalizer-v3.d.ts.map +1 -1
- package/dist/core/agents/state/live-coordination-view.d.ts +3 -3
- package/dist/core/agents/state/live-coordination-view.d.ts.map +1 -1
- package/dist/core/agents/state/live-coordination-view.js +2 -1
- package/dist/core/agents/state/names.d.ts +35 -2
- package/dist/core/agents/state/names.d.ts.map +1 -1
- package/dist/core/agents/state/names.js +80 -5
- package/dist/core/agents/state/stale-sweep.d.ts +2 -1
- package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
- package/dist/core/agents/state/stale-sweep.js +23 -6
- package/dist/core/agents/turn-elapsed.d.ts +51 -0
- package/dist/core/agents/turn-elapsed.d.ts.map +1 -0
- package/dist/core/agents/turn-elapsed.js +91 -0
- package/dist/core/artifacts/activity.d.ts +14 -0
- package/dist/core/artifacts/activity.d.ts.map +1 -0
- package/dist/core/artifacts/activity.js +52 -0
- package/dist/core/artifacts/delivery-card.d.ts +39 -0
- package/dist/core/artifacts/delivery-card.d.ts.map +1 -0
- package/dist/core/artifacts/delivery-card.js +282 -0
- package/dist/core/artifacts/index.d.ts +42 -7
- package/dist/core/artifacts/index.d.ts.map +1 -1
- package/dist/core/artifacts/index.js +288 -76
- package/dist/core/artifacts/mutation-lock.d.ts +2 -0
- package/dist/core/artifacts/mutation-lock.d.ts.map +1 -0
- package/dist/core/artifacts/mutation-lock.js +91 -0
- package/dist/core/backup/host-snapshot.d.ts +18 -0
- package/dist/core/backup/host-snapshot.d.ts.map +1 -0
- package/dist/core/backup/host-snapshot.js +64 -0
- package/dist/core/config.d.ts +20 -5
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +44 -10
- package/dist/core/diagnostics/advice.d.ts +38 -3
- package/dist/core/diagnostics/advice.d.ts.map +1 -1
- package/dist/core/diagnostics/advice.js +103 -99
- package/dist/core/diagnostics/bundle.d.ts.map +1 -1
- package/dist/core/diagnostics/bundle.js +111 -47
- package/dist/core/diagnostics/comparison.d.ts.map +1 -1
- package/dist/core/diagnostics/comparison.js +3 -2
- package/dist/core/diagnostics/contract.d.ts +15 -31
- package/dist/core/diagnostics/contract.d.ts.map +1 -1
- package/dist/core/diagnostics/contract.js +2 -5
- package/dist/core/diagnostics/index.d.ts +2 -0
- package/dist/core/diagnostics/index.d.ts.map +1 -1
- package/dist/core/diagnostics/index.js +2 -0
- package/dist/core/diagnostics/pressure-contract.d.ts +260 -0
- package/dist/core/diagnostics/pressure-contract.d.ts.map +1 -0
- package/dist/core/diagnostics/pressure-contract.js +70 -0
- package/dist/core/diagnostics/pressure.d.ts +25 -0
- package/dist/core/diagnostics/pressure.d.ts.map +1 -0
- package/dist/core/diagnostics/pressure.js +1138 -0
- package/dist/core/diagnostics/replay.d.ts.map +1 -1
- package/dist/core/diagnostics/replay.js +90 -10
- package/dist/core/events/v3/adapter-id.d.ts +6 -0
- package/dist/core/events/v3/adapter-id.d.ts.map +1 -0
- package/dist/core/events/v3/adapter-id.js +7 -0
- package/dist/core/events/v3/archive-retention.d.ts.map +1 -1
- package/dist/core/events/v3/archive-retention.js +3 -2
- package/dist/core/events/v3/bootstrap.d.ts +41 -4
- package/dist/core/events/v3/bootstrap.d.ts.map +1 -1
- package/dist/core/events/v3/bootstrap.js +120 -17
- package/dist/core/events/v3/capabilities.d.ts +16 -9
- package/dist/core/events/v3/capabilities.d.ts.map +1 -1
- package/dist/core/events/v3/capabilities.js +37 -2
- package/dist/core/events/v3/capability-drift.d.ts +2 -2
- package/dist/core/events/v3/capability-drift.d.ts.map +1 -1
- package/dist/core/events/v3/control-witness.d.ts +48 -5
- package/dist/core/events/v3/control-witness.d.ts.map +1 -1
- package/dist/core/events/v3/control-witness.js +91 -36
- package/dist/core/events/v3/control.d.ts.map +1 -1
- package/dist/core/events/v3/control.js +22 -2
- package/dist/core/events/v3/index.d.ts +2 -1
- package/dist/core/events/v3/index.d.ts.map +1 -1
- package/dist/core/events/v3/index.js +2 -1
- package/dist/core/events/v3/live-feed.d.ts +7 -1
- package/dist/core/events/v3/live-feed.d.ts.map +1 -1
- package/dist/core/events/v3/live-feed.js +21 -3
- package/dist/core/events/v3/live-routing.d.ts.map +1 -1
- package/dist/core/events/v3/live-routing.js +10 -2
- package/dist/core/events/v3/producers/command-recorder.d.ts +2 -2
- package/dist/core/events/v3/producers/command-recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/command-recorder.js +2 -1
- package/dist/core/events/v3/producers/coordination-recorder.d.ts +2 -2
- package/dist/core/events/v3/producers/coordination-recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/coordination-recorder.js +2 -1
- package/dist/core/events/v3/producers/hook-base.d.ts +2 -2
- package/dist/core/events/v3/producers/hook-base.d.ts.map +1 -1
- package/dist/core/events/v3/producers/hook-base.js +15 -5
- package/dist/core/events/v3/producers/hook.d.ts.map +1 -1
- package/dist/core/events/v3/producers/hook.js +17 -4
- package/dist/core/events/v3/producers/intake.d.ts +4 -4
- package/dist/core/events/v3/producers/intake.d.ts.map +1 -1
- package/dist/core/events/v3/producers/intake.js +3 -2
- package/dist/core/events/v3/producers/producer-state-observer.d.ts.map +1 -1
- package/dist/core/events/v3/producers/producer-state-observer.js +2 -1
- package/dist/core/events/v3/producers/recorder.d.ts +16 -5
- package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/recorder.js +31 -9
- package/dist/core/events/v3/reader.d.ts.map +1 -1
- package/dist/core/events/v3/reader.js +10 -13
- package/dist/core/events/v3/runtime-build.d.ts.map +1 -1
- package/dist/core/events/v3/runtime-build.js +3 -2
- package/dist/core/events/v3/runtime-telemetry-capabilities.d.ts +2 -2
- package/dist/core/events/v3/runtime-telemetry-capabilities.d.ts.map +1 -1
- package/dist/core/events/v3/turn-telemetry.d.ts +2 -2
- package/dist/core/events/v3/turn-telemetry.d.ts.map +1 -1
- package/dist/core/hooks/adapter/output.d.ts +16 -14
- package/dist/core/hooks/adapter/output.d.ts.map +1 -1
- package/dist/core/hooks/adapter/output.js +36 -38
- package/dist/core/hooks/backup-schedule.d.ts +34 -0
- package/dist/core/hooks/backup-schedule.d.ts.map +1 -0
- package/dist/core/hooks/backup-schedule.js +114 -0
- package/dist/core/hooks/cli.js +61 -18
- package/dist/core/hooks/codex-wsl-bridge.d.ts +2 -0
- package/dist/core/hooks/codex-wsl-bridge.d.ts.map +1 -1
- package/dist/core/hooks/codex-wsl-bridge.js +18 -4
- package/dist/core/hooks/index.d.ts +1 -0
- package/dist/core/hooks/index.d.ts.map +1 -1
- package/dist/core/hooks/index.js +1 -0
- package/dist/core/hooks/prompt-context/contract.d.ts +61 -0
- package/dist/core/hooks/prompt-context/contract.d.ts.map +1 -0
- package/dist/core/hooks/prompt-context/contract.js +102 -0
- package/dist/core/hooks/prompt-context/index.d.ts +5 -0
- package/dist/core/hooks/prompt-context/index.d.ts.map +1 -0
- package/dist/core/hooks/prompt-context/index.js +2 -0
- package/dist/core/hooks/prompt-context/runner.d.ts +24 -0
- package/dist/core/hooks/prompt-context/runner.d.ts.map +1 -0
- package/dist/core/hooks/prompt-context/runner.js +249 -0
- package/dist/core/qa-artifacts.d.ts +3 -0
- package/dist/core/qa-artifacts.d.ts.map +1 -1
- package/dist/core/qa-artifacts.js +25 -3
- package/dist/core/resources/contract.d.ts +69 -3
- package/dist/core/resources/contract.d.ts.map +1 -1
- package/dist/core/resources/contract.js +1 -1
- package/dist/core/resources/index.d.ts +1 -0
- package/dist/core/resources/index.d.ts.map +1 -1
- package/dist/core/resources/index.js +1 -0
- package/dist/core/resources/native.d.ts +36 -0
- package/dist/core/resources/native.d.ts.map +1 -0
- package/dist/core/resources/native.js +418 -0
- package/dist/core/resources/sampler.d.ts +2 -0
- package/dist/core/resources/sampler.d.ts.map +1 -1
- package/dist/core/resources/sampler.js +31 -1
- package/dist/core/resources/status.d.ts +61 -0
- package/dist/core/resources/status.d.ts.map +1 -0
- package/dist/core/resources/status.js +536 -0
- package/dist/core/resources/system.d.ts +27 -0
- package/dist/core/resources/system.d.ts.map +1 -0
- package/dist/core/resources/system.js +439 -0
- package/dist/core/semantic/contract.d.ts +13 -13
- package/dist/core/semantic/service.d.ts +2 -0
- package/dist/core/semantic/service.d.ts.map +1 -1
- package/dist/core/semantic/service.js +27 -4
- package/dist/core/storage/builtins.js +41 -0
- package/dist/core/supervisor/contract.d.ts +41 -3
- package/dist/core/supervisor/contract.d.ts.map +1 -1
- package/dist/core/supervisor/contract.js +3 -2
- package/dist/core/supervisor/findings.d.ts +3 -0
- package/dist/core/supervisor/findings.d.ts.map +1 -1
- package/dist/core/supervisor/findings.js +88 -0
- package/dist/core/supervisor/history.d.ts.map +1 -1
- package/dist/core/supervisor/history.js +14 -0
- package/dist/core/supervisor/ledger-state.d.ts +55 -0
- package/dist/core/supervisor/ledger-state.d.ts.map +1 -0
- package/dist/core/supervisor/ledger-state.js +0 -0
- package/dist/core/supervisor/service.d.ts.map +1 -1
- package/dist/core/supervisor/service.js +120 -7
- package/dist/core/supervisor/storage.d.ts +3 -1
- package/dist/core/supervisor/storage.d.ts.map +1 -1
- package/dist/core/supervisor/storage.js +4 -0
- package/dist/core/workflow/admission.d.ts +3 -0
- package/dist/core/workflow/admission.d.ts.map +1 -1
- package/dist/core/workflow/admission.js +28 -16
- package/dist/core/workflow/engine.js +2 -2
- package/dist/core/workflow/live-session-v3.d.ts.map +1 -1
- package/dist/core/workflow/live-session-v3.js +6 -1
- package/dist/core/workflow/proof.d.ts.map +1 -1
- package/dist/core/workflow/proof.js +4 -3
- package/dist/lib/browser/capture-fidelity.d.ts +7 -0
- package/dist/lib/browser/capture-fidelity.d.ts.map +1 -1
- package/dist/lib/browser/capture-fidelity.js +17 -0
- package/dist/lib/browser/client.d.ts +9 -0
- package/dist/lib/browser/client.d.ts.map +1 -1
- package/dist/lib/browser/client.js +85 -37
- package/dist/lib/browser/index.d.ts +3 -1
- package/dist/lib/browser/index.d.ts.map +1 -1
- package/dist/lib/browser/index.js +2 -1
- package/dist/lib/browser/page-review-budget.d.ts +13 -0
- package/dist/lib/browser/page-review-budget.d.ts.map +1 -0
- package/dist/lib/browser/page-review-budget.js +251 -0
- package/dist/lib/browser/page-review-contracts.d.ts +127 -0
- package/dist/lib/browser/page-review-contracts.d.ts.map +1 -0
- package/dist/lib/browser/page-review-contracts.js +5 -0
- package/dist/lib/browser/page-review-evidence.d.ts +48 -0
- package/dist/lib/browser/page-review-evidence.d.ts.map +1 -0
- package/dist/lib/browser/page-review-evidence.js +340 -0
- package/dist/lib/browser/page-review-judge.d.ts +1 -1
- package/dist/lib/browser/page-review-judge.d.ts.map +1 -1
- package/dist/lib/browser/page-review-judge.js +46 -38
- package/dist/lib/browser/page-review-pack.d.ts +44 -22
- package/dist/lib/browser/page-review-pack.d.ts.map +1 -1
- package/dist/lib/browser/page-review-pack.js +116 -36
- package/dist/lib/browser/page-review-source.d.ts +12 -0
- package/dist/lib/browser/page-review-source.d.ts.map +1 -0
- package/dist/lib/browser/page-review-source.js +22 -0
- package/dist/lib/browser/qa-run-contracts.d.ts +35 -9
- package/dist/lib/browser/qa-run-contracts.d.ts.map +1 -1
- package/dist/lib/browser/qa-run-contracts.js +48 -14
- package/dist/lib/browser/qa-run.d.ts.map +1 -1
- package/dist/lib/browser/qa-run.js +215 -68
- package/dist/lib/browser/qa-snapshot.d.ts +6 -0
- package/dist/lib/browser/qa-snapshot.d.ts.map +1 -1
- package/dist/lib/browser/qa-snapshot.js +259 -48
- package/dist/lib/browser/review-capture-transaction.d.ts +13 -0
- package/dist/lib/browser/review-capture-transaction.d.ts.map +1 -0
- package/dist/lib/browser/review-capture-transaction.js +79 -0
- package/dist/lib/browser/standalone-html.d.ts.map +1 -1
- package/dist/lib/browser/standalone-html.js +4 -2
- package/dist/lib/browser/thumbnail-association.d.ts +34 -0
- package/dist/lib/browser/thumbnail-association.d.ts.map +1 -0
- package/dist/lib/browser/thumbnail-association.js +158 -0
- package/dist/lib/browser/tiling.d.ts +30 -0
- package/dist/lib/browser/tiling.d.ts.map +1 -1
- package/dist/lib/browser/tiling.js +38 -0
- package/dist/lib/docs-metadata-managed.d.ts.map +1 -1
- package/dist/lib/docs-metadata-managed.js +4 -1
- package/dist/lib/identities/assume.d.ts.map +1 -1
- package/dist/lib/identities/assume.js +7 -1
- package/dist/lib/instructions/templates.d.ts.map +1 -1
- package/dist/lib/instructions/templates.js +1 -0
- package/dist/lib/local-file-url.d.ts +12 -0
- package/dist/lib/local-file-url.d.ts.map +1 -1
- package/dist/lib/local-file-url.js +16 -2
- package/dist/lib/tunnel/state.d.ts +19 -4
- package/dist/lib/tunnel/state.d.ts.map +1 -1
- package/dist/lib/tunnel/state.js +53 -13
- package/package.json +7 -4
- package/schemas/config.schema.json +69 -2
- package/src/commands/agents.ts +42 -9
- package/src/commands/artifacts.ts +130 -6
- package/src/commands/backup.ts +287 -34
- package/src/commands/browse.ts +564 -267
- package/src/commands/diagnostics.ts +6 -3
- package/src/commands/ledger-v3.ts +20 -3
- package/src/commands/qa-run.ts +37 -3
- package/src/commands/qa-status.ts +11 -2
- package/src/commands/resources.ts +23 -3
- package/src/commands/review-pack.ts +165 -62
- package/src/commands/tunnel.ts +2 -1
- package/src/core/agents/canonical-emit.ts +4 -2
- package/src/core/agents/cli.ts +14 -6
- package/src/core/agents/live-authority-v3.ts +7 -3
- package/src/core/agents/live-lifecycle-v3.ts +2 -2
- package/src/core/agents/render/prompt-context.ts +68 -14
- package/src/core/agents/session-finalizer-v3.ts +2 -2
- package/src/core/agents/state/live-coordination-view.ts +4 -4
- package/src/core/agents/state/names.ts +92 -7
- package/src/core/agents/state/stale-sweep.ts +22 -8
- package/src/core/agents/turn-elapsed.ts +135 -0
- package/src/core/artifacts/activity.ts +71 -0
- package/src/core/artifacts/delivery-card.ts +375 -0
- package/src/core/artifacts/index.ts +385 -91
- package/src/core/artifacts/mutation-lock.ts +106 -0
- package/src/core/backup/host-snapshot.ts +76 -0
- package/src/core/config.ts +66 -13
- package/src/core/diagnostics/advice.ts +148 -120
- package/src/core/diagnostics/bundle.ts +138 -52
- package/src/core/diagnostics/comparison.ts +6 -2
- package/src/core/diagnostics/contract.ts +39 -42
- package/src/core/diagnostics/index.ts +2 -0
- package/src/core/diagnostics/pressure-contract.ts +294 -0
- package/src/core/diagnostics/pressure.ts +1361 -0
- package/src/core/diagnostics/replay.ts +116 -11
- package/src/core/events/v3/adapter-id.ts +12 -0
- package/src/core/events/v3/archive-retention.ts +3 -6
- package/src/core/events/v3/bootstrap.ts +163 -16
- package/src/core/events/v3/capabilities.ts +75 -25
- package/src/core/events/v3/capability-drift.ts +2 -2
- package/src/core/events/v3/control-witness.ts +172 -41
- package/src/core/events/v3/control.ts +34 -2
- package/src/core/events/v3/index.ts +3 -0
- package/src/core/events/v3/live-feed.ts +23 -5
- package/src/core/events/v3/live-routing.ts +14 -2
- package/src/core/events/v3/producers/command-recorder.ts +4 -4
- package/src/core/events/v3/producers/coordination-recorder.ts +4 -4
- package/src/core/events/v3/producers/hook-base.ts +21 -16
- package/src/core/events/v3/producers/hook.ts +21 -4
- package/src/core/events/v3/producers/intake.ts +6 -6
- package/src/core/events/v3/producers/producer-state-observer.ts +2 -1
- package/src/core/events/v3/producers/recorder.ts +50 -21
- package/src/core/events/v3/reader.ts +14 -13
- package/src/core/events/v3/runtime-build.ts +3 -2
- package/src/core/events/v3/runtime-telemetry-capabilities.ts +3 -3
- package/src/core/events/v3/turn-telemetry.ts +4 -4
- package/src/core/hooks/adapter/output.ts +50 -36
- package/src/core/hooks/backup-schedule.ts +147 -0
- package/src/core/hooks/cli.ts +65 -14
- package/src/core/hooks/codex-wsl-bridge.ts +21 -5
- package/src/core/hooks/index.ts +1 -0
- package/src/core/hooks/prompt-context/contract.ts +177 -0
- package/src/core/hooks/prompt-context/index.ts +22 -0
- package/src/core/hooks/prompt-context/runner.ts +303 -0
- package/src/core/qa-artifacts.ts +29 -3
- package/src/core/resources/contract.ts +76 -3
- package/src/core/resources/index.ts +1 -0
- package/src/core/resources/native.ts +481 -0
- package/src/core/resources/sampler.ts +46 -1
- package/src/core/resources/status.ts +717 -0
- package/src/core/resources/system.ts +530 -0
- package/src/core/semantic/service.ts +37 -3
- package/src/core/storage/builtins.ts +41 -0
- package/src/core/supervisor/contract.ts +45 -3
- package/src/core/supervisor/findings.ts +159 -0
- package/src/core/supervisor/history.ts +15 -0
- package/src/core/supervisor/ledger-state.ts +0 -0
- package/src/core/supervisor/service.ts +133 -5
- package/src/core/supervisor/storage.ts +7 -0
- package/src/core/workflow/admission.ts +33 -17
- package/src/core/workflow/engine.ts +2 -2
- package/src/core/workflow/live-session-v3.ts +6 -8
- package/src/core/workflow/proof.ts +4 -3
- package/src/lib/browser/capture-fidelity.ts +23 -0
- package/src/lib/browser/client.ts +96 -44
- package/src/lib/browser/index.ts +15 -1
- package/src/lib/browser/page-review-budget.ts +309 -0
- package/src/lib/browser/page-review-contracts.ts +129 -0
- package/src/lib/browser/page-review-evidence.ts +465 -0
- package/src/lib/browser/page-review-judge.ts +56 -40
- package/src/lib/browser/page-review-pack.ts +181 -43
- package/src/lib/browser/page-review-source.ts +30 -0
- package/src/lib/browser/qa-run-contracts.ts +62 -18
- package/src/lib/browser/qa-run.ts +259 -71
- package/src/lib/browser/qa-snapshot.ts +289 -48
- package/src/lib/browser/review-capture-transaction.ts +106 -0
- package/src/lib/browser/standalone-html.ts +4 -2
- package/src/lib/browser/thumbnail-association.ts +190 -0
- package/src/lib/browser/tiling.ts +63 -0
- package/src/lib/docs-metadata-managed.ts +4 -1
- package/src/lib/identities/assume.ts +12 -4
- package/src/lib/instructions/templates.ts +1 -0
- package/src/lib/local-file-url.ts +17 -2
- package/src/lib/tunnel/state.ts +70 -13
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
// target, a subdirectory per context, atomic writes (tmp + rename) so a
|
|
14
14
|
// crashed writer never leaves a half-baseline the next run would trust.
|
|
15
15
|
|
|
16
|
+
import { randomUUID } from "node:crypto";
|
|
16
17
|
import {
|
|
17
18
|
existsSync,
|
|
18
19
|
mkdirSync,
|
|
@@ -24,6 +25,12 @@ import {
|
|
|
24
25
|
} from "node:fs";
|
|
25
26
|
import { homedir } from "node:os";
|
|
26
27
|
import { resolve } from "node:path";
|
|
28
|
+
import {
|
|
29
|
+
PAGE_REVIEW_DECISIONS_SCHEMA,
|
|
30
|
+
type PageReviewDecisionSlot,
|
|
31
|
+
type PageReviewNativeEvidence,
|
|
32
|
+
} from "./page-review-contracts.js";
|
|
33
|
+
import { machineFindingKey, validateNativeEvidence } from "./page-review-evidence.js";
|
|
27
34
|
import { fnv1a32, type QaContext, type QaSignature } from "./qa-plan.js";
|
|
28
35
|
import type { PersistedCritique } from "./qa-reuse.js";
|
|
29
36
|
|
|
@@ -43,6 +50,7 @@ export interface StoredQaSnapshot {
|
|
|
43
50
|
/** Critique results persisted with the snapshot (full-coverage runs only) —
|
|
44
51
|
* what the band-diff reuse layer replays for provably-unchanged regions. */
|
|
45
52
|
critique?: PersistedCritique;
|
|
53
|
+
tileEvidence?: PageReviewNativeEvidence;
|
|
46
54
|
}
|
|
47
55
|
|
|
48
56
|
/** Filesystem-safe key for a target URL/file + render context. The digest
|
|
@@ -75,6 +83,159 @@ function snapshotDir(target: string, context: QaContext, opts: QaSnapshotStoreOp
|
|
|
75
83
|
return resolve(opts.root ?? SNAPSHOT_DIR, qaSnapshotKey(target), contextDirName(context));
|
|
76
84
|
}
|
|
77
85
|
|
|
86
|
+
/** Serialize both owned slots across processes. A stuck writer fails closed. */
|
|
87
|
+
function withStoreLock<T>(dir: string, operation: () => T): T {
|
|
88
|
+
mkdirSync(dir, { recursive: true });
|
|
89
|
+
const lock = resolve(dir, ".write-lock");
|
|
90
|
+
const deadline = Date.now() + 5_000;
|
|
91
|
+
while (true) {
|
|
92
|
+
try {
|
|
93
|
+
mkdirSync(lock);
|
|
94
|
+
break;
|
|
95
|
+
} catch (error) {
|
|
96
|
+
if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error;
|
|
97
|
+
if (Date.now() >= deadline) throw new Error(`Snapshot writer lock busy: ${dir}`);
|
|
98
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
return operation();
|
|
103
|
+
} finally {
|
|
104
|
+
rmSync(lock, { recursive: true, force: true });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function validateDecisionSlot(
|
|
109
|
+
target: string,
|
|
110
|
+
context: QaContext,
|
|
111
|
+
slot: PageReviewDecisionSlot,
|
|
112
|
+
): void {
|
|
113
|
+
const only = (value: object, keys: string[]) =>
|
|
114
|
+
Object.keys(value).every((key) => keys.includes(key));
|
|
115
|
+
if (
|
|
116
|
+
slot.schema !== PAGE_REVIEW_DECISIONS_SCHEMA ||
|
|
117
|
+
slot.complete !== true ||
|
|
118
|
+
slot.target !== target ||
|
|
119
|
+
slot.context.viewport !== context.viewport ||
|
|
120
|
+
slot.context.theme !== context.theme ||
|
|
121
|
+
slot.context.state !== context.state ||
|
|
122
|
+
slot.context.critique_contract_version !== 2 ||
|
|
123
|
+
![slot.context.viewport_width, slot.context.viewport_height, slot.context.dpr].every(
|
|
124
|
+
(n) => Number.isFinite(n) && n > 0,
|
|
125
|
+
) ||
|
|
126
|
+
!/^[a-f0-9]{64}$/.test(slot.context.rubric_digest) ||
|
|
127
|
+
!slot.context.recipe_version ||
|
|
128
|
+
!only(slot, [
|
|
129
|
+
"schema",
|
|
130
|
+
"complete",
|
|
131
|
+
"target",
|
|
132
|
+
"context",
|
|
133
|
+
"source_run",
|
|
134
|
+
"source_revision",
|
|
135
|
+
"reviewer",
|
|
136
|
+
"reviewed_at",
|
|
137
|
+
"decisions",
|
|
138
|
+
]) ||
|
|
139
|
+
!only(slot.context, [
|
|
140
|
+
"viewport",
|
|
141
|
+
"viewport_width",
|
|
142
|
+
"viewport_height",
|
|
143
|
+
"theme",
|
|
144
|
+
"state",
|
|
145
|
+
"dpr",
|
|
146
|
+
"recipe_version",
|
|
147
|
+
"rubric_digest",
|
|
148
|
+
"critique_contract_version",
|
|
149
|
+
]) ||
|
|
150
|
+
!slot.reviewer?.trim() ||
|
|
151
|
+
!Number.isFinite(Date.parse(slot.reviewed_at)) ||
|
|
152
|
+
!slot.source_run ||
|
|
153
|
+
!Array.isArray(slot.decisions)
|
|
154
|
+
)
|
|
155
|
+
throw new Error("Invalid durable review decision slot");
|
|
156
|
+
const keys = new Set<string>();
|
|
157
|
+
for (const decision of slot.decisions) {
|
|
158
|
+
if (
|
|
159
|
+
!decision.reviewer?.trim() ||
|
|
160
|
+
!Number.isFinite(Date.parse(decision.at)) ||
|
|
161
|
+
keys.has(decision.finding_key) ||
|
|
162
|
+
!/^[a-f0-9]{64}$/.test(decision.tile_pixel_digest) ||
|
|
163
|
+
!only(decision, [
|
|
164
|
+
"finding_key",
|
|
165
|
+
"tile_pixel_digest",
|
|
166
|
+
"rect",
|
|
167
|
+
"finding",
|
|
168
|
+
"disposition",
|
|
169
|
+
"reviewer",
|
|
170
|
+
"at",
|
|
171
|
+
"note",
|
|
172
|
+
]) ||
|
|
173
|
+
!only(decision.finding, ["severity", "category", "description"]) ||
|
|
174
|
+
!only(decision.rect, ["x", "y", "width", "height"]) ||
|
|
175
|
+
![decision.rect.x, decision.rect.y, decision.rect.width, decision.rect.height].every(
|
|
176
|
+
Number.isInteger,
|
|
177
|
+
) ||
|
|
178
|
+
decision.rect.x < 0 ||
|
|
179
|
+
decision.rect.y < 0 ||
|
|
180
|
+
decision.rect.width < 1 ||
|
|
181
|
+
decision.rect.height < 1 ||
|
|
182
|
+
!["artifact", "not-a-defect", "confirmed", "duplicate-of-gate"].includes(
|
|
183
|
+
decision.disposition,
|
|
184
|
+
) ||
|
|
185
|
+
decision.finding.category === "provider-error" ||
|
|
186
|
+
machineFindingKey(
|
|
187
|
+
slot.context,
|
|
188
|
+
{ rect: decision.rect, pixel_digest: decision.tile_pixel_digest },
|
|
189
|
+
decision.finding,
|
|
190
|
+
) !== decision.finding_key
|
|
191
|
+
) {
|
|
192
|
+
throw new Error("Invalid durable review finding identity");
|
|
193
|
+
}
|
|
194
|
+
keys.add(decision.finding_key);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function loadReviewDecisions(
|
|
199
|
+
target: string,
|
|
200
|
+
context: QaContext,
|
|
201
|
+
opts: QaSnapshotStoreOptions = {},
|
|
202
|
+
): PageReviewDecisionSlot | null {
|
|
203
|
+
const dir = snapshotDir(target, context, opts);
|
|
204
|
+
if (!existsSync(dir)) return null;
|
|
205
|
+
try {
|
|
206
|
+
return withStoreLock(dir, () => {
|
|
207
|
+
const slot = JSON.parse(
|
|
208
|
+
readFileSync(resolve(dir, "review-decisions.json"), "utf8"),
|
|
209
|
+
) as PageReviewDecisionSlot;
|
|
210
|
+
validateDecisionSlot(target, context, slot);
|
|
211
|
+
return slot;
|
|
212
|
+
});
|
|
213
|
+
} catch {
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** Decisions contain text and digests only; replacing a baseline cannot touch this slot. */
|
|
219
|
+
export function saveReviewDecisions(
|
|
220
|
+
target: string,
|
|
221
|
+
context: QaContext,
|
|
222
|
+
slot: PageReviewDecisionSlot,
|
|
223
|
+
opts: QaSnapshotStoreOptions = {},
|
|
224
|
+
): void {
|
|
225
|
+
validateDecisionSlot(target, context, slot);
|
|
226
|
+
const dir = snapshotDir(target, context, opts);
|
|
227
|
+
withStoreLock(dir, () => {
|
|
228
|
+
const path = resolve(dir, "review-decisions.json");
|
|
229
|
+
const temp = `${path}.tmp-${randomUUID()}`;
|
|
230
|
+
try {
|
|
231
|
+
writeFileSync(temp, `${JSON.stringify(slot)}\n`);
|
|
232
|
+
renameSync(temp, path);
|
|
233
|
+
} finally {
|
|
234
|
+
rmSync(temp, { force: true });
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
78
239
|
/**
|
|
79
240
|
* Persist a QA snapshot atomically. The whole context directory is staged as
|
|
80
241
|
* `<dir>.tmp-<pid>` and renamed into place, replacing any prior snapshot for
|
|
@@ -88,33 +249,77 @@ export function saveQaSnapshot(
|
|
|
88
249
|
domHtml?: string;
|
|
89
250
|
screenshotPng?: Buffer;
|
|
90
251
|
critique?: PersistedCritique;
|
|
252
|
+
tileEvidence?: PageReviewNativeEvidence;
|
|
91
253
|
},
|
|
92
254
|
opts: QaSnapshotStoreOptions = {},
|
|
93
255
|
): StoredQaSnapshot {
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
256
|
+
const container = snapshotDir(target, context, opts);
|
|
257
|
+
return withStoreLock(container, () => {
|
|
258
|
+
const dir = resolve(container, "baseline");
|
|
259
|
+
const staging = `${dir}.tmp-${randomUUID()}`;
|
|
260
|
+
const previous = `${dir}.previous`;
|
|
261
|
+
if (content.tileEvidence && content.screenshotPng)
|
|
262
|
+
throw new Error("A pack native-tile baseline must not retain a full-page image");
|
|
263
|
+
if (content.tileEvidence && !validateNativeEvidence(content.tileEvidence))
|
|
264
|
+
throw new Error("Invalid native tile baseline evidence");
|
|
265
|
+
mkdirSync(staging, { recursive: true });
|
|
266
|
+
try {
|
|
267
|
+
writeFileSync(resolve(staging, "signature.json"), `${JSON.stringify(content.signature)}\n`);
|
|
268
|
+
if (content.domHtml !== undefined) {
|
|
269
|
+
writeFileSync(resolve(staging, "dom.html"), content.domHtml);
|
|
270
|
+
}
|
|
271
|
+
if (content.screenshotPng !== undefined) {
|
|
272
|
+
writeFileSync(resolve(staging, "screenshot.png"), content.screenshotPng);
|
|
273
|
+
}
|
|
274
|
+
if (content.critique !== undefined) {
|
|
275
|
+
writeFileSync(resolve(staging, "critique.json"), `${JSON.stringify(content.critique)}\n`);
|
|
276
|
+
}
|
|
277
|
+
if (content.tileEvidence) {
|
|
278
|
+
const tiles = content.tileEvidence.tiles.map(({ png, ...tile }, i) => {
|
|
279
|
+
const file = `tile-${i}.png`;
|
|
280
|
+
writeFileSync(resolve(staging, file), png);
|
|
281
|
+
return { ...tile, file };
|
|
282
|
+
});
|
|
283
|
+
writeFileSync(
|
|
284
|
+
resolve(staging, "native-evidence.json"),
|
|
285
|
+
JSON.stringify({ ...content.tileEvidence, tiles }),
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
// Recovery of a writer interrupted after moving its old baseline.
|
|
289
|
+
if (!existsSync(dir) && existsSync(previous)) renameSync(previous, dir);
|
|
290
|
+
rmSync(previous, { recursive: true, force: true });
|
|
291
|
+
if (existsSync(dir)) renameSync(dir, previous);
|
|
292
|
+
try {
|
|
293
|
+
renameSync(staging, dir);
|
|
294
|
+
} catch (error) {
|
|
295
|
+
if (existsSync(previous)) renameSync(previous, dir);
|
|
296
|
+
throw error;
|
|
297
|
+
}
|
|
298
|
+
rmSync(previous, { recursive: true, force: true });
|
|
299
|
+
// Remove superseded legacy baseline files, never the independent decision slot.
|
|
300
|
+
for (const name of [
|
|
301
|
+
"signature.json",
|
|
302
|
+
"dom.html",
|
|
303
|
+
"screenshot.png",
|
|
304
|
+
"critique.json",
|
|
305
|
+
"native-evidence.json",
|
|
306
|
+
]) {
|
|
307
|
+
rmSync(resolve(container, name), { force: true });
|
|
308
|
+
}
|
|
309
|
+
return {
|
|
310
|
+
signature: content.signature,
|
|
311
|
+
path: dir,
|
|
312
|
+
...(content.domHtml !== undefined ? { domHtmlPath: resolve(dir, "dom.html") } : {}),
|
|
313
|
+
...(content.screenshotPng !== undefined
|
|
314
|
+
? { screenshotPath: resolve(dir, "screenshot.png") }
|
|
315
|
+
: {}),
|
|
316
|
+
...(content.critique ? { critique: content.critique } : {}),
|
|
317
|
+
...(content.tileEvidence ? { tileEvidence: content.tileEvidence } : {}),
|
|
318
|
+
};
|
|
319
|
+
} finally {
|
|
320
|
+
rmSync(staging, { recursive: true, force: true });
|
|
321
|
+
}
|
|
322
|
+
});
|
|
118
323
|
}
|
|
119
324
|
|
|
120
325
|
/** Load the persisted snapshot for a target + context, or null. A corrupt
|
|
@@ -125,35 +330,71 @@ export function loadQaSnapshot(
|
|
|
125
330
|
context: QaContext,
|
|
126
331
|
opts: QaSnapshotStoreOptions = {},
|
|
127
332
|
): StoredQaSnapshot | null {
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
if (!existsSync(sigPath)) return null;
|
|
333
|
+
const container = snapshotDir(target, context, opts);
|
|
334
|
+
if (!existsSync(container)) return null;
|
|
131
335
|
try {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
336
|
+
return withStoreLock(container, () => {
|
|
337
|
+
const baseline = resolve(container, "baseline");
|
|
338
|
+
const dir = existsSync(baseline)
|
|
339
|
+
? baseline
|
|
340
|
+
: existsSync(`${baseline}.previous`)
|
|
341
|
+
? `${baseline}.previous`
|
|
342
|
+
: container;
|
|
343
|
+
const sigPath = resolve(dir, "signature.json");
|
|
344
|
+
if (!existsSync(sigPath)) return null;
|
|
141
345
|
try {
|
|
142
|
-
const
|
|
143
|
-
if (
|
|
144
|
-
|
|
346
|
+
const signature = JSON.parse(readFileSync(sigPath, "utf8")) as QaSignature;
|
|
347
|
+
if (
|
|
348
|
+
!signature ||
|
|
349
|
+
!Array.isArray(signature.nodes) ||
|
|
350
|
+
!Array.isArray(signature.stylesheets)
|
|
351
|
+
) {
|
|
352
|
+
return null;
|
|
145
353
|
}
|
|
354
|
+
const domHtmlPath = resolve(dir, "dom.html");
|
|
355
|
+
const screenshotPath = resolve(dir, "screenshot.png");
|
|
356
|
+
const critiquePath = resolve(dir, "critique.json");
|
|
357
|
+
let critique: PersistedCritique | undefined;
|
|
358
|
+
let tileEvidence: PageReviewNativeEvidence | undefined;
|
|
359
|
+
try {
|
|
360
|
+
const raw = JSON.parse(readFileSync(resolve(dir, "native-evidence.json"), "utf8"));
|
|
361
|
+
const candidate = {
|
|
362
|
+
...raw,
|
|
363
|
+
tiles: raw.tiles.map((tile: { file: string }) => {
|
|
364
|
+
if (!/^tile-\d+\.png$/.test(tile.file)) throw new Error("Invalid native tile path");
|
|
365
|
+
return { ...tile, png: readFileSync(resolve(dir, tile.file)) };
|
|
366
|
+
}),
|
|
367
|
+
} as PageReviewNativeEvidence;
|
|
368
|
+
if (validateNativeEvidence(candidate)) tileEvidence = candidate;
|
|
369
|
+
} catch {
|
|
370
|
+
/* Missing or corrupt evidence is a cache miss. */
|
|
371
|
+
}
|
|
372
|
+
if (existsSync(critiquePath)) {
|
|
373
|
+
try {
|
|
374
|
+
const parsed = JSON.parse(readFileSync(critiquePath, "utf8")) as PersistedCritique;
|
|
375
|
+
if (
|
|
376
|
+
parsed &&
|
|
377
|
+
typeof parsed.contract_version === "number" &&
|
|
378
|
+
Array.isArray(parsed.tiles)
|
|
379
|
+
) {
|
|
380
|
+
critique = parsed;
|
|
381
|
+
}
|
|
382
|
+
} catch {
|
|
383
|
+
// corrupt critique.json degrades to a reuse miss, never an error
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
return {
|
|
387
|
+
signature,
|
|
388
|
+
path: dir,
|
|
389
|
+
...(existsSync(domHtmlPath) ? { domHtmlPath } : {}),
|
|
390
|
+
...(existsSync(screenshotPath) ? { screenshotPath } : {}),
|
|
391
|
+
...(critique ? { critique } : {}),
|
|
392
|
+
...(tileEvidence ? { tileEvidence } : {}),
|
|
393
|
+
};
|
|
146
394
|
} catch {
|
|
147
|
-
|
|
395
|
+
return null;
|
|
148
396
|
}
|
|
149
|
-
}
|
|
150
|
-
return {
|
|
151
|
-
signature,
|
|
152
|
-
path: dir,
|
|
153
|
-
...(existsSync(domHtmlPath) ? { domHtmlPath } : {}),
|
|
154
|
-
...(existsSync(screenshotPath) ? { screenshotPath } : {}),
|
|
155
|
-
...(critique ? { critique } : {}),
|
|
156
|
-
};
|
|
397
|
+
});
|
|
157
398
|
} catch {
|
|
158
399
|
return null;
|
|
159
400
|
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, mkdtempSync, renameSync, rmSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
allocateTileBudget,
|
|
5
|
+
capturePlanDigest,
|
|
6
|
+
validatePageReviewAllocation,
|
|
7
|
+
} from "./page-review-budget.ts";
|
|
8
|
+
import type {
|
|
9
|
+
PageReviewCapturePlan,
|
|
10
|
+
PageReviewContextAllocation,
|
|
11
|
+
} from "./page-review-contracts.ts";
|
|
12
|
+
import { packPaths } from "./page-review-pack.ts";
|
|
13
|
+
|
|
14
|
+
/** Publish a complete context atomically; failed writes stay outside discoverable contexts. */
|
|
15
|
+
export function writeCaptureTransaction<T>(
|
|
16
|
+
packDir: string,
|
|
17
|
+
contextId: string,
|
|
18
|
+
write: (stage: string) => T,
|
|
19
|
+
): T {
|
|
20
|
+
mkdirSync(packDir, { recursive: true, mode: 0o700 });
|
|
21
|
+
const stage = mkdtempSync(join(packDir, ".pending-capture-"));
|
|
22
|
+
try {
|
|
23
|
+
const result = write(stage);
|
|
24
|
+
const destination = packPaths(packDir).contextDir(contextId);
|
|
25
|
+
mkdirSync(dirname(destination), { recursive: true });
|
|
26
|
+
if (existsSync(destination))
|
|
27
|
+
throw new Error("Capture context already exists; refusing to replace prior evidence.");
|
|
28
|
+
renameSync(packPaths(stage).contextDir(contextId), destination);
|
|
29
|
+
rmSync(stage, { recursive: true });
|
|
30
|
+
return result;
|
|
31
|
+
} catch (error) {
|
|
32
|
+
if (error instanceof Error) error.message += ` Incomplete capture retained at ${stage}.`;
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** A preliminary allocation reserves pixels; it never supplies current source evidence. */
|
|
38
|
+
export function allocateCaptureReservation(
|
|
39
|
+
reservation: PageReviewContextAllocation,
|
|
40
|
+
current: PageReviewCapturePlan,
|
|
41
|
+
): PageReviewContextAllocation {
|
|
42
|
+
validatePageReviewAllocation(reservation, reservation.plan);
|
|
43
|
+
for (const key of [
|
|
44
|
+
"context_id",
|
|
45
|
+
"viewport",
|
|
46
|
+
"viewport_width",
|
|
47
|
+
"viewport_height",
|
|
48
|
+
"theme",
|
|
49
|
+
"state",
|
|
50
|
+
"dpr",
|
|
51
|
+
"recipe_version",
|
|
52
|
+
] as const) {
|
|
53
|
+
if (reservation.plan[key] !== current[key])
|
|
54
|
+
throw new Error(`Capture reservation changed ${key}.`);
|
|
55
|
+
}
|
|
56
|
+
if (reservation.plan.required_scopes.some((scope) => !current.required_scopes.includes(scope)))
|
|
57
|
+
throw new Error("Capture reservation lost a required scope.");
|
|
58
|
+
// Reservations sum to the original global allocation, even when its per-context
|
|
59
|
+
// ceiling was higher than the number of tiles actually assigned to that context.
|
|
60
|
+
const ceiling = reservation.selected_ids.length;
|
|
61
|
+
const allocation = allocateTileBudget([current], ceiling, { [current.context_id]: ceiling })
|
|
62
|
+
.contexts[0];
|
|
63
|
+
if (
|
|
64
|
+
allocation.coverage.capped ||
|
|
65
|
+
allocation.coverage.omitted_scopes.length ||
|
|
66
|
+
allocation.coverage.uncovered_intervals.length
|
|
67
|
+
)
|
|
68
|
+
throw new Error("Fresh native capture coverage exceeds the reserved tile budget.");
|
|
69
|
+
return validatePageReviewAllocation(allocation, current);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export class CaptureSourceChanged extends Error {
|
|
73
|
+
constructor(message = "Page source changed during the gate and capture transaction.") {
|
|
74
|
+
super(message);
|
|
75
|
+
this.name = "CaptureSourceChanged";
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Gate-hit annotations may change, but source and every native rectangle must stay fixed. */
|
|
80
|
+
export function assertGatePlanStable(
|
|
81
|
+
before: PageReviewCapturePlan,
|
|
82
|
+
after: PageReviewCapturePlan,
|
|
83
|
+
): void {
|
|
84
|
+
if (capturePlanDigest(before) !== capturePlanDigest(after))
|
|
85
|
+
throw new CaptureSourceChanged(
|
|
86
|
+
"Page source or geometry changed while deterministic gates were running.",
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** One retry recollects every gate and allocation; no previous green result survives. */
|
|
91
|
+
export async function runCaptureTransaction<T>(
|
|
92
|
+
collectAndAllocate: (attempt: number) => Promise<void>,
|
|
93
|
+
capture: () => Promise<T>,
|
|
94
|
+
recordFailure: (attempt: number, error: unknown) => void,
|
|
95
|
+
): Promise<T> {
|
|
96
|
+
for (let attempt = 1; attempt <= 2; attempt++) {
|
|
97
|
+
try {
|
|
98
|
+
await collectAndAllocate(attempt);
|
|
99
|
+
return await capture();
|
|
100
|
+
} catch (error) {
|
|
101
|
+
recordFailure(attempt, error);
|
|
102
|
+
if (!(error instanceof CaptureSourceChanged) || attempt === 2) throw error;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
throw new Error("Capture transaction did not complete.");
|
|
106
|
+
}
|
|
@@ -73,7 +73,9 @@ export function buildStandaloneHtmlScript(): (
|
|
|
73
73
|
if (cached !== undefined) return cached;
|
|
74
74
|
let result: string | null = null;
|
|
75
75
|
try {
|
|
76
|
-
|
|
76
|
+
// Retain access to authenticated same-origin assets without imposing
|
|
77
|
+
// credentialed CORS on public assets served by another origin.
|
|
78
|
+
const res = await fetch(url, { credentials: "same-origin" });
|
|
77
79
|
if (res.ok) {
|
|
78
80
|
const buf = await res.arrayBuffer();
|
|
79
81
|
const fits =
|
|
@@ -169,7 +171,7 @@ export function buildStandaloneHtmlScript(): (
|
|
|
169
171
|
}
|
|
170
172
|
let css: string | null = null;
|
|
171
173
|
try {
|
|
172
|
-
const res = await fetch(url, { credentials: "
|
|
174
|
+
const res = await fetch(url, { credentials: "same-origin" });
|
|
173
175
|
if (res.ok) css = await res.text();
|
|
174
176
|
} catch {
|
|
175
177
|
css = null;
|