Build each file's generated_by at reconcile from the imported lineage (#577) - #590
Merged
Merged
Conversation
…#577) Reconcile reads the lineage of the run's catalog (#583), translates each line through the activity map (#584), and resolves its child and file parent within the child's dataset by file_id or drs_uri, in the same pass over the run's records the slot-evidence join makes. edges.merge_steps merges those steps with inference's own into the reconciled generated_by: sources naming one activity and parent are listed together in named_by, a lineage source cited by its activity-map row and a ClaimSource; two activities, or two parents in a role that takes one, are a conflict and give no generated_by. A generic Activity step counts only where its parent is one a specific source names. An unresolved parent gives no input. Sample parents wait for #582. reconcile_report.json gains a lineage block (per source and dataset: offered, resolved, not_in_dataset, several_match, untranslated, sample_parent and the child outcomes; who named each step; conflicts with who said what; steps that do not fit their declaration), rendered in the reconcile report and dashboard. The rules report lists the authored activity-map rows with the edge rules. On run 20260930_071213: 149,395 files gain a step, 222,892 name edges gain their agreeing lineage sources, 0 conflicts, and nothing outside generated_by changes. Inference output is untouched. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016p3omgkuHduf1UQjLH4K6t
Contributor
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Generic-only activity steps currently produce generated_by instead of the required edge conflict.
Review effort: Balanced
Findings: 1
What changed in this PR
Adds lineage-based generated_by reconciliation and reporting.
Changes:
- Resolves and merges imported lineage with inferred steps.
- Reports lineage outcomes, conflicts, misfits, and rule usage.
- Adds comprehensive fixtures and acceptance tests.
| File | Description |
|---|---|
src/meta_disco/reconcile.py |
Integrates lineage reconciliation. |
src/meta_disco/reconcile_lineage.py |
Translates and resolves lineage. |
src/meta_disco/edges.py |
Merges steps and detects conflicts. |
src/meta_disco/activities.py |
Adds activity agreement logic. |
src/meta_disco/activity_map.py |
Exposes lineage details and map digest. |
src/meta_disco/output_utils.py |
Adds shared relative-path helper. |
scripts/generate_reconcile_report.py |
Renders lineage reporting. |
scripts/generate_rules_report.py |
Includes activity-map edge rules. |
docs/reconcile-dashboard-template.html |
Displays lineage metrics. |
docs/claims-contract.md |
Documents lineage reconciliation. |
CLAUDE.md |
Updates architecture guidance. |
tests/test_reconcile_lineage.py |
Tests acceptance criteria and edge cases. |
tests/test_edges.py |
Tests step merging and misfits. |
tests/test_reconcile_report.py |
Tests lineage report rendering. |
tests/test_rules_report.py |
Tests activity-map rule inclusion. |
tests/test_activity_map.py |
Updates activity-map integration coverage. |
tests/lineage_fixtures.py |
Provides shared lineage fixtures. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
#577) A file every source of which names only the generic Activity (the step not known) gets no generated_by and no conflict: the link is held back until an activity-map row naming Activity is reviewed. The report counts such files per dataset (lineage.generic_only). No authored row names Activity today. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016p3omgkuHduf1UQjLH4K6t
…sitory, and say every way a file gets no step (#577) From a no-context review of PR #590: - A misfit two inputs show is counted once for its file. - With no catalog on the input, the latest catalog is chosen among this repository's lineage, not every repository's. - StepConflict, the report's conflict text, CLAUDE.md and the --no-evidence help name the generic-Activity conflict, generic_only and that lineage is excluded too; the report legend defines every outcome column. - Tests: an activity conflict through reconcile, another catalog's lineage not read, a misfit two inputs show. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016p3omgkuHduf1UQjLH4K6t
Contributor
There was a problem hiding this comment.
Copilot review overview
🟢 Approval recommended
The implementation matches the stated lineage contract and is supported by focused coverage of resolution, conflicts, provenance, reporting, and artifact preservation.
Review effort: Balanced
Findings: None
This was referenced Oct 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Closes #577
What changed
generated_byfrom the imported lineage (src/meta_disco/reconcile_lineage.py, new):file_idordrs_uri. An IGVF accession was already written as a DRS URI by the importer.edges.merge_stepsmerges those steps with inference's own (the name edges) into onegenerated_by:named_by.{source_type, rule_id: <activity-map row id>, activity_id?, source: ClaimSource}.generated_by: two activities, two parents in a role that takes one, or, beside a specific activity, a genericActivitystep whose parent no specific source names. The report lists them.edges.misfitsflags steps that don't fit their activity's declaration. The step is still written.reconcile_report.jsongains alineageblock, rendered as a new section ofdocs/reconcile-report.mdand the dashboard:offered,resolved,not_in_dataset,several_match,untranslated,sample_parent,child_not_in_run,child_several_match,parent_is_child;activities.agreedholds the "genericActivityagrees with any" rule.reconcile_lineage.appliesis the one catalog rule for slot evidence and lineage.output_utils.relative_toreplaces two copies of the same helper.tests/lineage_fixtures.pyholds the shared lineage test helpers.Why
Before this change, a file got a parent only from its own name: 224K index and checksum steps. For example, T2T_CHRY's 14K
.tbis whose name matches two VCFs got none (#438), and neither did any T2T QC file or IGVF derived file. The source tables say exactly which file each was made from. #577 joins that to the run, which is what #571 (inheritance) needs.Assumptions I made
These follow Dave's calls in the #577 walk-through, recorded in the issue:
parent_filestays required and comes from the parent record's own name.generated_by, including inference's own step. The report lists who said what.Activitystep (no authored row names it today), beside a specific activity, counts only where its parent matches one the specific source names; any other parent is an edge conflict. A file whose only steps are generic gets nogenerated_byand no conflict. It is counted asgeneric_onlyand held back until an activity-map row namingActivityis reviewed.lineage_evidencebeside--evidence-rootby default, so a test's temporary evidence root never reads the real lineage.--lineage-rootoverrides it, and--no-evidenceexcludes lineage too.used_biosample_id) are counted and wait for Record sample and donor lineage, SampleCollectionActivity and isBiologicalChildOf, in a run's edges.jsonl #582.parent_kindis read off the parent's name, as inference does. The misfit check judges the parent's inferreddata_typeinstead.output/run is read by the meta-disco browser, so that is a separate operational step after merge.How to verify
Reconcile a copy of the latest run, so the shared
output/is untouched:.tbithat Index files must not opine when two candidate parents share a filename #438 declines. In$C/reconciled/index_classifications.ndjson,simons_data_sample_207.chr17.hc.vcf.gz.tbi(two of them, one per assembly) hasgenerated_by.activity == "IndexActivity". Its oneindexedinput is its own VCF:parent_keyb8caea57…for one andad95dfaf…for the other. Itsnamed_bylistsrepository_activity(activity.indexing) andrepository_metadata(activity.t2t_index).HG01536.samtools.stats.txt(inremaining_classifications.ndjson) hasQualityControlActivitywithreported_on=HG01536.cram, a different CRAM key in ANVIL_T2T (3b029f77…) and in ANVIL_T2T_CHRY (2a4f10bf…).IGVFFI3781KRJF.baihasIndexActivitywithindexed=IGVFFI2580STXM.bam.IGVFFI2580STXM.bamhasAlignmentActivitywith three FASTQs asreads.HG01466.chr15.hc.vcf.gz.tbiin ANVIL_T2T_CHRY has one input withnamed_by=filename_rule,repository_metadata,repository_activity.uv run python scripts/generate_reconcile_report.py --run-dir $C --markdown $TMPDIR/rr.md --html $TMPDIR/rr.html. Section "Lineage: how each file was made" should show:repository_activity239,166 resolved, 84,029 sample parents and 8,246 untranslated;repository_metadata241,821 resolved and 15,017 untranslated;AlignmentActivitywith required rolereferencemissing, 4,396 files in all.shasum $R/*.json. Every slot result inreconcile_report.json(slots,values,conflicts,inputs) equals the shared run's existing report.make test.Definition of done, from #577:
.tbithat Index files must not opine when two candidate parents share a filename #438 declines, then its reconciledgenerated_byis anIndexActivitynamed byanvil_activity, with its VCF'sparent_key. → step 1 (andtest_ac1_*)HG01536.samtools.stats.txton a T2Tparticipantrow, then it has aQualityControlActivitywith its own row's CRAM. Given a sample with a GRCh38 and a CHM13 CRAM, then each stats file gets the CRAM on its own row. → step 2 (andtest_ac2_*)IGVFFI3781KRJF.bai, then it hasIndexActivity→IGVFFI2580STXM.bam, and that BAM hasAlignmentActivitywith its FASTQs asreads. → step 3 (andtest_ac3_*).tbiwhose name andanvil_activityname one VCF, then it has one input with both sources innamed_by. Given they name different VCFs, then the report lists an edge conflict. → step 4, andtest_ac4_*for the conflict (0 occur in the real data)test_ac5_*)🤖 Generated with Claude Code
https://claude.ai/code/session_016p3omgkuHduf1UQjLH4K6t