logicsrc/docs/opencontext/conformance.md
Anthony Ettinger 3ab8a4b38b
Add the LogicSRC OpenContext specification (#132)
* 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>

* Point install docs at @logicsrc/opencontext; record the npm name collision

The unscoped `opencontext` name is already published on npm by an unrelated
third party (federicodeponte/opencontext, 2.0.0), so `npx opencontext` would
install a stranger's package. Docs now use `npx @logicsrc/opencontext`; the bin
stays named `opencontext` so the command reads as the PRD specifies once
installed.

Recorded in PRD 0003 as a blocker to resolve before any publication, along with
the fact that no @logicsrc spec package has ever been published, so there is no
existing release path to slot into.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Advance the logicsrc-mcp next-PRD-id assertion to 0004

standards.test.ts asserts prd_next_id against the live prd/ directory, so
adding PRD 0003 makes the next free id 0004. The test's own comment
anticipates this: "advances with every PRD added".

Caught by CI, not locally — the earlier verification ran per-package tests for
the packages this branch touches, and logicsrc-mcp is coupled to the PRD
directory without importing from it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 11:46:11 -07:00

5.5 KiB

Conformance

The fixtures are published in @logicsrc/schemas under fixtures/opencontext/. The schema half needs no LogicSRC code — only a JSON Schema validator.

fixtures/opencontext/
├── conformance.json      the manifest: what to run and what to expect
├── valid/                every fixture MUST validate
├── invalid/              every fixture MUST fail, for the stated reason
└── resolution/           self-contained projects pinning resolver behaviour

What a v1 implementation must do

  1. parse valid v1 manifests;
  2. validate required schema rules;
  3. resolve local file context;
  4. enforce include/exclude scopes;
  5. enforce deny-overrides-allow;
  6. calculate lifecycle state;
  7. process supersession;
  8. apply authority precedence;
  9. preserve provenance;
  10. emit canonical JSON Context Bundles;
  11. generate deterministic bundle digests;
  12. report canonical conflicts;
  13. pass the fixture suite.

Levels

Level Requires
Core Schema validation and local resolution
Resolver Full resolution pipeline and bundles
Tooling CLI-compatible commands, flags, and exit codes
Adapter The adapter contract

Running the schema fixtures

{
  "valid":   [{ "fixture": "valid/manifest.json", "kind": "opencontext-manifest" }],
  "invalid": [{ "fixture": "invalid/object-missing-type.json",
                "kind": "opencontext-object",
                "why": "type is required" }]
}

Every valid/ fixture must validate against its schema; every invalid/ fixture must fail. Each invalid fixture violates exactly one rule and states which, so a failing run tells you which rule your validator missed rather than merely that something is wrong.

Any language works:

import json, jsonschema

suite = json.load(open("fixtures/opencontext/conformance.json"))

for case in suite["valid"]:
    jsonschema.validate(load(case["fixture"]), schema_for(case["kind"]))

for case in suite["invalid"]:
    try:
        jsonschema.validate(load(case["fixture"]), schema_for(case["kind"]))
        raise AssertionError(f"{case['fixture']} should have failed: {case['why']}")
    except jsonschema.ValidationError:
        pass

Running the resolution scenarios

Schemas cannot express "an exclusion beats an include" or "stale context still resolves". The resolution/ scenarios do.

Each is a complete miniature project plus an expected.json:

{
  "description": "An exclude pattern beats an include that also matches. Deny overrides allow, unconditionally.",
  "resolve": { "role": "support", "at": "2026-08-09T12:00:00Z" },
  "expect": {
    "included": ["mission", "policies.refunds"],
    "excluded": [{ "id": "policies.internal.margins", "reason": "scope-exclusion" }]
  }
}
Scenario Pins
deny-overrides-allow An exclusion beats a matching include
classification-ceiling Classification bounds a role regardless of scope
object-permissions An object read grant narrows a role
supersession Superseded versions excluded; --include-historical returns them
lifecycle Expired and future excluded; stale resolved and warned
redaction Redaction after authorization; disclosure of that, not what
authority-conflict A settled conflict is still reported
duplicate-canonical Two active canonical objects for one id is an error

Assertion keys: included, objectCount, includedVersions, excluded (id + reason), warnings, lifecycle, redacted, contentAbsent, contentEquals. A scenario may also carry validate.expectDiagnostics and validate.expectFailure, and also for a second resolution against the same project.

Determinism

A conforming implementation must produce an identical digest for a repeated run over unchanged sources. The suite asserts this for every scenario:

const first = (await OpenContext.load(dir)).bundle(options);
const second = (await OpenContext.load(dir)).bundle(options);
expect(second.digest).toBe(first.digest);

The digest covers resolved objects, computed lifecycle states, exclusions, and warnings. It excludes generated_at, bundle_id, digest, and as_of — see authority for why as_of is on that list.

Running the reference suite

npm --workspace @logicsrc/opencontext test
npm --workspace @logicsrc/opencontext run bench

226 tests across seven files: core primitives, permissions and redaction, the resolution pipeline, security, the conformance fixtures, project-level behaviour, and the five shipped examples — which are held to --strict and a 100% health score, so a resolver change that quietly degrades a published example fails the build.

Performance targets

Local projects, measured by npm run bench against a 1,000-object repository:

Target Budget
Manifest parse < 100 ms
Validation of 1,000 objects < 2 s
Id lookup after load < 100 ms
Local resolution < 2 s
Network calls for a local-only project zero

The benchmark exits non-zero on a regression, so it can gate a release rather than merely inform one.

Claiming conformance

You may state that an implementation is "OpenContext compatible" when it passes the suite at a named level. Please say which level and which specification version, and keep the fixtures runnable in your CI so the claim stays true.

Official branding and conformance marks are reserved; truthful compatibility statements are not.