Skip to content

test(scripts): assert the AGENTS.md symlink the guard's coverage depends on - #1809

Merged
lilyshen0722 merged 1 commit into
mainfrom
kai/guard-agents-symlink
Sep 20, 2026
Merged

lilyshen0722 merged 1 commit into
mainfrom
kai/guard-agents-symlink

Conversation

@lilyshen0722

@lilyshen0722 lilyshen0722 commented Sep 20, 2026 •

Copy link
Copy Markdown
Contributor

Follow-up to #1807, from Vera's residual on it (pod message 70645) — deferred out of that PR so its two stamps stayed attached to a head that was already authorised for press. Extended while under wren's HOLD at e47bdfea (message 70661), which found the first version of this test asserting less than its own title.

The gap

The guard names CLAUDE.md as a scanned root file, and AGENTS.md is covered — but only because it is a symlink to it (git mode 120000, blob = the 9 characters CLAUDE.md), which the file's own comment says and nothing checked. Two events take AGENTS.md out of scope silently, with every other test still green:

it('AGENTS.md is a symlink to the scanned file, not a second copy', () => {
  const link = path.join(REPO_ROOT, 'AGENTS.md');
  expect(fs.lstatSync(link).isSymbolicLink()).toBe(true);
  const target = path.resolve(path.dirname(link), fs.readlinkSync(link));
  expect(SCANNED_ROOT_FILES.map((file) => path.join(REPO_ROOT, file))).toContain(target);
});

lstatSync, not statSync — stat follows the link and would report true for a regular file too. And the second assertion is the fix for the HOLD: isSymbolicLink() alone passes for ln -s README.md AGENTS.md, which is a symlink the scan does not follow. The target is derived from SCANNED_ROOT_FILES rather than hard-coded to CLAUDE.md, so the assertion and the scan's own notion of "the scanned file" cannot drift apart.

Also recorded, in the same comment

Her second finding, kept as a named limitation rather than left to be discovered: a parent-relative ../docs/….md is rejected by DOC_REF's lookbehind ((?<![A-Za-z0-9_/.-]) fails on the . before ..), and there are zero instances in the scanned dirs. Its target depends on the referencing file's directory, which this scan does not model, so widening the boundary without modelling that would trade a false negative for false positives.

Mutations

M5 AGENTS.md replaced by a real copy of CLAUDE.md   RED   (the real event)
M6 the symlink assertion inverted                    RED
M7 AGENTS.md retargeted to README.md                 RED   (wren's case)
restored                                             clean, 6/6

M5 and M7 are the two ways the file leaves scope, and each reddens only this test — nothing else in the suite notices. M6 shows the assertion is live rather than vacuously true.

Disclosed survivor: loosening the target assertion to expect(target.length).toBeGreaterThan(0) survives. That is the expected shape for M8 — removing a check is undetectable on a tree whose link target is correct — and it is not a gap: M7 is the mutation that supplies the wrong target and goes RED. Recording it because a survivor that is explained is evidence and a survivor that is not is a hole.

Test-only: backend/__tests__/ ⇒ no package version slot.

@samxu01
samxu01 force-pushed the kai/guard-agents-symlink branch 2 times, most recently from 4d7eafe to b87b8c1 Compare September 20, 2026 05:54
@lilyshen0722

Copy link
Copy Markdown
Contributor Author

Refresh: 4d7eafea → b87b8c12, onto 774076cb (#1786).

mergeStateStatus read BEHIND (branch protection is strict: true), so the branch could not be pressed as it stood. #1786 is docs-only and touches no file this PR touches; wren's merge-tree 0 read is confirmed — the one file in this PR's diff is this PR's own change.

Carry evidence, so the CLEAR at 4d7eafea does not have to be re-taken:

range-diff  c3234383..4d7eafea  origin/main..b87b8c12
            1: 4d7eafea = 1: b87b8c12 test(scripts): assert the AGENTS.md symlink…

patch-id --stable  old cacee7d3f900518ea188ae50ecc7fe670a03eb98
                   new cacee7d3f900518ea188ae50ecc7fe670a03eb98

One commit, one file (backend/__tests__/unit/scripts/docReferences.test.js, +28/−1 the same on both sides), Lily Shen on the noreply address for author and committer.

Re-run at b87b8c12: guard 6/6, and the mutation ledger re-executed at this head — M5 (real copy) RED, M7 (retargeted to README.md) RED, M6 (inverted assertion) RED, restored clean. M8 remains the disclosed survivor: loosening the target assertion is undetectable on a tree whose link target is correct, and M7 is what supplies the failing input.

@samxu01
samxu01 force-pushed the kai/guard-agents-symlink branch from b87b8c1 to 705669b Compare September 20, 2026 06:25
…nds on (vera 70645, wren 70661)

The scan names CLAUDE.md, and AGENTS.md is covered only because git mode
120000 makes it the same file. That is a convention the comment described and
nothing checked.

Two events take AGENTS.md out of scope, and both are asserted because
`isSymbolicLink()` alone only catches the first:

  - replaced by a real copy    -> not a symlink at all
  - retargeted (`ln -s README.md`) -> still a symlink, pointing somewhere the
    scan does not follow, so `isSymbolicLink()` passes while AGENTS.md has left
    scope. The target is now resolved against the link's own directory and must
    be one of SCANNED_ROOT_FILES, derived from that constant rather than
    hard-coded, so the title and the assertion cannot drift apart.

Also names, in the same comment, the other form the boundary rejects — a
parent-relative `../docs/….md` — as a known blind spot rather than leaving it
to be discovered; its target depends on the referencing file's directory,
which this scan does not model, and there are zero instances in the scanned
dirs.

Mutations: replace AGENTS.md with a real copy -> RED; retarget it to README.md
-> RED (wren's case, and the one the first version of this test let through);
invert the symlink assertion -> RED. Restored clean, 6/6.

Disclosed survivor: loosening the target assertion to `target.length > 0`
survives, because removing a check is undetectable on a tree whose link target
is correct — the retarget mutation is what supplies the failing input, and it
is the case that goes RED.

Held as a follow-up rather than folded into #1807: that head carried two
reviewers' stamps and the press was authorised on it.
@lilyshen0722
lilyshen0722 force-pushed the kai/guard-agents-symlink branch from 705669b to 08cb985 Compare September 20, 2026 07:24
@lilyshen0722
lilyshen0722 merged commit cf0f2b6 into main Sep 20, 2026
14 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.

1 participant