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>
This commit is contained in:
Anthony Ettinger 2026-08-09 18:22:42 +00:00
parent 1bb7ba6e60
commit ec3ed64f20
211 changed files with 17234 additions and 6 deletions

View file

@ -0,0 +1,279 @@
{
"opencontextConformance": "1.0",
"description": "Conformance fixtures for LogicSRC OpenContext. A third-party implementation can run these against the published JSON Schemas with no LogicSRC code: every valid fixture MUST validate, and every invalid fixture MUST fail for the stated reason. The resolution/ scenarios go further and pin resolver behaviour — scope, authority, supersession, lifecycle, and redaction — which schemas alone cannot express.",
"schemas": {
"manifest": "https://logicsrc.com/schemas/opencontext/manifest.schema.json",
"object": "https://logicsrc.com/schemas/opencontext/object.schema.json",
"bundle": "https://logicsrc.com/schemas/opencontext/bundle.schema.json",
"role": "https://logicsrc.com/schemas/opencontext/role.schema.json",
"provenance": "https://logicsrc.com/schemas/opencontext/provenance.schema.json",
"decision": "https://logicsrc.com/schemas/opencontext/decision.schema.json",
"diagnostic": "https://logicsrc.com/schemas/opencontext/diagnostic.schema.json",
"audit-event": "https://logicsrc.com/schemas/opencontext/audit-event.schema.json"
},
"valid": [
{
"fixture": "valid/manifest.json",
"kind": "opencontext-manifest"
},
{
"fixture": "valid/manifest-minimal.json",
"kind": "opencontext-manifest"
},
{
"fixture": "valid/object-policy.json",
"kind": "opencontext-object"
},
{
"fixture": "valid/object-minimal.json",
"kind": "opencontext-object"
},
{
"fixture": "valid/object-structured.json",
"kind": "opencontext-object"
},
{
"fixture": "valid/object-untrusted.json",
"kind": "opencontext-object"
},
{
"fixture": "valid/decision.json",
"kind": "opencontext-decision"
},
{
"fixture": "valid/role.json",
"kind": "opencontext-role"
},
{
"fixture": "valid/provenance.json",
"kind": "opencontext-provenance"
},
{
"fixture": "valid/provenance-canonical.json",
"kind": "opencontext-provenance"
},
{
"fixture": "valid/bundle.json",
"kind": "opencontext-bundle"
},
{
"fixture": "valid/diagnostic.json",
"kind": "opencontext-diagnostic"
},
{
"fixture": "valid/audit-event.json",
"kind": "opencontext-audit-event"
}
],
"invalid": [
{
"fixture": "invalid/manifest-missing-version.json",
"kind": "opencontext-manifest",
"why": "manifest has no opencontext version"
},
{
"fixture": "invalid/manifest-bad-id.json",
"kind": "opencontext-manifest",
"why": "namespace id must be a lowercase slug"
},
{
"fixture": "invalid/manifest-unknown-key.json",
"kind": "opencontext-manifest",
"why": "unknown top-level key (contexts vs context)"
},
{
"fixture": "invalid/manifest-bad-authority.json",
"kind": "opencontext-manifest",
"why": "authority precedence contains a level that is not defined"
},
{
"fixture": "invalid/manifest-bad-ttl.json",
"kind": "opencontext-manifest",
"why": "duration must look like 30d"
},
{
"fixture": "invalid/manifest-role-bad-pattern.json",
"kind": "opencontext-manifest",
"why": "a wildcard must be a whole segment, never a substring"
},
{
"fixture": "invalid/manifest-bad-extension.json",
"kind": "opencontext-manifest",
"why": "extension keys must be reverse-DNS namespaced"
},
{
"fixture": "invalid/object-missing-type.json",
"kind": "opencontext-object",
"why": "type is required"
},
{
"fixture": "invalid/object-missing-id.json",
"kind": "opencontext-object",
"why": "id is required"
},
{
"fixture": "invalid/object-bad-id.json",
"kind": "opencontext-object",
"why": "id must be a dotted lowercase slug"
},
{
"fixture": "invalid/object-unknown-authority.json",
"kind": "opencontext-object",
"why": "authority must be one of the six defined levels"
},
{
"fixture": "invalid/object-unknown-classification.json",
"kind": "opencontext-object",
"why": "classification must be public/internal/confidential/restricted"
},
{
"fixture": "invalid/object-unknown-trust.json",
"kind": "opencontext-object",
"why": "trust must be trusted/verified/untrusted"
},
{
"fixture": "invalid/object-extra-property.json",
"kind": "opencontext-object",
"why": "unknown field must go under extensions"
},
{
"fixture": "invalid/object-confidence-out-of-range.json",
"kind": "opencontext-object",
"why": "confidence must be between 0 and 1"
},
{
"fixture": "invalid/object-bad-digest.json",
"kind": "opencontext-object",
"why": "source digest must be sha256:<64 hex>"
},
{
"fixture": "invalid/object-bad-layer.json",
"kind": "opencontext-object",
"why": "layer must be L0 to L5"
},
{
"fixture": "invalid/object-source-without-uri.json",
"kind": "opencontext-object",
"why": "every source needs a uri"
},
{
"fixture": "invalid/object-bad-supersedes.json",
"kind": "opencontext-object",
"why": "version pin must be @<integer>"
},
{
"fixture": "invalid/object-bad-redaction-mode.json",
"kind": "opencontext-object",
"why": "redaction mode must be remove/mask/hash"
},
{
"fixture": "invalid/decision-missing-decision.json",
"kind": "opencontext-decision",
"why": "a decision record must state the decision"
},
{
"fixture": "invalid/decision-wrong-type.json",
"kind": "opencontext-decision",
"why": "type must be decision"
},
{
"fixture": "invalid/decision-bad-status.json",
"kind": "opencontext-decision",
"why": "status must be proposed/accepted/rejected/superseded/deprecated"
},
{
"fixture": "invalid/bundle-missing-digest.json",
"kind": "opencontext-bundle",
"why": "a bundle must carry a digest"
},
{
"fixture": "invalid/bundle-bad-digest.json",
"kind": "opencontext-bundle",
"why": "digest must be sha256:<64 hex>"
},
{
"fixture": "invalid/bundle-bad-id.json",
"kind": "opencontext-bundle",
"why": "bundle_id must be prefixed ocb_"
},
{
"fixture": "invalid/bundle-unknown-exclusion-reason.json",
"kind": "opencontext-bundle",
"why": "exclusion reasons are a closed set"
},
{
"fixture": "invalid/role-bad-classification.json",
"kind": "opencontext-role",
"why": "max_classification must be one of the four bands"
},
{
"fixture": "invalid/role-bad-include.json",
"kind": "opencontext-role",
"why": "scope patterns are lowercase"
},
{
"fixture": "invalid/provenance-no-source.json",
"kind": "opencontext-provenance",
"why": "must declare sources or canonical_source"
},
{
"fixture": "invalid/provenance-canonical-false.json",
"kind": "opencontext-provenance",
"why": "canonical_source: false does not satisfy the requirement"
},
{
"fixture": "invalid/diagnostic-unknown-code.json",
"kind": "opencontext-diagnostic",
"why": "diagnostic codes are normative and closed"
},
{
"fixture": "invalid/diagnostic-missing-ok.json",
"kind": "opencontext-diagnostic",
"why": "ok is required — it is what the exit code follows"
},
{
"fixture": "invalid/audit-event-unknown-event.json",
"kind": "opencontext-audit-event",
"why": "event names are a closed set"
},
{
"fixture": "invalid/audit-event-missing-actor.json",
"kind": "opencontext-audit-event",
"why": "an audit event without an actor is not attributable"
}
],
"resolution": [
{
"scenario": "resolution/authority-conflict",
"expected": "resolution/authority-conflict/expected.json"
},
{
"scenario": "resolution/classification-ceiling",
"expected": "resolution/classification-ceiling/expected.json"
},
{
"scenario": "resolution/deny-overrides-allow",
"expected": "resolution/deny-overrides-allow/expected.json"
},
{
"scenario": "resolution/duplicate-canonical",
"expected": "resolution/duplicate-canonical/expected.json"
},
{
"scenario": "resolution/lifecycle",
"expected": "resolution/lifecycle/expected.json"
},
{
"scenario": "resolution/object-permissions",
"expected": "resolution/object-permissions/expected.json"
},
{
"scenario": "resolution/redaction",
"expected": "resolution/redaction/expected.json"
},
{
"scenario": "resolution/supersession",
"expected": "resolution/supersession/expected.json"
}
]
}

View file

@ -0,0 +1,177 @@
[
{
"fixture": "invalid/manifest-missing-version.json",
"kind": "opencontext-manifest",
"why": "manifest has no opencontext version"
},
{
"fixture": "invalid/manifest-bad-id.json",
"kind": "opencontext-manifest",
"why": "namespace id must be a lowercase slug"
},
{
"fixture": "invalid/manifest-unknown-key.json",
"kind": "opencontext-manifest",
"why": "unknown top-level key (contexts vs context)"
},
{
"fixture": "invalid/manifest-bad-authority.json",
"kind": "opencontext-manifest",
"why": "authority precedence contains a level that is not defined"
},
{
"fixture": "invalid/manifest-bad-ttl.json",
"kind": "opencontext-manifest",
"why": "duration must look like 30d"
},
{
"fixture": "invalid/manifest-role-bad-pattern.json",
"kind": "opencontext-manifest",
"why": "a wildcard must be a whole segment, never a substring"
},
{
"fixture": "invalid/manifest-bad-extension.json",
"kind": "opencontext-manifest",
"why": "extension keys must be reverse-DNS namespaced"
},
{
"fixture": "invalid/object-missing-type.json",
"kind": "opencontext-object",
"why": "type is required"
},
{
"fixture": "invalid/object-missing-id.json",
"kind": "opencontext-object",
"why": "id is required"
},
{
"fixture": "invalid/object-bad-id.json",
"kind": "opencontext-object",
"why": "id must be a dotted lowercase slug"
},
{
"fixture": "invalid/object-unknown-authority.json",
"kind": "opencontext-object",
"why": "authority must be one of the six defined levels"
},
{
"fixture": "invalid/object-unknown-classification.json",
"kind": "opencontext-object",
"why": "classification must be public/internal/confidential/restricted"
},
{
"fixture": "invalid/object-unknown-trust.json",
"kind": "opencontext-object",
"why": "trust must be trusted/verified/untrusted"
},
{
"fixture": "invalid/object-extra-property.json",
"kind": "opencontext-object",
"why": "unknown field must go under extensions"
},
{
"fixture": "invalid/object-confidence-out-of-range.json",
"kind": "opencontext-object",
"why": "confidence must be between 0 and 1"
},
{
"fixture": "invalid/object-bad-digest.json",
"kind": "opencontext-object",
"why": "source digest must be sha256:<64 hex>"
},
{
"fixture": "invalid/object-bad-layer.json",
"kind": "opencontext-object",
"why": "layer must be L0 to L5"
},
{
"fixture": "invalid/object-source-without-uri.json",
"kind": "opencontext-object",
"why": "every source needs a uri"
},
{
"fixture": "invalid/object-bad-supersedes.json",
"kind": "opencontext-object",
"why": "version pin must be @<integer>"
},
{
"fixture": "invalid/object-bad-redaction-mode.json",
"kind": "opencontext-object",
"why": "redaction mode must be remove/mask/hash"
},
{
"fixture": "invalid/decision-missing-decision.json",
"kind": "opencontext-decision",
"why": "a decision record must state the decision"
},
{
"fixture": "invalid/decision-wrong-type.json",
"kind": "opencontext-decision",
"why": "type must be decision"
},
{
"fixture": "invalid/decision-bad-status.json",
"kind": "opencontext-decision",
"why": "status must be proposed/accepted/rejected/superseded/deprecated"
},
{
"fixture": "invalid/bundle-missing-digest.json",
"kind": "opencontext-bundle",
"why": "a bundle must carry a digest"
},
{
"fixture": "invalid/bundle-bad-digest.json",
"kind": "opencontext-bundle",
"why": "digest must be sha256:<64 hex>"
},
{
"fixture": "invalid/bundle-bad-id.json",
"kind": "opencontext-bundle",
"why": "bundle_id must be prefixed ocb_"
},
{
"fixture": "invalid/bundle-unknown-exclusion-reason.json",
"kind": "opencontext-bundle",
"why": "exclusion reasons are a closed set"
},
{
"fixture": "invalid/role-bad-classification.json",
"kind": "opencontext-role",
"why": "max_classification must be one of the four bands"
},
{
"fixture": "invalid/role-bad-include.json",
"kind": "opencontext-role",
"why": "scope patterns are lowercase"
},
{
"fixture": "invalid/provenance-no-source.json",
"kind": "opencontext-provenance",
"why": "must declare sources or canonical_source"
},
{
"fixture": "invalid/provenance-canonical-false.json",
"kind": "opencontext-provenance",
"why": "canonical_source: false does not satisfy the requirement"
},
{
"fixture": "invalid/diagnostic-unknown-code.json",
"kind": "opencontext-diagnostic",
"why": "diagnostic codes are normative and closed"
},
{
"fixture": "invalid/diagnostic-missing-ok.json",
"kind": "opencontext-diagnostic",
"why": "ok is required \u2014 it is what the exit code follows"
},
{
"fixture": "invalid/audit-event-unknown-event.json",
"kind": "opencontext-audit-event",
"why": "event names are a closed set"
},
{
"fixture": "invalid/audit-event-missing-actor.json",
"kind": "opencontext-audit-event",
"why": "an audit event without an actor is not attributable"
}
]

View file

@ -0,0 +1,5 @@
{
"opencontext": "1.0",
"event": "context.read",
"at": "2026-08-09T15:00:00Z"
}

View file

@ -0,0 +1,9 @@
{
"opencontext": "1.0",
"event": "context.peek",
"at": "2026-08-09T15:00:00Z",
"actor": {
"type": "agent",
"id": "a"
}
}

View file

@ -0,0 +1,11 @@
{
"opencontext": "1.0",
"bundle_id": "ocb_abc",
"generated_at": "2026-08-09T15:00:00Z",
"consumer": {
"type": "agent",
"id": "a"
},
"objects": [],
"digest": "sha1:abc"
}

View file

@ -0,0 +1,11 @@
{
"opencontext": "1.0",
"bundle_id": "bundle-1",
"generated_at": "2026-08-09T15:00:00Z",
"consumer": {
"type": "agent",
"id": "a"
},
"objects": [],
"digest": "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
}

View file

@ -0,0 +1,10 @@
{
"opencontext": "1.0",
"bundle_id": "ocb_abc",
"generated_at": "2026-08-09T15:00:00Z",
"consumer": {
"type": "agent",
"id": "a"
},
"objects": []
}

View file

@ -0,0 +1,17 @@
{
"opencontext": "1.0",
"bundle_id": "ocb_abc",
"generated_at": "2026-08-09T15:00:00Z",
"consumer": {
"type": "agent",
"id": "a"
},
"objects": [],
"excluded": [
{
"id": "x",
"reason": "vibes"
}
],
"digest": "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
}

View file

@ -0,0 +1,7 @@
{
"id": "decision.2026-08-09-x",
"type": "decision",
"title": "X",
"decision": "Do X.",
"status": "maybe"
}

View file

@ -0,0 +1,5 @@
{
"id": "decision.2026-08-09-x",
"type": "decision",
"title": "X"
}

View file

@ -0,0 +1,6 @@
{
"id": "decision.2026-08-09-x",
"type": "policy",
"title": "X",
"decision": "Do X."
}

View file

@ -0,0 +1,4 @@
{
"opencontext": "1.0",
"findings": []
}

View file

@ -0,0 +1,11 @@
{
"opencontext": "1.0",
"ok": false,
"findings": [
{
"code": "vibes-off",
"severity": "error",
"message": "x"
}
]
}

View file

@ -0,0 +1,10 @@
{
"opencontext": "1.0",
"id": "acme",
"authority": {
"precedence": [
"gospel",
"canonical"
]
}
}

View file

@ -0,0 +1,9 @@
{
"opencontext": "1.0",
"id": "acme",
"extensions": {
"risk": {
"score": 1
}
}
}

View file

@ -0,0 +1,4 @@
{
"opencontext": "1.0",
"id": "ACME Corp"
}

View file

@ -0,0 +1,7 @@
{
"opencontext": "1.0",
"id": "acme",
"freshness": {
"default_ttl": "30 days"
}
}

View file

@ -0,0 +1,3 @@
{
"id": "acme"
}

View file

@ -0,0 +1,11 @@
{
"opencontext": "1.0",
"id": "acme",
"roles": {
"support": {
"include": [
"policies.*support"
]
}
}
}

View file

@ -0,0 +1,5 @@
{
"opencontext": "1.0",
"id": "acme",
"contexts": {}
}

View file

@ -0,0 +1,10 @@
{
"id": "policy.refunds",
"type": "policy",
"sources": [
{
"uri": "https://example.com/p",
"digest": "md5:abc"
}
]
}

View file

@ -0,0 +1,4 @@
{
"id": "Policy Refunds",
"type": "policy"
}

View file

@ -0,0 +1,5 @@
{
"id": "policy.refunds",
"type": "policy",
"layer": "L6"
}

View file

@ -0,0 +1,10 @@
{
"id": "c.acme",
"type": "customer",
"redact": [
{
"path": "ssn",
"mode": "shred"
}
]
}

View file

@ -0,0 +1,7 @@
{
"id": "policy.refunds",
"type": "policy",
"supersedes": [
"policy.refunds@v2"
]
}

View file

@ -0,0 +1,5 @@
{
"id": "policy.refunds",
"type": "policy",
"confidence": 1.5
}

View file

@ -0,0 +1,5 @@
{
"id": "policy.refunds",
"type": "policy",
"authorship": "support"
}

View file

@ -0,0 +1,3 @@
{
"type": "policy"
}

View file

@ -0,0 +1,3 @@
{
"id": "policy.refunds"
}

View file

@ -0,0 +1,9 @@
{
"id": "policy.refunds",
"type": "policy",
"sources": [
{
"type": "document"
}
]
}

View file

@ -0,0 +1,5 @@
{
"id": "policy.refunds",
"type": "policy",
"authority": "gospel"
}

View file

@ -0,0 +1,5 @@
{
"id": "policy.refunds",
"type": "policy",
"classification": "secret"
}

View file

@ -0,0 +1,5 @@
{
"id": "policy.refunds",
"type": "policy",
"trust": "probably-fine"
}

View file

@ -0,0 +1,4 @@
{
"id": "policies.refunds",
"canonical_source": false
}

View file

@ -0,0 +1,3 @@
{
"id": "policies.refunds"
}

View file

@ -0,0 +1,4 @@
{
"id": "support",
"max_classification": "top-secret"
}

View file

@ -0,0 +1,6 @@
{
"id": "support",
"include": [
"Policies.*"
]
}

View file

@ -0,0 +1,10 @@
---
id: policies.refunds-observed
type: policy
layer: L3
authority: observed
owner: support
canonical_source: true
---
An agent observed staff granting refunds up to 60 days.

View file

@ -0,0 +1,12 @@
---
id: policies.refunds
type: policy
layer: L3
authority: canonical
owner: support
canonical_source: true
conflicts_with:
- policies.refunds-observed
---
Refunds within 30 days.

View file

@ -0,0 +1,21 @@
{
"description": "A declared conflict that authority settles is still reported, never silently hidden. Canonical outranks observed, and the losing side is named in the warning.",
"resolve": {
"role": "everyone",
"at": "2026-08-09T12:00:00Z"
},
"expect": {
"included": [
"policies.refunds",
"policies.refunds-observed"
],
"warnings": [
"conflict-declared"
]
},
"validate": {
"expectDiagnostics": [
"conflict-declared"
]
}
}

View file

@ -0,0 +1,11 @@
opencontext: "1.0"
id: conflict-test
name: Declared conflicts
collections:
policies: ./context/policies/**
roles:
everyone:
include:
- policies.*

View file

@ -0,0 +1,11 @@
---
id: docs.handbook
type: knowledge
layer: L2
authority: approved
owner: ops
classification: internal
canonical_source: true
---
How we work.

View file

@ -0,0 +1,11 @@
---
id: docs.litigation
type: knowledge
layer: L2
authority: approved
owner: legal
classification: restricted
canonical_source: true
---
Privileged and confidential.

View file

@ -0,0 +1,32 @@
{
"description": "Classification bounds a role regardless of scope: support includes docs.* and is still denied the restricted document, while legal is not.",
"resolve": {
"role": "support",
"at": "2026-08-09T12:00:00Z"
},
"expect": {
"included": [
"docs.handbook"
],
"excluded": [
{
"id": "docs.litigation",
"reason": "classification-denied"
}
]
},
"also": [
{
"resolve": {
"role": "legal",
"at": "2026-08-09T12:00:00Z"
},
"expect": {
"included": [
"docs.handbook",
"docs.litigation"
]
}
}
]
}

View file

@ -0,0 +1,16 @@
opencontext: "1.0"
id: classification-test
name: Classification ceiling
collections:
docs: ./context/docs/**
roles:
support:
include:
- docs.*
max_classification: internal
legal:
include:
- docs.*
max_classification: restricted

View file

@ -0,0 +1,10 @@
---
id: mission
type: mission
layer: L0
authority: canonical
owner: founders
canonical_source: true
---
Make refunds boring.

View file

@ -0,0 +1,10 @@
---
id: policies.internal.margins
type: policy
layer: L3
authority: canonical
owner: finance
canonical_source: true
---
Gross margin floor is 62%.

View file

@ -0,0 +1,10 @@
---
id: policies.refunds
type: policy
layer: L3
authority: canonical
owner: support
canonical_source: true
---
Refunds within 30 days.

View file

@ -0,0 +1,19 @@
{
"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"
}
]
}
}

View file

@ -0,0 +1,17 @@
opencontext: "1.0"
id: scope-test
name: Deny overrides allow
context:
mission: ./context/mission.md
collections:
policies: ./context/policies/**
roles:
support:
include:
- mission
- policies.*
exclude:
- policies.internal.*

View file

@ -0,0 +1,12 @@
---
id: policies.refunds
type: policy
layer: L3
version: 2
authority: canonical
owner: support
canonical_source: true
updated: 2026-08-01T00:00:00Z
---
Refunds within 60 days. Nobody added supersedes.

View file

@ -0,0 +1,12 @@
---
id: policies.refunds
type: policy
layer: L3
version: 1
authority: canonical
owner: support
canonical_source: true
updated: 2026-01-01T00:00:00Z
---
Refunds within 30 days.

View file

@ -0,0 +1,10 @@
{
"description": "Two active canonical objects for one id, with no supersession linking them. Canonical means exactly one source of truth, so this is an error a strict run must fail on.",
"validate": {
"expectDiagnostics": [
"duplicate-canonical",
"multiple-active-versions"
],
"expectFailure": true
}
}

View file

@ -0,0 +1,11 @@
opencontext: "1.0"
id: duplicate-test
name: Duplicate canonical
collections:
policies: ./context/policies/**
roles:
everyone:
include:
- policies.*

View file

@ -0,0 +1,11 @@
---
id: notes.current
type: note
layer: L2
authority: reference
owner: ops
canonical_source: true
updated: 2026-08-01T00:00:00Z
---
Fresh, inside the 30d window.

View file

@ -0,0 +1,12 @@
---
id: notes.expired
type: note
layer: L2
authority: reference
owner: ops
canonical_source: true
updated: 2026-08-01T00:00:00Z
expires: 2026-08-05T00:00:00Z
---
Expired before the resolution timestamp.

View file

@ -0,0 +1,12 @@
---
id: notes.future
type: note
layer: L2
authority: reference
owner: ops
canonical_source: true
updated: 2026-08-01T00:00:00Z
valid_from: 2027-01-01T00:00:00Z
---
Not valid until next year.

View file

@ -0,0 +1,11 @@
---
id: notes.stale
type: note
layer: L2
authority: reference
owner: ops
canonical_source: true
updated: 2026-01-01T00:00:00Z
---
Outside the 30d window, so stale — resolved anyway, and reported.

View file

@ -0,0 +1,30 @@
{
"description": "Lifecycle is computed against the resolution timestamp. Expired and not-yet-valid context is excluded; stale context is still resolved, and warned about \u2014 silence would be worse than staleness.",
"resolve": {
"role": "everyone",
"at": "2026-08-09T12:00:00Z"
},
"expect": {
"included": [
"notes.current",
"notes.stale"
],
"excluded": [
{
"id": "notes.expired",
"reason": "expired"
},
{
"id": "notes.future",
"reason": "not-yet-valid"
}
],
"warnings": [
"stale"
],
"lifecycle": {
"notes.current": "current",
"notes.stale": "stale"
}
}
}

View file

@ -0,0 +1,14 @@
opencontext: "1.0"
id: lifecycle-test
name: Lifecycle
collections:
notes: ./context/notes/**
freshness:
default_ttl: 30d
roles:
everyone:
include:
- notes.*

View file

@ -0,0 +1,12 @@
---
id: policies.payroll
type: policy
layer: L3
authority: canonical
owner: finance
canonical_source: true
permissions:
read: [finance]
---
Payroll runs on the 25th.

View file

@ -0,0 +1,10 @@
---
id: policies.refunds
type: policy
layer: L3
authority: canonical
owner: support
canonical_source: true
---
Refunds within 30 days.

View file

@ -0,0 +1,32 @@
{
"description": "An object-level read grant narrows a role that would otherwise include it. Both roles include policies.*; only finance may read the payroll policy.",
"resolve": {
"role": "support",
"at": "2026-08-09T12:00:00Z"
},
"expect": {
"included": [
"policies.refunds"
],
"excluded": [
{
"id": "policies.payroll",
"reason": "permission-denied"
}
]
},
"also": [
{
"resolve": {
"role": "finance",
"at": "2026-08-09T12:00:00Z"
},
"expect": {
"included": [
"policies.payroll",
"policies.refunds"
]
}
}
]
}

View file

@ -0,0 +1,14 @@
opencontext: "1.0"
id: permissions-test
name: Object-level permissions
collections:
policies: ./context/policies/**
roles:
support:
include:
- policies.*
finance:
include:
- policies.*

View file

@ -0,0 +1,21 @@
{
"id": "customers.acme",
"type": "customer",
"layer": "L2",
"title": "ACME Inc.",
"authority": "reference",
"owner": "support",
"classification": "confidential",
"canonical_source": true,
"content": {
"name": "ACME Inc.",
"plan": "enterprise",
"ssn": "000-00-0000",
"contacts": [
{
"name": "Dana",
"email": "dana@acme.example"
}
]
}
}

View file

@ -0,0 +1,28 @@
{
"description": "Redaction runs after authorization: the consumer is entitled to the record, and still does not receive the SSN. The bundle discloses that redaction happened without disclosing what was redacted.",
"resolve": {
"role": "support",
"at": "2026-08-09T12:00:00Z"
},
"expect": {
"included": [
"customers.acme"
],
"redacted": {
"customers.acme": [
"ssn",
"contacts[*].email"
]
},
"contentAbsent": {
"customers.acme": [
"ssn"
]
},
"contentEquals": {
"customers.acme": {
"contacts.0.email": "[REDACTED]"
}
}
}
}

View file

@ -0,0 +1,19 @@
opencontext: "1.0"
id: redaction-test
name: Redaction
collections:
customers: ./context/customers/**
roles:
support:
include:
- customers.*
max_classification: confidential
redact:
- path: ssn
mode: remove
reason: PII
- path: contacts[*].email
mode: mask
replacement: "[REDACTED]"

View file

@ -0,0 +1,12 @@
---
id: pricing.enterprise
type: policy
layer: L3
version: 1
authority: canonical
owner: sales
canonical_source: true
updated: 2026-01-01T00:00:00Z
---
Enterprise plans start at $1,800/month.

View file

@ -0,0 +1,14 @@
---
id: pricing.enterprise
type: policy
layer: L3
version: 2
authority: canonical
owner: sales
canonical_source: true
updated: 2026-08-01T00:00:00Z
supersedes:
- pricing.enterprise@1
---
Enterprise plans start at $2,500/month.

View file

@ -0,0 +1,33 @@
{
"description": "Superseded versions are retained on disk and excluded from default resolution; --include-historical brings them back so a past view can be reconstructed.",
"resolve": {
"role": "sales",
"at": "2026-08-09T12:00:00Z"
},
"expect": {
"included": [
"pricing.enterprise"
],
"includedVersions": {
"pricing.enterprise": 2
},
"excluded": [
{
"id": "pricing.enterprise",
"reason": "superseded"
}
]
},
"also": [
{
"resolve": {
"role": "sales",
"at": "2026-08-09T12:00:00Z",
"includeHistorical": true
},
"expect": {
"objectCount": 2
}
}
]
}

View file

@ -0,0 +1,11 @@
opencontext: "1.0"
id: supersession-test
name: Supersession
collections:
pricing: ./context/pricing/**
roles:
sales:
include:
- pricing.*

View file

@ -0,0 +1,26 @@
{
"opencontext": "1.0",
"event": "context.resolve",
"at": "2026-08-09T15:00:00Z",
"actor": {
"type": "agent",
"id": "support-agent",
"roles": [
"support"
],
"on_behalf_of": "dana@acme.example"
},
"task": "Handle refund request for ACME",
"objects": [
"mission",
"policies.refunds"
],
"bundle": {
"bundle_id": "ocb_3b541a2e1a933b60",
"digest": "sha256:eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
"object_count": 2
},
"outcome": "partial",
"reason": "3 objects excluded by scope",
"namespace": "acme"
}

View file

@ -0,0 +1,67 @@
{
"opencontext": "1.0",
"bundle_id": "ocb_3b541a2e1a933b60",
"generated_at": "2026-08-09T15:00:00Z",
"namespace": "acme",
"consumer": {
"type": "agent",
"id": "support-agent",
"roles": [
"support"
]
},
"task": "Handle refund request for ACME",
"as_of": "2026-08-09T15:00:00Z",
"objects": [
{
"id": "mission",
"type": "mission",
"layer": "L0",
"title": "Why ACME exists",
"content": "ACME exists to make refunds boring.",
"authority": "canonical",
"trust": "trusted",
"owner": "founders",
"lifecycle": "current",
"classification": "public"
}
],
"excluded": [
{
"id": "finance.payroll",
"reason": "permission-denied",
"detail": "outside the support scope"
},
{
"id": "policies.refunds.v1",
"reason": "superseded",
"outranked_by": "policies.refunds"
}
],
"warnings": [
{
"code": "stale",
"message": "procedures.refund is stale.",
"id": "procedures.refund",
"severity": "warning"
}
],
"provenance": [
{
"id": "mission",
"canonical_source": true
}
],
"permissions": [
"customer.read",
"ticket.write"
],
"stats": {
"considered": 12,
"included": 1,
"excluded": 2,
"redacted": 0,
"characters": 38
},
"digest": "sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
}

View file

@ -0,0 +1,51 @@
{
"id": "decision.2026-08-09-model-provider",
"type": "decision",
"layer": "L5",
"title": "Default model provider",
"authority": "approved",
"owner": "platform",
"status": "accepted",
"decision": "Use provider X as the default runtime.",
"rationale": [
"latency",
"cost",
"reliability"
],
"alternatives": [
{
"option": "provider Y",
"rejected_because": "no EU region"
}
],
"consequences": [
"Re-evaluate at renewal."
],
"approved_by": [
{
"role": "CTO",
"at": "2026-08-09T15:00:00Z"
}
],
"decided_by": {
"type": "human",
"id": "cto@acme.example"
},
"bundle": {
"bundle_id": "ocb_3b541a2e1a933b60",
"digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"generated_at": "2026-08-09T15:00:00Z"
},
"created": "2026-08-09T15:00:00Z",
"updated": "2026-08-09T15:00:00Z",
"durability": "long-lived",
"classification": "internal",
"canonical_source": true,
"references": [
"policies.vendor-selection"
],
"tags": [
"platform"
],
"version": 1
}

View file

@ -0,0 +1,37 @@
{
"opencontext": "1.0",
"ok": false,
"generated_at": "2026-08-09T15:00:00Z",
"namespace": "acme",
"score": 91.5,
"counts": {
"objects": 42,
"errors": 1,
"warnings": 3,
"info": 0,
"stale": 3,
"expired": 0,
"conflicting": 1,
"orphaned": 7,
"missing_owner": 3,
"broken_sources": 1
},
"findings": [
{
"code": "duplicate-canonical",
"severity": "error",
"message": "2 active canonical objects share the id \"policies.refunds\".",
"id": "policies.refunds",
"ids": [
"context/policies/refunds.md",
"context/policies/refunds-new.md"
],
"file": "context/policies/refunds.md",
"line": 3,
"field": "authority",
"expected": "exactly one canonical object per id",
"actual": 2,
"remediation": "Supersede the older one, or lower its authority to reference."
}
]
}

View file

@ -0,0 +1,4 @@
{
"opencontext": "1.0",
"id": "example"
}

View file

@ -0,0 +1,134 @@
{
"opencontext": "1.0",
"id": "acme",
"name": "ACME Corporation",
"description": "Every durable thing ACME's humans and agents need to know.",
"context": {
"mission": "./context/mission.md",
"glossary": "./context/glossary.md"
},
"collections": {
"policies": "./context/policies/**",
"procedures": {
"source": "./context/sops/**",
"type": "procedure",
"layer": "L4",
"owner": "support"
}
},
"roles": {
"support": {
"description": "Front-line support.",
"include": [
"mission",
"glossary",
"policies.*",
"procedures.*"
],
"exclude": [
"policies.internal.*"
],
"permissions": [
"customer.read",
"ticket.write"
],
"max_classification": "internal",
"redact": [
{
"path": "customer.ssn",
"mode": "remove",
"reason": "PII"
}
]
},
"finance": {
"include": [
"mission",
"policies.*"
],
"max_classification": "confidential",
"inherits": [
"support"
]
}
},
"agents": {
"support-agent": {
"roles": [
"support"
],
"description": "Handles tickets."
}
},
"authority": {
"precedence": [
"canonical",
"approved",
"reference",
"observed",
"inferred",
"historical"
],
"tie_breakers": [
"version",
"updated",
"confidence",
"id"
]
},
"freshness": {
"default_ttl": "30d",
"stale_is_error": false,
"exclude_expired": true
},
"provenance": {
"required": true,
"digest": "sha256",
"require_digest": false
},
"audit": {
"context_reads": true,
"context_writes": true,
"decisions": true,
"sink": "file://./context/.audit/events.ndjson"
},
"redact": [
{
"path": "payment.card",
"mode": "mask",
"replacement": "[REDACTED]"
}
],
"review": {
"interval": "180d",
"required_approvers": 2
},
"adapters": {
"https": {
"enabled": true,
"trust": "untrusted",
"timeout_ms": 5000
}
},
"defaults": {
"classification": "internal",
"trust": "trusted"
},
"health": {
"minimum_score": 90,
"fail_on": "error",
"require_owner": true,
"weights": {
"stale": 0.2
}
},
"related": {
"prd": "./openprd.yaml",
"topology": "./opentopology.yaml"
},
"extensions": {
"com.acme.region": {
"primary": "eu-west-1"
}
}
}

View file

@ -0,0 +1,4 @@
{
"id": "policy.refunds",
"type": "policy"
}

View file

@ -0,0 +1,75 @@
{
"id": "pricing.enterprise",
"type": "policy",
"layer": "L3",
"title": "Enterprise Pricing",
"content": "Enterprise plans start at $2,500/month.\n",
"content_type": "text/markdown",
"authority": "canonical",
"trust": "trusted",
"owner": "sales",
"status": "approved",
"version": 3,
"created": "2026-07-01T00:00:00Z",
"updated": "2026-08-09T00:00:00Z",
"valid_from": "2026-08-01T00:00:00Z",
"expires": null,
"durability": "long-lived",
"classification": "internal",
"permissions": {
"read": [
"sales-agent",
"finance-agent"
],
"write": [
"sales-admin"
]
},
"sources": [
{
"uri": "crm://pricing/enterprise",
"type": "canonical-record",
"retrieved_at": "2026-08-09T15:00:00Z",
"digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"trust": "verified"
}
],
"supersedes": [
"pricing.enterprise@2"
],
"confidence": 1.0,
"tags": [
"pricing",
"enterprise"
],
"applies_to": [
"sales"
],
"references": [
"policies.discounts"
],
"approval": {
"required": true,
"roles": [
"finance"
],
"minimum": 1,
"approved_by": [
{
"role": "finance",
"id": "cfo@acme.example",
"at": "2026-08-08T10:00:00Z"
}
]
},
"review": {
"interval": "180d",
"next_review": "2027-02-09",
"last_review": "2026-08-09"
},
"extensions": {
"com.acme.risk": {
"score": 0.25
}
}
}

View file

@ -0,0 +1,34 @@
{
"id": "customers.acme",
"type": "customer",
"layer": "L2",
"title": "ACME Inc.",
"content": {
"name": "ACME Inc.",
"plan": "enterprise",
"ssn": "000-00-0000",
"contacts": [
{
"name": "Dana",
"email": "dana@acme.example"
}
]
},
"content_type": "application/json",
"authority": "reference",
"owner": "support",
"classification": "confidential",
"redact": [
{
"path": "ssn",
"mode": "remove"
},
{
"path": "contacts[*].email",
"mode": "hash"
}
],
"canonical_source": true,
"durability": "operational",
"trust": "verified"
}

View file

@ -0,0 +1,19 @@
{
"id": "operations.ticket-4821",
"type": "operational",
"layer": "L5",
"title": "Ticket 4821",
"content_uri": "https://support.example.com/tickets/4821",
"authority": "observed",
"trust": "untrusted",
"owner": "support",
"durability": "session",
"classification": "internal",
"sources": [
{
"uri": "https://support.example.com/tickets/4821",
"type": "conversation",
"trust": "untrusted"
}
]
}

View file

@ -0,0 +1,4 @@
{
"id": "mission",
"canonical_source": true
}

View file

@ -0,0 +1,14 @@
{
"id": "policies.refunds",
"sources": [
{
"uri": "git://github.com/acme/context/policies/refunds.md",
"type": "document",
"retrieved_at": "2026-08-09T15:00:00Z",
"digest": "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
"trust": "trusted",
"author": "support"
}
],
"retrieved_at": "2026-08-09T15:00:00Z"
}

View file

@ -0,0 +1,38 @@
{
"id": "support",
"description": "Front-line customer support.",
"include": [
"mission",
"organization.public",
"products.*",
"customers.current",
"policies.support.*"
],
"exclude": [
"finance.payroll.*",
"legal.privileged.*"
],
"permissions": [
"customer.read",
"ticket.read",
"ticket.write"
],
"max_classification": "internal",
"redact": [
{
"path": "customer.ssn"
},
{
"path": "payment.card",
"mode": "mask"
}
],
"inherits": [
"everyone"
],
"extensions": {
"com.acme.tier": {
"level": 1
}
}
}

View file

@ -1,7 +1,7 @@
{
"name": "@logicsrc/schemas",
"version": "0.1.0",
"description": "LogicSRC JSON schemas for tasks, agents, runs, events, plugins, the AgentAd ad standard, and the OpenOntology knowledge contracts.",
"description": "LogicSRC JSON schemas for tasks, agents, runs, events, plugins, the AgentAd ad standard, the OpenOntology knowledge contracts, and the OpenContext context plane.",
"license": "MIT",
"type": "module",
"repository": {
@ -15,10 +15,12 @@
"agentad",
"agents",
"cli",
"context",
"json-schema",
"knowledge-graph",
"logicsrc",
"ontology",
"opencontext",
"openontology",
"standards"
],
@ -65,7 +67,16 @@
"./openontology-approval": "./schemas/logicsrc-openontology-approval.schema.json",
"./openontology-event": "./schemas/logicsrc-openontology-event.schema.json",
"./openontology-package": "./schemas/logicsrc-openontology-package.schema.json",
"./openontology-conformance": "./fixtures/openontology/conformance.json"
"./openontology-conformance": "./fixtures/openontology/conformance.json",
"./opencontext-manifest": "./schemas/logicsrc-opencontext-manifest.schema.json",
"./opencontext-object": "./schemas/logicsrc-opencontext-object.schema.json",
"./opencontext-bundle": "./schemas/logicsrc-opencontext-bundle.schema.json",
"./opencontext-role": "./schemas/logicsrc-opencontext-role.schema.json",
"./opencontext-provenance": "./schemas/logicsrc-opencontext-provenance.schema.json",
"./opencontext-decision": "./schemas/logicsrc-opencontext-decision.schema.json",
"./opencontext-diagnostic": "./schemas/logicsrc-opencontext-diagnostic.schema.json",
"./opencontext-audit-event": "./schemas/logicsrc-opencontext-audit-event.schema.json",
"./opencontext-conformance": "./fixtures/opencontext/conformance.json"
},
"files": [
"schemas",

View file

@ -0,0 +1,69 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://logicsrc.com/schemas/opencontext/audit-event.schema.json",
"title": "OpenContext Audit Event",
"description": "One recorded interaction with the context plane: a read, a bundle generation, a write, a conflict the resolver refused to guess about, or a decision. The specification defines the event shape and leaves storage to the implementation — an NDJSON file in the repository is a conforming sink, and so is a warehouse. The point is that after an agent is retired, its reads and writes remain attributable.",
"type": "object",
"required": ["opencontext", "event", "at", "actor"],
"additionalProperties": false,
"properties": {
"opencontext": { "type": "string", "pattern": "^\\d+\\.\\d+(\\.\\d+)?$" },
"event": {
"type": "string",
"enum": [
"context.read",
"context.search",
"context.resolve",
"context.bundle",
"context.write",
"context.supersede",
"context.conflict",
"context.denied",
"decision.record"
],
"description": "What happened. context.denied records an authorization refusal, which is usually the most interesting line in the log."
},
"at": { "type": "string", "format": "date-time", "description": "When it happened." },
"actor": {
"type": "object",
"required": ["type", "id"],
"additionalProperties": false,
"description": "Who did it. An agent acting for a person records both, so an action is never attributable to a model alone.",
"properties": {
"type": { "type": "string", "enum": ["agent", "human", "role", "service"] },
"id": { "type": "string", "minLength": 1 },
"roles": { "type": "array", "uniqueItems": true, "items": { "type": "string", "minLength": 1 } },
"on_behalf_of": { "type": "string", "description": "The human or service the actor was acting for." }
}
},
"task": { "type": "string", "description": "The task the context was being used for, when there was one." },
"objects": {
"type": "array",
"uniqueItems": true,
"items": { "type": "string", "minLength": 1 },
"description": "Context object ids the event touched."
},
"bundle": {
"type": "object",
"additionalProperties": false,
"description": "The bundle produced or consumed. The digest is what makes the record verifiable rather than merely descriptive.",
"properties": {
"bundle_id": { "type": "string", "pattern": "^ocb_[a-z0-9_-]+$" },
"digest": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" },
"object_count": { "type": "integer", "minimum": 0 }
}
},
"outcome": {
"type": "string",
"enum": ["allowed", "denied", "partial", "error"],
"description": "partial is the normal outcome of a resolution that excluded some candidates; denied means the consumer was refused everything it asked for."
},
"reason": { "type": "string", "description": "Why, especially for denied and error outcomes." },
"namespace": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]*$" },
"extensions": {
"type": "object",
"propertyNames": { "type": "string", "pattern": "^[a-z0-9]+(\\.[a-z0-9-]+)+$" },
"additionalProperties": true
}
}
}

View file

@ -0,0 +1,210 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://logicsrc.com/schemas/opencontext/bundle.schema.json",
"title": "OpenContext Context Bundle",
"description": "The portable output of resolution: the authorized, valid, current subset of context selected for one consumer and one task, with its provenance and a deterministic digest. JSON is the canonical interchange form. A bundle is the unit that makes agents replaceable — the same bundle handed to a different model or runtime carries the same organizational knowledge, and a decision can record exactly which context produced it by citing the digest.",
"type": "object",
"required": ["opencontext", "bundle_id", "generated_at", "consumer", "objects", "digest"],
"additionalProperties": false,
"properties": {
"opencontext": {
"type": "string",
"pattern": "^\\d+\\.\\d+(\\.\\d+)?$",
"description": "Specification version the bundle conforms to."
},
"bundle_id": {
"type": "string",
"pattern": "^ocb_[a-z0-9_-]+$",
"description": "Identifier for this bundle, prefixed ocb_. Derived from the digest by the reference implementation so identical inputs produce an identical id."
},
"generated_at": {
"type": "string",
"format": "date-time",
"description": "When the bundle was compiled. Excluded from the digest so that two runs over unchanged sources are byte-identical apart from this field."
},
"namespace": {
"type": "string",
"pattern": "^[a-z0-9][a-z0-9-]*$",
"description": "The manifest id the bundle was resolved from."
},
"consumer": {
"type": "object",
"required": ["type", "id"],
"additionalProperties": false,
"description": "Who this bundle was resolved for. Recorded so an audit can answer which agent saw which context.",
"properties": {
"type": { "type": "string", "enum": ["agent", "human", "role", "service"] },
"id": { "type": "string", "minLength": 1 },
"roles": {
"type": "array",
"uniqueItems": true,
"items": { "type": "string", "minLength": 1 },
"description": "Effective roles applied during authorization."
}
}
},
"task": {
"type": "string",
"description": "The task the context was resolved for, verbatim. Used for relevance ranking and recorded for reproducibility."
},
"as_of": {
"type": "string",
"format": "date-time",
"description": "The instant resolution was evaluated against. Lifecycle state, supersession, and validity windows are all judged at this timestamp, so passing --at reproduces a past view of context."
},
"objects": {
"type": "array",
"description": "The resolved context, in deterministic order: layer, then authority, then id. Content here has already been authorized, filtered, and redacted.",
"items": { "$ref": "#/$defs/bundledObject" }
},
"excluded": {
"type": "array",
"description": "What was considered and left out, and why. Populated when --explain is requested; a bundle with an empty excluded list is not a claim that nothing was excluded.",
"items": { "$ref": "#/$defs/exclusion" }
},
"warnings": {
"type": "array",
"description": "Non-fatal findings that survived into the bundle: stale context, unresolved canonical conflicts, missing provenance, untrusted content. Warnings are never silently dropped.",
"items": { "$ref": "#/$defs/warning" }
},
"provenance": {
"type": "array",
"description": "Flattened source records for every object in the bundle. Provenance MUST survive compilation, so this list stands on its own even if content was summarised.",
"items": { "$ref": "#/$defs/provenanceEntry" }
},
"permissions": {
"type": "array",
"uniqueItems": true,
"items": { "type": "string", "minLength": 1 },
"description": "Capability strings the consumer holds, carried through for the runtime to enforce."
},
"stats": {
"type": "object",
"additionalProperties": false,
"description": "Counters describing the resolution, useful for context budgeting.",
"properties": {
"considered": { "type": "integer", "minimum": 0 },
"included": { "type": "integer", "minimum": 0 },
"excluded": { "type": "integer", "minimum": 0 },
"redacted": { "type": "integer", "minimum": 0 },
"characters": { "type": "integer", "minimum": 0, "description": "Total characters of compiled content. A proxy for token cost that needs no tokenizer." }
}
},
"digest": {
"type": "string",
"pattern": "^sha256:[0-9a-f]{64}$",
"description": "sha256 over the canonical JSON of the bundle with generated_at and digest themselves omitted. Deterministic: identical source state and inputs produce an identical digest, which is what lets a decision record cite exactly the context that produced it."
},
"extensions": { "$ref": "#/$defs/extensions" }
},
"$defs": {
"bundledObject": {
"type": "object",
"required": ["id", "type"],
"additionalProperties": true,
"description": "A resolved context object. Carries the object's declared fields plus the state the resolver computed for it.",
"properties": {
"id": { "type": "string", "minLength": 1 },
"type": { "type": "string", "minLength": 1 },
"layer": { "type": "string", "enum": ["L0", "L1", "L2", "L3", "L4", "L5"] },
"title": { "type": "string" },
"content": { "anyOf": [{ "type": "string" }, { "type": "object" }, { "type": "array" }] },
"content_type": { "type": "string" },
"authority": { "type": "string", "enum": ["canonical", "approved", "reference", "observed", "inferred", "historical"] },
"trust": {
"type": "string",
"enum": ["trusted", "verified", "untrusted"],
"description": "Preserved through resolution. An integration MUST be able to tell canonical policy apart from text a stranger wrote into a ticket."
},
"owner": { "type": "string" },
"version": { "type": "integer", "minimum": 1 },
"updated": { "type": "string", "format": "date-time" },
"classification": { "type": "string", "enum": ["public", "internal", "confidential", "restricted"] },
"durability": { "type": "string", "enum": ["ephemeral", "session", "operational", "long-lived", "permanent"] },
"lifecycle": {
"type": "string",
"enum": ["future", "current", "stale", "expired", "superseded"],
"description": "Computed against as_of, not stored on the object."
},
"redacted": {
"type": "array",
"items": { "type": "string" },
"description": "Paths removed or masked before compilation. The bundle discloses that redaction happened without disclosing what was redacted."
},
"sources": { "type": "array", "items": { "type": "object" } },
"tags": { "type": "array", "items": { "type": "string" } },
"extensions": { "$ref": "#/$defs/extensions" }
}
},
"exclusion": {
"type": "object",
"required": ["id", "reason"],
"additionalProperties": false,
"description": "One object that was considered and rejected, with the pipeline stage that rejected it.",
"properties": {
"id": { "type": "string", "minLength": 1 },
"reason": {
"type": "string",
"enum": [
"permission-denied",
"classification-denied",
"scope-exclusion",
"not-in-scope",
"superseded",
"expired",
"not-yet-valid",
"outranked",
"unapproved",
"not-relevant",
"conflict",
"source-unavailable"
],
"description": "Why it was left out. permission-denied and classification-denied are authorization outcomes and are decided before any relevance work is done."
},
"detail": { "type": "string", "description": "Human-readable specifics, e.g. the pattern that excluded it or the object that outranked it." },
"outranked_by": { "type": "string", "description": "Id of the object that won, when reason is outranked or superseded." }
}
},
"warning": {
"type": "object",
"required": ["code", "message"],
"additionalProperties": false,
"properties": {
"code": { "type": "string", "pattern": "^[a-z][a-z0-9-]*$" },
"message": { "type": "string", "minLength": 1 },
"id": { "type": "string", "description": "Object the warning concerns, when it concerns one." },
"severity": { "type": "string", "enum": ["info", "warning", "error"], "default": "warning" }
}
},
"provenanceEntry": {
"type": "object",
"required": ["id"],
"additionalProperties": false,
"description": "Where one bundled object came from.",
"properties": {
"id": { "type": "string", "minLength": 1, "description": "The context object id." },
"canonical_source": { "type": "boolean", "description": "True when the object is itself the origin and has no upstream source." },
"sources": {
"type": "array",
"items": {
"type": "object",
"required": ["uri"],
"additionalProperties": true,
"properties": {
"uri": { "type": "string", "minLength": 1 },
"type": { "type": "string" },
"retrieved_at": { "type": "string", "format": "date-time" },
"digest": { "type": "string", "pattern": "^(sha256):[0-9a-f]{64}$" },
"trust": { "type": "string", "enum": ["trusted", "verified", "untrusted"] }
}
}
}
}
},
"extensions": {
"type": "object",
"propertyNames": { "type": "string", "pattern": "^[a-z0-9]+(\\.[a-z0-9-]+)+$" },
"additionalProperties": true
}
}
}

View file

@ -0,0 +1,229 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://logicsrc.com/schemas/opencontext/decision.schema.json",
"title": "OpenContext Decision Record",
"description": "A context object recording a decision that was made, why, by whom, and on what context. A decision record is what turns a resolution into organizational history: by citing the digest of the Context Bundle it was made from, it stays reproducible after the agent that made it is gone, the model is replaced, and the underlying policies have moved on. Decision records are ordinary context objects with type 'decision', so everything true of an object — authority, permissions, supersession, provenance — is also true here.",
"type": "object",
"required": ["id", "type", "title", "decision"],
"additionalProperties": false,
"properties": {
"id": {
"type": "string",
"pattern": "^[a-z0-9][a-z0-9_-]*(\\.[a-z0-9][a-z0-9_-]*)*$",
"description": "Stable id. Convention is decision.<date>-<slug>, e.g. decision.2026-08-09-model-provider, which sorts chronologically and never collides."
},
"type": { "type": "string", "const": "decision" },
"layer": {
"type": "string",
"enum": ["L0", "L1", "L2", "L3", "L4", "L5"],
"default": "L5",
"description": "Decisions are usually L5 operational when fresh; a decision that becomes standing policy is better superseded by an L3 policy object than silently re-labelled."
},
"title": { "type": "string", "minLength": 1, "description": "What was decided, in one line." },
"status": {
"type": "string",
"enum": ["proposed", "accepted", "rejected", "superseded", "deprecated"],
"description": "Where the decision stands. Only accepted decisions are resolved into bundles by default."
},
"decision": {
"type": "string",
"minLength": 1,
"description": "The decision itself, stated as a commitment rather than a discussion, e.g. 'Use provider X as the default runtime.'"
},
"rationale": {
"anyOf": [
{ "type": "string" },
{ "type": "array", "items": { "type": "string", "minLength": 1 } }
],
"description": "Why. A list of drivers (latency, cost, reliability) or a paragraph. This is the part that survives usefully once the people who were in the room have moved on."
},
"alternatives": {
"type": "array",
"description": "Options considered and not taken. Recording these stops the same option being relitigated every six months.",
"items": {
"type": "object",
"required": ["option"],
"additionalProperties": false,
"properties": {
"option": { "type": "string", "minLength": 1 },
"rejected_because": { "type": "string" }
}
}
},
"consequences": {
"anyOf": [
{ "type": "string" },
{ "type": "array", "items": { "type": "string", "minLength": 1 } }
],
"description": "What this commits the organization to, including the costs accepted."
},
"authority": {
"type": "string",
"enum": ["canonical", "approved", "reference", "observed", "inferred", "historical"],
"description": "Usually approved once a decision is accepted. A decision an agent proposed is inferred until a human approves it."
},
"owner": { "type": "string", "minLength": 1, "description": "Role or identity accountable for the decision." },
"approved_by": {
"type": "array",
"description": "Who signed off. An accepted decision with no approvals is reported by doctor.",
"items": {
"type": "object",
"additionalProperties": false,
"anyOf": [
{ "required": ["role"], "properties": { "role": { "type": "string" } } },
{ "required": ["id"], "properties": { "id": { "type": "string" } } }
],
"properties": {
"role": { "type": "string", "minLength": 1 },
"id": { "type": "string", "minLength": 1 },
"at": { "type": "string", "format": "date-time" }
}
}
},
"decided_by": {
"type": "object",
"additionalProperties": false,
"description": "The consumer that made the decision — a human, or the agent that proposed it. Recorded so a decision made by an agent is never indistinguishable from one made by a person.",
"properties": {
"type": { "type": "string", "enum": ["agent", "human", "role", "service"] },
"id": { "type": "string", "minLength": 1 }
}
},
"bundle": {
"type": "object",
"additionalProperties": false,
"description": "The Context Bundle this decision was made from. Citing the digest makes the decision auditable: a reader can prove which context was and was not in front of the decider.",
"properties": {
"bundle_id": { "type": "string", "pattern": "^ocb_[a-z0-9_-]+$" },
"digest": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" },
"generated_at": { "type": "string", "format": "date-time" },
"uri": { "type": "string", "description": "Where the bundle itself was archived, if it was." }
}
},
"created": { "type": "string", "format": "date-time" },
"updated": { "type": "string", "format": "date-time" },
"valid_from": { "type": "string", "format": "date-time" },
"expires": { "anyOf": [{ "type": "string", "format": "date-time" }, { "type": "null" }] },
"ttl": {
"type": "string",
"pattern": "^\\d+(ms|s|m|h|d|w|y)$",
"description": "Staleness window. A decision that has not been revisited inside it is reported stale — decisions rot like any other context."
},
"review": {
"type": "object",
"additionalProperties": false,
"properties": {
"interval": { "type": "string", "pattern": "^\\d+(ms|s|m|h|d|w|y)$" },
"required_approvers": { "type": "integer", "minimum": 1 },
"next_review": { "type": "string", "format": "date" },
"last_review": { "type": "string", "format": "date" }
}
},
"applies_to": {
"type": "array",
"uniqueItems": true,
"items": { "type": "string", "minLength": 1 },
"description": "Roles, agents, products, or scopes this decision governs."
},
"depends_on": {
"type": "array",
"uniqueItems": true,
"items": { "type": "string", "pattern": "^[a-z0-9][a-z0-9_-]*(\\.[a-z0-9][a-z0-9_-]*)*(@\\d+)?$" },
"description": "Context that must resolve alongside this decision for it to make sense."
},
"conflicts_with": {
"type": "array",
"uniqueItems": true,
"items": { "type": "string", "pattern": "^[a-z0-9][a-z0-9_-]*(\\.[a-z0-9][a-z0-9_-]*)*(@\\d+)?$" },
"description": "Decisions or policies known to contradict this one."
},
"confidence": { "type": "number", "minimum": 0, "maximum": 1 },
"language": { "type": "string" },
"approval": {
"type": "object",
"additionalProperties": false,
"properties": {
"required": { "type": "boolean" },
"roles": { "type": "array", "uniqueItems": true, "items": { "type": "string", "minLength": 1 } },
"minimum": { "type": "integer", "minimum": 1 },
"approved_by": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"role": { "type": "string", "minLength": 1 },
"id": { "type": "string", "minLength": 1 },
"at": { "type": "string", "format": "date-time" }
}
}
}
}
},
"redact": {
"type": "array",
"items": {
"type": "object",
"required": ["path"],
"additionalProperties": false,
"properties": {
"path": { "type": "string", "minLength": 1 },
"mode": { "type": "string", "enum": ["remove", "mask", "hash"] },
"replacement": { "type": "string" },
"reason": { "type": "string" }
}
}
},
"content_uri": { "type": "string", "minLength": 1 },
"durability": { "type": "string", "enum": ["ephemeral", "session", "operational", "long-lived", "permanent"] },
"classification": { "type": "string", "enum": ["public", "internal", "confidential", "restricted"] },
"trust": { "type": "string", "enum": ["trusted", "verified", "untrusted"] },
"permissions": {
"type": "object",
"additionalProperties": false,
"properties": {
"read": { "type": "array", "uniqueItems": true, "items": { "type": "string", "minLength": 1 } },
"write": { "type": "array", "uniqueItems": true, "items": { "type": "string", "minLength": 1 } },
"deny": { "type": "array", "uniqueItems": true, "items": { "type": "string", "minLength": 1 } }
}
},
"supersedes": {
"type": "array",
"uniqueItems": true,
"items": { "type": "string", "pattern": "^[a-z0-9][a-z0-9_-]*(\\.[a-z0-9][a-z0-9_-]*)*(@\\d+)?$" },
"description": "Earlier decisions this one replaces. Reversing a decision supersedes it; it does not delete it."
},
"superseded_by": { "type": "string", "pattern": "^[a-z0-9][a-z0-9_-]*(\\.[a-z0-9][a-z0-9_-]*)*(@\\d+)?$" },
"references": {
"type": "array",
"uniqueItems": true,
"items": { "type": "string", "pattern": "^[a-z0-9][a-z0-9_-]*(\\.[a-z0-9][a-z0-9_-]*)*(@\\d+)?$" },
"description": "Context this decision depends on — the policies, products, or prior decisions it was reasoning about."
},
"version": { "type": "integer", "minimum": 1 },
"sources": {
"type": "array",
"items": {
"type": "object",
"required": ["uri"],
"additionalProperties": true,
"properties": {
"uri": { "type": "string", "minLength": 1 },
"type": { "type": "string" },
"retrieved_at": { "type": "string", "format": "date-time" },
"digest": { "type": "string", "pattern": "^(sha256):[0-9a-f]{64}$" }
}
}
},
"canonical_source": { "type": "boolean" },
"tags": { "type": "array", "uniqueItems": true, "items": { "type": "string", "minLength": 1 } },
"content": { "anyOf": [{ "type": "string" }, { "type": "object" }, { "type": "array" }] },
"content_type": { "type": "string" },
"summary": { "type": "string" },
"extensions": {
"type": "object",
"propertyNames": { "type": "string", "pattern": "^[a-z0-9]+(\\.[a-z0-9-]+)+$" },
"additionalProperties": true
}
}
}

View file

@ -0,0 +1,123 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://logicsrc.com/schemas/opencontext/diagnostic.schema.json",
"title": "OpenContext Diagnostic Report",
"description": "The machine-readable output of `opencontext validate` and `opencontext doctor`: every finding, where it came from, how bad it is, and the resulting health score. Diagnostics are the contract CI depends on, so the codes are normative and stable — a pipeline that fails on duplicate-canonical must keep failing on it across implementations and versions.",
"type": "object",
"required": ["opencontext", "ok", "findings"],
"additionalProperties": false,
"properties": {
"opencontext": { "type": "string", "pattern": "^\\d+\\.\\d+(\\.\\d+)?$" },
"ok": {
"type": "boolean",
"description": "True when no finding meets or exceeds the configured failure severity. This is what the exit code follows."
},
"generated_at": { "type": "string", "format": "date-time" },
"namespace": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]*$" },
"score": {
"type": "number",
"minimum": 0,
"maximum": 100,
"description": "Context health, 0-100. Computed as 100 minus the sum of weight x affected objects for each finding, normalised by the object count and clamped at 0. The weights are documented and configurable, so a score is comparable only within a repository's own configuration."
},
"counts": {
"type": "object",
"additionalProperties": false,
"description": "Roll-up used by the human-readable report.",
"properties": {
"objects": { "type": "integer", "minimum": 0 },
"errors": { "type": "integer", "minimum": 0 },
"warnings": { "type": "integer", "minimum": 0 },
"info": { "type": "integer", "minimum": 0 },
"stale": { "type": "integer", "minimum": 0 },
"expired": { "type": "integer", "minimum": 0 },
"conflicting": { "type": "integer", "minimum": 0 },
"orphaned": { "type": "integer", "minimum": 0 },
"missing_owner": { "type": "integer", "minimum": 0 },
"broken_sources": { "type": "integer", "minimum": 0 }
}
},
"findings": {
"type": "array",
"description": "Every finding, ordered most severe first, then by code, then by object id, so two runs over the same repository produce byte-identical reports.",
"items": { "$ref": "#/$defs/finding" }
},
"extensions": {
"type": "object",
"propertyNames": { "type": "string", "pattern": "^[a-z0-9]+(\\.[a-z0-9-]+)+$" },
"additionalProperties": true
}
},
"$defs": {
"severity": {
"type": "string",
"enum": ["info", "warning", "error"],
"description": "error breaks the contract and fails a strict run; warning is context rot that needs attention but still resolves; info is advisory."
},
"finding": {
"type": "object",
"required": ["code", "severity", "message"],
"additionalProperties": false,
"properties": {
"code": {
"type": "string",
"enum": [
"schema-invalid",
"manifest-invalid",
"duplicate-id",
"duplicate-canonical",
"unknown-authority",
"conflict-declared",
"conflict-ambiguous",
"broken-supersession",
"supersession-cycle",
"multiple-active-versions",
"broken-reference",
"orphaned",
"missing-owner",
"missing-provenance",
"missing-digest",
"stale",
"expired",
"not-yet-valid",
"review-overdue",
"unapproved",
"unknown-scheme",
"source-unavailable",
"path-traversal",
"invalid-permission",
"unknown-role",
"role-cycle",
"empty-scope",
"secret-detected",
"untrusted-canonical",
"unknown-extension"
],
"description": "Stable diagnostic code. duplicate-canonical, conflict-ambiguous, and broken-supersession are the checks that keep the resolver from quietly guessing; secret-detected and path-traversal are security checks; unknown-extension is only ever raised in strict mode."
},
"severity": { "$ref": "#/$defs/severity" },
"message": {
"type": "string",
"minLength": 1,
"description": "What is wrong, in a sentence an author can act on."
},
"id": { "type": "string", "description": "Context object id the finding concerns." },
"ids": {
"type": "array",
"items": { "type": "string" },
"description": "Every object involved, when a finding is about a relationship rather than one object — the two canonical policies that collide, or the chain that broke."
},
"file": { "type": "string", "description": "Path the object was loaded from, relative to the manifest." },
"line": { "type": "integer", "minimum": 1, "description": "1-indexed line, when the source format carries positions." },
"column": { "type": "integer", "minimum": 1 },
"field": { "type": "string", "description": "Dotted path of the offending field, e.g. permissions.read." },
"expected": { "description": "What the field should have been." },
"actual": { "description": "What it was." },
"remediation": {
"type": "string",
"description": "The concrete next action, e.g. 'Add owner: support, or set health.require_owner: false'."
}
}
}
}
}

View file

@ -0,0 +1,337 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://logicsrc.com/schemas/opencontext/manifest.schema.json",
"title": "OpenContext Manifest",
"description": "The root manifest of an OpenContext repository, canonically named opencontext.yaml (opencontext.json is also permitted). It declares which context exists, where it is loaded from, who may read it, how authority is ranked, how freshness is judged, and which audit events are recorded. The manifest is a control plane: it points at systems that remain the sources of truth, and is not itself the database.",
"type": "object",
"required": ["opencontext", "id"],
"additionalProperties": false,
"properties": {
"opencontext": {
"type": "string",
"pattern": "^\\d+\\.\\d+(\\.\\d+)?$",
"description": "OpenContext specification version this manifest conforms to, e.g. '1.0'. Implementations MUST refuse a major version they do not support rather than guess."
},
"id": {
"type": "string",
"pattern": "^[a-z0-9][a-z0-9-]*$",
"description": "Stable identifier for this context namespace, e.g. 'acme'. Object ids are unique within it."
},
"name": {
"type": "string",
"minLength": 1,
"description": "Human-readable name of the organization or project this context belongs to."
},
"description": {
"type": "string",
"description": "One-paragraph summary of what this context repository covers."
},
"context": {
"type": "object",
"description": "Named single-document context entries. Each key becomes a resolvable object id; each value is a path or URI. Example: mission: ./context/mission.md",
"additionalProperties": { "type": "string", "minLength": 1 },
"propertyNames": { "$ref": "#/$defs/segment" }
},
"collections": {
"type": "object",
"description": "Named globs or URIs that expand to many context objects. The key namespaces the ids of everything the collection loads, so ./context/policies/refunds.md under the 'policies' collection becomes policies.refunds unless the document declares its own id.",
"additionalProperties": {
"anyOf": [
{ "type": "string", "minLength": 1 },
{ "$ref": "#/$defs/collectionSpec" }
]
},
"propertyNames": { "$ref": "#/$defs/segment" }
},
"roles": {
"type": "object",
"description": "Named scopes. A role declares which context its holders may read, which context is denied, and which capabilities they hold. Deny always overrides allow.",
"additionalProperties": { "$ref": "#/$defs/role" },
"propertyNames": { "$ref": "#/$defs/segment" }
},
"agents": {
"type": "object",
"description": "Named consumers mapped to the roles they hold. An agent's scope is the union of its roles' includes minus the union of their excludes; an agent holds no context rights of its own.",
"additionalProperties": { "$ref": "#/$defs/agentBinding" },
"propertyNames": { "$ref": "#/$defs/segment" }
},
"authority": {
"type": "object",
"additionalProperties": false,
"description": "How competing objects are ranked. Precedence MAY be overridden but MUST remain a permutation of the standard authority levels — a repository cannot invent a level that outranks canonical.",
"properties": {
"precedence": {
"type": "array",
"minItems": 1,
"uniqueItems": true,
"items": { "$ref": "#/$defs/authority" },
"description": "Highest authority first. Default: canonical, approved, reference, observed, inferred, historical."
},
"tie_breakers": {
"type": "array",
"uniqueItems": true,
"items": { "type": "string", "enum": ["version", "updated", "created", "confidence", "id"] },
"description": "Applied in order when two candidates remain tied after authority and supersession. Default: version, updated, confidence, id. Resolution appends id as a final total tie breaker so the outcome is always deterministic."
}
}
},
"freshness": {
"type": "object",
"additionalProperties": false,
"description": "Defaults for lifecycle evaluation. Per-object metadata always wins over these defaults.",
"properties": {
"default_ttl": {
"$ref": "#/$defs/duration",
"description": "How long an object stays 'current' after its updated timestamp before it is reported 'stale', e.g. '30d'. Staleness is a warning: stale context is still resolved, and still flagged."
},
"stale_is_error": {
"type": "boolean",
"default": false,
"description": "When true, --strict treats stale context as a failure rather than a warning."
},
"exclude_expired": {
"type": "boolean",
"default": true,
"description": "When true (default), expired objects are excluded from resolution unless historical context is explicitly requested."
}
}
},
"provenance": {
"type": "object",
"additionalProperties": false,
"description": "Provenance policy. When required, every resolved object MUST carry a source or declare itself canonical source material.",
"properties": {
"required": { "type": "boolean", "default": false, "description": "Require every resolved object to be attributable." },
"digest": { "type": "string", "enum": ["sha256"], "default": "sha256", "description": "Digest algorithm for source integrity and bundle digests." },
"require_digest": { "type": "boolean", "default": false, "description": "Require every declared remote source to carry an integrity digest." }
}
},
"audit": {
"type": "object",
"additionalProperties": false,
"description": "Which events implementations should record. The specification defines the event shape; it does not mandate a storage backend.",
"properties": {
"context_reads": { "type": "boolean", "default": false },
"context_writes": { "type": "boolean", "default": false },
"decisions": { "type": "boolean", "default": false },
"conflicts": { "type": "boolean", "default": false },
"sink": { "type": "string", "description": "Optional URI the reference implementation appends audit events to, e.g. file://./context/.audit/events.ndjson" }
}
},
"redact": {
"type": "array",
"description": "Repository-wide redaction rules, applied after authorization and before compilation.",
"items": { "$ref": "#/$defs/redaction" }
},
"review": {
"$ref": "#/$defs/review",
"description": "Default review cadence for objects that do not declare their own."
},
"adapters": {
"type": "object",
"description": "URI schemes this repository expects to resolve, mapped to adapter configuration. A scheme no installed adapter claims MUST fail clearly rather than silently resolve to nothing.",
"additionalProperties": { "$ref": "#/$defs/adapterConfig" },
"propertyNames": { "type": "string", "pattern": "^[a-z][a-z0-9+.-]*$" }
},
"defaults": {
"type": "object",
"additionalProperties": false,
"description": "Field defaults applied to objects that omit them. Defaults describe house style; they never launder authority, and an implementation MUST NOT default observed or inferred content to canonical.",
"properties": {
"layer": { "$ref": "#/$defs/layer" },
"authority": { "$ref": "#/$defs/authority" },
"classification": { "$ref": "#/$defs/classification" },
"durability": { "$ref": "#/$defs/durability" },
"trust": { "$ref": "#/$defs/trust" },
"owner": { "type": "string", "minLength": 1 },
"ttl": { "$ref": "#/$defs/duration" }
}
},
"health": {
"type": "object",
"additionalProperties": false,
"description": "Configuration for `opencontext doctor`. The score formula is documented and configurable so a CI threshold means the same thing across repositories.",
"properties": {
"minimum_score": { "type": "number", "minimum": 0, "maximum": 100, "description": "Doctor exits non-zero below this score when --strict is set." },
"weights": {
"type": "object",
"description": "Per-diagnostic-code weight overriding the documented default. Deductions are weight x affected objects, normalised by object count.",
"additionalProperties": { "type": "number", "minimum": 0 },
"propertyNames": { "type": "string", "pattern": "^[a-z][a-z0-9-]*$" }
},
"fail_on": { "$ref": "#/$defs/severity", "description": "Lowest severity that fails a strict run. Default: error." },
"require_owner": { "type": "boolean", "default": false, "description": "Treat objects with no owner as an error rather than a warning." }
}
},
"related": {
"type": "object",
"additionalProperties": false,
"description": "Optional links to sibling LogicSRC specifications. These integrations MUST remain optional: OpenContext is independently usable without either of them.",
"properties": {
"prd": { "type": "string", "description": "Path or URI of an OpenPRD document or collection." },
"topology": { "type": "string", "description": "Path or URI of an OpenTopology manifest." },
"ontology": { "type": "string", "description": "Path or URI of an OpenOntology package." }
}
},
"extensions": { "$ref": "#/$defs/extensions" }
},
"$defs": {
"segment": {
"type": "string",
"pattern": "^[a-z0-9][a-z0-9_-]*$",
"description": "A single dotted-id segment: lowercase alphanumerics, dashes, underscores."
},
"duration": {
"type": "string",
"pattern": "^\\d+(ms|s|m|h|d|w|y)$",
"description": "A duration such as '30d', '12h', or '180d'. Units are fixed lengths: y = 365d, w = 7d, d = 24h."
},
"layer": {
"type": "string",
"enum": ["L0", "L1", "L2", "L3", "L4", "L5"],
"description": "L0 mission, L1 identity, L2 knowledge, L3 policy, L4 procedure, L5 operational."
},
"authority": {
"type": "string",
"enum": ["canonical", "approved", "reference", "observed", "inferred", "historical"],
"description": "Declared truth level, highest to lowest by default."
},
"trust": {
"type": "string",
"enum": ["trusted", "verified", "untrusted"],
"description": "Whether the content originated inside the trust boundary."
},
"durability": {
"type": "string",
"enum": ["ephemeral", "session", "operational", "long-lived", "permanent"]
},
"classification": {
"type": "string",
"enum": ["public", "internal", "confidential", "restricted"]
},
"severity": {
"type": "string",
"enum": ["info", "warning", "error"]
},
"collectionSpec": {
"type": "object",
"required": ["source"],
"additionalProperties": false,
"description": "A collection declared with options rather than as a bare glob string.",
"properties": {
"source": { "type": "string", "minLength": 1, "description": "Glob or URI the collection loads from." },
"type": { "type": "string", "pattern": "^[a-z][a-z0-9-]*$", "description": "Default object type for members that omit one." },
"layer": { "$ref": "#/$defs/layer" },
"authority": { "$ref": "#/$defs/authority" },
"classification": { "$ref": "#/$defs/classification" },
"durability": { "$ref": "#/$defs/durability" },
"trust": { "$ref": "#/$defs/trust" },
"owner": { "type": "string", "minLength": 1 },
"ttl": { "$ref": "#/$defs/duration" }
}
},
"role": {
"type": "object",
"additionalProperties": false,
"description": "A named scope. Relevance and authorization are different questions: include says what is in scope, and nothing in scope is returned if a deny, an exclusion, or a classification ceiling says otherwise.",
"properties": {
"description": { "type": "string" },
"include": {
"type": "array",
"uniqueItems": true,
"items": { "$ref": "#/$defs/pattern" },
"description": "Id patterns in scope, e.g. mission, products.*, policies.support.*. A role with no include sees nothing."
},
"exclude": {
"type": "array",
"uniqueItems": true,
"items": { "$ref": "#/$defs/pattern" },
"description": "Id patterns denied. Applied before relevance ranking and unconditionally overriding include."
},
"permissions": {
"type": "array",
"uniqueItems": true,
"items": { "type": "string", "minLength": 1 },
"description": "Capability strings such as customer.read or ticket.write. OpenContext carries these; it does not enforce your application's actions."
},
"max_classification": {
"$ref": "#/$defs/classification",
"description": "Highest classification this role may read. Objects above it are denied even when included. Defaults to internal."
},
"redact": {
"type": "array",
"description": "Redaction rules applied to everything this role reads.",
"items": { "$ref": "#/$defs/redaction" }
},
"inherits": {
"type": "array",
"uniqueItems": true,
"items": { "$ref": "#/$defs/segment" },
"description": "Roles whose scope is merged into this one. Includes union; excludes and redactions also union, so inheriting can only ever narrow what is readable."
},
"extensions": { "$ref": "#/$defs/extensions" }
}
},
"pattern": {
"type": "string",
"minLength": 1,
"pattern": "^([a-z0-9][a-z0-9_-]*|\\*)(\\.([a-z0-9][a-z0-9_-]*|\\*))*$",
"description": "An exact object id, a trailing wildcard such as policies.support.* covering that subtree, an interior wildcard such as customers.*.churn-risk matching exactly one segment, or * for everything. Wildcards match whole segments only."
},
"agentBinding": {
"type": "object",
"required": ["roles"],
"additionalProperties": false,
"properties": {
"roles": {
"type": "array",
"minItems": 1,
"uniqueItems": true,
"items": { "$ref": "#/$defs/segment" },
"description": "Roles this agent holds. A role named here that the manifest does not define is a validation error."
},
"description": { "type": "string" },
"extensions": { "$ref": "#/$defs/extensions" }
}
},
"adapterConfig": {
"type": "object",
"additionalProperties": true,
"description": "Adapter options. Unknown keys are passed through to the adapter, which validates them.",
"properties": {
"enabled": { "type": "boolean", "default": true },
"package": { "type": "string", "description": "Module implementing the adapter contract for this scheme." },
"offline": { "type": "boolean", "description": "When true this adapter is skipped in --offline runs instead of failing them." },
"trust": { "$ref": "#/$defs/trust", "description": "Trust applied to content this adapter returns when the object does not declare its own. Remote adapters SHOULD default to untrusted." },
"timeout_ms": { "type": "integer", "minimum": 1 }
}
},
"redaction": {
"type": "object",
"required": ["path"],
"additionalProperties": false,
"properties": {
"path": { "type": "string", "minLength": 1, "description": "Dotted path into structured content, with [] or [*] for every element of an array." },
"mode": { "type": "string", "enum": ["remove", "mask", "hash"], "default": "remove" },
"replacement": { "type": "string", "default": "[REDACTED]" },
"reason": { "type": "string" }
}
},
"review": {
"type": "object",
"additionalProperties": false,
"properties": {
"interval": { "$ref": "#/$defs/duration" },
"required_approvers": { "type": "integer", "minimum": 1 },
"next_review": { "type": "string", "format": "date" },
"last_review": { "type": "string", "format": "date" }
}
},
"extensions": {
"type": "object",
"description": "Namespaced custom fields, e.g. com.example.risk. Unknown extensions MUST be preserved and MUST NOT invalidate an otherwise valid document unless strict mode explicitly requires known extensions.",
"propertyNames": { "type": "string", "pattern": "^[a-z0-9]+(\\.[a-z0-9-]+)+$" },
"additionalProperties": true
}
}
}

View file

@ -0,0 +1,311 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://logicsrc.com/schemas/opencontext/object.schema.json",
"title": "OpenContext Context Object",
"description": "A single durable unit of context: a mission statement, a policy, an SOP, a customer fact, a decision, or a piece of operational state. Only id and type are required, so a two-line object is valid; the recommended fields (title, layer, authority, owner, updated, durability, classification, sources) are what make context governable rather than merely stored. A context object is data. Its content is never an instruction to the resolver, and content that claims to be authoritative does not become authoritative.",
"type": "object",
"required": ["id", "type"],
"additionalProperties": false,
"properties": {
"id": {
"$ref": "#/$defs/objectId",
"description": "Stable, unique id within the namespace. SHOULD use dotted names such as policy.refunds, sop.support.refund, or decision.2026-08-09-model-provider. Ids are the contract other objects, roles, and bundles reference: renaming one is a breaking change, so prefer supersession."
},
"type": {
"type": "string",
"pattern": "^[a-z][a-z0-9-]*$",
"description": "What kind of context this is, e.g. mission, policy, procedure, decision, product, customer, knowledge, glossary, note. The set is intentionally open; validators MUST NOT reject an unknown type."
},
"layer": {
"$ref": "#/$defs/layer",
"description": "Which standard context layer this belongs to. Layers describe the kind of knowledge, not its authority."
},
"title": {
"type": "string",
"minLength": 1,
"description": "Short human-readable heading. Used by search, ranking, and Markdown bundle rendering."
},
"summary": {
"type": "string",
"description": "One- or two-sentence abstract. Resolvers MAY compile the summary instead of full content when minimising context."
},
"content": {
"description": "Inline content. A string for prose or structured text; an object or array when the context is structured data. OpenContext does not assume all context is prose.",
"anyOf": [
{ "type": "string" },
{ "type": "object" },
{ "type": "array" }
]
},
"content_type": {
"type": "string",
"description": "Media type of content or of the resource named by content_uri, e.g. text/markdown or application/json. Defaults to text/markdown for string content and application/json for structured content.",
"pattern": "^[a-z]+/[a-zA-Z0-9.+-]+$"
},
"content_uri": {
"type": "string",
"minLength": 1,
"description": "Where content is loaded from when it is not inline: file://, http://, https://, git://, sqlite://, or any scheme an installed adapter claims. An unknown scheme MUST fail clearly rather than resolve to empty content."
},
"authority": {
"$ref": "#/$defs/authority",
"description": "How much this object counts as truth. Authority is declared by the owner of the context, never inferred from retrieval rank, recency, or the content's own claims."
},
"trust": {
"$ref": "#/$defs/trust",
"description": "Where the content came from, in terms of whether it can be believed. Content pulled from a ticket, a chat, or a web page is untrusted even when the object about it is canonical. Resolvers MUST preserve this through compilation so agent integrations can delimit untrusted text."
},
"owner": {
"type": "string",
"minLength": 1,
"description": "Accountable role, team, or identity. Doctor reports objects with no owner because unowned context is what goes stale."
},
"status": {
"$ref": "#/$defs/status",
"description": "Approval state. Only approved objects satisfy an approval requirement; draft and pending objects are excluded from default resolution."
},
"version": {
"type": "integer",
"minimum": 1,
"description": "Monotonic version within the id. Referenced from supersedes as id@version."
},
"created": { "type": "string", "format": "date-time", "description": "RFC 3339 timestamp the object was first written." },
"updated": { "type": "string", "format": "date-time", "description": "RFC 3339 timestamp of the last substantive edit. Freshness is measured from here." },
"valid_from": { "type": "string", "format": "date-time", "description": "Object is 'future' and excluded from resolution before this instant." },
"expires": {
"anyOf": [
{ "type": "string", "format": "date-time" },
{ "type": "null" }
],
"description": "Object is 'expired' after this instant. Explicit null means it never expires, which is different from omitting the field (the repository ttl applies)."
},
"ttl": {
"$ref": "#/$defs/duration",
"description": "Per-object staleness window, overriding freshness.default_ttl. An object older than updated + ttl is 'stale' — a warning, never a silent omission."
},
"durability": {
"$ref": "#/$defs/durability",
"description": "How long this context is meant to survive. Permanent context SHOULD be superseded rather than deleted."
},
"classification": {
"$ref": "#/$defs/classification",
"description": "Sensitivity band. Classification bounds who may read the object regardless of scope: a role may include an object and still be denied it by classification."
},
"permissions": {
"type": "object",
"additionalProperties": false,
"description": "Object-level access control. Deny always overrides allow, and permissions are evaluated before relevance ranking, so an unauthorised object never reaches a prompt.",
"properties": {
"read": { "$ref": "#/$defs/principalList", "description": "Roles or agents that may read this object. Absent means the repository scope rules decide." },
"write": { "$ref": "#/$defs/principalList", "description": "Roles or agents that may modify or supersede it." },
"deny": { "$ref": "#/$defs/principalList", "description": "Roles or agents explicitly denied, overriding any include or read grant." }
}
},
"redact": {
"type": "array",
"description": "Redaction rules applied to this object's structured content after authorization.",
"items": { "$ref": "#/$defs/redaction" }
},
"sources": {
"type": "array",
"description": "Where this context came from. Required for every resolved object when provenance.required is true, unless canonical_source is true. Provenance MUST survive bundle compilation.",
"items": { "$ref": "#/$defs/source" }
},
"canonical_source": {
"type": "boolean",
"default": false,
"description": "True when this object is itself the origin of the fact and has no upstream source. Satisfies a provenance requirement on its own."
},
"supersedes": {
"type": "array",
"uniqueItems": true,
"items": { "$ref": "#/$defs/objectRef" },
"description": "Objects this one replaces, as id or id@version. Superseded objects are excluded from default resolution but retained, so history can be reconstructed."
},
"superseded_by": {
"$ref": "#/$defs/objectRef",
"description": "Set on the older object when a chain is written explicitly rather than inferred. A chain that points at a missing object is a broken supersession error."
},
"conflicts_with": {
"type": "array",
"uniqueItems": true,
"items": { "$ref": "#/$defs/objectRef" },
"description": "Objects known to contradict this one. Declared conflicts between canonical objects are reported, never silently resolved."
},
"references": {
"type": "array",
"uniqueItems": true,
"items": { "$ref": "#/$defs/objectRef" },
"description": "Other context this object depends on or cites. Drives the graph and orphan detection."
},
"depends_on": {
"type": "array",
"uniqueItems": true,
"items": { "$ref": "#/$defs/objectRef" },
"description": "Context that must resolve alongside this object for it to make sense. Resolvers SHOULD pull dependencies in when the object is selected and the consumer is authorised for them."
},
"applies_to": {
"type": "array",
"uniqueItems": true,
"items": { "type": "string", "minLength": 1 },
"description": "Roles, agents, products, or scopes this object is specifically about. Used to rank task relevance and to keep irrelevant context out of a bundle."
},
"confidence": {
"type": "number",
"minimum": 0,
"maximum": 1,
"description": "How sure the owner is, from 0 to 1. Confidence breaks ties within an authority level; it never promotes an object across levels."
},
"tags": {
"type": "array",
"uniqueItems": true,
"items": { "type": "string", "minLength": 1 },
"description": "Free-form labels for search and scope matching."
},
"approval": {
"type": "object",
"additionalProperties": false,
"description": "Approval requirements. The specification defines the metadata and states; it does not require a hosted approval workflow.",
"properties": {
"required": { "type": "boolean", "default": false },
"roles": { "type": "array", "uniqueItems": true, "items": { "type": "string", "minLength": 1 }, "description": "Roles entitled to approve." },
"minimum": { "type": "integer", "minimum": 1, "description": "How many approvals are needed. Defaults to 1 when required is true." },
"approved_by": {
"type": "array",
"items": { "$ref": "#/$defs/approver" },
"description": "Approvals recorded so far. Fewer than minimum leaves the object unapproved, which doctor reports."
}
}
},
"review": {
"type": "object",
"additionalProperties": false,
"description": "Review cadence for durable context that must not be allowed to rot quietly.",
"properties": {
"interval": { "$ref": "#/$defs/duration" },
"required_approvers": { "type": "integer", "minimum": 1 },
"next_review": { "type": "string", "format": "date" },
"last_review": { "type": "string", "format": "date" }
}
},
"language": { "type": "string", "description": "BCP 47 tag of the content language, e.g. 'en'." },
"extensions": { "$ref": "#/$defs/extensions" }
},
"$defs": {
"objectId": {
"type": "string",
"pattern": "^[a-z0-9][a-z0-9_-]*(\\.[a-z0-9][a-z0-9_-]*)*$",
"maxLength": 512,
"description": "Dotted lowercase id. Segments are alphanumerics, dashes, and underscores."
},
"objectRef": {
"type": "string",
"pattern": "^[a-z0-9][a-z0-9_-]*(\\.[a-z0-9][a-z0-9_-]*)*(@\\d+)?$",
"maxLength": 520,
"description": "An object id, optionally pinned to a version with @N."
},
"layer": {
"type": "string",
"enum": ["L0", "L1", "L2", "L3", "L4", "L5"],
"description": "L0 mission (why the organization exists), L1 identity (brand, values, terminology), L2 knowledge (products, customers, architecture, facts), L3 policy (rules, permissions, compliance), L4 procedure (SOPs, workflows, playbooks), L5 operational (tasks, incidents, temporary state)."
},
"authority": {
"type": "string",
"enum": ["canonical", "approved", "reference", "observed", "inferred", "historical"],
"description": "canonical = the organization's own source of truth; approved = reviewed and sanctioned; reference = useful but not binding; observed = seen in the wild, unverified; inferred = derived by a model or heuristic; historical = retained for the record only. Observed and inferred context never becomes canonical automatically."
},
"trust": {
"type": "string",
"enum": ["trusted", "verified", "untrusted"],
"description": "trusted = authored inside the trust boundary; verified = external but integrity-checked; untrusted = arrived from a system that can carry attacker-controlled text. Default for content fetched by a remote adapter is untrusted."
},
"durability": {
"type": "string",
"enum": ["ephemeral", "session", "operational", "long-lived", "permanent"],
"description": "How long the context is expected to matter, from a single exchange through to organizational record."
},
"classification": {
"type": "string",
"enum": ["public", "internal", "confidential", "restricted"],
"description": "Sensitivity band, least to most sensitive."
},
"status": {
"type": "string",
"enum": ["draft", "pending", "approved", "rejected", "retired"],
"description": "Approval lifecycle state. Distinct from the computed freshness state (future, current, stale, expired, superseded)."
},
"duration": {
"type": "string",
"pattern": "^\\d+(ms|s|m|h|d|w|y)$",
"description": "A duration such as '30d' or '12h'. y = 365d, w = 7d, d = 24h."
},
"principalList": {
"type": "array",
"uniqueItems": true,
"items": { "type": "string", "minLength": 1 },
"description": "Role names, agent ids, or '*' for everyone. Matching is exact except for a trailing .* wildcard."
},
"redaction": {
"type": "object",
"required": ["path"],
"additionalProperties": false,
"description": "A rule removing or masking part of an object's structured content before it reaches a bundle.",
"properties": {
"path": {
"type": "string",
"minLength": 1,
"description": "Dotted path into the object's content, with [] or [*] for every element of an array, e.g. customer.ssn or contacts[*].email."
},
"mode": {
"type": "string",
"enum": ["remove", "mask", "hash"],
"default": "remove",
"description": "remove deletes the key; mask replaces the value with the replacement string; hash replaces it with a sha256 digest so equality is still testable without disclosure."
},
"replacement": { "type": "string", "default": "[REDACTED]", "description": "Text used by mask mode." },
"reason": { "type": "string", "description": "Why the field is redacted. Surfaced in --explain." }
}
},
"source": {
"type": "object",
"required": ["uri"],
"additionalProperties": false,
"description": "One origin of this context. Sources make a claim attributable; they do not make it authoritative.",
"properties": {
"uri": { "type": "string", "minLength": 1, "description": "Where the context came from, e.g. git://github.com/acme/context/policies/refunds.md or crm://pricing/enterprise." },
"type": { "type": "string", "description": "What kind of origin this is, e.g. canonical-record, document, conversation, observation, api." },
"retrieved_at": { "type": "string", "format": "date-time", "description": "When the content was last read from this source." },
"digest": {
"type": "string",
"pattern": "^(sha256):[0-9a-f]{64}$",
"description": "Integrity digest of the retrieved bytes, as sha256:<64 lowercase hex>. Lets a consumer detect that a remote source changed under them."
},
"label": { "type": "string", "description": "Human-readable name of the source." },
"trust": { "$ref": "#/$defs/trust", "description": "Trust of this specific origin, when it differs from the object's." }
}
},
"approver": {
"type": "object",
"additionalProperties": false,
"description": "One recorded approval.",
"properties": {
"role": { "type": "string", "minLength": 1 },
"id": { "type": "string", "minLength": 1, "description": "Identity of the approver, e.g. an email or DID." },
"at": { "type": "string", "format": "date-time" }
},
"anyOf": [
{ "required": ["role"], "properties": { "role": { "type": "string" } } },
{ "required": ["id"], "properties": { "id": { "type": "string" } } }
]
},
"extensions": {
"type": "object",
"description": "Namespaced custom fields, e.g. com.example.risk. Unknown extensions MUST be preserved through resolution and MUST NOT invalidate an otherwise valid document unless strict mode explicitly requires known extensions.",
"propertyNames": {
"type": "string",
"pattern": "^[a-z0-9]+(\\.[a-z0-9-]+)+$"
},
"additionalProperties": true
}
}
}

View file

@ -0,0 +1,76 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://logicsrc.com/schemas/opencontext/provenance.schema.json",
"title": "OpenContext Provenance Record",
"description": "Where one context object came from. Provenance answers 'who says so and when did we last check', which is a different question from 'is it true' (authority) and 'may you read it' (permissions). Provenance MUST survive bundle compilation: summarising or reformatting content may not erase its origin, because an agent that cannot cite its sources cannot be audited or corrected.",
"type": "object",
"required": ["id"],
"additionalProperties": false,
"anyOf": [
{ "required": ["sources"], "properties": { "sources": { "type": "array" } } },
{ "required": ["canonical_source"], "properties": { "canonical_source": { "const": true } } }
],
"properties": {
"id": {
"type": "string",
"pattern": "^[a-z0-9][a-z0-9_-]*(\\.[a-z0-9][a-z0-9_-]*)*$",
"description": "The context object this record describes."
},
"canonical_source": {
"type": "boolean",
"description": "True when the object is itself the origin of the fact. A mission statement written in this repository has no upstream source and satisfies a provenance requirement on its own; a pricing object mirrored from a CRM does not."
},
"sources": {
"type": "array",
"minItems": 1,
"description": "Origins, most authoritative first. More than one source is normal — the same fact may be mirrored from a CRM and confirmed in a policy document.",
"items": { "$ref": "#/$defs/source" }
},
"retrieved_at": {
"type": "string",
"format": "date-time",
"description": "When the object as a whole was last refreshed from its sources, when that differs from the per-source timestamps."
},
"extensions": {
"type": "object",
"propertyNames": { "type": "string", "pattern": "^[a-z0-9]+(\\.[a-z0-9-]+)+$" },
"additionalProperties": true
}
},
"$defs": {
"source": {
"type": "object",
"required": ["uri"],
"additionalProperties": false,
"properties": {
"uri": {
"type": "string",
"minLength": 1,
"description": "Where the content came from, e.g. git://github.com/acme/context/policies/refunds.md, crm://pricing/enterprise, or https://example.com/handbook. The scheme tells a reader which system to go argue with when the fact is wrong."
},
"type": {
"type": "string",
"description": "What kind of origin this is: canonical-record, document, conversation, observation, api, inference. An origin of type conversation or observation is a reason to keep the object's authority low."
},
"retrieved_at": { "type": "string", "format": "date-time", "description": "When these bytes were last read." },
"digest": {
"type": "string",
"pattern": "^(sha256):[0-9a-f]{64}$",
"description": "sha256:<64 lowercase hex> over the retrieved bytes. Lets a consumer detect that a remote source changed under them since the context was written, which is the difference between stale context and silently wrong context."
},
"label": { "type": "string", "description": "Human-readable name of the source." },
"trust": {
"type": "string",
"enum": ["trusted", "verified", "untrusted"],
"description": "Trust of this specific origin. A digest-checked external document is verified; a scraped page or a customer message is untrusted no matter how confident the object is."
},
"author": { "type": "string", "description": "Who produced the source material, when known." },
"extensions": {
"type": "object",
"propertyNames": { "type": "string", "pattern": "^[a-z0-9]+(\\.[a-z0-9-]+)+$" },
"additionalProperties": true
}
}
}
}
}

View file

@ -0,0 +1,93 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://logicsrc.com/schemas/opencontext/role.schema.json",
"title": "OpenContext Role / Scope",
"description": "A named scope: the authorized subset of context available to a human, role, agent, task, or runtime. Roles answer 'may this consumer read it', which OpenContext keeps strictly separate from 'is it relevant'. Exclusions and denials are applied before relevance ranking, so unauthorized context never reaches a ranker, a prompt, or a bundle. This schema governs a single role; the manifest embeds the same shape under its roles map.",
"type": "object",
"additionalProperties": false,
"properties": {
"id": {
"type": "string",
"pattern": "^[a-z0-9][a-z0-9_-]*$",
"description": "Role name. Optional when the role is embedded in a manifest, where the map key names it."
},
"description": {
"type": "string",
"description": "What this role is for, in one line."
},
"include": {
"type": "array",
"uniqueItems": true,
"items": { "$ref": "#/$defs/pattern" },
"description": "Id patterns in scope, e.g. mission, products.*, policies.support.*. A role with no include list sees nothing: scope is opt-in, never opt-out."
},
"exclude": {
"type": "array",
"uniqueItems": true,
"items": { "$ref": "#/$defs/pattern" },
"description": "Id patterns denied to this role. Deny overrides allow unconditionally — an exclusion cannot be outvoted by a more specific include, by inheritance, or by an object-level read grant."
},
"permissions": {
"type": "array",
"uniqueItems": true,
"items": { "type": "string", "minLength": 1 },
"description": "Capability strings such as customer.read or ticket.write. OpenContext transports and scopes these so a runtime can enforce them; OpenContext does not itself perform your application's actions."
},
"max_classification": {
"$ref": "#/$defs/classification",
"description": "Highest classification this role may read. An object above the ceiling is denied even when an include pattern matches it. Defaults to internal, so confidential and restricted context requires an explicit grant."
},
"redact": {
"type": "array",
"description": "Redaction rules applied to everything this role reads, unioned with repository-wide and object-level rules.",
"items": { "$ref": "#/$defs/redaction" }
},
"inherits": {
"type": "array",
"uniqueItems": true,
"items": { "type": "string", "pattern": "^[a-z0-9][a-z0-9_-]*$" },
"description": "Roles whose scope merges into this one. Includes, excludes, and redactions all union, and the classification ceiling takes the lowest of the parents — inheritance can therefore only narrow what is readable, never widen it. Cycles are a validation error."
},
"extensions": { "$ref": "#/$defs/extensions" }
},
"$defs": {
"pattern": {
"type": "string",
"minLength": 1,
"pattern": "^([a-z0-9][a-z0-9_-]*|\\*)(\\.([a-z0-9][a-z0-9_-]*|\\*))*$",
"description": "An exact object id (policy.refunds), a trailing wildcard covering a subtree (policies.support.*), an interior wildcard matching exactly one segment (customers.*.churn-risk), or * for everything. Wildcards always match whole dotted segments, never substrings, so products.* matches products.enterprise but never products-internal."
},
"classification": {
"type": "string",
"enum": ["public", "internal", "confidential", "restricted"],
"description": "Sensitivity band, least to most sensitive."
},
"redaction": {
"type": "object",
"required": ["path"],
"additionalProperties": false,
"description": "A rule removing or masking part of an object's structured content before it reaches a bundle.",
"properties": {
"path": {
"type": "string",
"minLength": 1,
"description": "Dotted path into the object's content, with [] or [*] for every element of an array, e.g. customer.ssn or contacts[*].email."
},
"mode": {
"type": "string",
"enum": ["remove", "mask", "hash"],
"default": "remove",
"description": "remove deletes the key; mask replaces the value with the replacement string; hash replaces it with a sha256 digest so equality remains testable without disclosure."
},
"replacement": { "type": "string", "default": "[REDACTED]" },
"reason": { "type": "string", "description": "Why the field is redacted. Surfaced in --explain." }
}
},
"extensions": {
"type": "object",
"description": "Namespaced custom fields, e.g. com.example.risk.",
"propertyNames": { "type": "string", "pattern": "^[a-z0-9]+(\\.[a-z0-9-]+)+$" },
"additionalProperties": true
}
}
}