logicsrc/packages/opencontext/src/core.test.ts
Anthony Ettinger ec3ed64f20 Add the LogicSRC OpenContext specification
OpenContext is an open specification for durable, portable, permissioned,
provenance-aware context shared between humans and AI agents. It defines how
organizational knowledge is described, authorized, versioned, resolved,
audited, and handed between replaceable workers without losing institutional
state.

Follows the OpenPRD/OpenOntology pattern already in the repo: self-contained
JSON Schemas in @logicsrc/schemas, a reference implementation package, CLI
subcommands, docs, examples, and an OpenPRD record.

Schemas (8, all self-contained so a third party can fetch one file and
validate against it with no further resolution):
  manifest, object, bundle, role, provenance, decision, diagnostic,
  audit-event — registered in @logicsrc/validators and schemas:validate.

Reference implementation (@logicsrc/opencontext):
  loader with upward manifest discovery, the full resolution pipeline,
  authority/supersession, permissions, redaction, lifecycle, provenance,
  deterministic digests, doctor, search, graph, history/diff, guarded writes,
  audit events, and file/http/git/sqlite adapters.

CLI: all 15 specified commands, as a standalone `opencontext` binary and as
`logicsrc context`, sharing one implementation so the two cannot drift.

Design decisions worth noting:

- Supersession is declared, never inferred from version numbers. Inferring it
  would hide the governance failure it represents and make
  multiple-active-versions and duplicate-canonical impossible to detect.

- The bundle digest identifies the resolved context, not the moment it was
  computed, so generated_at/bundle_id/digest/as_of are excluded while objects,
  lifecycle states, exclusions and warnings are covered. That is what lets a
  decision record cite exactly the context that produced it.

- A role's own max_classification beats an inherited one, so a ceiling on a
  shared base role cannot silently cap a role deliberately granted more;
  requesting several roles at once still takes the lowest, so combining roles
  never escalates.

- Scope wildcards match whole dotted segments only. A trailing .* covers a
  subtree; an interior * matches exactly one segment. Substring matching here
  would be an access-control bug.

- --include narrows an existing scope and is applied after it, never merged
  into it, so a request can never widen what a role holds.

Verified: 226 tests across core primitives, permissions/redaction, the
resolution pipeline, security, the published conformance fixtures (13 valid,
35 invalid, 8 resolution scenarios), project behaviour, and the five shipped
examples — which are held to --strict and a 100% health score. Benchmarks meet
every published budget (resolve 1,000 objects in ~33ms against a 2s target).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 18:22:42 +00:00

237 lines
11 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { compareIds, deriveId, isValidId, isValidScopePattern, matchPattern, matchesPrincipal, parseRef } from "./ids.js";
import { globToRegExp, staticPrefix, normalizePattern } from "./glob.js";
import { formatAge, parseDuration, parseTimestamp, resolveAsOf } from "./time.js";
import { bundleIdFromDigest, canonicalJson, digestBundle, digestOf, sha256Uri } from "./digest.js";
import { parseContextDocument, findFieldLine } from "./parse.js";
import { defaultTypeFor } from "./store.js";
describe("id patterns", () => {
it("matches whole dotted segments only", () => {
expect(matchPattern("products.*", "products.enterprise")).toBe(true);
expect(matchPattern("products.*", "products")).toBe(true);
expect(matchPattern("products.*", "products.enterprise.pricing")).toBe(true);
// The bug this guards against: a prefix wildcard leaking into a sibling id
// that merely starts with the same characters would silently widen access.
expect(matchPattern("products.*", "products-internal")).toBe(false);
expect(matchPattern("products.*", "productsx.thing")).toBe(false);
});
it("treats * as everything and anything else as exact", () => {
expect(matchPattern("*", "anything.at.all")).toBe(true);
expect(matchPattern("mission", "mission")).toBe(true);
expect(matchPattern("mission", "mission.statement")).toBe(false);
});
it("matches exactly one segment for an interior wildcard", () => {
expect(matchPattern("customers.*.churn-risk", "customers.acme.churn-risk")).toBe(true);
expect(matchPattern("customers.*.churn-risk", "customers.northwind.churn-risk")).toBe(true);
// Interior wildcards must not span segments, or "this field on any record"
// would silently become "this whole subtree".
expect(matchPattern("customers.*.churn-risk", "customers.acme.eu.churn-risk")).toBe(false);
expect(matchPattern("customers.*.churn-risk", "customers.churn-risk")).toBe(false);
expect(matchPattern("customers.*.churn-risk", "customers.acme.plan")).toBe(false);
});
it("keeps a trailing wildcard meaning the whole subtree", () => {
expect(matchPattern("customers.*", "customers.acme.churn-risk")).toBe(true);
expect(matchPattern("customers.*.*", "customers.acme.eu.churn-risk")).toBe(true);
expect(matchPattern("*.churn-risk", "customers.churn-risk")).toBe(true);
expect(matchPattern("*.churn-risk", "customers.acme.churn-risk")).toBe(false);
});
it("validates scope patterns", () => {
expect(isValidScopePattern("customers.*.churn-risk")).toBe(true);
expect(isValidScopePattern("policies.support.*")).toBe(true);
expect(isValidScopePattern("*")).toBe(true);
expect(isValidScopePattern("Policies.*")).toBe(false);
expect(isValidScopePattern("policies.*support")).toBe(false);
});
it("parses and rejects references", () => {
expect(parseRef("policy.refunds")).toEqual({ id: "policy.refunds" });
expect(parseRef("policy.refunds@3")).toEqual({ id: "policy.refunds", version: 3 });
expect(parseRef("policy.refunds@v3")).toBeNull();
expect(parseRef("Policy.Refunds")).toBeNull();
});
it("validates ids", () => {
expect(isValidId("decision.2026-08-09-model-provider")).toBe(true);
expect(isValidId("policies.support.refund")).toBe(true);
expect(isValidId("Policy Refunds")).toBe(false);
expect(isValidId(".leading")).toBe(false);
});
it("matches principals including wildcards", () => {
expect(matchesPrincipal(["support"], ["support-agent", "support"])).toBe(true);
expect(matchesPrincipal(["*"], ["anyone"])).toBe(true);
expect(matchesPrincipal(["team.*"], ["team.support"])).toBe(true);
expect(matchesPrincipal(["finance"], ["support"])).toBe(false);
expect(matchesPrincipal(undefined, ["support"])).toBe(false);
expect(matchesPrincipal([], ["support"])).toBe(false);
});
it("derives ids from collection-relative paths", () => {
expect(deriveId("policies", "refunds.md")).toBe("policies.refunds");
expect(deriveId("policies", "support/refund.md")).toBe("policies.support.refund");
expect(deriveId("policies", "Refund Policy (v2).md")).toBe("policies.refund-policy-v2");
// index.md is the collection root, not a child named "index".
expect(deriveId("policies", "support/index.md")).toBe("policies.support");
});
it("orders ids stably", () => {
expect(compareIds("a", "b")).toBeLessThan(0);
expect(compareIds("b", "a")).toBeGreaterThan(0);
expect(compareIds("a", "a")).toBe(0);
});
});
describe("globs", () => {
it("expands ** across directories and * within one", () => {
expect(globToRegExp("context/policies/**").test("context/policies/a.md")).toBe(true);
expect(globToRegExp("context/policies/**").test("context/policies/sub/a.md")).toBe(true);
expect(globToRegExp("context/policies/*.md").test("context/policies/a.md")).toBe(true);
expect(globToRegExp("context/policies/*.md").test("context/policies/sub/a.md")).toBe(false);
});
it("lets **/ match zero directories", () => {
expect(globToRegExp("a/**/b.md").test("a/b.md")).toBe(true);
expect(globToRegExp("a/**/b.md").test("a/x/y/b.md")).toBe(true);
});
it("finds the static prefix", () => {
expect(staticPrefix("context/policies/**")).toBe("context/policies");
expect(staticPrefix("context/policies/*.md")).toBe("context/policies");
expect(staticPrefix("context/mission.md")).toBe("context");
});
it("normalises leading ./ and trailing /", () => {
expect(normalizePattern("./context/policies/")).toBe("context/policies");
});
});
describe("durations and timestamps", () => {
it("parses fixed-length durations", () => {
expect(parseDuration("30d")).toBe(30 * 86_400_000);
expect(parseDuration("12h")).toBe(12 * 3_600_000);
expect(parseDuration("1y")).toBe(365 * 86_400_000);
expect(parseDuration("30 days")).toBeNull();
expect(parseDuration(undefined)).toBeNull();
});
it("reads a bare date as the end of that day", () => {
// --at 2026-08-09 should include everything that happened during the 9th,
// not only what existed at midnight.
expect(resolveAsOf("2026-08-09").toISOString()).toBe("2026-08-09T23:59:59.999Z");
});
it("rejects an unparseable timestamp rather than defaulting to now", () => {
expect(() => resolveAsOf("last tuesday")).toThrow(/Invalid timestamp/);
});
it("parses RFC 3339 instants", () => {
expect(parseTimestamp("2026-08-09T15:00:00Z")?.toISOString()).toBe("2026-08-09T15:00:00.000Z");
expect(parseTimestamp("nonsense")).toBeNull();
});
it("formats ages", () => {
expect(formatAge(3_600_000 * 5)).toBe("5h");
expect(formatAge(86_400_000 * 10)).toBe("10d");
});
});
describe("canonical serialization and digests", () => {
it("sorts keys so key order cannot change a digest", () => {
expect(canonicalJson({ b: 1, a: 2 })).toBe('{"a":2,"b":1}');
expect(digestOf({ a: 1, b: 2 })).toBe(digestOf({ b: 2, a: 1 }));
});
it("drops undefined but preserves null", () => {
expect(canonicalJson({ a: undefined, b: null })).toBe('{"b":null}');
});
it("canonicalises nested structures and dates", () => {
expect(canonicalJson({ x: [{ b: 1, a: 2 }] })).toBe('{"x":[{"a":2,"b":1}]}');
expect(canonicalJson({ d: new Date("2026-08-09T00:00:00Z") })).toBe('{"d":"2026-08-09T00:00:00.000Z"}');
});
it("excludes clock and self-referential fields from a bundle digest", () => {
const base = { opencontext: "1.0", objects: [{ id: "a" }] };
const one = digestBundle({ ...base, generated_at: "2026-01-01T00:00:00Z", as_of: "2026-01-01T00:00:00Z", digest: "x", bundle_id: "ocb_1" });
const two = digestBundle({ ...base, generated_at: "2027-06-06T06:06:06Z", as_of: "2027-06-06T06:06:06Z", digest: "y", bundle_id: "ocb_2" });
expect(one).toBe(two);
});
it("changes the digest when the selected context changes", () => {
const one = digestBundle({ objects: [{ id: "a" }] });
const two = digestBundle({ objects: [{ id: "a" }, { id: "b" }] });
expect(one).not.toBe(two);
});
it("changes the digest when an exclusion changes", () => {
const one = digestBundle({ objects: [], excluded: [{ id: "a", reason: "not-in-scope" }] });
const two = digestBundle({ objects: [], excluded: [{ id: "a", reason: "permission-denied" }] });
expect(one).not.toBe(two);
});
it("derives a stable bundle id from the digest", () => {
const digest = sha256Uri("hello");
expect(bundleIdFromDigest(digest)).toMatch(/^ocb_[0-9a-f]{16}$/);
expect(bundleIdFromDigest(digest)).toBe(bundleIdFromDigest(digest));
});
it("refuses to canonicalise non-finite numbers", () => {
expect(() => canonicalJson({ x: Number.NaN })).toThrow(/non-finite/);
});
});
describe("document parsing", () => {
it("reads front matter and body", () => {
const parsed = parseContextDocument("---\nid: a\ntype: policy\n---\n\nBody text.\n", "a.md");
expect(parsed.object.id).toBe("a");
expect(parsed.object.content).toBe("Body text.\n");
expect(parsed.format).toBe("markdown");
});
it("accepts Markdown with no front matter as content", () => {
// Point OpenContext at an existing docs/ folder and it works; metadata is
// added where governance actually matters.
const parsed = parseContextDocument("Just prose.\n", "a.md");
expect(parsed.object.content).toBe("Just prose.\n");
expect(parsed.declaredKeys).toEqual([]);
});
it("does not let an empty body clobber declared content", () => {
const parsed = parseContextDocument("---\nid: a\ncontent: declared\n---\n\n", "a.md");
expect(parsed.object.content).toBe("declared");
});
it("parses YAML and JSON documents whole", () => {
expect(parseContextDocument("id: a\ntype: policy\n", "a.yaml").object.id).toBe("a");
expect(parseContextDocument('{"id":"a","type":"policy"}', "a.json").object.id).toBe("a");
});
it("reports parse errors with a file and a reason", () => {
expect(() => parseContextDocument("[1,2", "a.json")).toThrow(/Invalid JSON/);
expect(() => parseContextDocument("- a\n- b\n", "a.yaml")).toThrow(/Expected a context object/);
});
it("locates a field for diagnostics", () => {
expect(findFieldLine("---\nid: a\nowner: b\n---\n", "owner")).toBe(3);
expect(findFieldLine("id: a\n", "missing")).toBeUndefined();
});
});
describe("type defaults", () => {
it("de-pluralises a collection key", () => {
expect(defaultTypeFor("policies", true)).toBe("policy");
expect(defaultTypeFor("procedures", true)).toBe("procedure");
expect(defaultTypeFor("decisions", true)).toBe("decision");
expect(defaultTypeFor("knowledge", true)).toBe("knowledge");
// A context: entry is named by its key, which is already singular.
expect(defaultTypeFor("mission", false)).toBe("mission");
});
});