Commit 841c6a8
fix: redact current_stage from student_public bundles (#56)
* fix: redact current_stage from student_public bundles
Add an `is_leakage_trap` flag to FeatureSpec and an exposure-layer
column-redaction pass that drops `leakage_risk and not is_leakage_trap`
columns from student_public snapshots, task splits, feature dictionary,
and dataset card. `current_stage` (true label leak) is now removed in
student_public; `total_touches_all` (intentional pedagogical trap) is
preserved via the new flag.
- `schema/features.py`: add `is_leakage_trap` field; mark
`total_touches_all`; export `STUDENT_PUBLIC_REDACTED_COLUMNS`.
- `schema/dictionaries.py`: emit `is_leakage_trap` column in
`feature_dictionary.csv`.
- `exposure/filters.py`: `BundleFilter.redacted_columns`; populated for
student_public.
- `api/bundle.py`: drop redacted columns from snapshot before splits;
thread the visible feature tuple to dataset card and dictionary writers.
- `narrative/dataset_card.py`: accept a `features` arg so categories /
leakage section reflect what is actually published.
- `validation/bundle_checks.py`: new `_check_exposure_redaction()`
enforces CLAUDE.md invariant #1 — redacted columns must not appear in
the published task splits or dictionary.
- `validation/invariants.py`: relax exposure-monotonicity from byte
identity to subset-with-shared-content for `feature_dictionary.csv`
and task splits, so redaction doesn't trip the check.
Tests: extend `tests/schema/test_features.py` with five new assertions
covering the trap flag, the redaction set, and `current_stage`'s role.
All 880 existing tests still pass.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* docs: update release docs and add end-to-end redaction tests
- `tests/exposure/test_redaction.py` (19 tests): bundle-level proof that
`current_stage` is absent from every student_public task split, that
`total_touches_all` (the pedagogical trap) is preserved, that the
research_instructor bundle still contains both, that the student
feature dictionary differs from the instructor's by exactly the
redacted set, that shared columns hold identical values across modes,
and that `validate_bundle()` flags a tampered student_public bundle
whose task split still carries `current_stage`.
- `scripts/build_public_release.py`: drop `_FLAT_CSV_DROP_COLS` — the
bundle writer now redacts at source, so the flat CSV inherits it.
- `release/README.md`, `release/HF_DATASET_CARD.md`: update column
counts (33 features + 1 trap + 1 target in student_public; 34 + 1 + 1
in the instructor companion) and rewrite the leakage section to
distinguish stripped columns from the deliberate trap.
- `.agent-plan.md`: flip "Known issue: current_stage leakage" to
resolved with a link to the redaction mechanism.
Bundles regenerated; all four pass `leadforge validate`;
`scripts/verify_hash_determinism.py` confirms 73/73 files identical
across two consecutive builds. Full test suite: 899 passing.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* refactor: collapse two-flag redaction into prescriptive redact_in_modes
Addresses senior-dev review of the previous two commits:
1. **Single source of truth.** Replaced the awkward
`leakage_risk + is_leakage_trap` flag pair with a single prescriptive
field `FeatureSpec.redact_in_modes: frozenset[ExposureMode]`.
`leakage_risk` reverts to purely descriptive ("post-snapshot
correlated"). `current_stage` carries `redact_in_modes={student_public}`;
`total_touches_all` keeps `leakage_risk=True` with empty `redact_in_modes`
(deliberate trap). New `redacted_columns_for(mode, features=...)`
parameterizable function replaces the frozen module-level set.
2. **Validation independent of the writer.** `_check_exposure_redaction`
now derives the expected redaction set directly from `LEAD_SNAPSHOT_FEATURES`
via `redacted_columns_for`, *not* from `BundleFilter`. A bug in the
filter no longer agrees-with-itself past the validator. Added a real
regression test that writes a real student_public bundle, mutates a
parquet to reinsert `current_stage`, and asserts validation fails.
3. **Self-describing manifest.** `manifest.json` now records
`redacted_columns: [...]`. The validator cross-checks the manifest's
declared set against the feature-spec-derived expected set; a
second new test mutates the manifest to claim nothing was redacted
and asserts validation flags the disagreement.
4. **Stricter exposure monotonicity.** `check_exposure_monotonicity`
now asserts that `instructor_columns - student_columns` *equals*
`redacted_columns_for(student_public)` — not just "is a superset".
A future column drop in student_public outside the redaction set is
now caught.
5. **Reverted feature_dictionary.csv schema.** The previous commit added
an `is_leakage_trap` column to the published CSV without flagging the
schema change. Removed: the redaction policy is package-internal and
the bundle's actual published schema is observable from the parquet
files and `manifest.redacted_columns`. CSV stays back-compat with
prior releases (6 columns: name, dtype, description, category,
is_target, leakage_risk).
6. **De-duplicated documentation.** `release/README.md` no longer
maintains a per-category feature count table by hand — that data is
in the auto-generated `dataset_card.md`. Removed the duplicated
table; added explicit caveats covering known structural-leakage
issues (event aggregates over the label window, `is_mql` zero
variance, `is_sql=False` near-deterministic for non-conversion).
7. **Documented the structural follow-up.** `.agent-plan.md` now
includes a clearly-scoped "Follow-up: structural leakage" section
covering: (a) windowed-snapshot fix for event aggregates;
(b) measured `is_sql` leakage P(conv|is_sql=False) = 0.038/0.015/0.006
across tiers; (c) `is_mql` is constant True (zero variance). Suggests
filing a tracked issue (deferred — needs user confirmation per
shared-state-action rules).
Removed: `BundleFilter.redacted_columns` (replaced by direct calls to
`redacted_columns_for(mode)` from the bundle writer).
Tests: schema tests rewritten for `redact_in_modes` semantics; redaction
tests updated for the new function-based API; +2 manifest-redaction
tests; +1 real regression test (mutated parquet) replacing the previous
manifest-string-edit test. 902 tests passing (was 899).
Hash determinism verified: 73/73 files identical across two consecutive
builds. All four release bundles regenerated; `feature_dictionary.csv`
is now back to its pre-PR 6-column schema.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>1 parent f61e98a commit 841c6a8
14 files changed
Lines changed: 580 additions & 60 deletions
File tree
- leadforge
- api
- exposure
- narrative
- render
- schema
- validation
- release
- scripts
- tests
- exposure
- schema
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
45 | 45 | | |
46 | 46 | | |
47 | 47 | | |
| 48 | + | |
48 | 49 | | |
49 | 50 | | |
50 | 51 | | |
| |||
61 | 62 | | |
62 | 63 | | |
63 | 64 | | |
64 | | - | |
| 65 | + | |
65 | 66 | | |
66 | | - | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
67 | 78 | | |
68 | 79 | | |
69 | 80 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
23 | 23 | | |
24 | 24 | | |
25 | 25 | | |
| 26 | + | |
26 | 27 | | |
27 | 28 | | |
28 | 29 | | |
| |||
72 | 73 | | |
73 | 74 | | |
74 | 75 | | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
75 | 82 | | |
76 | 83 | | |
77 | 84 | | |
| |||
80 | 87 | | |
81 | 88 | | |
82 | 89 | | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
83 | 97 | | |
84 | 98 | | |
85 | 99 | | |
86 | 100 | | |
87 | 101 | | |
88 | 102 | | |
89 | 103 | | |
90 | | - | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
91 | 110 | | |
92 | | - | |
| 111 | + | |
93 | 112 | | |
94 | 113 | | |
95 | 114 | | |
| |||
106 | 125 | | |
107 | 126 | | |
108 | 127 | | |
| 128 | + | |
109 | 129 | | |
110 | 130 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
7 | 13 | | |
8 | 14 | | |
9 | 15 | | |
| |||
16 | 22 | | |
17 | 23 | | |
18 | 24 | | |
19 | | - | |
| 25 | + | |
20 | 26 | | |
21 | 27 | | |
22 | 28 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
12 | | - | |
| 12 | + | |
13 | 13 | | |
14 | 14 | | |
15 | 15 | | |
| |||
20 | 20 | | |
21 | 21 | | |
22 | 22 | | |
| 23 | + | |
23 | 24 | | |
24 | 25 | | |
25 | 26 | | |
| |||
31 | 32 | | |
32 | 33 | | |
33 | 34 | | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
34 | 39 | | |
35 | 40 | | |
36 | 41 | | |
| |||
149 | 154 | | |
150 | 155 | | |
151 | 156 | | |
152 | | - | |
| 157 | + | |
153 | 158 | | |
154 | 159 | | |
155 | 160 | | |
156 | 161 | | |
157 | 162 | | |
158 | 163 | | |
159 | | - | |
160 | | - | |
161 | | - | |
| 164 | + | |
162 | 165 | | |
163 | | - | |
| 166 | + | |
164 | 167 | | |
165 | 168 | | |
166 | 169 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
35 | 35 | | |
36 | 36 | | |
37 | 37 | | |
| 38 | + | |
38 | 39 | | |
39 | 40 | | |
40 | 41 | | |
| |||
49 | 50 | | |
50 | 51 | | |
51 | 52 | | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
52 | 58 | | |
53 | 59 | | |
54 | 60 | | |
55 | 61 | | |
56 | 62 | | |
57 | 63 | | |
58 | 64 | | |
| 65 | + | |
| 66 | + | |
59 | 67 | | |
60 | 68 | | |
61 | 69 | | |
| |||
91 | 99 | | |
92 | 100 | | |
93 | 101 | | |
| 102 | + | |
94 | 103 | | |
95 | 104 | | |
96 | 105 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
23 | 23 | | |
24 | 24 | | |
25 | 25 | | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
26 | 33 | | |
27 | 34 | | |
28 | 35 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
11 | | - | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
12 | 14 | | |
13 | 15 | | |
14 | 16 | | |
15 | 17 | | |
16 | 18 | | |
17 | 19 | | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
18 | 36 | | |
19 | 37 | | |
20 | 38 | | |
| |||
23 | 41 | | |
24 | 42 | | |
25 | 43 | | |
26 | | - | |
27 | | - | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
28 | 48 | | |
29 | 49 | | |
30 | 50 | | |
| |||
33 | 53 | | |
34 | 54 | | |
35 | 55 | | |
| 56 | + | |
36 | 57 | | |
37 | 58 | | |
38 | 59 | | |
| |||
122 | 143 | | |
123 | 144 | | |
124 | 145 | | |
| 146 | + | |
125 | 147 | | |
126 | 148 | | |
127 | 149 | | |
| |||
235 | 257 | | |
236 | 258 | | |
237 | 259 | | |
238 | | - | |
| 260 | + | |
239 | 261 | | |
240 | 262 | | |
241 | 263 | | |
| |||
254 | 276 | | |
255 | 277 | | |
256 | 278 | | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
0 commit comments