feat(spec)!: retire the connector resilience family — health (probe + breaker), status and nested webhooks, sixteen keys nothing read (#20273) - #20350
Conversation
… nested webhooks tombstones, defs, D2, D3, pins Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QcAS3qiYYZNezaxZxaUdMV
…; tombstone rows regenerated Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QcAS3qiYYZNezaxZxaUdMV
…eference docs, strictness counts and the shrunk test-typecheck debt Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QcAS3qiYYZNezaxZxaUdMV
…atus default Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QcAS3qiYYZNezaxZxaUdMV
…n no longer spells the sibling retirement's key Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QcAS3qiYYZNezaxZxaUdMV
…nnector-resilience-keys-retired # Conflicts: # packages/spec/src/migrations/registry.ts
…hors the retired status Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QcAS3qiYYZNezaxZxaUdMV
…re note name the resilience removal Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QcAS3qiYYZNezaxZxaUdMV
…nnector-resilience-keys-retired # Conflicts: # packages/spec/src/migrations/registry.ts
… the merged tree; reconcile the duration-rename D3 entry with the absorbed breaker half The rename family's D3 entry (landed from the D3-per-family census) still prescribed `monitoringWindow` -> `monitoringWindowMs`; that renamed key is itself retired with the whole `health` block, so the entry now prescribes the trigger rename only and sends the breaker spelling to the removal. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QcAS3qiYYZNezaxZxaUdMV
Contract reviewServed-tier: 62/62 ① Derived judgments
② Semver levelPASS. ③ Boundary flags
Implemented-by: VERDICT: PASS |
…nnector-resilience-keys-retired
Main moved the action and translation rows; the connector row keeps this branch's retirement (dead 30, total 60). Regenerated with gen:liveness-counts, never hand-merged. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QcAS3qiYYZNezaxZxaUdMV
…ooks as a row that has left Review nit on the retirement: the `_containers` prose still described `connector/webhooks` as a recorded row after the row was deleted. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QcAS3qiYYZNezaxZxaUdMV
📓 Docs Drift CheckThis PR changes 7 package(s): 4 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 3 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 136 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 407b8d42e6e392211a6e6165653c45a8c6fe0829 && git checkout 407b8d42e6e392211a6e6165653c45a8c6fe0829
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 2c310705f7c7b6b073b3ada0a1eaff48ff9078f4 e54b124b82f4ad3fe88cc240807335028a5ffa49 && git checkout -B drift-repro 2c310705f7c7b6b073b3ada0a1eaff48ff9078f4 && git merge --no-ff e54b124b82f4ad3fe88cc240807335028a5ffa49
node scripts/docs-audit/affected-docs.mjs --json 2c310705f7c7b6b073b3ada0a1eaff48ff9078f4
|
Contract reviewServed-tier: 79/79 ① Derived judgments
② Semver levelUnchanged: ③ Boundary flags
Implemented-by: VERDICT: PASS |
…nnector-resilience-keys-retired
…the connector reference page on the merged tree Main moved the rest_api liveness row, the api/ strictness count and the connector page's front-matter description; each regenerated with its own generator (gen:liveness-counts, gen:strictness-ledger, gen:docs on a build of the merged tree), never hand-merged. The retirement's rows are unchanged. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QcAS3qiYYZNezaxZxaUdMV
Contract reviewServed-tier: 93/93 ① Derived judgments
② Semver levelUnchanged: ③ Boundary flags
Implemented-by: VERDICT: PASS |
…nnector-resilience-keys-retired # Conflicts: # packages/spec/src/migrations/registry.ts
Main moved the qa row (and so the total); the connector row keeps this branch's retirement. Regenerated with gen:liveness-counts, never hand-merged; gen:migration-registry and gen:strictness-ledger produced no diff. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QcAS3qiYYZNezaxZxaUdMV
…nnector-resilience-keys-retired
…nnector-resilience-keys-retired # Conflicts: # packages/spec/src/migrations/registry.ts
…nnector-resilience-keys-retired # Conflicts: # packages/spec/src/migrations/registry.ts # packages/spec/vitest.repo-tests.json
…nnector-resilience-keys-retired # Conflicts: # packages/spec/src/type-alias-convention.pin.test.ts
…nnector-resilience-keys-retired # Conflicts: # packages/spec/src/migrations/registry.ts
Main's action.aria retirement and this branch's connector retirement each moved a row of state-counts.md; the merge took main's side, and this regeneration re-derives both rows from the merged ledger. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QcAS3qiYYZNezaxZxaUdMV
Landing lap 2: origin/main 40b315b, 6 commits past dcd3bce. #20350 (`40b315b0`) appended its connector-resilience sentence to step 18's `rationale`, the one conflict region (conversions/registry.ts and step 18's `conversionIds` merged cleanly). Resolved per the seat's answer B on #15429 (5865957805, re-applied by 5867190364), and nowhere else: main's text kept whole (the action-aria sentence, then the connector-resilience sentence), this branch's sentence appended verbatim; the one string join at the seam makes main's closing literal end in a space so the concatenation continues. Claude-Session: https://claude.ai/code/session_01ARcDurZ5j34RdqsGgc4jgH Co-authored-by: Claude <noreply@anthropic.com>
…retiredAfter; the artifact door opens its window per entry (objectstack-ai#20390) (objectstack-ai#20435) Fixes objectstack-ai#20390 Clause-②: yes Implements ruling `5865890672` (batch objectstack-ai#235 item 1, letter **A**, maintainer 「同意 A」; maintainer record `5865873150`, route `5866178043`): every retired entry in the ADR-0087 conversion registry carries a REQUIRED `retiredAfter`, and the artifact forward-conversion window decides per entry. It is one vertical PR across `packages/spec`, `packages/metadata-core` and the artifact door in `packages/metadata`. ## Spec half - **`MetadataConversion` is a live-or-retired union** (`packages/spec/src/conversions/types.ts`). An entry with `retiredFromLoadPath: true` must also carry `retiredAfter`, typed as a stable `x.y.z` template-literal string; a live entry carries neither. tsc refuses an unstamped retirement (the reverse verification is below). The type moves from an interface to a type alias, so `gen:api-surface` and `gen:export-origins` each rewrite one row: `MetadataConversion (interface)` becomes `MetadataConversion (type)`. - **Backfill, from the published tarballs.** Each published entry's value is the stable release just before the first tarball that carries it retired. Each entry in no published tarball carries the current `package.json` label, `17.4.0`. - **Census test.** `src/conversions/retired-after.census.json` holds raw facts per stable release since the registry first shipped (14.8.0 through 17.4.0): the tarball integrity and the ids its `ALL_CONVERSIONS` marks retired. `src/conversions/retired-after.census.test.ts` pins every entry's value against it, offline, in the `local` tier. It pins that every entry absent from the last published tarball carries the label, and that no value is malformed or above the label. `scripts/build-retired-after-census.ts` re-derives the census from registry.npmjs.org. It checks each tarball's integrity, imports each release's `dist/index.mjs`, and writes the census, or compares it with `--check`. ## metadata-core half `applyArtifactForwardConversions` replays entry E when the artifact's floor is below the runtime label OR at or below `E.retiredAfter`. `DEFAULT_FLIPS_NOT_REPLAYED_HERE` is still read first. Its membership is unchanged; `flow-decision-mode-inclusive-explicit` came in with the merge of objectstack-ai#20344. When the floor is at or above the label, only the entries the floor predates are replayed. The rest reach the strict parse and their tombstones through the existing `excludeConversionIds` seam, computed per entry from the registry. There is no second table. `ArtifactForwardConversionVerdict` gains `'converted-retired-after'` for that case. `ArtifactForwardConversionResult` gains `replayedRetirements` (element type `ArtifactReplayedRetirement`): under that verdict, each retirement this runtime enforces past the artifact's floor, with its `retiredAfter`; it is empty for every other verdict. The module docblock's two policy sentences still hold: "a key retired at version V stays a loud refusal for anything authored at ≥ V" (the floor-at-or-above-label bullet), and "Not a second conversion table". ## The door's consumer arm (`packages/metadata/src/plugin.ts`) The verdict has one in-tree consumer that branches on it, and the new arm is added there. - **Which verdicts open the window** is now one total table, `FORWARD_WINDOW_OPENED` (a readonly `Record` keyed by every `ArtifactForwardConversionVerdict` member, valued `boolean`), with `'converted-retired-after'` on the open side. `_warnUnboundFormPredicateRoots` (the objectstack-ai#12915 scope-C notice) returns on `!FORWARD_WINDOW_OPENED[result.verdict]`. That makes its docblock sentence true again: the notice is "read off that pass's own verdict rather than recomputed, so the two can never disagree", and it no longer depends on the label. On `main` today, a 17.4.0-built artifact with a bare-root form predicate is announced now, not once the label reaches 17.5.0. - **Why the table, not the inverted guard.** The two forms the order offered have opposite defaults for a verdict that does not exist yet. Adding the arm to the old hand-written guard defaults a future verdict to "closed", which is how this defect arose. Inverting the guard (return only on `'authored-current'` / `'runtime-version-unknown'`) defaults it to "open", and it would also admit `'not-an-object'`. A total `Record` over the verdict union has no default: a new member is a compile error until someone places it. This is the "add the arm" route, spelled so that tsc forces the next decision. Reverse-verified below. - **The warn lines under the new verdict** no longer say the artifact "predates this runtime's spec" beside a runtime version equal to its floor. The conversion summary names the retirement this runtime enforces past the artifact's floor, with the release that last accepted the shape (from `replayedRetirements`). It then says the artifact converts again on every boot until it is rebuilt with tooling from a release that ships the retirement. The objectstack-ai#12915 notice opens with the same verdict-aware clause. Every other verdict keeps its existing wording. - `plugin-unbound-form-predicate-roots.test.ts`'s "current surface" silence pin had derived that surface as a caret range on the installed label. That spelling is itself the label-dependence this change removes: on `main` it names an artifact built BY the last release. It now derives the first `x.y.z` past both the label and every `retiredAfter`. ## The four pins | Pin | Where | Asserts | |:--|:--|:--| | (1) a 17.4.0-CLI-built artifact with dashboard charts and page `assignedProfiles` boots on `main` and logs the notices | `packages/metadata/src/plugin-artifact-forward-conversion-retired-after.test.ts`, on a REAL fixture: `dist/objectstack.json` built verbatim by the published `@objectstack/cli` 17.4.0 | the dashboard and page register with `chartConfig.type`/`xAxis`/`yAxis` and `assignedProfiles` converted away; one warn line each for `dashboard-widget-chart-config-structure-removed` (3 sites) and `page-assigned-profiles-removed` (1 site) | | (2) newly authored sources using the retired keys are still refused loudly | same file | `defineStack` refuses with `code: 'STACK_SCHEMA_INVALID'`, `status: 422`, and one issue per retired site (4 paths) | | (3) floor exactly 17.5.0 on a 17.5.0-labelled runtime is refused, not converted | `packages/metadata-core/src/artifact-forward-conversion.test.ts` | verdict `authored-current`, zero notices, and the strict parse refuses the same 4 paths | | (4) unreleased `main` (label 17.4.0), artifact at the last release (`^17.4.0`) | same file | verdict `converted-retired-after`, notices by id and path, and the strict parse passes | Beside pin (1), **the objectstack-ai#12915 pin** (`plugin-artifact-forward-conversion-retired-after.test.ts`, "announces a bare-root form predicate once"): the `^17.4.0` fixture with one bare-root form predicate (`stage == "won"`) on the 17.4.0 runtime logs the unbound-root line exactly once, including across a second ingestion. It is red under the old guard and green now (below). Three companions sit beside the pins. After the release (label 17.5.0) the same artifact converts through the label half, with `replayedRetirements` empty. A 17.2.0 retirement still meets its tombstone inside the open per-entry window. `flow-decision-mode-inclusive-explicit` stays refused inside its own per-entry window. Pin (4) also asserts `replayedRetirements`: both retirements at `17.4.0`, and never the default flip. ## Census (re-derived on this tree, npm `latest` = `17.4.0`, label = `17.4.0`) 94 retired entries: **73 published** and **21 unpublished**. The ruling counted 91 retired with 18 unpublished at `df3ba164`. Three unpublished entries landed since then: `action-aria-removed`, `connector-resilience-keys-removed` (objectstack-ai#20350) and `flow-decision-mode-inclusive-explicit` (objectstack-ai#20344, merged into this branch). | first published retirement | entries | `retiredAfter` | |:--|--:|:--| | 15.1.0 | 5 | 15.0.0 | | 17.0.0 | 45 | 16.1.0 | | 17.1.0 | 5 | 17.0.0 | | 17.2.0 | 2 | 17.1.0 | | 17.3.0 | 8 | 17.2.0 | | 17.4.0 | 8 | 17.3.0 | | none (unpublished) | 21 | 17.4.0 | The ruling's census bucket of 50 entries "first retired in 17.0.0" is 45 + 5. The engine seat's census started at the 17.0.0 tarball. Those 5 entries (`object-compactLayout-to-highlightFields`, `stack-roles-to-positions`, `owd-legacy-read-aliases`, `sharing-recipient-role-to-position`, `book-audience-profile-to-permission-set`) are already retired in the 15.1.0, 15.1.1, 16.0.0 and 16.1.0 tarballs, so the ruling's own principle gives them `15.0.0`. ## Verification (final HEAD `2c537b7e`) - Derived gates: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` gave 90 commands at `2c537b7e` (16 files, +1667/−72), and all 90 exit 0 on that head. The `--ran` reconciliation (each line carrying its exit code) reads: "90 derived, 90 run, 0 NOT-MEASURED, 0 UNRUN". The full package closure was rebuilt first (turbo 71/71). - `@objectstack/spec` `test` (`--project local`): Test Files 565 passed (565), Tests 16645 passed, 1 todo. `test:repo` (`--project repo`, run in two halves of 18 files each to fit the foreground cap): 18 files / 460 tests and 18 files / 195 tests, together Test Files 36 passed (36), Tests 655 passed. - `@objectstack/metadata-core` `test`: Test Files 16 passed (16), Tests 295 passed (295). `typecheck` exit 0. - `@objectstack/metadata` `test`: Test Files 55 passed (55), Tests 826 passed (826). `typecheck` exit 0. - eslint `--no-inline-config --format json` on the 10 changed source files: 10 files linted, 0 errors, 0 warnings. `eslint.config.mjs` never enables type-aware linting, so this diff cannot move the verdict on any untouched file. - Main was merged three times, all through `scripts/pm/os-regen-merge.sh`. None of this round's incoming commits touch `packages/spec/src/conversions`, `packages/metadata-core` or `packages/metadata`, and none adds a retired entry: every one of the 94 carries `retiredAfter`. ## Ablation and reverse verification (from committed state, through `scripts/ablation-replace.mjs`) - **Guard ablation (this round).** In `plugin.ts`, `if (!FORWARD_WINDOW_OPENED[result.verdict]) return;` was put back to the old guard, `if (result.verdict !== 'converted-forward' && result.verdict !== 'converted-undeclared') return;`, with a marker comment. On-disk count: marker 1, new guard 0. Across the three door suites (21 tests), exactly one went red, the objectstack-ai#12915 pin ("announces a bare-root form predicate once"). The rest stayed green, including the updated current-surface silence pin. Restore: blob `8f43972c` equals HEAD, `git diff HEAD` is empty, `git status --porcelain` has 0 lines, and all 21 tests pass again. The suites import `plugin.ts` from source, so no build sits between the mutation and the run. - **tsc forces the next verdict decision.** With the `'converted-retired-after': true` row removed from `FORWARD_WINDOW_OPENED`, `tsc --noEmit` in `packages/metadata` exits 2 with `error TS2741: Property '"converted-retired-after"' is missing`. Restored to the HEAD blob. - **Window ablation (round 0, at `87da6b88`).** The per-entry branch was replaced with the old label-only verdict, and `metadata-core` was rebuilt, with the marker present in 2 built files. Pin (4), pin (1) boot and pin (1) notices went red, along with both per-entry companions. Pins (2) and (3) stayed green. The restore was proven (blob equals HEAD, 0 porcelain lines, and the marker absent from the rebuilt dist). - **tsc refuses an unstamped retirement.** With `retiredAfter` removed from `page-assigned-profiles-removed`, spec `tsc --noEmit` exits 2 with exactly one `error TS2322`. - **The census test fails when it should.** A published entry stamped low reds the PUBLISHED test, and an unpublished entry stamped low reds the UNPUBLISHED test. `build-retired-after-census.ts --check` passes against npm (11 releases), and exits 1 on a tampered census. ## Deviations from the ruling text, and why 1. **The rule for unpublished entries has one tolerance.** While the label is AHEAD of the census's last release, an unpublished entry may carry any version from that release up to the label. Taken literally ("carries the current label"), the rule turns the Version Packages PR red. That PR bumps the label to 17.5.0 before 17.5.0 is published, while the 17.5.0 entries correctly carry 17.4.0. The tolerance closes again once the census records the new tarball. The seat confirmed this reading (`5869635456`). The refresh is now a written step of the GA release flow: `docs/releases-maintenance.md`, under "Cutting a GA release — the Version Packages PR flow", says to run `scripts/build-retired-after-census.ts` after a stable `@objectstack/spec` publish and commit the refreshed census. The seat answered the refresh question with A; no workflow and no gate are added. 2. **The network half is a script, not a repo-tier test** (accepted by the seat, `5869635456`). `vitest.repo-tests.json` is held equal to the set of tests that read outside the package (`check:cross-package-test-inputs`), so a network-only test cannot be listed there. Reading the tarballs means downloading every stable release since 14.8.0 (about 11 tarballs, over 250 MB), so no per-run suite does it. So CI pins the committed census offline, and `scripts/build-retired-after-census.ts` re-derives it. The script refuses loudly when offline; it never skips. It is not a `package.json` script and not wired into CI, so no gate is added. 3. **Stable releases only.** The census and the rule skip `-rc` versions: a caret floor never names a prerelease, and the door compares `x.y.z` triples. 4. **Counts.** See the Census section: 73 published, 21 unpublished, and a 15.1.0 bucket the ruling's counts did not have. ## Acceptance notes - `packages/metadata` now carries a `patch` changeset entry for the door change. It changes no public API; the objectstack-ai#12915 notice and the conversion summary wording follow the per-entry window. - `field-required-notnull-explicit` appears retired in the 17.0.0 through 17.3.0 tarballs and is gone from 17.4.0 and `main`, withdrawn by objectstack-ai#16693. The census test ignores ids not on `main`. - The seat files two follow-ups at landing, as governed surfaces outside this PR (per `5869635456`): ADR-0087's objectstack-ai#12772 addendum sentence that a floor at or above the runtime "replays nothing", and the retirement kit in `.claude/skills/spec-property-retirement/SKILL.md`. - Once this lands, any open PR that adds a retired entry fails typecheck until it stamps `retiredAfter`. That is the designed loud direction. - `main` narrowed `manifest.id` (underscores refused, objectstack-ai#17534). So a 17.4.0-built artifact whose id has an underscore is refused whatever this window does. The pin fixture uses a reverse-domain id for that reason. --- _Generated by [Claude Code](https://claude.ai/code/session_01ARcDurZ5j34RdqsGgc4jgH)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Fixes #20273
Clause-②: no (narrowing)
Retires the connector resilience family under ADR-0049 enforce-or-remove, one batch, by the triage verdict RETIRE (comment 5858520070) under the maintainer's criterion on #18900:
connector.health(thehealthCheckprobe, eight keys, and thecircuitBreaker, six keys),connector.statusand the connector-nestedwebhooks— sixteen authorable keys that nothing read. Authoring any of them is now a tsc error and a parse error that carries the prescription; no alias window.Census first (origin/main 3f86dc5, each zero beside a lit control)
packages/spechealth.healthCheck.*/health.circuitBreaker.*leavescircuitBreaker,fallbackStrategy,halfOpenMaxRequests,unhealthyThreshold,healthyThreshold,monitoringWindowMs,resetTimeoutMsoutside generated docs;healthCheckonly as the kernel plugin-health contract; no.health.read inpackages/connectorsorservice-automation)retryConfigread 17 times inpackages/connectors+service-automationstatus.statusreads, every one on an HTTP answer, an error case or a flow-run entryrequestTimeoutMsread off a connector entry / provider context 5 timeswebhooksstack.webhooksread 5 timesobjectui: nothing imports a removed name at the pinned
.objectui-shaf8a9d0fb (one comment mentionsWebhookEventSchema), and no connectorstatus/health/webhooksreader at objectui main 610819c40. No key had a live reader, so nopremise_still_validfork.What changed
health,status,webhooksareretiredKey()tombstones on the privateConnectorBaseSchemathat both published carriers wrap (ConnectorSchema,DeclarativeConnectorEntrySchema), sodefineConnector,registerConnector,stack.connectors[]andPUT /api/v1/meta/connector/:nameall refuse them. SixRETIRED_KEYS_BY_MAJOR[18]rows.statuswas.default('inactive'), emitted by every 17.x parse into every connector;status: 'inactive'joinsconnectionTimeoutMs: 30000inCONNECTOR_RETIRED_KEY_RESIDUE(accepted and stripped). Every other value is refused.RETIRED_DEFS_BY_MAJOR[18]):ConnectorHealth,HealthCheckConfig,CircuitBreakerConfig,ConnectorStatus,WebhookConfig,WebhookEvent,WebhookSignatureAlgorithm. The manifest keys and baseline rows were deleted deliberately after the build named them.connector-resilience-keys-removed(step 18, retired from the load path): strips the three keys fromconnectors[]and from stored rows, one notice per key; nested webhooks are stripped, never moved.connector-resilience-keys-retired(one per family, ruling B on [Decision] 一次退役,要写一条记录还是两条?—— 迁移条目的 D2/D3 约定,两处成文相互矛盾 #17152), naming the D2 and the chain below.status: 'active'in the four shipped connector packages andstatus: 'error'on the automation service's degraded husk were writes nothing read back; tsc found the husk's test fixture too.packages/spec/docs/SYNC_ARCHITECTURE.md: the "Monitoring: Health checks" tick is gone, and so are the ticks and example lines this retirement made false (connector webhooks, circuit breaker,status: 'active'); the doc's compile gate (connector-author-shape.test.ts) holds the example.automation/webhook.zod.ts: its connector-webhook note is corrected, and theextraKeys: ['signatureAlgorithm']suggestion is dropped (the only surface accepting that key is gone, so a typo on the delivered webhook would have been pointed at a refused key).statusandwebhooksstay onedeadrow each, now tombstones;connector/webhooksleft the undrilled baseline (the gate called it stale);state-counts.mdand the README notes cell regenerated / corrected (dead 44 to 30).migrations/registry.ts,api-surface/,declaration-map/,export-origins/,authorable-surface/,authorable-defaults/,json-schema.manifest/,content/docs/references/**, strictness counts,test-typecheck-debt.json(shrink only).spec-changes.jsonanddocs/protocol-upgrade-guide.mddo not move: they fold majors up toPROTOCOL_MAJOR17, and this is step 18 (check:spec-changes/check:upgrade-guidegreen).docs/adr/0122-...mdis NOT edited: no gate forced it.type-alias-convention.pin.test.tsloses the three isomorphic pins of the removed enums (783 to 780 on the merged tree; [finding] four more exported spec types resolve tounknownwhile their TSDoc promises a shape —ViewMetadataParsed,InlineAction,AssembledViewArtifact,JoinedReportBlock(the #19871 class, other sites) #19920 landed first with its own 786 to 783), the way the error-mapping precedent did.Deviations from the dispatch's mechanism assumptions (measured)
ConnectorHealth/ConnectorStatusto leave viaRETIRED_DEFS_BY_MAJOR. Both cannot hold for the fourteenhealth.*leaves: onceConnectorHealthleaves, its leaves have no shape to carry a tombstone. I followed the error-mapping precedent (13c48c2): one carrier tombstone per key the walk still reaches (health,status,webhooks), defs whole.health.*rows cannot stay: withhealtha leaf,check:livenessrefuses them (measured:connector/health (declared children but property is not a container)).healthis onedeadtombstone row whose note carries the fourteen verdicts and their census.connector-health-and-trigger-durations-unit-in-keyis impossible under the conversion table's disjoint-fixture contract: the rename's own fixture carries ahealthblock that the removal strips. Perspec-property-retirement§0 (same unreleased step) the breaker half is ABSORBED: the rename now carries onlytriggers[].interval, and the removal serves an author holding either spelling (a pin replaysmonitoringWindowplus a triggerintervaland gets exactly one rename notice and one removal notice).plugin.ts:1792was not comment-only. The line under that comment WROTEstatus: 'error'into the husk def, which the tombstone makes a tsc error and a registration-time parse refusal; the write is deleted and the comment corrected.automation/webhook.zod.ts(orphanedextraKeys), plugin-webhooks' docblock and its pin test's docblock (they quoted the retired spec prose),rest-server.test.ts(a stale comment),connector-author-shape.test.ts,type-alias-convention.pin.test.ts.Acceptance notes
mainwas merged here and its D3 entryconnector-resilience-durations-unit-in-keyis reconciled in this PR: it now prescribes onlytriggers[].intervaltointervalSecondsand tells an author holdingmonitoringWindow/monitoringWindowMsthat the renamed key is itself retired (delete thehealthblock).triggers[].intervalis untouched otherwise.@objectstack/specis not measured.Evidence (head 153f652)
pnpm --filter @objectstack/spec buildgreen (manifest and baseline gates fired on the seven defs first, as they must on a whole-def removal, then passed once the rows were deleted deliberately).check:generated: all 15 artifacts current.check:liveness,check:migration-registry,check:spec-changes,check:upgrade-guidegreen.node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandsat 153f652, reconciled with--ran: 118 derived, 113 run with exit 0, 5 NOT MEASURED —check:skill-examples,check:dual-build-cjs-loads,check:i18n,check:type-check-debtrefused with exit 3 (PREREQUISITE NOT MET: builds outside this diff's closure — client-react, the CLI plugin set, the whole monorepo),check:query-options-erasure(repo-wide ESLint scan; self-test passed, the ratchet outran a 300s per-gate bound — exit 124). CI runs all five.--project localoversrc/migrations,src/conversions,src/integration, the ADR-0122 pin,rest-server,webhook,stack— 18 files, 777 passed; spec--project repo(this PR's pin, the connectionTimeoutMs pin, the migrate-sentence pin, two sibling tree-scoped pins, three reference-tree scripts) — 8 files, 221 passed.--project local552 files / 16265 passed;service-automation146 files / 1757 passed; connector-mcp / -openapi / -rest / -slack and plugin-webhooks 27 files / 255 passed; typecheck of those six packages green; dogfoodexpression-conformance7 passed.--no-inline-config --format json): 32 files, 0 errors, 0 warnings. Population:eslint.config.mjsflat config over**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}; the config enables no type-aware linting (noparserOptions.project), so this diff cannot move a verdict in an untouched file. The repo-widepnpm lintis CI's.Ablation (one per closed door)
Via
node scripts/ablation-replace.mjs(anchor must hit, restore proven by blob hash equal to HEAD and an emptygit diff HEAD), on committed head 849fb62, runningconnector-resilience-keys-retirement.test.ts:healthhealth, the three-door refusal, the either-spelling breaker refusal, the walked-shape pinstatusstatus, the three-door refusal, the non-default-value refusal, the walked-shape pinwebhookswebhooks, the three-door refusal, the walked-shape pinEach leg restored to blob 704684dc6f0c (HEAD's). No permanent ablation test is left.
Generated by Claude Code