Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions src/contracts/context-graph-integrity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/**
* Context Graph integrity validators. Implements TC-CG-01 (context
* parameters surfaced), TC-CG-02 (constraints surfaced), TC-CG-03
* (contextualises edges to decisions), per RFC-0004.
*
* The Context Graph is derived from the COTS alone; these validators check
* the data-level contract that makes the derivation complete: every
* parameter key is present, every constraint is declared, and every
* decision is linked to the context.
*
* See: https://github.com/Source-Engineering/source-engineering-specifications/blob/main/compatibility-test-suite.md
*/

import type { Cots } from "../types.js";
import { type Result, combine, fail } from "../result.js";

/** TC-CG-01: every context parameter is recorded and therefore surfaceable. */
export const validateContextParametersSurfaced = (cots: Cots): Result => {
const errors: Result[] = [];
const p = cots.context.parameters;

const required: Array<[string, unknown]> = [
["temperature", p.temperature],
["top_p", p.top_p],
["prompt_cache_id", p.prompt_cache_id],
["tool_budget", p.tool_budget],
["retrieval_depth", p.retrieval_depth],
];

for (const [key, value] of required) {
if (value === undefined) {
errors.push(
fail("TC-CG-01", `context.parameters.${key} is missing; it cannot be surfaced in the Context Graph`)
);
}
}

for (const key of Object.keys(p.additional ?? {})) {
if (p.additional?.[key] === undefined) {
errors.push(
fail("TC-CG-01", `context.parameters.additional.${key} is present but undefined; it cannot be surfaced`)
);
}
}

return combine(...errors);
};

/** TC-CG-02: every constraint is declared with an id so it can carry a constrains edge. */
export const validateConstraintsSurfaced = (cots: Cots): Result => {
const errors: Result[] = [];

for (let i = 0; i < cots.context.constraints.length; i++) {
const c = cots.context.constraints[i];
if (!c.id || !/^[a-z][a-z0-9+\-.]*:/i.test(c.id)) {
errors.push(
fail("TC-CG-02", `context.constraints[${i}].id is missing or not a valid URI ('${c.id}'); the constraint cannot be surfaced in the Context Graph`)
);
}
}

return combine(...errors);
};

/** TC-CG-03: every decision is made under the recorded context. */
export const validateDecisionsContextualised = (cots: Cots): Result => {
const errors: Result[] = [];

if (cots.decisions.length === 0) {
return combine();
}

const paramKeys = Object.keys(cots.context.parameters).filter(
(k) => k !== "additional"
);
if (paramKeys.length === 0) {
errors.push(
fail("TC-CG-03", "context.parameters has no named parameters; no contextualises edge can be drawn to any decision")
);
}

for (let i = 0; i < cots.decisions.length; i++) {
const d = cots.decisions[i];
const chain = cots.evidence.evidence_chain.find((c) => c.decision_id === d.id);
if (!chain) {
errors.push(
fail("TC-CG-03", `decisions[${i}] (${d.id}) has no evidence chain; the decision cannot be contextualised in the Context Graph`)
);
}
}

return combine(...errors);
};
157 changes: 157 additions & 0 deletions src/fixtures/cg-bad-constraint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
{
"cots_version": "0.1.0",
"cots_id": "urn:source-engineering:cots:00000000-0000-0000-0000-000000000001",
"produced_at": "2026-10-11T12:00:00Z",
"produced_by": {
"system_name": "test-fixture",
"system_version": "0.1.0"
},
"inputs": {
"source_artifacts": [
{
"id": "urn:source-engineering:source:contract-2026-001",
"origin": {
"system_name": "Acme Contracts",
"system_version": "1.0.0",
"configuration_hash": "0000000000000000000000000000000000000000000000000000000000000001"
},
"version": {
"content_hash": "1111111111111111111111111111111111111111111111111111111111111111",
"issued_at": "2026-10-10T08:00:00Z"
},
"authority": {
"asserted_by": "acme-contracts",
"trust_score": 0.95
},
"temporal_validity": {
"valid_from": "2026-10-10T08:00:00Z",
"valid_until": null
},
"content_reference": {
"uri": "https://example.com/contracts/2026-001.pdf",
"media_type": "application/pdf"
}
}
]
},
"context": {
"model": {
"model_name": "claude-sonnet-4-5",
"model_version": "2026-09-15"
},
"parameters": {
"temperature": 0.3,
"top_p": 0.95,
"prompt_cache_id": null,
"tool_budget": 8,
"retrieval_depth": 5
},
"constraints": [
{
"id": "not-a-uri",
"type": "jurisdiction",
"scope": "all_decisions",
"value": "US-NY"
}
]
},
"decisions": [
{
"id": "urn:source-engineering:decision:00000000-0000-0000-0000-000000000001",
"decision_text": "The contract is a standard commercial services agreement with mutual indemnification, a 30-day termination notice, and a New York governing-law clause.",
"basis_id": "urn:source-engineering:basis:00000000-0000-0000-0000-000000000001",
"evidence_chain_ref": "urn:source-engineering:evidence-chain:00000000-0000-0000-0000-000000000001",
"validation_status": {
"status": "pending",
"last_changed_at": "2026-10-11T12:00:00Z",
"last_changed_by": "test-fixture"
},
"responsible_authority": {
"authority_id": "claude-sonnet-4-5",
"authority_type": "automated"
},
"audit_history": [
{
"id": "urn:source-engineering:audit:00000000-0000-0000-0000-000000000001",
"action": "created",
"performed_by": "test-fixture",
"performed_at": "2026-10-11T12:00:00Z",
"notes": "Initial creation."
}
],
"created_at": "2026-10-11T12:00:00Z"
}
],
"evidence": {
"evidence_chain": [
{
"decision_id": "urn:source-engineering:decision:00000000-0000-0000-0000-000000000001",
"supporting_evidence": [
{
"id": "urn:source-engineering:evidence:00000000-0000-0000-0000-000000000001",
"claim_id": "urn:source-engineering:claim:00000000-0000-0000-0000-000000000001",
"source_id": "urn:source-engineering:source:contract-2026-001",
"transformation": {
"operation": "extract_clause",
"inputs": [
"https://example.com/contracts/2026-001.pdf#section-7"
],
"output": "Section 7.1: Each party shall indemnify and hold harmless...",
"recorded_at": "2026-10-11T12:00:00Z"
},
"uncertainty": {
"calibrated_score": 0.92,
"basis": "Direct extraction from the section 7.1 text."
},
"polarity": "supports"
}
],
"contradicting_evidence": [
{
"id": "urn:source-engineering:evidence:00000000-0000-0000-0000-000000000002",
"claim_id": "urn:source-engineering:claim:00000000-0000-0000-0000-000000000001",
"source_id": "urn:source-engineering:source:contract-2026-001",
"transformation": {
"operation": "search_clause",
"inputs": [
"https://example.com/contracts/2026-001.pdf#section-12"
],
"output": "Section 12.4: This agreement shall be governed by the laws of the State of Delaware.",
"recorded_at": "2026-10-11T12:00:00Z"
},
"uncertainty": {
"calibrated_score": 0.78,
"basis": "Contradictory governing-law clause in section 12.4 (the model rejected it because section 14.1 is the prevailing clause)."
},
"polarity": "contradicts"
}
],
"claims": [
{
"id": "urn:source-engineering:claim:00000000-0000-0000-0000-000000000001",
"decision_record_id": "urn:source-engineering:decision:00000000-0000-0000-0000-000000000001",
"claim_text": "The contract contains a mutual indemnification clause in section 7.1.",
"claim_type": "factual",
"supporting_evidence": [
"urn:source-engineering:evidence:00000000-0000-0000-0000-000000000001"
],
"contradicting_evidence": [
"urn:source-engineering:evidence:00000000-0000-0000-0000-000000000002"
]
}
]
}
]
},
"validation": {
"feedback_surfaces": [
{
"id": "urn:source-engineering:surface:00000000-0000-0000-0000-000000000001",
"type": "appeal",
"target": "urn:source-engineering:decision:00000000-0000-0000-0000-000000000001",
"endpoint": "https://example.com/appeal/00000000-0000-0000-0000-000000000001",
"description": "File an appeal against this decision."
}
]
}
}
Loading
Loading