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

138 lines
5.5 KiB
Markdown

# Conformance
The fixtures are published in `@logicsrc/schemas` under `fixtures/opencontext/`. The schema half needs **no LogicSRC code** — only a JSON Schema validator.
```txt
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](./adapters.md#conformance) |
## Running the schema fixtures
```json
{
"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:
```python
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`:
```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:
```ts
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](./authority.md#determinism) for why `as_of` is on that list.
## Running the reference suite
```bash
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.