fix: restore V3 HTTP observability
Project one typed HttpExecutionObservation per logical V3 execution through a closed composition-root projector: only registered diagnostic context keys and bucketed values reach the sinks, and terminal non-abort failures now emit exactly one api.request.failed telemetry event. Caller cancellation and scope fencing record a diagnostic but never a failure event. routeId becomes a required input at the installed operation-executor boundary so the feature gateway's low-cardinality route identity survives to the sink. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
f7bec8274b
commit
67cc5b6d2c
@@ -10,6 +10,8 @@ import {
|
||||
const installed: InstalledHttpContract<unknown, unknown, unknown> =
|
||||
TEST_LIST_HTTP_CONTRACT;
|
||||
|
||||
const ROUTE_ID = "TEST_ROUTE";
|
||||
|
||||
const scope = Object.freeze({
|
||||
generation: 1,
|
||||
fingerprint: "scope-1",
|
||||
@@ -82,7 +84,7 @@ describe("descriptor-driven HTTP execution lifetime", () => {
|
||||
});
|
||||
|
||||
await expect(
|
||||
executor.execute(installed, { limit: 20 }, { scope }),
|
||||
executor.execute(installed, { limit: 20 }, { routeId: ROUTE_ID, scope }),
|
||||
).resolves.toMatchObject({
|
||||
kind: "RATE_LIMITED",
|
||||
effect: "NOT_APPLICABLE",
|
||||
@@ -119,7 +121,7 @@ describe("descriptor-driven HTTP execution lifetime", () => {
|
||||
executor.execute(
|
||||
createInstalled,
|
||||
{ name: "created" },
|
||||
{ scope, ...(intent === undefined ? {} : { intent }) },
|
||||
{ routeId: ROUTE_ID, scope, ...(intent === undefined ? {} : { intent }) },
|
||||
),
|
||||
).resolves.toMatchObject({
|
||||
kind: "CONTRACT_VIOLATION",
|
||||
@@ -154,7 +156,7 @@ describe("descriptor-driven HTTP execution lifetime", () => {
|
||||
});
|
||||
|
||||
await expect(
|
||||
executor.execute(installed, { limit: 20 }, { scope, intent }),
|
||||
executor.execute(installed, { limit: 20 }, { routeId: ROUTE_ID, scope, intent }),
|
||||
).resolves.toMatchObject({
|
||||
kind: "CONTRACT_VIOLATION",
|
||||
violation: {
|
||||
@@ -173,14 +175,14 @@ describe("descriptor-driven HTTP execution lifetime", () => {
|
||||
label: "query with the canonical reserved header",
|
||||
operation: installed,
|
||||
input: { limit: 20 },
|
||||
context: { scope },
|
||||
context: { routeId: ROUTE_ID, scope },
|
||||
headerName: "Idempotency-Key",
|
||||
},
|
||||
{
|
||||
label: "valid KEYED command with a case-variant reserved header",
|
||||
operation: createInstalled,
|
||||
input: { name: "created" },
|
||||
context: { scope, intent: mutationIntent() },
|
||||
context: { routeId: ROUTE_ID, scope, intent: mutationIntent() },
|
||||
headerName: "iDeMpOtEnCy-KeY",
|
||||
},
|
||||
])(
|
||||
@@ -252,7 +254,7 @@ describe("descriptor-driven HTTP execution lifetime", () => {
|
||||
executor.execute(
|
||||
retryingCreate,
|
||||
{ name: "created" },
|
||||
{ scope, intent: mutationIntent({ idempotencyKey: "logical-key" }) },
|
||||
{ routeId: ROUTE_ID, scope, intent: mutationIntent({ idempotencyKey: "logical-key" }) },
|
||||
),
|
||||
).resolves.toMatchObject({ kind: "SUCCESS" });
|
||||
expect(fetcher).toHaveBeenCalledTimes(2);
|
||||
@@ -284,7 +286,7 @@ describe("descriptor-driven HTTP execution lifetime", () => {
|
||||
executor.execute(
|
||||
installed,
|
||||
{ limit: 20 },
|
||||
{ scope },
|
||||
{ routeId: ROUTE_ID, scope },
|
||||
),
|
||||
).resolves.toMatchObject({ kind: "SUCCESS" });
|
||||
expect(fetcher).toHaveBeenCalledOnce();
|
||||
@@ -317,6 +319,7 @@ describe("descriptor-driven HTTP execution lifetime", () => {
|
||||
createInstalled,
|
||||
{ name: "created" },
|
||||
{
|
||||
routeId: ROUTE_ID,
|
||||
scope,
|
||||
intent: Object.freeze({
|
||||
intentId: "private-intent-id",
|
||||
@@ -353,7 +356,7 @@ describe("descriptor-driven HTTP execution lifetime", () => {
|
||||
fetcher,
|
||||
});
|
||||
|
||||
await expect(executor.execute(installed, input, { scope })).resolves.toMatchObject({
|
||||
await expect(executor.execute(installed, input, { routeId: ROUTE_ID, scope })).resolves.toMatchObject({
|
||||
kind: "SUCCESS",
|
||||
});
|
||||
expect(String((fetcher.mock.calls as unknown[][])[0]?.[0])).toContain(
|
||||
@@ -389,11 +392,11 @@ describe("descriptor-driven HTTP execution lifetime", () => {
|
||||
},
|
||||
} as unknown as typeof installed;
|
||||
|
||||
await expect(executor.execute(throwing, { limit: 20 }, { scope })).resolves.toMatchObject({
|
||||
await expect(executor.execute(throwing, { limit: 20 }, { routeId: ROUTE_ID, scope })).resolves.toMatchObject({
|
||||
kind: "CONTRACT_VIOLATION",
|
||||
effect: "NOT_APPLICABLE",
|
||||
});
|
||||
await expect(executor.execute(malformed, { limit: 20 }, { scope })).resolves.toMatchObject({
|
||||
await expect(executor.execute(malformed, { limit: 20 }, { routeId: ROUTE_ID, scope })).resolves.toMatchObject({
|
||||
kind: "CONTRACT_VIOLATION",
|
||||
effect: "NOT_APPLICABLE",
|
||||
});
|
||||
@@ -416,6 +419,7 @@ describe("descriptor-driven HTTP execution lifetime", () => {
|
||||
createInstalled,
|
||||
{ name: "created" },
|
||||
{
|
||||
routeId: ROUTE_ID,
|
||||
scope,
|
||||
intent: mutationIntent(),
|
||||
},
|
||||
@@ -458,6 +462,7 @@ describe("descriptor-driven HTTP execution lifetime", () => {
|
||||
createInstalled,
|
||||
{ name: "created" },
|
||||
{
|
||||
routeId: ROUTE_ID,
|
||||
scope: fencedScope,
|
||||
intent: mutationIntent({ intentId: "intent-2", idempotencyKey: "key-2" }),
|
||||
},
|
||||
@@ -479,7 +484,7 @@ describe("descriptor-driven HTTP execution lifetime", () => {
|
||||
});
|
||||
|
||||
const result = executor
|
||||
.execute(operation({ deadlineMs: 5 }), { limit: 20 }, { scope })
|
||||
.execute(operation({ deadlineMs: 5 }), { limit: 20 }, { routeId: ROUTE_ID, scope })
|
||||
.then((outcome) => {
|
||||
settled = true;
|
||||
return outcome;
|
||||
@@ -532,7 +537,7 @@ describe("descriptor-driven HTTP execution lifetime", () => {
|
||||
const result = executor.execute(
|
||||
operation({ deadlineMs: 5 }),
|
||||
{ limit: 20 },
|
||||
{ scope },
|
||||
{ routeId: ROUTE_ID, scope },
|
||||
);
|
||||
await vi.advanceTimersByTimeAsync(5);
|
||||
await flushMicrotasks();
|
||||
@@ -548,7 +553,14 @@ describe("descriptor-driven HTTP execution lifetime", () => {
|
||||
expect(observe).toHaveBeenCalledTimes(iterationCount);
|
||||
for (const [observation] of observe.mock.calls) {
|
||||
expect(observation).toEqual(
|
||||
expect.objectContaining({ attempts: 1, certainty: "TIMEOUT" }),
|
||||
expect.objectContaining({
|
||||
attemptCount: 1,
|
||||
errorKind: "TIMEOUT",
|
||||
terminalReason: "TIMEOUT",
|
||||
cancellationOwner: "DEADLINE",
|
||||
routeId: ROUTE_ID,
|
||||
operationId: "TEST_LIST_ENTITIES",
|
||||
}),
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
@@ -582,7 +594,7 @@ describe("descriptor-driven HTTP execution lifetime", () => {
|
||||
});
|
||||
|
||||
const result = executor
|
||||
.execute(installed, { limit: 20 }, { scope, signal: caller.signal })
|
||||
.execute(installed, { limit: 20 }, { routeId: ROUTE_ID, scope, signal: caller.signal })
|
||||
.then((outcome) => {
|
||||
settled = true;
|
||||
return outcome;
|
||||
@@ -617,7 +629,7 @@ describe("descriptor-driven HTTP execution lifetime", () => {
|
||||
executor.execute(
|
||||
operation({ responseBody: "NONE" }),
|
||||
{ limit: 20 },
|
||||
{ scope },
|
||||
{ routeId: ROUTE_ID, scope },
|
||||
),
|
||||
).resolves.toMatchObject({
|
||||
kind: "TRANSPORT_FAILURE",
|
||||
|
||||
Reference in New Issue
Block a user