Skip to content

Commit be2ddba

Browse files
[Agent: Claude] fix(lws): HEAD legacy branch mirrors the non-RDF source gate — no 200 where GET teaches 406
- negotiateHeadFileContentType's legacy JSON-LD branch (~line 1190) was still gated on bare isRdfContentType(storedContentType), which includes plain application/json — under --lws a stored .json HEAD with Accept: text/turtle or Accept: application/ld+json returned a lying 200 where GET correctly 406-teaches (reviewer-reproduced, final whole-round review Important #1) - fix: mirror GET's ternary (lwsEnabled ? isRdfSourceType : isRdfContentType) so under --lws plain JSON falls past the legacy branch to the F3 HEAD 406-teaching mirror; stored JSON-LD → JSON-LD target still passes; --lws-off stays byte-identical (ternary's else arm is the old predicate) - test/lws-nonrdf-teaching.test.js: two new HEAD/.json cases (Accept: text/turtle and Accept: application/ld+json), each asserting HEAD status equals the paired GET's 406 and content-type application/problem+json - verified: targeted 4-file run 33/33 pass; full suite 1437 tests, 1436 pass, 0 fail, 1 pre-existing skip Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 71da6f0 commit be2ddba

2 files changed

Lines changed: 27 additions & 1 deletion

File tree

src/handlers/resource.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1183,7 +1183,11 @@ async function negotiateHeadFileContentType({ request, storagePath, urlPath, sta
11831183
|| negotiated === RDF_TYPES.N3
11841184
|| negotiated === 'application/n-triples';
11851185

1186-
if (isRdfContentType(storedContentType)) {
1186+
// Legacy JSON-LD gate mirrors GET's ternary (spec 2026-07-11 §2 parity):
1187+
// under --lws, narrowed to isRdfSourceType so plain application/json
1188+
// falls through to the F3 406-teaching gate below instead of a lying
1189+
// 200; --lws-off keeps the old bare isRdfContentType predicate.
1190+
if (lwsEnabled ? isRdfSourceType(storedContentType) : isRdfContentType(storedContentType)) {
11871191
const targetType = wantsTurtle ? 'text/turtle' : selectContentType(acceptHeader, connegEnabled);
11881192
if (!fitsFullRead) {
11891193
// Optimistic large-file path — see docstring.

test/lws-nonrdf-teaching.test.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,28 @@ describe('lws: teaching 406 on non-RDF sources', () => {
3333
assert.equal(r.status, 406);
3434
});
3535

36+
// Reviewer-reproduced bug (final whole-round review, Important #1): the
37+
// legacy HEAD branch gated on bare isRdfContentType (includes plain JSON),
38+
// so a stored .json HEAD lied 200 where GET correctly 406-teaches. Fixed
39+
// by mirroring GET's lwsEnabled ? isRdfSourceType : isRdfContentType ternary.
40+
it('HEAD parity: plain JSON + Accept: text/turtle → 406, matches GET, problem+json', async () => {
41+
const getRes = await request(`${base}/f3/d.json`, { headers: { Accept: 'text/turtle' }, auth: 'f3' });
42+
const headRes = await request(`${base}/f3/d.json`, { method: 'HEAD', headers: { Accept: 'text/turtle' }, auth: 'f3' });
43+
assert.equal(getRes.status, 406);
44+
assert.equal(headRes.status, getRes.status);
45+
assert.equal(headRes.headers.get('content-type').split(';')[0], 'application/problem+json');
46+
assert.equal(await headRes.text(), '');
47+
});
48+
49+
it('HEAD parity: plain JSON + Accept: application/ld+json → 406, matches GET, problem+json', async () => {
50+
const getRes = await request(`${base}/f3/d.json`, { headers: { Accept: 'application/ld+json' }, auth: 'f3' });
51+
const headRes = await request(`${base}/f3/d.json`, { method: 'HEAD', headers: { Accept: 'application/ld+json' }, auth: 'f3' });
52+
assert.equal(getRes.status, 406);
53+
assert.equal(headRes.status, getRes.status);
54+
assert.equal(headRes.headers.get('content-type').split(';')[0], 'application/problem+json');
55+
assert.equal(await headRes.text(), '');
56+
});
57+
3658
it('markdown + Accept: */* → 200 markdown, unchanged', async () => {
3759
const r = await request(`${base}/f3/card.md`, { headers: { Accept: 'text/html,application/xhtml+xml,*/*;q=0.8' }, auth: 'f3' });
3860
assert.equal(r.status, 200);

0 commit comments

Comments
 (0)