The tree was a debranded snapshot of upstream taken around 2026-02-20, and upstream has moved about five months since. This replaces it with upstream master, mechanically re-derived: bin/debrand does the rename, and the pipeline and squashed migrations are restored from the shipped tree. Not a merge. The previous import replaced the tree wholesale in a single commit, so there is no shared content history to merge against. Reproducing the rename on the new tree is what actually converges. What survives: .hanzo/workflows, the Dockerfiles and bin/docker* entrypoints, compose files, LLM.md, and insights/migrations. The squashed initial migration is load-bearing — the live database records it by name, so upstream's ~918 individual migrations cannot come back. Nine products are dropped as unreferenced. Twenty-one more that we do not run are kept anyway because the core app still imports them; unpicking those is a refactor and does not belong in a rebase. This base does not build yet, and the reasons are not in the rename: upstream now wants Python 3.13.13 against our 3.12.12 and Django 5.2 against our 4.2, and the dependency manifests still name upstream packages that we republish under our own names. Those are decisions, not renames.
2.0 KiB
insights-replay-anonymizer
PII scrubber for rrweb session-replay events: text/URL redaction, native image blur, and canvas/cv neutralization. This is the scrub core behind Insights's ml-mirror pipelines.
Consumers:
replay-anonymizer-node(this workspace) wraps it as a Neon addon for the Node ingestion workers.- The MLHog training pipelines consume it from crates.io.
Behavior is pinned by the JSON fixtures under tests/fixtures/, shared with the Node addon's Jest suite. Scrubbing operates on untrusted input; the public entry points contain panics and convert them to errors, so callers fail closed (drop the message) without their own catch_unwind. Under panic = "abort" that backstop cannot run — builds that must fail closed need panic = "unwind".
Offline consumers (JSONL of individual events rather than Kafka messages) get a dedicated surface: anonymize_line scrubs one line (bare event or ["window_id", event] tuple), AllowLists::from_json_bytes loads the shipped { text, url } document and AllowLists::default() embeds the production default lists, and the default typed-parse feature adds parse_scrubbed_event — scrub-then-parse to a typed rrweb AST (envelope, DOM tree, mutations, interactions) with cv payloads transparently decompressed. The Node addon builds with default-features = false, so none of the typed surface exists on the ingestion hot path.
Releasing
Publishing to crates.io is automated by .github/workflows/publish-replay-anonymizer-crate.yml: bump version in Cargo.toml in your PR, and on merge to master the workflow publishes any version not yet on crates.io via trusted publishing (GitHub OIDC, no long-lived token). No tags involved.
If crate code changes in a PR without a version bump, CI posts a reminder comment on the PR. If un-bumped crate changes still reach master (say the version got taken by a PR that merged first), the publish run fails loudly instead of silently skipping; bump the version in a follow-up PR.