test: clarify oc-path resolver assertions

This commit is contained in:
Peter Steinberger
2026-05-08 13:33:19 +01:00
parent 4baf472285
commit 20037285fb
3 changed files with 224 additions and 207 deletions

View File

@@ -11,82 +11,89 @@
* 5. parse → emit → parse is fixpoint
* 6. hostile inputs do not throw at parse time
*/
import { describe, expect, it } from 'vitest';
import { inferKind } from '../../dispatch.js';
import { emitMd } from '../../emit.js';
import { setMdOcPath } from '../../edit.js';
import { resolveMdOcPath } from '../../resolve.js';
import { emitJsonc } from '../../jsonc/emit.js';
import { setJsoncOcPath } from '../../jsonc/edit.js';
import { resolveJsoncOcPath } from '../../jsonc/resolve.js';
import { parseJsonc } from '../../jsonc/parse.js';
import { emitJsonl } from '../../jsonl/emit.js';
import { setJsonlOcPath } from '../../jsonl/edit.js';
import { resolveJsonlOcPath } from '../../jsonl/resolve.js';
import { parseJsonl } from '../../jsonl/parse.js';
import { parseOcPath } from '../../oc-path.js';
import { parseMd } from '../../parse.js';
import { describe, expect, it } from "vitest";
import { inferKind } from "../../dispatch.js";
import { setMdOcPath } from "../../edit.js";
import { emitMd } from "../../emit.js";
import { setJsoncOcPath } from "../../jsonc/edit.js";
import { emitJsonc } from "../../jsonc/emit.js";
import { parseJsonc } from "../../jsonc/parse.js";
import { resolveJsoncOcPath } from "../../jsonc/resolve.js";
import { setJsonlOcPath } from "../../jsonl/edit.js";
import { emitJsonl } from "../../jsonl/emit.js";
import { parseJsonl } from "../../jsonl/parse.js";
import { resolveJsonlOcPath } from "../../jsonl/resolve.js";
import { parseOcPath } from "../../oc-path.js";
import { parseMd } from "../../parse.js";
import { resolveMdOcPath } from "../../resolve.js";
describe('wave-22 cross-kind property invariants', () => {
const mdRaw = '---\nname: x\n---\n\n## Boundaries\n\n- enabled: true\n';
describe("wave-22 cross-kind property invariants", () => {
const mdRaw = "---\nname: x\n---\n\n## Boundaries\n\n- enabled: true\n";
const jsoncRaw = '// h\n{ "k": 1, "n": [1,2,3] }\n';
const jsonlRaw = '{"a":1}\n\nbroken\n{"b":2}\n';
it('P-01 round-trip parse → emit is byte-stable across all kinds', () => {
it("P-01 round-trip parse → emit is byte-stable across all kinds", () => {
expect(emitMd(parseMd(mdRaw).ast)).toBe(mdRaw);
expect(emitJsonc(parseJsonc(jsoncRaw).ast)).toBe(jsoncRaw);
expect(emitJsonl(parseJsonl(jsonlRaw).ast)).toBe(jsonlRaw);
});
it('P-02 resolve is non-mutating across all kinds', () => {
it("P-02 resolve is non-mutating across all kinds", () => {
const md = parseMd(mdRaw).ast;
let before = JSON.stringify(md);
resolveMdOcPath(md, parseOcPath('oc://X/[frontmatter]/name'));
resolveMdOcPath(md, parseOcPath('oc://X/boundaries'));
resolveMdOcPath(md, parseOcPath("oc://X/[frontmatter]/name"));
resolveMdOcPath(md, parseOcPath("oc://X/boundaries"));
expect(JSON.stringify(md)).toBe(before);
const jsonc = parseJsonc(jsoncRaw).ast;
before = JSON.stringify(jsonc);
resolveJsoncOcPath(jsonc, parseOcPath('oc://X/k'));
resolveJsoncOcPath(jsonc, parseOcPath('oc://X/n.0'));
resolveJsoncOcPath(jsonc, parseOcPath("oc://X/k"));
resolveJsoncOcPath(jsonc, parseOcPath("oc://X/n.0"));
expect(JSON.stringify(jsonc)).toBe(before);
const jsonl = parseJsonl(jsonlRaw).ast;
before = JSON.stringify(jsonl);
resolveJsonlOcPath(jsonl, parseOcPath('oc://X/L1'));
resolveJsonlOcPath(jsonl, parseOcPath('oc://X/$last'));
resolveJsonlOcPath(jsonl, parseOcPath("oc://X/L1"));
resolveJsonlOcPath(jsonl, parseOcPath("oc://X/$last"));
expect(JSON.stringify(jsonl)).toBe(before);
});
it('P-03 unresolvable set never throws across all kinds', () => {
const ocPath = parseOcPath('oc://X/totally.missing.path');
expect(() =>
setMdOcPath(parseMd(mdRaw).ast, ocPath, 'x'),
).not.toThrow();
expect(() =>
it("P-03 unresolvable set never throws across all kinds", () => {
const ocPath = parseOcPath("oc://X/totally.missing.path");
expect(setMdOcPath(parseMd(mdRaw).ast, ocPath, "x")).toEqual({
ok: false,
reason: "not-writable",
});
expect(
setJsoncOcPath(parseJsonc(jsoncRaw).ast, ocPath, {
kind: 'string',
value: 'x',
kind: "string",
value: "x",
}),
).not.toThrow();
expect(() =>
).toEqual({
ok: false,
reason: "unresolved",
});
expect(
setJsonlOcPath(parseJsonl(jsonlRaw).ast, ocPath, {
kind: 'string',
value: 'x',
kind: "string",
value: "x",
}),
).not.toThrow();
).toEqual({
ok: false,
reason: "unresolved",
});
});
it('P-04 inferKind aligns with the parser actually used', () => {
expect(inferKind('AGENTS.md')).toBe('md');
expect(inferKind('SOUL.md')).toBe('md');
expect(inferKind('config.jsonc')).toBe('jsonc');
expect(inferKind('plugins.json')).toBe('jsonc');
expect(inferKind('events.jsonl')).toBe('jsonl');
expect(inferKind('audit.ndjson')).toBe('jsonl');
it("P-04 inferKind aligns with the parser actually used", () => {
expect(inferKind("AGENTS.md")).toBe("md");
expect(inferKind("SOUL.md")).toBe("md");
expect(inferKind("config.jsonc")).toBe("jsonc");
expect(inferKind("plugins.json")).toBe("jsonc");
expect(inferKind("events.jsonl")).toBe("jsonl");
expect(inferKind("audit.ndjson")).toBe("jsonl");
});
it('P-05 parse → emit → parse is fixpoint across all kinds', () => {
it("P-05 parse → emit → parse is fixpoint across all kinds", () => {
const md1 = emitMd(parseMd(mdRaw).ast);
const md2 = emitMd(parseMd(md1).ast);
expect(md1).toBe(md2);
@@ -100,54 +107,52 @@ describe('wave-22 cross-kind property invariants', () => {
expect(jl1).toBe(jl2);
});
it('P-06 hostile inputs do not throw at parse time across all kinds', () => {
it("P-06 hostile inputs do not throw at parse time across all kinds", () => {
const hostile = [
'\x00\x01\x02 binary garbage',
"\x00\x01\x02 binary garbage",
'{ "unclosed":',
'## heading without anything',
'\n\n\n\n\n',
"## heading without anything",
"\n\n\n\n\n",
];
for (const raw of hostile) {
expect(() => parseMd(raw)).not.toThrow();
expect(() => parseJsonc(raw)).not.toThrow();
expect(() => parseJsonl(raw)).not.toThrow();
expect(parseMd(raw).ast.raw).toBe(raw);
expect(
parseJsonc(raw).diagnostics.every((diagnostic) => diagnostic.severity === "error"),
).toBe(true);
expect(parseJsonl(raw).ast.raw).toBe(raw);
}
});
it('P-07 resolver returns null for paths past valid kinds (no throw)', () => {
const overlong = parseOcPath('oc://X/a/b/c.d.e.f.g.h');
expect(() => resolveMdOcPath(parseMd(mdRaw).ast, overlong)).not.toThrow();
expect(() => resolveJsoncOcPath(parseJsonc(jsoncRaw).ast, overlong)).not.toThrow();
expect(() => resolveJsonlOcPath(parseJsonl(jsonlRaw).ast, overlong)).not.toThrow();
it("P-07 resolver returns null for paths past valid kinds", () => {
const overlong = parseOcPath("oc://X/a/b/c.d.e.f.g.h");
expect(resolveMdOcPath(parseMd(mdRaw).ast, overlong)).toBeNull();
expect(resolveJsoncOcPath(parseJsonc(jsoncRaw).ast, overlong)).toBeNull();
expect(resolveJsonlOcPath(parseJsonl(jsonlRaw).ast, overlong)).toBeNull();
});
it('P-08 set-then-resolve produces the value just written (jsonc)', () => {
it("P-08 set-then-resolve produces the value just written (jsonc)", () => {
const ast = parseJsonc('{ "k": 1 }').ast;
const r = setJsoncOcPath(ast, parseOcPath('oc://X/k'), {
kind: 'number',
const r = setJsoncOcPath(ast, parseOcPath("oc://X/k"), {
kind: "number",
value: 42,
});
if (r.ok) {
const m = resolveJsoncOcPath(r.ast, parseOcPath('oc://X/k'));
if (m?.kind === 'object-entry') {
expect(m.node.value).toEqual({ kind: 'number', value: 42 });
const m = resolveJsoncOcPath(r.ast, parseOcPath("oc://X/k"));
if (m?.kind === "object-entry") {
expect(m.node.value).toEqual({ kind: "number", value: 42 });
}
}
});
it('P-09 verbs are deterministic — same input twice produces same output', () => {
it("P-09 verbs are deterministic — same input twice produces same output", () => {
expect(emitMd(parseMd(mdRaw).ast)).toBe(emitMd(parseMd(mdRaw).ast));
expect(emitJsonc(parseJsonc(jsoncRaw).ast)).toBe(
emitJsonc(parseJsonc(jsoncRaw).ast),
);
expect(emitJsonl(parseJsonl(jsonlRaw).ast)).toBe(
emitJsonl(parseJsonl(jsonlRaw).ast),
);
expect(emitJsonc(parseJsonc(jsoncRaw).ast)).toBe(emitJsonc(parseJsonc(jsoncRaw).ast));
expect(emitJsonl(parseJsonl(jsonlRaw).ast)).toBe(emitJsonl(parseJsonl(jsonlRaw).ast));
});
it('P-10 inferKind returns null for unknown extensions', () => {
expect(inferKind('binary.bin')).toBeNull();
expect(inferKind('no-ext')).toBeNull();
expect(inferKind('archive.tar.gz')).toBeNull();
it("P-10 inferKind returns null for unknown extensions", () => {
expect(inferKind("binary.bin")).toBeNull();
expect(inferKind("no-ext")).toBeNull();
expect(inferKind("archive.tar.gz")).toBeNull();
});
});

View File

@@ -5,128 +5,136 @@
* with mixed dotted / segment paths, returns null on any unresolvable
* walk, and never throws on hostile inputs.
*/
import { describe, expect, it } from 'vitest';
import { parseJsonc } from '../../jsonc/parse.js';
import { resolveJsoncOcPath } from '../../jsonc/resolve.js';
import { parseOcPath } from '../../oc-path.js';
import { describe, expect, it } from "vitest";
import { parseJsonc } from "../../jsonc/parse.js";
import { resolveJsoncOcPath } from "../../jsonc/resolve.js";
import { parseOcPath } from "../../oc-path.js";
function rs(raw: string, ocPath: string) {
return resolveJsoncOcPath(parseJsonc(raw).ast, parseOcPath(ocPath));
}
describe('wave-17 jsonc resolver edges', () => {
it('JR-01 root resolves on empty object', () => {
expect(rs('{}', 'oc://config')?.kind).toBe('root');
describe("wave-17 jsonc resolver edges", () => {
it("JR-01 root resolves on empty object", () => {
expect(rs("{}", "oc://config")?.kind).toBe("root");
});
it('JR-02 root resolves on scalar root', () => {
expect(rs('42', 'oc://config')?.kind).toBe('root');
it("JR-02 root resolves on scalar root", () => {
expect(rs("42", "oc://config")?.kind).toBe("root");
});
it('JR-03 root resolves on array root', () => {
expect(rs('[1,2,3]', 'oc://config')?.kind).toBe('root');
it("JR-03 root resolves on array root", () => {
expect(rs("[1,2,3]", "oc://config")?.kind).toBe("root");
});
it('JR-04 deep dotted descent within section', () => {
const m = rs('{"a":{"b":{"c":1}}}', 'oc://config/a.b.c');
expect(m?.kind).toBe('object-entry');
it("JR-04 deep dotted descent within section", () => {
const m = rs('{"a":{"b":{"c":1}}}', "oc://config/a.b.c");
expect(m?.kind).toBe("object-entry");
});
it('JR-05 missing intermediate key returns null', () => {
expect(rs('{"a":{"b":1}}', 'oc://config/a.x.b')).toBeNull();
it("JR-05 missing intermediate key returns null", () => {
expect(rs('{"a":{"b":1}}', "oc://config/a.x.b")).toBeNull();
});
it('JR-06 numeric segment indexes into array', () => {
const m = rs('{"items":["a","b","c"]}', 'oc://config/items.1');
expect(m?.kind).toBe('value');
if (m?.kind === 'value') {
expect(m.node).toMatchObject({ kind: 'string', value: 'b' });
it("JR-06 numeric segment indexes into array", () => {
const m = rs('{"items":["a","b","c"]}', "oc://config/items.1");
expect(m?.kind).toBe("value");
if (m?.kind === "value") {
expect(m.node).toMatchObject({ kind: "string", value: "b" });
}
});
it('JR-07 negative array index resolves to Nth-from-last', () => {
expect(rs('{"x":[1,2]}', 'oc://config/x.-1')).toMatchObject({ kind: 'value', node: { kind: 'number', value: 2 } });
expect(rs('{"x":[1,2]}', 'oc://config/x.-2')).toMatchObject({ kind: 'value', node: { kind: 'number', value: 1 } });
expect(rs('{"x":[1,2]}', 'oc://config/x.-5')).toBeNull();
it("JR-07 negative array index resolves to Nth-from-last", () => {
expect(rs('{"x":[1,2]}', "oc://config/x.-1")).toMatchObject({
kind: "value",
node: { kind: "number", value: 2 },
});
expect(rs('{"x":[1,2]}', "oc://config/x.-2")).toMatchObject({
kind: "value",
node: { kind: "number", value: 1 },
});
expect(rs('{"x":[1,2]}', "oc://config/x.-5")).toBeNull();
});
it('JR-08 out-of-bounds array index returns null', () => {
expect(rs('{"x":[1,2]}', 'oc://config/x.99')).toBeNull();
it("JR-08 out-of-bounds array index returns null", () => {
expect(rs('{"x":[1,2]}', "oc://config/x.99")).toBeNull();
});
it('JR-09 non-integer index returns null (no NaN coercion)', () => {
expect(rs('{"x":[1,2]}', 'oc://config/x.foo')).toBeNull();
it("JR-09 non-integer index returns null (no NaN coercion)", () => {
expect(rs('{"x":[1,2]}', "oc://config/x.foo")).toBeNull();
});
it('JR-10 null AST root returns null on any path', () => {
expect(rs('', 'oc://config/x')).toBeNull();
it("JR-10 null AST root returns null on any path", () => {
expect(rs("", "oc://config/x")).toBeNull();
});
it('JR-11 descending past a primitive returns null', () => {
expect(rs('{"x":42}', 'oc://config/x.y')).toBeNull();
it("JR-11 descending past a primitive returns null", () => {
expect(rs('{"x":42}', "oc://config/x.y")).toBeNull();
});
it('JR-12 empty segment in dotted path throws OcPathError', () => {
it("JR-12 empty segment in dotted path throws OcPathError", () => {
// v1 invariant: malformed paths fail loud at parse time, not silently null.
expect(() => rs('{"x":1}', 'oc://config/x..y')).toThrow(/Empty dotted sub-segment/);
expect(() => rs('{"x":1}', "oc://config/x..y")).toThrow(/Empty dotted sub-segment/);
});
it('JR-13 string value at leaf surfaces via object-entry shape', () => {
const m = rs('{"k":"v"}', 'oc://config/k');
expect(m?.kind).toBe('object-entry');
if (m?.kind === 'object-entry') {expect(m.node.key).toBe('k');}
it("JR-13 string value at leaf surfaces via object-entry shape", () => {
const m = rs('{"k":"v"}', "oc://config/k");
expect(m?.kind).toBe("object-entry");
if (m?.kind === "object-entry") {
expect(m.node.key).toBe("k");
}
});
it('JR-14 boolean and null values resolve', () => {
const m1 = rs('{"k":true}', 'oc://config/k');
expect(m1?.kind).toBe('object-entry');
const m2 = rs('{"k":null}', 'oc://config/k');
expect(m2?.kind).toBe('object-entry');
it("JR-14 boolean and null values resolve", () => {
const m1 = rs('{"k":true}', "oc://config/k");
expect(m1?.kind).toBe("object-entry");
const m2 = rs('{"k":null}', "oc://config/k");
expect(m2?.kind).toBe("object-entry");
});
it('JR-15 mixed slash + dot segments resolve identically', () => {
const a = rs('{"a":{"b":{"c":1}}}', 'oc://config/a.b.c');
const b = rs('{"a":{"b":{"c":1}}}', 'oc://config/a/b.c');
const c = rs('{"a":{"b":{"c":1}}}', 'oc://config/a/b/c');
it("JR-15 mixed slash + dot segments resolve identically", () => {
const a = rs('{"a":{"b":{"c":1}}}', "oc://config/a.b.c");
const b = rs('{"a":{"b":{"c":1}}}', "oc://config/a/b.c");
const c = rs('{"a":{"b":{"c":1}}}', "oc://config/a/b/c");
expect(a?.kind).toBe(b?.kind);
expect(b?.kind).toBe(c?.kind);
});
it('JR-16 keys with special characters resolve', () => {
const m = rs('{"a-b_c":{"x":1}}', 'oc://config/a-b_c.x');
expect(m?.kind).toBe('object-entry');
it("JR-16 keys with special characters resolve", () => {
const m = rs('{"a-b_c":{"x":1}}', "oc://config/a-b_c.x");
expect(m?.kind).toBe("object-entry");
});
it('JR-17 unicode keys resolve', () => {
const m = rs('{"héllo":1}', 'oc://config/héllo');
expect(m?.kind).toBe('object-entry');
it("JR-17 unicode keys resolve", () => {
const m = rs('{"héllo":1}', "oc://config/héllo");
expect(m?.kind).toBe("object-entry");
});
it('JR-18 large nested structure (depth 20) resolves to leaf', () => {
it("JR-18 large nested structure (depth 20) resolves to leaf", () => {
let json = '"leaf"';
const segs: string[] = [];
for (let i = 19; i >= 0; i--) {
json = `{"k${i}":${json}}`;
segs.unshift(`k${i}`);
}
const m = rs(json, `oc://config/${segs.join('.')}`);
expect(m?.kind).toBe('object-entry');
if (m?.kind === 'object-entry') {
expect(m.node.value).toMatchObject({ kind: 'string', value: 'leaf' });
const m = rs(json, `oc://config/${segs.join(".")}`);
expect(m?.kind).toBe("object-entry");
if (m?.kind === "object-entry") {
expect(m.node.value).toMatchObject({ kind: "string", value: "leaf" });
}
});
it('JR-19 resolver is non-mutating across calls', () => {
it("JR-19 resolver is non-mutating across calls", () => {
const { ast } = parseJsonc('{"x":{"y":1}}');
const before = JSON.stringify(ast);
rs('{"x":{"y":1}}', 'oc://config/x.y');
rs('{"x":{"y":1}}', 'oc://config/x');
rs('{"x":{"y":1}}', 'oc://config/missing');
rs('{"x":{"y":1}}', "oc://config/x.y");
rs('{"x":{"y":1}}', "oc://config/x");
rs('{"x":{"y":1}}', "oc://config/missing");
expect(JSON.stringify(ast)).toBe(before);
});
it('JR-20 hostile input shapes do not throw', () => {
expect(() => rs('{garbage}', 'oc://config/x')).not.toThrow();
expect(() => rs('{"a":', 'oc://config/a')).not.toThrow();
it("JR-20 hostile input shapes do not throw", () => {
expect(rs("{garbage}", "oc://config/x")).toBeNull();
expect(rs('{"a":', "oc://config/a")).toBeNull();
});
});

View File

@@ -5,121 +5,125 @@
* deterministically; missing addresses, blank-line targets, and
* malformed-line targets all surface as null without throwing.
*/
import { describe, expect, it } from 'vitest';
import { parseJsonl } from '../../jsonl/parse.js';
import { resolveJsonlOcPath } from '../../jsonl/resolve.js';
import { parseOcPath } from '../../oc-path.js';
import { describe, expect, it } from "vitest";
import { parseJsonl } from "../../jsonl/parse.js";
import { resolveJsonlOcPath } from "../../jsonl/resolve.js";
import { parseOcPath } from "../../oc-path.js";
function rs(raw: string, ocPath: string) {
return resolveJsonlOcPath(parseJsonl(raw).ast, parseOcPath(ocPath));
}
describe('wave-18 jsonl resolver edges', () => {
it('JLR-01 root resolves with no segments', () => {
expect(rs('{"a":1}\n', 'oc://log')?.kind).toBe('root');
describe("wave-18 jsonl resolver edges", () => {
it("JLR-01 root resolves with no segments", () => {
expect(rs('{"a":1}\n', "oc://log")?.kind).toBe("root");
});
it('JLR-02 L1 resolves to a value line', () => {
const m = rs('{"a":1}\n', 'oc://log/L1');
expect(m?.kind).toBe('line');
it("JLR-02 L1 resolves to a value line", () => {
const m = rs('{"a":1}\n', "oc://log/L1");
expect(m?.kind).toBe("line");
});
it('JLR-03 L99 unknown line returns null', () => {
expect(rs('{"a":1}\n', 'oc://log/L99')).toBeNull();
it("JLR-03 L99 unknown line returns null", () => {
expect(rs('{"a":1}\n', "oc://log/L99")).toBeNull();
});
it('JLR-04 $last picks the most recent value line', () => {
const m = rs('{"a":1}\n{"a":2}\n{"a":3}\n', 'oc://log/$last/a');
expect(m?.kind).toBe('object-entry');
if (m?.kind === 'object-entry') {
expect(m.node.value).toMatchObject({ kind: 'number', value: 3 });
it("JLR-04 $last picks the most recent value line", () => {
const m = rs('{"a":1}\n{"a":2}\n{"a":3}\n', "oc://log/$last/a");
expect(m?.kind).toBe("object-entry");
if (m?.kind === "object-entry") {
expect(m.node.value).toMatchObject({ kind: "number", value: 3 });
}
});
it('JLR-05 $last skips trailing blank lines', () => {
const m = rs('{"a":1}\n\n\n', 'oc://log/$last/a');
expect(m?.kind).toBe('object-entry');
if (m?.kind === 'object-entry') {
expect(m.node.value).toMatchObject({ kind: 'number', value: 1 });
it("JLR-05 $last skips trailing blank lines", () => {
const m = rs('{"a":1}\n\n\n', "oc://log/$last/a");
expect(m?.kind).toBe("object-entry");
if (m?.kind === "object-entry") {
expect(m.node.value).toMatchObject({ kind: "number", value: 1 });
}
});
it('JLR-06 $last skips trailing malformed lines', () => {
const m = rs('{"a":1}\nbroken\n', 'oc://log/$last/a');
expect(m?.kind).toBe('object-entry');
it("JLR-06 $last skips trailing malformed lines", () => {
const m = rs('{"a":1}\nbroken\n', "oc://log/$last/a");
expect(m?.kind).toBe("object-entry");
});
it('JLR-07 $last on empty file returns null', () => {
expect(rs('', 'oc://log/$last/x')).toBeNull();
it("JLR-07 $last on empty file returns null", () => {
expect(rs("", "oc://log/$last/x")).toBeNull();
});
it('JLR-08 $last on all-blank file returns null', () => {
expect(rs('\n\n\n', 'oc://log/$last/x')).toBeNull();
it("JLR-08 $last on all-blank file returns null", () => {
expect(rs("\n\n\n", "oc://log/$last/x")).toBeNull();
});
it('JLR-09 $last on all-malformed file returns null', () => {
expect(rs('a\nb\nc\n', 'oc://log/$last/x')).toBeNull();
it("JLR-09 $last on all-malformed file returns null", () => {
expect(rs("a\nb\nc\n", "oc://log/$last/x")).toBeNull();
});
it('JLR-10 garbage line address returns null', () => {
expect(rs('{"a":1}\n', 'oc://log/garbage')).toBeNull();
expect(rs('{"a":1}\n', 'oc://log/L')).toBeNull();
expect(rs('{"a":1}\n', 'oc://log/Labc')).toBeNull();
it("JLR-10 garbage line address returns null", () => {
expect(rs('{"a":1}\n', "oc://log/garbage")).toBeNull();
expect(rs('{"a":1}\n', "oc://log/L")).toBeNull();
expect(rs('{"a":1}\n', "oc://log/Labc")).toBeNull();
});
it('JLR-11 descent into a blank line returns null', () => {
expect(rs('{"a":1}\n\n{"b":2}\n', 'oc://log/L2/anything')).toBeNull();
it("JLR-11 descent into a blank line returns null", () => {
expect(rs('{"a":1}\n\n{"b":2}\n', "oc://log/L2/anything")).toBeNull();
});
it('JLR-12 descent into a malformed line returns null', () => {
expect(rs('{"a":1}\nbroken\n{"b":2}\n', 'oc://log/L2/anything')).toBeNull();
it("JLR-12 descent into a malformed line returns null", () => {
expect(rs('{"a":1}\nbroken\n{"b":2}\n', "oc://log/L2/anything")).toBeNull();
});
it('JLR-13 missing field on a value line returns null', () => {
expect(rs('{"a":1}\n', 'oc://log/L1/missing')).toBeNull();
it("JLR-13 missing field on a value line returns null", () => {
expect(rs('{"a":1}\n', "oc://log/L1/missing")).toBeNull();
});
it('JLR-14 dotted descent through line value resolves', () => {
const m = rs('{"r":{"ok":true,"d":"x"}}\n', 'oc://log/L1/r.d');
expect(m?.kind).toBe('object-entry');
if (m?.kind === 'object-entry') {
expect(m.node.value).toMatchObject({ kind: 'string', value: 'x' });
it("JLR-14 dotted descent through line value resolves", () => {
const m = rs('{"r":{"ok":true,"d":"x"}}\n', "oc://log/L1/r.d");
expect(m?.kind).toBe("object-entry");
if (m?.kind === "object-entry") {
expect(m.node.value).toMatchObject({ kind: "string", value: "x" });
}
});
it('JLR-15 array index inside a line resolves', () => {
const m = rs('{"items":["a","b","c"]}\n', 'oc://log/L1/items.2');
expect(m?.kind).toBe('value');
if (m?.kind === 'value') {
expect(m.node).toMatchObject({ kind: 'string', value: 'c' });
it("JLR-15 array index inside a line resolves", () => {
const m = rs('{"items":["a","b","c"]}\n', "oc://log/L1/items.2");
expect(m?.kind).toBe("value");
if (m?.kind === "value") {
expect(m.node).toMatchObject({ kind: "string", value: "c" });
}
});
it('JLR-16 line numbers are 1-indexed', () => {
const m = rs('{"a":1}\n{"a":2}\n', 'oc://log/L1/a');
if (m?.kind === 'object-entry') {
expect(m.node.value).toMatchObject({ kind: 'number', value: 1 });
it("JLR-16 line numbers are 1-indexed", () => {
const m = rs('{"a":1}\n{"a":2}\n', "oc://log/L1/a");
if (m?.kind === "object-entry") {
expect(m.node.value).toMatchObject({ kind: "number", value: 1 });
}
});
it('JLR-17 line numbers preserved across blank/malformed entries', () => {
const m = rs('{"a":1}\n\nbroken\n{"a":4}\n', 'oc://log/L4/a');
expect(m?.kind).toBe('object-entry');
if (m?.kind === 'object-entry') {
expect(m.node.value).toMatchObject({ kind: 'number', value: 4 });
it("JLR-17 line numbers preserved across blank/malformed entries", () => {
const m = rs('{"a":1}\n\nbroken\n{"a":4}\n', "oc://log/L4/a");
expect(m?.kind).toBe("object-entry");
if (m?.kind === "object-entry") {
expect(m.node.value).toMatchObject({ kind: "number", value: 4 });
}
});
it('JLR-18 resolver is non-mutating', () => {
it("JLR-18 resolver is non-mutating", () => {
const { ast } = parseJsonl('{"a":1}\n{"b":2}\n');
const before = JSON.stringify(ast);
rs('{"a":1}\n{"b":2}\n', 'oc://log/L1');
rs('{"a":1}\n{"b":2}\n', 'oc://log/$last');
rs('{"a":1}\n{"b":2}\n', "oc://log/L1");
rs('{"a":1}\n{"b":2}\n', "oc://log/$last");
expect(JSON.stringify(ast)).toBe(before);
});
it('JLR-19 hostile inputs do not throw', () => {
expect(() => rs('not json\n', 'oc://log/L1')).not.toThrow();
expect(() => rs('', 'oc://log/$last')).not.toThrow();
it("JLR-19 hostile inputs do not throw", () => {
const malformed = rs("not json\n", "oc://log/L1");
expect(malformed?.kind).toBe("line");
if (malformed?.kind === "line") {
expect(malformed.node.kind).toBe("malformed");
}
expect(rs("", "oc://log/$last")).toBeNull();
});
});