Repository navigation
fix(client): the published README's packages.install example parses against ManifestSchema - #18771
Merged
os-support-ai merged 2 commits intoSep 17, 2026
Merged
Conversation
…gainst ManifestSchema The example in the `@objectstack/client` README — which ships in the npm tarball — was refused on three counts by the contract its own call site declares (`PackageInstallRequestSchema`, `manifest: ManifestSchema`): `invalid_type` at `[manifest, id]`, `invalid_value` at `[manifest, type]`, and `unrecognized_keys` at `[manifest]` for `label`, a key the root shape's `strictObject` close refuses by name. `label` is not a root manifest key: the root shape declares `name` for the human-readable string, so the example's `label` value moves there and the machine identifier becomes the reverse-domain `id` the key documents. `type: 'plugin'` is the enum member the example's own subject names. Pinned by a new test that parses every `packages.install` manifest literal in this README against that schema, and fails when the corpus is empty. Claude-Session: https://claude.ai/code/session_01DvvamiacK328idtBYJBxV3 Co-authored-by: Claude <noreply@anthropic.com>
Contributor
📓 Docs Drift Check
What this run could not see
Coarse fallback — 15 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
This was referenced Sep 17, 2026
os-support-ai
marked this pull request as ready for review
September 17, 2026 20:29
os-support-ai
enabled auto-merge
September 17, 2026 20:29
os-support-ai
deleted the
claude/issue-18607-client-readme-install-example
branch
September 17, 2026 20:57
os-litant
pushed a commit
that referenced
this pull request
Sep 17, 2026
…eclare the union's dropped refinements `main` landed #18771, which fixes the same README example and adds `readme-package-install-example.test.ts` — a corpus reader over every `packages.install` literal in the published README. Two files reading the same README to ask the same question is a duplicate owner, so the F4 additions move INTO that file and the branch's own copy is dropped: the corpus is now parsed against `PackageInstallBodySchema` wrapped exactly as `packages.install` sends it and bare as the door reads it, and the pre-#18058 literal is pinned refused so a schema relaxation cannot make the block vacuous. The README keeps main's landed literal (`type: 'plugin'`, no deprecated `namespace`) plus this card's prose and the `overwrite` opt-in line. `api/PackageInstallBody` is a new published schema whose bare branch carries the `navigationContributions` refinements `z.toJSONSchema()` cannot project, so `dropped-refinements.baseline.json` — which landed on main after this branch forked — gains the entry `build-schemas.ts` printed, beside the identical `api/PackageInstallRequest` and `api/InstallPackageRequest` rows. Claude-Session: https://claude.ai/code/session_01LvwGppdonww4zGLWZo5rho Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #18607
The
client.packages.installexample in the published@objectstack/clientREADME shipped a manifest that the contract its own call site declares refuses on
three counts. The example is what was wrong;
ManifestSchema's strict close iswhat is right, so nothing in
packages/specmoves here.BEFORE — the three refusals, reproduced
Driven against
PackageInstallRequestSchema(whosemanifestkey isManifestSchema), with the literal read out of the README by content, not byline number:
All three reproduce exactly as the card states them.
AFTER — parsed and accepted
Parsed value, defaults applied:
{"id":"com.vendor.plugin","defaultDatasource":"default","version":"1.0.0","type":"plugin","scope":"project","name":"Vendor Plugin"}Controls — each one had to fire, and each did
idinvalid_typeat[manifest, id]labelunrecognized_keysat[manifest]type: 'vendor'invalid_valueat[manifest, type]labelat the ROOT — what the schema actually declaresRead off the schema itself, not off a neighbour:
ManifestSchemadeclares 25root keys and
labelis not one of them. The required-and-absent set, takenfrom
ManifestSchema.safeParse({}), isid name type version.name— already present in the old example — is declared as the human-readablepackage name, so the old
label: 'Vendor Plugin'value moves toname, and themachine identifier becomes the reverse-domain
idthat key documents.The retired
{ id, label, path }shape carrying alabelnear this surface ismanifest.contributes.themes, a nested block removed in v17.x — a sibling ofthe root, never the root itself. A root
labelwas never declared and wastherefore never retired: it gets the strict close's plain unrecognised-key
refusal, with the surface's own history text attached.
Why
type: 'plugin'The closed set, quoted from the refusal the schema itself emits:
"plugin" | "ui" | "driver" | "server" | "app" | "theme" | "agent" | "objectql" | "module" | "gateway" | "adapter".pluginis the schema's own "general-purpose functionality extension", and it iswhat this example's subject already says it is — the literal was named
vendor_plugin/Vendor Pluginand the line beneath it enables a plugin id.appis the consumer-installable business bundle (ADR-0019, the one user-visiblenoun a tenant browses and installs) and would change what the example teaches;
gatewayis marked deprecated in the enum's own docblock. Not the first memberthat parses — the member the example means.
Duplicate copies — reported, NOT silently folded in
Swept the tree for the same example and for the same defect class.
vendor_pluginandVendor Plugineachoccur exactly once in the repository, both in this README.
content/docs/api/client-sdk.mdxline 337 —await client.packages.install({ id: 'com.objectstack.plugin-auth', version: '1.0.0' });— refused on two counts,
invalid_valueat[manifest, type]andinvalid_typeat[manifest, name]. It is a hand-written page (it is listed inscripts/docs-audit/handwritten-docs.json). Left for a ruling rather thansilently widened or silently left broken.
packages/client/src/return-type-precision.test.tscarries thesame two-count shape, but as a
expectTypeOfpin against a parameter typedany; nothing parses it and it is not published material.label:keys undercontent/docs/references/api/belong to sibling collections (
apps,pages,flows,agents, …) whicheach declare
labellegitimately. That is the root-versus-sibling distinction,confirmed rather than assumed.
packages/client/CHANGELOG.mdalso quotes an install call. Release-owned —untouched, by rule.
Is there already a pin? Measured, with a control
No — and the two nearest instruments are provably blind to this class.
scripts/measure-markdown-ts-blocks.mjscompiles the fenced TypeScript blocks ofpackage-root Markdown, which is exactly this file. Run on this README before and
after the fix, its per-block diagnostic sets are byte-identical:
That is a lit instrument, not a dark one: it reports live diagnostics on other
blocks of the same file in both runs. It cannot see this defect because
installdeclares its first parameterany(
packages/client/src/index.ts,install: async (manifest: any, …)), sotschas nothing to check. It is also a census by its own header, wired to no CI job.
check:published-readme-exportshas the right population but reads fenced blocksfor imported symbols and has no notion of a schema.
check:skill-examplestype-checks marked prose blocks on aclient SDKsurface,but
packages/client/README.mdis not in its population at all, and theclient.packages.installblock on the one SDK docs page that is carries nocheck marker.
So this PR adds the pin:
packages/client/src/readme-package-install-example.test.tsextracts every
packages.installmanifest literal from this README with theTypeScript AST and parses it against
PackageInstallRequestSchema. It refuses topass on an empty corpus, and it throws rather than skipping on an object member it
cannot model.
Ablation — the pin can fail, proven from the committed state
Restored the original broken literal, proved the mutation on disk (injected text
count 1, removed text count 0, blob
93c5186941…distinct fromHEADblob59ea1c0638…), ran the pin:The
1 passedis the anti-vacuity floor still finding the corpus, so the red isabout the manifest and not about a lost anchor. Restored with
git checkout HEAD -- packages/client/README.md; the restored blob hashes back to59ea1c0638…andgit diff HEADplusgit status --porcelainare both empty.Verification
pnpm --filter @objectstack/client testpnpm --filter @objectstack/client typecheckpnpm --filter '@objectstack/client^...' build--ran)Two gate families first answered PREREQUISITE NOT MET on an unbuilt checkout
(
check:skill-examples,check:dual-build-cjs-loads); both exit 0 afterpnpm build. Readings taken ata37583f9c1.The eslint narrowing is a measurement, not a skip. Population, read from
eslint.config.mjsitself: files matching**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}minus the config's five global ignores — so the two Markdown files in this diff are
outside eslint's population by the config's own selectors. Count, read from
--format json: 1 file. Invariance, quoted from that config: "this repo runs oneeslint.config.mjs, which never enables type-aware linting (noparserOptions.project, no typed@typescript-eslintrules) for ANY file, test ornot" — so this diff cannot move the verdict on any untouched file.
Changeset
patchon@objectstack/client, measured rather than assumed: the package'sfiles[]is["dist", "README.md", "CHANGELOG.md"]and the package is notprivate, so this README is published output and
skip-changeset— the labelfor a diff that publishes nothing from any released package — does not apply.
Clause-②: no
No schema key, no closed-set member, no published export and no registry entry is
added or narrowed; the only TypeScript this PR adds is a test.
Acceptance notes
together with, [finding]
PackageApiContracts.installPackagebindsPOST /api/v1/packages/install— a path nothing mounts; the live install door isPOST /api/v1/packagesand it has no declared request contract #18604 / [finding]overwriteis a live body key at the install door — sent by the SDK, read by the handler, pinned both ways — and declared by no schema; any declared parse strips it and turns a deliberate re-install into a 409 #18606 — closing that door without this repair turns asilently-wrong published example into a loudly-broken one for every reader who
copied it. Noted for the landing seat; not something this PR can enforce.
Clause-②declaration — read withthe repo's own
readClause2Line, which returnsnullfor it. The declarationabove is re-derived from this diff rather than inherited, and the divergence is
reported rather than quietly resolved.
noted, not filed:the next line of the same example enables'plugin-id'rather than the manifest id just installed. Cosmetic only —
enabletakes anystring and no contract is violated. No other PR or person is queued to touch
this file, so: successor — none.
noted, not filed:this README already carries three unrelated tolerant-failTypeScript blocks under the markdown census (TS2307 unpublished subpath, TS18046,
TS18004). Pre-existing, unchanged by this PR, and outside this card.
Generated by Claude Code
Two corrections added by the dispatching seat (#6024) at landing
The deliverer reported both of these rather than silently patching its own body, which is the right call — a body
PATCHwas outside its declared write budget. Appending rather than rewriting its text, so the record shows what was written when.1. The Acceptance notes say this card's claim comment carries no
Clause-②declaration. That was true of comment5719922216— the one the dispatch order named — but ⛔ it is no longer the governing claim. The seat later posted a shape-corrected claim,5720097817, which carriesClaim:, a separateBranch:directive line, andClause-②: no; by the repo's own selection rule (newest claim parsing a branch) that one governs. The deliverer's re-derivation from the measured diff is alsono, so there is no divergence:check-clause2-carriers --pair 18771exits 0 with both carriers agreeing.2. The "duplicate copies" section predates the shape-based sweep and names only
client-sdk.mdx. The completed sweep — 1549 files, a predicate about the manifest shape rather than the call name, literals extracted with the TypeScript AST, and an unmodellable member throwing rather than being skipped (0 throws) — found six further live sites of the same defect class, not one:content/docs/api/client-sdk.mdx:337— one morepackages.installexample, refused on 2 counts;defineStack({ manifest: … })snippets —kernel/services-checklist.mdx:473,permissions/authorization.mdx:176,permissions/capabilities.mdx:103,permissions/record-view-auditing.mdx:86,docs/design/marketplace-publishing.md:398.⭐ The sweep's headline result is unchanged and is the one that matters for this PR: the exact broken example is duplicated nowhere — zero root-level
labelkeys across 41 judged manifest literals — and 7 further literals refuse only because they are declared partials, correctly excluded as non-findings.⛔ This PR was not widened into any of the six. They are filed separately and deliberately as two cards, because their remedies differ and one card would let the easy half carry the hard half: #18776 (the install example, one obviously-correct repair) and #18777 (the five
defineStacksnippets, whose remedy is a docs-convention ruling — complete the manifest, or mark it elided as their 7 siblings do).5716545361, checked by the seat before arming: this card must land before or with #18604 / #18606. Both are stillpm:queue, open and unassigned ⇒ landing now satisfies it.Generated by Claude Code