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
|
@@ -14,9 +14,11 @@
|
|
|
14
14
|
|
|
15
15
|
import { createHash } from "node:crypto";
|
|
16
16
|
import {
|
|
17
|
+
copyFileSync,
|
|
17
18
|
existsSync,
|
|
18
19
|
lstatSync,
|
|
19
20
|
mkdirSync,
|
|
21
|
+
mkdtempSync,
|
|
20
22
|
readdirSync,
|
|
21
23
|
readFileSync,
|
|
22
24
|
renameSync,
|
|
@@ -28,9 +30,19 @@ import { basename, dirname, join, relative, resolve } from "node:path";
|
|
|
28
30
|
import { gunzipSync, gzipSync } from "node:zlib";
|
|
29
31
|
import { PNG } from "pngjs";
|
|
30
32
|
import type { CritiqueCoverage, CritiqueFinding, CritiqueTile } from "./critique.js";
|
|
33
|
+
import type {
|
|
34
|
+
PageReviewBudgetCoverage,
|
|
35
|
+
PageReviewCapturePlan,
|
|
36
|
+
PageReviewEvidenceContext,
|
|
37
|
+
PageReviewNativeTile,
|
|
38
|
+
PageReviewRect,
|
|
39
|
+
} from "./page-review-contracts.js";
|
|
40
|
+
import { machineEvidenceDigest, PAGE_REVIEW_CRITIQUE_SCHEMA } from "./page-review-evidence.js";
|
|
31
41
|
import type { QaContext, QaSignature } from "./qa-plan.js";
|
|
32
42
|
|
|
33
|
-
export
|
|
43
|
+
export type { PageReviewRect } from "./page-review-contracts.js";
|
|
44
|
+
|
|
45
|
+
export const PAGE_REVIEW_PACK_SCHEMA = "harnery-page-review/v2";
|
|
34
46
|
export const PAGE_REVIEW_FINDINGS_SCHEMA = "harnery-page-review-findings/v3";
|
|
35
47
|
|
|
36
48
|
/** Directory name a qa-run gives its pack inside the run directory. */
|
|
@@ -127,6 +139,7 @@ export interface PageReviewFindingsDocument {
|
|
|
127
139
|
target: string;
|
|
128
140
|
reviewer: string | null;
|
|
129
141
|
reviewed_at: string | null;
|
|
142
|
+
machine_evidence_digest?: string | null;
|
|
130
143
|
delegated_reviews: PageReviewDelegatedReviewRecord[];
|
|
131
144
|
findings: PageReviewReviewerFinding[];
|
|
132
145
|
dispositions?: PageReviewDispositionRecord[];
|
|
@@ -249,6 +262,14 @@ export interface PageReviewContextRecord {
|
|
|
249
262
|
title?: string;
|
|
250
263
|
captured_at: string;
|
|
251
264
|
page: { width: number; height: number };
|
|
265
|
+
viewport_size: { width: number; height: number };
|
|
266
|
+
dpr: number;
|
|
267
|
+
recipe_version: string;
|
|
268
|
+
full_page_scale: 0.5;
|
|
269
|
+
capture_plan?: PageReviewCapturePlan;
|
|
270
|
+
allocation_coverage?: PageReviewBudgetCoverage;
|
|
271
|
+
evidence_context?: PageReviewEvidenceContext;
|
|
272
|
+
capture_incomplete?: boolean;
|
|
252
273
|
/** Full-page band coverage (what the tiler kept of the page). */
|
|
253
274
|
coverage: CritiqueCoverage;
|
|
254
275
|
scopes: Array<{ selector: string; tiles: number }>;
|
|
@@ -277,6 +298,12 @@ export interface PageReviewContextCapture {
|
|
|
277
298
|
fullPage: Buffer;
|
|
278
299
|
pageWidth: number;
|
|
279
300
|
pageHeight: number;
|
|
301
|
+
viewportSize: { width: number; height: number };
|
|
302
|
+
dpr: number;
|
|
303
|
+
recipeVersion: string;
|
|
304
|
+
capturePlan?: PageReviewCapturePlan;
|
|
305
|
+
allocationCoverage?: PageReviewBudgetCoverage;
|
|
306
|
+
captureIncomplete?: boolean;
|
|
280
307
|
tiles: CritiqueTile[];
|
|
281
308
|
coverage: CritiqueCoverage;
|
|
282
309
|
scopeTiles?: Array<{ selector: string; tiles: CritiqueTile[] }>;
|
|
@@ -302,13 +329,11 @@ export interface PageReviewCritiqueRecord {
|
|
|
302
329
|
findings: Array<CritiqueFinding & { tile_id: string }>;
|
|
303
330
|
coverage: CritiqueCoverage;
|
|
304
331
|
error?: string;
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
width: number;
|
|
311
|
-
height: number;
|
|
332
|
+
evidence?: {
|
|
333
|
+
schema: string;
|
|
334
|
+
context: PageReviewEvidenceContext;
|
|
335
|
+
tiles: Array<Omit<PageReviewNativeTile, "png">>;
|
|
336
|
+
};
|
|
312
337
|
}
|
|
313
338
|
|
|
314
339
|
/** One gate finding that carries a document-space rectangle (CSS px at the
|
|
@@ -515,6 +540,7 @@ export function writePackExpandedTile(
|
|
|
515
540
|
const paths = packPaths(packDir);
|
|
516
541
|
const dir = paths.contextDir(contextId);
|
|
517
542
|
const file = join(dir, "tiles", expandedTileFilename(tile.id, input.dpr));
|
|
543
|
+
const scale = input.dpr / record.dpr;
|
|
518
544
|
const { png, width, height } = input.region
|
|
519
545
|
? {
|
|
520
546
|
png: input.region,
|
|
@@ -522,10 +548,10 @@ export function writePackExpandedTile(
|
|
|
522
548
|
height: input.region.readUInt32BE(20),
|
|
523
549
|
}
|
|
524
550
|
: cropPngRegion(input.fullPage, {
|
|
525
|
-
x: tile.x *
|
|
526
|
-
y: tile.scrollY *
|
|
527
|
-
width: tile.width *
|
|
528
|
-
height: tile.height *
|
|
551
|
+
x: tile.x * scale,
|
|
552
|
+
y: tile.scrollY * scale,
|
|
553
|
+
width: tile.width * scale,
|
|
554
|
+
height: tile.height * scale,
|
|
529
555
|
});
|
|
530
556
|
mkdirSync(join(dir, "tiles"), { recursive: true });
|
|
531
557
|
writeFileSync(file, png);
|
|
@@ -557,6 +583,17 @@ export function writePackContext(
|
|
|
557
583
|
packDir: string,
|
|
558
584
|
capture: PageReviewContextCapture,
|
|
559
585
|
): PageReviewContextRecord {
|
|
586
|
+
if (
|
|
587
|
+
!capture.viewportSize ||
|
|
588
|
+
![capture.viewportSize.width, capture.viewportSize.height, capture.dpr].every(
|
|
589
|
+
(n) => Number.isFinite(n) && n > 0,
|
|
590
|
+
) ||
|
|
591
|
+
!capture.recipeVersion?.trim()
|
|
592
|
+
) {
|
|
593
|
+
throw new Error(
|
|
594
|
+
"Native pack capture requires viewportSize, positive DPR, and a capture recipe version",
|
|
595
|
+
);
|
|
596
|
+
}
|
|
560
597
|
const contextId =
|
|
561
598
|
capture.context.id ??
|
|
562
599
|
`${capture.context.viewport}-${capture.context.theme}-${capture.context.state}`;
|
|
@@ -571,7 +608,14 @@ export function writePackContext(
|
|
|
571
608
|
const signaturePath = join(dir, "signature.json");
|
|
572
609
|
const tilesPath = join(dir, "tiles.json");
|
|
573
610
|
const contextPath = join(dir, "context.json");
|
|
574
|
-
|
|
611
|
+
// Full-page pixels are transient. The saved image is orientation only.
|
|
612
|
+
const native = PNG.sync.read(capture.fullPage);
|
|
613
|
+
const overview = boxDownscale(
|
|
614
|
+
native,
|
|
615
|
+
Math.max(1, Math.round(native.width / 2)),
|
|
616
|
+
Math.max(1, Math.round(native.height / 2)),
|
|
617
|
+
);
|
|
618
|
+
writeFileSync(fullPagePath, PNG.sync.write(overview));
|
|
575
619
|
// The serialized DOM compresses roughly 10:1; `dom_sha256` stays the digest
|
|
576
620
|
// of the uncompressed bytes so a reader can verify what `readPackDom` returns.
|
|
577
621
|
writeFileSync(domPath, gzipSync(Buffer.from(capture.domHtml, "utf8")));
|
|
@@ -615,7 +659,17 @@ export function writePackContext(
|
|
|
615
659
|
url: capture.url,
|
|
616
660
|
...(capture.title !== undefined ? { title: capture.title } : {}),
|
|
617
661
|
captured_at: capture.capturedAt ?? new Date().toISOString(),
|
|
618
|
-
page: {
|
|
662
|
+
page: {
|
|
663
|
+
width: Math.round(capture.pageWidth * capture.dpr),
|
|
664
|
+
height: Math.round(capture.pageHeight * capture.dpr),
|
|
665
|
+
},
|
|
666
|
+
viewport_size: capture.viewportSize,
|
|
667
|
+
dpr: capture.dpr,
|
|
668
|
+
recipe_version: capture.recipeVersion,
|
|
669
|
+
full_page_scale: 0.5,
|
|
670
|
+
...(capture.capturePlan ? { capture_plan: capture.capturePlan } : {}),
|
|
671
|
+
...(capture.allocationCoverage ? { allocation_coverage: capture.allocationCoverage } : {}),
|
|
672
|
+
...(capture.captureIncomplete ? { capture_incomplete: true } : {}),
|
|
619
673
|
coverage: capture.coverage,
|
|
620
674
|
scopes: (capture.scopeTiles ?? []).map((entry) => ({
|
|
621
675
|
selector: entry.selector,
|
|
@@ -862,7 +916,9 @@ export function readPackContext(packDir: string, contextId: string): PageReviewC
|
|
|
862
916
|
const path = join(packPaths(packDir).contextDir(contextId), "context.json");
|
|
863
917
|
const { schema, ...record } = readJson<PageReviewContextRecord & { schema?: string }>(path);
|
|
864
918
|
if (schema !== PAGE_REVIEW_PACK_SCHEMA) {
|
|
865
|
-
throw new Error(
|
|
919
|
+
throw new Error(
|
|
920
|
+
`${path}: expected schema ${PAGE_REVIEW_PACK_SCHEMA}, found ${schema}; recapture the target to create a v2 pack`,
|
|
921
|
+
);
|
|
866
922
|
}
|
|
867
923
|
return record;
|
|
868
924
|
}
|
|
@@ -897,7 +953,8 @@ export function readPackTiles(
|
|
|
897
953
|
});
|
|
898
954
|
}
|
|
899
955
|
|
|
900
|
-
|
|
956
|
+
/** Half-scale navigation image; never native evidence. */
|
|
957
|
+
export function readPackOverview(packDir: string, record: PageReviewContextRecord): Buffer {
|
|
901
958
|
return readFileSync(join(packDir, record.files.full_page));
|
|
902
959
|
}
|
|
903
960
|
|
|
@@ -917,7 +974,7 @@ export function readPackManifest(packDir: string): PageReviewPackManifest {
|
|
|
917
974
|
const manifest = readJson<PageReviewPackManifest>(packPaths(packDir).manifest);
|
|
918
975
|
if (manifest.schema !== PAGE_REVIEW_PACK_SCHEMA) {
|
|
919
976
|
throw new Error(
|
|
920
|
-
`${packPaths(packDir).manifest}: expected schema ${PAGE_REVIEW_PACK_SCHEMA}, found ${manifest.schema}`,
|
|
977
|
+
`${packPaths(packDir).manifest}: expected schema ${PAGE_REVIEW_PACK_SCHEMA}, found ${manifest.schema}; recapture the target to create a v2 pack`,
|
|
921
978
|
);
|
|
922
979
|
}
|
|
923
980
|
return manifest;
|
|
@@ -974,6 +1031,7 @@ export function validateFindingsDocument(doc: unknown): string[] {
|
|
|
974
1031
|
"target",
|
|
975
1032
|
"reviewer",
|
|
976
1033
|
"reviewed_at",
|
|
1034
|
+
"machine_evidence_digest",
|
|
977
1035
|
"delegated_reviews",
|
|
978
1036
|
"findings",
|
|
979
1037
|
"dispositions",
|
|
@@ -1238,7 +1296,8 @@ export function machineFindingTargets(
|
|
|
1238
1296
|
* critique: `evidence/critique.json` stays the machine record.
|
|
1239
1297
|
*/
|
|
1240
1298
|
export function resolvePackVerdict(
|
|
1241
|
-
manifest: Pick<PageReviewPackManifest, "critique"
|
|
1299
|
+
manifest: Pick<PageReviewPackManifest, "critique"> &
|
|
1300
|
+
Partial<Pick<PageReviewPackManifest, "gates" | "contexts">>,
|
|
1242
1301
|
inspectionPlan: PageReviewInspectionPlan,
|
|
1243
1302
|
findings: PageReviewFindingsDocument,
|
|
1244
1303
|
): PageReviewVerdict {
|
|
@@ -1271,7 +1330,8 @@ export function resolvePackVerdict(
|
|
|
1271
1330
|
high_total += 1;
|
|
1272
1331
|
const disposition = byTarget.get(target)?.disposition;
|
|
1273
1332
|
if (disposition === undefined) high_open += 1;
|
|
1274
|
-
else if (disposition === "confirmed"
|
|
1333
|
+
else if (disposition === "confirmed" || finding.category === "provider-error")
|
|
1334
|
+
high_confirmed += 1;
|
|
1275
1335
|
else high_dismissed += 1;
|
|
1276
1336
|
}
|
|
1277
1337
|
const reviewer_high = findings.findings.filter(
|
|
@@ -1292,8 +1352,20 @@ export function resolvePackVerdict(
|
|
|
1292
1352
|
.filter((tile) => !completedTiles.has(tile))
|
|
1293
1353
|
.sort();
|
|
1294
1354
|
|
|
1295
|
-
const blocking =
|
|
1296
|
-
|
|
1355
|
+
const blocking =
|
|
1356
|
+
high_confirmed +
|
|
1357
|
+
high_open +
|
|
1358
|
+
reviewer_high +
|
|
1359
|
+
(manifest.gates?.filter((g) => g.outcome === "failed").length ?? 0);
|
|
1360
|
+
const anyIncomplete =
|
|
1361
|
+
(critique?.some(
|
|
1362
|
+
(row) =>
|
|
1363
|
+
row.outcome === "incomplete" ||
|
|
1364
|
+
(row.outcome === "skipped" && machine_outcome !== "skipped"),
|
|
1365
|
+
) ??
|
|
1366
|
+
false) ||
|
|
1367
|
+
manifest.contexts?.some((c) => c.capture_incomplete || c.coverage.capped) ||
|
|
1368
|
+
false;
|
|
1297
1369
|
const reviewed_outcome: PageReviewVerdict["reviewed_outcome"] =
|
|
1298
1370
|
blocking > 0
|
|
1299
1371
|
? "fail"
|
|
@@ -1359,6 +1431,7 @@ export function findingsSchemaDocument(): Record<string, unknown> {
|
|
|
1359
1431
|
additionalProperties: false,
|
|
1360
1432
|
required: ["schema", "target", "reviewer", "reviewed_at", "delegated_reviews", "findings"],
|
|
1361
1433
|
properties: {
|
|
1434
|
+
machine_evidence_digest: { type: ["string", "null"], pattern: "^[a-f0-9]{64}$" },
|
|
1362
1435
|
schema: { const: PAGE_REVIEW_FINDINGS_SCHEMA },
|
|
1363
1436
|
schema_path: { type: "string" },
|
|
1364
1437
|
target: { type: "string" },
|
|
@@ -1495,11 +1568,12 @@ function str(value: unknown, fallback = "?"): string {
|
|
|
1495
1568
|
*/
|
|
1496
1569
|
export function gateHitsFromEnvelope(
|
|
1497
1570
|
envelope: Record<string, unknown> | undefined,
|
|
1571
|
+
maxHits = GATE_HITS_PER_ENVELOPE,
|
|
1498
1572
|
): PageReviewGateHit[] {
|
|
1499
1573
|
if (!envelope) return [];
|
|
1500
1574
|
const hits: PageReviewGateHit[] = [];
|
|
1501
1575
|
const push = (rule: string, label: string, rect: PageReviewRect | undefined): void => {
|
|
1502
|
-
if (rect && hits.length <
|
|
1576
|
+
if (rect && hits.length < maxHits) hits.push({ rule, label, rect });
|
|
1503
1577
|
};
|
|
1504
1578
|
const runts = envelope.runts as Record<string, unknown> | undefined;
|
|
1505
1579
|
for (const hit of asArray(runts?.runts)) {
|
|
@@ -1663,8 +1737,7 @@ export function buildInspectionPlan(
|
|
|
1663
1737
|
add(last.id, "page bottom: footer and final call to action");
|
|
1664
1738
|
for (const tile of ctx.tiles) {
|
|
1665
1739
|
if (tile.scope !== undefined) add(tile.id, `scoped tile for ${tile.scope}`);
|
|
1666
|
-
if (tile.kind === "hit-band")
|
|
1667
|
-
add(tile.id, "hit band: captured past the tile cap for a gate hit");
|
|
1740
|
+
if (tile.kind === "hit-band") add(tile.id, "hit band: selected for a gate hit");
|
|
1668
1741
|
}
|
|
1669
1742
|
const row = critique?.find((entry) => entry.context_id === ctx.id);
|
|
1670
1743
|
for (const finding of row?.findings ?? []) {
|
|
@@ -1710,9 +1783,9 @@ function walkFiles(root: string, dir: string, out: Array<{ path: string; bytes:
|
|
|
1710
1783
|
|
|
1711
1784
|
/**
|
|
1712
1785
|
* Write the pack's navigation layer: manifest, evidence files, the findings
|
|
1713
|
-
* skeleton and schema
|
|
1714
|
-
*
|
|
1715
|
-
*
|
|
1786
|
+
* skeleton and schema, and `review.md`. An unchanged judge result preserves
|
|
1787
|
+
* the review; changed machine evidence archives it before starting a fresh
|
|
1788
|
+
* review, so a previous finding position cannot acquire a new meaning.
|
|
1716
1789
|
*/
|
|
1717
1790
|
export function finalizePageReviewPack(input: FinalizePageReviewPackInput): {
|
|
1718
1791
|
manifest: string;
|
|
@@ -1726,6 +1799,52 @@ export function finalizePageReviewPack(input: FinalizePageReviewPackInput): {
|
|
|
1726
1799
|
const gates = input.gates ?? [];
|
|
1727
1800
|
const notChecked = [...(input.not_checked ?? [])];
|
|
1728
1801
|
const warnings = [...(input.warnings ?? [])];
|
|
1802
|
+
const reviewDigest = critique === null ? null : machineEvidenceDigest(input.target, critique);
|
|
1803
|
+
const freshFindings = (): PageReviewFindingsDocument => ({
|
|
1804
|
+
schema: PAGE_REVIEW_FINDINGS_SCHEMA,
|
|
1805
|
+
schema_path: PAGE_REVIEW_FINDINGS_SCHEMA_FILENAME,
|
|
1806
|
+
target: input.target,
|
|
1807
|
+
machine_evidence_digest: reviewDigest,
|
|
1808
|
+
reviewer: null,
|
|
1809
|
+
reviewed_at: null,
|
|
1810
|
+
delegated_reviews: [],
|
|
1811
|
+
findings: [],
|
|
1812
|
+
dispositions: [],
|
|
1813
|
+
});
|
|
1814
|
+
|
|
1815
|
+
// Preserve the old machine evidence before any navigation files are replaced.
|
|
1816
|
+
// Archive failure aborts finalization without clearing the current review.
|
|
1817
|
+
if (critique !== null && existsSync(paths.findings)) {
|
|
1818
|
+
const previous = readPackFindings(input.packDir);
|
|
1819
|
+
if (previous.machine_evidence_digest !== reviewDigest) {
|
|
1820
|
+
if (
|
|
1821
|
+
previous.machine_evidence_digest ||
|
|
1822
|
+
previous.reviewer ||
|
|
1823
|
+
previous.reviewed_at ||
|
|
1824
|
+
previous.delegated_reviews.length ||
|
|
1825
|
+
previous.findings.length ||
|
|
1826
|
+
previous.dispositions?.length
|
|
1827
|
+
) {
|
|
1828
|
+
const historyRoot = join(input.packDir, "review-history");
|
|
1829
|
+
mkdirSync(historyRoot, { recursive: true });
|
|
1830
|
+
const historyDir = mkdtempSync(join(historyRoot, "review-"));
|
|
1831
|
+
for (const source of [
|
|
1832
|
+
paths.findings,
|
|
1833
|
+
paths.critique,
|
|
1834
|
+
paths.inspectionPlan,
|
|
1835
|
+
paths.verdict,
|
|
1836
|
+
paths.manifest,
|
|
1837
|
+
]) {
|
|
1838
|
+
if (existsSync(source)) copyFileSync(source, join(historyDir, basename(source)));
|
|
1839
|
+
}
|
|
1840
|
+
warnings.push(
|
|
1841
|
+
`Changed machine evidence requires a new review. Previous review archived at ${rel(historyDir)}.`,
|
|
1842
|
+
);
|
|
1843
|
+
}
|
|
1844
|
+
rmSync(paths.verdict, { force: true });
|
|
1845
|
+
writePackFindings(input.packDir, freshFindings());
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1729
1848
|
|
|
1730
1849
|
if (critique === null) {
|
|
1731
1850
|
notChecked.push({
|
|
@@ -1750,7 +1869,7 @@ export function finalizePageReviewPack(input: FinalizePageReviewPackInput): {
|
|
|
1750
1869
|
}
|
|
1751
1870
|
if (ctx.hit_bands !== undefined && ctx.hit_bands > 0) {
|
|
1752
1871
|
warnings.push(
|
|
1753
|
-
`${ctx.id}: ${ctx.hit_bands} band(s)
|
|
1872
|
+
`${ctx.id}: ${ctx.hit_bands} band(s) were selected because gate hits land in them.`,
|
|
1754
1873
|
);
|
|
1755
1874
|
}
|
|
1756
1875
|
}
|
|
@@ -1800,7 +1919,7 @@ export function finalizePageReviewPack(input: FinalizePageReviewPackInput): {
|
|
|
1800
1919
|
});
|
|
1801
1920
|
if (critique !== null) {
|
|
1802
1921
|
writeJson(paths.critique, {
|
|
1803
|
-
schema:
|
|
1922
|
+
schema: PAGE_REVIEW_CRITIQUE_SCHEMA,
|
|
1804
1923
|
...(input.pool ? { pool: input.pool } : {}),
|
|
1805
1924
|
contexts: critique,
|
|
1806
1925
|
});
|
|
@@ -1808,16 +1927,7 @@ export function finalizePageReviewPack(input: FinalizePageReviewPackInput): {
|
|
|
1808
1927
|
|
|
1809
1928
|
writeJson(paths.findingsSchema, findingsSchemaDocument());
|
|
1810
1929
|
if (!existsSync(paths.findings)) {
|
|
1811
|
-
writeJson(paths.findings,
|
|
1812
|
-
schema: PAGE_REVIEW_FINDINGS_SCHEMA,
|
|
1813
|
-
schema_path: PAGE_REVIEW_FINDINGS_SCHEMA_FILENAME,
|
|
1814
|
-
target: input.target,
|
|
1815
|
-
reviewer: null,
|
|
1816
|
-
reviewed_at: null,
|
|
1817
|
-
delegated_reviews: [],
|
|
1818
|
-
findings: [],
|
|
1819
|
-
dispositions: [],
|
|
1820
|
-
});
|
|
1930
|
+
writeJson(paths.findings, freshFindings());
|
|
1821
1931
|
}
|
|
1822
1932
|
|
|
1823
1933
|
// Retention: what the caller says, else what the manifest already carries.
|
|
@@ -1880,9 +1990,8 @@ export function finalizePageReviewPack(input: FinalizePageReviewPackInput): {
|
|
|
1880
1990
|
),
|
|
1881
1991
|
},
|
|
1882
1992
|
});
|
|
1883
|
-
// A verdict
|
|
1884
|
-
//
|
|
1885
|
-
// when the verdict predates the current critique.
|
|
1993
|
+
// A verdict survives an unchanged refinalize. Changed machine evidence
|
|
1994
|
+
// archives and removes it above, before starting the next review.
|
|
1886
1995
|
let verdict: PageReviewVerdictDocument | undefined;
|
|
1887
1996
|
try {
|
|
1888
1997
|
verdict = readPackVerdict(input.packDir);
|
|
@@ -1930,7 +2039,7 @@ export function renderReviewMarkdown(
|
|
|
1930
2039
|
lines.push("## Delegated review protocol");
|
|
1931
2040
|
lines.push("");
|
|
1932
2041
|
lines.push(
|
|
1933
|
-
"1. The coordinating agent reads the context table and coverage below. A
|
|
2042
|
+
"1. The coordinating agent reads the context table and coverage below. A budgeted context can have unreviewed gaps between selected tiles; use its recorded coverage intervals.",
|
|
1934
2043
|
);
|
|
1935
2044
|
lines.push(
|
|
1936
2045
|
"2. Read `evidence/inspection-plan.json`, then dispatch review subagents with disjoint assignments covering every `primary_tiles` entry. Choose the first model available in this exact order: GPT-5.6 Luna (`gpt-5.6-luna` where an id is required), Composer 2.5, Haiku 4.5. Use that model for every tile-review subagent; do not substitute another model. If none exists, the review is incomplete. Each tile must be opened at native pixels by at least one completed subagent. The coordinating agent must not open tile images itself.",
|
|
@@ -2155,6 +2264,14 @@ export interface PageReviewExpiredStub {
|
|
|
2155
2264
|
contexts: number;
|
|
2156
2265
|
/** Aggregate of the manifest's critique outcomes; null when no judge ran. */
|
|
2157
2266
|
machine_outcome: PageReviewCritiqueRecord["outcome"] | null;
|
|
2267
|
+
result: PageReviewVerdictDocument | null;
|
|
2268
|
+
findings_summary: {
|
|
2269
|
+
machine_high: number;
|
|
2270
|
+
machine_medium: number;
|
|
2271
|
+
machine_low: number;
|
|
2272
|
+
reviewer_findings: number;
|
|
2273
|
+
dispositions: number;
|
|
2274
|
+
} | null;
|
|
2158
2275
|
}
|
|
2159
2276
|
|
|
2160
2277
|
/** One pack as seen by the expiry sweep. `expires_at`, `size_bytes`, and
|
|
@@ -2295,6 +2412,25 @@ export function aggregateMachineOutcome(
|
|
|
2295
2412
|
* `retention.expires_at` in the past is eligible, and an unmanaged pack
|
|
2296
2413
|
* (explicit `--out`) only when `includeUnmanaged` is set.
|
|
2297
2414
|
*/
|
|
2415
|
+
function expiredFindingsSummary(
|
|
2416
|
+
dir: string,
|
|
2417
|
+
manifest: PageReviewPackManifest,
|
|
2418
|
+
): PageReviewExpiredStub["findings_summary"] {
|
|
2419
|
+
try {
|
|
2420
|
+
const findings = readPackFindings(dir);
|
|
2421
|
+
const machine = manifest.critique?.flatMap((row) => row.findings) ?? [];
|
|
2422
|
+
return {
|
|
2423
|
+
machine_high: machine.filter((f) => f.severity === "high").length,
|
|
2424
|
+
machine_medium: machine.filter((f) => f.severity === "medium").length,
|
|
2425
|
+
machine_low: machine.filter((f) => f.severity === "low").length,
|
|
2426
|
+
reviewer_findings: findings.findings.length,
|
|
2427
|
+
dispositions: findings.dispositions?.length ?? 0,
|
|
2428
|
+
};
|
|
2429
|
+
} catch {
|
|
2430
|
+
return null;
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2433
|
+
|
|
2298
2434
|
export function deleteExpiredPacks(input: DeleteExpiredPacksInput): DeleteExpiredPacksResult {
|
|
2299
2435
|
const now = input.now ?? new Date();
|
|
2300
2436
|
const nowMs = now.getTime();
|
|
@@ -2329,6 +2465,8 @@ export function deleteExpiredPacks(input: DeleteExpiredPacksInput): DeleteExpire
|
|
|
2329
2465
|
deleted_at: now.toISOString(),
|
|
2330
2466
|
contexts: Array.isArray(manifest.contexts) ? manifest.contexts.length : 0,
|
|
2331
2467
|
machine_outcome: aggregateMachineOutcome(manifest.critique),
|
|
2468
|
+
result: readPackVerdict(dir) ?? null,
|
|
2469
|
+
findings_summary: expiredFindingsSummary(dir, manifest),
|
|
2332
2470
|
};
|
|
2333
2471
|
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
2334
2472
|
if (entry.name === ARTIFACT_WORKSPACE_MANIFEST_FILENAME) continue;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import type { PageReviewSourceEvidence } from "./page-review-contracts.ts";
|
|
5
|
+
|
|
6
|
+
/** The caller supplies the already-canonicalized DOM. Keep every identity input
|
|
7
|
+
* and its order: this receipt diagnoses differences without accepting them. */
|
|
8
|
+
export function retainPageReviewSourceIdentity(
|
|
9
|
+
input: { nodes: unknown[]; stylesheets: unknown[]; dom: string },
|
|
10
|
+
artifactPrefix?: string,
|
|
11
|
+
): { digest: string; evidence?: PageReviewSourceEvidence } {
|
|
12
|
+
const bytes = Buffer.from(
|
|
13
|
+
JSON.stringify({ nodes: input.nodes, stylesheets: input.stylesheets, dom: input.dom }),
|
|
14
|
+
"utf8",
|
|
15
|
+
);
|
|
16
|
+
const digest = createHash("sha256").update(bytes).digest("hex");
|
|
17
|
+
if (!artifactPrefix) return { digest };
|
|
18
|
+
|
|
19
|
+
// Raw source can contain session metadata. Keep it out of stdout and create
|
|
20
|
+
// a private directory per attempt, preserving both sides of a failed retry.
|
|
21
|
+
const prefix = resolve(artifactPrefix);
|
|
22
|
+
mkdirSync(dirname(prefix), { recursive: true, mode: 0o700 });
|
|
23
|
+
const dir = mkdtempSync(`${prefix}.source-identity-`);
|
|
24
|
+
const path = join(dir, `${digest}.json`);
|
|
25
|
+
writeFileSync(path, bytes, { mode: 0o600, flag: "wx" });
|
|
26
|
+
return {
|
|
27
|
+
digest,
|
|
28
|
+
evidence: { path, sha256: digest, bytes: bytes.length, encoding: "utf8" },
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -83,11 +83,13 @@ export interface QaRunPolicy {
|
|
|
83
83
|
* recorded, and the result finalizes as incomplete — releasing the
|
|
84
84
|
* admission slot instead of holding it open-endedly. */
|
|
85
85
|
run_deadline_ms?: number;
|
|
86
|
-
/**
|
|
87
|
-
*
|
|
88
|
-
* per-tile cost is accepted; each critique row's `coverage` records what the
|
|
89
|
-
* run actually saw, which is why this knob can stay out of the job digest. */
|
|
86
|
+
/** Explicit per-context ceiling. When absent, a pack context may use the
|
|
87
|
+
* whole shared budget; independent browse commands keep their own default. */
|
|
90
88
|
critique_max_tiles?: number;
|
|
89
|
+
/** Maximum native tiles across the complete pack (8-400, default 96). */
|
|
90
|
+
critique_tile_budget?: number;
|
|
91
|
+
/** Apply exact-match, fully reviewed prior dispositions. Default false. */
|
|
92
|
+
accept_dispositions?: boolean;
|
|
91
93
|
/** Vision calls in flight during the judge stage, across every context of
|
|
92
94
|
* the run (1 to 16). Default: the host provider's own concurrency. The
|
|
93
95
|
* capture stage closes every browser before judging starts, so this knob
|
|
@@ -154,6 +156,11 @@ export interface QaRunCritiqueCoverage {
|
|
|
154
156
|
bands_total: number;
|
|
155
157
|
bands_reviewed: number;
|
|
156
158
|
capped: boolean;
|
|
159
|
+
selected_rectangles?: Array<{ x: number; y: number; width: number; height: number }>;
|
|
160
|
+
reviewed_intervals?: Array<{ start: number; end: number }>;
|
|
161
|
+
uncovered_intervals?: Array<{ start: number; end: number }>;
|
|
162
|
+
omitted_gate_hits?: string[];
|
|
163
|
+
omitted_scopes?: string[];
|
|
157
164
|
}
|
|
158
165
|
|
|
159
166
|
export interface QaRunCritiqueOutcome {
|
|
@@ -164,6 +171,8 @@ export interface QaRunCritiqueOutcome {
|
|
|
164
171
|
tiles_reused: number;
|
|
165
172
|
outcome: "passed" | "failed" | "unknown";
|
|
166
173
|
findings: Array<{ severity: string; summary: string; selector?: string; tile?: string }>;
|
|
174
|
+
/** Applied reviewer decisions; the machine findings above remain unchanged. */
|
|
175
|
+
dismissed?: Array<{ finding_key: string; target: string; disposition: string }>;
|
|
167
176
|
/** Tile coverage of the page. Absent when the capture died or reported none. */
|
|
168
177
|
coverage?: QaRunCritiqueCoverage;
|
|
169
178
|
}
|
|
@@ -490,6 +499,18 @@ export function validateQaRunJob(value: unknown): QaRunJobValidation {
|
|
|
490
499
|
|
|
491
500
|
if (job.policy !== undefined) {
|
|
492
501
|
const p = job.policy as Record<string, unknown>;
|
|
502
|
+
if (p === null || typeof p !== "object" || Array.isArray(p)) {
|
|
503
|
+
errors.push("policy must be an object");
|
|
504
|
+
}
|
|
505
|
+
if (p?.accept_dispositions !== undefined && typeof p.accept_dispositions !== "boolean") {
|
|
506
|
+
errors.push("policy.accept_dispositions must be a boolean");
|
|
507
|
+
}
|
|
508
|
+
if (p?.critique_tile_budget !== undefined) {
|
|
509
|
+
const n = p.critique_tile_budget;
|
|
510
|
+
if (typeof n !== "number" || !Number.isInteger(n) || n < 8 || n > 400) {
|
|
511
|
+
errors.push("policy.critique_tile_budget must be an integer between 8 and 400");
|
|
512
|
+
}
|
|
513
|
+
}
|
|
493
514
|
if (p?.command_concurrency !== undefined) {
|
|
494
515
|
const n = p.command_concurrency;
|
|
495
516
|
if (typeof n !== "number" || !Number.isInteger(n) || n < 1 || n > 8) {
|
|
@@ -510,8 +531,8 @@ export function validateQaRunJob(value: unknown): QaRunJobValidation {
|
|
|
510
531
|
}
|
|
511
532
|
if (p?.critique_max_tiles !== undefined) {
|
|
512
533
|
const n = p.critique_max_tiles;
|
|
513
|
-
if (typeof n !== "number" || !Number.isInteger(n) || n < 1 || n >
|
|
514
|
-
errors.push("policy.critique_max_tiles must be an integer between 1 and
|
|
534
|
+
if (typeof n !== "number" || !Number.isInteger(n) || n < 1 || n > 400) {
|
|
535
|
+
errors.push("policy.critique_max_tiles must be an integer between 1 and 400");
|
|
515
536
|
}
|
|
516
537
|
}
|
|
517
538
|
if (p?.critique_pool !== undefined) {
|
|
@@ -545,7 +566,10 @@ export function contextIdFor(context: QaContext): string {
|
|
|
545
566
|
/**
|
|
546
567
|
* Merge planner-manifest coverage with the job's extra contexts. The manifest
|
|
547
568
|
* is the floor: its contexts always run, in manifest order, and a job can only
|
|
548
|
-
* append
|
|
569
|
+
* append coverage or supply setup arguments for an exactly matching context.
|
|
570
|
+
* IDs and rendering identities must agree; aliases are rejected rather than
|
|
571
|
+
* silently losing a setup or leaving a check pointed at an absent context.
|
|
572
|
+
* The union construction makes narrowing impossible by design; the
|
|
549
573
|
* returned list is what the runner executes and what the result reports.
|
|
550
574
|
*/
|
|
551
575
|
export function mergeCoverage(manifest: QaManifest, job: QaRunJob): QaRunContext[] {
|
|
@@ -555,13 +579,30 @@ export function mergeCoverage(manifest: QaManifest, job: QaRunJob): QaRunContext
|
|
|
555
579
|
theme: context.theme,
|
|
556
580
|
state: context.state,
|
|
557
581
|
}));
|
|
558
|
-
const
|
|
582
|
+
const identity = (context: QaRunContext) =>
|
|
583
|
+
JSON.stringify([context.viewport, context.theme, context.state]);
|
|
584
|
+
const byId = new Map(merged.map((context) => [context.id, context]));
|
|
585
|
+
const byRender = new Map(merged.map((context) => [identity(context), context]));
|
|
586
|
+
const jobIds = new Set<string>();
|
|
559
587
|
for (const context of job.contexts ?? []) {
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
588
|
+
if (jobIds.has(context.id)) throw new Error(`Duplicate QA context ID: ${context.id}`);
|
|
589
|
+
jobIds.add(context.id);
|
|
590
|
+
const existingId = byId.get(context.id);
|
|
591
|
+
const existingRender = byRender.get(identity(context));
|
|
592
|
+
if (existingId || existingRender) {
|
|
593
|
+
if (!existingId || existingId !== existingRender) {
|
|
594
|
+
throw new Error(`Conflicting QA context ID or rendering identity: ${context.id}`);
|
|
595
|
+
}
|
|
596
|
+
if (context.args !== undefined) existingId.args = [...context.args];
|
|
597
|
+
continue;
|
|
598
|
+
}
|
|
599
|
+
const added = {
|
|
600
|
+
...context,
|
|
601
|
+
...(context.args !== undefined ? { args: [...context.args] } : {}),
|
|
602
|
+
};
|
|
603
|
+
byId.set(added.id, added);
|
|
604
|
+
byRender.set(identity(added), added);
|
|
605
|
+
merged.push(added);
|
|
565
606
|
}
|
|
566
607
|
return merged;
|
|
567
608
|
}
|
|
@@ -599,12 +640,15 @@ function canonicalize(value: unknown): unknown {
|
|
|
599
640
|
}
|
|
600
641
|
|
|
601
642
|
/** SHA-256 hex digest over the effective validated job (after the CLI merges
|
|
602
|
-
* its authoritative `target` and `mode`).
|
|
603
|
-
*
|
|
604
|
-
*
|
|
605
|
-
* them would make the same job file verify differently across invocations. */
|
|
643
|
+
* its authoritative `target` and `mode`). Runtime policy knobs are excluded.
|
|
644
|
+
* Disposition acceptance changes what can establish a pass, so enabling it
|
|
645
|
+
* is identity-bearing; an ordinary run cannot verify as an accepting run. */
|
|
606
646
|
export function computeJobDigest(job: QaRunJob): string {
|
|
607
|
-
const { policy
|
|
647
|
+
const { policy, ...rest } = job;
|
|
648
|
+
const identityBearing =
|
|
649
|
+
policy?.accept_dispositions === true
|
|
650
|
+
? { ...rest, policy: { accept_dispositions: true } }
|
|
651
|
+
: rest;
|
|
608
652
|
return createHash("sha256")
|
|
609
653
|
.update(JSON.stringify(canonicalize(identityBearing)))
|
|
610
654
|
.digest("hex");
|