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>
This commit is contained in:
Anthony Ettinger 2026-08-09 11:46:11 -07:00 committed by GitHub
parent 1bb7ba6e60
commit 3ab8a4b38b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
212 changed files with 17249 additions and 7 deletions

View file

@ -0,0 +1,185 @@
# Integration patterns
OpenContext never requires a specific LLM provider, framework, or database. These are the shapes people actually deploy.
## System prompts
The most direct use: resolve, render, prepend.
```ts
import { OpenContext, renderBundle } from "@logicsrc/opencontext";
const oc = await OpenContext.load("./opencontext.yaml");
const { bundle } = oc.resolve({ agent: "support-agent", task: userMessage });
const messages = [
{ role: "system", content: renderBundle(bundle, "markdown") },
{ role: "user", content: userMessage }
];
```
The Markdown renderer groups by layer, opens with a statement that everything below is context rather than instruction, and fences untrusted spans. Do not flatten it into raw text — the envelope is load-bearing. See [security](./security.md).
Record the digest alongside whatever the agent produces, and the decision stays reconstructable after the model is replaced.
## RAG pipelines
OpenContext is the **control plane above** retrieval, not a replacement for it.
```ts
// 1. Your retriever proposes candidates.
const candidates = await vectorStore.search(query, { k: 50 });
// 2. OpenContext decides what this consumer may actually see.
const scope = oc.scope({ agent: "support-agent" });
const allowed = candidates.filter((hit) => {
const object = oc.get(hit.id);
return object ? authorize(object, scope).allowed : false;
});
```
Two rules worth stating plainly:
- **Never rank before authorizing.** Embedding similarity has no idea what a role may read.
- **Retrieval rank is not authority.** The top hit is not thereby the truth; a `reference` note that scores well does not outrank a `canonical` policy.
A reasonable division of labour: vectors find *candidates*, OpenContext decides *eligibility* and *authority*, and the bundle is what reaches the model.
## MCP servers
MCP is complementary. A server exposes operations over the same resolution rules:
```txt
context.get one object, subject to authorization
context.search lexical search, authorized before results are returned
context.resolve a bundle for a consumer and task
context.list what exists in scope
context.explain why an object was included or excluded
```
MCP access **must use the same permission and resolution rules as the CLI and SDK**. An MCP server that resolves with a wider scope than the agent holds is a privilege escalation wearing a protocol.
Carry the caller's identity into `resolve({ agent })` rather than resolving unrestricted and filtering afterwards.
## Agent frameworks
Bundles are framework-neutral: resolve, render, hand over.
```ts
const bundle = oc.bundle({ agent: agentId, task });
// Any framework — the bundle is just text plus metadata.
agent.setSystemPrompt(renderBundle(bundle, "markdown"));
agent.setCapabilities(bundle.permissions ?? []);
```
`bundle.permissions` carries the capability strings the role holds, for your runtime to enforce. OpenContext transports and scopes them; it does not perform your application's actions.
For multi-agent systems, give each agent a role in the manifest rather than a bespoke prompt. A hand-written prompt is context that exists only inside that agent — exactly the state this specification exists to end.
## CLI agents
```bash
opencontext resolve --role support --task "$TASK" --format markdown > /tmp/context.md
my-agent --system /tmp/context.md "$TASK"
```
Discovery searches upward, so this works from any directory in the project.
## Human onboarding
The same bundle that briefs an agent briefs a person:
```bash
opencontext resolve --role support --format markdown > onboarding.md
```
If it is not good enough for a new hire, it is not good enough for an agent — and the reverse is the useful test for whether your context is actually written down.
## CI/CD
```yaml
name: OpenContext
on: [pull_request, push]
jobs:
context:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npx @logicsrc/opencontext validate --strict
- run: npx @logicsrc/opencontext doctor --strict
- run: npx @logicsrc/opencontext bundle --role support --output bundle.json
- uses: actions/upload-artifact@v4
with:
name: context-bundle
path: bundle.json
```
Useful gates:
```bash
opencontext conflicts --strict # reject duplicate canonical policies
opencontext stale --strict # reject expired required context
opencontext doctor --strict --min-score 95 # enforce a health floor
```
Publishing the bundle as a build artifact means every release records exactly what its agents knew.
## GitOps
Context lives in the repository and changes through pull requests, reviewed like code.
- A policy change is a diff a human approves.
- `opencontext diff` shows the governance-relevant fields, not every byte.
- `git://<rev>/<path>` reads context out of any past commit, offline.
- Branch protection on `context/` gives you approval workflow without a hosted service.
## API servers
```ts
app.post("/context/resolve", async (req, res) => {
const identity = await authenticate(req); // your IdP, not OpenContext
const { bundle } = oc.resolve({
agent: identity.agentId, // never from the request body
task: req.body.task
});
res.json(bundle);
});
```
Take the consumer identity from your authenticated session, never from the payload. OpenContext enforces what a *named* consumer may read; it does not authenticate who is asking.
## Recording decisions
```ts
const { bundle } = oc.resolve({ agent, task });
const answer = await model.complete(renderBundle(bundle, "markdown"), task);
oc.add({
id: `decisions.${today}-${slug}`,
type: "decision",
title,
decision: answer.decision,
rationale: answer.rationale,
decided_by: { type: "agent", id: agent },
bundle: { bundle_id: bundle.bundle_id, digest: bundle.digest }
}, { allowPromotion: false });
```
Note `allowPromotion: false`. An agent records a decision at ordinary authority; a human promotes it to `approved`. Observed context does not become truth automatically.
## Anti-patterns
**Copying context into a prompt template.** It drifts within a week, and nothing tells you.
**Resolving unrestricted and filtering later.** Unauthorized context has already been ranked, logged, and possibly cached.
**Treating a bundle as a cache.** It is a snapshot for one task at one instant. Re-resolve; it costs milliseconds.
**Writing back inferred context as canonical.** That is how a model's guess becomes company policy without anyone deciding.
**Stripping the untrusted envelope to save tokens.** The label is what stops a ticket from reading as an instruction.