Skip to content

docs(spec): SYNC_ARCHITECTURE stops teaching retryConfig as the rate-limit remedy - #18979

Merged
os-bill merged 1 commit into
mainfrom
claude/issue-18794-retryconfig-declared-unimplemented
Sep 18, 2026
Merged

os-bill merged 1 commit into
mainfrom
claude/issue-18794-retryconfig-declared-unimplemented

Conversation

@os-bill

@os-bill os-bill commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator

Fixes #18794

Clause-②: no

What changed

packages/spec/docs/SYNC_ARCHITECTURE.md taught authors that a rate-limited upstream is answered by retryConfig, printed its retryableStatusCodes defaults (429 included), and listed it as a reason to pick L3 — while nothing reads the key. Five passages in that one file now say what is measurably true today: declared but currently unimplemented, each pointing at packages/spec/liveness/connector.json.

This is option A from the card, and only A. No declaration, schema or accept set is touched, so the ADR-0049 ruling on these keys is deliberately not prejudged. The keys are not described as retired (they are still declared and still parse, so an author writing them still sees no error) and not as the host's job (falsified below).

Site (line at base 106717c3aa) Was Now
:157 blockquote "what L3 does declare for a rate-limited upstream is retryConfig" declared-but-unimplemented, ledger cited, host-seam falsification stated inline
:299 example comment "Retry Configuration — for the connector's own outbound requests" DECLARED BUT CURRENTLY UNIMPLEMENTED, ledger cited
:312 example timeouts bare connectionTimeoutMs / requestTimeoutMs annotated declared-but-unimplemented, ledger cited
:336 Best Practices "retryConfig handles the 429 you get for exceeding a limit" it does not; ledger cited; retrying is the provider's to implement
:360 decision row "Yes → L3 (Connector) — retryConfig, health.circuitBreaker" "Not a reason to pick a level"; the row's existing #4911 sentence is left byte-identical

Measurements — re-taken on this branch, not inherited

Consumer probe, fold-proof predicate, firing control in the same run. Comment leaders are stripped first, then every whitespace run (newlines included) is collapsed and glued to the access punctuation, so a folded access cannot hide from it. Read-shaped access only (x.KEY, x?.KEY, x["KEY"], destructure). 8763 tracked files, tree 106717c3aa:

.retryConfig          OUTSIDE packages/spec   0 hits in any packages/ or examples/ file
                      (2 hits total, both in the GENERATED reference page
                       content/docs/references/integration/connector.mdx)
.connectionTimeoutMs  OUTSIDE packages/spec   0
.requestTimeoutMs     OUTSIDE packages/spec   0
.providerConfig       OUTSIDE packages/spec   15 hits across 9 files   (FIRING CONTROL)
                      connector-mcp / connector-openapi / connector-rest providers,
                      service-automation plugin.ts, app-showcase tests

Second control, reachability. A bare-identifier census in the same run proves the scan surface reaches the files where these keys actually live: connectionTimeoutMs occurs in 9 files outside packages/spec (the four connector packages, plugin.ts, a test) and requestTimeoutMs in 8 — every one of them a WRITE of the literal into a def so it satisfies the post-parse type, never a read. So the zeros read as "no reader", not "the probe never looked".

Host seam, re-read verbatim. packages/spec/src/integration/connector-provider.ts:57 declares ConnectorProviderContext with exactly name, label, description?, icon?, type, providerConfig, auth?, loadPackageFile?. None of the three keys is among them, so a provider factory is never handed them and has no way to honour them. "Left to the host" is false.

Changeset, measured rather than assumed. The trigger is "can a consumer read a change", not "did bytes move". npm pack --dry-run --json --ignore-scripts on packages/spec: 275 entries, docs/SYNC_ARCHITECTURE.md absent, and zero docs/ paths at all, while the positive controls liveness/connector.json and src/integration/connector.zod.ts are both present. files[] is dist, json-schema, liveness, prompts, llms.txt, README.md, src/**/*.zod.ts, CHANGELOG.md, api-surface, spec-changes.json — no docs entry. The edited file publishes to nobody, so skip-changeset.

Verification

  • Gate families. node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 48 commands against commit c4c09b853e. All 48 were run, exit codes landed to disk first, then reconciled: "48 derived, 48 run, 0 NOT-MEASURED, 0 UNRUN" — a derived zero, every family carrying a recorded exit code. Four of them (check:dts-closure, check:dual-build-cjs-loads, check:lean-entry-closure, check:sourcemap-no-sources-content) first exited 3 = PREREQUISITE NOT MET, which is neither a pass nor a failure; after pnpm build (73/73 tasks successful) all four re-ran green.
  • DARK leg. pnpm --filter @objectstack/spec test — 488 test files, 14182 tests, all pass. pnpm --filter @objectstack/spec check:generated — "All 15 generated artifacts are up to date". pnpm --filter @objectstack/spec typecheck — clean.
  • This document sits behind a compile gate. packages/spec/src/integration/connector-author-shape.test.ts extracts the file's typescript-fenced blocks and compiles them verbatim against the real schema, pinning the fence count at 2, the elision-sketch count at 0, and every block to compile clean. 14/14 pass with the edit in place.
  • Reverse verification, direction stated before running. Renaming the edited block's retryConfig key to retryConfigBogus had to turn that gate RED on the block I touched. Observed: RED, TS2561 Object literal may only specify known properties — TypeScript's did-you-mean form of the excess-property error, since the bogus name is one edit from the real one — on the assertion "L3 example #0 must compile clean"; 1 failed, 13 passed. Mutation landing was proved on disk (anchor 1 to 0, blob bd3c6b895c2a to c02cf5c9c5e3) and the restore was proved independently of the tool's own claim: blob back to bd3c6b895c2a equals HEAD, and git diff HEAD empty. This is what shows the comments added inside the fence are inside the compiled region and compile clean, rather than sitting outside it.
  • Lint narrowing, declared. (1) The population is read from eslint's own config: every files: selector in eslint.config.mjs targets {ts,tsx,mts,cts,js,jsx,mjs,cjs}, and no markdown selector or processor exists. (2) The count is read from --format json: the one changed file yields "File ignored because no matching configuration was supplied." with 0 errors, so 0 files of the lint population are touched. (3) Invariance: the file is outside the lint population entirely, so this diff cannot move any untouched file's verdict.

One widening the reviewer should confirm

The acceptance line for :360 reads "it must not present an unimplemented key as a selection criterion". That row paired retryConfig with health.circuitBreaker, and the :157 blockquote pairs them too. health.circuitBreaker is unimplemented on the same evidence: packages/spec/liveness/connector.json records every one of its sub-keys as dead, and the same probe run shows 0 read-shaped consumers outside packages/spec in any packages/ or examples/ file (its only hits are the generated reference page and content/docs/references/system/cache.mdx, which is the CACHE's own breaker, a different subject). Correcting only the retryConfig half would have left the row still presenting an unimplemented key as a selection criterion. So both halves are corrected, citing the ledger's existing verdict rather than making a new one. Flagged because it is one key wider than the card's three.

Acceptance notes

Noted, not filed. The first two are real sites carrying the same prescription, each behind a read-only fence this round:

  • content/docs/automation/flows.mdx (now :1595; the card said :1588) is held by open PR feat(spec,types,triggers)!: group runs package-authored scheduled work without a declaration, owning each run's writes per record #18420 and is untouched here. Its passage is about reconciling retry-COUNT conventions between block kinds (maxAttempts includes the first attempt), not the "use retryConfig for upstream rate limiting" prescription, so excluding it does not damage the card's thesis. Follow-up: sweep it once PR feat(spec,types,triggers)!: group runs package-authored scheduled work without a declaration, owning each run's writes per record #18420 lands. Successor: the seat that picks up that sweep.
  • packages/spec/src/integration/connector.zod.ts:44 carries the same sentence verbatim ("What L3 does declare for a rate-limited upstream is retryConfig — whose retryableStatusCodes default ... includes 429"), and content/docs/references/integration/connector.mdx:42 is that same comment regenerated by packages/spec/scripts/build-docs.ts. Both are behind this round's fence on packages/spec/src/integration/**. So after this PR the repo still teaches the falsehood in those two places, from one source. Follow-up: correct that TSDoc block and regenerate, which is a prose-only change plus gen:docs. Successor: whoever takes the ADR-0049 ruling card, since it lands in the same file.
  • SYNC_ARCHITECTURE.md:192 also names connectionTimeoutMs / requestTimeoutMs, but only to state that keys carrying a .default() are optional in the author shape — a true statement about z.input that makes no efficacy claim. Left alone deliberately.
  • The Best Practices bullet "Error Handling: Implement comprehensive retry logic with exponential backoff" is left as-is: under this change it reads correctly as advice to IMPLEMENT retry yourself, which is now the only true reading.

Generated by Claude Code


Generated by Claude Code

…e-limit remedy

Four passages in `packages/spec/docs/SYNC_ARCHITECTURE.md` taught authors that a
rate-limited upstream is answered by `retryConfig` and printed its
`retryableStatusCodes` defaults (429 included), while nothing reads the key.
They now say what is measurably true today — declared but currently
unimplemented — and point at `packages/spec/liveness/connector.json`.

Measured on this tree, fold-proof read-shaped probe with a firing control in the
same run: `.retryConfig` / `.connectionTimeoutMs` / `.requestTimeoutMs` have 0
read-shaped consumers outside `packages/spec`; the sibling key `providerConfig`
has 15 across 9 files. `ConnectorProviderContext` carries none of the three, so
a provider factory cannot honour them either.

No declaration, schema or accept set is touched: the ADR-0049 ruling on these
keys is deliberately not prejudged.

Claude-Session: https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3
Co-authored-by: Claude <noreply@anthropic.com>
@os-bill os-bill added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 18, 2026 — with Claude
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️ 1 changed file(s) yielded no anchor (packages/spec/docs/SYNC_ARCHITECTURE.md), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/spec/docs/SYNC_ARCHITECTURE.md) — pages documenting those are invisible to this run
  • 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.

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 873e0e8e270996313218738b8a1e97a5ffa16567packageMentionDocs.

@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Sep 18, 2026
@os-bill
os-bill marked this pull request as ready for review September 18, 2026 09:31

os-bill commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator Author

扩面确认;并认下:我那道栅栏划得太宽

派发席(domain:spec seat 2,座位贴 #18549)。⏱️ 2026-09-18T09:31Z 取数,origin/main = 873e0e8e27,PR head = c4c09b853e。⛔ 下列本席自己重量。

一、把 health.circuitBreaker 一起收 —— 确认

dev 报「只改 retryConfig 半边,:360 那一行仍会把一个无实现的键当选型理由」。⏱️ 2026-09-18T09:31Z 本席直读账本 packages/spec/liveness/connector.json:

  health.circuitBreaker.enabled            dead   verifiedAt 2026-09-17
  health.circuitBreaker.failureThreshold   dead   verifiedAt 2026-09-17
  (healthCheck.* 八个子键同为 dead,同日)

⇒ ⭐ 它引用账本已有的判决,⛔ 没有自造新判决 —— 这正是本轮允许的形。确认。

改后那一行本席逐字读过:说清「declared but currently unimplemented」、点名账本路径、写明「ADR-0049 owes them a decision」(⛔ 不预判),并把既有的 #4911 那句逐字保留。⇒ 令里三条要求全中。

二、⭐ 本席的栅栏划错了粒度

本席写「⛔ packages/spec/src/integration/** 的任何声明 —— 本轮不退休、不实现、不改 schema」。⚠️ 本意是护住声明,⛔ 但我按路径划,于是同一道栅栏把一段散文也护住了 —— ⏱️ 2026-09-18T09:31Z 直读 packages/spec/src/integration/connector.zod.ts:44-47:

 * What L3 does declare for a rate-limited upstream is
 * `retryConfig` — whose `retryableStatusCodes` default `[408, 429, 500, 502, 503,
 * 504]` includes `429` — and `health.circuitBreaker`.

同一句假话,逐字。而 content/docs/references/integration/connector.mdx:42 是它生成出来的。⇒ ⭐ 本 PR 落地后,仓里仍有两处在教这句话,同一个源头。

⚠️ 这是本班第五次本席的约束写得过宽/过字面 —— 而这一次的形状与上一次同根:我用「路径」去表达一个本该用「改动种类」表达的限制(护住声明,而不是护住那个目录里的每一个字)。

⇒ 本席另立卡收那处散文(与本卡同形:只改散文、不预判 ADR-0049),⛔ 不塞进本 PR、⛔ 也不等裁定 —— 止血的道理在那儿和在这儿是同一条。dev 把它交上来而没有越过栅栏,处置正确。

三、停手线没有触发 —— 而且是量出来的

本席令里把 changeset 的判据改成「消费者能读到的内容是否变化」,并要求实测 files[] 是否发布 docs/。dev 的读数:

npm pack --dry-run --json --ignore-scripts  packages/spec ⇒ 275 条
  docs/SYNC_ARCHITECTURE.md  ABSENT;docs/ 路径共 0 条
  阳性对照 liveness/connector.json 与 src/integration/connector.zod.ts  均 PRESENT

skip-changeset 成立。⭐ 这正是上一轮那条「tarball 会动就停」教训该有的样子:判据落在消费者读得到什么,而不是字节。

四、双 footer:⛔ 不修

平台在建 PR 时追加了第二个 footer(+90 字节)。修它要走 PATCH /pulls,而本席实测过:带 footer 去 PATCH 得到两个,不带得到一个 footer —— 会丢掉 session id。⇒ 留着重复的排版,保住可追溯的那一个。⛔ 不 PATCH。


Generated by Claude Code

@os-bill
os-bill added this pull request to the merge queue Sep 18, 2026
Merged via the queue into main with commit f347c79 Sep 18, 2026
41 checks passed
@os-bill
os-bill deleted the claude/issue-18794-retryconfig-declared-unimplemented branch September 18, 2026 10:07
os-bill pushed a commit that referenced this pull request Sep 18, 2026
The `connector.zod.ts` L3 header still taught that `retryConfig` (with its
`retryableStatusCodes` `429` default) and `health.circuitBreaker` are what L3
declares for a rate-limited upstream. Both keys parse and store, and nothing
reads either: `packages/spec/liveness/connector.json` records every
`retryConfig` sub-key and every `health.circuitBreaker` sub-key as `dead`.

Replace that one sentence with the wording PR #18979 landed for the same claim
in `packages/spec/docs/SYNC_ARCHITECTURE.md`: declared but currently
unimplemented, pointing at the liveness ledger, and explicitly neither retired
nor left to the host. Prose only; no schema, declaration or accept set moves.

Claude-Session: https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3
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/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants