You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
[finding][spec] #6414 retired the L2 ETL layer, but retry-policy.zod.ts still teaches ETLPipeline.retry as a live surface in six places — including the retryDelayMs tombstone an upgrading author actually reads #6630
Found during a read-only truth sweep of packages/spec's text surfaces against the mechanisms they name. Filed unassigned for triage.
This is the same shape as #5554 / #6383 (prose still advertising connector Rate Limiting after #4911 retired it), applied to the #6414 ETL retirement — and one of the six sites is an error template, not a comment.
The defect
#6414 removed automation/etl.zod.ts outright: ETLPipeline, ETLPipelineRun, their source/destination/transformation vocabulary, four enums and the ETL factory — 9 defs, 27 exported names. The absence is pinned in packages/spec/src/automation/sync-retirement.test.ts:147-158:
for(constalsoRetiredof['ETLPipeline','ETLPipelineSchema','ETLPipelineRun','ETLPipelineRunSchema','ETLSource','ETLSourceSchema','ETLDestination','ETLDestinationSchema','ETLTransformation','ETLTransformationSchema','ETLEndpointTypeSchema','ETLTransformationTypeSchema','ETLSyncModeSchema','ETLRunStatusSchema','ETL',]){expect(automationNames,`./automation must not export ${alsoRetired} (#6414, L2 retired on L1's reading)`,).not.toContain(alsoRetired);}
packages/spec/src/shared/retry-policy.zod.tsknows this — exactly one stanza in the file was updated (L30-35):
* - `automation/etl.zod.ts` → `ETLPipeline.retry` (#4962; the whole L2 layer
* was retired at #6414, so this surface no longer exists — the convergence
* is recorded because it is how the divergence was FOUND) — spelled the count
* `maxAttempts`, defaulted it to **3** ...
Every other mention in the file stayed in the present tense.
1. The retryDelayMs tombstone — the error text, retry-policy.zod.ts:120-126:
retryDelayMs: retiredKey('`retryDelayMs` was removed in @objectstack/spec 17.0.0 (#4661, #4964) — the retry policy now '+'has ONE spelling for its base delay across every surface that carries it: `job.retryPolicy`, '+"a `try_catch` node's `retry`, `flow.errorHandling` and an ETL pipeline's `retry`. "+'Rename the key to `backoffMs`; the value (milliseconds before the first retry) '+'is unchanged. `os migrate meta --from 16` rewrites it for you.',),
2. RetryPolicySchema's own TSDoc, retry-policy.zod.ts:130-134 (present tense, and {@link}-bearing, so it reaches generated reference docs):
* Exponential-backoff retry policy — the named schema for `job.retryPolicy` and
* a `try_catch` node's `retry` region. `Flow.errorHandling` and
* `ETLPipeline.retry` carry the same contract via {@link retryPolicyShape},
* which they must, being `.strict()` (see that function's note).
3. retry-policy.zod.ts:80-82:
* Two of the four surfaces that carry this policy cannot simply reference
* {@link RetryPolicySchema}: `Flow.errorHandling` and `ETLPipeline.retry` are
* `.strict()` (`strictObject`, the #4001 campaign standard) ...
4. retry-policy.zod.ts:149-155 — present tense, and it points at a tombstone that no longer exists:
* The ETL half needed no conversion branch and deliberately has none: an ETL
* pipeline is not a `defineStack` collection and `etl.zod.ts` has no parse site
* anywhere in objectstack / objectui / cloud (批 12's measurement), so there is
* no stored document for a D2 walker to reach. Writing one anyway would be a
* conversion advertising coverage it does not have. The `maxAttempts` tombstone
* on that block is the whole migration channel, and it reaches the only door
* that exists — `tsc` at the authoring site, and the parse.
There is no maxAttempts tombstone. Grepping maxAttempts across packages/spec/src (excluding tests) returns only prose references in this file and in conversions/registry.ts, plus an unrelated local in shared/resilient-fetch.ts:54 and an unrelated alias in ai/agent.zod.ts:85 (maxAttempts: 'maxRetries'). The tombstone went with the block it sat on.
5. The arithmetic, retry-policy.zod.ts:42 — "the four surfaces share one declaration". Three now.
6. automation/flow.zod.ts:665-667:
* The retry knobs are the converged `RetryPolicySchema` contract, shared with
* `job.retryPolicy`, a `try_catch` node's `retry` and an ETL pipeline's
* `retry` (#4661 + #4964 — see `shared/retry-policy.zod.ts`).
with the same stale arithmetic twice more in the same file — flow.zod.ts:745-747 ("A key added to the policy lands on all four surfaces at once") and flow.zod.ts:752-753 ("the flow surface has a reading the other three do not").
Adjacent, same cause, likely same fix: conversions/registry.ts:3736 ("The two surfaces this entry grew to cover") and :3750-3761, which still says "Its maxAttempts tombstone carries the rename AND the default change, and the tombstone reaches the only doors that exist". Note the registry's step list WAS updated at #6414 (registry.ts:1227-1228 records etl-retry-converged-onto-retry-policy as "ABSORBED by etl-pipeline-layer-retired"), so this is the same partial-sweep, not a separate decision.
The authority
packages/spec/src/automation/sync-retirement.test.ts:147-158 — the export-absence pin quoted above.
packages/spec/src/migrations/registry.ts:2623-2674 — the D3 etl-pipeline-layer-retired semantic migration, which states the consequence directly: "tsc reports TS2724/TS2305" for the ETL factory from @objectstack/spec/automation.
retry-policy.zod.ts:30-35 itself — the one stanza that got the update, and therefore the in-file proof that the other five are drift and not a deliberate historical framing.
Contrast with the sites that were correctly re-aimed, which is what shows the intended pattern: integration/connector.zod.ts:105 ("This used to point at automation/etl.zod.ts; L2 was retired…") and automation/index.ts:49 ("⚠️ This note used to send readers on to etl.zod.ts … that pointer is gone rather than re-aimed").
Why it matters — the authoring path
Site 1 is the load-bearing one. retiredKey() is not a comment; it is the upgrade channel, and shared/retired-key.ts:15-32 says so in its own words:
* A tombstone keeps the key declared but makes it unwritable, so the removal
* lands in the two channels an upgrading author — very often an AI (ADR-0033) —
* actually reads:
* 1. **`tsc`.** ...
* 2. **The parse.** A value reaching the runtime raises the prescription
* itself — not a generic "unrecognized key". ...
* ... an agent bumping `@objectstack/spec` sees THIS string, not our docs site.
So the one string guaranteed to reach an author or agent migrating off retryDelayMs currently enumerates four surfaces where the converged spelling applies, and one of them was deleted in the same major. Concretely, an AI author reading that message is told an ETL pipeline's retry is a live place to write backoffMs. If it acts on that — writes or keeps an ETLPipeline — the failure is tsc TS2724/TS2305 on a name the spec's own upgrade prescription just recommended. That is the message spending its credibility to send the reader somewhere that no longer exists.
Sites 2 and 3 additionally flow into generated reference pages (the {@link} and module-TSDoc surfaces #6085 and #6383 were filed about), so the retired layer keeps a published footprint.
Site 4 is the subtler harm: it tells a future maintainer that a migration channel exists for the ETL rename ("the maxAttempts tombstone … is the whole migration channel"). It does not. Anyone auditing "is the #4962 convergence fully migrated?" would read that sentence and stop.
Suggested direction
Non-binding: one text-only pass over shared/retry-policy.zod.ts, automation/flow.zod.ts and the conversions/registry.ts:3736-3761 stanza, moving every ETLPipeline.retry mention into the past tense the way L30-35 already models, and fixing the counts (four → three, "the other three" → "the other two", "two surfaces" → one). The tombstone text at L120-126 should simply drop the fourth item from its list.
The retryDelayMs tombstone's prescription (→ backoffMs), which is correct — only its enumeration of carrying surfaces is stale.
No acceptance change. Nothing here alters what any schema accepts; retryPolicyShape() and RetryPolicySchema keep their exact key sets, bounds and defaults.
content/docs/** pages that may have inherited these sentences via doc generation — flagging them as likely downstream, but that is the docs seat's surface, not this finding's.
Provenance
Audited at origin/main = 1f65bfc390ca896ec2575346520c4af60fe4176d, read via git show origin/main:<path> (never the working tree).
Measurement: ETL (word-boundary, case-insensitive) over packages/spec/src excluding *.test.ts returns 9 non-test sites; 6 are the present-tense ones listed above, 3 are correctly past-tense (retry-policy.zod.ts:30, automation/index.ts:15-49, integration/connector.zod.ts:20-121). maxAttempts over the same corpus confirms no surviving tombstone.
Sibling-repo coverage: objectstack + objectui at their respective origin/main. /home/user/cloud is not checked out in this environment, so the "no parse site in … cloud" half of the L151 claim was not independently re-measured — but that clause is part of the sentence this finding says should move to the past tense anyway.
Found during a read-only truth sweep of
packages/spec's text surfaces against the mechanisms they name. Filed unassigned for triage.This is the same shape as #5554 / #6383 (prose still advertising connector Rate Limiting after #4911 retired it), applied to the #6414 ETL retirement — and one of the six sites is an error template, not a comment.
The defect
#6414 removed
automation/etl.zod.tsoutright:ETLPipeline,ETLPipelineRun, their source/destination/transformation vocabulary, four enums and theETLfactory — 9 defs, 27 exported names. The absence is pinned inpackages/spec/src/automation/sync-retirement.test.ts:147-158:packages/spec/src/shared/retry-policy.zod.tsknows this — exactly one stanza in the file was updated (L30-35):Every other mention in the file stayed in the present tense.
1. The
retryDelayMstombstone — the error text,retry-policy.zod.ts:120-126:2.
RetryPolicySchema's own TSDoc,retry-policy.zod.ts:130-134(present tense, and{@link}-bearing, so it reaches generated reference docs):3.
retry-policy.zod.ts:80-82:4.
retry-policy.zod.ts:149-155— present tense, and it points at a tombstone that no longer exists:There is no
maxAttemptstombstone. GreppingmaxAttemptsacrosspackages/spec/src(excluding tests) returns only prose references in this file and inconversions/registry.ts, plus an unrelated local inshared/resilient-fetch.ts:54and an unrelated alias inai/agent.zod.ts:85(maxAttempts: 'maxRetries'). The tombstone went with the block it sat on.5. The arithmetic,
retry-policy.zod.ts:42— "the four surfaces share one declaration". Three now.6.
automation/flow.zod.ts:665-667:with the same stale arithmetic twice more in the same file —
flow.zod.ts:745-747("A key added to the policy lands on all four surfaces at once") andflow.zod.ts:752-753("the flow surface has a reading the other three do not").Adjacent, same cause, likely same fix:
conversions/registry.ts:3736("The two surfaces this entry grew to cover") and:3750-3761, which still says "ItsmaxAttemptstombstone carries the rename AND the default change, and the tombstone reaches the only doors that exist". Note the registry's step list WAS updated at #6414 (registry.ts:1227-1228recordsetl-retry-converged-onto-retry-policyas "ABSORBED byetl-pipeline-layer-retired"), so this is the same partial-sweep, not a separate decision.The authority
packages/spec/src/automation/sync-retirement.test.ts:147-158— the export-absence pin quoted above.packages/spec/src/automation/index.ts:15-32— the retirement note: "etl.zod.ts… was removed here (L2 ETLPipeline 在本仓无任何执行侧消费者(仅 spec 自身 + 生成文档),而 SYNC_ARCHITECTURE.md 把它作为 L1 退役后的推荐去处 #6414, ADR-0049 enforce-or-remove, protocol 17) … Multi-source, multi-stage movement has no protocol surface at all now — deliberately, because it had no executor".packages/spec/src/migrations/registry.ts:2623-2674— the D3etl-pipeline-layer-retiredsemantic migration, which states the consequence directly: "tscreports TS2724/TS2305" for theETLfactory from@objectstack/spec/automation.retry-policy.zod.ts:30-35itself — the one stanza that got the update, and therefore the in-file proof that the other five are drift and not a deliberate historical framing.Contrast with the sites that were correctly re-aimed, which is what shows the intended pattern:⚠️ This note used to send readers on to
integration/connector.zod.ts:105("This used to point atautomation/etl.zod.ts; L2 was retired…") andautomation/index.ts:49("etl.zod.ts… that pointer is gone rather than re-aimed").Why it matters — the authoring path
Site 1 is the load-bearing one.
retiredKey()is not a comment; it is the upgrade channel, andshared/retired-key.ts:15-32says so in its own words:So the one string guaranteed to reach an author or agent migrating off
retryDelayMscurrently enumerates four surfaces where the converged spelling applies, and one of them was deleted in the same major. Concretely, an AI author reading that message is toldan ETL pipeline's retryis a live place to writebackoffMs. If it acts on that — writes or keeps anETLPipeline— the failure istscTS2724/TS2305 on a name the spec's own upgrade prescription just recommended. That is the message spending its credibility to send the reader somewhere that no longer exists.Sites 2 and 3 additionally flow into generated reference pages (the
{@link}and module-TSDoc surfaces#6085and#6383were filed about), so the retired layer keeps a published footprint.Site 4 is the subtler harm: it tells a future maintainer that a migration channel exists for the ETL rename ("the
maxAttemptstombstone … is the whole migration channel"). It does not. Anyone auditing "is the #4962 convergence fully migrated?" would read that sentence and stop.Suggested direction
Non-binding: one text-only pass over
shared/retry-policy.zod.ts,automation/flow.zod.tsand theconversions/registry.ts:3736-3761stanza, moving everyETLPipeline.retrymention into the past tense the way L30-35 already models, and fixing the counts (four → three, "the other three" → "the other two", "two surfaces" → one). The tombstone text at L120-126 should simply drop the fourth item from its list.Not in scope
retryDelayMstombstone's prescription (→ backoffMs), which is correct — only its enumeration of carrying surfaces is stale.retryPolicyShape()andRetryPolicySchemakeep their exact key sets, bounds and defaults.content/docs/**pages that may have inherited these sentences via doc generation — flagging them as likely downstream, but that is the docs seat's surface, not this finding's.Provenance
origin/main=1f65bfc390ca896ec2575346520c4af60fe4176d, read viagit show origin/main:<path>(never the working tree).ETL(word-boundary, case-insensitive) overpackages/spec/srcexcluding*.test.tsreturns 9 non-test sites; 6 are the present-tense ones listed above, 3 are correctly past-tense (retry-policy.zod.ts:30,automation/index.ts:15-49,integration/connector.zod.ts:20-121).maxAttemptsover the same corpus confirms no surviving tombstone.retry-policy ETL(8 hits —ETLPipeline.retryis a third retry-policy vocabulary that #4661 的收敛没有覆盖到 #4962, 重试策略在仓里有三份形状、两种拼法:#4661 收敛了共享同一导出名的两份,flow.errorHandling是匿名内联块,检查照不到它 #4964,automation/etl.zod.ts的九个类型别名全部导出 parsed 形状,违反X/XParsedhouse convention(SYNC_ARCHITECTURE.md 的示例因此不可编译) #4963, L2 ETLPipeline 在本仓无任何执行侧消费者(仅 spec 自身 + 生成文档),而 SYNC_ARCHITECTURE.md 把它作为 L1 退役后的推荐去处 #6414 and fourX/XParsed-convention issues, all closed, none covering this text),is:open "retry-policy.zod"(0 hits),6414 ETL retired prose(3 hits — L2 ETLPipeline 在本仓无任何执行侧消费者(仅 spec 自身 + 生成文档),而 SYNC_ARCHITECTURE.md 把它作为 L1 退役后的推荐去处 #6414 closed, plus two PM tracking issues).systemFieldsownerguidance misstatesownership: 'org'— tells authors org "chooses the principal" when it injects noowner_idat all #6365, HookContext 契约表把before*的input.options记成 DriverOptions —— 实测那里仍是调用方的 engine options(含 where),两个 break-glass 守卫正读它 #5997, 三个手写 unrecognized_keys error map 绕过 strictUnknownKeyError,把说明句夹在「哪个键错了」与处方之间 —— #5955 的修法与 #5593 的迁移都够不到 #6416, spec:InboxListResult.unreadCount的 JSDoc 仍写「over the returned window」—— 与 #6363 落地后的实现和同族.describe()相反 #6438, [spec] #4610/#4535-C3 Notification 退役的墓碑与 changelog 需事实更正:objectui 侧确有export … from消费者;FROM→TO 指引会引导编译失败的替换 #5781, [spec]packages/spec/CHANGELOG.md的 17.0.0-rc.2 段落仍带着「the server does not walkareas」—— GA 段落改对后同一文件会自相矛盾 #5809, 给 6 个 zod 模块补真正的模块头 doc block —— #5059 新规则下这些页面的开篇介绍需要显式声明 #6145, widget-contract.mdx 整页 + quick-reference.mdx 一行仍在教 #5055 退役的 WidgetManifest 家族 —— 手写散文面,门禁不覆盖 #6413.objectstack+objectuiat their respectiveorigin/main./home/user/cloudis not checked out in this environment, so the "no parse site in … cloud" half of the L151 claim was not independently re-measured — but that clause is part of the sentence this finding says should move to the past tense anyway.