Install the REST auth profile registry once at composition and make it the single transport authority for V3. Contract composition now rejects an unregistered authProfileId, so the executor never resolves a profile at runtime. The credential collaborator contributes proof headers only: Fetch credentials come from the resolved profile, transport-owned and forbidden headers are rejected, headers outside the profile's allowed set are rejected, and a missing required header fails closed as AUTH_INTEGRATION_FAILURE with zero fetch calls. The final invariant re-proves credentials mode and the exact header sets. Demo mode satisfies the strict bearer profile with a fixed non-secret marker instead of weakening REFERENCE_EXTERNAL_BEARER. Credential owners now receive the operation lifetime through AuthOperationContext. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
221 lines
6.2 KiB
TypeScript
221 lines
6.2 KiB
TypeScript
import { z } from "zod";
|
|
|
|
import type {
|
|
CommandEffectDescriptor,
|
|
InstalledContractContribution,
|
|
InstalledHttpContract,
|
|
RuntimeValidator,
|
|
} from "../../src/contracts/external-contract-runtime.ts";
|
|
|
|
function zodValidator<T>(
|
|
schemaId: string,
|
|
schema: z.ZodType<T>,
|
|
): RuntimeValidator<T> {
|
|
return Object.freeze({
|
|
schemaId,
|
|
safeParse(value: unknown) {
|
|
const result = schema.safeParse(value);
|
|
if (result.success) {
|
|
return Object.freeze({ success: true as const, data: result.data });
|
|
}
|
|
return Object.freeze({
|
|
success: false as const,
|
|
issues: Object.freeze(
|
|
result.error.issues.map((issue) =>
|
|
Object.freeze({
|
|
path: Object.freeze(
|
|
issue.path.map((segment) =>
|
|
typeof segment === "number" ? segment : String(segment),
|
|
),
|
|
),
|
|
code: String(issue.code),
|
|
}),
|
|
),
|
|
),
|
|
});
|
|
},
|
|
});
|
|
}
|
|
|
|
const entitySchema = z
|
|
.object({
|
|
id: z.string().min(1),
|
|
name: z.string().min(1),
|
|
})
|
|
.strip();
|
|
|
|
const problemSchema = z
|
|
.object({
|
|
type: z.string().min(1),
|
|
title: z.string().min(1),
|
|
status: z.int().min(100).max(599),
|
|
})
|
|
.strip();
|
|
|
|
type TestProblem = z.output<typeof problemSchema>;
|
|
|
|
const problemValidator = zodValidator("TestProblem", problemSchema);
|
|
const readPolicy = Object.freeze({
|
|
policyId: "TEST_READ_V1",
|
|
requestByteLimit: 0,
|
|
responseByteLimit: 32_768,
|
|
totalDeadlineMs: 10_000,
|
|
retryBudget: 2 as const,
|
|
authProfileId: "ANONYMOUS",
|
|
diagnosticsOperation: "test.read",
|
|
});
|
|
|
|
export const TEST_LIST_HTTP_CONTRACT: InstalledHttpContract<
|
|
Readonly<{ limit: number }>,
|
|
readonly z.output<typeof entitySchema>[],
|
|
TestProblem
|
|
> = Object.freeze({
|
|
contract: Object.freeze({
|
|
operationId: "TEST_LIST_ENTITIES",
|
|
method: "GET" as const,
|
|
pathTemplate: "/api/test-entities",
|
|
inputValidator: zodValidator(
|
|
"TestEntityListQuery",
|
|
z
|
|
.object({
|
|
limit: z.coerce.number().int().min(1).max(100).default(20),
|
|
})
|
|
.strict(),
|
|
),
|
|
outputValidator: zodValidator(
|
|
"TestEntityListPayload",
|
|
z.array(entitySchema).max(100),
|
|
),
|
|
problemValidator,
|
|
acceptedStatuses: Object.freeze([200]),
|
|
emptyBodyStatuses: Object.freeze([]),
|
|
retrySemantics: "SAFE" as const,
|
|
requestBody: "NONE" as const,
|
|
responseBody: "REQUIRED_JSON" as const,
|
|
commandRecovery: null,
|
|
commandEffect: null,
|
|
projectRequest(input: Readonly<{ limit: number }>) {
|
|
return Object.freeze({
|
|
pathValues: Object.freeze({}),
|
|
queryEntries: Object.freeze([
|
|
Object.freeze(["limit", String(input.limit)] as const),
|
|
]),
|
|
body: null,
|
|
});
|
|
},
|
|
}),
|
|
frontend: readPolicy,
|
|
});
|
|
|
|
export const TEST_DETAIL_HTTP_CONTRACT: InstalledHttpContract<
|
|
Readonly<{ entityId: string }>,
|
|
z.output<typeof entitySchema>,
|
|
TestProblem
|
|
> = Object.freeze({
|
|
contract: Object.freeze({
|
|
operationId: "TEST_GET_ENTITY",
|
|
method: "GET" as const,
|
|
pathTemplate: "/api/test-entities/{entityId}",
|
|
inputValidator: zodValidator(
|
|
"TestEntityParams",
|
|
z.object({ entityId: z.string().min(1) }).strict(),
|
|
),
|
|
outputValidator: zodValidator("TestEntityPayload", entitySchema),
|
|
problemValidator,
|
|
acceptedStatuses: Object.freeze([200]),
|
|
emptyBodyStatuses: Object.freeze([]),
|
|
retrySemantics: "SAFE" as const,
|
|
requestBody: "NONE" as const,
|
|
responseBody: "REQUIRED_JSON" as const,
|
|
commandRecovery: null,
|
|
commandEffect: null,
|
|
projectRequest(input: Readonly<{ entityId: string }>) {
|
|
return Object.freeze({
|
|
pathValues: Object.freeze({ entityId: input.entityId }),
|
|
queryEntries: Object.freeze([]),
|
|
body: null,
|
|
});
|
|
},
|
|
}),
|
|
frontend: Object.freeze({
|
|
...readPolicy,
|
|
policyId: "TEST_DETAIL_V1",
|
|
diagnosticsOperation: "test.detail",
|
|
}),
|
|
});
|
|
|
|
const commandEffect: CommandEffectDescriptor<TestProblem> = Object.freeze({
|
|
successEffect: "APPLIED_CONFIRMED" as const,
|
|
classifyProblem({
|
|
status,
|
|
}: Readonly<{ status: number; problem: TestProblem }>) {
|
|
return status === 400 ? "NOT_APPLIED" : "MAYBE_APPLIED";
|
|
},
|
|
});
|
|
|
|
export const TEST_CREATE_HTTP_CONTRACT: InstalledHttpContract<
|
|
Readonly<{ name: string }>,
|
|
z.output<typeof entitySchema>,
|
|
TestProblem
|
|
> = Object.freeze({
|
|
contract: Object.freeze({
|
|
operationId: "TEST_CREATE_ENTITY",
|
|
method: "POST" as const,
|
|
pathTemplate: "/api/test-entities",
|
|
inputValidator: zodValidator(
|
|
"TestCreateEntityCommand",
|
|
z.object({ name: z.string().trim().min(1) }).strict(),
|
|
),
|
|
outputValidator: zodValidator("TestEntityPayload", entitySchema),
|
|
problemValidator,
|
|
acceptedStatuses: Object.freeze([201]),
|
|
emptyBodyStatuses: Object.freeze([]),
|
|
retrySemantics: "KEYED" as const,
|
|
requestBody: "JSON" as const,
|
|
responseBody: "REQUIRED_JSON" as const,
|
|
commandRecovery: Object.freeze({
|
|
mode: "IDEMPOTENCY_REPLAY" as const,
|
|
operationIdentityField: "idempotencyKey",
|
|
}),
|
|
commandEffect,
|
|
projectRequest(input: Readonly<{ name: string }>) {
|
|
return Object.freeze({
|
|
pathValues: Object.freeze({}),
|
|
queryEntries: Object.freeze([]),
|
|
body: Object.freeze({ ...input }),
|
|
});
|
|
},
|
|
}),
|
|
frontend: Object.freeze({
|
|
policyId: "TEST_CREATE_V1",
|
|
requestByteLimit: 32_768,
|
|
responseByteLimit: 32_768,
|
|
totalDeadlineMs: 10_000,
|
|
retryBudget: 0 as const,
|
|
authProfileId: "ANONYMOUS",
|
|
diagnosticsOperation: "test.create",
|
|
}),
|
|
});
|
|
|
|
export const TEST_CONTRACT_CONTRIBUTION: InstalledContractContribution =
|
|
Object.freeze({
|
|
contributionId: "test-http-v1",
|
|
featureId: "test-feature",
|
|
source: Object.freeze({
|
|
kind: "EXTERNAL_PACKAGE" as const,
|
|
package: Object.freeze({
|
|
packageId: "@test/contracts",
|
|
version: "1.0.0",
|
|
digest: `sha256:${"a".repeat(64)}`,
|
|
runtimeProtocolVersion: 1 as const,
|
|
sourceRevision: "abcdef1",
|
|
}),
|
|
}),
|
|
http: Object.freeze([
|
|
TEST_LIST_HTTP_CONTRACT,
|
|
TEST_DETAIL_HTTP_CONTRACT,
|
|
TEST_CREATE_HTTP_CONTRACT,
|
|
]) as readonly InstalledHttpContract<unknown, unknown, unknown>[],
|
|
events: Object.freeze([]),
|
|
});
|