From 1a02af6d4ffe0137023025adfaebac8c0b49ad88 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 16 Sep 2026 19:03:07 +0000 Subject: [PATCH 1/4] feat(spec): declare the closed build-progress PHASE vocabulary the agent loop emits during post-apply verification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `data-build-progress` frame has shipped as prose only: `AIToolContext.onProgress` documents the channel and its example carries a `phase`, but no declaration ever said which phases exist. The consumer filled that gap by guessing — objectui's `extractBuildProgress` coerces any value it does not recognise to `structure`, which renders a "still building" spinner, so the 111 seconds a build turn spends being verified after it finished read as the wrong phase rather than an unknown one. Declare the vocabulary where both ends can read it: - `BUILD_PROGRESS_PHASES` / `BuildProgressPhaseSchema` — a CLOSED enum whose members were measured, not designed. `structure`/`data`/`done` are the consumer's own declared union; `verify` is the post-apply window, corroborated here by the `verify_build` tool `service-ai-studio` actually registers. Per-member provenance is recorded in the source. - `BuildProgressFrameSchema` — the frame's floor: a required `phase` plus an optional verification hop counter and tool name. Deliberately loose, because the panel fields the consumer already reads ride the same frame and are its to shape. - `BUILD_PROGRESS_FRAME_TYPE` — the one literal both ends select on. The docblock names producer (the cloud agent loop — not the tool, whose `onProgress` handle dies when it returns) and consumer (the objectui chat panel). Where in the loop the frames are emitted, and what copy each phase gets, stay with their owners. Claude-Session: https://claude.ai/code/session_01KB5PFtxuy1x3dcR5gxudx6 Co-authored-by: Claude --- packages/spec/src/ai/build-progress.test.ts | 182 ++++++++++++++++++++ packages/spec/src/ai/build-progress.zod.ts | 134 ++++++++++++++ packages/spec/src/ai/index.ts | 2 + 3 files changed, 318 insertions(+) create mode 100644 packages/spec/src/ai/build-progress.test.ts create mode 100644 packages/spec/src/ai/build-progress.zod.ts diff --git a/packages/spec/src/ai/build-progress.test.ts b/packages/spec/src/ai/build-progress.test.ts new file mode 100644 index 00000000000..3f48b90477b --- /dev/null +++ b/packages/spec/src/ai/build-progress.test.ts @@ -0,0 +1,182 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Pin for the build-progress PHASE vocabulary (cloud#2172 ruling A). + * + * What is actually at stake here is not "does zod reject a bad string" — it is + * that this vocabulary is CLOSED and that its refusal is LOUD. objectui#7388 + * measured the cost of the alternative: the consumer's reader coerces any + * value it does not recognise to `'structure'`, so a phase nobody declared + * renders as a "still building" spinner for the 111 seconds the build spends + * being verified after it finished. A refusal that merely throws leaves the + * next author guessing; a refusal that names the accepted set tells them. + * + * So the refusal leg asserts the ENVELOPE, not the throw: zod 4 answers an + * out-of-vocabulary enum value with `invalid_value` and carries the full + * accepted set on the issue. ⛔ A bare `expect(...).toThrow()` here would stay + * green against a schema that had degraded to a bare `z.string()` with a + * refinement, which is exactly the degradation worth catching. + * + * The membership leg is the closed-enum pin. Each member was measured against + * a real end of the channel (provenance is recorded per member on + * `BUILD_PROGRESS_PHASES` in the source), so this array moving is a claim that + * someone re-measured — not an edit to wave through. + */ + +import { describe, it, expect } from 'vitest'; + +import { + BUILD_PROGRESS_PHASES, + BUILD_PROGRESS_FRAME_TYPE, + BuildProgressPhaseSchema, + BuildProgressFrameSchema, + type BuildProgressPhase, +} from './build-progress.zod'; + +/** + * The phases objectui's reader discriminates TODAY + * (`packages/plugin-chatbot/src/mapMessages.ts` `extractBuildProgress`, and the + * `ChatBuildProgress['phase']` union in `ChatbotEnhanced.tsx`). Declaring + * `verify` must not disturb the inherited three — this is the control that has + * to keep moving, not a restatement of the vocabulary under test. + */ +const PHASES_THE_CONSUMER_ALREADY_RENDERS = ['structure', 'data', 'done'] as const; + +describe('BUILD_PROGRESS_PHASES (closed membership)', () => { + it('is exactly the measured vocabulary, in lifecycle order', () => { + expect(BUILD_PROGRESS_PHASES).toEqual(['structure', 'data', 'verify', 'done']); + }); + + it('still contains every phase the objectui reader already discriminates', () => { + for (const phase of PHASES_THE_CONSUMER_ALREADY_RENDERS) { + expect(BUILD_PROGRESS_PHASES).toContain(phase); + } + }); + + it('names the post-apply verification phase the card exists to declare', () => { + expect(BUILD_PROGRESS_PHASES).toContain('verify'); + }); +}); + +describe('BuildProgressPhaseSchema', () => { + it('parses every declared phase', () => { + for (const phase of BUILD_PROGRESS_PHASES) { + const result = BuildProgressPhaseSchema.safeParse(phase); + expect(result.success).toBe(true); + expect(result.success && result.data).toBe(phase); + } + }); + + it('REFUSES an undeclared phase, and the refusal names the accepted set', () => { + const result = BuildProgressPhaseSchema.safeParse('rebuilding'); + + expect(result.success).toBe(false); + if (result.success) throw new Error('unreachable: an undeclared phase parsed'); + + const [issue] = result.error.issues; + expect(result.error.issues).toHaveLength(1); + expect(issue.code).toBe('invalid_value'); + // The loud half: the author is told what WAS allowed, not merely that they + // were wrong. Compared against the exported vocabulary so the two cannot + // drift apart silently. + expect((issue as { values?: readonly unknown[] }).values).toEqual([...BUILD_PROGRESS_PHASES]); + }); + + it('REFUSES the neighbouring vocabulary of the sibling blueprint-progress frame', () => { + // `data-blueprint-progress` is a DIFFERENT channel with its own phases + // (`designing` / `done`). Folding its vocabulary in here would be the + // "one enum for two frames" mistake; `designing` must not parse. + expect(BuildProgressPhaseSchema.safeParse('designing').success).toBe(false); + }); + + it('REFUSES a non-string', () => { + expect(BuildProgressPhaseSchema.safeParse(3).success).toBe(false); + expect(BuildProgressPhaseSchema.safeParse(undefined).success).toBe(false); + }); +}); + +describe('BUILD_PROGRESS_FRAME_TYPE', () => { + it('is the literal both ends select on', () => { + // objectui selects parts with `p.type === 'data-build-progress'`; the + // `data-` prefix is what makes it a custom data part at all. + expect(BUILD_PROGRESS_FRAME_TYPE).toBe('data-build-progress'); + expect(BUILD_PROGRESS_FRAME_TYPE.startsWith('data-')).toBe(true); + }); +}); + +describe('BuildProgressFrameSchema', () => { + it('parses a frame carrying only the required phase', () => { + const result = BuildProgressFrameSchema.safeParse({ phase: 'verify' }); + expect(result.success).toBe(true); + expect(result.success && result.data.phase).toBe('verify'); + expect(result.success && result.data.hop).toBeUndefined(); + expect(result.success && result.data.tool).toBeUndefined(); + }); + + it('parses a frame carrying both optional fields', () => { + const result = BuildProgressFrameSchema.safeParse({ + phase: 'verify', + hop: 4, + tool: 'verify_build', + }); + expect(result.success).toBe(true); + expect(result.success && result.data).toMatchObject({ + phase: 'verify', + hop: 4, + tool: 'verify_build', + }); + }); + + it('parses a frame carrying each optional field on its own', () => { + expect(BuildProgressFrameSchema.safeParse({ phase: 'data', hop: 0 }).success).toBe(true); + expect(BuildProgressFrameSchema.safeParse({ phase: 'data', tool: 'create_seed' }).success).toBe(true); + }); + + it('is a FLOOR: the panel fields objectui already reads survive the parse', () => { + // Regression guard for "tighten it to .strict()". Every key below is read + // by `extractBuildProgress` off this same frame today; refusing or + // stripping them would break the shipping consumer. + const shipping = { + phase: 'structure' as const, + appLabel: 'CRM', + items: [{ type: 'object', name: 'contacts' }], + done: 1, + total: 4, + seq: 12, + }; + + const result = BuildProgressFrameSchema.safeParse(shipping); + expect(result.success).toBe(true); + expect(result.success && result.data).toMatchObject(shipping); + }); + + it('REFUSES an undeclared phase, locating it at `phase`', () => { + const result = BuildProgressFrameSchema.safeParse({ phase: 'rebuilding', hop: 1 }); + + expect(result.success).toBe(false); + if (result.success) throw new Error('unreachable: a frame with an undeclared phase parsed'); + + const [issue] = result.error.issues; + expect(issue.code).toBe('invalid_value'); + expect(issue.path).toEqual(['phase']); + }); + + it('REFUSES a frame with no phase at all', () => { + const result = BuildProgressFrameSchema.safeParse({ hop: 2, tool: 'verify_build' }); + expect(result.success).toBe(false); + expect(result.success === false && result.error.issues[0].path).toEqual(['phase']); + }); + + it('REFUSES a malformed hop and an empty tool name', () => { + expect(BuildProgressFrameSchema.safeParse({ phase: 'verify', hop: -1 }).success).toBe(false); + expect(BuildProgressFrameSchema.safeParse({ phase: 'verify', hop: 1.5 }).success).toBe(false); + expect(BuildProgressFrameSchema.safeParse({ phase: 'verify', tool: '' }).success).toBe(false); + }); +}); + +describe('BuildProgressPhase (type)', () => { + it('admits every declared member and is assignable from the array', () => { + const everyPhase: BuildProgressPhase[] = [...BUILD_PROGRESS_PHASES]; + expect(everyPhase).toHaveLength(BUILD_PROGRESS_PHASES.length); + }); +}); diff --git a/packages/spec/src/ai/build-progress.zod.ts b/packages/spec/src/ai/build-progress.zod.ts new file mode 100644 index 00000000000..890eb437397 --- /dev/null +++ b/packages/spec/src/ai/build-progress.zod.ts @@ -0,0 +1,134 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +import { z } from 'zod'; +import { lazySchema } from '../shared/lazy-schema'; + +/** + * Build-progress PHASE vocabulary for the `data-build-progress` stream frame + * (cloud#2172 ruling A). + * + * ## Producer + * + * The cloud AI-studio **agent loop** — deliberately not the tool it just ran. + * A build turn applies its change through `apply_blueprint` / `apply_edit` and + * then keeps working: the loop spends a further POST-APPLY VERIFICATION window + * re-reading and re-seeding what it wrote (measured on cloud#1838: 111 seconds + * and 9 tool calls *after* `apply_blueprint` returned, one of them the + * registered `verify_build` tool — see `PLATFORM_TOOLS_BY_PACKAGE` in + * `../system/constants/platform-tool-names`). A tool's own `ctx.onProgress` + * handle dies when the tool returns, so a frame emitted in that window can only + * come from the loop. WHERE in the loop it is emitted is cloud#2172's decision, + * not this module's; what such a frame may SAY is declared here. + * + * ## Consumer + * + * The objectui chat panel — `extractBuildProgress` in + * `packages/plugin-chatbot/src/mapMessages.ts`, which lifts the reconciled part + * onto `ChatBuildProgress` for the build panel in `ChatbotEnhanced.tsx`. That + * reader recognises a fixed set and coerces everything else to `'structure'`, + * which renders a "still building" spinner. So an UNDECLARED phase is not + * merely unlabelled — it reads as the wrong phase, and the user watches a + * build that finished two minutes ago still claim to be building + * (objectui#7388). The panel's per-phase COPY is objectui's to choose; this + * module fixes only the set of values it must be able to tell apart. + * + * ## Channel + * + * Unchanged: the `data-`-prefixed custom part described on + * `AIToolContext.onProgress` in `../contracts/ai-service`, reconciled in place + * under a stable part id. This module adds the vocabulary that prose has always + * assumed and never declared; it moves no transport and renames nothing. + */ + +/** + * The closed phase vocabulary, in emission-lifecycle order. + * + * Membership was MEASURED against the two ends of the channel, not designed — + * every member below is one a real producer emits or a real consumer already + * discriminates: + * + * - `structure`, `data`, `done` — the consumer's own declared union, + * `ChatBuildProgress['phase']` at `packages/plugin-chatbot/src/ChatbotEnhanced.tsx:163` + * in objectui, and the set its reader discriminates at + * `packages/plugin-chatbot/src/mapMessages.ts:744` + * (`d.phase === 'data' || d.phase === 'done' ? d.phase : 'structure'`). + * `structure` doubles as that reader's coercion default. + * - `verify` — the post-apply verification window objectui#7388 asks the panel + * to be able to name, and the reason cloud#2172 ruled this vocabulary into + * the spec. Corroborated in this repo by the `verify_build` tool that + * `service-ai-studio` actually registers + * (`../system/constants/platform-tool-names`). + * + * Order is the order a build turn passes through these states; it is NOT a + * scale. ⛔ Consumers compare phases by VALUE — never by index, and never by + * assuming every phase occurs (a turn that applies no seed data never reports + * `data`, and `apply_edit` turns need not report `structure`). + * + * Exported as an array as well as a schema so a consumer can build an + * exhaustive per-phase label map without forcing the lazy schema to + * construct just to read `.options`. + */ +export const BUILD_PROGRESS_PHASES = ['structure', 'data', 'verify', 'done'] as const; + +/** + * The phase a `data-build-progress` frame is reporting. + * + * Closed on purpose: an unrecognised value is REFUSED here, loudly, at the + * seam that can still say which value was wrong. The alternative is what + * objectui#7388 measured — a silent coercion to a neighbouring phase, which + * costs the user a correct-looking label describing something that already + * finished. + */ +export const BuildProgressPhaseSchema = lazySchema(() => z.enum(BUILD_PROGRESS_PHASES)); + +/** + * The stream part name this vocabulary rides, as a Vercel UI-message-stream + * custom data-part name. Emitters pass it as `ctx.onProgress({ type, id, data })` + * and the objectui reader selects parts by exactly this string, so it is the + * one literal both ends must agree on. + */ +export const BUILD_PROGRESS_FRAME_TYPE = 'data-build-progress'; + +/** + * The `data` payload of a `data-build-progress` frame — a FLOOR, not a + * ceiling. + * + * Declared minimal on purpose, and deliberately **not** strict. The same frame + * already carries the objectui build panel's own presentation fields + * (`appLabel`, `items`, `done`, `total`, `seq` — read in `extractBuildProgress`), + * which are the consumer's to shape; a strict schema here would refuse every + * frame shipping today and would claim ownership of fields this package does + * not define. What this schema asserts is the part any consumer may rely on: + * a frame says which phase it is in, and may say which verification hop it is + * on and which tool that hop is running. + */ +export const BuildProgressFrameSchema = lazySchema(() => z.looseObject({ + /** Which phase of the build turn this frame reports. */ + phase: BuildProgressPhaseSchema.describe('Build-turn phase this frame reports'), + /** + * Which hop of the post-apply verification loop this frame is on — the + * counter behind the "9 tool calls" cloud#1838 measured, so the panel can + * show motion through a window that is otherwise a single flat phase. + * + * Typed as a non-negative integer rather than pinned to a base: whether the + * loop counts its first hop as 0 or 1 is part of the emitter's placement, + * which cloud#2172 owns and this card does not decide. ⛔ Consumers render it + * as progress, never as an index into anything here. + */ + hop: z.number().int().nonnegative().optional().describe('Post-apply verification hop this frame reports'), + /** + * Name of the tool the current hop is running, e.g. `verify_build`. + * + * A free string, not a closed set: the runtime's executable tool set is + * registered at boot and legitimately includes plugin-contributed names that + * `PLATFORM_PROVIDED_TOOL_NAMES` cannot know about + * (`../system/constants/platform-tool-names`). Consumers treat it as a label. + */ + tool: z.string().min(1).optional().describe('Name of the tool the current hop is running'), +})); + +/** A phase from the closed {@link BUILD_PROGRESS_PHASES} vocabulary. */ +export type BuildProgressPhase = z.infer; + +/** The `data` payload of a `data-build-progress` frame. */ +export type BuildProgressFrame = z.input; diff --git a/packages/spec/src/ai/index.ts b/packages/spec/src/ai/index.ts index 55a878bacd2..12f67b2da75 100644 --- a/packages/spec/src/ai/index.ts +++ b/packages/spec/src/ai/index.ts @@ -19,6 +19,7 @@ * - Embedding — embedding model + vector store references * - Usage — token accounting + per-call cost * - MCP — references and bindings to external MCP servers + * - Build Progress — phase vocabulary for the agent loop's `data-build-progress` frames */ export * from './agent.zod'; @@ -35,6 +36,7 @@ export * from './mcp.zod'; export * from './knowledge-source.zod'; export * from './knowledge-document.zod'; export * from './solution-blueprint.zod'; +export * from './build-progress.zod'; // [#12414] entry-nameability: these factories' return types expand to mention // `/data`'s `FilterCondition` and `/automation`'s `StateNodeConfig` — both From fbae68b9b0ae1535e1b048be3955323512208467 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 16 Sep 2026 19:10:17 +0000 Subject: [PATCH 2/4] chore(spec): regenerate the artifacts the new build-progress exports move, and declare the changeset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `check:generated` proved exactly five stale: api-surface, export-origins, declaration-map, the rendered reference pages, and the unknown-key strictness ledger. Regenerated only those. api-surface reads 6 added / 0 removed on `./ai` — a purely additive published surface, which is what the `minor` and the `Clause-②: yes (widening)` declaration record. Claude-Session: https://claude.ai/code/session_01KB5PFtxuy1x3dcR5gxudx6 Co-authored-by: Claude --- .changeset/build-progress-phase-vocabulary.md | 31 +++++++ content/docs/references/ai/build-progress.mdx | 83 +++++++++++++++++++ content/docs/references/ai/index.mdx | 1 + content/docs/references/ai/meta.json | 4 +- content/docs/references/index.mdx | 9 +- ...07-unknown-key-strictness-ledger.counts.md | 2 +- packages/spec/api-surface/ai.json | 6 ++ packages/spec/authorable-surface/ai.json | 3 + packages/spec/declaration-map/ai.json | 4 + packages/spec/export-origins/ai.json | 6 ++ packages/spec/json-schema.manifest/ai.json | 2 + 11 files changed, 145 insertions(+), 6 deletions(-) create mode 100644 .changeset/build-progress-phase-vocabulary.md create mode 100644 content/docs/references/ai/build-progress.mdx diff --git a/.changeset/build-progress-phase-vocabulary.md b/.changeset/build-progress-phase-vocabulary.md new file mode 100644 index 00000000000..8f98826549d --- /dev/null +++ b/.changeset/build-progress-phase-vocabulary.md @@ -0,0 +1,31 @@ +--- +'@objectstack/spec': minor +--- + +**Declare the build-progress PHASE vocabulary on `@objectstack/spec/ai`.** + +The `data-build-progress` stream frame has shipped as prose only: `AIToolContext.onProgress` +documents the channel and its example carries a `phase`, but nothing ever declared which +phases exist. Consumers filled that gap by guessing, and a guess here is not merely +unlabelled — the objectui chat panel coerces any value it does not recognise to `structure`, +which renders a "still building" spinner, so a build turn that has finished and moved on to +verifying itself keeps claiming to be building. + +New exports (additive; nothing removed or renamed): + +- `BUILD_PROGRESS_PHASES` / `BuildProgressPhaseSchema` / `BuildProgressPhase` — the CLOSED + phase vocabulary: `structure`, `data`, `verify`, `done`, in lifecycle order. An + out-of-vocabulary value is refused, and the refusal names the accepted set. +- `BuildProgressFrameSchema` / `BuildProgressFrame` — the frame's FLOOR: a required `phase` + plus an optional `hop` (which post-apply verification hop) and `tool` (the tool that hop is + running). Deliberately loose, not strict: the presentation fields the chat panel already + reads ride the same frame and belong to it, so a strict schema here would refuse every + frame shipping today. +- `BUILD_PROGRESS_FRAME_TYPE` — `'data-build-progress'`, the one literal both ends select on. + +Producers emit these frames from the agent loop rather than from the applying tool: a tool's +`ctx.onProgress` handle dies when the tool returns, and the verification window opens after +it does. Consumers should compare phases by value and treat every phase as optional — a turn +that seeds no sample data never reports `data`. + +Clause-②: yes (widening) diff --git a/content/docs/references/ai/build-progress.mdx b/content/docs/references/ai/build-progress.mdx new file mode 100644 index 00000000000..039020e025b --- /dev/null +++ b/content/docs/references/ai/build-progress.mdx @@ -0,0 +1,83 @@ +--- +title: Build Progress +description: Build Progress protocol schemas +--- + +{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} + +Build-progress PHASE vocabulary for the `data-build-progress` stream frame +(cloud#2172 ruling A). + +## Producer + +The cloud AI-studio **agent loop** — deliberately not the tool it just ran. +A build turn applies its change through `apply_blueprint` / `apply_edit` and +then keeps working: the loop spends a further POST-APPLY VERIFICATION window +re-reading and re-seeding what it wrote (measured on cloud#1838: 111 seconds +and 9 tool calls *after* `apply_blueprint` returned, one of them the +registered `verify_build` tool — see `PLATFORM_TOOLS_BY_PACKAGE` in +`../system/constants/platform-tool-names`). A tool's own `ctx.onProgress` +handle dies when the tool returns, so a frame emitted in that window can only +come from the loop. WHERE in the loop it is emitted is cloud#2172's decision, +not this module's; what such a frame may SAY is declared here. + +## Consumer + +The objectui chat panel — `extractBuildProgress` in +`packages/plugin-chatbot/src/mapMessages.ts`, which lifts the reconciled part +onto `ChatBuildProgress` for the build panel in `ChatbotEnhanced.tsx`. That +reader recognises a fixed set and coerces everything else to `'structure'`, +which renders a "still building" spinner. So an UNDECLARED phase is not +merely unlabelled — it reads as the wrong phase, and the user watches a +build that finished two minutes ago still claim to be building +(objectui#7388). The panel's per-phase COPY is objectui's to choose; this +module fixes only the set of values it must be able to tell apart. + +## Channel + +Unchanged: the `data-`-prefixed custom part described on +`AIToolContext.onProgress` in `../contracts/ai-service`, reconciled in place +under a stable part id. This module adds the vocabulary that prose has always +assumed and never declared; it moves no transport and renames nothing. + + +**Source:** `packages/spec/src/ai/build-progress.zod.ts` + + +## TypeScript Usage + +```typescript +import { BuildProgressFrameSchema, BuildProgressPhaseSchema } from '@objectstack/spec/ai'; +import type { BuildProgressFrame, BuildProgressPhase } from '@objectstack/spec/ai'; + +// Validate data +const result = BuildProgressFrameSchema.parse(data); +``` + +--- + +## BuildProgressFrame + +### Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **phase** | `Enum<'structure' \| 'data' \| 'verify' \| 'done'>` | ✅ | Build-turn phase this frame reports | +| **hop** | `integer` | optional | Post-apply verification hop this frame reports | +| **tool** | `string` | optional | Name of the tool the current hop is running | + + +--- + +## BuildProgressPhase + +### Allowed Values + +* `structure` +* `data` +* `verify` +* `done` + + +--- + diff --git a/content/docs/references/ai/index.mdx b/content/docs/references/ai/index.mdx index a0d0157560d..8fb0c1b7041 100644 --- a/content/docs/references/ai/index.mdx +++ b/content/docs/references/ai/index.mdx @@ -9,6 +9,7 @@ This section contains all protocol schemas for the ai layer of ObjectStack. + diff --git a/content/docs/references/ai/meta.json b/content/docs/references/ai/meta.json index 46db5a19698..25ab80c64e5 100644 --- a/content/docs/references/ai/meta.json +++ b/content/docs/references/ai/meta.json @@ -14,6 +14,8 @@ "---Models & Runtime---", "conversation", "model-registry", - "usage" + "usage", + "---More---", + "build-progress" ] } \ No newline at end of file diff --git a/content/docs/references/index.mdx b/content/docs/references/index.mdx index b401bd9b17f..e913a27704a 100644 --- a/content/docs/references/index.mdx +++ b/content/docs/references/index.mdx @@ -1,6 +1,6 @@ --- title: Protocol Reference -description: Every schema published by @objectstack/spec — 1525 schemas across 14 protocol modules +description: Every schema published by @objectstack/spec — 1527 schemas across 14 protocol modules --- {/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} @@ -19,7 +19,7 @@ counts are sums of the rows they head. Regenerate with | Module | Pages | Schemas | Description | | :--- | ---: | ---: | :--- | -| [AI Protocol](/docs/references/ai) | 11 | 66 | Agents, tools, skills, RAG and knowledge sources, model registry, conversations. | +| [AI Protocol](/docs/references/ai) | 12 | 68 | Agents, tools, skills, RAG and knowledge sources, model registry, conversations. | | [API Protocol](/docs/references/api) | 31 | 440 | REST contracts, endpoints, routing, realtime, batch, discovery. | | [Automation Protocol](/docs/references/automation) | 14 | 74 | Flows and their nodes, approvals, ETL pipelines, webhooks, state machines, execution records. | | [Data Protocol](/docs/references/data) | 29 | 173 | Objects, fields, queries, filters, datasources and drivers — the ObjectQL layer. | @@ -33,19 +33,20 @@ counts are sums of the rows they head. Regenerate with | [Studio Protocol](/docs/references/studio) | 3 | 35 | Studio designer metadata — the authoring surfaces for the protocols above. | | [System Protocol](/docs/references/system) | 33 | 272 | The runtime environment — logging, jobs, cache, metrics, notifications, i18n and compliance. | | [UI Protocol](/docs/references/ui) | 16 | 156 | Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI layer. | -| **Total** | **193** | **1525** | 14 protocol modules | +| **Total** | **194** | **1527** | 14 protocol modules | --- ## AI Protocol -**Source:** `packages/spec/src/ai/` · **Import:** `@objectstack/spec/ai` · **11 pages, 66 schemas** +**Source:** `packages/spec/src/ai/` · **Import:** `@objectstack/spec/ai` · **12 pages, 68 schemas** Agents, tools, skills, RAG and knowledge sources, model registry, conversations. | File | Schemas | | :--- | :--- | | [`agent.zod.ts`](/docs/references/ai/agent) | `AIModelConfig`, `Agent`, `StructuredOutputConfig`, `StructuredOutputFormat`, `TransformPipelineStep` | +| [`build-progress.zod.ts`](/docs/references/ai/build-progress) | `BuildProgressFrame`, `BuildProgressPhase` | | [`conversation.zod.ts`](/docs/references/ai/conversation) | `CodeContent`, `ConversationAnalytics`, `ConversationContext`, `ConversationMessage`, `ConversationSession`, `ConversationSummary`, `FileContent`, `FunctionCall`, `ImageContent`, `MessageContent`, `MessageContentType`, `MessagePruningEvent`, `MessageRole`, `TextContent`, `TokenBudgetConfig`, `TokenBudgetStrategy`, `TokenUsageStats`, `ToolCall` | | [`embedding.zod.ts`](/docs/references/ai/embedding) | `EmbeddingModel`, `VectorStore`, `VectorStoreProvider` | | [`knowledge-document.zod.ts`](/docs/references/ai/knowledge-document) | `KnowledgeChunk`, `KnowledgeDocument`, `KnowledgeHit` | diff --git a/docs/audits/2026-07-unknown-key-strictness-ledger.counts.md b/docs/audits/2026-07-unknown-key-strictness-ledger.counts.md index 10b21e9c004..f81ac8b9742 100644 --- a/docs/audits/2026-07-unknown-key-strictness-ledger.counts.md +++ b/docs/audits/2026-07-unknown-key-strictness-ledger.counts.md @@ -256,7 +256,7 @@ directory rather than per file. | Dir | Sites | |---|---| -| `ai/` | 77 | +| `ai/` | 78 | | `api/` | 451 | | `identity/` | 32 | | `integration/` | 8 | diff --git a/packages/spec/api-surface/ai.json b/packages/spec/api-surface/ai.json index 9d7c970b3dc..453b8c8a442 100644 --- a/packages/spec/api-surface/ai.json +++ b/packages/spec/api-surface/ai.json @@ -8,6 +8,8 @@ "Agent (type)", "AgentParsed (type)", "AgentSchema (const)", + "BUILD_PROGRESS_FRAME_TYPE (const)", + "BUILD_PROGRESS_PHASES (const)", "BlueprintApp (type)", "BlueprintAppParsed (type)", "BlueprintAppSchema (const)", @@ -31,6 +33,10 @@ "BlueprintViewSchema (const)", "BlueprintWidgetCondition (type)", "BlueprintWidgetConditionSchema (const)", + "BuildProgressFrame (type)", + "BuildProgressFrameSchema (const)", + "BuildProgressPhase (type)", + "BuildProgressPhaseSchema (const)", "CodeContentSchema (const)", "ConversationAnalytics (type)", "ConversationAnalyticsParsed (type)", diff --git a/packages/spec/authorable-surface/ai.json b/packages/spec/authorable-surface/ai.json index a2f7ca17a4f..35c3ebc34b8 100644 --- a/packages/spec/authorable-surface/ai.json +++ b/packages/spec/authorable-surface/ai.json @@ -84,6 +84,9 @@ "ai/BlueprintWidgetCondition:field", "ai/BlueprintWidgetCondition:op", "ai/BlueprintWidgetCondition:value", + "ai/BuildProgressFrame:hop", + "ai/BuildProgressFrame:phase", + "ai/BuildProgressFrame:tool", "ai/CodeContent:language", "ai/CodeContent:metadata", "ai/CodeContent:text", diff --git a/packages/spec/declaration-map/ai.json b/packages/spec/declaration-map/ai.json index 4f6d260852d..f047617c2f7 100644 --- a/packages/spec/declaration-map/ai.json +++ b/packages/spec/declaration-map/ai.json @@ -27,6 +27,10 @@ "BlueprintViewSchema": "ai/BlueprintView", "BlueprintWidgetCondition": "ai/BlueprintWidgetCondition", "BlueprintWidgetConditionSchema": "ai/BlueprintWidgetCondition", + "BuildProgressFrame": "ai/BuildProgressFrame", + "BuildProgressFrameSchema": "ai/BuildProgressFrame", + "BuildProgressPhase": "ai/BuildProgressPhase", + "BuildProgressPhaseSchema": "ai/BuildProgressPhase", "CodeContentSchema": "ai/CodeContent", "ConversationAnalytics": "ai/ConversationAnalytics", "ConversationAnalyticsSchema": "ai/ConversationAnalytics", diff --git a/packages/spec/export-origins/ai.json b/packages/spec/export-origins/ai.json index 1cf29b92e70..13b6fab762a 100644 --- a/packages/spec/export-origins/ai.json +++ b/packages/spec/export-origins/ai.json @@ -8,6 +8,8 @@ "Agent": "src/ai/agent.zod.ts#Agent (type)", "AgentParsed": "src/ai/agent.zod.ts#AgentParsed (type)", "AgentSchema": "src/ai/agent.zod.ts#AgentSchema (const)", + "BUILD_PROGRESS_FRAME_TYPE": "src/ai/build-progress.zod.ts#BUILD_PROGRESS_FRAME_TYPE (const)", + "BUILD_PROGRESS_PHASES": "src/ai/build-progress.zod.ts#BUILD_PROGRESS_PHASES (const)", "BlueprintApp": "src/ai/solution-blueprint.zod.ts#BlueprintApp (type)", "BlueprintAppParsed": "src/ai/solution-blueprint.zod.ts#BlueprintAppParsed (type)", "BlueprintAppSchema": "src/ai/solution-blueprint.zod.ts#BlueprintAppSchema (const)", @@ -31,6 +33,10 @@ "BlueprintViewSchema": "src/ai/solution-blueprint.zod.ts#BlueprintViewSchema (const)", "BlueprintWidgetCondition": "src/ai/solution-blueprint.zod.ts#BlueprintWidgetCondition (type)", "BlueprintWidgetConditionSchema": "src/ai/solution-blueprint.zod.ts#BlueprintWidgetConditionSchema (const)", + "BuildProgressFrame": "src/ai/build-progress.zod.ts#BuildProgressFrame (type)", + "BuildProgressFrameSchema": "src/ai/build-progress.zod.ts#BuildProgressFrameSchema (const)", + "BuildProgressPhase": "src/ai/build-progress.zod.ts#BuildProgressPhase (type)", + "BuildProgressPhaseSchema": "src/ai/build-progress.zod.ts#BuildProgressPhaseSchema (const)", "CodeContentSchema": "src/ai/conversation.zod.ts#CodeContentSchema (const)", "ConversationAnalytics": "src/ai/conversation.zod.ts#ConversationAnalytics (type)", "ConversationAnalyticsParsed": "src/ai/conversation.zod.ts#ConversationAnalyticsParsed (type)", diff --git a/packages/spec/json-schema.manifest/ai.json b/packages/spec/json-schema.manifest/ai.json index 1ab2704cdb4..e76b41a930e 100644 --- a/packages/spec/json-schema.manifest/ai.json +++ b/packages/spec/json-schema.manifest/ai.json @@ -15,6 +15,8 @@ "ai/BlueprintSummaryOperations", "ai/BlueprintView", "ai/BlueprintWidgetCondition", + "ai/BuildProgressFrame", + "ai/BuildProgressPhase", "ai/CodeContent", "ai/ConversationAnalytics", "ai/ConversationContext", From 421afed0df3bcf0a3a9c38c9f12b210d234892de Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 16 Sep 2026 19:48:45 +0000 Subject: [PATCH 3/4] fix(spec): put the build-progress aliases on the ADR-0122 convention and correct the counts a new schema moves MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three gate-named corrections, all mechanical consequences of the new export: - ADR-0122 (`check:spec-parsed-alias`): the bare alias is the AUTHOR state, so `BuildProgressPhase` becomes `z.input`. Both schemas are isomorphic — no default, no transform anywhere in either tree — so neither gets a redundant `XParsed` synonym; they are pinned in `type-alias-convention.pin.test.ts` instead, which is the route the ADR prescribes and the gate reads as its exemption registry. tsc is what proves the two new pins, and the pin count moves 783 -> 785 in all three places that state it. - `check:llms-txt`: the hand-kept inventory ships to AI consumers inside the tarball; `ai` 11 -> 12 and the total 200 -> 201, with the domain's subject list naming the new module rather than only its count. - `check:quick-reference-counts`: the AI section was complete at 11 of 11, so it stays complete at 12 of 12 with a row for the new reference page. Claude-Session: https://claude.ai/code/session_01KB5PFtxuy1x3dcR5gxudx6 Co-authored-by: Claude --- .../docs/getting-started/quick-reference.mdx | 3 +- packages/spec/llms.txt | 4 +-- packages/spec/src/ai/build-progress.zod.ts | 2 +- .../src/type-alias-convention.pin.test.ts | 31 +++++++++++++++++-- 4 files changed, 33 insertions(+), 7 deletions(-) diff --git a/content/docs/getting-started/quick-reference.mdx b/content/docs/getting-started/quick-reference.mdx index ef079cef641..bed22c22d4f 100644 --- a/content/docs/getting-started/quick-reference.mdx +++ b/content/docs/getting-started/quick-reference.mdx @@ -108,7 +108,7 @@ Runtime environment, logging, jobs, caching, and observability. | **[Translation](/docs/references/system/translation)** | `translation.zod.ts` | Translation | i18n support | | **[Worker](/docs/references/system/worker)** | `worker.zod.ts` | Worker | Background workers | -## AI Protocol (11 of 11 schemas) +## AI Protocol (12 of 12 schemas) AI/ML capabilities - agents, skills, tools, MCP exposure, RAG, and cost tracking. @@ -125,6 +125,7 @@ AI/ML capabilities - agents, skills, tools, MCP exposure, RAG, and cost tracking | **[Knowledge Document](/docs/references/ai/knowledge-document)** | `knowledge-document.zod.ts` | KnowledgeDocument, KnowledgeChunk | RAG documents and chunks | | **[Usage](/docs/references/ai/usage)** | `usage.zod.ts` | AIUsageRecord, TokenUsage | AI usage and cost tracking | | **[Solution Blueprint](/docs/references/ai/solution-blueprint)** | `solution-blueprint.zod.ts` | BlueprintObject, BlueprintApp | Blueprint format for AI app generation | +| **[Build Progress](/docs/references/ai/build-progress)** | `build-progress.zod.ts` | BuildProgressPhase, BuildProgressFrame | Phases the agent loop streams while building and verifying | ## API Protocol (17 of 31 schemas) diff --git a/packages/spec/llms.txt b/packages/spec/llms.txt index 141b7166c6a..2318f1e994a 100644 --- a/packages/spec/llms.txt +++ b/packages/spec/llms.txt @@ -77,7 +77,7 @@ const query = { --- -## 3. Schema Inventory by Domain (200 schemas) +## 3. Schema Inventory by Domain (201 schemas) Counted as `*.zod.ts` modules under `packages/spec/src//` — the sources that ship in this tarball (`files` includes `src/**/*.zod.ts`), so every number @@ -92,7 +92,7 @@ here is verifiable from the installed package. | ui | 18 | View, App, Action, Dashboard, Page, Chart, Component, Animation | | automation | 14 | Flow, Approval, BPMN Interop, Control Flow, State Machine, Webhook, Schedule Organization | | shared | 15 | Enums, HTTP, Identifiers, Mapping, Metadata Types, Connector Auth, Retry Policy, Value Domain, Epoch Instant (EpochMs), Duration (DurationMs / DurationSeconds) | -| ai | 11 | Agent, Conversation, Knowledge Source/Document, Model Registry, MCP, Skill, Tool | +| ai | 12 | Agent, Build Progress, Conversation, Knowledge Source/Document, Model Registry, MCP, Skill, Tool | | identity | 5 | Identity, Organization, Position, SCIM, Eval User | | marketplace | 4 | Marketplace, Package, Package Version, Template Manifest | | security | 4 | Permission, RLS, Sharing, Explain | diff --git a/packages/spec/src/ai/build-progress.zod.ts b/packages/spec/src/ai/build-progress.zod.ts index 890eb437397..53034bc672c 100644 --- a/packages/spec/src/ai/build-progress.zod.ts +++ b/packages/spec/src/ai/build-progress.zod.ts @@ -128,7 +128,7 @@ export const BuildProgressFrameSchema = lazySchema(() => z.looseObject({ })); /** A phase from the closed {@link BUILD_PROGRESS_PHASES} vocabulary. */ -export type BuildProgressPhase = z.infer; +export type BuildProgressPhase = z.input; /** The `data` payload of a `data-build-progress` frame. */ export type BuildProgressFrame = z.input; diff --git a/packages/spec/src/type-alias-convention.pin.test.ts b/packages/spec/src/type-alias-convention.pin.test.ts index b8f95f8022d..8ad36018f81 100644 --- a/packages/spec/src/type-alias-convention.pin.test.ts +++ b/packages/spec/src/type-alias-convention.pin.test.ts @@ -271,9 +271,11 @@ import type * as M185 from './shared/epoch.zod.js'; // [#18122] The closed duration vocabulary beside that instant — new module, // next free index (M186 is `automation/schedule-organization.zod.ts`). import type * as M187 from './shared/duration.zod.js'; +// [#18451] The build-progress PHASE vocabulary -- new module, next free index. +import type * as M188 from './ai/build-progress.zod.js'; // --------------------------------------------------------------------------- -// 783 isomorphic aliases: `z.input` === `z.infer`, so no `XParsed` is declared. +// 785 isomorphic aliases: `z.input` === `z.infer`, so no `XParsed` is declared. // // That number is machine-checked, not hand-kept. The runtime companion at the // bottom of this file recomputes the pin count from the source and asserts that @@ -1026,6 +1028,18 @@ export type Iso868 = Assert, z.infer< typeof export type Iso873 = Assert, z.infer< typeof M187.DurationMs > >>; export type Iso874 = Assert, z.infer< typeof M187.DurationSeconds > >>; +// ai/build-progress.zod.ts -- the closed build-progress PHASE vocabulary +// (#18451, cloud#2172 ruling A) and the frame that carries it. The enum has +// no default and no transform; the frame is a `z.looseObject` whose three +// members are a bare enum and two plain optionals, so both are the (RISE) +// case. The frame pin is the load-bearing one: it is deliberately a FLOOR +// that passes the consumer's own panel fields through untouched, and the day +// someone gives `phase` or `hop` a `.default()` to 'help' a producer that +// omits it, author state and parsed state part company -- this is the line +// that says so by name. +export type Iso875 = Assert, z.infer< typeof M188.BuildProgressPhaseSchema > >>; +export type Iso876 = Assert, z.infer< typeof M188.BuildProgressFrameSchema > >>; + // shared/value-domain.zod.ts — the ONE standard-domain vocabulary (#14168); // `SpecifierValueDomainSchema` (Iso758) is an alias of it, so both pins hold // or fall together. A `z.enum` has no default or transform, the (RISE) case. @@ -1664,7 +1678,7 @@ describe('ADR-0122 type-alias convention', () => { // this title and the section header above the pin list — are now asserted // against the recomputed count below, so neither can go stale without a red // test naming it. - it('still declares all 783 isomorphic pins', () => { + it('still declares all 785 isomorphic pins', () => { // The truth of each pin is proved by tsc, not here — an `Assert>` // that stops holding is a compile error with the alias named. What tsc // cannot notice is a pin that was DELETED: removing the assertion removes @@ -2227,7 +2241,18 @@ describe('ADR-0122 type-alias convention', () => { // `startTime: durationMs`-style `.default()` or a `.transform()` that // fills one member from another, this pin is the line that says the alias // has gained a second shape. - expect(pins).toHaveLength(783); + // 783 -> 785 is #18451's build-progress PHASE vocabulary + // (ai/build-progress.zod.ts, new module slot M188): `BuildProgressPhase` + // and `BuildProgressFrame`, the (RISE) case twice. The enum is a bare + // `z.enum` like the `ConnectorActionEffectSchema` pin that first taught + // this count to rise; the frame is a `z.looseObject` of one enum and two + // plain optionals. Neither carries a default or a transform, so no + // `XParsed` is declared and both come here instead. +2 added. + // + // Note the number is the same 783 -> 785 the DURATION block above records, + // arrived at from the same 783 after #16059's retirement took it back down. + // The two entries are different movements that share a pair of endpoints. + expect(pins).toHaveLength(785); // The count is stated in PROSE twice as well — this case's title and the // section header above the pin list — and until #6605 nothing read either From 3233eea6733eb621d76ed7386c558e290c987497 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 16 Sep 2026 20:21:39 +0000 Subject: [PATCH 4/4] docs: state the AI reference count without expanding the quick-reference table `check:quick-reference-counts` reds because a new reference page moved M, the number of pages the tree publishes. Correcting M is the whole requirement; listing the new page in the section table is discretionary polish and is not this card's. The section is now "11 of 12", the same shape the API section already carries at "17 of 31". Claude-Session: https://claude.ai/code/session_01KB5PFtxuy1x3dcR5gxudx6 Co-authored-by: Claude --- content/docs/getting-started/quick-reference.mdx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/content/docs/getting-started/quick-reference.mdx b/content/docs/getting-started/quick-reference.mdx index bed22c22d4f..1c924a95192 100644 --- a/content/docs/getting-started/quick-reference.mdx +++ b/content/docs/getting-started/quick-reference.mdx @@ -108,7 +108,7 @@ Runtime environment, logging, jobs, caching, and observability. | **[Translation](/docs/references/system/translation)** | `translation.zod.ts` | Translation | i18n support | | **[Worker](/docs/references/system/worker)** | `worker.zod.ts` | Worker | Background workers | -## AI Protocol (12 of 12 schemas) +## AI Protocol (11 of 12 schemas) AI/ML capabilities - agents, skills, tools, MCP exposure, RAG, and cost tracking. @@ -125,7 +125,6 @@ AI/ML capabilities - agents, skills, tools, MCP exposure, RAG, and cost tracking | **[Knowledge Document](/docs/references/ai/knowledge-document)** | `knowledge-document.zod.ts` | KnowledgeDocument, KnowledgeChunk | RAG documents and chunks | | **[Usage](/docs/references/ai/usage)** | `usage.zod.ts` | AIUsageRecord, TokenUsage | AI usage and cost tracking | | **[Solution Blueprint](/docs/references/ai/solution-blueprint)** | `solution-blueprint.zod.ts` | BlueprintObject, BlueprintApp | Blueprint format for AI app generation | -| **[Build Progress](/docs/references/ai/build-progress)** | `build-progress.zod.ts` | BuildProgressPhase, BuildProgressFrame | Phases the agent loop streams while building and verifying | ## API Protocol (17 of 31 schemas)