Skip to content

[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

Description

@os-project-manager

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 (const alsoRetired of [
      '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.ts knows 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/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 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.

Not in scope

Provenance

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions