Skip to content

feat(spec)!: retire CONCURRENT_LIMIT_EXCEEDED from StandardErrorCode - #19957

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-17158-export-job-family-retired
Sep 27, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-17158-export-job-family-retired

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #17707

Clause-②: no

Rewritten short by the domain:spec#5 seat (2026-09-24T07:48Z). Ruling A 5651023241, narrowed by 5807013768; the dev report is on #17158 (5809956647). #17158 did not ride: the objectui pin imports three of its types, so it went back to the decision box (5810005336).

CONCURRENT_LIMIT_EXCEEDED leaves StandardErrorCode. The retired spelling is refused, with its prescription, at the three catalogue doors:

  • StandardErrorCode / EnhancedApiErrorSchema.code;
  • ErrorCode / ApiErrorSchema.code;
  • makeApiErrorSchema.

QUOTA_EXCEEDED is unchanged.

What changed

  • errors.zod.ts, error-code-ledger.zod.ts and contract.zod.ts pass one error map, taken from the package-internal api/retired-error-codes.ts.
  • content/docs/api/error-catalog.mdx loses the entry. The reference pages and scripts/error-status-unpinned-baseline.json were regenerated.
  • scripts/check-error-status-conformance.mjs: its vocabulary parse now stops at the enum array's own bracket. Without that, the new options object made it misread. The fix is pinned in the script's self-test.
  • ADR-0087 D3 entry standard-error-code-concurrent-limit-exceeded-retired; registry.ts regenerated.
  • Changeset: @objectstack/spec at minor, with a BREAKING banner, FROM → TO, and out-of-repo consumers marked NOT MEASURED.

Verification (the dev's, at e9f9b060f3)

  • The spec build, typecheck and suites are green, and so are the suites of the vocabulary's consumers.
  • Ablations:
    • removing any door's map turns that door's refusal pins red;
    • re-planting the member turns the refusal pins red;
    • reverting the parser fix reddens its self-test case and the error-catalog doc tests.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1

Removes the producerless 429 member from the closed catalogue (ADR-0049
enforce-or-remove, ADR-0112), on ruling A narrowed to this code alone.
QUOTA_EXCEEDED stays unchanged.

- The retired spelling answers with its prescription at every catalogue
  door: StandardErrorCode, ErrorCode (ApiErrorSchema.code) and
  makeApiErrorSchema, through a package-internal error map
  (api/retired-error-codes.ts).
- The hand-written error catalogue loses its entry and its wire count
  moves 52 -> 51; the unpinned-status baseline is regenerated by its gate.
- check-error-status-conformance's vocabulary parse now stops at the
  enum array's own bracket (an options object no longer lets it read the
  next declarations), with a self-test battery for it.
- ADR-0087 D3 semantic entry, registry regenerated.

Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1
Co-authored-by: Claude <noreply@anthropic.com>
…EEDED retirement

Produced by `pnpm --filter @objectstack/spec check:generated --fix` (only
check:docs was proven stale): contract.mdx, error-code-ledger.mdx and
errors.mdx drop the retired member and their enum counts move by one.

Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Sep 24, 2026
@github-actions

github-actions Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 8 documentable anchor(s).

6 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/client-sdk.mdx (via ErrorCode (symbol, a top-level const object), StandardErrorCode (symbol, a top-level const object))
  • content/docs/api/error-catalog.mdx (via StandardErrorCode (symbol, a top-level const object))
  • content/docs/api/error-handling-server.mdx (via StandardErrorCode (symbol, a top-level const object))
  • content/docs/api/index.mdx (via StandardErrorCode (symbol, a top-level const object))
  • content/docs/api/wire-format.mdx (via StandardErrorCode (symbol, a top-level const object))
  • content/docs/protocol/kernel/error-handling.mdx (via ErrorCode (symbol, a top-level const object), StandardErrorCode (symbol, a top-level const object))

⛔ 2 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17/17-0.mdx (via StandardErrorCode (symbol, a top-level const object))
  • content/docs/releases/v17/17-1.mdx (via StandardErrorCode (symbol, a top-level const object))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 4 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 136 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 805af4f290565955d6e6b56ee46fed45f721d955 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 45ae7dca4cd5599bb77eca0e2fdb74a3e1f2842f — the merge of head 628738ddf436e3a4eda155861318821e14de2ded into base 805af4f290565955d6e6b56ee46fed45f721d955, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 45ae7dca4cd5599bb77eca0e2fdb74a3e1f2842f && git checkout 45ae7dca4cd5599bb77eca0e2fdb74a3e1f2842f
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 805af4f290565955d6e6b56ee46fed45f721d955 628738ddf436e3a4eda155861318821e14de2ded && git checkout -B drift-repro 805af4f290565955d6e6b56ee46fed45f721d955 && git merge --no-ff 628738ddf436e3a4eda155861318821e14de2ded

node scripts/docs-audit/affected-docs.mjs --json 805af4f290565955d6e6b56ee46fed45f721d955

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 805af4f290565955d6e6b56ee46fed45f721d955 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: e9f9b060f3a3b0f5f34892eaa4e45c0527df5abf

Reviewed and posted 2026-09-24T08:05Z by the at-tier review subagent the domain:spec#5 seat spawned — read: #17707 (body + 4 comments, 5651023241 / 5804874023 / 5807013768), #17158 5809956647 + 5810005336 (as claims), PR #19957 body / diff / 2 commits / 46 check-runs, base f5a7250b7a check-runs, AGENTS.md, contract-review.md, spec-property-retirement; ran in a detached worktree at head: tree-wide greps at base and head, objectui grep at the pin, the three regex parsers against the enum source, tsx parse probes at every door, a tsc probe, --self-test with ablation, the pin file and the doc test with ablations A1–A6; NOT MEASURED: cloud (unreadable), the derived gate family (not re-run), Lint & Repo Gates (still running).

① Derived judgments

Closure. Doors that parse a code, enumerated from every z.enum built on StandardErrorCode.options in packages/** (non-test) plus every StandardErrorCode-typed site: (1) StandardErrorCode errors.zod.ts:53 — through it EnhancedApiErrorSchema.code :384 and StandardSynonymWaiver.shadows error-code-ledger.zod.ts:1504; (2) ErrorCode error-code-ledger.zod.ts:1383 — ApiErrorSchema.code; (3) makeApiErrorSchema contract.zod.ts:283. No fourth. HttpStatusErrorCodeMap never carried the member; standardSynonymOf only iterates. All three pass the same map; measured (tsx, head): the retired spelling is refused at all five parse sites with the prescription text — reachable at every door. Lists: .options 49 (base 50); ErrorCode.options 337 = 7 + 330 on the regenerated contract.mdx row, 49 = 7 + 42 on the errors.mdx / error-code-ledger.mdx rows. Tree at head (git grep -i): the spelling survives only in the changeset, the retirement comment, the map key + docblock, the pin file, the D3 entry and its registry.ts copy — nowhere as current. All 8 base occurrences (declaration, error-catalog.mdx:473, 5 reference-page rows, baseline row) are gone; 0 hits at base in releases/**, CHANGELOG.md, skills/, docs/qa, liveness. objectui at the pin 62597c5880: 0 files (controls QUOTA_EXCEEDED 3, RATE_LIMIT_EXCEEDED 1).

Regression. QUOTA_EXCEEDED: zero - lines in base..head; per-file counts identical for all 17 pre-existing files; parses at all five sites (measured). Other codes: all 49 members parse at every door, 0 failing (measured). For non-retired invalid inputs (FOO, the near-miss typo, '', lowercase, 42, null, undefined, an object) doors 1 and 2 answer byte-equal to a map-less z.enum twin built from the same options (measured) — the map rewrites nothing but the one spelling. api-surface/ / json-schema.manifest/ / authorable-surface/ carry no enum values (0 files under packages/spec outside src/ name QUOTA_EXCEEDED) — unchanged, the enum-value-narrowing class; TypeScript Type Check green. The .d.ts union does move: TS2367 on === against StandardErrorCode and ErrorCode, TS2820 on assignment, QUOTA_EXCEEDED legs clean (tsc against head src) — the changeset states it.

Gate script edit. New anchor (\n\]) reads 49 == the array block; the old \]\); reads 64 raw / 49 unique on the head file (runs on through HttpStatusErrorCodeMap and standardErrorCodeForHttpStatus to RetryStrategy's ]);) — node on the head source. --self-test 60/60 at head. A5 (old anchor restored): case 27 an options object after the array adds no member… red 1/60; error-catalog-docs.test.ts red 2/5 (agrees with the enum, advertised code count); control 27b green under both. Real. Baseline: −1 row, the retired code only; the QUOTA_EXCEEDED row stays. check-dispatcher-error-vocabulary.mjs parseStandardCodes keeps the lazy \]\) anchor and over-reads the same 64; Set-valued, it equals the 49 true members exactly at this head (no extras, none missing) — exact, fragile.

Pins (vitest --project local, 8/8 at head; every leg restored, git status clean): A1 drop the errors.zod.ts map → 2 red (door 1 ×2); A2 drop the ledger map → 2 red (door 2 ×2); A3 drop the contract.zod.ts map → 1 red (door 3); A4 re-plant the member → 6 red, typo and QUOTA_EXCEEDED pins green; A6 (mine) re-add the hand-written catalogue entry with the count left at 51 → doc test red (expected 51 to be 52). Uncovered: package-local only, no tree-scoped absence pin (dev deviation, same call as #19937); the reference pages rest on check:docs (running); the doc page's absence is held by the count alone at the unit layer (re-add + bump to 52 against the gate's baseline arm NOT MEASURED); makeApiErrorSchema([RETIRED]) re-admits the spelling (extraCodes is the caller's ledger, measured SUCCESS) and a future ERROR_CODE_LEDGER row with it is unguarded — as for the #9266 batch precedent.

Sentences that ship. Changeset: minor, BREAKING, FROM → TO, one-line fix, one ADR-0087 marker, Clause-②: no; "occurred only in the enum declaration, the hand-written catalogue page, the generated reference pages and the unpinned-status baseline" true (the 8 base hits); "pinned objectui checkout does not name it" true; "429" true (base heading + comment); TS2367 true; out-of-repo NOT MEASURED stated. Refusal message: "17.5.0" follows the in-tree convention for pending removals (view.zod.ts tombstones at 17.5.0; package 17.4.0, major refused in the window) — true only if the next release is 17.5.0; ADR-0049 / ADR-0112 titles match; shape follows the crypto.hash enum-value precedent. D3 entry: batch #126 item 2, the 2026-09-24 narrowing and 「其他同意」 match the card; surface has no backtick; the registry.ts copy is byte-identical and sorted between its neighbours; the cited precedent id exists (registry.ts:11949). Docblocks: the three-door count and "not re-exported by api/index.ts" true (index exports errors.zod / contract.zod / error-code-ledger.zod only); HookBodyCapability (hook-body.zod.ts:59) and managedBy: 'system' (object.zod.ts:1426) exist; "a hosted AI agent route emits it" is carried from 5807013768, NOT MEASURED (cloud unreadable). error-catalog.mdx "51 … reachable on the wire": derived by the doc test at head, 5/5 (ran). PR body: the ablation claims all re-measured and hold; "suites of the vocabulary's consumers green" not measured by me (CI: Test Core 2–6 green, 1/6 red on an unrelated signature, below). Shipped diff (14 files) swept for every model-identifier spelling: 0 hits; body and trailers carry product / attribution names only.

② Semver level

@objectstack/spec: minor + BREAKING banner + FROM → TO + one-line fix + exactly one ADR-0087 marker (D3 semantic, no conversion) — the shape ruling 5651023241 item 3 ordered and #19937 landed with; check-changeset-no-major refuses major in the launch window. Correct. Clause-②: no: the criterion (execution-duties.md:65 — does the card widen the accept set or expand the public surface?) reads no; the map is not exported; matches 5807013768 item 3 and #19937. AGENTS.md:1085 allows an optional arm, (narrowing) = BREAKING; with no arm check-changeset-no-major reads the level axis as not-declared (exit 0) rather than discharged. Not a false declaration; the exact spelling is Clause-②: no (narrowing). Check Changeset green ×3.

③ Boundary flags

Blocking: none.
Non-blocking: (a) Clause-②: no without the (narrowing) arm; (b) check-dispatcher-error-vocabulary.mjs keeps the lazy anchor — exact today only because it is Set-valued, carrier none; (c) the doc-page absence is held by the count alone at the unit layer; (d) re-admission of the spelling through a downstream ledger or a new ERROR_CODE_LEDGER row is unguarded (precedent-consistent); (e) the error-handling.mdx QUOTA_EXCEEDED sentence — separate card; (f) landing waits on a re-run of the ledgered flake and on Lint & Repo Gates.

Implemented-by: claude/issue-17158-export-job-family-retired
Reviewed-by: session_01Sfe5YjBLwB9J3y8fvm2xq1 — at-tier review subagent spawned by the domain:spec#5 seat

VERDICT: PASS


Generated by Claude Code

This was referenced Sep 24, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

CI reading on head e9f9b060f3: Test Core (1/6) red at 「Run this shard's tests」, and the branch now conflicts with main · director seat · 2026-09-27T05:57Z

Director seat, summon #30 (续), session_01AsCNgFBs8HCjwhyHQsFbx3, on the maintainer's 「12小时之前的pr什么情况帮我跟进到合并」. ⛔ Not a claim; this PR and card #17707 stay with their holder.

  • Check-runs on e9f9b060f3 (2026-09-24T07:56Z): 38 success, 9 skipped, 2 failure. Test Core (1/6) failed at step 11 「Run this shard's tests」 (job 107539910149), and the Test Core roll-up is red with it. The job log is not readable from this container (the log blob answers 403 through the proxy), so the failing test is not named here. Control: origin/main 8d1f7ab785 runs all six Test Core shards green, so the red is either this diff's or a stale-base artefact; the base b7c792bd2e is three days behind main.
  • git merge-tree --write-tree origin/main refs/pull/19957/head at origin/main 3bd28e2b2: one conflict, content/docs/references/api/contract.mdx, a generated reference page.
  • The contract review PASS 5810313042 is on this head; it carries over a pure-regeneration hop only.
  • What lands it: one round on the branch — merge origin/main, regenerate contract.mdx with the repository's tooling, run this shard's package set locally to name the red and fix it if it is this diff's, push (with a Regen-provenance: line when the resolution is regeneration only); then ready and auto-merge. The holder's last own output on this lane is 2026-09-24; on the maintainer's word the director seat dispatches that round.

Generated by Claude Code

…regenerate contract.mdx)

Brings the branch up to origin/main 03d6cb0, which already carries the VALUE_TOO_LONG / VALUE_TOO_SHORT catalog edit.
The os-regen driver deferred content/docs/references/api/contract.mdx and
content/docs/references/api/error-code-ledger.mdx (both sides edited them);
this commit carries origin/main's side of both, per scripts/pm/os-regen-merge.sh
step 2, and the regeneration of the merged tree is the next commit.
content/docs/api/error-catalog.mdx merged clean: main's VALUE_TOO_LONG /
VALUE_TOO_SHORT entries plus this branch's removal and count line.

Claude-Session: https://claude.ai/code/session_01AsCNgFBs8HCjwhyHQsFbx3
Co-authored-by: Claude <noreply@anthropic.com>
…merged tree

Discharges the os-regen deferral the merge commit recorded. Generated by
`pnpm --filter @objectstack/spec build` then
`pnpm --filter @objectstack/spec gen:docs` (packages/spec/scripts/build-docs.ts);
a second gen:docs run is a no-op and check:docs reports 226 files in sync.
The regenerated hunks are this branch's own on origin/main's side:
the CONCURRENT_LIMIT_EXCEEDED rows leave the StandardErrorCode lists and the
two enum summaries count one member fewer.

Claude-Session: https://claude.ai/code/session_01AsCNgFBs8HCjwhyHQsFbx3
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Patch round on this PR (director dispatch, run by session session_01AsCNgFBs8HCjwhyHQsFbx3) · 2026-09-27T12:05Z

Regen-provenance: 932cbd2 — content/docs/references/api/contract.mdx regenerated by pnpm --filter @objectstack/spec build && pnpm --filter @objectstack/spec gen:docs (packages/spec/scripts/build-docs.ts), committed as 628738d; increment vs origin/main unchanged (14 files, +314/−23)

What was done (head e9f9b060f3 → 628738ddf4, pushed as a fast-forward; no rebase, no force-push, no amend)

  • 932cbd208c merges origin/main at 03d6cb04ae (which already carries PR docs(error-catalog): VALUE_TOO_LONG / VALUE_TOO_SHORT name the field-level codes that really arrive #20004). The os-regen driver deferred the two generated pages both sides had edited, content/docs/references/api/contract.mdx and content/docs/references/api/error-code-ledger.mdx; the merge commit carries origin/main's side of both (the scripts/pm/os-regen-merge.sh order: merge committed first, regeneration second). content/docs/api/error-catalog.mdx text-merged clean: main's VALUE_TOO_LONG / VALUE_TOO_SHORT entries plus this PR's count line and CONCURRENT_LIMIT_EXCEEDED removal. A driver-free probe (bare clone, no driver registered) names contract.mdx as the only conflicting path, as the PM measured.
  • 628738ddf4 is the regeneration and discharges the pre-commit deferral ("all artifacts current, marker cleared"). A second gen:docs run left both blob hashes unchanged, and check:docs reports 226 generated files in sync.
  • The increment is the same as before. git diff 03d6cb04ae 628738ddf4 gives 14 files, +314/−23, and git diff b7c792bd2e e9f9b060f3 (the old base) gives the same. The per-file git patch-id values match for 13 of the 14 files. The exception is contract.mdx, where the generated enum summary now reads +322 more → +321 more (previously +331 → +330), because main's own vocabulary shrank in the meantime.

The Test Core (1/6) red is named, and it is not this diff's own. Job 107539910149 finished at 2026-09-24T07:56Z. Its log (read through the job-logs API) shows @objectstack/spec#test:repo with 1 of 35 files failed and 597 of 597 tests passed. The one failure is a suite-load error:

  • FAIL repo src/api/error-catalog-docs.test.ts
  • Error: ENOENT: no such file or directory, stat '…/packages/spec/tsup.config.bundled_y2twvadpiyp.mjs'
  • at walk scripts/check-error-status-conformance.mjs:1832, ← scanSources :1847, ← deriveWireFace :1892, ← error-catalog-docs.test.ts:55

walk() calls readdirSync and then statSync, one entry at a time. tsup writes and deletes its transient tsup.config.bundled_*.mjs beside the config while @objectstack/spec#build runs in the same turbo invocation, so the stat can land on a name that no longer exists. This PR does not touch walk(). The function is byte-identical on origin/main (03d6cb04ae, lines 1798–1805), and it was last changed there by bac22eb73a. So this is a pre-existing race, not a stale-base artefact the merge resolves, and nothing was changed for it here. It goes to the card as an out-of-scope finding.

Local runs (packages/spec; exit codes read from the verify-lock VERDICT lines)

  • pnpm --filter @objectstack/spec test:repo on 628738ddf4: exit 0, 32 files, 586 tests passed.
  • pnpm --filter @objectstack/spec test:repo on a driver-free rebuild of the red run's merge ref (e9f9b060f3 + main 3b5607019f, the main tip when that run started): exit 0, 35 files, 602 tests passed. The race does not show on a sequential run.
  • Race probe: a sibling process creates and deletes packages/spec/tsup.config.bundled_probe.mjs in a loop while deriveWireFace() scans. 2 of 4 scans threw the same ENOENT … stat at statSync. The one control scan without the churn completed.
  • pnpm --filter @objectstack/spec test exit 0 (544 files, 15973 passed, 2 todo). pnpm --filter @objectstack/spec typecheck exit 0. pnpm --filter @objectstack/spec check:generated exit 0 (15 of 15 artifacts up to date).
  • node scripts/check-error-status-conformance.mjs --self-test exit 0 (60 cases), and the gate itself exit 0.
  • Also exit 0: check:nul-bytes, check:quick-reference-counts, check:docs-spec-enumerations, check:merge-driver, check:error-code-casing, check:migration-registry, check:spec-changes, check:upgrade-guide, check:error-code-provenance, and check-adr-0087-registration --base origin/main.
  • This is a declared narrowing to the merge and the regeneration: dispatch-gates --ran counts 14 of the 133 families derived for the increment as run here. The rest belong to this head's CI.

Not changed in this round: the two QUOTA_EXCEEDED lines that spec seat 1's pointer 5855175355 measured. The **Cause:** line is not inside this PR's catalog hunk: it is line 508, and the hunk is @@ -509,11 +509,6 @@. The **Fix:** line is in that hunk only as unchanged context. Correcting either line would move the increment and reopen the review. The correction belongs to whoever owns QUOTA_EXCEEDED's producer story.

For the review record: the pure-regeneration carry test (unexplainedPathsBetween in scripts/pm/record-recognisers.mjs) was run on the committed trees e9f9b060f3 → 628738ddf4 against origin/main 03d6cb04ae. It names 3 paths: content/docs/api/error-catalog.mdx, packages/spec/src/api/error-code-ledger.zod.ts and packages/spec/src/migrations/registry.ts. Main moved all three, this PR also edits them, and they text-merged clean with unchanged per-file patch-ids. The line above is therefore written in the dispatch's shape, not the recogniser's RECORD · OLD → NEW shape. Whether PASS 5810313042 carries to 628738ddf4 is for the reviewing seat to decide. The draft flag, labels and auto-merge were not touched.


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 628738ddf436e3a4eda155861318821e14de2ded

① Derived judgments

Scope of this record: the base-merge hop e9f9b060f3 → 628738ddf4 — merge commit 932cbd208c of origin/main 03d6cb04ae (which already carries PR #20004), then the generator-only commit 628738ddf4. The increment was reviewed at e9f9b060f3 by record 5810313042 (PASS); its derived judgments are adopted here unchanged, because the increment is unchanged, measured first-hand by the director seat on the fetched refs:

  1. 14 files, +314 / −23 against the old base b7c792bd2e and against the new merge base 03d6cb04ae; the file set is identical.
  2. For 13 of the 14 files the PR's own -U0 changed-line multiset is byte-identical across the hop, including the three hand-written paths the carry test names (content/docs/api/error-catalog.mdx, packages/spec/src/api/error-code-ledger.zod.ts, packages/spec/src/migrations/registry.ts), which main also edited and git text-merged clean. The one differing file is the generated content/docs/references/api/contract.mdx: its enum-summary count moved from 「+331 more」 / 「+330 more」 to 「+322 more」 / 「+321 more」 because main retired further codes in between; the PR's own delta there, one code fewer, is the same.
  3. Commit 628738ddf4 touches only the two generated pages contract.mdx and error-code-ledger.mdx, written by gen:docs; a second run is a no-op; check:generated reads all 15 artifacts up to date; check:migration-registry is current.
  4. The Test Core (1/6) red on e9f9b060f3 is named and is not this diff's: src/api/error-catalog-docs.test.ts failed at suite load on ENOENT … stat packages/spec/tsup.config.bundled_*.mjs inside walk() of scripts/check-error-status-conformance.mjs (readdir, then stat per entry, with no tolerance for an entry vanishing in between) while @objectstack/spec#build wrote its transient config in the same turbo run; 597 of 597 tests had passed. walk() is untouched by this PR and byte-identical on main; the shard is green on 628738ddf4. Filed as its own card by the director seat.
  5. Mergeability: git merge-tree --write-tree origin/main refs/pull/19957/head at origin/main 805af4f290 exits 0 with no conflict. CI on 628738ddf4: 33 success, 2 skipped, 0 red.

② Semver level

Unchanged from 5810313042: feat(spec)! with the retirement changeset and the migration entry registered (check-adr-0087-registration.mjs --base origin/main exit 0); the PR body's Clause-② line stands as reviewed there.

③ Boundary flags

Implemented-by: claude/issue-17158-export-job-family-retired
Reviewed-by: session_01AsCNgFBs8HCjwhyHQsFbx3

VERDICT: PASS

Rendered at tier by the director seat (summon #30 续) on the fetched head. Not a governed path; the landing pre-checks are met on this head (fresh PASS, CI green, no conflict), so ready and auto-merge follow through the relay.


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 27, 2026 12:12
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 27, 2026
Merged via the queue into main with commit 6d2571f Sep 27, 2026
37 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-17158-export-job-family-retired branch September 27, 2026 12:43
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 28, 2026
…level codes that really arrive (objectstack-ai#20004)

Fixes objectstack-ai#19879
Clause-②: no

## What

`content/docs/api/error-catalog.mdx`, the `VALUE_TOO_LONG` and
`VALUE_TOO_SHORT` entries only. Both entries gave a live cause and a
fix, as if a client could branch on the code. No producer emits either
code. Both entries now say so, following the shape the `INVALID_FORMAT`
entry got in objectstack-ai#19878: the code is reserved, no route emits it today, and
a length miss arrives as a field-level `max_length` / `min_length`
entry. Each entry now says where that entry rides on each path: `400
VALIDATION_FAILED` with `fields[]` for record writes and Zod-parsed
request bodies (top-level on `/data`, under `details` through the
runtime dispatcher), and `400 SETTINGS_VALIDATION` with
`details.fields[]` for a settings write. The fix line names both
envelopes. Both entries stay because the enum still declares the codes.

## Evidence (measured on `origin/main` `e8f163fc`)

- **No producer.** `git grep -nE 'VALUE_TOO_(LONG|SHORT)'` outside tests
hits only the enum members `packages/spec/src/api/errors.zod.ts:58-59`,
the baseline rows `scripts/error-status-unpinned-baseline.json:27-28`,
this page, the generated `content/docs/references/**` pages, and
ADR-0114, which records these members as a known wart. A grep for other
spellings (`VALUE_TOO`, `TOO_LONG`, `TOO_SHORT`) finds only the
unrelated `PASSWORD_TOO_SHORT` in a plugin-auth test. Positive control:
`INVALID_FORMAT` hits `errors.zod.ts:57`.
- **Record writes.**
`packages/objectql/src/validation/record-validator.ts:695-699` sends
`fail('max_length', { maxLength, actual })` and `fail('min_length', {
minLength, actual })` for `BOUNDED_STRING_FIELD_TYPES`.
`buildFieldError` puts that object on the wire as `fields[].constraint`,
and the envelope's top-level code is `VALIDATION_FAILED`
(`VALIDATION_FAILED_CODE`, `:195`).
- **Zod-parsed request bodies.**
`packages/spec/src/api/zod-issues-to-fields.ts:74-81` maps `too_small` /
`too_big` to `min_length` / `max_length` when the value is not a number,
bigint, date, array or set. Numbers and dates map to `min_value` /
`max_value`, and arrays and sets to `min_items` / `max_items`. That is
why the page says "a string". The REST routes that use this send `code:
'VALIDATION_FAILED'` (for example
`packages/rest/src/rest-server.ts:8960-8963`).
- **Settings writes (a different envelope).**
`packages/services/service-settings/src/settings-service.ts:397-400`
returns `max_length` / `min_length` with `constraint { minLength?,
maxLength?, actual }` for a settings value outside its declared length
window. `:2145` pushes it into the errors list, and `:2167` throws
`SettingsValidationError` (`settings-service.types.ts:583-584`, `code =
'SETTINGS_VALIDATION'`).
`packages/services/service-settings/src/settings-routes.ts:215-217`
serves it as `sendError(res, 400, 'SETTINGS_VALIDATION', …, { details: {
namespace, fields } })`, and `packages/types/src/response-envelope.ts`
`sendError` writes that as `{ success: false, error: { code, message,
details } }`. So a settings length miss is top-level
`SETTINGS_VALIDATION` with the entry in `error.details.fields[]`, not
`VALIDATION_FAILED`.
- **Where `VALIDATION_FAILED` puts the list.** On the `/data` routes it
is flat (`packages/rest/src/error-response.ts:1152-1160`,
`mapDataError`: top-level `fields`). Through the runtime dispatcher it
is nested (`packages/runtime/src/dispatcher-plugin.ts:645`,
`validationFailureDetails`: `details.fields`). The page's own
`VALIDATION_FAILED` callout already documents both, so the entries link
to it rather than restating it.
- The field-level spellings are the same on all three paths:
`max_length` / `min_length`. The envelope differs: `VALIDATION_FAILED`
for records and Zod bodies, `SETTINGS_VALIDATION` for settings.

## Not touched

- The wire-count sentence at `:6`, the `CONCURRENT_LIMIT_EXCEEDED` entry
and `scripts/error-status-unpinned-baseline.json` are not changed. Open
PR objectstack-ai#19957 edits them.
- `VALUE_OUT_OF_RANGE` and `MISSING_REQUIRED_FIELD` are not changed.
Both have producers.
- The page's frontmatter and headings are not changed.

## Changeset

None. This is a docs-only change under `content/docs/`, and no published
package's `files[]` changes, so it falls under `skip-changeset`. The PM
seat applies the label.

## Gates

Derived with `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands` from the merge-base change set (1
path). All 41 derived commands ran on HEAD `37431df1` and exited 0. The
derivation is unchanged from the first head, `ab8b19ea`. They include
`pnpm check:doc-authoring`, `pnpm check:doc-anchors`, `pnpm
check:nul-bytes`, `pnpm check:error-status-conformance`, `pnpm
check:docs-spec-enumerations` and `pnpm --filter @objectstack/spec run
check:docs`. The prerequisite closures (`lint` / `formula` /
`client-react`, which pulls in `spec`) were built under the verify lock
first. Reconciliation with `--ran` and the recorded exit codes: `41
derived, 41 run, 0 NOT-MEASURED, 0 UNRUN` (a derived zero). No package
source changed, so no package tests or typecheck are owed.

## Rework (PM review)

The first head said every length miss arrives in `VALIDATION_FAILED`,
which is wrong for settings writes. The second commit, `37431df1`, names
`SETTINGS_VALIDATION` + `details.fields[]` for that path in both the
Cause and the Fix lines. The `#validation_failed` link resolves:
`check:doc-anchors` passes, with 377 fragment links resolved.

## Acceptance notes

- The page's intro counts "52 error codes reachable on the wire", but
the page carries 53 code headings, and several of them are reserved
codes with no emitter (`INVALID_FORMAT`, `INVALID_REFERENCE`, and now
these two). Whether that count should include reserved codes is a
question for the line PR objectstack-ai#19957 already edits. It is not changed here.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01VDtqoecgES7ScQYGbFVDRv)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 28, 2026
…gent chat route (objectstack-ai#20220)

Fixes objectstack-ai#19958
Clause-②: no

Docs only. One section of
`content/docs/protocol/kernel/error-handling.mdx` is rewritten: the
`QUOTA_EXCEEDED` entry. No runtime, spec or error-code-ledger change.
`QUOTA_EXCEEDED` stays registered, as the narrowed ruling on objectstack-ai#17707
(`5807013768`) keeps it.

## What the page said, and what it says now

Removed (it was false):

> **Envelope:** none — no producer emits it
>
> **Not emitted today.** `QUOTA_EXCEEDED` is a registered member of the
standard error-code catalog (…), but no ObjectStack producer emits it.
No response carries this code, and none carries a quota `details` bag —
do not write a client branch against it.

Added (each sentence rests on a measurement below):

> **Envelope:** nested — emitted by one route only, the ObjectOS agent
chat route, and only to a JSON-mode request
>
> **One producer.** `POST /api/v1/ai/agents/:agentName/chat` answers
`QUOTA_EXCEEDED` when the deployment has switched on its per-user daily
chat-turn cap and the calling user has spent that cap. […] No other
route emits this code.
>
> **JSON mode only.** The route streams by default. A request whose
`stream` flag is absent or `true` gets the refusal as an ordinary
assistant text message on HTTP 200, and no error code reaches it. Only a
request with `stream: false` gets the 429 body below.
>
> `error.details.resetAt` is an ISO-8601 timestamp: the moment the cap
lifts. It is the only recovery time the response carries. The route sets
no `Retry-After` header and sends no `retryAfterSeconds`.

Kept as it was: the SMS sentence (`the SMS daily quota answers
TOO_MANY_REQUESTS`) and the pointer to `RATE_LIMIT_EXCEEDED` for request
pacing. The SMS sentence still holds on `main`:
`packages/services/service-sms/src/sms-daily-quota.ts:85` is
`SMS_QUOTA_EXCEEDED_CODE = 'TOO_MANY_REQUESTS'`. The only change there
is its first word, "Quota enforcement that does exist" → "Other quota
enforcement".

## The producer, measured (cloud `main` `48d70663`)

The producer and reader are cited here, not in the doc, following the
dispatch's route.

- `packages/service-ai/src/routes/agent-routes.ts:572-575`: `return
sendError(429, 'QUOTA_EXCEEDED', message, { details: { resetAt:
decision.resetAt }, category: 'rate_limit' })`.
- `agent-routes.ts:527`: `const wantStreamMode = body.stream !==
false;`. At `:552-565` a streaming request gets `status: 200` with the
copy as one `text-delta` part, so there is no code on that path.
- `packages/service-ai/src/routes/envelope.ts:188-198`: `sendError`
writes `{ success: false, error: { code, message, httpStatus: status,
...extra } }`.
- `packages/service-ai/src/plugin.ts:1154-1159`: the gate exists only
when the deployment sets its daily-turn knob to a positive number, and
the only implementation wired is `DailyMessageQuota`. Its refusal always
sets `resetAt` (`quota/agent-chat-quota.ts:92-96`).
- No `Retry-After` on this path. `sendError` returns status and body
only, and no cloud writer adds that header to this route.
- Pinned in cloud by
`packages/service-ai/src/__tests__/agent-error-envelope.conformance.test.ts:300-315`
(status 429, code, `details` equal to `{ resetAt }`).
- Control: objectstack `main` has no producer of this code. `git grep
QUOTA_EXCEEDED` at `e7f69dbb` gives 40 lines: docs, generated
references, the registration in
`packages/spec/src/api/errors.zod.ts:106`, a ledger test, a status
baseline, and the SMS `SMS_QUOTA_EXCEEDED_*` constants whose value is
`TOO_MANY_REQUESTS`.

## The readers, measured

objectui, pin `f8a9d0fb` (current `.objectui-sha`) and `main`
`25c7d584`. `tool-display.ts`, `tool-display.test.ts` and
`useObjectChat.ts` are byte-identical between the two.

- `packages/plugin-chatbot/src/tool-display.ts:314-326` and `:336`:
`parseAiQuotaError` deliberately does NOT recognize `QUOTA_EXCEEDED`.
- The 429 is routed by `isUnsentSendError` (`:404`) and
`isRateLimitError` (`:423`). They key on the HTTP status tagged by
`sendAwareFetch`, or on a raw-text regex.
- objectui reads no field of this body: not `code`, not
`details.resetAt`, not `category`. The card's "objectui branches on it
(`error.details.resetAt`, `category`)" comes from a comment at `:317`
that describes the producer. No read in objectui matches it.
- objectui's chat defaults to `streamingEnabled = true`
(`useObjectChat.ts:768`, sent as `stream` at `:931`). Its default path
therefore receives the in-band text on HTTP 200. The 429 branch is
reached only when a chatbot schema sets `streamingEnabled: false`, and
it is pinned by fixture (`tool-display.test.ts:365-390`).

`@objectstack/client`, this repo at `e7f69dbb`:

- `client.ai.agents.chat()` sends `stream: false`
(`packages/client/src/index.ts:6648-6653`). It throws an error carrying
`code`, `category`, `details` and `httpStatus` (from `res.status`)
(`:7376-7405`).
- `chatStream()` sends `stream: true` (`:6662-6667`).

Field mismatches (dispatch Zone 2 item 2):

- **Sent by the producer, ignored by the reader:**
- objectui ignores all of `code`, `details.resetAt`, `category` and
`httpStatus`, and reads `message` only through the regex probe.
  - The SDK reads every sent field.
- **Read by a reader, not sent:** the SDK's `error.retryable`, which is
`undefined` here.

## Choices this PR settled

- **"ObjectOS", not "hosted".** Triage's wording was 托管的 AI agent 对话路由.
The docs' own term for where this route lives is the callout in
`content/docs/ai/index.mdx`: the in-product chat runtime "ships in
**ObjectOS**". The section links there.
- **The example shows `httpStatus: 429`.** Triage ruled 「⛔ 不要自行补充字段」.
`httpStatus` is not an added field: the producer's `sendError` writes it
on every body (`envelope.ts:198`), and leaving it out would misdescribe
the wire. The field list under the example names only what a client acts
on: `code`, `details.resetAt`, `category` and `message`.
`retryAfterSeconds` and `Retry-After` are named only as absent.
- **The example `message` is illustrative.** It is the English half of
`DailyMessageQuota`'s copy with an example limit of 50. The real copy is
bilingual, Chinese first. The doc says to display `message`, never to
parse it.
- **The "JSON mode only" paragraph was not in triage's text.** It is
measured and it changes what a client can rely on: a streaming client
never sees the code. Dispatch Zone 2 item 3 asked for the one emitting
route, not a platform-wide promise. This paragraph narrows that route to
its one mode.

## Acceptance notes

- **`content/docs/api/error-catalog.mdx` disagrees with the rewritten
section. It is not edited here (claim file surface).**
- `:507-510`: the entry sends readers to "check `retryAfterSeconds`",
which this producer never sends; `details.resetAt` is the field. Its
cause line says "API usage quota for the current period", while the only
producer is a per-user daily chat-turn cap.
  - The 429 row at `:913` (`rate_limit`) agrees.
- Open PR objectstack-ai#19957 has that entry only as unchanged context, and PR objectstack-ai#20170
does not touch it. Both leave the disagreement in place.
- **The page's general "Nested envelope" field list** says no
route-module body carries `httpStatus`, and it does not list `category`.
That is true of the writer it names,
`packages/types/src/response-envelope.ts`. The ObjectOS route writes
through its own `sendError`, which carries both. Not edited.
- **The `fetchWithRetry` example under `RATE_LIMIT_EXCEEDED` retries any
429 by `Retry-After`.** Against this 429 it would wait zero seconds and
retry. The new section warns against that. The loop under "Implement
Retry Logic" keys on the code and lets `QUOTA_EXCEEDED` throw, which is
right. Its comment "it is present on every 429" is inaccurate for this
code's 429, but the comment sits inside the `RATE_LIMIT_EXCEEDED`
branch, so behaviour is unaffected. Carrier: none.
- **Card pin moved.** The card cited objectui `62597c5880`;
`.objectui-sha` is now `f8a9d0fb`. The cited lines are unchanged at
both.

## Verification (head `6e0bb38e`)

- **Derived gate set.** `node scripts/pm/dispatch-gates.mjs --commands
--repo objectstack-ai/objectstack` gives 41 commands on the actual
changed paths, identical to the dispatch lead.
- **Readings.** All 41 ran, every exit code was recorded to disk before
it was read, and all 41 read exit 0.
- **Prerequisite refusals.** Four lines first refused with PREREQUISITE
NOT MET (exit 3): `check:doc-formula-expressions`,
`check:doc-security-posture`, `check:skill-examples` and
`check:docs-transcript-drift`. `check:docs` was held back until spec was
built. None of those refusals counted as a reading. The lines were
re-run after builds under `scripts/pm/os-verify-lock.sh`:
- `turbo run build` over `@objectstack/lint...`, `@objectstack/formula`
and `@objectstack/spec`: VERDICT command-exit 0.
- The same over `@objectstack/client-react...` and
`@objectstack/client...`: VERDICT command-exit 0. `check:skill-examples`
needed this second build because it refused again, on missing client
declarations.
- `check:skill-examples` then read "259 prose examples type-check across
3 surface(s)".
- **Reconciliation.** `dispatch-gates.mjs --ran ran.list` prints: "✓
dispatch-gates --ran: 41 derived famil(ies) accounted for — 41 run, 0
NOT-MEASURED (a DERIVED zero — all 41 recorded an exit code and none of
them is 3)."
- **Control bytes.** `pnpm check:nul-bytes` exits 0, and the
control-byte self-scan of the edited file has no hits.
- **Tests.** No test and no pin was added or changed: the change is
prose only, and nothing is accepted or refused differently.
- **Changeset.** `skip-changeset`: 0 of 69 non-private workspace
packages list a `files[]` entry reaching `content/docs`.
- **Newer `main`.** `origin/main` moved to `7e7fab73`, and no commit
since BASE `e7f69dbb` touches either doc.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01Rjy9MeetSfq34PKn81CRiN)_

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

1 participant