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:
DongHyeonka
2026-08-18 03:07:02 +09:00
co-authored by Claude Opus 5
parent c9c832c365
commit 2cab4974b7
16 changed files with 793 additions and 55 deletions
@@ -43,6 +43,30 @@ export function useStudio(): StudioContextValue {
return context;
}
/**
* Fix round 1 (I3). `StudioContextValue.assetGateway` stays nullable — most
* test harnesses render `StudioProvider` without a `createAssetGateway` prop
* and that must keep compiling — but a nullable field lets Asset UI write
* `if (!assetGateway) return null` and ship a confusingly empty screen with
* the type checker fully satisfied, since "no gateway" and "gateway with an
* empty list" look identical to that check. Asset UI must use this accessor
* instead of reading `useStudio().assetGateway` directly: it throws with a
* clear message the one time this is actually unmet — in the real app,
* `StudioShell` always supplies `createAssetGateway`, so this never throws in
* production.
*/
export function useStudioAssetGateway(): StudioAssetGateway {
const { assetGateway } = useStudio();
if (!assetGateway) {
throw new Error(
"useStudioAssetGateway must be used within a StudioProvider that was " +
"given a createAssetGateway prop. StudioShell (the production path) " +
"always supplies one; a test harness that renders Asset UI must too.",
);
}
return assetGateway;
}
export function useStudioEditorSession() {
const studio = useStudio();
return useMemo(