Repository navigation
Commit e7eb4e9
docs(api): make the client-sdk packages.install example parse as a manifest (#18803)
Fixes #18776
## What was wrong
`content/docs/api/client-sdk.mdx:337` — the `client.packages` fenced
block on the SDK page an integrator reads before the README — showed:
```ts
await client.packages.install({ id: 'com.objectstack.plugin-auth', version: '1.0.0' });
```
Parsed against the contract that door itself declares —
`PackageInstallRequestSchema`, whose `manifest` key is `ManifestSchema`
(`packages/spec/src/kernel/manifest.zod.ts:244`) — the literal is
refused twice:
```
invalid_value at [manifest, type]
invalid_type at [manifest, name]
```
`type` and `name` are both required root manifest keys and both were
absent, so the example fails when copied verbatim. Same kind as #18607,
one page along.
## The repair
Two required keys added, nothing else, in the key order the package's
own published README uses for the same door
(`packages/client/README.md:247`, landed by #18607) so the two copies of
this example agree:
```ts
await client.packages.install({
id: 'com.objectstack.plugin-auth',
type: 'plugin',
name: 'Auth Plugin',
version: '1.0.0',
});
```
⛔ No schema was touched. This card is an example that is wrong, not a
contract that is wrong, and the measurement below did not invert that.
## Triage's escalation probe — the second deliverable
Triage attached a mandatory escalation condition: parse every
install/bootstrap example across `content/docs/api/**`, and if more than
this one is refused, the finding is not one broken example but a
published-example surface with no gate.
**Method** (⛔ not an eye-pass). A throwaway AST probe over all 13 files
of `content/docs/api/**`: `ts/typescript/tsx` fences are parsed with the
TypeScript compiler API, every call site in the population below is
located mechanically, and its argument literal is parsed against the
contract that call site declares. An object member shape the reader does
not model **throws** rather than passing quietly, and an empty
population **throws** — a probe that measures nothing must not read as
green.
| door | population | contract it is parsed against |
|:---|---:|:---|
| `packages.install(<literal>)` | 1 | `PackageInstallRequestSchema`
(`manifest: ManifestSchema`) |
| `packages.enable/disable/uninstall(<arg>)` | 3 | the declared `id:
string` |
| `packages.list(<arg?>)` | 2 | the declared `filters?: { status, type,
enabled }` |
| `new ObjectStackClient(<literal>)` | 4 | `ClientConfig`, read out of
`packages/client/src/index.ts` itself |
| `defineStack(<literal>)` | 2 | `ObjectStackDefinitionSchema` |
| shell fences `pnpm add` / `npm install` | 2 | the named workspace
package exists and is not `private` |
| **total** | **14** across 3 files | |
**Positive control** — the probe must catch the refusal we already know
about, or it is not measuring this. It does:
```
BEFORE tree /home/user/objectstack-18776-base @ 6de7a2d (origin/main at the branch point)
population: 14 example(s) across 3 file(s)
REFUSED: 1
content/docs/api/client-sdk.mdx:337 [packages.install]
-> invalid_value at [manifest, type]
-> invalid_type at [manifest, name]
AFTER tree /home/user/objectstack-issue-18776 @ fd887ab
population: 14 example(s) across 3 file(s) (unchanged — the repair moved a verdict, not the corpus)
REFUSED: 0
```
**Verdict: exactly one refusal across `content/docs/api/**`, and it is
the one this card names.** The escalation condition is **not met** —
nothing here re-grades the card or calls for the separate "published
examples have no executability gate" carrier. Two things are reported
rather than silently excluded:
- `content/docs/api/metadata-api.mdx:108` and `:116` carry the install
and publish request bodies as **inline prose with an explicit ellipsis**
— `{ manifest: { id: "plugin-auth", name: "Plugin Auth", version:
"1.0.0", ... }, ... }`. They omit `type`, but the ellipsis is written
in, they are not valid TS/JSON as printed, and nothing can be copied
verbatim out of them. On this lane's boundary that is 不完整, not 错误 —
outside the probe's parseable population and left alone.
- `content/docs/api/environment-routing.mdx:31` is a `defineStack`
example whose manifest is an explicit `// ...your manifest, objects,
apis, etc.` placeholder. It is accepted by `ObjectStackDefinitionSchema`
as written (that key is optional), so it is a pass, not a waiver.
⛔ No gate was built, wired or modified. The card measured why three
existing gates cannot see this class and recorded that wiring a census
into a **required** gate is a maintainer's floor decision; this PR does
not take that decision, and it deliberately does not extend #18607's
README pin to this page for the same reason.
## Verification
- **Gate families** — `node scripts/pm/dispatch-gates.mjs --commands
--repo objectstack-ai/objectstack` derives **39** families for this
change set (1 path). All 39 run on the final commit `fd887ab61`, each
exit code captured before any pipe: **39 exit 0**. `--ran`
reconciliation: `39 derived, 39 run, 0 NOT-MEASURED, 0 UNRUN (a DERIVED
zero)`.
- Four of them first answered a **prerequisite**, not a finding —
`check:doc-formula-expressions`, `check:doc-security-posture` and
`check:docs-transcript-drift` exited **3** (`@objectstack/lint` /
`@objectstack/formula` not built) and `check:skill-examples` exited 1 on
the same shape (`packages/client-react/dist` holds no `.d.ts`). Built
what each one named, re-ran, all four exit 0. `check:skill-examples` is
the gate whose `SDK_DOCS_PAGES` population contains this page.
- **`pnpm lint` is CI's run; the local narrowing is measured, not
skipped.** ① The universe read from eslint's own config: every `files:`
block in `eslint.config.mjs` names `{ts,tsx,mts,cts,js,jsx,mjs,cjs}` and
**zero** of them name `md` or `mdx`. ② `eslint --no-inline-config
--format json content/docs/api/client-sdk.mdx` reports 1 file, 0 errors,
1 warning — *"File ignored because no matching configuration was
supplied."* ③ Invariance: neither `projectService` nor
`parserOptions.project` appears in `eslint.config.mjs`, so type-aware
linting is off and this diff cannot move the verdict on any untouched
file — and the changed file is outside the linted set entirely.
- **`check:pm-dispatch-gates`** is not in this change set's derived
families (it is not reachable from a `content/docs/**` path), so its
detached-run prescription does not apply here.
- **Merged `origin/main` at `9846f2763`** before opening; no conflict,
and nothing on `main` had touched this file since the branch point.
Re-derived the families from the merged tree with `main`'s newer
`scripts/pm/dispatch-gates.mjs`: the same 39, no additions. Rebuilt
`spec` / `lint` / `formula` / `client-react` after the merge and re-ran
all 39 on the merge commit — the reading above is that run.
- **No same-file collision with #18792**, which edits `:767-783` of this
file. Untouched here.
## Changeset — measured, not defaulted
`skip-changeset`. The criterion is whether anything published moves.
- **Positive control** (the instrument can say "this ships"): `npm pack
--dry-run --ignore-scripts --json` in `packages/client` lists
`README.md` in the tarball — a documentation file that really does
publish, and the one that carried the sibling instance in #18607.
- **Reading**: that same listing contains **zero** `content/docs`
entries; no `package.json` in the repo declares a `files[]` entry naming
`content/docs` or escaping its own directory; `content/` contains no
`package.json` at all; and its only consumer, `apps/docs`, is `private:
true`. Nothing copies `content/docs/**` into a tarball at build time.
⇒ this diff publishes nothing from any released package.
## Acceptance notes
- `content/docs/api/metadata-api.mdx:108` / `:116` omit the required
`manifest.type` inside an explicitly-abbreviated inline body. Noted, not
filed: 不完整, not an example that fails when copied, and the carrier for
the class (published examples parsed by nothing) is the open question
already recorded on #18607's PR — not a new card from this one.
- The probe lives in this session's scratchpad and is deliberately not
committed: turning it into a test is the census-into-a-required-gate
decision this card says is the maintainer's.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_017ef78bLdybu3AffehKkhfk
---
_Generated by [Claude
Code](https://claude.ai/code/session_017ef78bLdybu3AffehKkhfk)_
Co-authored-by: claude[bot] <noreply@anthropic.com>1 parent e08fae8 commit e7eb4e9
1 file changed
Lines changed: 6 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
334 | 334 | | |
335 | 335 | | |
336 | 336 | | |
337 | | - | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
338 | 343 | | |
339 | 344 | | |
340 | 345 | | |
| |||
0 commit comments