Skip to content

fix(sessions): freeze one entry per distinct session identity - #189

Merged
phodal merged 1 commit into
QoderAI:mainfrom
1339190177:fix/session-population-duplicate-identity
Sep 24, 2026
Merged

phodal merged 1 commit into
QoderAI:mainfrom
1339190177:fix/session-population-duplicate-identity

Conversation

@1339190177

@1339190177 1339190177 commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Summary

freezeSessionPopulation now keeps at most one entry per distinct trimmed
session identity, and dropped duplicate or empty-identity entries are recorded
in an additive binding omission field duplicateIdentitySessions. The
invariant population.sessions.length === binding.eligible.count now holds for
every provider without touching adapter discovery.

Why

  • Issue/Story: same failure family as [Bug]: lead lane always fails with SESSION_POPULATION_BINDING_MISMATCH on Claude while sessionEvidence succeeds #164 (no linked
    issue; this is a self-found, previously unreported defect).

  • User or maintainer outcome: the population binding counts eligible sessions
    by distinct trimmed sessionId, but the freeze stored the raw prepared
    inventory. A provider whose discovery can emit two entries with the same
    content-derived sessionId therefore freezes an inventory whose raw length
    contradicts its own binding count, and the Session facts lane
    (all-eligible scope counts), selectSessions, and every other raw-count
    consumer fail the whole bundle with SESSION_POPULATION_BINDING_MISMATCH —
    no report can ever be produced for that workspace.

    Static review of all 13 platform adapters: the reachable trigger today is
    Copilot — session identity comes from file content (workspace.yaml
    id:, then the session.start event's data.sessionId) rather than a
    unique filesystem location, and the discovery collector is a plain array.
    Two session-state directories that resolve to one content id (copied,
    re-synced, or resumed session state) reproduce the failure deterministically
    on current main. Augment shares the content-derived identity pattern but
    is not an evidence-bundle provider, so its duplicates cannot reach the
    freeze; the other eleven adapters are Map-keyed or path-derived and cannot
    emit duplicates.

Traceability and Scope

  • Spec/ADR: docs/specs/2026-09-24-session-population-duplicate-identity.md
  • Acceptance criteria addressed: AC-1 dedup with first-wins so sessions length
    equals binding count; AC-2 additive duplicateIdentitySessions omission
    field with unchanged fingerprints/schema/samePopulation; AC-3 the Copilot
    duplicate fixture freezes a one-entry population and the Session facts lane
    stays available with matching counts.
  • Canonical owners changed: scripts/session-analysis/session-population.mjs
    plus focused tests in test/sessions/session-population.test.mjs and
    test/reporting/better-harness-evidence-bundle.test.mjs, and the new spec.
  • Explicit non-goals: no adapter discovery or identity-semantics changes (no
    source-ref merging), no selection-strategy or lane-envelope changes, no
    binding schema version bump.

Change Type

  • Feature
  • Bug fix
  • Tests only
  • Documentation/community
  • Refactor with no intended behavior change
  • Dependency, packaging, or infrastructure

Test and Review Evidence

Check Result
End-to-end driver on a synthetic Copilot home (two session-state dirs, one shared content id), pre-fix bundle failed; Session facts lane and lead both SESSION_POPULATION_BINDING_MISMATCH; diagnostics conflict
Layer probe, pre-fix discovery 2 entries (same id) → population.sessions 2 vs binding.eligible.count 1 → selectSessions.eligibleCount 2
Same driver, post-fix population bound; both lanes available; omission.duplicateIdentitySessions: 1
New regression tests on the pre-fix implementation (fix stashed) red — 2 failed
CWD-inheritance regression test against the pre-fix last-wins candidate map red — 1 failed
npx vitest run test/sessions/session-population.test.mjs test/reporting/better-harness-evidence-bundle.test.mjs 45 passed
npm test (root suite) 1774 passed, 6 skipped, 0 failed
npm run pack:verify (the freeze module ships in the npm package) passed — npm 743 entries, runtime zip 1004
Claude end-to-end drivers (healthy lead / failing lead) unchanged behavior (bound + available; original error passthrough)

Manual or visual evidence: none (no rendered surface changed).

Risk and Recovery

  • Compatibility and cross-platform impact: single freeze-site change, platform
    neutral, no OS-specific code. Additive omission field;
    samePopulation compares only the pre-existing omission fields. For
    duplicate-free inventories (all current tests and providers except the
    trigger scenario) the frozen population is byte-identical in shape.
  • Package, plugin, schema, or generated-file impact: none (no packaged file
    list, schema version, or generated artifact changes).
  • Rollback or recovery path: revert the single commit; duplicate-identity
    workspaces return to the hard bundle failure.
  • Residual risk or unverified boundary: for a duplicated identity the freeze
    keeps the first entry's source refs; the dropped duplicate's evidence is not
    merged (recorded only as an omission count). Merging refs is adapter-level
    work and deliberately out of scope. Windows/macOS verified only through the
    platform-neutral unit and fixture contracts here; CI matrix is the
    authoritative receipt.

AI Involvement

  • Level: Generated
  • Human review and validation: the adapter audit, root-cause isolation, code,
    spec, and tests were produced by ZCode (GLM 5.3) under the fork owner's
    direction; the fork owner approved the change and this PR text. Red-green
    verification and the suite runs above are the recorded evidence.

Checklist

  • I followed AGENTS.md, CONTRIBUTING.md, and the relevant canonical-owner guidance.
  • The change is focused and does not include unrelated local or generated state.
  • Tests and documentation match the behavior actually delivered.
  • Markdown links were checked when documentation moved or changed (doc-link suite green in the full run).
  • Cross-platform behavior was considered for Windows, macOS, and Linux.
  • Package/runtime verification was run when shipped files or dependencies changed (no packaged surface touched).
  • User-facing or compatibility changes are recorded in CHANGELOG.md (not edited, following the repository's change-scope rule; happy to add an entry if maintainers prefer).
  • I have the right to contribute this work under the repository's MIT License.

The population binding counts eligible sessions by distinct trimmed
sessionId, but freezeSessionPopulation stored the raw prepared inventory,
so a provider that emits two entries with the same content-derived id
(reachable today via Copilot session-state directories that resolve to
one content id) made every raw-count consumer contradict the binding and
fail the whole bundle with SESSION_POPULATION_BINDING_MISMATCH.

The freeze now keeps at most one entry per distinct trimmed identity
(first-wins) and records dropped duplicate or empty-identity entries in
an additive binding omission field, duplicateIdentitySessions, so the
invariant population.sessions.length === eligible.count holds for every
provider without touching adapter discovery. The workspace-CWD candidate
map follows the same first-wins rule so the surviving entry never
inherits a dropped duplicate's CWD candidates.

Validated with new unit and real-fixture Copilot regression tests that
were verified red on the pre-fix implementation, the full root suite
(1774 tests), npm run pack:verify, and unchanged Claude end-to-end
behavior. Spec: docs/specs/2026-09-24-session-population-duplicate-identity.md,
related to QoderAI#164.

Co-authored-by: ZCode (GLM 5.3) <zcode@z.ai>
@1339190177
1339190177 force-pushed the fix/session-population-duplicate-identity branch from 627dddf to bfdf766 Compare September 24, 2026 02:01
@phodal
phodal merged commit a0dd528 into QoderAI:main Sep 24, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants