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
11 changes: 6 additions & 5 deletions packages/spec/scripts/build-react-blocks-contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
//
// Generates the react-tier component contract from packages/spec/src/ui/
// react-blocks.ts: the `data` (config) props are read from each block's SPEC
// zod schema via z.toJSONSchema (single source — no re-authoring); the
// binding/controlled/callback props come from the hand-authored interaction
// overlay. Emits ONE artifact:
// zod schema via projectPublishedJsonSchema (single source — no re-authoring;
// the same projection, refinement override included, that every published
// JSON Schema artifact goes through); the binding/controlled/callback props
// come from the hand-authored interaction overlay. Emits ONE artifact:
// - skills/objectstack-ui/references/react-blocks.md (AI-facing)
//
// It used to emit a second, machine-readable rendering of the same table at
Expand All @@ -20,9 +21,9 @@
process.env.OS_EAGER_SCHEMAS = '1';

import path from 'path';
import { z } from 'zod';
import { REACT_BLOCKS, type ReactInteractionProp } from '../src/ui/react-blocks';
import { createSink } from './lib/generated-output';
import { projectPublishedJsonSchema } from './lib/refinement-projection';

const REPO = path.resolve(__dirname, '../../..');
const OUT_MD = path.join(REPO, 'skills/objectstack-ui/references/react-blocks.md');
Expand Down Expand Up @@ -72,7 +73,7 @@ interface Prop {
function dataProps(schema: any, allow?: string[]): Prop[] {
let js: any;
try {
js = resolveRoot(z.toJSONSchema(schema, { unrepresentable: 'any' } as any));
js = resolveRoot(projectPublishedJsonSchema(schema, { unrepresentable: 'any' }));
} catch {
return [];
}
Expand Down
21 changes: 12 additions & 9 deletions packages/spec/scripts/lib/refinement-projection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ export interface ProjectionOverrideContext {

/**
* ⭐ The ONE call through which `z.toJSONSchema` is reached anywhere
* **`packages/spec` writes a published JSON Schema artifact**. Four producers,
* **`packages/spec` writes a published JSON Schema artifact**. Five producers,
* named because the claim is only worth as much as its enumeration:
*
* 1. `build-schemas.ts` — the generator's three attempts, writing
Expand All @@ -307,11 +307,16 @@ export interface ProjectionOverrideContext {
* `json-schema/openapi.json`. That file ships in the tarball (`files[]`
* carries `json-schema`) and is exported as `./openapi.json`, so it is a
* published projection like any other; it reached this list late, and the
* shape of the gap is the point — see below.
* shape of the gap is the point — see below;
* 5. `build-react-blocks-contract.ts` — the react-tier contract generator,
* rendering its block schemas' projection as markdown prop tables into
* the published skills catalog (`skills/objectstack-ui/references/
* react-blocks.md`). Not a JSON Schema file, but a published description
* of the same schemas, so it projects the way (1) does.
*
* ## ⛔ What is NOT behind it — stated so the next reader need not re-derive it
*
* This helper governs the projection CALL for the four producers above. It is
* This helper governs the projection CALL for the five producers above. It is
* ⛔ not a repo-wide guarantee, and three populations sit deliberately outside
* it. Naming them is the difference between a claim and a slogan; each was
* measured, not assumed:
Expand All @@ -323,12 +328,10 @@ export interface ProjectionOverrideContext {
* question.
* - **Producers outside `packages/spec`'s own artifacts** — the CLI's
* `os generate` writes a JSON Schema of `ObjectStackDefinitionSchema` into
* an author's project, and `build-react-blocks-contract.ts` renders block
* prop tables into the published skills catalog. Both call
* `z.toJSONSchema` directly, and the first was measured DIVERGENT from this
* projection at seven declared sites. They are a separate decision about
* how wide the published-projection guarantee reaches, ⛔ not an oversight
* to be silently swept in here.
* an author's project. It calls `z.toJSONSchema` directly and was measured
* DIVERGENT from this projection at seven declared sites. That is a
* separate decision about how wide the published-projection guarantee
* reaches, ⛔ not an oversight to be silently swept in here.
* - **Runtime derivations** (`packages/metadata-protocol`) project schemas to
* SERVE them, not to publish an artifact; they are governed by their own
* contracts.
Expand Down
26 changes: 12 additions & 14 deletions packages/spec/scripts/published-projection-choke-point.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,14 @@
*
* ## Why an allowance table and not a flat zero
*
* Two files in this tree hold calls that are legitimately direct (two calls
* in all), and each is a different reason rather than one exemption repeated:
* Beside the choke point itself, one file in this tree holds a call that is
* legitimately direct, for a reason of its own rather than an exemption:
*
* - `union-branch-projection.ts`'s `projectsUnderStrictMode` asks zod whether
* a node is representable at all and DISCARDS the result — a yes/no
* question, not a projection;
* - `build-react-blocks-contract.ts` renders a markdown prop table into the
* governed `skills/**` catalog — a published artifact, but not a published
* JSON Schema, and rewriting it is a governed-surface decision of its own.
* question, not a projection.
*
* Each is pinned at its exact count, so a SECOND call in any of those files
* Each row is pinned at its exact count, so a SECOND call in either file
* fails too, and the reason travels with the row.
*
* ## Why the controls are not decoration
Expand Down Expand Up @@ -113,15 +110,16 @@ const DECLARED_DIRECT_CALLS: ReadonlyArray<{ file: string; count: number; why: s
count: 1,
why: 'projectsUnderStrictMode asks zod whether a node is representable and DISCARDS the result; nothing it produces is published',
},
{
file: 'build-react-blocks-contract.ts',
count: 1,
why: 'writes a markdown prop table into the governed skills catalog, not a JSON Schema artifact — measured DIVERGENT from this projection for 1 of its 3 block schemas, filed separately; routing it rewrites a skills/** file and is its own decision',
},
];

/** The producers whose output is published and must reach it through the helper. */
const PUBLISHED_PRODUCERS = ['build-schemas.ts', 'build-openapi.ts'] as const;
/**
* The producers whose output is published and must reach it through the helper.
* `build-react-blocks-contract.ts` renders its projection as markdown prop tables
* into the published skills catalog rather than as a JSON Schema file, and is
* held to the same projection so the two published descriptions of one schema
* cannot disagree.
*/
const PUBLISHED_PRODUCERS = ['build-schemas.ts', 'build-openapi.ts', 'build-react-blocks-contract.ts'] as const;

describe('published projection choke point', () => {
const sources = collectScriptSources();
Expand Down
Loading