From a33c0d4a9c2e2a579321f03f90d2bba247ef24ee Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 18 Sep 2026 02:25:11 +0000 Subject: [PATCH] fix(spec): label every producer claim in the build-progress docblock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The module docblock on `ai/build-progress.zod.ts` stated three producer claims as MEASUREMENTS. It ships inside the published `@objectstack/spec` tarball and is rendered verbatim into the generated reference page, and for a CLOSED vocabulary it is the audit trail the "re-measure before you move the array" discipline reads — so a reader deciding whether a fifth phase is warranted read all three as readings. One of them was false. Every producer claim now carries exactly one of three labels, defined at the top of the module: measured on a named reachable source / declared by ruling / inferred. - Membership is no longer described as uniformly measured. `structure`, `data` and `done` stay measured, cited with the tree they were read against; `verify` is declared by ruling (cloud#2172, objectui#7388), with the zero-occurrence reading and its bright control recorded beside it. - The cloud#1838 window (111 seconds, 9 tool calls, one of them `verify_build`) is inferred: that record is unreachable from this repo. What is measured is narrower and stated as such — `verify_build` is a registered platform tool. - The two skipped-phase illustrations are inferred; the consumer guidance around them is unchanged and does not rest on them. Adds a `## Liveness watch` section recording that `verify`, `hop` and `tool` are declared ahead of any code that uses them, who the named carriers are, and that no gate watches it — `BuildProgressFrame` is not a registered metadata type, so the ADR-0049 liveness ledger never sees it. No schema, export or parse behaviour moves. `content/docs/references/ai/ build-progress.mdx` is regenerated from the docblock by `gen:docs`. Claude-Session: https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3 Co-authored-by: Claude --- ...8552-build-progress-docblock-provenance.md | 46 +++++++ content/docs/references/ai/build-progress.mdx | 61 ++++++++-- packages/spec/src/ai/build-progress.test.ts | 57 +++++++++ packages/spec/src/ai/build-progress.zod.ts | 112 ++++++++++++++---- 4 files changed, 247 insertions(+), 29 deletions(-) create mode 100644 .changeset/18552-build-progress-docblock-provenance.md diff --git a/.changeset/18552-build-progress-docblock-provenance.md b/.changeset/18552-build-progress-docblock-provenance.md new file mode 100644 index 00000000000..3a26195311c --- /dev/null +++ b/.changeset/18552-build-progress-docblock-provenance.md @@ -0,0 +1,46 @@ +--- +'@objectstack/spec': patch +--- + +fix(spec): label every producer claim in the `build-progress` docblock — measured, ruled, or inferred (#18552) + +Clause-②: no + +The module docblock on `ai/build-progress.zod.ts` stated three producer claims +as MEASUREMENTS. It ships in this tarball (the published `files[]` carries the +`.zod.ts` sources) and is rendered verbatim into the generated reference page, +and for a CLOSED vocabulary it is the audit trail the "re-measure before you +move the array" discipline reads. One of the three was false, and a reader +deciding whether a fifth phase is warranted would have read all three as +readings. + +Each producer claim now carries exactly one of three labels, defined at the top +of the module: **measured on a named reachable source**, **declared by ruling**, +or **inferred**. + +- Membership is no longer described as uniformly measured. `structure`, `data` + and `done` stay **measured** — the objectui reader's own union and coercion + default, cited with the tree they were read against. `verify` is **declared by + ruling** (cloud#2172, objectui#7388): at the read tree the chat panel has zero + occurrences of `'verify'` against a control of four files for `'structure'`, + and this repository emits no frame at all. That is a good reason for the + member; it is not an observation, and the docblock no longer says it is. +- The cloud#1838 window — "111 seconds and 9 tool calls", "one of them + `verify_build`" — is **inferred**: that record is not reachable from this + repository, so the figure is carried, not measured, and which tools those + calls were is recorded nowhere reachable. What is measured is narrower and + stated as such: `verify_build` is a registered platform tool. +- "A turn that seeds no sample data never reports `data`" and "`apply_edit` + turns need not report `structure`" are **inferred**. The consumer guidance + around them is unchanged and does not rest on them: treat every phase as + optional and compare by value. + +A new `## Liveness watch` section records that `verify`, `hop` and `tool` are +declared ahead of any code that uses them, that cloud#2172 and objectui#7388 +block 2 are the named carriers meant to close that, and that no gate watches it +— `BuildProgressFrame` is not a registered metadata type, so the ADR-0049 +liveness ledger never sees it. + +No schema, export or parse behaviour moves: `BUILD_PROGRESS_PHASES`, +`BuildProgressPhaseSchema` and `BuildProgressFrameSchema` accept and refuse +exactly what they did before. diff --git a/content/docs/references/ai/build-progress.mdx b/content/docs/references/ai/build-progress.mdx index 039020e025b..3939d97cd4a 100644 --- a/content/docs/references/ai/build-progress.mdx +++ b/content/docs/references/ai/build-progress.mdx @@ -8,18 +8,45 @@ description: Build Progress protocol schemas Build-progress PHASE vocabulary for the `data-build-progress` stream frame (cloud#2172 ruling A). +## How to read the claims in this module + +This vocabulary is CLOSED, so whoever next asks whether a fifth member is +warranted has to re-measure before moving the array — and can only do that +if they can tell a reading from a ruling. Every producer claim below +therefore carries exactly one of three labels, and none is left bare: + +- **measured on a named reachable source** — a repository and path the + reader can open and re-measure, cited with the tree it was read against. +- **declared by ruling** — a maintainer decision. The authority is the + ruling; ⛔ it is not evidence the behaviour exists yet. +- **inferred** — deduced from something reachable, or carried from a record + this repository cannot open. ⛔ Never re-cite it as a measurement. + ## Producer The cloud AI-studio **agent loop** — deliberately not the tool it just ran. +**Declared by ruling**: cloud#2172 ruling A owns WHERE in the loop a frame is +emitted; this module declares only what such a frame may SAY. + 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. +re-reading and re-seeding what it wrote. **Inferred.** The only record of +that window is the cloud#1838 turn, reported there as 111 seconds and 9 tool +calls *after* `apply_blueprint` returned — and `objectstack-ai/cloud` is +outside the set of repositories a reader of this file can open, so the +figure is carried here, not measured, and cannot be re-measured from this +repo. WHICH tools those 9 calls were is recorded nowhere reachable either: +neither objectstack#18451 nor objectui#7388 names them, so "one of them ran +the registered `verify_build` tool" is **inferred** as well. What is +**measured on a named reachable source** is narrower than that — only that +`verify_build` is a registered platform tool, listed in +`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. **Inferred** from a +reachable contract: `AIToolContext.onProgress` in `../contracts/ai-service` +declares the emit as happening WHILE the tool executes, before it returns; +that the handle is gone afterwards is the deduction, not the declaration. ## Consumer @@ -40,6 +67,26 @@ Unchanged: the `data-`-prefixed custom part described on 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. +## Liveness watch — declared ahead, and ⛔ nothing gates it + +Three surfaces here are declared ahead of any code that uses them: the +`verify` phase, and the frame's `hop` and `tool` fields. Across every +repository reachable from here they have zero emitters and zero readers +(**measured on named reachable sources** — each reading is recorded in the +source beside its surface: on `BUILD_PROGRESS_PHASES` for the phase, and on +the `hop` / `tool` field declarations). They stand on a ruling instead +(**declared by ruling**): cloud#2172 ruled the vocabulary in, and +objectui#7388 asked the panel to be able to name the phase. That is a good +reason, and it is not a measurement. + +⛔ No gate watches this. The ADR-0049 liveness ledger is rooted in the +metadata-type registry, and `BuildProgressFrame` is not a registered +metadata type, so the liveness job stays green however long these three go +unused. Two named carriers are meant to close it — cloud#2172 for the +emitter, objectui#7388 block 2 for the consumer's strict parse. If neither +lands, the three become enforce-or-remove candidates with nothing watching +them, and the only thing that notices is a person reading this paragraph. + **Source:** `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 index 3f48b90477b..9add52a3a9d 100644 --- a/packages/spec/src/ai/build-progress.test.ts +++ b/packages/spec/src/ai/build-progress.test.ts @@ -23,6 +23,10 @@ * someone re-measured — not an edit to wave through. */ +import { readFileSync } from 'node:fs'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + import { describe, it, expect } from 'vitest'; import { @@ -180,3 +184,56 @@ describe('BuildProgressPhase (type)', () => { expect(everyPhase).toHaveLength(BUILD_PROGRESS_PHASES.length); }); }); + +/** + * The provenance leg — and the one leg whose subject is TEXT on purpose. + * + * This module's docblock is not incidental prose. The published + * `@objectstack/spec` tarball ships the `.zod.ts` sources themselves, and the + * same text is rendered verbatim into + * `content/docs/references/ai/build-progress.mdx`. For a CLOSED vocabulary it + * is the audit trail the "re-measure before you move the array" discipline + * reads: a reader who cannot tell a MEASUREMENT from a RULING re-cites the + * ruling as evidence, and the next member goes in on a claim nobody ever made. + * + * So what is pinned here is narrow and load-bearing: that the three labels are + * defined, that `verify` is labelled as the ruling it is rather than as a + * measurement, and that the liveness watch is still present — ⛔ not the + * wording around any of them. Nothing else in this file asserts on text. + */ +describe('module docblock provenance', () => { + const SOURCE = readFileSync( + resolve(dirname(fileURLToPath(import.meta.url)), 'build-progress.zod.ts'), + 'utf-8', + ); + + it('defines all three provenance labels', () => { + for (const label of ['measured on a named reachable source', 'declared by ruling', 'inferred']) { + expect(SOURCE).toContain(label); + } + }); + + it('labels `verify` as declared by ruling, and ⛔ never as measured membership', () => { + // The bullet itself, not the surrounding paragraph: `verify` is in the + // enum because cloud#2172 ruled it in, which is a good reason and is not + // an observation of any producer or consumer. + const bullet = SOURCE.slice(SOURCE.indexOf(' * - `verify`')); + expect(bullet.slice(0, 200)).toContain('**declared by ruling**'); + + // The retired headline claimed every member was measured against a real + // end of the channel. It was false for `verify` on the day it was written. + expect(SOURCE).not.toContain('Membership was MEASURED'); + expect(SOURCE).not.toContain('every member below is one a real producer emits'); + }); + + it('keeps the liveness watch for the three declared-ahead surfaces', () => { + expect(SOURCE).toContain('## Liveness watch'); + for (const surface of ['`verify` phase', '`hop`', '`tool`']) { + expect(SOURCE).toContain(surface); + } + // The point of the watch: no gate notices, so the paragraph is the notice. + expect(SOURCE).toContain('ADR-0049'); + expect(SOURCE).toContain('cloud#2172'); + expect(SOURCE).toContain('objectui#7388 block 2'); + }); +}); diff --git a/packages/spec/src/ai/build-progress.zod.ts b/packages/spec/src/ai/build-progress.zod.ts index 53034bc672c..d1f4bb211c4 100644 --- a/packages/spec/src/ai/build-progress.zod.ts +++ b/packages/spec/src/ai/build-progress.zod.ts @@ -7,18 +7,45 @@ import { lazySchema } from '../shared/lazy-schema'; * Build-progress PHASE vocabulary for the `data-build-progress` stream frame * (cloud#2172 ruling A). * + * ## How to read the claims in this module + * + * This vocabulary is CLOSED, so whoever next asks whether a fifth member is + * warranted has to re-measure before moving the array — and can only do that + * if they can tell a reading from a ruling. Every producer claim below + * therefore carries exactly one of three labels, and none is left bare: + * + * - **measured on a named reachable source** — a repository and path the + * reader can open and re-measure, cited with the tree it was read against. + * - **declared by ruling** — a maintainer decision. The authority is the + * ruling; ⛔ it is not evidence the behaviour exists yet. + * - **inferred** — deduced from something reachable, or carried from a record + * this repository cannot open. ⛔ Never re-cite it as a measurement. + * * ## Producer * * The cloud AI-studio **agent loop** — deliberately not the tool it just ran. + * **Declared by ruling**: cloud#2172 ruling A owns WHERE in the loop a frame is + * emitted; this module declares only what such a frame may SAY. + * * 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. + * re-reading and re-seeding what it wrote. **Inferred.** The only record of + * that window is the cloud#1838 turn, reported there as 111 seconds and 9 tool + * calls *after* `apply_blueprint` returned — and `objectstack-ai/cloud` is + * outside the set of repositories a reader of this file can open, so the + * figure is carried here, not measured, and cannot be re-measured from this + * repo. WHICH tools those 9 calls were is recorded nowhere reachable either: + * neither objectstack#18451 nor objectui#7388 names them, so "one of them ran + * the registered `verify_build` tool" is **inferred** as well. What is + * **measured on a named reachable source** is narrower than that — only that + * `verify_build` is a registered platform tool, listed in + * `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. **Inferred** from a + * reachable contract: `AIToolContext.onProgress` in `../contracts/ai-service` + * declares the emit as happening WHILE the tool executes, before it returns; + * that the handle is gone afterwards is the deduction, not the declaration. * * ## Consumer * @@ -38,31 +65,64 @@ import { lazySchema } from '../shared/lazy-schema'; * `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. + * + * ## Liveness watch — declared ahead, and ⛔ nothing gates it + * + * Three surfaces here are declared ahead of any code that uses them: the + * `verify` phase, and the frame's `hop` and `tool` fields. Across every + * repository reachable from here they have zero emitters and zero readers + * (**measured on named reachable sources** — each reading is recorded in the + * source beside its surface: on `BUILD_PROGRESS_PHASES` for the phase, and on + * the `hop` / `tool` field declarations). They stand on a ruling instead + * (**declared by ruling**): cloud#2172 ruled the vocabulary in, and + * objectui#7388 asked the panel to be able to name the phase. That is a good + * reason, and it is not a measurement. + * + * ⛔ No gate watches this. The ADR-0049 liveness ledger is rooted in the + * metadata-type registry, and `BuildProgressFrame` is not a registered + * metadata type, so the liveness job stays green however long these three go + * unused. Two named carriers are meant to close it — cloud#2172 for the + * emitter, objectui#7388 block 2 for the consumer's strict parse. If neither + * lands, the three become enforce-or-remove candidates with nothing watching + * them, and the only thing that notices is a person reading this paragraph. */ /** * 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: + * Membership is MIXED-PROVENANCE, not uniformly measured. Each member carries + * its own label (the three are defined at the top of this module), because the + * question a re-measure has to answer is which members a real end of the + * channel already uses and which are standing on a ruling alone: * - * - `structure`, `data`, `done` — the consumer's own declared union, + * - `structure`, `data`, `done` — **measured on a named reachable source**: + * 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` + * `packages/plugin-chatbot/src/mapMessages.ts:743` * (`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`). + * `structure` doubles as that reader's coercion default. Read at objectui + * `dda8f3815`; line numbers drift, the two symbols are the anchor. + * - `verify` — **declared by ruling**, ⛔ not measured: 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. At objectui + * `dda8f3815` `packages/plugin-chatbot` has ZERO occurrences of `'verify'` + * (bright control, same instrument: `'structure'` hits 4 files there), and + * this repository emits no frame at all, so `verify` has no reachable + * emitter and no reachable reader today. **Measured on a named reachable + * source** alongside it, and narrower than a corroboration of the phase: + * `service-ai-studio` registers a `verify_build` tool + * (`../system/constants/platform-tool-names`). See the liveness watch at the + * top of this module. * * 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`). + * assuming every phase occurs. The two illustrations of a phase being skipped + * — a turn that applies no seed data never reporting `data`, and `apply_edit` + * turns not reporting `structure` — are **inferred**: no reachable repository + * records either, and cloud#2172's emitter has not landed. ⛔ The guidance + * does not rest on them; treat every phase as optional whatever a producer + * turns out to do. * * 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 @@ -107,13 +167,18 @@ export const BuildProgressFrameSchema = lazySchema(() => z.looseObject({ 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. + * counter behind the "9 tool calls" the cloud#1838 turn record reports, so + * the panel can show motion through a window that is otherwise a single flat + * phase. **Inferred** that a producer counts hops at all: that record is not + * reachable from this repository (see the Producer section). * * 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. + * + * ⛔ Zero emitters and zero readers in every reachable repository at this + * tree — see the liveness watch at the top of this module. */ hop: z.number().int().nonnegative().optional().describe('Post-apply verification hop this frame reports'), /** @@ -123,6 +188,9 @@ export const BuildProgressFrameSchema = lazySchema(() => z.looseObject({ * 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. + * + * ⛔ Zero emitters and zero readers in every reachable repository at this + * tree — see the liveness watch at the top of this module. */ tool: z.string().min(1).optional().describe('Name of the tool the current hop is running'), }));