fix: break the TechLog CSRF bootstrap cycle and close the review's fix-round-1 items
C1 (Critical): getStudioSession was stamped with the same TECH_LOG_STUDIO_SESSION auth profile as every other Studio operation, and that profile requires the CSRF header it is getStudioSession's own job to issue -- an unconditional cycle that recursed without bound in HTTP mode. Fixed with a credential-free TECH_LOG_STUDIO_BOOTSTRAP auth profile for getStudioSession alone, a synchronous re-entrancy guard in createCsrfTokenProvider as defense in depth, and a throwing stub in place of the prior `let x!: T` assertion. Added a composition-level regression test that wires the real executor, CSRF provider, and credential-attach function together and proves getStudioSession dispatches exactly once while its token reaches both a JSON operation and the multipart upload. Also: invalidate the cached CSRF token on a 401/403 from the upload path (I2), a throwing useStudioAssetGateway() accessor so Task 11 cannot silently compile a null-gateway UI (I3), and the M1-M5 minors from the review (guard a malformed success body, cover the untested error fallbacks, align aborted uploads with the JSON path's non-retryable CANCELLED mapping, derive the credential header name from one source instead of two, and correct the adapter review doc's operation count). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
c9c832c365
commit
2cab4974b7
@@ -9,6 +9,10 @@ import type {
|
||||
import { TECH_LOG_FEATURE_ID } from "../application/tech-log-feature-input.ts";
|
||||
import canonicalSource from "./studio/canonical-source.json" with { type: "json" };
|
||||
import { STUDIO_ERROR_CODES } from "../adapters/http/studio-error-mapping.ts";
|
||||
import {
|
||||
TECH_LOG_STUDIO_BOOTSTRAP_AUTH_PROFILE_ID,
|
||||
TECH_LOG_STUDIO_SESSION_AUTH_PROFILE_ID,
|
||||
} from "../adapters/http/studio-session-credentials.ts";
|
||||
|
||||
function zodValidator<T>(schemaId: string, schema: z.ZodType<T>): RuntimeValidator<T> {
|
||||
return Object.freeze({
|
||||
@@ -75,6 +79,11 @@ function safeOperation(
|
||||
pathTemplate: string,
|
||||
responseByteLimit: number,
|
||||
project: (input: never) => Readonly<{ pathValues: PathValues; queryEntries: QueryEntries }>,
|
||||
// Task 7 fix round 1 (C1). `getStudioSession` issues the CSRF token, so it
|
||||
// is the one operation that must NOT require one — it is the only caller
|
||||
// of `TECH_LOG_STUDIO_BOOTSTRAP`. Every other safe/keyed operation keeps
|
||||
// the default `TECH_LOG_STUDIO_SESSION`.
|
||||
authProfileId: string = TECH_LOG_STUDIO_SESSION_AUTH_PROFILE_ID,
|
||||
): InstalledHttpContract<unknown, unknown, unknown> {
|
||||
return Object.freeze({
|
||||
contract: Object.freeze({
|
||||
@@ -102,7 +111,7 @@ function safeOperation(
|
||||
responseByteLimit,
|
||||
totalDeadlineMs: 10_000,
|
||||
retryBudget: 2 as const,
|
||||
authProfileId: "TECH_LOG_STUDIO_SESSION",
|
||||
authProfileId,
|
||||
diagnosticsOperation: `techLog.studio.${operationId}`,
|
||||
}),
|
||||
}) as InstalledHttpContract<unknown, unknown, unknown>;
|
||||
@@ -151,7 +160,7 @@ function keyedOperation(
|
||||
totalDeadlineMs: 10_000,
|
||||
// §8.3. 발신된 KEYED 명령은 자동 재시도하지 않는다.
|
||||
retryBudget: 0 as const,
|
||||
authProfileId: "TECH_LOG_STUDIO_SESSION",
|
||||
authProfileId: TECH_LOG_STUDIO_SESSION_AUTH_PROFILE_ID,
|
||||
diagnosticsOperation: `techLog.studio.${operationId}`,
|
||||
}),
|
||||
}) as InstalledHttpContract<unknown, unknown, unknown>;
|
||||
@@ -178,6 +187,7 @@ const GET_STUDIO_SESSION = safeOperation(
|
||||
"/api/v1/studio/session",
|
||||
8_192,
|
||||
() => Object.freeze({ pathValues: NO_PATH, queryEntries: NO_QUERY }),
|
||||
TECH_LOG_STUDIO_BOOTSTRAP_AUTH_PROFILE_ID,
|
||||
);
|
||||
|
||||
const GET_STUDIO_DASHBOARD = safeOperation(
|
||||
|
||||
Reference in New Issue
Block a user