Skip to content

test(spec): downstream-consumer contract — backward-compat gate for third parties (#2035) - #2089

Merged
xuyushun441-sys merged 1 commit into
mainfrom
spec/external-consumer-gate
Jun 21, 2026
Merged

test(spec): downstream-consumer contract — backward-compat gate for third parties (#2035)#2089
xuyushun441-sys merged 1 commit into
mainfrom
spec/external-consumer-gate

Conversation

@xuyushun441-sys

Copy link
Copy Markdown
Contributor

Follow-up to #2035 / #2088. Builds recommendation #1 from that discussion: the missing gate that catches a spec change breaking a lagging third-party consumer before release.

The gap this closes

Every spec consumer the framework currently tests — the example apps, @objectstack/dogfood — lives in this monorepo and co-evolves with the spec in the same commit. When a spec change would break them, the same PR just fixes them. So none of them can catch a change that breaks a real third party (e.g. hotcrm) that is pinned to a published release and authors metadata independently. grep hotcrm .github/ → 0 hits today.

What this adds

@objectstack/downstream-contract — a frozen fixture authored the way an external consumer does: the ObjectSchema.create builder, the defineX factories, and bare metadata literals (: ActionInput / : ReportInput / : PageInput — the pre-#2035 pattern real third parties used). It is governed by a freeze contract (see the package README):

A spec change that requires editing the fixtures to stay green is, by definition, a breaking change for third parties.

Two signals, both riding existing CI jobs (no new workflow):

SignalCatchesWired into
typechecktype-surface breaks (removed/narrowed export — the #2023 class)lint.yml → TypeScript Type Check
testruntime schema + cross-reference breaksturbo run test → Test Core

The test runs each bare-literal fixture through its schema's .parse() and assembles all metadata via defineStack (schema + cross-reference validation — the metadata core of objectstack validate).

Grounding (done for real, not theory)

Before writing this I overlaid the merged spec build into a local checkout of hotcrm (real external consumer on published 9.11.0) and ran its own gates: tsc --noEmit → 0 errors, objectstack validate → ✓ passed (15 objects / 15 pages / 10 reports / 11 actions / 17 flows). hotcrm also proved the original thesis — it uses defineView() but authors pages/reports/actions/flows as bare literals (incl. : Action, the output-type footgun), exactly because the factories were missing. This package is the deterministic, in-CI floor under that manual check.

Verification (local)

  • pnpm --filter @objectstack/downstream-contract typecheck → exit 0
  • pnpm --filter @objectstack/downstream-contract test → 2 passed
  • turbo run test --filter=@objectstack/downstream-contract → picked up ✓
  • Teeth confirmed: ActionSchema.parse rejects a broken action type (not a no-op)

Follow-ups (not in this PR)

  • A spec public-API surface snapshot (api-extractor) for an even tighter type-break signal.
  • Optional release-pipeline step that runs hotcrm's live validate && typecheck && build against the unreleased spec.

🤖 Generated with Claude Code

…2035)
Adds `@objectstack/downstream-contract` — a frozen, representative third-party
consumer of `@objectstack/spec`, and the CI gate that runs it.
Why: every other spec consumer the framework tests (example apps,
`@objectstack/dogfood`) lives in this repo and co-evolves with the spec in the
same commit, so none can catch a change that breaks a *real* third party (e.g.
hotcrm) pinned to a published release. This fixture is authored the way an
external project does — builder + `defineX` factories + bare metadata literals
(the pre-#2035 pattern) — and is FROZEN: a spec change that requires editing it
to stay green is, by definition, breaking.
Two signals, both wired into existing CI jobs (no new workflow):
• typecheck — fixtures are typed with real spec types (`ActionInput`,
`ReportInput`, `PageInput`, …); a removed/narrowed export fails (the #2023
class). Added to lint.yml's TypeScript Type Check job.
• test — each bare-literal fixture is run through its schema's `.parse()`, and
everything is assembled via `defineStack` (schema + cross-reference
validation, the metadata core of `objectstack validate`). Rides
`turbo run test` (Test Core).
Verified locally: typecheck + test green; confirmed the schema parse has teeth
(rejects a broken action `type`). README documents the freeze contract.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercelBot commented Jun 21, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
specReadyReadyPreview, CommentJun 21, 2026 5:14am

Request Review

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation ci/cd dependencies Pull requests that update a dependency file tests tooling size/m labels Jun 21, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

No hand-written docs reference the 1 changed package(s). ✅

@xuyushun441-sys
xuyushun441-sys merged commit 92647c1 into mainJun 21, 2026
17 checks passed
@xuyushun441-sys
xuyushun441-sys deleted the spec/external-consumer-gate branch June 21, 2026 05:18
xuyushun441-sys added a commit that referenced this pull request Jun 21, 2026
…2035) (#2093)
The last layer of the third-party validation strategy from #2035: a live ceiling
above the deterministic in-repo floor (@objectstack/downstream-contract, #2089).
`scripts/downstream-smoke.sh` clones objectstack-ai/hotcrm at a pinned tag
(v1.2.0), installs it against the PUBLISHED @objectstack/* packages, overlays the
freshly-built — unreleased — @objectstack/spec dist, and runs hotcrm's own
`typecheck` + `objectstack validate`. If the about-to-publish spec breaks a real,
independently-authored third-party consumer, the release is blocked.
Wired into release.yml between the build and the changesets publish step, so it
runs only in the release pipeline (never reddens a feature PR). Mirrors the
existing console-SPA step that already clones an external repo at a pinned ref.
Verified end-to-end locally: clone hotcrm@v1.2.0 → install → overlay current
spec → typecheck + validate both pass (15 objects / 15 pages / 10 reports / 11
actions / 17 flows). Override the pinned ref with HOTCRM_REF.
Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
xuyushun441-sys added a commit that referenced this pull request Jun 21, 2026
) (#2095)
The depth gate (#2089) only exercised object/view/action/report/page. Adds a
frozen bare-literal fixture for the remaining domains — datasource, connector,
policy, sharing rule, role, permission set, email template, webhook, object
extension, cube, mapping, theme, translation bundle — typed with the spec's own
input aliases and parsed against each domain's schema.
Now a narrowed/removed schema property on ANY writable domain breaks the
contract (authoring it surfaced two already: EmailTemplate requires `bodyHtml`,
Cube requires `dimensions`). 15 cases, typecheck + test green.
Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
xuyushun441-sys added a commit that referenced this pull request Jun 21, 2026
…2097)
The #2088 guard only covered examples/**. Broadens it to the platform's own apps
(account/setup/studio) so the "author metadata via defineX, never a bare
`: DomainType` literal" convention holds across every authoring surface, not just
the example corpus. Explicitly excludes packages/downstream-contract, whose bare
literals are an intentional frozen backward-compat fixture (#2089).
No migrations — apps author no bare-literal metadata today; this is future-
proofing. Verified the guard fires on a probe under packages/apps and that
`pnpm lint` is green.
Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
xuyushun441-sys added a commit that referenced this pull request Jun 21, 2026
…ates (#2035) (#2098)
Documents the layered strategy built across #2088/#2089/#2092/#2093/#2095/#2097
in one authoritative place: why in-repo consumers can't witness backward compat
(they co-evolve with the spec), the six gates and each one's job, and — crucially
— the FREEZE CONTRACT: a change that requires editing the fixtures or removing a
snapshot entry is by definition breaking (bump major), never a mechanical
test-update. Also formalizes `objectstack validate` as the third party's
authoritative self-gate.
Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cddependenciesPull requests that update a dependency filedocumentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@xuyushun441-sys@os-zhuang