feat(policies): config refinements + new-version output (post-#6598) (#6604)

Follow-up to #6598 (squash-merged into `SaaS`). These are the policy
refinements made after that merge, against the current `SaaS` tip.

## Changes
- **Simplify Security config + plain-language info buttons** — Redact
config reduced to the PII field; Sanitise has no config
(JavaScript-removal only) with a non-technical info button; per-tool
info buttons reworded to match the tool-steps style.
- **Hide 'Flatten PDF pages to images' from the watermark policy
config** — new `PolicyWatermarkConfig` wrapping the watermark settings
with the flatten checkbox gated off.
- **Flatten-to-image on by default for redact + watermark** — both
normalise `convertPDFToImage: true` on mount.
- **Self-heal a stale backing folder** — `ensurePolicyFolder` recreates
a backing folder whose `folderId` no longer resolves (preferring the
backend's stored automation), instead of hanging Edit Settings on a
permanent "Loading…".
- **Version the input file on 'new version' output mode** — completed
runs whose policy output mode is `new_version` replace the input file
with a versioned child (origin tool `automate`) rather than adding a
separate file; falls back to a new file if the input is gone.
`outputMode` is plumbed through `PolicyState`, the local-cache default,
and backend reconciliation.

## Verification
- `typecheck:proprietary` + `typecheck:core` clean
- policy + hooks vitest: 17 passing
- eslint + prettier clean on all changed files
This commit is contained in:
Reece Browne
2026-06-11 14:45:22 +01:00
committed by GitHub
parent 68e031ac55
commit 11ab762f57
16 changed files with 373 additions and 250 deletions
@@ -2,10 +2,22 @@ import { Suspense } from "react";
import { Loader } from "@mantine/core";
import { ToggleSwitch } from "@shared/components/ToggleSwitch";
import { Card } from "@shared/components/Card";
import LocalIcon from "@app/components/shared/LocalIcon";
import { Tooltip as AppTooltip } from "@app/components/shared/Tooltip";
import { PolicyRedactConfig } from "@app/components/policies/PolicyRedactConfig";
import { PolicyWatermarkConfig } from "@app/components/policies/PolicyWatermarkConfig";
import type { ToolRegistry } from "@app/data/toolsTaxonomy";
import type { ToolId } from "@app/types/toolId";
/** Plain-language, non-technical descriptions shown by each tool's info button. */
const TOOL_PLAIN_INFO: Record<string, string> = {
redact:
"Automatically finds and blacks out sensitive details — like Social Security and card numbers — so they can't be read in the document.",
sanitize:
"Removes hidden JavaScript from the file, so nothing can run automatically when someone opens it.",
watermark: "Stamps a visible mark (e.g. “Confidential”) across every page.",
};
/** One tool in a policy's fixed chain: whether it runs + its configured params. */
export interface PolicyToolState {
/** Frontend tool-registry id (also the registry key + the thing we map to an endpoint). */
@@ -54,6 +66,26 @@ export function PolicyToolConfig({
<span className="pol-tool-name">
{entry?.name ?? tool.operation}
</span>
{TOOL_PLAIN_INFO[tool.operation] && (
<AppTooltip
content={TOOL_PLAIN_INFO[tool.operation]}
sidebarTooltip
pinOnClick
>
<button
type="button"
className="pol-info-btn"
aria-label={`What does ${entry?.name ?? tool.operation} do?`}
>
<LocalIcon
icon="info-outline-rounded"
width="1rem"
height="1rem"
style={{ color: "var(--icon-files-color)" }}
/>
</button>
</AppTooltip>
)}
<ToggleSwitch
size="sm"
checked={tool.enabled}
@@ -64,8 +96,7 @@ export function PolicyToolConfig({
</div>
{tool.enabled &&
(tool.operation === "redact" ? (
// Redact has a bespoke config: PII preset dropdown + a custom
// word/regex field + advanced options, mode + regex locked on.
// Redact config is reduced to just the PII type picker.
<div className="pol-tool-body">
<PolicyRedactConfig
parameters={tool.parameters}
@@ -73,6 +104,20 @@ export function PolicyToolConfig({
disabled={!editable}
/>
</div>
) : tool.operation === "sanitize" ? (
// Sanitize is config-less: it only removes JavaScript (params
// are fixed in the policy preset), so no settings are shown.
<></>
) : tool.operation === "watermark" ? (
// Watermark: full settings minus the "Flatten PDF pages to
// images" toggle (hidden), with flatten forced on.
<div className="pol-tool-body">
<PolicyWatermarkConfig
parameters={tool.parameters}
onChange={(parameters) => patchTool(index, { parameters })}
disabled={!editable}
/>
</div>
) : Settings ? (
<div className="pol-tool-body">
<Suspense fallback={<Loader size="sm" />}>