feat: select the TechLog Studio adapter from runtime configuration

Adds TECH_LOG_STUDIO_SOURCE (MOCK | HTTP, default MOCK) to the V2 runtime
config schema so a build can switch createTechLogFeatureInstalledInput
between the mock and HTTP Studio gateways without a rebuild. V1 documents
predate the key and always normalize to MOCK. The HTTP gateway is
constructed with only { operations } per Task 4's actual signature -
no CSRF provider is wired here; that lands with attachCredentials at a
later composition-root task.

Updates every existing Studio test call site to the new required
createTechLogFeatureInstalledInput(context) signature via a shared
tests/helpers/studio-install-context.ts MOCK fixture, so the whole
existing Studio suite keeps exercising the mock adapter unchanged.
This commit is contained in:
DongHyeonka
2026-08-18 01:57:15 +09:00
parent 7424ed4594
commit 3b641906b8
31 changed files with 169 additions and 47 deletions
+1
View File
@@ -505,6 +505,7 @@ export async function createRuntimeAdapters(
});
const featureInputs = createInstalledFeatureInputs({
contractOperations,
studioSource: config.TECH_LOG_STUDIO_SOURCE,
});
return Object.freeze({
+11
View File
@@ -50,6 +50,12 @@ export type RuntimeConfig = Readonly<{
* feature the build did not install cannot be named into existence here.
*/
FEATURE_OVERRIDES: ProductFeatureOverrideMap;
/**
* §3.5-adjacent runtime switch: which Studio gateway adapter this build
* talks to. A V1 document predates the key and is always normalized to
* MOCK.
*/
TECH_LOG_STUDIO_SOURCE: "MOCK" | "HTTP";
/** Present only while a V1 document is still accepted. */
LEGACY_API_CONTRACT_VERSION?: string;
}>;
@@ -132,6 +138,11 @@ export function validateRuntimeConfig(value: unknown): RuntimeConfigValidation {
FEATURE_OVERRIDES: Object.freeze({
...(isV2 ? (parsed as RuntimeConfigV2).FEATURE_OVERRIDES : {}),
}),
// A V1 document predates the Studio adapter switch; it always normalizes
// to the mock so the backend-absent default holds for legacy documents.
TECH_LOG_STUDIO_SOURCE: isV2
? (parsed as RuntimeConfigV2).TECH_LOG_STUDIO_SOURCE
: "MOCK",
...(isV2
? {}
: {
+3
View File
@@ -49,6 +49,9 @@ export const ENV_REGISTRY = Object.freeze({
CAPABILITY_OVERRIDES: runtime("public", false, null),
// §3.5: likewise for features — subtractive, keyed by installed feature id.
FEATURE_OVERRIDES: runtime("public", false, null),
// TechLog Studio gateway adapter selection. Defaults to MOCK while the
// backend does not exist yet.
TECH_LOG_STUDIO_SOURCE: runtime("public", false, "MOCK"),
// §3.5: build-time narrowing of the product manifest. A feature left out
// here is not imported by any registry and never reaches the bundle.
VITE_PRODUCT_FEATURES: build("compile-time", false, null),
+3
View File
@@ -156,6 +156,9 @@ export const runtimeConfigV2ArtifactSchema = z
CONFIG_SCHEMA_VERSION: z.literal("2.0"),
CAPABILITY_OVERRIDES: capabilityOverrideArtifactSchema,
FEATURE_OVERRIDES: featureOverrideArtifactSchema,
// TechLog Studio adapter selection. Backend is not live yet, so the
// default is the in-memory mock; a document may opt a build into HTTP.
TECH_LOG_STUDIO_SOURCE: z.enum(["MOCK", "HTTP"]).default("MOCK"),
})
.strict()
.superRefine(runtimeConfigArtifactInvariants);
+3 -2
View File
@@ -19,9 +19,10 @@ type InstalledFeatureInputs = Readonly<
>;
export function createInstalledFeatureInputs(
context: Parameters<typeof createReferenceFeatureInstalledInput>[0],
context: Parameters<typeof createReferenceFeatureInstalledInput>[0] &
Readonly<{ studioSource: "MOCK" | "HTTP" }>,
): InstalledFeatureInputs {
const techLogFeature = createTechLogFeatureInstalledInput();
const techLogFeature = createTechLogFeatureInstalledInput(context);
if (!INSTALLED_PRODUCT_FEATURE_IDS.includes(REFERENCE_FEATURE_ID)) {
// A build that narrowed the reference feature out still ships TechLog.
return Object.freeze({
@@ -2,13 +2,36 @@ import {
TECH_LOG_FEATURE_ID,
type TechLogFeatureInput,
} from "../application/tech-log-feature-input.ts";
import {
createHttpStudioGateway,
type StudioOperationExecutor,
} from "./http/http-studio-gateway.ts";
import { createMockStudioGateway } from "./mock/mock-studio-gateway.ts";
import { publicContentQueries } from "./static/public-query.ts";
export function createTechLogFeatureInstalledInput() {
/**
* The HTTP gateway builds no headers of its own — `Idempotency-Key` comes
* from the execution intent and credentials (including CSRF) come from the
* platform's `attachCredentials` collaborator at the composition root. This
* context only has to say which adapter to construct and hand it the
* composed contract executor.
*/
export type TechLogInstallContext = Readonly<{
studioSource: "MOCK" | "HTTP";
contractOperations: StudioOperationExecutor;
}>;
export function createTechLogFeatureInstalledInput(
context: TechLogInstallContext,
) {
const createStudioGateway = () =>
context.studioSource === "MOCK"
? createMockStudioGateway()
: createHttpStudioGateway({ operations: context.contractOperations });
const input: TechLogFeatureInput = Object.freeze({
publicContent: publicContentQueries,
createStudioGateway: () => createMockStudioGateway(),
createStudioGateway,
});
return Object.freeze({