Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ id: api.golden.component-boundaries
title: api golden component boundaries
scope: local-component-tests
workflow:
- id: inbox-question-scope-native
action: { verifier: api.golden.inbox-question-scope-native }
expect: { status: passed }
- id: retired-class-profile-unit
action: { verifier: api.golden.retired-class-profile-unit }
expect: { status: passed }
Expand Down Expand Up @@ -2577,6 +2580,12 @@ workflow:
- id: mcp-write-preconditions-unit
action: { verifier: api.golden.mcp-write-preconditions-unit }
expect: { status: passed }
- id: mcp-problem-legacy
action: { verifier: api.golden.mcp-problem-legacy }
expect: { status: passed }
- id: mcp-problem-modern
action: { verifier: api.golden.mcp-problem-modern }
expect: { status: passed }
- id: mcp-write-preconditions-postgres
action: { verifier: api.golden.mcp-write-preconditions-postgres }
expect: { status: passed }
Expand Down
15 changes: 15 additions & 0 deletions guarantees/verifiers/golden.verifiers.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
schemaVersion: treeseed.guarantee-verifiers/v1
ownerPackage: "@treeseed/api"
verifiers:
api.golden.inbox-question-scope-native:
kind: vitestCase
ownerPackage: "@treeseed/api"
testFile: tests/unit/control-plane/knowledge/workspaces/architecture/relation-authoring-native.test.ts
testName: "real native inbox question authoring grants only its question path and retains exact Git bytes and relations"
api.golden.retired-class-profile-unit:
kind: vitestCase
ownerPackage: "@treeseed/api"
Expand Down Expand Up @@ -4526,6 +4531,16 @@ verifiers:
ownerPackage: "@treeseed/api"
testFile: tests/unit/control-plane/mcp/authorization-equivalence.test.ts
testName: "retains real MCP mutation identity and version headers and denies missing authority before dispatch"
api.golden.mcp-problem-legacy:
kind: vitestCase
ownerPackage: "@treeseed/api"
testFile: tests/unit/control-plane/mcp/authorization-equivalence.test.ts
testName: "preserves the shared safe operation problem over native MCP legacy"
api.golden.mcp-problem-modern:
kind: vitestCase
ownerPackage: "@treeseed/api"
testFile: tests/unit/control-plane/mcp/authorization-equivalence.test.ts
testName: "preserves the shared safe operation problem over native MCP 2026-07-28"
api.golden.mcp-write-preconditions-postgres:
kind: vitestCase
ownerPackage: "@treeseed/api"
Expand Down
18 changes: 18 additions & 0 deletions src/api/capacity/policy/execution/execution-graph-projector.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { createHash } from 'node:crypto';
import { agentProfileReference } from './agent-profile-reference.ts';
import { contributionBindings } from './contribution-bindings.ts';
import type { QuestionConditionSource } from '../../services/capacity/execution/question-condition-source.ts';
import {
executionNodeSchema,
graphRevisionSchema,
Expand Down Expand Up @@ -203,6 +204,7 @@ export function projectTeamExecutionGraph(input: {
sources: ExecutableProposalSource[];
profiles: Record<string, AgentDefinition>;
dependencyLinks?: VerifiedDependencyLink[];
questionSources?: QuestionConditionSource[];
createdAt?: string;
}): ExecutionGraphProjection {
const nodes: ExecutionNode[] = [];
Expand Down Expand Up @@ -258,6 +260,22 @@ export function projectTeamExecutionGraph(input: {
} else completionByItem.set(text(workItem.id), actor);
}

for (const question of input.questionSources ?? []) {
if (question.teamId !== source.teamId || question.projectId !== source.projectId || question.severity !== 'blocking'
|| !question.relatedProposalIds.includes(sourceRef.id)
|| question.relatedDecisionIds.length && !question.relatedDecisionIds.includes(source.decision?.id ?? '')) continue;
const targets = [...new Set(question.relatedWorkItemIds)].flatMap(id => actorByItem.has(id) ? [actorByItem.get(id)!] : []);
if (!targets.length) continue;
const questionRef: ExactEntityReference = { store: 'treedx', model: 'question', id: question.id,
revision: question.revision, digest: question.digest, repository: question.repositoryId, path: question.path, commit: question.commit };
const resolved = question.status === 'answered' && question.answerRef !== null;
const condition = feedbackConditionNode(exactSource, { id: `inbox:${question.id}`, kind: 'question', sourceRef: questionRef, resolved }, input.revision);
if (resolved) condition.authorityRefs = [question.answerRef!];
nodes.push(condition); changedSourceRefs.push(questionRef, ...(resolved ? [question.answerRef!] : []));
for (const actor of targets) edges.push(edge({ teamId: source.teamId, fromNodeId: condition.id, toNodeId: actor.id,
provenance: 'governance', sourceRef: questionRef }, input.revision));
}

for (const workItem of workItems) {
const actor = actorByItem.get(text(workItem.id));
if (!actor) continue;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createHash } from 'node:crypto';
import { exactDependencyLinkSchema, validatePortableContentData } from '@treeseed/sdk/content-validation';
import { projectLibraryPath, resolveKnowledgeGatewayConnection } from '../../../../knowledge/gateway-treedx-connection.ts';
import { completedGraphRefresh, projectLibraryPath, resolveKnowledgeGatewayConnection } from '../../../../knowledge/gateway-treedx-connection.ts';
import type { ExecutableProposalSource, VerifiedDependencyLink } from '../../../policy/execution/execution-graph-projector.ts';

type Row = Record<string, unknown>;
Expand Down Expand Up @@ -42,9 +42,19 @@ export async function loadTeamExactDependencyLinks(store: any, sources: Executab
projectId: project.projectId, write: false, relationPaths: true, readRefs: [commit],
});
if (!pinned || pinned.repositoryId !== project.repository) throw new Error(`TreeDX exact dependency read grant is missing for project ${project.projectId}.`);
const response = record(await pinned.client.queryGraph({ repoId: pinned.repositoryId,
const query = { repoId: pinned.repositoryId,
ref: commit, seeds: [{ kind: 'type', value: 'ExactEntityReference' }],
relations: ['depends_on'], options: { direction: 'both', depth: 1, maxNodes: 50 } }));
relations: ['depends_on'], options: { direction: 'both', depth: 1, maxNodes: 50 } };
let response: Row;
try { response = record(await pinned.client.queryGraph(query)); }
catch (error) {
if (text(record(error).code) !== 'graph_not_ready') throw error;
const refresh = await resolveKnowledgeGatewayConnection(store, { projectId: project.projectId,
write: false, relationPaths: true, readRefs: [commit], refreshGraph: true });
if (!refresh || refresh.repositoryId !== project.repository) throw new Error(`TreeDX exact dependency refresh grant is missing for project ${project.projectId}.`);
await completedGraphRefresh(refresh.client, { repoId: refresh.repositoryId, ref: commit, paths: refresh.allowedPaths });
response = record(await pinned.client.queryGraph(query));
}
if (text(response.resolvedRef) !== commit) throw new Error('TreeDX dependency graph moved during read.');
const nodes = response.nodes;
if (!Array.isArray(nodes) || nodes.length >= 50 || !Array.isArray(response.edges)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { exactEntityReferenceSchema, type ExactEntityReference } from '@treeseed/sdk/agent-capacity';
import { isDeepStrictEqual } from 'node:util';
import { inboxQuestionReference } from '../../../../control-plane/inbox/question-content.ts';
type Row = Record<string, unknown>;
function record(value: unknown): Row {
if (value && typeof value === 'object' && !Array.isArray(value)) return value as Row;
Expand All @@ -24,28 +27,36 @@ export interface QuestionConditionSource {
relatedProposalIds: string[];
relatedDecisionIds: string[];
relatedWorkItemIds: string[];
answerRef: { model: 'discussion_message'; id: string; revision: number; digest: string; repositoryId: string; path: string; commit: string } | null;
answerRef: ExactEntityReference | null;
}

/** Read the indexed current question lifecycle while retaining exact TreeDX
* provenance. Question and answer bytes remain in TreeDX. */
/** Question and answer bytes remain in TreeDX. Only the owning readback's
* existing lifecycle event can prove an answer; author-supplied MDX metadata
* cannot assert verification or reuse proof after scope edits or reopen. */
export async function loadTeamQuestionConditionSources(store: any, teamId: string): Promise<QuestionConditionSource[]> {
const rows = await store.all(`SELECT id,team_id,project_id,status,title,version,repository_id,content_path,commit_sha,digest,metadata_json
FROM inbox_items WHERE team_id=? AND kind='question' AND status IN ('outstanding','answered','closed') ORDER BY project_id,id`, [teamId]);
if (!rows.length) return [];
const lifecycle = await store.all(`SELECT DISTINCT ON (item_id) item_id,event_type,payload_json FROM inbox_events WHERE team_id=?
AND event_type IN ('question.answered','question.reopened') ORDER BY item_id,sequence DESC`, [teamId]);
const latest = new Map<string, Row>();
for (const event of lifecycle) if (!latest.has(text(event.item_id))) latest.set(text(event.item_id), event);
return rows.flatMap((row: Row) => {
const metadata = record(row.metadata_json), answer = record(metadata.answerProvenance);
const metadata = record(row.metadata_json), event = latest.get(text(row.id)), proof = record(event?.payload_json);
const relatedProposalIds = strings(metadata.related_proposals, metadata.relatedProposals), relatedWorkItemIds = strings(metadata.related_work_items, metadata.relatedWorkItems);
if (text(metadata.severity) === 'advisory' || !relatedProposalIds.length || !relatedWorkItemIds.length) return [];
const repositoryId = text(row.repository_id), path = text(row.content_path), commit = text(row.commit_sha), digest = text(row.digest);
if (!repositoryId || !path || !/^[a-f0-9]{40}$/u.test(commit) || !digest) return [];
const answerCommit = text(answer.commit), answerPath = text(answer.path), answerDigest = text(answer.digest);
const answerRef = /^[a-f0-9]{40}$/u.test(answerCommit) && answerPath && answerDigest ? {
model: 'discussion_message' as const, id: text(answer.id) || answerPath, revision: Math.max(1, Number(answer.revision) || 1),
digest: answerDigest, repositoryId: text(answer.repositoryId) || repositoryId, path: answerPath, commit: answerCommit,
} : null;
if (!repositoryId || !path || !/^[a-f0-9]{40}$/u.test(commit) || !digest) throw Object.assign(new Error('A scoped blocking question lacks exact TreeDX authority.'), { code: 'inbox_question_provenance_invalid' });
const questionRef = inboxQuestionReference(row), parsedQuestion = exactEntityReferenceSchema.safeParse(proof.questionRef);
const parsedAnswer = exactEntityReferenceSchema.safeParse(proof.answerRef);
const answerRef = event?.event_type === 'question.answered' && parsedQuestion.success && isDeepStrictEqual(parsedQuestion.data, questionRef)
&& parsedAnswer.success && parsedAnswer.data.store === 'treedx'
&& parsedAnswer.data.model === 'discussion-message' && parsedAnswer.data.repository === repositoryId ? parsedAnswer.data : null;
return [{ teamId: text(row.team_id), projectId: text(row.project_id), id: text(row.id), revision: Math.max(1, Number(row.version) || 1),
digest, status: row.status === 'answered' ? 'answered' as const : row.status === 'closed' ? 'closed' as const : 'open' as const,
digest: digest.startsWith('sha256:') ? digest : `sha256:${digest}`, status: row.status === 'answered' ? 'answered' as const : row.status === 'closed' ? 'closed' as const : 'open' as const,
severity: text(metadata.severity) === 'advisory' ? 'advisory' as const : 'blocking' as const, title: text(row.title), repositoryId, path, commit,
relatedProposalIds: strings(metadata.related_proposals, metadata.relatedProposals),
relatedProposalIds,
relatedDecisionIds: strings(metadata.related_decisions, metadata.relatedDecisions),
relatedWorkItemIds: strings(metadata.related_work_items, metadata.relatedWorkItems), answerRef }];
relatedWorkItemIds, answerRef }];
});
}
13 changes: 13 additions & 0 deletions src/api/control-plane/catalog/operation-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,19 @@ export class ControlPlaneOperationError extends Error {
}
}

export function controlPlaneProblem(error: ControlPlaneOperationError, requestId: string, instance: string) {
const title = error.status === 400 ? 'Invalid request'
: error.status === 401 ? 'Authentication required'
: error.status === 403 ? 'Access denied'
: error.status === 404 ? 'Not found'
: error.status === 409 ? 'Conflict'
: error.status === 412 ? 'Precondition failed'
: error.status === 429 ? 'Rate limited'
: error.status === 503 ? 'Service unavailable' : 'Operation failed';
return { type: `https://treeseed.dev/problems/${error.code}`, title, status: error.status,
code: error.code, detail: error.message, instance, requestId };
}

export class OperationRegistry {
readonly catalog: ControlPlaneCatalog;
readonly operations: ReadonlyMap<string, BoundOperation>;
Expand Down
21 changes: 3 additions & 18 deletions src/api/control-plane/http/operation-http-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,15 @@ import { createHash, randomUUID } from 'node:crypto';
import type { AuthInfo } from '@modelcontextprotocol/server';
import type { Context } from 'hono';
import { ZodError } from 'zod';
import { ControlPlaneOperationError, type BoundOperation } from '../catalog/operation-registry.ts';
import { controlPlaneProblem, ControlPlaneOperationError, type BoundOperation } from '../catalog/operation-registry.ts';
import { enforceOperationAuthorization, readOperationPreconditions } from '../authorization/invocation-policy.ts';
import { CONFIRMATION_HEADER, decodeConfirmation, type ConfirmationService } from '../confirmation/confirmation-service.ts';

type Authenticate = (request: Request) => Promise<AuthInfo | Response>;

function problem(context: Context, error: ControlPlaneOperationError, requestId: string) {
const title = error.status === 400 ? 'Invalid request'
: error.status === 401 ? 'Authentication required'
: error.status === 403 ? 'Access denied'
: error.status === 404 ? 'Not found'
: error.status === 409 ? 'Conflict'
: error.status === 412 ? 'Precondition failed'
: error.status === 429 ? 'Rate limited'
: error.status === 503 ? 'Service unavailable' : 'Operation failed';
return context.json({
type: `https://treeseed.dev/problems/${error.code}`,
title,
status: error.status,
code: error.code,
detail: error.message,
instance: new URL(context.req.url).pathname,
requestId,
}, error.status, { 'content-type': 'application/problem+json', 'x-request-id': requestId });
return context.json(controlPlaneProblem(error, requestId, new URL(context.req.url).pathname),
error.status, { 'content-type': 'application/problem+json', 'x-request-id': requestId });
}

function etag(value: unknown) {
Expand Down
Loading
Loading