# CFC Authoring Contract **Status:** Draft contract **Scope:** `packages/api`, `packages/ts-transformers`, `packages/schema-generator` This document specifies the compile-time contract for CFC-aware TypeScript authoring. It is intentionally narrower than the runner CFC specs: the concern here is how authored types and JSX lower into schema metadata and helper calls. ## Source Of Truth This document is normative for the CFC-specific TypeScript authoring surface. Related contracts: - `docs/specs/ts-transformer/cfc_ui_helper_contract.md` - `docs/specs/ts-transformer/ts_transformers_current_behavior_spec.md` ## Goals - Give authors a type-level way to express CFC metadata without hand-writing raw `ifc` JSON for common cases. - Keep lowering deterministic across `toSchema()`, inferred `pattern()` schemas, and explicit `pattern(..., outputSchema)` paths. - Preserve implementation identity where the schema must refer back to a local binding, especially for `WriteAuthorizedBy`. ## Non-Goals - Model every possible CFC construct as TypeScript sugar. - Infer arbitrary runtime expressions into schema metadata. - Treat the transformer as a trust boundary. ## Canonical Surface ### Base Carrier The canonical compiler-facing carrier is: ```ts type Cfc = T & { readonly __ct_cfc__?: Meta; }; ``` `Cfc` must preserve the runtime/schema shape of `T` and only add to the emitted `ifc` metadata. ### Path-Bearing Helpers Projection-like constructs must preserve path identity, not only the projected value type. Canonical helpers: ```ts type Ref = { readonly __ct_ref_root__?: Root; readonly __ct_ref_path__?: Path; }; type PathValue = unknown; type RefValue = unknown; type CanonicalPointer = `/${string}`; ``` ### Supported Alias Set The implementation must keep the supported alias list synchronized across the public API, transformer diagnostics, and schema-generator formatter support. Canonical alias set: - `Cfc` - `Classified` - `Integrity` - `AddIntegrity` - `RequiresIntegrity` - `MaxConfidentiality` - `OpaqueInput` - `WriteAuthorizedBy` - `ExactCopy` - `ProjectionPath` - `ProjectionOf` - `Projection` - `LengthPreservedFrom` - `FilteredFrom` - `SubsetOf` - `PermutationOf` Friendly aliases may expand to those forms, but the formatter contract is keyed to this canonical set. ## Lowering Rules ### `Cfc` - Lower the base schema exactly as if `T` had been authored directly. - Evaluate `Meta` as a type-level object/tuple/literal payload. - Merge the evaluated metadata into `schema.ifc`. - If the base schema already contains `ifc`, the merge is additive/overwriting by key, not replacement of the entire schema object. ### Simple Wrapper Aliases These aliases lower to direct `ifc` keys: - `Confidential` -> `ifc.confidentiality = X` - `Integrity` -> `ifc.integrity = X` - `AddIntegrity` -> `ifc.addIntegrity = X` - `RequiresIntegrity` -> `ifc.requiredIntegrity = X` - `MaxConfidentiality` -> `ifc.maxConfidentiality = X` - `ExactCopy` -> `ifc.exactCopyOf = P` - `LengthPreservedFrom` -> `ifc.collection = { sourceCollection: P, lengthPreserved: true }` - `FilteredFrom` -> `ifc.collection = { filteredFrom: P }` - `SubsetOf` -> `ifc.collection = { subsetOf: P }` - `PermutationOf` -> `ifc.collection = { permutationOf: P }` ### Opaque Inputs Opaque inputs are part of the CFC authoring surface even though they are not ordinary label-transition sugar. The canonical form is: ```ts // Shown at module scope. type OpaqueInput< T, Spec extends true | { schema?: unknown; allowPassThrough?: boolean; } = true, > = Cfc, { opaque: Spec }>; ``` Normative behavior: - `OpaqueRef` supplies the handler-side reference/read restriction shape. - `OpaqueRef` by itself does not emit `ifc.opaque`. - `Cfc, { opaque: Spec }>` emits the base schema of `T` plus `ifc.opaque = Spec`. - `Spec = true` means the field is opaque with default runtime handling. - `Spec = { schema, allowPassThrough? }` lowers that object verbatim into `ifc.opaque`. - Because the emitted schema still describes `T`, opaque inputs remain type-checkable for pass-through and schema validation, but they are not authoring sugar for readable value access. ### Projection Helpers - `ProjectionPath` lowers to `ifc.projection = { from: From, path: Path }`. - `ProjectionOf` lowers to `ifc.projection = { from: "/", path: encode(PathTuple) }`. - `Projection>` lowers identically to `ProjectionOf`. Path tuple encoding rules: - each segment must be a string literal at compile time - `~` escapes to `~0` - `/` escapes to `~1` - `[]` encodes to `/` - otherwise encode as `/${segments.join("/")}` ### `WriteAuthorizedBy` `WriteAuthorizedBy` is special because the emitted schema must refer to a local implementation binding, not a plain JSON value. Normative behavior: 1. The second type argument must be a direct `typeof ...` query. 2. The queried root binding must be declared in the same source file. 3. Supported binding declarations are intentionally narrow: - a local variable initialized from `handler(...)` - a local variable initialized from `module(...)` - a local variable initialized from `requireEventIntegrity(...)` - a local function declaration 4. The transformer must report `cfc-write-authorized-by` if any of the above conditions fail. 5. The schema-generator must preserve the identity through a marker payload, then rehydrate it back into emitted schema AST as ` as any`. One valid marker shape is: ```ts // Shown for illustration only. { __ctWriterIdentityOf: } ``` That marker is an implementation detail, but the implementation still needs an equivalent cross-stage identity channel. ## Pipeline Contract The CFC authoring path is not owned by one transformer. The required stage ordering is: 1. `CfcJsxTransformer` 2. `SchemaInjectionTransformer` 3. `SchemaGeneratorTransformer` More precisely: - `CfcJsxTransformer` rewrites recognized UI helpers and attaches node-local schema hints. - `SchemaInjectionTransformer` seeds `[UI]` member schema hints and constructs `toSchema<...>()` calls that preserve type/identity information. - `SchemaGeneratorTransformer` validates `WriteAuthorizedBy` usage, evaluates CFC type metadata, and emits the final schema AST. Reordering these stages changes behavior and is not allowed without updating this spec. ## Alias Expansion And Type Evaluation The formatter must be able to resolve simple type-alias indirection before it decides whether a type is CFC-aware. Required capabilities: - resolve nested type aliases recursively - substitute type parameters through alias expansion - evaluate literal, tuple, array, and type-literal payloads - preserve the base type when stripping the `Cfc` carrier intersection - preserve wrapper erasure rules for `OpaqueRef` so `OpaqueInput` lowers to the schema shape of `T` rather than inventing a separate opaque runtime value shape If alias expansion cannot resolve back to a supported form, lowering must fall back to ordinary schema generation rather than inventing partial metadata. ## Diagnostics Required diagnostic type: - `cfc-write-authorized-by` Required failure modes: - second type argument is not `typeof ...` - target is imported rather than local - target is not a supported handler/module-style binding ## Current Limits - `WriteAuthorizedBy` only supports in-scope local bindings. - Projection paths must be statically known string tuples. - Metadata payload evaluation is limited to literal-like type syntax; arbitrary conditional or computed type programs are out of scope. - The supported alias set is closed by name. New sugar needs explicit formatter support. - `OpaqueInput` only declares schema-level opacity. The compile-time read restrictions on opaque values still come from the existing `OpaqueRef` type/runtime contract. ## Acceptance Coverage The contract is not fully implemented until these tests pass or equivalent coverage exists: - `packages/ts-transformers/test/cfc-authoring.test.ts` - `packages/schema-generator/test/schema/cfc-type.test.ts` - equivalent coverage for `OpaqueInput` lowering to `ifc.opaque`