Repository navigation
Commit 3937ad2
feat(spec)!: an agent's structuredOutput is JSON-only — retire the regex / grammar / xml formats and the coerce_types step; the block is live, enforced by the cloud AI runtime (#21367)
Fixes #21277
Part of #20274
Clause-②: no (narrowing)
## What this does
Ruling of record 5945617233 (letter A, maintainer 「其他同意」): retire the
four `agent.structuredOutput` members the cloud AI runtime refuses, flip
the ledger row, correct the describes.
- **The four members leave the spec, refused by name with a
prescription.** `regex`, `grammar` and `xml` leave
`StructuredOutputFormat` (so `format` and `fallbackFormat`), and
`coerce_types` leaves `TransformPipelineStep` (so `transformPipeline`).
Both enums are now declared through `enumWithRetiredValues`
(`packages/spec/src/shared/retired-key.ts`), the house value-level
retirement mechanism. An authored retired member fails `tsc` (it is gone
from the type) and is refused at parse with its prescription; any other
unknown value keeps zod's own message. The prescriptions are
module-private, so the public export surface does not grow.
- The `regex` / `grammar` / `xml` prescription: structured output is
JSON-only; use `json_schema` with a JSON Schema in `schema`, or
`json_object`, or delete the block; at `fallbackFormat`, name a JSON
format or delete the key.
- The `coerce_types` prescription: there is no coercion engine; delete
the step and declare the exact types in `schema`.
- Both close with the house `os migrate meta --from 17` sentence, and
neither carries a tracker number.
- **D2 conversion `agent-structured-output-refused-members-removed`**
(step 18, retired from the load path, `retiredAfter` 17.6.0, order 55):
- a block whose `format` was retired is DELETED, because `format` is
required and no rewrite can say which JSON contract the author wanted
(the `agent.tools` / dataset-measure precedent);
- a retired `fallbackFormat` is deleted;
- `coerce_types` is dropped from the pipeline, and the other steps keep
their order. The key stays even when the list empties (the `crypto.hash`
precedent).
- The fixture covers all three edits plus two untouched controls, for 4
notices.
- **D3 entry `agent-structured-output-refused-members-retired`**
(`conversionIds` names the D2 entry). It carries the judgement the
conversion cannot make: whether an agent whose block was deleted should
now carry a `json_schema` contract. A `STEP18_RATIONALE` fragment is at
order 60.
- **Ledger:** the `agent.structuredOutput` row moves `experimental` →
`live`.
- Evidence: `cloud cb62c3ea`,
`agent-runtime.ts#compileStructuredOutput` (via
`AgentRuntime.resolveTurnGuardrails`) and
`ai-service.ts#AIService.settleFinalAnswer`. The pre-run refusal
`TurnGovernor.unsupportedOutput` is named in the evidence text without a
path, because the reading names the symbol and not its file.
- `evidenceScope: cross-repo`, `verifiedAt` 2026-10-02.
- The note attributes all of this to the `repo:cloud#1` seat's reading
5943158888. No seat in this session can read cloud, and none did.
- Producer: none in cloud at that ref; tenant-authored agents are NOT
MEASURED.
- `state-counts/agent.md` was regenerated: live 21 → 22, experimental 3
→ 2.
- **Describes.** `structuredOutput` drops `[EXPERIMENTAL — not
enforced]`, worded on the `guardrails` precedent: the cloud AI runtime
enforces it on every final answer, and the open framework edition does
not run agents.
- `fallbackFormat` now states what the runtime does with it: once the
primary format's retries are spent, the last answer is checked against
it.
- The rider 5944103492 item 1: `guardrails.blockedTopics` states the
enforced match, "an exact, case-sensitive match on the tool name, on
`action_` plus the action type, or on the tool category". Rider item 2
(`structuredOutput.schema`) is not in this PR; it landed separately, and
this branch merged it.
- **Regenerated:** `content/docs/references/ai/agent.mdx`.
- **Changeset:**
`.changeset/21277-agent-structured-output-json-only.md`,
`@objectstack/spec` minor, BREAKING, FROM → TO table, one-line fix,
`Clause-②: no (narrowing)`, and an ADR-0087 marker `registered` naming
both entries.
The four surface ratchets (`api-surface`, `authorable-surface`,
`json-schema.manifest`, `api-surface-signatures`) are byte-identical, as
the playbook predicts for an enum-value narrowing: no def, export or
authorable key changed, so nothing lands in `RETIRED_KEYS_BY_MAJOR`.
## Verification (at `ff5810c0ed`, after merging `origin/main` at
`23365eaedf`)
- `pnpm --filter @objectstack/spec build` + `check:generated`: 15 of 15
artifacts current.
- `@objectstack/spec` `vitest --project local`: 598 files, 17523 passed,
1 todo.
- `@objectstack/spec` `typecheck` (`tsc --noEmit`, scripts, and the test
layer): OK. The two `@ts-expect-error` pins in `agent.test.ts` are in
the compiled test program, so this green also proves `tsc` rejects
`'regex'` and `'coerce_types'` at their typed positions.
- `@objectstack/spec` `vitest --project repo`: 42 of 48 files, 691
passed. The other six are NOT MEASURED; see below.
- Consumer package `@objectstack/lint` (reads `liveness/agent.json`;
built `formula`, `sdui-parser` and `lint` first): 119 files, 5575
passed.
- `node scripts/pm/dispatch-gates.mjs --ran`: 114 derived families, 113
run green, 1 NOT MEASURED (below), 0 unrun. This covers
`check:adr-0087-registration` ("1 declared-breaking changeset(s), each
carrying an ADR-0087 disposition … registered
agent-structured-output-refused-members-removed,
agent-structured-output-refused-members-retired"),
`check-changeset-no-major` ("This diff introduces no `major` bump"),
`check:liveness`, `check:doc-authoring`, `check:nul-bytes`,
`check:skill-examples` and `check:migration-registry`.
- **Ablation** (committed tree; `scripts/ablation-replace.mjs`, mutation
proven on disk, restore proven by blob hash equal to HEAD and an empty
`git diff HEAD`):
- The format prescription map was swapped for a dummy member: 6 failed
(the six `format` / `fallbackFormat` refusal pins), 45 passed.
- The `coerce_types` map was swapped: 1 failed (its pin), 50 passed.
- In both runs the "never-legal value keeps zod's message" control
stayed green, which is the expected direction.
**NOT MEASURED**
- `pnpm check:type-check-debt`, reason: it re-measures `tsc` over all 80
workspace packages and was cap-killed twice (150 s, then 570 s). It is a
repo-wide sweep CI owns; `@objectstack/spec` typecheck is green above.
- `@objectstack/spec` repo-project files `publish-smoke-boot-failure`,
`publish-smoke-port-collision`, `dist-freshness`,
`dist-freshness-adoption`, `build-schemas-check-mode` and
`schema-tree-freshness`, reason: they drive whole builds, and two runs
were cap-killed at the 595 s foreground ceiling. They exercise build
tooling this diff does not touch.
- `@objectstack/cli` integration tier, reason: declared to CI. No CLI
test authors `structuredOutput`.
## Fixture census
- Producers outside `packages/spec`, tests excluded (`examples/**`,
`packages/**`): 0 files author `structuredOutput`.
- Tests and fixtures outside `packages/spec` naming a retired member or
the structured-output keys: one hit, a comment in
`packages/lint/src/lint-liveness-properties.test.ts`, which asserts
nothing about the row. The lit control (`guardrails` / `blockedTopics`)
hits 4 files.
- Inside `packages/spec`, the three `agent.test.ts` cases that pinned
the old acceptance were flipped: the five-format list, a pipeline with
`coerce_types`, and `format: 'regex'` beside a fallback. Seven refusal
pins (the three formats at `format` and at `fallbackFormat`, plus
`coerce_types`), one never-legal-value control and one type-level pin
were added: 9 new tests, so `agent.test.ts` goes from 42 to 51.
- objectui at the pinned `31971ff1e2`: 0 hits for `StructuredOutput` /
`structuredOutput` / `coerce_types`. The control `blockedTopics` /
`AgentSchema` hits 3 files.
- `content/docs/ai/**`: 0 hits. `skills/**`: 0 hits for a retired
member. `skills/objectstack-ai/references/_index.md` lists the two enum
export names, which are unchanged.
## Acceptance notes
- **The Studio offer for `structuredOutput` is now undecided.**
`packages/spec/src/system/metadata-form-zod-reconciliation.test.ts`
carries an `omit` row for `agent.structuredOutput` whose reason cites
the `[EXPERIMENTAL — not enforced]` describe and the `experimental`
ledger status. This PR removes both.
- That ledger's own rule says a not-enforced-yet row is stale once the
key is enforced: delete it and decide the offer.
- Deciding the offer means a control in `agent.form.ts` plus the four
translation bundles in `platform-objects`. That is outside this card's
claim surface and was not ruled, so the row is left as it is (the test
stays green).
- Carrier: the `domain:spec` seat, to route.
- **The release number in the prescriptions.** They say "removed in
@objectstack/spec 17.7.0", assuming the next release is a minor (the
launch-window no-major rule; the label is 17.6.0, which is published).
If the next release is cut as a major, those runtime strings need the
new number.
- **No tree-scoped absence pin.** That follows the value-retirement
precedent (`RealtimeEventType`, `HookBodyCapability.crypto.hash`, the
sub-day intervals): the pins sit on the enum, and `tsc` plus the parse
cover authored sources. The docs and skills census above is zero.
- **`action_` plus the action type** renders the reading's `action_` + T
as the rider and dispatch worded it. No seat here can open cloud to
confirm that T is the action type.
- **The claim declared `Clause-②: yes`; this PR declares `no
(narrowing)`.** The diff widens nothing (no new export, `api-surface`
byte-identical, no accept-set growth) and narrows an accept set, the arm
`check-adr-0087-registration` reads. The gate outcome is the same either
way: minor, breaking, ADR-0087 marker owed.
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent f397608 commit 3937ad2
9 files changed
Lines changed: 528 additions & 42 deletions
File tree
- .changeset
- content/docs/references/ai
- packages/spec
- liveness
- state-counts
- src
- ai
- conversions
- migrations
- entries/semantic
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
60 | 60 | | |
61 | 61 | | |
62 | 62 | | |
63 | | - | |
| 63 | + | |
64 | 64 | | |
65 | 65 | | |
66 | 66 | | |
| |||
110 | 110 | | |
111 | 111 | | |
112 | 112 | | |
113 | | - | |
| 113 | + | |
114 | 114 | | |
115 | 115 | | |
116 | 116 | | |
117 | 117 | | |
118 | 118 | | |
119 | | - | |
| 119 | + | |
120 | 120 | | |
121 | 121 | | |
122 | 122 | | |
123 | 123 | | |
124 | | - | |
125 | | - | |
| 124 | + | |
| 125 | + | |
126 | 126 | | |
127 | 127 | | |
128 | 128 | | |
| |||
143 | 143 | | |
144 | 144 | | |
145 | 145 | | |
146 | | - | |
| 146 | + | |
147 | 147 | | |
148 | 148 | | |
149 | 149 | | |
150 | 150 | | |
151 | | - | |
152 | | - | |
| 151 | + | |
| 152 | + | |
153 | 153 | | |
154 | 154 | | |
155 | 155 | | |
156 | 156 | | |
157 | 157 | | |
158 | 158 | | |
159 | | - | |
| 159 | + | |
160 | 160 | | |
161 | 161 | | |
162 | 162 | | |
163 | 163 | | |
164 | 164 | | |
165 | | - | |
166 | | - | |
167 | | - | |
168 | 165 | | |
169 | 166 | | |
170 | 167 | | |
| |||
178 | 175 | | |
179 | 176 | | |
180 | 177 | | |
181 | | - | |
182 | 178 | | |
183 | 179 | | |
184 | 180 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
103 | 103 | | |
104 | 104 | | |
105 | 105 | | |
106 | | - | |
107 | | - | |
108 | | - | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
109 | 111 | | |
110 | 112 | | |
111 | 113 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
15 | | - | |
| 15 | + | |
0 commit comments