Skip to content

watch: keep hub fallback community labels out of the tracked file (#3334) - #3551

Open
ayushcodes10 wants to merge 7 commits into
Graphify-Labs:v8from
ayushcodes10:fix-3334-labels-pending-sidecar
Open

ayushcodes10 wants to merge 7 commits into
Graphify-Labs:v8from
ayushcodes10:fix-3334-labels-pending-sidecar

Conversation

@ayushcodes10

Copy link
Copy Markdown
Contributor

Summary

Fixes #3334 (option 1 from the issue's own preference-ordered list: keep unlabelled communities out of the tracked file). graphify update names any community without a saved label after its deterministic hub (its highest-degree member — usually a filename like README.md, vite.config.ts), so reports and graph.json always show something readable. That fallback name was written straight into .graphify_labels.json alongside genuinely curated (manual or LLM) names — the file the project's own "Migration and portability" guidance says to commit as the curated semantic layer that makes label reuse work. Every from-empty rebuild (a fresh clone, a new worktree, a CI job with no prior graphify-out/) therefore appended fresh placeholder entries to that tracked file.

Hub-fallback names now live in a new, separate sidecar (.graphify_labels.pending.json) next to the tracked file. The tracked file only ever contains names someone actually set. A pending entry is still reused across rebuilds (not regenerated every run, matching the existing reuse guarantee curated labels already had) as long as the community's membership hasn't changed, and a graphify label pass promoting a pending community to a real name removes it from the sidecar in the same move. Reports and graph.json are unaffected — they already read from the full, merged label set, so a fallback-named community still shows its name exactly as before; only the on-disk tracked file's content changes.

Scoped to the graphify update/watch rebuild path only (graphify/watch.py), which is the issue's own reproduction. cli.py's equivalent cluster-only/label code paths carry a materially more complex reuse/--missing-only/LLM-dispatch flow; extending the same split there is a reasonable follow-up but is deliberately left out of this PR to keep the change bounded and reviewable.

Test plan

  • Updated the existing test_rebuild_code_drops_labels_whose_community_changed test, which had assumed all first-rebuild labels land in the tracked file.
  • Added three new tests: hub-fallback names stay out of the tracked file (and still reach graph.json's community_name); a pending name is reused, not regenerated, across an unrelated rebuild; and a promoted (curated) name correctly leaves the pending sidecar.
  • Verified all 4 updated/new tests fail against the pre-fix code and pass with the fix.
  • Manually reproduced the issue's exact scenario end to end (fresh corpus, first rebuild, curated one label, grow the corpus, rebuild again) and confirmed the curated label survives while fallback names correctly split across files.
  • Full suite: python3 -m pytest -q — 5489 passed, only the pre-existing unrelated failures (test_ollama_retry_cap.py missing openai in this env, one flaky timing assertion in test_ts_import_type_arguments.py).
  • python3 -m tools.skillgen --check — OK.

🤖 Generated with Claude Code

https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh

@graphify-labs graphify-labs Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).

Formal verification. No changes could be formally verified in this run.


Graphify review — findings

Splits community label persistence so hub-fallback names (used when a community has no curated name) now write to a separate .graphify_labels.pending.json sidecar instead of the tracked .graphify_labels.json, keeping the tracked file to genuinely curated names only. Fallback names are still merged into labels for report and graph.json generation, reused across rebuilds when membership is unchanged, and a curated name promotes its cid out of the pending file; the pending sidecar is deleted when nothing remains pending. Count-based staleness detection compares against the tracked file alone so a from-empty rebuild's fresh pending entries don't skew the "unchanged" signal.

No blocking issues surfaced. 6 lower-confidence candidates did not survive cross-model review.

Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 593 functions depend on the 407 functions this change touches.

Health — this change adds coupling hotspots:

  • new: _rebuild_code() — 119 callers, 51 callees
  • new: dispatch_command() — 2 callers, 124 callees
  • new: watch() — 5 callers, 7 callees
  • new: _reconcile_graph_html() — 6 callers, 5 callees
  • new: _reconcile_existing_graph() — 1 callers, 8 callees
  • new: _reconcile_markdown_links() — 1 callers, 6 callees
  • new: test_poisoned_manifest_is_healed() — 0 callers, 6 callees

Verification — 593 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 419 function(s) in the blast radius were not formally verified this run

Test selection

Test selection

3 of 275 test file(s) selected (1%) via static blast radius.

  • tests/test_labeling.py — impact
  • tests/test_watch.py — impact, changed-test
  • tests/test_watch_manifest_location.py — impact

Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.

Formal verification

Could not verify: Could not verify \_rebuild\_code.

The verifier did not have enough to check \_rebuild\_code, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `watch_path` is annotated `Path` — outside the synthesizable primitive/collection set

· 7 more finding(s) on lines outside this diff (see the check run).

ayushcodes10 and others added 6 commits September 16, 2026 14:04
A community without a curated name gets a deterministic hub fallback
so reports and graph.json always show something readable, but that
fallback has always been written straight into the same tracked file
as genuinely curated, manual or LLM, names. This is the read half of
splitting the two apart: a new sidecar next to the tracked labels
file is now also loaded and merged into the working labels dict, and
every cid that came from it, or gets freshly hub filled this run
rather than reused from the tracked file, is tracked in a separate
set so the write step can route each entry back to the file it
belongs in. Stale invalidation is extended to cover sidecar entries
the same way it already covers tracked ones. Toward Graphify-Labs#3334.

The write side, which is what actually stops fallback names from
landing in the tracked file, follows in the next commit; this commit
alone still writes the merged result into the tracked file as
before, so existing behavior is unchanged and no test needs updating
yet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
The write half of splitting the two label kinds apart. Labels are
split back into the tracked, curated set and the pending, hub
fallback set right before serialising, using the cid set the read
side built up. The tracked file gets only the curated names, exactly
as before this change set began; the pending set goes to the new
sidecar, or the sidecar is removed outright once nothing in it is
still unpromoted, so a stale, now empty pending file never lingers
once its last entry becomes a real label. Fixes Graphify-Labs#3334.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
This test simulates a naming pass by reading whatever cid list the
first rebuild produced and overwriting each with a distinctive name.
That list now comes from the pending sidecar rather than the tracked
file, matching what a real naming pass does: promote a pending, hub
fallback entry into a curated one, removing it from the sidecar in
the same move.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
A from empty rebuild on a two file corpus must leave the tracked
labels file empty while writing both communities' hub names to the
pending sidecar, and every node's community_name in graph.json must
still carry the right name regardless of which file it actually
lives in.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
A hand edited pending sidecar entry must survive an unrelated
rebuild untouched, matching the reuse guarantee already given to
curated labels, rather than getting silently regenerated from the
hub every single run.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
Simulates a real naming pass by promoting every pending entry into
the tracked file and deleting the sidecar, then forces a real
rebuild. The promoted names must survive, and the community must not
reappear in the sidecar even though it did not change membership.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
@ayushcodes10
ayushcodes10 force-pushed the fix-3334-labels-pending-sidecar branch from 13d8009 to 240b759 Compare September 16, 2026 08:36

@graphify-labs graphify-labs Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).

Formal verification. No changes could be formally verified in this run.


Graphify review — findings

Splits community naming into two files so unreviewed hub-fallback names stop polluting the tracked semantic layer: _rebuild_code now writes deterministic hub-fill names to a separate .graphify_labels.pending.json sidecar while .graphify_labels.json holds only genuinely curated (manual/LLM) names. Reports and graph.json still see the merged label set, so fallback names remain visible everywhere they were; on write, a community whose fallback got promoted to a curated name is dropped from the sidecar, and an empty sidecar is unlinked rather than left stale. Pending names are reused across rebuilds when membership is unchanged, and the "unchanged label count" heuristic now compares against the tracked file alone so a from-empty rebuild's fresh pending entries don't skew it.

No blocking issues surfaced. 4 lower-confidence candidates did not survive cross-model review.

Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 605 functions depend on the 415 functions this change touches.

Health — this change adds coupling hotspots:

  • new: _rebuild_code() — 126 callers, 53 callees
  • new: dispatch_command() — 2 callers, 125 callees
  • new: watch() — 5 callers, 7 callees
  • new: _reconcile_graph_html() — 6 callers, 5 callees
  • new: _reconcile_existing_graph() — 1 callers, 8 callees
  • new: _reconcile_markdown_links() — 1 callers, 6 callees
  • new: test_poisoned_manifest_is_healed() — 0 callers, 6 callees

Verification — 605 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 431 function(s) in the blast radius were not formally verified this run

Test selection

Test selection

4 of 283 test file(s) selected (1%) via static blast radius.

  • tests/test_labeling.py — impact
  • tests/test_terraform_modules.py — impact
  • tests/test_watch.py — impact, changed-test
  • tests/test_watch_manifest_location.py — impact

Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.

Formal verification

Could not verify: Could not verify \_rebuild\_code.

The verifier did not have enough to check \_rebuild\_code, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: no capturable inputs from the test suite; property tier: parameter `watch_path` is annotated `Path` — outside the synthesizable primitive/collection set

· 7 more finding(s) on lines outside this diff (see the check run).

Resolves a conflict in the rebuild's sidecar write block: upstream
restructured it after this branch diverged, switching report_path and
labels_file to atomic writes and relocating the labels_file write
further down to sit right before a new signatures sidecar it added,
with its own explanatory comment on why labels must land before
signatures (a crash between them must look stale, never falsely
current). Kept that structure and moved this branch's own pending
labels sidecar write to sit right after the relocated labels_file
write instead of its old position, using write_text_atomic for it too
to match the surrounding writes, and verified the existing atomicity
and label ordering regression test still passes with it inserted.
@github-actions github-actions Bot added the needs:title PR title should follow the conventional-commit format label Oct 10, 2026
@github-actions

Copy link
Copy Markdown

Thanks @ayushcodes10! One small thing: our changelog and release notes are built from PR titles, so we use the conventional-commits format, for example:

fix(python): resolve a bare stdlib import correctly

Prefixes we use: feat, fix, docs, chore, refactor, test, perf, build, ci. Editing the PR title clears this automatically. This is just a nudge and does not block review.

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

needs:title PR title should follow the conventional-commit format

Projects

None yet

Development

Successfully merging this pull request may close these issues.

update writes filename placeholder names into the tracked .graphify_labels.json for unlabelled communities

1 participant