Sync-writer redaction guard (the re-introduction keystone)
Sibling of #13772. The content mirrors are SyncService output; scrubbing them (or re-syncing) is necessary but not sufficient — without redaction at the WRITE choke-point, the next sync re-introduces any sensitive term from the live source (especially external-handle activity-logs that cannot be edited out of GitHub). This wires the shared redactSensitiveContent (#13773) into the syncers so re-sync redacts on write.
Design (decided — Tier-2, reversible)
- The body-projection seam is
#projectAuthoredNode(node, contentTrust, field) (per-syncer; sanitizes untrusted-author content via the existing contentTrust). contentTrust is author-TRUST-based → it does NOT redact the partner name (which must go regardless of author trust). So the redaction is a SEPARATE, trust-independent step applied to all projected content.
- A shared helper (DRY, one choke-point) applies
redactSensitiveContent(body, aiConfig.redaction?.denyPairs ?? []) at the body-projection in all three syncers (Issue / PR / Discussion), after the contentTrust step.
- Deny-pairs from
aiConfig.redaction.denyPairs: config.template.mjs default [] (name-clean); the gitignored config.mjs carries the pairs (operator exception: partner name in, real external handles out). Read the resolved leaf at the use-site (ADR-0019).
Acceptance Criteria
Sibling of #13772. Builds on #13773 (redactSensitiveContent). Part of the gate-9 remediation (surface E).
Sync-writer redaction guard (the re-introduction keystone)
Sibling of #13772. The content mirrors are SyncService output; scrubbing them (or re-syncing) is necessary but not sufficient — without redaction at the WRITE choke-point, the next sync re-introduces any sensitive term from the live source (especially external-handle activity-logs that cannot be edited out of GitHub). This wires the shared
redactSensitiveContent(#13773) into the syncers so re-sync redacts on write.Design (decided — Tier-2, reversible)
#projectAuthoredNode(node, contentTrust, field)(per-syncer; sanitizes untrusted-author content via the existingcontentTrust). contentTrust is author-TRUST-based → it does NOT redact the partner name (which must go regardless of author trust). So the redaction is a SEPARATE, trust-independent step applied to all projected content.redactSensitiveContent(body, aiConfig.redaction?.denyPairs ?? [])at the body-projection in all three syncers (Issue / PR / Discussion), after the contentTrust step.aiConfig.redaction.denyPairs:config.template.mjsdefault[](name-clean); the gitignoredconfig.mjscarries the pairs (operator exception: partner name in, real external handles out). Read the resolved leaf at the use-site (ADR-0019).Acceptance Criteria
aiConfig.redaction.denyPairsconfig-seam (template default[]); read at the use-site.Sibling of #13772. Builds on #13773 (
redactSensitiveContent). Part of the gate-9 remediation (surface E).