Repository navigation
Conversation
e29daf4 to
4d68f7d
Compare
|
@nikhilwoodruff since you reported #1155 and have been living with this at PolicyEngine — if you have a chance to run this branch against your workloads, that would be a much stronger validation than my regression tests alone. Happy to adjust anything that surfaces. |
guillett
left a comment
There was a problem hiding this comment.
That looks good to me. Many thanks @Adelagric
|
Thanks @guillett! One thing: the CI workflows haven't run on this PR yet — as a first-time contributor they need a maintainer to approve the run. Happy to rebase if the version bump conflicts with anything landing before this. While you're at it: #1386 (spiral warning, #956) and #1387 ( |
| assert simulation.calculate("birth", "2017-01") == numpy.datetime64("1987-01-01") | ||
|
|
||
|
|
||
| @pytest.mark.filterwarnings("ignore") |
There was a problem hiding this comment.
Why are warnings ignored here?
There was a problem hiding this comment.
Good catch — no good reason. The only warning raised there is MemoryConfigWarning (the "experimental feature" notice), and test_holders.py already uses MemoryConfig(max_memory_occupation=0) without filtering anything. Removed in the latest commit.
There was a problem hiding this comment.
Did you try to run tests directly on your own machine?
There was a problem hiding this comment.
I'm trying to figure out, I much AI assisted you are.
There was a problem hiding this comment.
Fair question, and I'd rather answer it plainly than have it hang over the PR.
Yes, the tests were run locally several ways (pytest, make test-core, full suite), all green with the project config. The filterwarnings was a defensive reflex from a run with -W error, where MemoryConfigWarning fails the disk tests; since test_holders.py uses MemoryConfig without filtering, I removed it to follow the existing convention.
On AI: I use AI tooling in my workflow, the way I'd use an IDE — but the analysis is mine, and I can defend every line. The five compounding defects behind #1155 were each reproduced by hand before a line of fix was written; the subtle one is the shared _data_storage_dir, where fixing the holder storage alone just moves the bug to the filesystem. Ask me anything about it.
To be clear about where I stand: I'm here because these are real silent-data-loss bugs in a codebase people rely on for policy decisions, not for the merge count. If the project would rather not take contributions from people who use these tools, say so and I'll respect it — no hard feelings either way. If the concern is maintainability, I'm happy to stay reachable on this code after merge.
There was a problem hiding this comment.
Fair question. Yes — the tests were run locally several ways (pytest, make test-core, full suite), all green with the project config. The filterwarnings came from a run with -W error, where MemoryConfigWarning fails the disk tests; since test_holders.py uses MemoryConfig without filtering, I removed it to follow the convention.
On AI: I use AI tooling in my workflow, the way I'd use an IDE. The analysis is mine and I can defend every line — the five compounding defects behind #1155 were each reproduced by hand before any fix was written, and the subtle one (the shared _data_storage_dir, where fixing the holder storage alone just moves the bug to the filesystem) is the kind of thing you only find by running it. Ask me anything about it.
I'm here because these are real silent-data-loss bugs in code people rely on for policy decisions. If the project prefers not to take contributions from people who use these tools, just say so — I'll respect it. Otherwise I'm happy to stay reachable on this code after merge.
There was a problem hiding this comment.
Shoudn't these cloned? I guess they are array references, no?
There was a problem hiding this comment.
Array references, yes — this block predates the PR. Nothing in core mutates them in place (every write path reassigns a new array; the indexed write in members_position is on a freshly built one), so sharing them was safe under the same immutability policy as holder values. It's a convention, not a guarantee, and copying costs nothing: done in e14e0d6, with a test.
|
Thanks for the clarifications @Adelagric. As you said, this is “code people rely on for policy decisions”, and unfortunately that means we have to deal with real risks of reputation farming, privilege escalation, or even exploit planting (prepositioning). Core was never, until LLM assistance, the entry point for new contributors due to the complexity of the codebase and the potential global impact of changes. We are slowly learning to deal with the size and complexity of AI-generated contributions, but this is an area under active definition, and I'm sorry that everything is not super clear up front. Please bear with us! In particular, the OpenFisca community is trying to define its approach with regards to AI-assisted contributions. You can find of our current line of thinking in #1372. In particular, defining the level of assistance and model(s) used would be helpful, as @guillett mentioned in #1385 (comment) 🙂 It would also make a significant difference in the trust and effort we can allocate to such changesets if we understood a bit more of your context for using OpenFisca and contributing to it 🙂 I could gather from your public website that you claim to be based in France, but a little hi and an intro in our community chat would be great as well! I sent you an invite to your publicly listed email. Feel free to join there or drop a line on contact@openfisca.org. In particular, understanding which production uses you have of OpenFisca, what's your experience with it, and what motivates you to contribute to Core would be very helpful. Note that this is not an official precondition as we have not made this into a strict prerequisite policy, and I'll let @guillett and other Core maintainers judge the relevance of the changeset itself. I just hope to bring some guidance on what can make the integration of your work into the codebase smoother 🙂 OpenFisca is not merely an open source project, it is a digital common and that means governance and the human connections that make the codebase evolve are an integral part of it. Have a good day! 😊 |
|
I've checked the list of changed files: none modify CI. I've checked that the introduced code does not make suspicious new logs (secret extraction) or remote calls: all good. |
|
Thanks @MattiSG — both for taking the time to write this and for reviewing the diff and approving the workflows. I read #1372, and I appreciate that the policy is being worked out in the open. On AI usage: under the RFC terminology, I would classify the submitted fix and tests as 🟠 AI-generated: I use an LLM coding assistant under my direction to write code, while the investigation and reproduction work here was mine. Every defect in #1155 was first reproduced independently against the actual code; the fix was then developed test-first, with the tests confirmed red on Everything I submit is reread, executed and verified by me, and I consider myself responsible for it. I can explain every line and maintain the code afterward. For tooling, I use an agentic coding setup with an LLM assistant. I'm happy to disclose the exact tool/model explicitly once the contribution template has a place for it. On who I am: I'm Adel Kaleche, based in Occitanie, France. I teach part-time and run a small software and operations-research consultancy on the side (kaleche.dev). I do not use OpenFisca in production, so I want to be explicit about that. Most of my open-source work has converged on the same kind of problem: silent data loss and silent failures. I have contributed fixes in projects such as rust-bio, Rostam, conda and x402, and I currently have work open in several data/infrastructure projects. The recurring pattern is the same: find a case where the system loses, corrupts or mis-associates data without failing loudly; reproduce it deterministically; then fix it behind a red-then-green regression test. Why OpenFisca: part of the work I'm building a specialty around sits at the boundary between probabilistic and deterministic systems. On the probabilistic side, I recently wrote a preprint on detecting silent distribution drift: https://arxiv.org/abs/2608.01268. On the deterministic side, I tend to hunt exactly the kind of failures I found in the clone/dump/spiral paths here. As more code is produced with AI assistance, I think this boundary — systems appearing to work while silently producing the wrong state — is going to matter more, not less. The second reason is more personal: France took me in, and contributing to a digital common the country relies on felt like a small way to give something back. On future involvement: I can't promise a regular contribution rate — teaching and client work make that difficult — but I do consider myself responsible for the code I submit. I'll stay available for review questions, regressions, or follow-up work on #1385, #1386 and #1387 after merge. Beyond that, I'll probably continue following this same thread across other codebases whenever I find another silent failure worth digging into. I'll say hi in the community chat as well. Thanks for the invitation. |
e14e0d6 to
d72ff00
Compare
|
Rebased on |
d72ff00 to
59e8a41
Compare
59e8a41 to
27e38d4
Compare
|
Could you please refactor this PR moving toward a breaking change that removes It looks like |
|
Happy to look at a |
Cloned holders shared their in-memory and on-disk storages with the original simulation, so any value set, cached or deleted through one simulation silently altered the other. GroupPopulation.clone also attached cloned holders to the original population and kept the original persons as members, and the clone shared its data storage directory with the original, letting lazily created holders of both simulations overwrite and delete each other's files. Storages are now copied on clone (arrays stay shared, as they are treated as immutable), the clone gets its own storage directory, group holders and members are bound to the cloned populations, and invalidated_caches is copied. Fixes openfisca#1155
The only warning raised by these tests is MemoryConfigWarning, the experimental-feature notice, which other tests using MemoryConfig do not filter either.
ids, members_entity_id, members_role, members_position and ordered_members_map were shared by reference between a population and its clone. Nothing in core mutates them in place today, but that is a convention rather than a guarantee; copying them at clone time costs nothing and makes the isolation defensive.
27e38d4 to
646317a
Compare
|
Rebased on |
Fixes #1155.
Problem
Simulation.clone()promises to "copy the simulation just enough to be able to run the copy without modifying the original simulation", but the clone actually shares most of its mutable state with the original. Four distinct defects compound:Holder.cloneshares storage by reference: the cloned holder reuses the original'sInMemoryStorageandOnDiskStorageobjects. Anyset_input,put_in_cacheordelete_arraysthrough one simulation silently alters the other. Worse, the original can end up internally inconsistent: changesalarythrough a clone and the original now returns the new salary but keeps its stale cachedincome_tax.GroupPopulation.clonebinds cloned holders to the original population:holder.clone(self)is called instead ofholder.clone(result), so the clone's group-entity holders havepopulationandsimulationpointing at the original simulation.GroupPopulation.clonekeeps the original persons asmembers— this is the mechanism @nikhilwoodruff suspected in Possible bug inclonelogic #1155:clone.household.members is original.persons._data_storage_dir: when both simulations lazily create a holder for the same variable (with amemory_configactive), their two distinctOnDiskStorageobjects write{period}.npyfiles into the same directory. One simulation then reads the other's values, and garbage-collecting either simulationrmtrees the directory, physically destroying the other's data.Minimal reproduction of defect 1 (before this fix):
Fix
InMemoryStorage.clone()/OnDiskStorage.clone(storage_dir): new methods returning independent copies. The in-memory clone copies the period-to-array mapping only (arrays stay shared, as the engine treats them as immutable). The on-disk clone copies the.npyfiles into the new directory and re-registers the enum mapping under the new directory key, soEnumArrayvalues survive the copy.Holder.cloneuses them instead of sharing references.GroupPopulation.clonebinds holders to the cloned population and takes the cloned persons (simulation.persons) asmembers.Simulation.cloneresets_data_storage_dir(the clone lazily gets its own temporary directory) and copiesinvalidated_caches.Trade-offs
clone.Test plan
tests/core/test_simulations.py, each failing onmaster(except the enum guard, which protects the new copy logic): holder-storage isolation, group holders bound to the clone, clonedmembers,invalidated_cachescopy, on-disk isolation incl. garbage-collection of the clone, enum preservation on disk clone.isort/black/flake8/pylintclean on the changed files.