Skip to content

Healthsheet bundles: take the origin line from the profile, so a non-study record carries no false FAIRhub origin (#4009) - #4387

Open
realmarcin wants to merge 3 commits into
mainfrom
fix/4009-healthsheet-origin-from-profile
Open

realmarcin wants to merge 3 commits into
mainfrom
fix/4009-healthsheet-origin-from-profile

Conversation

@realmarcin

@realmarcin realmarcin commented Oct 5, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #4009
Fixes #4420

healthsheet.render wrote the literal Origin: FAIRhub API record, metadata.healthsheet into every healthsheet-only bundle. That included a bundle built from a record that is not the profile's (d4d healthsheet bundle --record X --project P [--name N]), under either profile. That bundle is the model input of the healthsheet arm ({p}_healthsheet_only.txt), so a non-study dataset's model was told its healthsheet came from the study's platform. The generation-specificity audit (#4007) reported this as the only deterministic violation.

Header of a foreign record's bundle (--record clinic_record.json --project CLINIC), same output under bridge2ai and neutral:

before (origin/main)                       after (this branch)
Project: CLINIC                            Project: CLINIC
Dataset: Independent Clinic Cohort         Dataset: Independent Clinic Cohort
DOI: 10.1234/clinic                        DOI: 10.1234/clinic
Source: <tmp>/clinic_record.json           Source: <tmp>/clinic_record.json
Origin: FAIRhub API record, metadata.healthsheet

Merge held on the owner's audit28 decision (#4761)

audit28's controllers compare src/data_sheets_schema/profiles.py (and schema_digest.py) between a continuation's repository and its parent's, and stop on any difference:

  • validate_scientific_identity in notes/matched_cborg_2026-09-13/audit_controls/registration.py:479-491 raises "shared scientific instrument changed";
  • the same check in finalization_controls/registration.py:125-129 raises "finalization changes the inherited scientific instrument".

This PR changes profiles.py. When it was opened, main's profiles.py still hashed to the registered pin (36de6c72…), so a continuation prepared from a main that included this PR would have stopped at that check. That no longer distinguishes this PR (#5030). Since bfab086a6 (#4459, 2026-10-05), main's schema_digest.py hashes to 2626779774de…, which matches neither registration's pin. A continuation prepared from current main therefore already stops, with or without this PR, and audit28 can only run from a fixed compatible checkout, which a merge to main does not touch. The merge waits only on the owner's decision in #4761, which the plan's D10 applies to #2910, #2922 and this PR together. Review continues meanwhile.

What changed

  • Profile.healthsheet_origin (profiles.py) holds what the bundle's Origin: line says about the profile's own record. bridge2ai carries today's text (FAIRhub API record, metadata.healthsheet) and neutral carries none. The field holds the line's text, and the renderer keeps the Origin: label, as it does for Project: and Source:.
  • render(..., origin=None) writes Origin: <origin> directly after Source:, and only when it is given one.
  • build_bundle passes the profile's origin only for the profile's own record (own, the resolved-path identity the bundle name already uses), whatever project the caller names. The origin describes the record, not the dataset label it is filed under.
  • Any other record gets no Origin: line. The scope asked to either derive the origin or omit the line, and to say which; this omits it. For such a record the module knows only what the caller passed, and the Source: and Project: lines already state that, so a derived line would repeat them. The record also has no generic field naming the service that served it, so reading a platform off its content (a DOI prefix, a title) would be the platform-specific guess the audit forbids.
  • The default bundle name, documented and refused correctly (build_bundle docstring (edited by this PR) and the CLI --name help say the default bundle name comes from the record's stem; the code names it after the project #4420). build_bundle's docstring and the --name and --project help of d4d healthsheet bundle (cli/healthsheet.py) now say what has been true since a553d42 (#1436 r2 (Claude): a foreign healthsheet record under the study profile is written to the study's tracked bundle name #1493): a record that is not the profile's is named after the project it is filed under ({project}_healthsheet_only.txt), and the profile's bundle name is the default only for the profile's own record filed under the profile's project. A reserved default is refused naming the profile it belongs to ("AI_READI_healthsheet_only.txt is the bridge2ai profile's bundle name; pass --name for ..."); the old message called it "the active profile's bundle name", which was wrong under neutral. Documentation and message only: no bundle byte changes.
  • exceptions.yaml in the user-approved audit skill (nothing else under .claude/ changes) gets a tracked reason for the IRB wording at healthsheet.py:138 (line 134 on origin/main; PR body and commit message put the IRB wording at healthsheet.py:134; at this head it is line 138 (134 is the Source: line) #4421). The entry is appended, so no existing exception number moves. The wording is not changed because the sentence is in the tracked study bundle.

The tracked bundle does not change by a single byte

data/preprocessed/concatenated/AI_READI_healthsheet_only.txt, 56,453 bytes:

sha256 md5
committed ef2f5a5c98c56f565728c09305ecee10f47586472533b3648c2421c637ed210a a66b16818139bca4018efe11cae5cc7a
chunk manifest pins same same
rebuilt on origin/main (4 routes) same same
rebuilt on this branch (4 routes) same same

The four routes are: the library default, the library with an absolute record path, d4d healthsheet bundle, and the same CLI run from tests/. The same md5 is pinned by 12 claudecode_agent_healthsheet_core provenance records. No file under data/ changes.

Specificity audit, before and after (scan.py on this branch)

before (bc1f41d) after (1d78540)
exit 1 1
violations 167 166
deterministic violations 1 (healthsheet.py:131, FAIRhub) 0
gc_project hits / model-facing / with reason 1,136 / 388 / 394 1,136 / 387 / 395
biomedical_clinical hits / model-facing / with reason 1,665 / 1,376 / 1,347 1,665 / 1,376 / 1,348
bridge2ai_program hits / model-facing / with reason 2,478 / 2,377 / 2,368 unchanged
tracked hits without a reason (triage queue) 94 93
self-test passed passed
surfaces 327 327, none re-described
tests listed as asserting project behaviour 95 95
"what api means" section identical

Every count that moved is accounted for:

Tests

tests/test_rocrate/test_healthsheet.py adds seven tests: five in TestOrigin, and two in TestDefaultName for #4420.

TestOrigin:

  • test_the_study_bundle_keeps_its_bytes (bridge2ai): the profile's record renders the tracked bundle's whole bytes (sha256 compare), and its one Origin: line is the profile's text.
  • test_the_origin_follows_the_record_not_the_project: the study record filed under --project CLINIC keeps the profile's origin.
  • test_another_record_states_no_origin (bridge2ai): a foreign record with --project, with --project --name, and with --project AI_READI --name other.txt gets no Origin: line and no "fairhub" anywhere.
  • test_neutral_states_no_origin_for_any_record: NEUTRAL.healthsheet_origin is None, and under neutral neither a foreign record nor the study's capture gets an Origin: line.
  • test_render_writes_the_origin_it_is_given_after_the_source: render writes no line without an origin, and writes a given one directly after Source:.

TestDefaultName (#4420), under bridge2ai and neutral, on a record that is not the profile's:

  • test_the_stated_default_name_is_the_name_written: the --name help, the command's help and build_bundle's docstring each name exactly the file that was written.
  • test_a_reserved_default_is_refused_naming_the_profile_it_belongs_to: the refusal names the owning profile, and nothing is written.

Re-checked 2026-10-13 at c5115f439 merged into origin/main 5c952d210 (adversarial review, #5029/#5030): the merge is clean, the tracked bundle rebuilds byte-identical through all four routes, no pin, schema digest, chunk manifest or record moves, and 1,426 targeted tests pass.

I ran every test file that imports healthsheet or profiles:

  • 27 files under tests/ (-m "not corpus", -n 2): 239 + 888 passed, 1 skipped. The skip is test_resources.py:371, which needs a symlinked .venv that a worktree does not have.
  • The five notes/matched_cborg_2026-09-13/audit_controls files that import profiles, run in CI's environment: 205 passed, and notes/ was left untouched.
  • I did not run the full fast lane: the machine's load average was about 200, the change is additive, and every test file that imports a changed module ran.

Mutation checks

Each mutant was run on a git archive copy, never the worktree. All eight were killed:

mutant killed by
M1 the literal line restored in render another_record, neutral, render
M2 the origin given to every record another_record
M3 the origin keyed on the project (own_name) origin_follows_the_record
M4 the profile's text changed study_bundle_keeps_its_bytes, #1542 test
M5 the profile's text dropped study_bundle_keeps_its_bytes, origin_follows_the_record, #1542 test
M6 neutral given the study's origin neutral
M7 render ignores origin render, origin_follows_the_record, study_bundle_keeps_its_bytes, #1542 test
M8 the line moved after the blank line render, study_bundle_keeps_its_bytes, #1542 test

The existing byte test (#1542) catches only M4, M5, M7 and M8. The false-origin regressions (M1, M2, M3, M6) are caught only by the new tests.

Not done here

🤖 Generated with Claude Code

`healthsheet.render` wrote "Origin: FAIRhub API record,
metadata.healthsheet" into every healthsheet-only bundle, a record built
with --record/--project/--name included, so a non-study dataset's model
input named the study's platform as its origin.

- `Profile.healthsheet_origin`: what the `Origin:` line says the
  profile's own record is. `bridge2ai` carries today's text; `neutral`
  carries none.
- `render(..., origin=None)` writes the line after `Source:` only when
  given one; `build_bundle` gives the profile's origin only for the
  profile's own record, whatever project the caller names, since the
  origin describes the record.
- Any other record gets no `Origin:` line. The module knows of it only
  what the caller passed, which `Source:` and `Project:` already state,
  and reading a platform off the record's content would be a guess.
- The tracked AI_READI_healthsheet_only.txt is byte-identical (sha256
  ef2f5a5c..., md5 a66b1681..., the chunk manifest's pins), rebuilt
  through the library, the CLI, an absolute record path and tests/.
- The IRB wording at healthsheet.py:134 gets a tracked reason in the
  specificity audit's exceptions.yaml; rewording it would move bytes
  the chunk manifest and the healthsheet arm's records pin.

Tests: the study bundle's whole bytes; the origin follows the record,
not the project; a foreign record under bridge2ai (--project, --name,
claiming AI_READI) and every record under neutral get no `Origin:`
line; render writes a given origin after `Source:`.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
realmarcin and others added 2 commits October 5, 2026 08:29
`build_bundle`'s docstring said the default bundle name was "derived
from the record's stem", and the `--name` help of `d4d healthsheet
bundle` said "else <record stem>_healthsheet_only.txt". Neither has been
true since a553d42 (#1493). That commit named a record that is not the
profile's after the project it is filed under,
`{project}_healthsheet_only.txt`, which is what lets the name be checked
against the profiles' reserved bundle names. Both texts now say so.
They also add what the old wording left out: the profile's bundle name
is the default only for the profile's own record filed under the
profile's project.

The `--project` help and the docstring's `project` sentence had the same
gap, and get the same qualifier: only the profile's own record defaults
to the profile's project; any other record must name one.

`TestDefaultName` runs the command on a record that is not the profile's,
under `bridge2ai` and `neutral`. It requires the `--name` help, the
command's help and `build_bundle`'s docstring each to name exactly the
file that was written.

This changes documentation only. No bundle byte changes, and the tracked
healthsheet bundle stays as it is.

Correction to the message of 1d78540 (#4421): "The IRB wording at
healthsheet.py:134" uses origin/main's line numbers. At 1d78540, and
at this commit, the IRB literal is healthsheet.py:138; line 134 is the
`Source:` line.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
`build_bundle` refuses a default name that is any profile's bundle name,
under every active profile (#1565). The refusal said the name "is the
active profile's bundle name". Under `neutral`, which has no bundle name,
that told a record filed under the study's project that the name was the
neutral profile's.

The refusal now names the profile the name belongs to:
"AI_READI_healthsheet_only.txt is the bridge2ai profile's bundle name;
pass --name for ...". This matches the docstring and `--name` help
corrected in the previous commit, which say the default is refused where
it is a profile's bundle name.

`TestDefaultName::test_a_reserved_default_is_refused_naming_the_profile_it_belongs_to`
checks the message, and that nothing is written, under `bridge2ai` and
`neutral`. The existing tests (#1543, #1565) check only "pass --name"
and still pass.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant