# dsh-mask bundle patch: mount the mask plugin.
#
# Every key below is a Config field (Schemastery schema); invalid values fail
# the profile load loudly. See README.md "Configuration" for the full table.
#
# This patch inserts only the `mask` row. It deliberately does NOT insert the
# storage stack (`storage` / `storage-json` / `storage-domain`): a profile that
# already composes it — the `web` profile does, via @deepseek-ai/dsh-web-app —
# would collide on the duplicate loader entry id `storage` and refuse to boot
# (issue #2). The plugin treats `storageDomain` as an optional service: when
# the profile provides it, the restore table persists to the `dsh_mask` domain;
# on a profile without storage, the restore table is memory-only and
# `persistRestoreTable: true` is a no-op with a one-time warning.
- insert:
    - id: mask
      name: dsh-mask
      config:
        # Master switch; false unregisters the pre-step listener, the /mask
        # command, and the mask_test tool.
        enabled: true
        # Detection mode. Only 'regex' is implemented (zero-dependency pure
        # regex). 'regex+ner' is reserved for an external name/address
        # recognizer and fails loud at load until one is wired in.
        mode: regex
        # Which entity types to mask. 'phone', 'email', 'id-card',
        # 'bank-card', and 'key' are regex-capable; 'ip' is regex-capable but
        # opt-in (frequent false positives in technical context); 'person' and
        # 'address' require NER (mode: regex+ner) and fail loud here.
        entities:
          - phone
          - email
          - id-card
          - bank-card
          - key
        # Masking surface(s). 'messages' masks agent/pre-step messages before
        # they enter the model; 'tools' masks tool-result text on
        # tools/post-execute before it is logged and fed back to the model
        # (tool-argument rewriting is blocked upstream — see ARCHITECTURE.md).
        # Accepts a single string or an array: [messages, tools].
        scope:
          - messages
        # Surface switches: register the /mask command and the mask_test tool.
        registerCommand: true
        registerTools: true
        # Persist the placeholder->original restore table to the controlled
        # dsh_mask storage domain (false = memory only, lost on restart). A
        # no-op with a one-time warning when the profile composes no storage
        # stack (storageDomain service absent).
        persistRestoreTable: true
        # Per-session cap on restore entries; the oldest placeholders are
        # evicted first (their responses can no longer be restored).
        maxRestoreEntriesPerSession: 500
        # Cap on in-memory session strippers; least-recently-used sessions are
        # evicted (mapping reloads from the storage domain on demand).
        maxSessions: 1000
        # Browser half "reveal" bubble (defensive, feature-flagged). The host
        # restore surface (/mask restore + the in-memory/domain restore table)
        # already backs it; the client slot ships separately and is off by
        # default until the live slot catalog verifies the target slot.
        maskClientEnabled: false
        # Where /mask restore delivers the restored plaintext. The host writes a
        # command's result (command/done.text) into the session log verbatim,
        # and the client renders that same event, so the plaintext can never
        # live in the result text: it goes to one controlled file per session
        # (mode 0600, overwritten by the next restore) and the result carries
        # the path plus the placeholder names only. Empty = os.tmpdir()/dsh-mask-restore.
        restoreDir: ''
