Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 49 additions & 2 deletions src/sections/contract/plan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,13 @@ import type {
GraphqlVariablesOf,
} from "./graphql.js";
import { parseLive } from "./live.js";
import type { EndpointDict, GraphqlDict, SectionContext, SectionMeta } from "./module.js";
import type {
EndpointDict,
GraphqlDict,
SectionContext,
SectionMeta,
ValidatedInput,
} from "./module.js";
import {
call,
callGraphql,
Expand Down Expand Up @@ -358,13 +364,54 @@ export interface PlanContext<
* A plan handler with its context's key brand erased: a shared handler serving several sections is
* compared to each branded per-key signature through this (repo-secrets, repo-variables, setup-section).
*/
export type KeyErasedPlan<P> = P extends (
type KeyErasedPlan<P> = P extends (
ctx: PlanContext<infer E, infer G, infer _K>,
declared: infer D,
) => infer R
? (ctx: PlanContext<E, G>, declared: D) => R
: never;

/**
* One section's plan() under its own key brand, over exactly its own dictionary and declared value (the
* registry's exactness lockstep).
*/
export type KeyedPlan<K extends SectionKey, E extends EndpointDict> = (
ctx: PlanContext<E, GraphqlDict, K>,
declared: ValidatedInput<K>,
) => Promise<Result<SectionPlan<PlannedOp<E>>, SectionFailure>>;

/**
* The plan a factory file writes ONCE over its wide dictionary (every route the union over the sections it
* serves): inside the generic factory the section is unresolved, so the contract's role derivations only
* resolve over this view. The brand on `declared` names the section it is called as.
*/
export type WidePlan<Keys extends SectionKey, Wide extends EndpointDict> = <F extends Keys>(
ctx: PlanContext<Wide>,
declared: ValidatedInput<F>,
) => Promise<Result<SectionPlan<PlannedOp<Wide>>, SectionFailure>>;

type WidePlanAt<F extends SectionKey, Wide extends EndpointDict> = (
ctx: PlanContext<Wide>,
declared: ValidatedInput<F>,
) => Promise<Result<SectionPlan<PlannedOp<Wide>>, SectionFailure>>;

type Invariant<A, B> = [A] extends [B] ? ([B] extends [A] ? true : false) : false;

/**
* The sections whose own plan signature the wide plan is NOT, key brand aside: a factory file pins it to
* never, so a role the wide view carries and a section's dictionary does not (or the reverse) fails to compile
* there instead of losing role checking silently.
*/
export type PlanMisfits<
Keys extends SectionKey,
Wide extends EndpointDict,
Table extends { readonly [F in Keys]: Wide },
> = {
[K in Keys]: Invariant<WidePlanAt<K, Wide>, KeyErasedPlan<KeyedPlan<K, Table[K]>>> extends true
? never
: K;
}[Keys];

/** The run's on-missing-permission input: how a read the token is denied classifies. */
export type OnMissingPermission = "fail" | "warn";

Expand Down
58 changes: 5 additions & 53 deletions src/sections/shared/keyed-values.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
/**
* The scaffolding the repo-scoped secret families (./repo-secrets.ts) and variable families (./repo-variables.ts)
* share: what a section supplies, the undeclared-policy unwrap, the knobbed snapshot wrap, the wide plan every
* family of a kind is served by, and the lockstep proving it is each family's own plan. What a value IS (sealed
* and write-only, or readable and compared) stays in the family file: it decides the routes, the plan scope, and
* the read-back, so those are written once per kind, not once per section. The plan types and the lockstep are
* not keyed-values specific: the setup factory (./setup-section.ts) proves its one plan the same way.
* share: what a section supplies, the undeclared-policy unwrap, and the knobbed snapshot wrap. What a value IS
* (sealed and write-only, or readable and compared) stays in the family file: it decides the routes, the plan
* scope, and the read-back, so those are written once per kind, not once per section. The wide plan a family file
* writes and the lockstep proving it is each family's own plan are the contract's (../contract/plan.ts).
*/

import type { Result } from "neverthrow";
Expand All @@ -14,20 +13,11 @@ import type { SectionFailure } from "../contract/errors.js";
import {
defaultUndeclaredPolicy,
type EndpointDict,
type GraphqlDict,
type SectionMeta,
undeclaredPolicy,
type ValidatedInput,
} from "../contract/module.js";
import type { PatResource } from "../contract/permissions.js";
import type {
KeyErasedPlan,
PlanContext,
PlannedOp,
Read,
SectionPlan,
SnapshotContext,
} from "../contract/plan.js";
import type { Read, SnapshotContext } from "../contract/plan.js";
import { knobbedSnapshot } from "./snapshot-helpers.js";

export interface KeyedValuesFamily<K extends SectionKey> {
Expand All @@ -40,44 +30,6 @@ export interface KeyedValuesFamily<K extends SectionKey> {

export type Declared<Entry> = Entry[] | UndeclaredPolicyList<Entry>;

/** One family's plan() over exactly its own dictionary and declared value (the registry's exactness lockstep). */
export type FamilyPlan<K extends SectionKey, E extends EndpointDict> = (
ctx: PlanContext<E, GraphqlDict, K>,
declared: ValidatedInput<K>,
) => Promise<Result<SectionPlan<PlannedOp<E>>, SectionFailure>>;

/**
* The plan a family file writes ONCE over its wide dictionary (every route the union over the kind's
* segments): inside the generic factory the segment is unresolved, so the contract's role derivations only
* resolve over this view. The brand on `declared` names the family it is called as.
*/
export type WidePlan<Keys extends SectionKey, Wide extends EndpointDict> = <F extends Keys>(
ctx: PlanContext<Wide>,
declared: ValidatedInput<F>,
) => Promise<Result<SectionPlan<PlannedOp<Wide>>, SectionFailure>>;

type WidePlanAt<F extends SectionKey, Wide extends EndpointDict> = (
ctx: PlanContext<Wide>,
declared: ValidatedInput<F>,
) => Promise<Result<SectionPlan<PlannedOp<Wide>>, SectionFailure>>;

type Invariant<A, B> = [A] extends [B] ? ([B] extends [A] ? true : false) : false;

/**
* The sections whose own plan signature the wide plan is NOT, key brand aside: a factory file pins it to
* never, so a role the wide view carries and a section's dictionary does not (or the reverse) fails to compile
* there instead of losing role checking silently.
*/
export type PlanMisfits<
Keys extends SectionKey,
Wide extends EndpointDict,
Table extends { readonly [F in Keys]: Wide },
> = {
[K in Keys]: Invariant<WidePlanAt<K, Wide>, KeyErasedPlan<FamilyPlan<K, Table[K]>>> extends true
? never
: K;
}[Keys];

export function knobbedEntries<Entry>(
meta: SectionMeta<UndeclaredPolicySection>,
declared: Declared<Entry>,
Expand Down
13 changes: 8 additions & 5 deletions src/sections/shared/repo-secrets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,19 @@ import {
type SectionSnapshot,
} from "../contract/module.js";
import type { PatResource } from "../contract/permissions.js";
import type { PlannedOp, SnapshotContext } from "../contract/plan.js";
import type {
KeyedPlan,
PlanMisfits,
PlannedOp,
SnapshotContext,
WidePlan,
} from "../contract/plan.js";
import { DependabotSecretConfig } from "../dependabot_secrets/schema.js";
import {
type Declared,
type FamilyPlan,
type KeyedValuesFamily,
knobbedEntries,
type PlanMisfits,
snapshotOf,
type WidePlan,
} from "./keyed-values.js";
import { knobbed, routed, type sealedSecretConfig } from "./schema-helpers.js";
import {
Expand Down Expand Up @@ -117,7 +120,7 @@ type WideEndpoints = RepoSecretsEndpoints<SecretsSegment>;

/** One family's plan(), indexed by K so the generic factory can assign its one WidePlan to it. */
type RepoSecretsPlan<K extends RepoSecretsKey> = {
[F in RepoSecretsKey]: FamilyPlan<F, SecretsTable[F]>;
[F in RepoSecretsKey]: KeyedPlan<F, SecretsTable[F]>;
}[K];

type _WidePlanIsEveryFamilyPlan = MustBeNever<
Expand Down
13 changes: 8 additions & 5 deletions src/sections/shared/repo-variables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,18 @@ import {
type SectionSnapshot,
} from "../contract/module.js";
import type { PatResource } from "../contract/permissions.js";
import type { PlannedOp, SnapshotContext } from "../contract/plan.js";
import type {
KeyedPlan,
PlanMisfits,
PlannedOp,
SnapshotContext,
WidePlan,
} from "../contract/plan.js";
import {
type Declared,
type FamilyPlan,
type KeyedValuesFamily,
knobbedEntries,
type PlanMisfits,
snapshotOf,
type WidePlan,
} from "./keyed-values.js";
import { knobbed, routed } from "./schema-helpers.js";
import { projectOntoSchema } from "./snapshot-helpers.js";
Expand Down Expand Up @@ -105,7 +108,7 @@ type WideEndpoints = RepoVariablesEndpoints<VariablesSegment>;

/** One family's plan(), indexed by K so the generic factory can assign its one WidePlan to it. */
type RepoVariablesPlan<K extends RepoVariablesKey> = {
[F in RepoVariablesKey]: FamilyPlan<F, VariablesTable[F]>;
[F in RepoVariablesKey]: KeyedPlan<F, VariablesTable[F]>;
}[K];

type _WidePlanIsEveryFamilyPlan = MustBeNever<
Expand Down
6 changes: 4 additions & 2 deletions src/sections/shared/setup-section.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@ import { type GraphqlDict, requirePlainMapping, type SectionSnapshot } from "../
import type { SectionPermission } from "../contract/permissions.js";
import {
hasDrift,
type KeyedPlan,
type PlanMisfits,
type PlannedOp,
type SectionPlan,
type SnapshotContext,
type WidePlan,
} from "../contract/plan.js";
import type { FamilyPlan, PlanMisfits, WidePlan } from "./keyed-values.js";
import { type SetupLanguages, undeclarableLanguages } from "./setup-schema.js";
import { leftOutOfSnapshot, projectOntoSchema } from "./snapshot-helpers.js";

Expand Down Expand Up @@ -76,7 +78,7 @@ type SetupTable = { readonly [F in SetupKey]: SetupEndpoints<F> };

/** One setup's plan(), indexed by K so the factory's one WidePlan can be assigned to it. */
type SetupPlan<K extends SetupKey> = {
[F in SetupKey]: FamilyPlan<F, SetupTable[F]>;
[F in SetupKey]: KeyedPlan<F, SetupTable[F]>;
}[K];

type WideEndpoints = SetupEndpoints<SetupKey>;
Expand Down
Loading