Skip to content

Commit 929d9e3

Browse files
os-billclaude
andauthored
refactor(spec)!: delete the seven cron-typed positions outright — no tombstone, no D2, no D3 (#16320, ADR-0049, the 直接删 ruling) (#17146)
* wip(spec): tombstone the seven cron-typed positions, per-family ADR-0087 entries, pins Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH * wip(spec): move export constants below the module docblock, regenerate docs, fix cache carrier fixture Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH * wip(spec): connector family D3 twin per the #15954 letter, re-judge the two missed fixtures, harden pins Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH * wip(spec): name the tool without the house-sentence marker in the connector D3 twin (class pin `retired-key-migrate-sentence`) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH * chore(docs): regenerate export.mdx on the merged tree The os-regen merge driver resolved content/docs/references/api/export.mdx with exit 0 and no conflict markers while silently dropping origin/main's side: the merge commit's blob was byte-identical to this branch's tip and carried 0 of PR #17090's `refusal` envelope rows. scripts/pm/os-regen-merge.sh step 2 restored main's side in the worktree, and `gen:schema && gen:docs` re-derived this branch's cron-retirement tombstones on top. The merged page now carries both intents. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH * refactor(spec)!: delete the seven cron-typed positions outright — no tombstone, no D2, no D3 Executes the maintainer ruling of 2026-09-10 on this PR: 直接删. The previous round retired the seven positions the tombstone way — `retiredKey()` at every site, seven `RETIRED_KEYS_BY_MAJOR[18]` entries, one D2 conversion for the connector family and five D3 semantic entries. The maintainer ruled that whole kit out on the reading that customers do not upgrade major by major in order, after the seat had recommended the opposite in writing and stated the cost. So the keys simply stop existing: - 7 `migrations/entries/retired-keys/18.*.ts` deleted - 5 `migrations/entries/semantic/18.*-retired.ts` deleted - `conversions/registry.ts`: `connector-sync-schedule-removed` (D2) deleted, and its id dropped from step 18's `conversionIds` - `migrations/registry.ts` regenerated — the generated regions collapse - the six schema files keep only the deletion half; no `retiredKey()` survives. Each site keeps a source comment recording what left, why nothing read it, and that `Job.schedule.expression` is the one cron slot the platform evaluates - `authorable-surface/`: the four rows that existed are deleted rather than marked `[RETIRED]`; the build's own #4650 proof 2 carries each of them (the def is not reachable from the 26 metadata-type roots) - the pin test is rewritten around what a bare deletion actually produces: the authored value is accepted and STRIPPED, and no ADR-0087 surface names any of the seven — with a lit and a dark control on every absence reading - the changeset is rewritten for the smaller change Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH Co-authored-by: Claude <noreply@anthropic.com> * test(spec): restore the two well-formed fixtures the rewrite paraphrased `CACHE_WELL_FORMED` and `DR_PLAN_WELL_FORMED` are the ATTRIBUTION CONTROLS of every pin in this file — each site asserts that the same document without the deleted key still parses, so a fixture that does not parse turns the control into a second failure mode and the pin proves nothing. Retyping them from memory rather than carrying them across produced exactly that: `DistributedCacheConfig` needs `tiers` and `invalidation`, and `DisasterRecoveryPlan` takes no `name`. Both are restored to the bytes they carried before, and the suite is green. Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH Co-authored-by: Claude <noreply@anthropic.com> * docs(spec): correct the "silent strip" cost statement at all four sites, and re-count clause ② as seven A CONTRACT_REVIEW_TIER review measured this branch's head and found the cost statement the maintainer signs off on by merging to be factually wrong. Four sites said the strip is silent. It is not: `lintUnknownAuthoringKeys` (#3786) walks every stack collection whose entry schema is strip-mode, and `connectors` is one, so for `connectors[].syncConfig.schedule` — the one deleted position a stack manifest reaches — `os validate` and `os build` NAME the dropped key and `os validate --strict` EXITS 1 on it. Measured at head on an otherwise-clean stack: without the key 0 warnings / exit 0, with it 1 warning / exit 1. The truth is more favourable to authors than what the maintainer was told, but it is not the sentence they were asked to confirm. Corrected at all four sites: the changeset, `migrations/registry.ts`'s step-18 rationale, two comments in `cron-typed-positions-retirement.test.ts`, and the PR body's 维护者速读. The accurate statement is per channel: the PARSE strips silently, `os validate` / `os build` name the key, `--strict` refuses, `os migrate meta` still lists nothing. Same round, two more corrections and no behaviour change: * Clause ② is SEVEN widenings, not two, and THREE of them are required-key removals (`ScheduleState.cronExpression`, `ScheduledExport.schedule.cronExpression`, `ScheduleExportRequest.schedule.cronExpression`). Measured on both legs: the five schema files restored to the merge-base on disk, one probe run, restored to HEAD and re-run, with a byte-identity check after the restore. 14 cases, 14 refusals on the base leg, 14 accept-and-strip on the head leg. * The changeset now names the departure of the required `cronExpression: string` member from `ScheduleExportInput` — a published TS interface on `IExportService`. "No def leaves the public surface" is true and does not cover a member. Sweep: zero consumers outside `packages/spec`. ⛔ No behaviour changed: the seven deletions, the merge and the ADR-0087 disposition are untouched, and the 直接删 ruling is not re-litigated. Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH Co-authored-by: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent d57611d commit 929d9e3

29 files changed

Lines changed: 840 additions & 154 deletions
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
---
2+
"@objectstack/spec": minor
3+
---
4+
5+
feat(spec)!: delete the seven cron-typed positions nothing evaluated — export schedules, `ScheduleState.cronExpression`, `DataSyncConfig.schedule`, `CacheWarmup.schedule`, backup / DR-test schedules (ADR-0049)
6+
7+
<!-- adr-0087: not-required (no-migration-prescription) a bare deletion on a non-strict schema refuses nothing and converts nothing, so no metadata upgrader has an edit to make and `os migrate meta` has nothing to list; nothing is registered, by the maintainer ruling of 2026-09-10 -->
8+
9+
**BREAKING** — seven authorable positions across five schemas are DELETED. Executes the
10+
maintainer ruling of 2026-09-06 (director decision batch #56, 「其他同意」 on the per-family
11+
recommendation: option A — retire — per family) under ADR-0049 enforce-or-remove, by the
12+
route the maintainer ruled on 2026-09-10: **直接删** — a bare deletion, with no
13+
`retiredKey()` tombstone, no ADR-0087 D2 conversion and no D3 semantic entry.
14+
15+
Seven positions declared a `CronExpressionInputSchema` slot that the parse normalized into
16+
the `{ dialect: 'cron', source }` envelope and that NOTHING evaluated — the ADR-0058 D7
17+
ledger row `cron-declared-unwired` had every one of them `unevaluated`.
18+
19+
| family | schema | deleted position | reachable from a stack manifest |
20+
|:--|:--|:--|:--|
21+
| export schedules | `ScheduledExport`, `ScheduleExportRequest` (`api/export.zod.ts`) | `schedule.cronExpression` (both) | no — API contract nothing serves |
22+
| flow schedule state | `ScheduleState` (`automation/execution.zod.ts`) | `cronExpression` (was REQUIRED) | no — runtime state |
23+
| connector sync | `DataSyncConfig` (`integration/connector.zod.ts`) | `schedule` | **yes** — `Connector.syncConfig`, `defineStack({ connectors })` |
24+
| cache warmup | `CacheWarmup` (`system/cache.zod.ts`) | `schedule` | no |
25+
| backup / DR testing | `BackupConfig`, `DisasterRecoveryPlan.testing` (`system/disaster-recovery.zod.ts`) | `schedule` (both) | no |
26+
27+
**What an upgrading author actually observes.** None of the five schemas is `.strict()`, so
28+
a bare deletion means Zod DROPS the key at the PARSE: an existing document still parses and
29+
still loads, and the value is discarded there without a word. There is nothing for
30+
`objectstack migrate meta` to list and nothing for the ADR-0087 chain to replay — the value
31+
was already inert before this change, and it is inert after.
32+
33+
The parse is not the only channel, and the two that speak are worth stating exactly,
34+
because a reader who stops at "non-strict schema" will conclude the opposite:
35+
36+
- **`os validate` / `os build` NAME the dropped key**, for the one deleted position a stack
37+
manifest reaches (`connectors[].syncConfig.schedule`). `os validate` exits 0 and reports
38+
`connectors.<name>.syncConfig.schedule: 'schedule' is not a declared connector key, so its
39+
value is dropped at load.` — in the text face and in `--json`'s `warnings`; `os build`
40+
prints the same line under `Undeclared authoring keys — dropped at load (#3786)`. The
41+
channel is `lintUnknownAuthoringKeys`, which walks every stack collection whose entry
42+
schema is strip-mode, and `connectors` is one. **`os validate --strict` treats that warning
43+
as an error and EXITS 1**, so a pipeline running `--strict` over an otherwise-clean stack
44+
refuses the upgraded manifest until the key is deleted. `os migrate meta` still lists
45+
nothing, in either direction.
46+
- **`tsc`**: a TypeScript author annotating with `Connector`, `ScheduledExport`,
47+
`ScheduleState`, `CacheWarmup`, `BackupConfig` or `DisasterRecoveryPlan` gets an
48+
excess-property error at the key and deletes it.
49+
50+
The other six positions are not reachable from a stack manifest, so no CLI walk visits them:
51+
for those the parse-level strip really is the whole of it.
52+
53+
**What stays, byte-identical:** every other key of the five schemas and every export — no def
54+
leaves the public surface. `ScheduledExport.schedule` / `ScheduleExportRequest.schedule` keep
55+
their `timezone` (still defaulting to `UTC`); `ScheduleState` keeps `timezone`, `status` and
56+
`nextRunAt`, and a state without `cronExpression` now parses (the requiredness left with the
57+
key); `CacheWarmup.strategy` keeps its `scheduled` member — a value, not a position the
58+
ruling names, and exactly as inert as before.
59+
60+
**One published TS MEMBER does leave, and "no def leaves" does not cover it.** The required
61+
`cronExpression: string` member is deleted from `ScheduleExportInput` in
62+
`contracts/export-service.ts` — the input type of `IExportService.scheduleExport`, a
63+
published runtime TS interface (both names are in `api-surface/contracts.json`). It follows
64+
the two spec positions it mirrored: with `ScheduledExport.schedule.cronExpression` gone, an
65+
input demanding the key would ask a provider for a cadence it cannot store. The interface,
66+
the method and every other member stay. Measured blast radius: no source outside
67+
`packages/spec` names `ScheduleExportInput` or `IExportService` — 0 hits in this repo
68+
(positive control: a symbol of the same class resolves outside `packages/spec` in the same
69+
sweep) and 0 in `objectui` (control: 1326 files there import `@objectstack/spec`). An
70+
implementor that *does* exist off-tree drops the member from its object literal; a caller
71+
constructing a `ScheduleExportInput` drops it from the literal it passes.
72+
73+
**Not in scope, deliberately:** `CronSchedule.expression` (`system/job.zod.ts`, read by
74+
`croner` — the ONE cron slot the platform evaluates), `KnowledgeRefreshPolicy.cron`
75+
(experimental by design), `Object.titleFormat`, and the `PromptTemplate` pair (marked, not
76+
retired, on its sibling card).
77+
78+
## This change states no before/after rewrite, because there is none
79+
80+
A breaking changeset in this repo normally states the old spelling beside the new one.
81+
This one has no such pair to state: the same document PARSES before and after, the value
82+
was inert in both, and no conversion can be written for it — so a metadata upgrader has no
83+
edit to make and `os migrate meta` has nothing to list. That is a statement about the
84+
migration chain, not about silence: `os validate` / `os build` do name the dropped
85+
connector key and `os validate --strict` refuses on it (above), and `tsc` names the key and
86+
the line for a TypeScript author. What follows is guidance for authoring a cadence going
87+
forward, not a rewrite of an existing document.
88+
89+
## What to write instead
90+
91+
There is no replacement on any of the five schemas: no export scheduler, flow-state
92+
scheduler, connector-sync scheduler, cache-warmup engine, backup engine or DR-test runner
93+
exists to declare a cadence to. The one cron slot the platform evaluates is
94+
`Job.schedule.expression` (`system/job.zod.ts`) — work on a cadence is a `job` whose handler
95+
you write:
96+
97+
```ts
98+
// A connector that used to carry `syncConfig.schedule: '*/15 * * * *'` declares
99+
// the cadence as a job instead; the handler drives the connector.
100+
defineStack({
101+
connectors: [{ name: 'sap_erp', label: 'SAP ERP', type: 'saas', syncConfig: { strategy: 'incremental' } }],
102+
jobs: [{ name: 'sap_erp_sync', schedule: { expression: '*/15 * * * *' }, handler: 'syncSapErp' }],
103+
});
104+
```
105+
106+
The retirement kit, in the shape the 2026-09-10 ruling prescribes:
107+
108+
- the key is DELETED at all seven sites (`api/export.zod.ts` ×2,
109+
`automation/execution.zod.ts`, `integration/connector.zod.ts`, `system/cache.zod.ts`,
110+
`system/disaster-recovery.zod.ts` ×2). Each site keeps a source comment recording what
111+
left, why nothing ever read it, and what does work instead
112+
- **no ADR-0087 registration at all** — no `RETIRED_KEYS_BY_MAJOR[18]` entry, no D2
113+
conversion, no D3 semantic entry, and nothing added to the protocol-18 chain step. That is
114+
the ruling: 「直接删」, taken over the seat's written recommendation to keep the connector
115+
family's D2, on the reading 「我们的客户也不会按照你的设想的版本按顺序升级」
116+
- the four baseline rows that existed (`automation/ScheduleState:cronExpression`,
117+
`integration/DataSyncConfig:schedule`, `system/BackupConfig:schedule`,
118+
`system/CacheWarmup:schedule`) are deleted from `authorable-surface/` in this same commit,
119+
each carrying the #4650 proof the build computes for itself: the def is not reachable from
120+
the 26 metadata-type roots. The three nested positions never had a row of their own
121+
- no liveness-ledger row: none of the five schemas is an enrolled ledger type
122+
- the ADR-0058 D7 expression-conformance ledger loses its `cron-declared-unwired` row (every
123+
position it covered is gone, so discovery by roster name no longer sees them); the cron
124+
dialect is now exactly the one evaluated slot plus the one experimental-by-design slot
125+
- pin tests (`cron-typed-positions-retirement.test.ts`): per site, the authored value is
126+
accepted and stripped and the enclosing block still parses, on the base schema and through
127+
every nesting carrier (`Connector.syncConfig`, `stack.connectors[]`, the `/meta/connector`
128+
door, `DisasterRecoveryPlan.backup`, `DistributedCacheConfig.warmup`); the `tsc` channel;
129+
and — with lit and dark controls — that no `RETIRED_KEYS_BY_MAJOR` entry, no D2 conversion
130+
and no D3 semantic entry names any of the seven
131+
- generated baselines and docs follow the schema: the five reference pages are regenerated,
132+
the published `objectstack-formula` skill's `cron` row drops the retired carriers and keeps
133+
`Job.schedule.expression`, and `packages/spec/docs/SYNC_ARCHITECTURE.md` stops teaching
134+
`syncConfig.schedule`
135+
- `json-schema.manifest/` and `api-surface/` are unchanged, and correctly so: the first
136+
ratchets def *names* and the second export *existence*; deleting keys removes neither

‎content/docs/references/api/export.mdx‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -751,14 +751,13 @@ Type: `{ sourceField: string; targetField: string; targetLabel?: string; transfo
751751
| **fields** | `string[]` | optional | Fields to include |
752752
| **filter** | `Record<string, any>` | optional | Record filter criteria |
753753
| **templateId** | `string` | optional | Export template ID for field mappings |
754-
| **schedule** | `{ cronExpression: string \| object; timezone?: string }` | ✅ | Schedule timing configuration |
754+
| **schedule** | `{ timezone: string }` | ✅ | Schedule timing configuration |
755755
| **delivery** | `{ method: Enum<'email' \| 'storage' \| 'webhook'>; recipients?: string[]; storagePath?: string; webhookUrl?: string }` | ✅ | Export delivery configuration |
756756

757757
### Nested Shape: `ScheduleExportRequest.schedule`
758758

759759
| Property | Type | Required | Description |
760760
| :--- | :--- | :--- | :--- |
761-
| **cronExpression** | `string \| { dialect: 'cron'; source?: string; ast?: any; meta?: object }` | ✅ | Cron expression for schedule |
762761
| **timezone** | `string` | optional (default: `"UTC"`) | IANA timezone |
763762

764763
### Nested Shape: `ScheduleExportRequest.delivery`
@@ -825,7 +824,7 @@ Type: `{ sourceField: string; targetField: string; targetLabel?: string; transfo
825824
| **fields** | `string[]` | optional | Fields to include |
826825
| **filter** | `Record<string, any>` | optional | Record filter criteria |
827826
| **templateId** | `string` | optional | Export template ID for field mappings |
828-
| **schedule** | `{ cronExpression: string \| object; timezone?: string }` | ✅ | Schedule timing configuration |
827+
| **schedule** | `{ timezone: string }` | ✅ | Schedule timing configuration |
829828
| **delivery** | `{ method: Enum<'email' \| 'storage' \| 'webhook'>; recipients?: string[]; storagePath?: string; webhookUrl?: string }` | ✅ | Export delivery configuration |
830829
| **enabled** | `boolean` | optional (default: `true`) | Whether the scheduled export is active |
831830
| **lastRunAt** | `string` | optional | Last execution timestamp |
@@ -837,7 +836,6 @@ Type: `{ sourceField: string; targetField: string; targetLabel?: string; transfo
837836

838837
| Property | Type | Required | Description |
839838
| :--- | :--- | :--- | :--- |
840-
| **cronExpression** | `string \| { dialect: 'cron'; source?: string; ast?: any; meta?: object }` | ✅ | Cron expression for schedule |
841839
| **timezone** | `string` | optional (default: `"UTC"`) | IANA timezone |
842840

843841
### Nested Shape: `ScheduledExport.delivery`

‎content/docs/references/automation/execution.mdx‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,6 @@ const result = CheckpointSchema.parse(data);
345345
| :--- | :--- | :--- | :--- |
346346
| **id** | `string` | ✅ | Schedule instance ID |
347347
| **flowName** | `string` | ✅ | Flow machine name |
348-
| **cronExpression** | `string \| { dialect: 'cron'; source?: string; ast?: any; meta?: object }` | ✅ | Cron expression — cron`0 9 * * MON-FRI` |
349348
| **timezone** | `string` | optional (default: `"UTC"`) | IANA timezone for cron evaluation |
350349
| **status** | `Enum<'active' \| 'paused' \| 'disabled' \| 'expired'>` | optional (default: `"active"`) | Current schedule status |
351350
| **nextRunAt** | `string` | optional | Next scheduled execution timestamp |

0 commit comments

Comments
 (0)