Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/anonymizer/engine/detection/postprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ def resolve_overlaps(entities: list[EntitySpan]) -> list[EntitySpan]:
-(item.end_position - item.start_position),
item.start_position,
item.end_position,
-item.score,
Comment thread
asteier2026 marked this conversation as resolved.
item.label,
),
)
Expand Down
9 changes: 9 additions & 0 deletions tests/engine/test_detection_postprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,15 @@ def test_resolve_overlaps_empty_input() -> None:
assert resolve_overlaps([]) == []


def test_resolve_overlaps_same_span_keeps_highest_score() -> None:
"""When multiple labels share the exact same span, the highest-scoring label wins."""
last_name = EntitySpan("last_name_120_123", "Mum", "last_name", 120, 123, 0.719, "detector")
relationship = EntitySpan("relationship_120_123", "Mum", "relationship", 120, 123, 0.941, "detector")
resolved = resolve_overlaps([last_name, relationship])
assert len(resolved) == 1
assert resolved[0].label == "relationship"


def test_validation_decisions_from_json_string() -> None:
"""Validation output arrives as JSON string after parquet round-trip."""
entities = [EntitySpan("id1", "Alice", "first_name", 0, 5, 1.0, "detector")]
Expand Down
121 changes: 121 additions & 0 deletions tests/tools/test_measurement_strict_import_publisher.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,44 @@ def test_strict_import_exposes_multi_job_slurm_metadata(
]


def test_strict_import_projects_benchmark_identity_config(
tmp_path: Path,
wandb_import_tool: ModuleType,
) -> None:
measurement_path, seal_path = _write_sealed_import_case(wandb_import_tool, tmp_path)
commit_sha = "abcdef0123456789abcdef0123456789abcdef01"
prepared = wandb_import_tool.prepare_sealed_import(
measurement_path,
seal_path=seal_path,
settings=wandb_import_tool.ResolvedWandbConfig(wandb_mode=wandb_import_tool.WandbMode.offline),
benchmark_identity=wandb_import_tool.BenchmarkIdentityMetadata(
role="candidate",
kind="pr",
suite_version="2026-07-30",
branch="contributor/feat/wandb-benchmark-identity",
commit_sha=commit_sha,
commit_short=commit_sha[:12],
pr_number=210,
anonymizer_config_id="rat-rewrite-throughput",
anonymizer_mode="rewrite",
),
)

config = prepared.payload.config
sdk_values = config.sdk_values()
assert config.benchmark_role == "candidate"
assert config.benchmark_kind == "pr"
assert config.suite_version == "2026-07-30"
assert config.branch == "contributor/feat/wandb-benchmark-identity"
assert config.commit_sha == commit_sha
assert config.commit_short == commit_sha[:12]
assert config.pr_number == 210
assert config.anonymizer_config_id == "rat-rewrite-throughput"
assert config.anonymizer_mode == "rewrite"
assert sdk_values["benchmark_role"] == "candidate"
assert sdk_values["commit_sha"] == commit_sha


def test_strict_import_retry_is_a_remote_publication_noop(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
Expand Down Expand Up @@ -368,6 +406,89 @@ def test_strict_import_retry_is_a_remote_publication_noop(
)


def test_strict_import_retry_refreshes_benchmark_identity_config(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
wandb_import_tool: ModuleType,
) -> None:
measurement_path, seal_path = _write_sealed_import_case(wandb_import_tool, tmp_path)
settings = wandb_import_tool.ResolvedWandbConfig(
wandb_mode=wandb_import_tool.WandbMode.online,
wandb_base_url="https://wandb.example",
wandb_entity="entity",
wandb_project="project",
)
setup = sys.modules[wandb_import_tool.WandbPublisher.__module__]
state = _wandb_state()
monkeypatch.setattr(setup, "require_wandb", lambda: _fake_wandb_module(state))
publisher = wandb_import_tool.WandbPublisher()
prepared_without_identity = wandb_import_tool.prepare_sealed_import(
measurement_path,
seal_path=seal_path,
settings=settings,
)
commit_sha = "abcdef0123456789abcdef0123456789abcdef01"
prepared_with_identity = wandb_import_tool.prepare_sealed_import(
measurement_path,
seal_path=seal_path,
settings=settings,
benchmark_identity=wandb_import_tool.BenchmarkIdentityMetadata(
role="candidate",
kind="pr",
suite_version="2026-07-31",
branch="contributor/feat/example",
commit_sha=commit_sha,
commit_short=commit_sha[:12],
pr_number=236,
anonymizer_config_id="rat-throughput",
anonymizer_mode="rewrite",
),
)

first = publisher.publish_payload(
settings,
payload=prepared_without_identity.payload,
measurement_sha256=prepared_without_identity.measurement_sha256,
record_count=prepared_without_identity.record_count,
)
defined_metrics_after_first_publish = len(state.defined_metrics)
second = publisher.publish_payload(
settings,
payload=prepared_with_identity.payload,
measurement_sha256=prepared_with_identity.measurement_sha256,
record_count=prepared_with_identity.record_count,
)

assert first.run_id == second.run_id
assert second.publication_state == "already_complete"
assert len(state.config_updates) == 2
assert state.config_updates[1] == {
"benchmark_identity": {
"role": "candidate",
"kind": "pr",
"suite_version": "2026-07-31",
"branch": "contributor/feat/example",
"commit_sha": commit_sha,
"commit_short": commit_sha[:12],
"pr_number": 236,
"anonymizer_config_id": "rat-throughput",
"anonymizer_mode": "rewrite",
},
"benchmark_role": "candidate",
"benchmark_kind": "pr",
"suite_version": "2026-07-31",
"branch": "contributor/feat/example",
"commit_sha": commit_sha,
"commit_short": commit_sha[:12],
"pr_number": 236,
"anonymizer_config_id": "rat-throughput",
"anonymizer_mode": "rewrite",
}
assert len(state.logged) == 1
assert len(state.summary_updates) == 1
assert len(state.defined_metrics) == defined_metrics_after_first_publish


def test_strict_import_reports_resumed_incomplete_publication(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
Expand Down
48 changes: 38 additions & 10 deletions tests/tools/test_measurement_wandb_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,25 +306,21 @@ def test_wandb_stage_summary_uses_only_terminal_stage_but_table_preserves_all(


def test_wandb_scalar_registry_matches_package_field_catalog(wandb_logging_tool: ModuleType) -> None:
from measurement_tools.wandb_metric_schema import (
AGGREGATED_MEASUREMENT_FIELDS,
SCALAR_AGGREGATION_BY_FIELD,
)
from measurement_tools.wandb_models import WandbHistoryPayload

from anonymizer.measurement.fields import (
SCALAR_ADDITIVE_FIELDS,
SCALAR_AVERAGED_FIELDS,
SCALAR_LAST_VALUE_FIELDS,
)

metric_schema = sys.modules["measurement_tools.wandb_metric_schema"]
models = sys.modules["measurement_tools.wandb_models"]
field_groups = (SCALAR_LAST_VALUE_FIELDS, SCALAR_ADDITIVE_FIELDS, SCALAR_AVERAGED_FIELDS)
expected_fields = frozenset().union(*field_groups)

assert sum(map(len, field_groups)) == len(expected_fields)
assert frozenset(SCALAR_AGGREGATION_BY_FIELD) == expected_fields
for field_name in AGGREGATED_MEASUREMENT_FIELDS:
WandbHistoryPayload(metrics={f"measurement/record/{field_name}": 0})
assert frozenset(metric_schema.SCALAR_AGGREGATION_BY_FIELD) == expected_fields
for field_name in metric_schema.AGGREGATED_MEASUREMENT_FIELDS:
models.WandbHistoryPayload(metrics={f"measurement/record/{field_name}": 0})


def test_wandb_aggregates_rat_bench_reidentification_record(
Expand Down Expand Up @@ -452,6 +448,38 @@ def test_wandb_config_projects_only_declared_metadata(wandb_setup_tool: ModuleTy
assert config.sdk_values()["sweep_param_configs_all_detect_gliner_threshold"] == 0.3


def test_wandb_benchmark_identity_requires_pr_for_candidate_branch(wandb_import_tool: ModuleType) -> None:
with pytest.raises(ValidationError, match="requires pr_number"):
wandb_import_tool.BenchmarkIdentityMetadata(kind="branch", branch="feature/candidate")


def test_wandb_benchmark_identity_rejects_inconsistent_commit_identifiers(wandb_import_tool: ModuleType) -> None:
with pytest.raises(ValidationError, match="commit_short must match"):
wandb_import_tool.BenchmarkIdentityMetadata(
kind="experiment",
commit_sha="abcdef0123456789abcdef0123456789abcdef01",
commit_short="1234567",
)


@pytest.mark.parametrize(
("role", "kind"),
[
("main-baseline", "pr"),
("release-baseline", "main"),
("candidate", "main"),
("candidate", "release"),
],
)
def test_wandb_benchmark_identity_rejects_incompatible_role_kind_pairs(
role: str, kind: str, wandb_import_tool: ModuleType
) -> None:
with pytest.raises(ValidationError, match="incompatible"):
wandb_import_tool.BenchmarkIdentityMetadata(
role=role, kind=kind, pr_number=210 if kind in {"pr", "branch"} else None
)


def test_wandb_run_tags_filter_sensitive_generated_values(wandb_setup_tool: ModuleType) -> None:
metadata = wandb_setup_tool.WandbRunMetadata.model_validate(
{
Expand Down Expand Up @@ -539,7 +567,7 @@ def test_wandb_environment_isolates_routing_and_restores_exactly(
with wandb_setup_tool.WandbSdkEnvironment(settings):
assert os.environ["WANDB_GROUP"] == "resolved-group"
assert os.environ["WANDB_PROJECT"] == "resolved-project"
assert "WANDB_API_KEY" not in os.environ
assert os.environ["WANDB_API_KEY"] == "auth-token"
assert os.environ["WANDB_ERROR_REPORTING"] == "false"
assert "UNRELATED" not in os.environ
with pytest.raises(RuntimeError, match="nested or concurrent"):
Expand Down
42 changes: 30 additions & 12 deletions tools/measurement/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -300,9 +300,9 @@ and `WANDB_TAGS` are deliberately ignored. Precedence is an explicit CLI value,
then the corresponding `ANONYMIZER_MEASUREMENT_WANDB_*` variable, then the
publisher default. The W&B SDK still receives its audited timeout variables,
including `WANDB_HTTP_TIMEOUT` and `WANDB_INIT_TIMEOUT`. Authentication comes
from the SDK's local credential files under the preserved home directory; the
publisher removes `WANDB_API_KEY` from the SDK process environment. Set a
self-hosted endpoint through `--wandb-base-url` or
from the SDK's local credential files under the preserved home directory or
from `WANDB_API_KEY` when the launcher provides it in the process environment.
Set a self-hosted endpoint through `--wandb-base-url` or
`ANONYMIZER_MEASUREMENT_WANDB_BASE_URL`; ambient `WANDB_BASE_URL` is ignored.
Remote endpoints require HTTPS. Plain HTTP is accepted only for loopback
development endpoints. SDK error reporting is disabled before W&B is imported.
Expand Down Expand Up @@ -362,8 +362,9 @@ current user. Keep the directory when an offline run must be synchronized later.
During SDK use, a process-wide guard replaces the process environment with a
minimal runtime allowlist, audited W&B timeout settings, and the fully resolved
publisher settings. Local W&B credential files remain available through the
preserved home directory. Environment credentials, proxy settings, custom CA
settings, and ambient `WANDB_*` values are withheld from the SDK. The
preserved home directory, and `WANDB_API_KEY` remains available when explicitly
provided by a launcher. Proxy settings, custom CA settings, and ambient
`WANDB_*` routing values are withheld from the SDK. The
guard restores the exact original environment after the explicit run handle finishes.
Nested or concurrent native publishers in one process are rejected. Each
native run receives a fresh opaque 128-bit ID and uses `resume="never"`.
Expand All @@ -375,16 +376,16 @@ the native runner logs only the exception type to avoid echoing source values.

Before an external launcher starts benchmark work, verify W&B authentication from
the same account or container and the same preserved home directory used by the
publisher. Keep credentials in the local W&B credential files; do not pass a key
through the launcher environment or command line.
publisher. Keep credentials in the local W&B credential files or pass
`WANDB_API_KEY` through the launcher environment. Do not pass a key on the
command line.

```bash
# W&B public cloud
env -u WANDB_API_KEY uv run wandb login --verify --cloud
# W&B public cloud, using a stored SDK credential
uv run wandb login --verify --cloud

# Self-hosted or dedicated cloud
env -u WANDB_API_KEY uv run wandb login --verify \
--host https://wandb.example.com
# Self-hosted or dedicated cloud, using a stored SDK credential
uv run wandb login --verify --host https://wandb.example.com
```

Both commands verify the stored credential against the selected endpoint and
Expand Down Expand Up @@ -434,6 +435,23 @@ uv run python tools/measurement/import_wandb_run.py \
--json
```

External launchers can add safe benchmark identity fields to W&B config for
filtering and comparisons:

```bash
--benchmark-role candidate \
--benchmark-kind pr \
--suite-version 2026-07-30 \
--branch contributor/feat/wandb-benchmark-identity \
--commit-sha 0123456789abcdef0123456789abcdef01234567 \
--pr-number 210 \
--anonymizer-config-id rat-rewrite-throughput \
--anonymizer-mode rewrite
```

`--pr-number` is required for `--benchmark-kind pr` and `branch`. Main and
release baselines can omit it.

The importer captures the seal and JSONL once, verifies their content binding,
builds the complete typed payload, then initializes W&B. It derives a stable
128-bit run ID from the destination, sealed case identity, seal digest, schema
Expand Down
Loading
Loading