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:
DongHyeonka
2026-08-13 22:42:51 +09:00
co-authored by Claude Opus 5
parent f7bec8274b
commit 67cc5b6d2c
9 changed files with 559 additions and 59 deletions
+27 -15
View File
@@ -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",