Repository navigation
Commit 0511341
Fixes #23083
Clause-②: no
## What was wrong
`GET /api/v1/openapi.json` folds the declared `api` items into the
document through `selectDocumentableEndpoints`
(`packages/rest/src/openapi-endpoints.ts`). Where no endpoint matcher is
reachable, the handler passes it the items `protocol.getMetaItems({
type: 'api' })` serves. That read exits through `decorateMetadataItems`,
which stamps `_diagnostics` on every `api` item. `ApiEndpointSchema` is
closed, so the reader refused the platform's own badge by name
(`unrecognized_keys: ["_diagnostics"]`): every valid declaration was
left out of the document and logged at `error` as a declaration to fix.
## The change
- `selectDocumentableEndpoints` runs `stripReadDecorations`
(`@objectstack/spec/kernel`) before `ApiEndpointSchema.safeParse`. It
removes exactly `METADATA_READ_DECORATIONS` (`_diagnostics`, `_draft`),
the same strip the seed door applies. There is no local key list and no
blanket underscore strip, and the schema is unchanged.
- The ADR-0010 envelope keys (`_packageId`, `_provenance`, and the rest)
are allowlisted by the schema and parse exactly as before. Any other
unknown key is still refused.
- A truly invalid item is still omitted and logged at `error`. The log
line's second sentence ("The endpoint matcher excludes it too, so the
route it declares answers 404") is gone, because it was measured false
(below). The line now reads: `[REST] stored api item 'NAME' does not
satisfy ApiEndpointSchema — it is OMITTED from the OpenAPI document; fix
the declaration rather than the document.`
- Changeset: `@objectstack/rest` patch, carrying `Clause-②: no`.
## Reach, measured on this tree (base `62ce45d140`)
The caller is the `GET {basePath}/openapi.json` handler, step 4
(`rest-server.ts`). Its read is `protocol.getMetaItems({ type: 'api'
})`, served audience. The stamping site is `decorateMetadataItems`
(`packages/metadata-protocol/src/protocol.ts`, the `audience ===
'served'` exit). The raw items reach the reader only when
`resolveEndpointMatchAuthority` finds no `metadata` service with
`matchEndpoint`. With a matcher, the reader gets the matcher's own
parsed endpoints, which carry no decoration, and the strip is a no-op
that returns the same reference.
| composition | metadata slot has `matchEndpoint` | declared paths in
`openapi.json` | omission lines at `error` |
|:--|:--|:--|:--|
| `@objectstack/verify` `bootStack(showcase)`, before | no (kernel
in-memory fallback) | 0 of 2 | 2 |
| same, after | no | 2 of 2 | 0 |
| `os serve objectstack.config.ts` (showcase), after (CONTROL) | yes
(`MetadataPlugin` since #23078) | 2 of 2 | 0 |
- On `bootStack`, `GET /api/v1/meta/api` answers both items with keys
`_packageId`, `_packageVersion`, `_provenance` and `_diagnostics:
{"valid":true}`.
- On both runs `bootStack` still logs the once-per-server notice "no
endpoint matcher is reachable … which may advertise routes that answer
404". That is a different line, owned by `rest-server.ts`.
- On `bootStack` the declared route itself answers `GET
/api/v1/apps/showcase/tasks` with `404 ENDPOINT_NOT_FOUND`, because
nothing serves declared endpoints without a matcher. The document now
lists both routes there, which is the stored-items fallback that notice
describes. On `os serve` the same route answers `200`.
- `_draft` does not reach this reader today: the handler passes no
preview mode to `getMetaItems`. The `_draft` pin covers robustness only.
Before, `bootStack`, one of the two lines:
```text
[REST] stored api item 'showcase_task_feed' does not satisfy ApiEndpointSchema — it is OMITTED from the OpenAPI document. The endpoint matcher excludes it too, so the route it declares answers 404; fix the declaration rather than the document. {"issues":[{"code":"unrecognized_keys","keys":["_diagnostics"],"path":[], …}]}
```
After, `bootStack`: no line names either endpoint.
## The matcher sentence, measured
The real `MetadataManager` holds the STORED item, and `matchEndpoint` is
asked. The reader gets what the openapi.json caller hands it. Both
measurements were taken on this tree before the change:
| case | matcher serves | reader documents (before) | reader's refusal |
|:--|:--|:--|:--|
| A: valid item through `decorateMetadataItems` | yes | no |
`unrecognized_keys: _diagnostics` |
| B: valid item with `_draft: true` | yes | no | `unrecognized_keys:
_draft` |
| C: truly invalid (`type: 'nope'`) | no | no | `invalid_value: type` |
| D: valid body in a flat stored envelope (`packageId`, `state`) | yes |
no | `unrecognized_keys: packageId,state` |
| E: ADR-0010 `_packageId` | yes | yes | none |
The sentence held only in case C. The matcher peels the storage envelope
before it parses, and on the composition where this reader sees raw
items there is no matcher at all. After the change, A and B are
documented, and C and D are still refused.
## Tests, at `76f507c0ee`
- New file
`packages/rest/src/openapi-endpoints-read-decorations.test.ts`, 7 cases.
Its fixtures are the real producer's output (`decorateMetadataItems`
from `@objectstack/metadata-protocol`).
- Pins: two valid items decorated by the served read are documented and
nothing is logged; an item carrying `_draft` is documented; both
decorated items become path entries; and `GET /openapi.json` with no
matcher wired lists both endpoints, with no `console.error` line naming
either.
- CONTROLS: a decorated, truly invalid item is still omitted and logged,
and its issues name `type` with no `unrecognized_keys`; `_packageId` and
`_provenance` parse to exactly `ApiEndpointSchema.parse(item)`,
decorated or not; `_unexpected` is still refused as `unrecognized_keys`.
- `pnpm --filter @objectstack/rest test`: `Test Files 275 passed (275)`,
`Tests 5758 passed | 326 skipped (6084)`, lock `VERDICT command-exit 0`.
- `pnpm --filter @objectstack/rest typecheck`: exit 0.
`check:test-typecheck: OK — @objectstack/rest's test layer compiles
under packages/rest/tsconfig.test.json; 0 file(s) / 0 error(s)`. `tsc -p
tsconfig.test.json --listFiles` includes the new file, 0 errors.
- Ablation, committed first. `scripts/ablation-replace.mjs` replaced
`ApiEndpointSchema.safeParse(stripReadDecorations(item))` with
`ApiEndpointSchema.safeParse(item)`: anchor 1 to 0, blob `45e2bea3bfc8`
to `258febe723a5`. It ran inside the verify lock under an EXIT/INT/TERM
restore trap. No build was needed, because the tests import the subject
from `src` by relative path. The direction was predicted before the run:
6 red, the `_unexpected` control green, the existing file green.
Observed: `Tests 6 failed | 27 passed (33)`, exactly those 6. Restore:
blob `45e2bea3bfc8` equals HEAD and `git diff HEAD` is empty.
- Gates: `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derived 62 commands, identical with and
without explicit paths. All 62 exit 0. `check:dual-build-cjs-loads`
first exited 3 (`PREREQUISITE NOT MET`, 14 packages had no `dist/`) and
exited 0 after the cli closure was built. `--ran`: `62 derived
famil(ies) accounted for — 62 run, 0 NOT-MEASURED`.
- Lint, by proven narrowing (CI runs `pnpm lint`):
- eslint `--no-inline-config --format json` over the 2 touched `.ts`
files: 2 files, 0 errors, 0 warnings.
- `--print-config` gives both files a config (6 and 5 rules). The
changeset `.md` is outside eslint's population (`undefined`).
- `eslint.config.mjs` enables no type-aware linting (no
`parserOptions.project`, no `projectService`), and this diff touches
none of its inputs. So no untouched file's verdict can move.
## Acceptance notes
- Not filed, carrier none: case D above. A valid body in a flat stored
envelope (`packageId` / `state`) is refused by this reader while the
matcher serves it, and the log still says to fix the declaration. No
producer that hands this reader such rows was found, so its reach is
unmeasured.
- Not filed, carrier: the next PR to touch this file. The module
docblock "What it is HANDED" says the production caller passes the
matcher-confirmed set. That holds only when a matcher is reachable.
Without one, it passes the served items, which is the path this change
repairs.
- This branch is one commit behind `origin/main` (`0aef788816`,
`plugin-hono-server` only, disjoint from this diff). It is not merged.
- Not run locally: `pnpm --filter @objectstack/rest test:repo`. It is
declared to CI.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01VoSxBQujKLZKPwK2u5ehQ6)_
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 968acc8 commit 0511341
3 files changed
Lines changed: 277 additions & 3 deletions
File tree
- .changeset
- packages/rest/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
90 | 90 | | |
91 | 91 | | |
92 | 92 | | |
| 93 | + | |
93 | 94 | | |
94 | 95 | | |
95 | 96 | | |
| |||
317 | 318 | | |
318 | 319 | | |
319 | 320 | | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
320 | 332 | | |
321 | 333 | | |
322 | 334 | | |
| |||
325 | 337 | | |
326 | 338 | | |
327 | 339 | | |
328 | | - | |
| 340 | + | |
329 | 341 | | |
330 | 342 | | |
331 | 343 | | |
332 | 344 | | |
333 | 345 | | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
334 | 350 | | |
335 | 351 | | |
336 | | - | |
337 | | - | |
| 352 | + | |
338 | 353 | | |
339 | 354 | | |
340 | 355 | | |
| |||
0 commit comments