@treeseed/sdk 0.13.0-rc.3 → 0.13.0-rc.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/entrypoints/clients/control-plane-client.d.ts +62 -0
- package/dist/entrypoints/clients/control-plane-client.js +96 -0
- package/dist/market-client/interface.d.ts +0 -5
- package/dist/market-client/internal-capacity-allocation.d.ts +4 -0
- package/dist/market-client/internal-capacity-allocation.js +17 -0
- package/dist/market-client/methods.js +0 -10
- package/dist/operator-contracts/control-plane-operation.d.ts +60 -0
- package/dist/operator-contracts/control-plane-operation.js +75 -0
- package/dist/operator-contracts/index.d.ts +3 -0
- package/dist/operator-contracts/index.js +3 -0
- package/dist/operator-contracts/mcp.d.ts +78 -0
- package/dist/operator-contracts/mcp.js +23 -0
- package/dist/operator-contracts/oauth.d.ts +38 -0
- package/dist/operator-contracts/oauth.js +0 -0
- package/dist/operator-contracts/workday-profile.d.ts +6 -0
- package/dist/operator-contracts/workday-profile.js +17 -0
- package/dist/reconcile/capacity/capacity-core/live-acceptance-capacity-competition.js +3 -2
- package/dist/reconcile/capacity/capacity-core/live-acceptance-capacity-proof.js +4 -2
- package/dist/reconcile/runtime/live-acceptance-starter-runtime.js +5 -4
- package/dist/scenes/agent-lab/production-lifecycle.js +3 -2
- package/dist/seeds/runtime/local-capacity.js +4 -2
- package/dist/standards/mcp/compare.d.ts +2 -0
- package/dist/standards/mcp/compare.js +38 -0
- package/dist/standards/mcp/contracts.d.ts +34 -0
- package/dist/standards/mcp/contracts.js +0 -0
- package/dist/standards/mcp/index.d.ts +3 -0
- package/dist/standards/mcp/index.js +3 -0
- package/dist/standards/mcp/normalize.d.ts +4 -0
- package/dist/standards/mcp/normalize.js +30 -0
- package/package.json +9 -1
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { ApiPrincipal } from './remote.js';
|
|
2
|
+
export declare const DEFAULT_CONTROL_PLANE_BASE_URL = "http://127.0.0.1:3002";
|
|
3
|
+
export declare const CONTROL_PLANE_BASE_URL_ENV = "TREESEED_API_BASE_URL";
|
|
4
|
+
export interface ControlPlaneServerProfile {
|
|
5
|
+
serverId: string;
|
|
6
|
+
label: string;
|
|
7
|
+
baseUrl: string;
|
|
8
|
+
}
|
|
9
|
+
export interface ControlPlaneServerSession {
|
|
10
|
+
serverId: string;
|
|
11
|
+
accessToken: string;
|
|
12
|
+
refreshToken?: string;
|
|
13
|
+
expiresAt?: string;
|
|
14
|
+
principal?: ApiPrincipal | null;
|
|
15
|
+
}
|
|
16
|
+
export interface ControlPlaneClientOptions {
|
|
17
|
+
profile: ControlPlaneServerProfile;
|
|
18
|
+
accessToken?: string | null;
|
|
19
|
+
fetchImpl?: typeof fetch;
|
|
20
|
+
userAgent?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface ControlPlaneResponseEnvelope<T> {
|
|
23
|
+
data: T;
|
|
24
|
+
meta?: Record<string, unknown>;
|
|
25
|
+
links?: Record<string, string>;
|
|
26
|
+
}
|
|
27
|
+
export interface ProblemDetails {
|
|
28
|
+
type: string;
|
|
29
|
+
title: string;
|
|
30
|
+
status: number;
|
|
31
|
+
detail?: string;
|
|
32
|
+
instance?: string;
|
|
33
|
+
code: string;
|
|
34
|
+
requestId?: string;
|
|
35
|
+
traceId?: string;
|
|
36
|
+
fields?: Record<string, string[]>;
|
|
37
|
+
}
|
|
38
|
+
export interface ControlPlaneCallOptions {
|
|
39
|
+
method?: 'DELETE' | 'GET' | 'PATCH' | 'POST' | 'PUT';
|
|
40
|
+
path: `/v1/${string}` | '/openapi.json';
|
|
41
|
+
input?: unknown;
|
|
42
|
+
headers?: Record<string, string>;
|
|
43
|
+
idempotencyKey?: string;
|
|
44
|
+
ifMatch?: string;
|
|
45
|
+
signal?: AbortSignal;
|
|
46
|
+
}
|
|
47
|
+
export declare class ControlPlaneClientError extends Error {
|
|
48
|
+
readonly status: number;
|
|
49
|
+
readonly problem: ProblemDetails;
|
|
50
|
+
readonly responseHeaders: Headers;
|
|
51
|
+
constructor(message: string, status: number, problem: ProblemDetails, responseHeaders: Headers);
|
|
52
|
+
}
|
|
53
|
+
export declare function defaultLocalControlPlaneServer(env?: Record<string, string | undefined>): ControlPlaneServerProfile;
|
|
54
|
+
export declare class ControlPlaneClient {
|
|
55
|
+
readonly options: ControlPlaneClientOptions;
|
|
56
|
+
readonly baseUrl: string;
|
|
57
|
+
readonly accessToken: string | null;
|
|
58
|
+
readonly fetchImpl: typeof fetch;
|
|
59
|
+
readonly userAgent?: string;
|
|
60
|
+
constructor(options: ControlPlaneClientOptions);
|
|
61
|
+
call<T>(options: ControlPlaneCallOptions): Promise<ControlPlaneResponseEnvelope<T>>;
|
|
62
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
const DEFAULT_CONTROL_PLANE_BASE_URL = "http://127.0.0.1:3002";
|
|
2
|
+
const CONTROL_PLANE_BASE_URL_ENV = "TREESEED_API_BASE_URL";
|
|
3
|
+
class ControlPlaneClientError extends Error {
|
|
4
|
+
constructor(message, status, problem, responseHeaders) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.status = status;
|
|
7
|
+
this.problem = problem;
|
|
8
|
+
this.responseHeaders = responseHeaders;
|
|
9
|
+
this.name = "ControlPlaneClientError";
|
|
10
|
+
}
|
|
11
|
+
status;
|
|
12
|
+
problem;
|
|
13
|
+
responseHeaders;
|
|
14
|
+
}
|
|
15
|
+
function normalizeBaseUrl(value) {
|
|
16
|
+
const normalized = value.trim().replace(/\/+$/u, "");
|
|
17
|
+
if (!/^https?:\/\//u.test(normalized)) throw new Error("Control-plane server URLs must use HTTP or HTTPS.");
|
|
18
|
+
return normalized;
|
|
19
|
+
}
|
|
20
|
+
function defaultLocalControlPlaneServer(env = process.env) {
|
|
21
|
+
return {
|
|
22
|
+
serverId: "local",
|
|
23
|
+
label: "Local TreeSeed control plane",
|
|
24
|
+
baseUrl: normalizeBaseUrl(env[CONTROL_PLANE_BASE_URL_ENV] ?? DEFAULT_CONTROL_PLANE_BASE_URL)
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
async function responsePayload(response) {
|
|
28
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
29
|
+
if (contentType.includes("json")) return response.json();
|
|
30
|
+
const text = await response.text();
|
|
31
|
+
return text.length > 0 ? text : null;
|
|
32
|
+
}
|
|
33
|
+
function problemFrom(payload, status) {
|
|
34
|
+
const source = payload && typeof payload === "object" ? payload : {};
|
|
35
|
+
return {
|
|
36
|
+
type: typeof source.type === "string" ? source.type : "about:blank",
|
|
37
|
+
title: typeof source.title === "string" ? source.title : "Control-plane request failed",
|
|
38
|
+
status,
|
|
39
|
+
detail: typeof source.detail === "string" ? source.detail : void 0,
|
|
40
|
+
instance: typeof source.instance === "string" ? source.instance : void 0,
|
|
41
|
+
code: typeof source.code === "string" ? source.code : "control_plane_request_failed",
|
|
42
|
+
requestId: typeof source.requestId === "string" ? source.requestId : void 0,
|
|
43
|
+
traceId: typeof source.traceId === "string" ? source.traceId : void 0,
|
|
44
|
+
fields: source.fields && typeof source.fields === "object" ? source.fields : void 0
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
class ControlPlaneClient {
|
|
48
|
+
constructor(options) {
|
|
49
|
+
this.options = options;
|
|
50
|
+
this.baseUrl = normalizeBaseUrl(options.profile.baseUrl);
|
|
51
|
+
this.accessToken = options.accessToken ?? null;
|
|
52
|
+
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
53
|
+
this.userAgent = options.userAgent;
|
|
54
|
+
}
|
|
55
|
+
options;
|
|
56
|
+
baseUrl;
|
|
57
|
+
accessToken;
|
|
58
|
+
fetchImpl;
|
|
59
|
+
userAgent;
|
|
60
|
+
async call(options) {
|
|
61
|
+
const headers = new Headers(options.headers);
|
|
62
|
+
headers.set("accept", "application/json, application/problem+json");
|
|
63
|
+
if (this.accessToken) headers.set("authorization", `Bearer ${this.accessToken}`);
|
|
64
|
+
if (this.userAgent) headers.set("user-agent", this.userAgent);
|
|
65
|
+
if (options.idempotencyKey) headers.set("idempotency-key", options.idempotencyKey);
|
|
66
|
+
if (options.ifMatch) headers.set("if-match", options.ifMatch);
|
|
67
|
+
if (options.input !== void 0) headers.set("content-type", "application/json");
|
|
68
|
+
const response = await this.fetchImpl(`${this.baseUrl}${options.path}`, {
|
|
69
|
+
method: options.method ?? "GET",
|
|
70
|
+
headers,
|
|
71
|
+
body: options.input === void 0 ? void 0 : JSON.stringify(options.input),
|
|
72
|
+
signal: options.signal
|
|
73
|
+
});
|
|
74
|
+
const payload = await responsePayload(response);
|
|
75
|
+
if (!response.ok) {
|
|
76
|
+
const problem = problemFrom(payload, response.status);
|
|
77
|
+
throw new ControlPlaneClientError(problem.detail ?? problem.title, response.status, problem, response.headers);
|
|
78
|
+
}
|
|
79
|
+
if (!payload || typeof payload !== "object" || !("data" in payload)) {
|
|
80
|
+
throw new ControlPlaneClientError("The control plane returned an invalid success envelope.", 502, {
|
|
81
|
+
type: "https://treeseed.dev/problems/invalid-upstream-response",
|
|
82
|
+
title: "Invalid control-plane response",
|
|
83
|
+
status: 502,
|
|
84
|
+
code: "control_plane_response_invalid"
|
|
85
|
+
}, response.headers);
|
|
86
|
+
}
|
|
87
|
+
return payload;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
export {
|
|
91
|
+
CONTROL_PLANE_BASE_URL_ENV,
|
|
92
|
+
ControlPlaneClient,
|
|
93
|
+
ControlPlaneClientError,
|
|
94
|
+
DEFAULT_CONTROL_PLANE_BASE_URL,
|
|
95
|
+
defaultLocalControlPlaneServer
|
|
96
|
+
};
|
|
@@ -119,12 +119,7 @@ declare module "../support/market-client.js" {
|
|
|
119
119
|
createCapacityGrant: OmitThisParameter<typeof import("./capacity/allocations/creation/create-capacity-grant.js").createCapacityGrantMethod>;
|
|
120
120
|
transitionCapacityGrant: OmitThisParameter<typeof import("./capacity/allocations/updates/transition-capacity-grant.js").transitionCapacityGrantMethod>;
|
|
121
121
|
capacityAllocationSets: OmitThisParameter<typeof import("./capacity/allocations/contracts/capacity-allocation-sets.js").capacityAllocationSetsMethod>;
|
|
122
|
-
createCapacityAllocationSet: OmitThisParameter<typeof import("./capacity/allocations/creation/create-capacity-allocation-set.js").createCapacityAllocationSetMethod>;
|
|
123
|
-
planCapacityAllocationSet: OmitThisParameter<typeof import("./capacity/allocations/creation/plan-capacity-allocation-set.js").planCapacityAllocationSetMethod>;
|
|
124
122
|
capacityAllocationSet: OmitThisParameter<typeof import("./capacity/allocations/contracts/capacity-allocation-set.js").capacityAllocationSetMethod>;
|
|
125
|
-
activateCapacityAllocationSet: OmitThisParameter<typeof import("./capacity/allocations/lifecycle/activate-capacity-allocation-set.js").activateCapacityAllocationSetMethod>;
|
|
126
|
-
supersedeCapacityAllocationSet: OmitThisParameter<typeof import("./capacity/allocations/updates/supersede-capacity-allocation-set.js").supersedeCapacityAllocationSetMethod>;
|
|
127
|
-
archiveCapacityAllocationSet: OmitThisParameter<typeof import("./capacity/allocations/retirement/archive-capacity-allocation-set.js").archiveCapacityAllocationSetMethod>;
|
|
128
123
|
explainCapacityAllocationSet: OmitThisParameter<typeof import("./capacity/allocations/queries/explain-capacity-allocation-set.js").explainCapacityAllocationSetMethod>;
|
|
129
124
|
providerAvailabilitySessions: OmitThisParameter<typeof import("./capacity/providers/contracts/provider-availability-sessions.js").providerAvailabilitySessionsMethod>;
|
|
130
125
|
capacityProviderAssignments: OmitThisParameter<typeof import("./capacity/assignments/contracts/capacity-provider-assignments.js").capacityProviderAssignmentsMethod>;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { MarketClient } from '../entrypoints/clients/market-client.js';
|
|
2
|
+
export declare function createInternalCapacityAllocationSet(client: MarketClient, teamId: string, body: Record<string, unknown>, idempotencyKey: string): any;
|
|
3
|
+
export declare function activateInternalCapacityAllocationSet(client: MarketClient, teamId: string, allocationSetId: string, idempotencyKey: string): any;
|
|
4
|
+
export declare function supersedeInternalCapacityAllocationSet(client: MarketClient, teamId: string, allocationSetId: string, body: Record<string, unknown>, idempotencyKey: string): any;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createCapacityAllocationSetMethod } from "./capacity/allocations/creation/create-capacity-allocation-set.js";
|
|
2
|
+
import { activateCapacityAllocationSetMethod } from "./capacity/allocations/lifecycle/activate-capacity-allocation-set.js";
|
|
3
|
+
import { supersedeCapacityAllocationSetMethod } from "./capacity/allocations/updates/supersede-capacity-allocation-set.js";
|
|
4
|
+
function createInternalCapacityAllocationSet(client, teamId, body, idempotencyKey) {
|
|
5
|
+
return createCapacityAllocationSetMethod.call(client, teamId, body, idempotencyKey);
|
|
6
|
+
}
|
|
7
|
+
function activateInternalCapacityAllocationSet(client, teamId, allocationSetId, idempotencyKey) {
|
|
8
|
+
return activateCapacityAllocationSetMethod.call(client, teamId, allocationSetId, idempotencyKey);
|
|
9
|
+
}
|
|
10
|
+
function supersedeInternalCapacityAllocationSet(client, teamId, allocationSetId, body, idempotencyKey) {
|
|
11
|
+
return supersedeCapacityAllocationSetMethod.call(client, teamId, allocationSetId, body, idempotencyKey);
|
|
12
|
+
}
|
|
13
|
+
export {
|
|
14
|
+
activateInternalCapacityAllocationSet,
|
|
15
|
+
createInternalCapacityAllocationSet,
|
|
16
|
+
supersedeInternalCapacityAllocationSet
|
|
17
|
+
};
|
|
@@ -47,14 +47,9 @@ import { capacityAllocationSetMethod } from "./capacity/allocations/contracts/ca
|
|
|
47
47
|
import { capacityAllocationSetsMethod } from "./capacity/allocations/contracts/capacity-allocation-sets.js";
|
|
48
48
|
import { capacityGrantMethod } from "./capacity/allocations/contracts/capacity-grant.js";
|
|
49
49
|
import { capacityGrantsMethod } from "./capacity/allocations/contracts/capacity-grants.js";
|
|
50
|
-
import { createCapacityAllocationSetMethod } from "./capacity/allocations/creation/create-capacity-allocation-set.js";
|
|
51
50
|
import { createCapacityGrantMethod } from "./capacity/allocations/creation/create-capacity-grant.js";
|
|
52
|
-
import { planCapacityAllocationSetMethod } from "./capacity/allocations/creation/plan-capacity-allocation-set.js";
|
|
53
51
|
import { planCapacityGrantMethod } from "./capacity/allocations/creation/plan-capacity-grant.js";
|
|
54
|
-
import { activateCapacityAllocationSetMethod } from "./capacity/allocations/lifecycle/activate-capacity-allocation-set.js";
|
|
55
52
|
import { explainCapacityAllocationSetMethod } from "./capacity/allocations/queries/explain-capacity-allocation-set.js";
|
|
56
|
-
import { archiveCapacityAllocationSetMethod } from "./capacity/allocations/retirement/archive-capacity-allocation-set.js";
|
|
57
|
-
import { supersedeCapacityAllocationSetMethod } from "./capacity/allocations/updates/supersede-capacity-allocation-set.js";
|
|
58
53
|
import { transitionCapacityGrantMethod } from "./capacity/allocations/updates/transition-capacity-grant.js";
|
|
59
54
|
import { capacityProviderAssignmentMethod } from "./capacity/assignments/contracts/capacity-provider-assignment.js";
|
|
60
55
|
import { assignmentAuthorityProbeMethod } from "./capacity/assignments/contracts/assignment-authority-probe.js";
|
|
@@ -303,12 +298,7 @@ function installMarketClientMethods(prototype) {
|
|
|
303
298
|
prototype.createCapacityGrant = createCapacityGrantMethod;
|
|
304
299
|
prototype.transitionCapacityGrant = transitionCapacityGrantMethod;
|
|
305
300
|
prototype.capacityAllocationSets = capacityAllocationSetsMethod;
|
|
306
|
-
prototype.createCapacityAllocationSet = createCapacityAllocationSetMethod;
|
|
307
|
-
prototype.planCapacityAllocationSet = planCapacityAllocationSetMethod;
|
|
308
301
|
prototype.capacityAllocationSet = capacityAllocationSetMethod;
|
|
309
|
-
prototype.activateCapacityAllocationSet = activateCapacityAllocationSetMethod;
|
|
310
|
-
prototype.supersedeCapacityAllocationSet = supersedeCapacityAllocationSetMethod;
|
|
311
|
-
prototype.archiveCapacityAllocationSet = archiveCapacityAllocationSetMethod;
|
|
312
302
|
prototype.explainCapacityAllocationSet = explainCapacityAllocationSetMethod;
|
|
313
303
|
prototype.providerAvailabilitySessions = providerAvailabilitySessionsMethod;
|
|
314
304
|
prototype.capacityProviderAssignments = capacityProviderAssignmentsMethod;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export declare const CONTROL_PLANE_OPERATION_SCHEMA_VERSION: "treeseed.control-plane-operation/v1";
|
|
2
|
+
export type ControlPlaneHttpMethod = 'DELETE' | 'GET' | 'PATCH' | 'POST' | 'PUT';
|
|
3
|
+
export type ControlPlaneOperationKind = 'read' | 'mutation';
|
|
4
|
+
export type ControlPlaneRiskClass = 'ordinary' | 'destructive' | 'credential' | 'authority' | 'production' | 'irreversible';
|
|
5
|
+
export type ControlPlaneConfirmationPolicy = 'never' | 'input_required';
|
|
6
|
+
export type ControlPlaneOperationSurface = 'rest' | 'cli' | 'mcp_tool' | 'mcp_resource' | 'internal';
|
|
7
|
+
export type ControlPlaneCacheScope = 'none' | 'principal' | 'team' | 'project' | 'public';
|
|
8
|
+
export type ControlPlanePaginationKind = 'none' | 'cursor';
|
|
9
|
+
export interface ControlPlaneRestBinding {
|
|
10
|
+
method: ControlPlaneHttpMethod;
|
|
11
|
+
path: `/v1/${string}`;
|
|
12
|
+
}
|
|
13
|
+
export interface ControlPlaneSchemaBinding {
|
|
14
|
+
input: string;
|
|
15
|
+
output: string;
|
|
16
|
+
parameters?: string;
|
|
17
|
+
errors: string;
|
|
18
|
+
}
|
|
19
|
+
export interface ControlPlaneIdempotencyContract {
|
|
20
|
+
required: boolean;
|
|
21
|
+
header: 'Idempotency-Key';
|
|
22
|
+
}
|
|
23
|
+
export interface ControlPlaneConcurrencyContract {
|
|
24
|
+
required: boolean;
|
|
25
|
+
readHeader: 'ETag';
|
|
26
|
+
writeHeader: 'If-Match';
|
|
27
|
+
}
|
|
28
|
+
export interface ControlPlaneOperationDescriptor {
|
|
29
|
+
schemaVersion: typeof CONTROL_PLANE_OPERATION_SCHEMA_VERSION;
|
|
30
|
+
operationId: `${string}.${string}`;
|
|
31
|
+
description: string;
|
|
32
|
+
rest?: ControlPlaneRestBinding;
|
|
33
|
+
schemas: ControlPlaneSchemaBinding;
|
|
34
|
+
capability: string;
|
|
35
|
+
oauthScopes: OAuthScope[];
|
|
36
|
+
kind: ControlPlaneOperationKind;
|
|
37
|
+
riskClass: ControlPlaneRiskClass;
|
|
38
|
+
confirmation: ControlPlaneConfirmationPolicy;
|
|
39
|
+
idempotency: ControlPlaneIdempotencyContract;
|
|
40
|
+
concurrency: ControlPlaneConcurrencyContract;
|
|
41
|
+
surfaces: ControlPlaneOperationSurface[];
|
|
42
|
+
cacheScope: ControlPlaneCacheScope;
|
|
43
|
+
pagination: ControlPlanePaginationKind;
|
|
44
|
+
audited: boolean;
|
|
45
|
+
receipt: boolean;
|
|
46
|
+
redactedPaths: string[];
|
|
47
|
+
}
|
|
48
|
+
export declare const TREESEED_OAUTH_SCOPES: readonly ["treeseed:read", "treeseed:knowledge:write", "treeseed:governance:write", "treeseed:projects:write", "treeseed:execution", "treeseed:admin"];
|
|
49
|
+
export type OAuthScope = typeof TREESEED_OAUTH_SCOPES[number];
|
|
50
|
+
export interface ControlPlaneCatalog {
|
|
51
|
+
schemaVersion: 'treeseed.control-plane-catalog/v1';
|
|
52
|
+
operations: ControlPlaneOperationDescriptor[];
|
|
53
|
+
}
|
|
54
|
+
export interface ControlPlaneCatalogDiagnostic {
|
|
55
|
+
code: string;
|
|
56
|
+
path: string;
|
|
57
|
+
message: string;
|
|
58
|
+
}
|
|
59
|
+
export declare function validateControlPlaneCatalog(catalog: ControlPlaneCatalog): ControlPlaneCatalogDiagnostic[];
|
|
60
|
+
export declare function indexControlPlaneCatalog(catalog: ControlPlaneCatalog): Map<`${string}.${string}`, ControlPlaneOperationDescriptor>;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
const CONTROL_PLANE_OPERATION_SCHEMA_VERSION = "treeseed.control-plane-operation/v1";
|
|
2
|
+
const TREESEED_OAUTH_SCOPES = [
|
|
3
|
+
"treeseed:read",
|
|
4
|
+
"treeseed:knowledge:write",
|
|
5
|
+
"treeseed:governance:write",
|
|
6
|
+
"treeseed:projects:write",
|
|
7
|
+
"treeseed:execution",
|
|
8
|
+
"treeseed:admin"
|
|
9
|
+
];
|
|
10
|
+
const OPERATION_ID = /^[a-z][a-z0-9]*(?:\.[a-z][a-z0-9]*)+$/u;
|
|
11
|
+
const PATH_PARAMETER = /\{([A-Za-z][A-Za-z0-9]*)\}/gu;
|
|
12
|
+
function duplicates(values) {
|
|
13
|
+
const seen = /* @__PURE__ */ new Set();
|
|
14
|
+
return values.filter((value) => seen.size === seen.add(value).size);
|
|
15
|
+
}
|
|
16
|
+
function validateControlPlaneCatalog(catalog) {
|
|
17
|
+
const diagnostics = [];
|
|
18
|
+
const operationIds = /* @__PURE__ */ new Set();
|
|
19
|
+
const restBindings = /* @__PURE__ */ new Set();
|
|
20
|
+
for (const [index, operation] of catalog.operations.entries()) {
|
|
21
|
+
const path = `operations.${index}`;
|
|
22
|
+
if (operation.schemaVersion !== CONTROL_PLANE_OPERATION_SCHEMA_VERSION) {
|
|
23
|
+
diagnostics.push({ code: "operation_schema_version_invalid", path: `${path}.schemaVersion`, message: "Operation schemaVersion is not supported." });
|
|
24
|
+
}
|
|
25
|
+
if (!OPERATION_ID.test(operation.operationId)) {
|
|
26
|
+
diagnostics.push({ code: "operation_id_invalid", path: `${path}.operationId`, message: "Operation IDs must be stable dotted lowercase words." });
|
|
27
|
+
}
|
|
28
|
+
if (operationIds.has(operation.operationId)) {
|
|
29
|
+
diagnostics.push({ code: "operation_id_duplicate", path: `${path}.operationId`, message: `Duplicate operation ID ${operation.operationId}.` });
|
|
30
|
+
}
|
|
31
|
+
operationIds.add(operation.operationId);
|
|
32
|
+
if (operation.surfaces.includes("rest") !== Boolean(operation.rest)) {
|
|
33
|
+
diagnostics.push({ code: "rest_binding_mismatch", path: `${path}.rest`, message: "REST surface and REST binding must be declared together." });
|
|
34
|
+
}
|
|
35
|
+
if (operation.rest) {
|
|
36
|
+
const binding = `${operation.rest.method} ${operation.rest.path}`;
|
|
37
|
+
if (restBindings.has(binding)) diagnostics.push({ code: "rest_binding_duplicate", path: `${path}.rest`, message: `Duplicate REST binding ${binding}.` });
|
|
38
|
+
restBindings.add(binding);
|
|
39
|
+
const parameters = [...operation.rest.path.matchAll(PATH_PARAMETER)].map((match) => match[1]);
|
|
40
|
+
if (parameters.length > 0 && !operation.schemas.parameters) {
|
|
41
|
+
diagnostics.push({ code: "parameter_schema_required", path: `${path}.schemas.parameters`, message: "Parameterized REST paths require a parameter schema." });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
for (const scope of operation.oauthScopes) {
|
|
45
|
+
if (!TREESEED_OAUTH_SCOPES.includes(scope)) diagnostics.push({ code: "oauth_scope_invalid", path: `${path}.oauthScopes`, message: `Unknown OAuth scope ${scope}.` });
|
|
46
|
+
}
|
|
47
|
+
for (const duplicate of duplicates(operation.surfaces)) diagnostics.push({ code: "surface_duplicate", path: `${path}.surfaces`, message: `Duplicate operation surface ${duplicate}.` });
|
|
48
|
+
for (const duplicate of duplicates(operation.oauthScopes)) diagnostics.push({ code: "oauth_scope_duplicate", path: `${path}.oauthScopes`, message: `Duplicate OAuth scope ${duplicate}.` });
|
|
49
|
+
const elevatedRisk = operation.riskClass !== "ordinary";
|
|
50
|
+
if (elevatedRisk !== (operation.confirmation === "input_required")) {
|
|
51
|
+
diagnostics.push({ code: "confirmation_policy_invalid", path: `${path}.confirmation`, message: "Elevated-risk operations require input_required; ordinary operations must not." });
|
|
52
|
+
}
|
|
53
|
+
if (operation.kind === "read" && (operation.idempotency.required || operation.concurrency.required || operation.receipt)) {
|
|
54
|
+
diagnostics.push({ code: "read_mutation_contract_invalid", path, message: "Read operations cannot require mutation idempotency, write concurrency, or mutation receipts." });
|
|
55
|
+
}
|
|
56
|
+
if (operation.kind === "mutation" && operation.surfaces.some((surface) => surface !== "internal") && !operation.audited) {
|
|
57
|
+
diagnostics.push({ code: "mutation_audit_required", path: `${path}.audited`, message: "Every externally reachable mutation must be audited." });
|
|
58
|
+
}
|
|
59
|
+
if (operation.kind === "mutation" && !operation.receipt) {
|
|
60
|
+
diagnostics.push({ code: "mutation_receipt_required", path: `${path}.receipt`, message: "Mutations must return durable receipts." });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return diagnostics.sort((left, right) => `${left.path}:${left.code}`.localeCompare(`${right.path}:${right.code}`));
|
|
64
|
+
}
|
|
65
|
+
function indexControlPlaneCatalog(catalog) {
|
|
66
|
+
const diagnostics = validateControlPlaneCatalog(catalog);
|
|
67
|
+
if (diagnostics.length > 0) throw new Error(`Invalid control-plane catalog: ${diagnostics.map((entry) => entry.code).join(", ")}`);
|
|
68
|
+
return new Map(catalog.operations.map((operation) => [operation.operationId, operation]));
|
|
69
|
+
}
|
|
70
|
+
export {
|
|
71
|
+
CONTROL_PLANE_OPERATION_SCHEMA_VERSION,
|
|
72
|
+
TREESEED_OAUTH_SCOPES,
|
|
73
|
+
indexControlPlaneCatalog,
|
|
74
|
+
validateControlPlaneCatalog
|
|
75
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { ControlPlaneOperationDescriptor, OAuthScope } from './control-plane-operation.js';
|
|
2
|
+
export declare const MCP_PROTOCOL_VERSION: "2026-07-28";
|
|
3
|
+
export interface ResourceLink {
|
|
4
|
+
type: 'resource_link';
|
|
5
|
+
uri: `treeseed://${string}`;
|
|
6
|
+
name: string;
|
|
7
|
+
title?: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
mimeType?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface ActorChain {
|
|
12
|
+
principalId: string;
|
|
13
|
+
delegatedAgentId?: string;
|
|
14
|
+
oauthClientId: string;
|
|
15
|
+
interface: 'rest' | 'cli' | 'mcp' | 'site_bff' | 'internal';
|
|
16
|
+
conversationId?: string;
|
|
17
|
+
modelClaim?: string;
|
|
18
|
+
skillClaim?: string;
|
|
19
|
+
traceId: string;
|
|
20
|
+
}
|
|
21
|
+
export interface ConfirmationState {
|
|
22
|
+
schemaVersion: 'treeseed.confirmation-state/v1';
|
|
23
|
+
principalId: string;
|
|
24
|
+
clientId: string;
|
|
25
|
+
operationId: string;
|
|
26
|
+
argumentsDigest: `sha256:${string}`;
|
|
27
|
+
expiresAt: string;
|
|
28
|
+
nonce: string;
|
|
29
|
+
signature: string;
|
|
30
|
+
}
|
|
31
|
+
export interface InputRequired {
|
|
32
|
+
type: 'input_required';
|
|
33
|
+
requestId: string;
|
|
34
|
+
prompt: string;
|
|
35
|
+
confirmation: ConfirmationState;
|
|
36
|
+
}
|
|
37
|
+
export interface McpToolDescriptor {
|
|
38
|
+
name: string;
|
|
39
|
+
description: string;
|
|
40
|
+
inputSchemaId: string;
|
|
41
|
+
outputSchemaId: string;
|
|
42
|
+
operationId: string;
|
|
43
|
+
readOnlyHint: boolean;
|
|
44
|
+
destructiveHint: boolean;
|
|
45
|
+
idempotentHint: boolean;
|
|
46
|
+
openWorldHint: boolean;
|
|
47
|
+
}
|
|
48
|
+
export interface McpResourceDescriptor {
|
|
49
|
+
uriTemplate: `treeseed://${string}`;
|
|
50
|
+
name: string;
|
|
51
|
+
description: string;
|
|
52
|
+
mimeType: string;
|
|
53
|
+
operationId: string;
|
|
54
|
+
subscribable: boolean;
|
|
55
|
+
cacheTtlSeconds?: number;
|
|
56
|
+
}
|
|
57
|
+
export interface McpPromptDescriptor {
|
|
58
|
+
name: string;
|
|
59
|
+
description: string;
|
|
60
|
+
argumentSchemaId: string;
|
|
61
|
+
requiredScopes: OAuthScope[];
|
|
62
|
+
}
|
|
63
|
+
export interface McpCatalog {
|
|
64
|
+
schemaVersion: 'treeseed.mcp-catalog/v1';
|
|
65
|
+
protocolVersion: typeof MCP_PROTOCOL_VERSION;
|
|
66
|
+
tools: McpToolDescriptor[];
|
|
67
|
+
resources: McpResourceDescriptor[];
|
|
68
|
+
prompts: McpPromptDescriptor[];
|
|
69
|
+
capabilities: {
|
|
70
|
+
completion: true;
|
|
71
|
+
progress: true;
|
|
72
|
+
cancellation: true;
|
|
73
|
+
inputRequired: true;
|
|
74
|
+
resourceSubscriptions: true;
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
export declare function operationToMcpTool(operation: ControlPlaneOperationDescriptor): McpToolDescriptor | null;
|
|
78
|
+
export declare function buildMcpTools(operations: readonly ControlPlaneOperationDescriptor[]): McpToolDescriptor[];
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const MCP_PROTOCOL_VERSION = "2026-07-28";
|
|
2
|
+
function operationToMcpTool(operation) {
|
|
3
|
+
if (!operation.surfaces.includes("mcp_tool")) return null;
|
|
4
|
+
return {
|
|
5
|
+
name: operation.operationId,
|
|
6
|
+
description: operation.description,
|
|
7
|
+
inputSchemaId: operation.schemas.input,
|
|
8
|
+
outputSchemaId: operation.schemas.output,
|
|
9
|
+
operationId: operation.operationId,
|
|
10
|
+
readOnlyHint: operation.kind === "read",
|
|
11
|
+
destructiveHint: operation.riskClass !== "ordinary",
|
|
12
|
+
idempotentHint: operation.kind === "read" || operation.idempotency.required,
|
|
13
|
+
openWorldHint: false
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function buildMcpTools(operations) {
|
|
17
|
+
return operations.map(operationToMcpTool).filter((tool) => tool !== null).sort((left, right) => left.name.localeCompare(right.name));
|
|
18
|
+
}
|
|
19
|
+
export {
|
|
20
|
+
MCP_PROTOCOL_VERSION,
|
|
21
|
+
buildMcpTools,
|
|
22
|
+
operationToMcpTool
|
|
23
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { OAuthScope } from './control-plane-operation.js';
|
|
2
|
+
export interface OAuthAuthorizationServerMetadata {
|
|
3
|
+
issuer: string;
|
|
4
|
+
authorization_endpoint: string;
|
|
5
|
+
token_endpoint: string;
|
|
6
|
+
device_authorization_endpoint: string;
|
|
7
|
+
revocation_endpoint: string;
|
|
8
|
+
response_types_supported: ['code'];
|
|
9
|
+
grant_types_supported: ['authorization_code', 'refresh_token', 'urn:ietf:params:oauth:grant-type:device_code'];
|
|
10
|
+
code_challenge_methods_supported: ['S256'];
|
|
11
|
+
scopes_supported: OAuthScope[];
|
|
12
|
+
}
|
|
13
|
+
export interface OAuthProtectedResourceMetadata {
|
|
14
|
+
resource: string;
|
|
15
|
+
authorization_servers: string[];
|
|
16
|
+
scopes_supported: OAuthScope[];
|
|
17
|
+
bearer_methods_supported: ['header'];
|
|
18
|
+
}
|
|
19
|
+
export interface OAuthDeviceAuthorizationRequest {
|
|
20
|
+
clientId: string;
|
|
21
|
+
scope: OAuthScope[];
|
|
22
|
+
}
|
|
23
|
+
export interface OAuthDeviceAuthorizationResponse {
|
|
24
|
+
deviceCode: string;
|
|
25
|
+
userCode: string;
|
|
26
|
+
verificationUri: string;
|
|
27
|
+
verificationUriComplete: string;
|
|
28
|
+
expiresIn: number;
|
|
29
|
+
interval: number;
|
|
30
|
+
}
|
|
31
|
+
export interface OAuthTokenReceipt {
|
|
32
|
+
tokenType: 'Bearer';
|
|
33
|
+
accessToken: string;
|
|
34
|
+
expiresIn: number;
|
|
35
|
+
refreshToken?: string;
|
|
36
|
+
scope: OAuthScope[];
|
|
37
|
+
audience: string;
|
|
38
|
+
}
|
|
File without changes
|
|
@@ -24,6 +24,10 @@ export interface WorkdayAllocationProfile {
|
|
|
24
24
|
starvationLimitSeconds: number;
|
|
25
25
|
};
|
|
26
26
|
}
|
|
27
|
+
export interface RepositoryWorkdayProfileBundle {
|
|
28
|
+
schemaVersion: 'treeseed.workday-allocation-profile-bundle/v1';
|
|
29
|
+
profiles: WorkdayAllocationProfile[];
|
|
30
|
+
}
|
|
27
31
|
export interface ProjectAgentClassMembership {
|
|
28
32
|
projectId: string;
|
|
29
33
|
agentId: string;
|
|
@@ -63,4 +67,6 @@ export interface WorkdayBorrowingEvidence {
|
|
|
63
67
|
export declare function validateOneClassPerProjectAgent(memberships: ProjectAgentClassMembership[], expectedAgents?: ProjectAgentIdentity[]): WorkdayProfileDiagnostic[];
|
|
64
68
|
export declare function validateWorkdayAllocationProfile(profile: WorkdayAllocationProfile, classCatalogs?: ProjectClassCatalog[]): WorkdayProfileDiagnostic[];
|
|
65
69
|
export declare function normalizeWorkdayAllocationProfile(profile: WorkdayAllocationProfile): WorkdayAllocationProfile;
|
|
70
|
+
export declare function validateRepositoryWorkdayProfileBundle(bundle: RepositoryWorkdayProfileBundle, classCatalogs?: ProjectClassCatalog[]): WorkdayProfileDiagnostic[];
|
|
71
|
+
export declare function normalizeRepositoryWorkdayProfileBundle(bundle: RepositoryWorkdayProfileBundle): RepositoryWorkdayProfileBundle;
|
|
66
72
|
export declare function validateWorkdayBorrowingEvidence(evidence: WorkdayBorrowingEvidence): WorkdayProfileDiagnostic[];
|
|
@@ -71,6 +71,21 @@ function normalizeWorkdayAllocationProfile(profile) {
|
|
|
71
71
|
demandSources: [...new Set(profile.demandSources)].sort()
|
|
72
72
|
};
|
|
73
73
|
}
|
|
74
|
+
function validateRepositoryWorkdayProfileBundle(bundle, classCatalogs) {
|
|
75
|
+
const diagnostics = [];
|
|
76
|
+
if (bundle.schemaVersion !== "treeseed.workday-allocation-profile-bundle/v1") diagnostics.push({ code: "bundle_schema_version_invalid", path: "schemaVersion", message: "Unsupported repository workday profile bundle schema." });
|
|
77
|
+
if (!Array.isArray(bundle.profiles) || bundle.profiles.length === 0) diagnostics.push({ code: "bundle_profiles_empty", path: "profiles", message: "A repository workday profile bundle must contain at least one profile." });
|
|
78
|
+
const profileIds = /* @__PURE__ */ new Set();
|
|
79
|
+
for (const [index, profile] of (Array.isArray(bundle.profiles) ? bundle.profiles : []).entries()) {
|
|
80
|
+
if (profileIds.has(profile.id)) diagnostics.push({ code: "bundle_profile_id_duplicate", path: `profiles.${index}.id`, message: `Stable profile id ${profile.id} is declared more than once; a repository bundle contains exactly one current generation per profile.` });
|
|
81
|
+
profileIds.add(profile.id);
|
|
82
|
+
diagnostics.push(...validateWorkdayAllocationProfile(profile, classCatalogs).map((diagnostic) => ({ ...diagnostic, path: `profiles.${index}.${diagnostic.path}` })));
|
|
83
|
+
}
|
|
84
|
+
return diagnostics;
|
|
85
|
+
}
|
|
86
|
+
function normalizeRepositoryWorkdayProfileBundle(bundle) {
|
|
87
|
+
return { schemaVersion: "treeseed.workday-allocation-profile-bundle/v1", profiles: [...bundle.profiles].map(normalizeWorkdayAllocationProfile).sort((left, right) => left.id.localeCompare(right.id) || left.version.localeCompare(right.version)) };
|
|
88
|
+
}
|
|
74
89
|
function validateWorkdayBorrowingEvidence(evidence) {
|
|
75
90
|
const diagnostics = [];
|
|
76
91
|
if (!evidence.lendingPermitted) diagnostics.push({ code: "lending_not_permitted", path: "lendingPermitted", message: "The profile does not permit the lender to lend capacity." });
|
|
@@ -82,8 +97,10 @@ function validateWorkdayBorrowingEvidence(evidence) {
|
|
|
82
97
|
return diagnostics;
|
|
83
98
|
}
|
|
84
99
|
export {
|
|
100
|
+
normalizeRepositoryWorkdayProfileBundle,
|
|
85
101
|
normalizeWorkdayAllocationProfile,
|
|
86
102
|
validateOneClassPerProjectAgent,
|
|
103
|
+
validateRepositoryWorkdayProfileBundle,
|
|
87
104
|
validateWorkdayAllocationProfile,
|
|
88
105
|
validateWorkdayBorrowingEvidence
|
|
89
106
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ProviderProtocolClient } from "../../../capacity/providers/capacity-provider.js";
|
|
2
|
+
import { activateInternalCapacityAllocationSet, createInternalCapacityAllocationSet } from "../../../market-client/internal-capacity-allocation.js";
|
|
2
3
|
function projectRecord(payload) {
|
|
3
4
|
const project = payload.project ?? payload;
|
|
4
5
|
if (!project.id) throw new Error("Capacity competition project creation omitted its project id.");
|
|
@@ -96,7 +97,7 @@ async function provisionLocalCapacityCompetition(input) {
|
|
|
96
97
|
}, `${prefix}:grant-create`);
|
|
97
98
|
grantCreated = true;
|
|
98
99
|
await input.adminClient.transitionCapacityGrant(input.runtime.teamId, grantId, "activate", `${prefix}:grant-activate`);
|
|
99
|
-
const allocation = await input.adminClient
|
|
100
|
+
const allocation = await createInternalCapacityAllocationSet(input.adminClient, input.runtime.teamId, {
|
|
100
101
|
id: allocationId,
|
|
101
102
|
effectiveFrom: new Date(Date.now() - 1e3).toISOString(),
|
|
102
103
|
effectiveUntil: new Date(Date.now() + 10 * 6e4).toISOString(),
|
|
@@ -105,7 +106,7 @@ async function provisionLocalCapacityCompetition(input) {
|
|
|
105
106
|
borrowingRules: [],
|
|
106
107
|
metadata: { liveAcceptance: true, runId: input.runId }
|
|
107
108
|
}, `${prefix}:allocation-create`);
|
|
108
|
-
const activeAllocation = await input.adminClient
|
|
109
|
+
const activeAllocation = await activateInternalCapacityAllocationSet(input.adminClient, input.runtime.teamId, String(allocation.payload.id), `${prefix}:allocation-activate`);
|
|
109
110
|
availability = await providerClient.refreshAvailabilitySession(sessionId, {
|
|
110
111
|
expectedSequence: availability.payload.sequence,
|
|
111
112
|
environment: "local",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ProviderProtocolClient } from "../../../capacity/providers/capacity-provider.js";
|
|
2
2
|
import { MarketClient } from "../../../entrypoints/clients/market-client.js";
|
|
3
|
+
import { activateInternalCapacityAllocationSet, createInternalCapacityAllocationSet } from "../../../market-client/internal-capacity-allocation.js";
|
|
3
4
|
import { runLocalAutonomousStarterAcceptances } from "../../support/acceptance/live-acceptance-starters.js";
|
|
4
5
|
import { configuredLiveAcceptanceValue } from "../../support/acceptance/live-acceptance-values.js";
|
|
5
6
|
import { cleanupCapacityAssignmentProof } from "./live-acceptance-capacity-cleanup.js";
|
|
@@ -179,7 +180,7 @@ async function runCapacityProviderAssignmentProof(input) {
|
|
|
179
180
|
let activeAllocation = effectiveActiveAllocation(Array.isArray(allocations.payload.items) ? allocations.payload.items : []);
|
|
180
181
|
if (!activeAllocation?.id && input.environment === "local" && cleanupProvisionedProvider) {
|
|
181
182
|
const allocationId = `${input.prefix}-allocation`;
|
|
182
|
-
const created = await adminClient
|
|
183
|
+
const created = await createInternalCapacityAllocationSet(adminClient, config.teamId, {
|
|
183
184
|
id: allocationId,
|
|
184
185
|
effectiveFrom: new Date(Date.now() - 1e3).toISOString(),
|
|
185
186
|
effectiveUntil: new Date(Date.now() + 10 * 6e4).toISOString(),
|
|
@@ -193,7 +194,8 @@ async function runCapacityProviderAssignmentProof(input) {
|
|
|
193
194
|
borrowingRules: [],
|
|
194
195
|
metadata
|
|
195
196
|
}, `capacity-acceptance:${input.runId}:allocation-create`);
|
|
196
|
-
activeAllocation = (await
|
|
197
|
+
activeAllocation = (await activateInternalCapacityAllocationSet(
|
|
198
|
+
adminClient,
|
|
197
199
|
config.teamId,
|
|
198
200
|
String(created.payload.id),
|
|
199
201
|
`capacity-acceptance:${input.runId}:allocation-activate`
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
ProviderProtocolClient,
|
|
4
4
|
signCapacityProviderProof
|
|
5
5
|
} from "../../capacity/providers/capacity-provider.js";
|
|
6
|
+
import { createInternalCapacityAllocationSet, supersedeInternalCapacityAllocationSet } from "../../market-client/internal-capacity-allocation.js";
|
|
6
7
|
import { bindLocalCapacityTreeDxRepository, syncLocalAcceptanceAgentClasses } from "../capacity/capacity-core/live-acceptance-capacity-context.js";
|
|
7
8
|
const LIVE_PROVIDER_ASSIGNMENT_BUDGET_SECONDS = 600;
|
|
8
9
|
const TERMINAL_WORKDAY_RUN_STATUSES = /* @__PURE__ */ new Set(["completed", "cancelled", "failed", "degraded"]);
|
|
@@ -192,7 +193,7 @@ async function provisionLocalStarterCapacity(input) {
|
|
|
192
193
|
}, `${key}:grant-create`);
|
|
193
194
|
grantCreated = true;
|
|
194
195
|
await input.adminClient.transitionCapacityGrant(input.runtime.teamId, grantId, "activate", `${key}:grant-activate`);
|
|
195
|
-
const allocation = await input.adminClient
|
|
196
|
+
const allocation = await createInternalCapacityAllocationSet(input.adminClient, input.runtime.teamId, {
|
|
196
197
|
id: allocationId,
|
|
197
198
|
effectiveFrom: new Date(Date.now() - 1e3).toISOString(),
|
|
198
199
|
effectiveUntil: new Date(Date.now() + (durationSeconds + 300) * 1e3).toISOString(),
|
|
@@ -202,7 +203,7 @@ async function provisionLocalStarterCapacity(input) {
|
|
|
202
203
|
metadata: { liveAcceptance: true, runId: input.runId, starter: input.config.starter }
|
|
203
204
|
}, `${key}:allocation-create`);
|
|
204
205
|
const active = (await input.adminClient.capacityAllocationSets(input.runtime.teamId, { limit: 200 })).payload.items.find((entry) => entry && typeof entry === "object" && entry.status === "active");
|
|
205
|
-
await input.adminClient
|
|
206
|
+
await supersedeInternalCapacityAllocationSet(input.adminClient, input.runtime.teamId, String(allocation.payload.id), { expectedActiveAllocationSetId: active?.id ?? null }, `${key}:allocation-supersede`);
|
|
206
207
|
availability = await protocol.refreshAvailabilitySession(sessionId, {
|
|
207
208
|
expectedSequence: availability.payload.sequence,
|
|
208
209
|
environment: "local",
|
|
@@ -372,7 +373,7 @@ async function provisionLocalStarterPortfolioCapacity(input) {
|
|
|
372
373
|
}
|
|
373
374
|
const durationSeconds = Math.max(...input.configs.map(localStarterDurationSeconds));
|
|
374
375
|
const allocationId = `${key}:allocation`;
|
|
375
|
-
const allocation = await input.adminClient
|
|
376
|
+
const allocation = await createInternalCapacityAllocationSet(input.adminClient, input.runtime.teamId, {
|
|
376
377
|
id: allocationId,
|
|
377
378
|
effectiveFrom: new Date(Date.now() - 1e3).toISOString(),
|
|
378
379
|
effectiveUntil: new Date(Date.now() + (durationSeconds + 300) * 1e3).toISOString(),
|
|
@@ -382,7 +383,7 @@ async function provisionLocalStarterPortfolioCapacity(input) {
|
|
|
382
383
|
metadata: { liveAcceptance: true, runId: input.runId, concurrentStarters: true }
|
|
383
384
|
}, `${key}:allocation-create`);
|
|
384
385
|
const active = (await input.adminClient.capacityAllocationSets(input.runtime.teamId, { limit: 200 })).payload.items.find((entry) => entry && typeof entry === "object" && entry.status === "active");
|
|
385
|
-
await input.adminClient
|
|
386
|
+
await supersedeInternalCapacityAllocationSet(input.adminClient, input.runtime.teamId, String(allocation.payload.id), { expectedActiveAllocationSetId: active?.id ?? null }, `${key}:allocation-supersede`);
|
|
386
387
|
availability = await protocol.refreshAvailabilitySession(sessionId, {
|
|
387
388
|
expectedSequence: availability.payload.sequence,
|
|
388
389
|
environment: "local",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ProviderProtocolClient } from "../../capacity/providers/capacity-provider.js";
|
|
2
2
|
import { MarketClient } from "../../entrypoints/clients/market-client.js";
|
|
3
|
+
import { createInternalCapacityAllocationSet, supersedeInternalCapacityAllocationSet } from "../../market-client/internal-capacity-allocation.js";
|
|
3
4
|
import {
|
|
4
5
|
provisionLocalCapacityAcceptanceProvider,
|
|
5
6
|
syncLocalAcceptanceAgentClasses
|
|
@@ -288,7 +289,7 @@ function createProductionAgentLabExecutor(options) {
|
|
|
288
289
|
}, `agent-lab:${input.runId}:grant-create`);
|
|
289
290
|
await client.transitionCapacityGrant(scope.teamId, grantId, "activate", `agent-lab:${input.runId}:grant-activate`);
|
|
290
291
|
const allocationId = `agent-lab:${input.runId}:allocation`;
|
|
291
|
-
allocation = (await client
|
|
292
|
+
allocation = (await createInternalCapacityAllocationSet(client, scope.teamId, {
|
|
292
293
|
id: allocationId,
|
|
293
294
|
effectiveFrom: new Date(Date.now() - 1e3).toISOString(),
|
|
294
295
|
effectiveUntil: new Date(Date.now() + input.config.workdays.reduce((sum, day) => sum + day.durationSeconds, 0) * 1e3 + 6e5).toISOString(),
|
|
@@ -297,7 +298,7 @@ function createProductionAgentLabExecutor(options) {
|
|
|
297
298
|
slices: [{ id: `${allocationId}:market`, scope: "project", targetId: scope.projectId, policy: { minPercent: 0, targetPercent: 100, maxPercent: 100, hardCapPercent: 100 } }],
|
|
298
299
|
metadata: { agentLab: true, runId: input.runId }
|
|
299
300
|
}, `agent-lab:${input.runId}:allocation-create`)).payload;
|
|
300
|
-
await client
|
|
301
|
+
await supersedeInternalCapacityAllocationSet(client, scope.teamId, text(allocation.id), { expectedActiveAllocationSetId: null }, `agent-lab:${input.runId}:allocation-activate`);
|
|
301
302
|
availability = await protocol.refreshAvailabilitySession(sessionId, {
|
|
302
303
|
expectedSequence: availability.payload.sequence,
|
|
303
304
|
environment: "local",
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
} from "../../capacity/providers/capacity-provider.js";
|
|
13
13
|
import { MarketClient } from "../../entrypoints/clients/market-client.js";
|
|
14
14
|
import { MarketClientError } from "../../entrypoints/clients/market-client.js";
|
|
15
|
+
import { createInternalCapacityAllocationSet, supersedeInternalCapacityAllocationSet } from "../../market-client/internal-capacity-allocation.js";
|
|
15
16
|
function object(value) {
|
|
16
17
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
17
18
|
}
|
|
@@ -346,7 +347,7 @@ async function reconcilePolicy(input) {
|
|
|
346
347
|
}
|
|
347
348
|
if (!allocation) {
|
|
348
349
|
const target = 100 / allocationProjectIds.length;
|
|
349
|
-
const created = await input.client
|
|
350
|
+
const created = await createInternalCapacityAllocationSet(input.client, input.teamId, {
|
|
350
351
|
id: allocationId,
|
|
351
352
|
effectiveFrom: new Date(Date.now() - 1e3).toISOString(),
|
|
352
353
|
effectiveUntil: "2100-01-01T00:00:00.000Z",
|
|
@@ -359,7 +360,8 @@ async function reconcilePolicy(input) {
|
|
|
359
360
|
}
|
|
360
361
|
if (string(allocation.status) !== "active") {
|
|
361
362
|
const expectedActiveAllocationSetId = string(activeAllocation?.id);
|
|
362
|
-
await
|
|
363
|
+
await supersedeInternalCapacityAllocationSet(
|
|
364
|
+
input.client,
|
|
363
365
|
input.teamId,
|
|
364
366
|
allocationId,
|
|
365
367
|
{ expectedActiveAllocationSetId },
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const rank = { unchanged: 0, compatible_addition: 1, breaking: 2 };
|
|
2
|
+
function same(left, right) {
|
|
3
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
4
|
+
}
|
|
5
|
+
function compareMcp(baseline, candidate) {
|
|
6
|
+
const findings = [];
|
|
7
|
+
const add = (code, path, message, classification) => findings.push({ code, path, message, classification });
|
|
8
|
+
if (baseline.protocolVersion !== candidate.protocolVersion) add("mcp_protocol_changed", "protocolVersion", "The MCP protocol version changed.", "breaking");
|
|
9
|
+
for (const [name, tool] of Object.entries(baseline.tools)) {
|
|
10
|
+
const next = candidate.tools[name];
|
|
11
|
+
if (!next) {
|
|
12
|
+
add("mcp_tool_removed", `tools.${name}`, "An MCP tool was removed.", "breaking");
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
if (!same(tool.inputSchema, next.inputSchema)) add("mcp_tool_input_changed", `tools.${name}.inputSchema`, "Tool input changed.", "breaking");
|
|
16
|
+
if (!same(tool.outputSchema, next.outputSchema)) add("mcp_tool_output_changed", `tools.${name}.outputSchema`, "Tool output changed.", "breaking");
|
|
17
|
+
if (next.requiredScopes.some((scope) => !tool.requiredScopes.includes(scope))) add("mcp_tool_scope_escalated", `tools.${name}.requiredScopes`, "Tool scope requirements increased.", "breaking");
|
|
18
|
+
if (tool.riskClass !== next.riskClass) add("mcp_tool_risk_changed", `tools.${name}.riskClass`, "Tool risk classification changed.", "breaking");
|
|
19
|
+
}
|
|
20
|
+
for (const name of Object.keys(candidate.tools).filter((name2) => !(name2 in baseline.tools))) add("mcp_tool_added", `tools.${name}`, "An MCP tool was added.", "compatible_addition");
|
|
21
|
+
for (const [uri, resource] of Object.entries(baseline.resources)) {
|
|
22
|
+
const next = candidate.resources[uri];
|
|
23
|
+
if (!next) add("mcp_resource_removed", `resources.${uri}`, "An MCP resource was removed.", "breaking");
|
|
24
|
+
else if (!same(resource, next)) add("mcp_resource_changed", `resources.${uri}`, "An MCP resource contract changed.", "breaking");
|
|
25
|
+
}
|
|
26
|
+
for (const uri of Object.keys(candidate.resources).filter((uri2) => !(uri2 in baseline.resources))) add("mcp_resource_added", `resources.${uri}`, "An MCP resource was added.", "compatible_addition");
|
|
27
|
+
for (const [name, prompt] of Object.entries(baseline.prompts)) {
|
|
28
|
+
const next = candidate.prompts[name];
|
|
29
|
+
if (!next) add("mcp_prompt_removed", `prompts.${name}`, "An MCP prompt was removed.", "breaking");
|
|
30
|
+
else if (!same(prompt, next)) add("mcp_prompt_changed", `prompts.${name}`, "An MCP prompt contract changed.", "breaking");
|
|
31
|
+
}
|
|
32
|
+
for (const name of Object.keys(candidate.prompts).filter((name2) => !(name2 in baseline.prompts))) add("mcp_prompt_added", `prompts.${name}`, "An MCP prompt was added.", "compatible_addition");
|
|
33
|
+
findings.sort((left, right) => `${left.path}:${left.code}`.localeCompare(`${right.path}:${right.code}`));
|
|
34
|
+
return { classification: findings.reduce((value, finding) => rank[finding.classification] > rank[value] ? finding.classification : value, "unchanged"), findings };
|
|
35
|
+
}
|
|
36
|
+
export {
|
|
37
|
+
compareMcp
|
|
38
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { CompatibilityClassification } from '../contracts.js';
|
|
2
|
+
import type { OAuthScope } from '../../operator-contracts/control-plane-operation.js';
|
|
3
|
+
export interface McpNormalizedTool {
|
|
4
|
+
inputSchema: unknown;
|
|
5
|
+
outputSchema: unknown;
|
|
6
|
+
requiredScopes: OAuthScope[];
|
|
7
|
+
riskClass: string;
|
|
8
|
+
}
|
|
9
|
+
export interface McpNormalizedResource {
|
|
10
|
+
uriTemplate: string;
|
|
11
|
+
operationId: string;
|
|
12
|
+
subscribable: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface McpNormalizedPrompt {
|
|
15
|
+
argumentSchema: unknown;
|
|
16
|
+
requiredScopes: OAuthScope[];
|
|
17
|
+
}
|
|
18
|
+
export interface McpContractModel {
|
|
19
|
+
schemaVersion: 1;
|
|
20
|
+
protocolVersion: string;
|
|
21
|
+
tools: Record<string, McpNormalizedTool>;
|
|
22
|
+
resources: Record<string, McpNormalizedResource>;
|
|
23
|
+
prompts: Record<string, McpNormalizedPrompt>;
|
|
24
|
+
}
|
|
25
|
+
export interface McpCompatibilityFinding {
|
|
26
|
+
code: string;
|
|
27
|
+
path: string;
|
|
28
|
+
message: string;
|
|
29
|
+
classification: CompatibilityClassification;
|
|
30
|
+
}
|
|
31
|
+
export interface McpCompatibilityComparison {
|
|
32
|
+
classification: CompatibilityClassification;
|
|
33
|
+
findings: McpCompatibilityFinding[];
|
|
34
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { McpCatalog } from '../../operator-contracts/mcp.js';
|
|
2
|
+
import type { ControlPlaneOperationDescriptor } from '../../operator-contracts/control-plane-operation.js';
|
|
3
|
+
import type { McpContractModel } from './contracts.js';
|
|
4
|
+
export declare function normalizeMcpCatalog(catalog: McpCatalog, operations: readonly ControlPlaneOperationDescriptor[], schemas: Readonly<Record<string, unknown>>): McpContractModel;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { canonicalizeStandardsValue } from "../canonicalize.js";
|
|
2
|
+
function normalizeMcpCatalog(catalog, operations, schemas) {
|
|
3
|
+
const operationById = new Map(operations.map((operation) => [operation.operationId, operation]));
|
|
4
|
+
return {
|
|
5
|
+
schemaVersion: 1,
|
|
6
|
+
protocolVersion: catalog.protocolVersion,
|
|
7
|
+
tools: Object.fromEntries([...catalog.tools].sort((left, right) => left.name.localeCompare(right.name)).map((tool) => {
|
|
8
|
+
const operation = operationById.get(tool.operationId);
|
|
9
|
+
if (!operation) throw new Error(`MCP tool ${tool.name} references unknown operation ${tool.operationId}.`);
|
|
10
|
+
return [tool.name, {
|
|
11
|
+
inputSchema: canonicalizeStandardsValue(schemas[tool.inputSchemaId] ?? {}),
|
|
12
|
+
outputSchema: canonicalizeStandardsValue(schemas[tool.outputSchemaId] ?? {}),
|
|
13
|
+
requiredScopes: [...operation.oauthScopes].sort(),
|
|
14
|
+
riskClass: operation.riskClass
|
|
15
|
+
}];
|
|
16
|
+
})),
|
|
17
|
+
resources: Object.fromEntries([...catalog.resources].sort((left, right) => left.uriTemplate.localeCompare(right.uriTemplate)).map((resource) => [resource.uriTemplate, {
|
|
18
|
+
uriTemplate: resource.uriTemplate,
|
|
19
|
+
operationId: resource.operationId,
|
|
20
|
+
subscribable: resource.subscribable
|
|
21
|
+
}])),
|
|
22
|
+
prompts: Object.fromEntries([...catalog.prompts].sort((left, right) => left.name.localeCompare(right.name)).map((prompt) => [prompt.name, {
|
|
23
|
+
argumentSchema: canonicalizeStandardsValue(schemas[prompt.argumentSchemaId] ?? {}),
|
|
24
|
+
requiredScopes: [...prompt.requiredScopes].sort()
|
|
25
|
+
}]))
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export {
|
|
29
|
+
normalizeMcpCatalog
|
|
30
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@treeseed/sdk",
|
|
3
|
-
|
|
3
|
+
"version": "0.13.0-rc.6",
|
|
4
4
|
"description": "Shared Treeseed SDK for content-backed and D1-backed object models.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -135,6 +135,10 @@
|
|
|
135
135
|
"types": "./dist/standards/openapi/index.d.ts",
|
|
136
136
|
"default": "./dist/standards/openapi/index.js"
|
|
137
137
|
},
|
|
138
|
+
"./standards/mcp": {
|
|
139
|
+
"types": "./dist/standards/mcp/index.d.ts",
|
|
140
|
+
"default": "./dist/standards/mcp/index.js"
|
|
141
|
+
},
|
|
138
142
|
"./operator-contracts": {
|
|
139
143
|
"types": "./dist/operator-contracts/index.d.ts",
|
|
140
144
|
"default": "./dist/operator-contracts/index.js"
|
|
@@ -215,6 +219,10 @@
|
|
|
215
219
|
"types": "./dist/entrypoints/clients/market-client.d.ts",
|
|
216
220
|
"default": "./dist/entrypoints/clients/market-client.js"
|
|
217
221
|
},
|
|
222
|
+
"./control-plane-client": {
|
|
223
|
+
"types": "./dist/entrypoints/clients/control-plane-client.d.ts",
|
|
224
|
+
"default": "./dist/entrypoints/clients/control-plane-client.js"
|
|
225
|
+
},
|
|
218
226
|
"./market-gateway": {
|
|
219
227
|
"types": "./dist/gateway/index.d.ts",
|
|
220
228
|
"default": "./dist/gateway/index.js"
|