Skip to content

fix: five verified defects from the Broad-Side self-scan (#128-#132) - #145

Merged
TheAmericanMaker merged 3 commits into
mainfrom
fix/128-132-broadside-scan-fixes
Aug 23, 2026
Merged

fix: five verified defects from the Broad-Side self-scan (#128-#132)#145
TheAmericanMaker merged 3 commits into
mainfrom
fix/128-132-broadside-scan-fixes

Conversation

@TheAmericanMaker

Copy link
Copy Markdown
Member

Five real defects found by running Broad-Side (the batch scout in PR #144) on its own repo, each verified by hand before fixing:

# Fix
#128 writeLibraryConfig derives the parent dir with dirname() — the forward-slash match treated Windows paths as bare filenames and ENOENT'd on write
#130 isWithinPath respects roots ending in a separator (/, C:\) instead of producing a double-separator prefix that rejected every subpath
#131 acquireLock closes the lock descriptor when writeFile throws instead of leaking it to GC
#129 the phase runner settles the compaction promise with false when no compaction fired, so continuations stop paying the full 30s settle timeout
#132 completeValidatedPhase re-validates the output inside the status lock — a stale PASS whose output changed refuses with a clear error and leaves status untouched (validations with no outputPath keep the legacy synthetic path used by unit tests)

6 regression tests in tests/broadside-scan-fixes.test.mjs, each failing without its fix. 348/348 passing.

Closes #128, #129, #130, #131, #132.

James Sesler and others added 3 commits August 23, 2026 04:53
- #128 writeLibraryConfig now derives its parent directory with
  dirname(), fixing ENOENT on Windows where the hand-rolled forward-slash
  match treated every path as a bare filename.
- #130 isWithinPath respects roots that already end in a separator
  (/, C:\), which previously produced a double-separator prefix that
  rejected every legitimate subpath.
- #131 acquireLock closes the lock descriptor on a write failure
  instead of leaking it until GC.
- #129 the phase runner settles the compaction promise with false when
  no compaction occurred, so continuation stops paying the full
  COMPACTION_SETTLE_TIMEOUT_MS on every non-compacted continue.
- #132 completeValidatedPhase re-validates the output inside the
  status lock: a stale PASS whose output changed since the caller's
  validation refuses with a clear error and leaves status untouched.

6 regression tests in tests/broadside-scan-fixes.test.mjs; 348 total.
…ards

The file's header said each test fails without its fix. Two do: #130 and
the #132 stale-PASS refusal. The other three pass against unfixed code —
#128 is a Windows-only path bug that POSIX cannot reproduce, #131 covers
acquire/release rather than the writeFile-throws path the fix guards, and
#129 exercises waitForCompaction rather than runPhase where the fix lives.

The fixes are still right; the claim about them was not. Saying so keeps
the next reader from assuming those three are protected against a
refactor that reverts them.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LmPQsx1esS4uHDzVbbKVZg
@TheAmericanMaker
TheAmericanMaker merged commit 8e6b893 into main Aug 23, 2026
5 checks passed
TheAmericanMaker added a commit that referenced this pull request Aug 23, 2026
…127)

deriveSlug uses only the trailing path segment of source_repo, so
acme/whisper and openai/whisper both produce "whisper" and publishing the
second wrote it as v2 of the first. The entry's version history then spanned
two unrelated codebases, and because buildIndexEntry reads only the newest
version's metadata, index.yaml attributed the whole entry to whichever repo
published last. Nothing reported it, at publish time or after.

publishEntry now reads the source_repo recorded on the newest version and
fails before writing anything when it denotes a different repository. The
check sits ahead of the content-hash branch: identical spec bytes take the
metadata-only path, which overwrote the other project's source_repo and
headline in place, so guarding only the new-version path would have left the
quieter half of the bug intact. forceNewVersion does not bypass it — that
option means "another version of this entry", not "overwrite a different
project". Unreadable or malformed recorded metadata skips the check, since
refusing on unknown would turn a corrupt v1 into an entry nobody can publish
to.

Comparison is normalized so re-publishing one repository spelled another way
is unaffected: scheme or none, embedded credentials (git@, user:token@),
git@host:path SCP syntax, a default port, a www. prefix, a trailing .git,
repeated and trailing slashes, and separators. A non-default port is kept,
since two services on one host can differ by port alone.

Case folds for hosts, for the repository paths the forges serve over them,
and for Windows drive paths — all case-insensitive. It does not fold for
absolute POSIX paths: /srv/Repos/tool and /srv/repos/tool are two
directories on a case-sensitive filesystem, and folding them let a genuine
two-project collision through the guard silently. Pi records the analyzed
directory as source_repo, so local paths are the common shape on that
surface. Host aliases (ssh.github.com) and provider-specific SSH path
layouts stay deliberately distinct; docs/library-format.md says so.

The refusal names where each remedy lives rather than telling the caller to
pass an option it may not expose — /codecarto-publish takes no arguments and
allow_source_repo_change is MCP-only. Closing that gap on Pi is #146.

library-format.md previously promised auto-suffixing (-2, -3) for this case.
It was never implemented, and silent suffixing is its own surprise for an
agent-driven tool, so the doc now describes the refusal and the override.

Twelve tests. Three fail with the guard disabled (the two-project collision,
the metadata-only path, forceNewVersion) and four more fail against the
pre-review normalization (the equivalence set, the case rules both ways, an
end-to-end refusal of two local directories differing only in case, and the
message contract). Merged main to resolve a CHANGELOG conflict under
[Unreleased]; no source overlap with #144 or #145. 395 tests passing, tsc
clean.

Fixes #123. Follow-ups filed: #146, #147, #148.
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.

writeLibraryConfig breaks on Windows: POSIX-only separator matching causes ENOENT

2 participants