Commit 1378ec7
Fixes #20508
Clause-②: no
## What was wrong
On `RestServer`'s environment-scoped mount (`api.enableProjectScoping`),
the deprecated `?layers=true` spelling of the item read answered
`Deprecation: true` and a successor-version `Link` built from
`metaPath`. On that mount `metaPath` is the route template, so the
`Link` named the path
`/api/v1/environments/:environmentId/meta/view/lead_all/layers`, with a
literal `:environmentId` in it. A client that followed the header
requested that path.
## What changed
`packages/rest/src/rest-server.ts`, the `?layers=true` call site of
`metaItemLayersDeprecationHeaders` only. `RestServer` now passes the
request's own path (`IHttpRequest.path`), parsed as a URL path the way
the runtime dispatcher's `requestedItemPath` reads its request URL:
without the trailing slash. The shared helper in
`meta-item-read-gate.ts` is untouched (no template replace anywhere).
The parse matters for one measured reason: the Hono adapter hands
handlers a `decodeURI`'d path (`c.req.path`). A name requested as
`lead%20all` reaches the handler as `lead all`. The parse
percent-encodes it again, so the `Link` stays a valid URI reference. A
request that carries no path gets `Deprecation: true` alone, which is
what the helper prescribes for a transport that cannot say where it
serves the item.
Pins (triage grade `5879492075`):
- the scoped answer's `Link` names
`/api/v1/environments/env_1/meta/view/lead_all/layers`;
- the unscoped control's `Link` still names
`/api/v1/meta/view/lead_all/layers`.
Both pins run through the real `HonoHttpServer`
(`packages/rest/src/meta-item-layers-deprecation-link.test.ts`), because
the path comes from the adapter. The same file pins the encoded-name
case. The mock-harness file `meta-item-layered-route.test.ts` now passes
the `path` field that `IHttpRequest` declares required (its hand-built
request omitted it). It also pins the no-path branch.
## Measurements (PM mechanism hypotheses)
All at `fb386074f5` (unfixed) or `439dd81bd3` (fixed), through the real
`HonoHttpServer` with `enableProjectScoping: true, projectResolution:
'optional'`.
- **H0: confirmed.** Unfixed, `GET
/api/v1/environments/env_1/meta/view/lead_all?layers=true` answered
`200`, `deprecation=true`, with a `Link` naming
`/api/v1/environments/:environmentId/meta/view/lead_all/layers`. The
unscoped control `GET /api/v1/meta/view/lead_all?layers=true` named
`/api/v1/meta/view/lead_all/layers`, which is correct.
- **H1: half confirmed.** The request's own path is available at the
call site as `req.path`. The Hono adapter sets it from `c.req.path`, and
the Node conformance port from `url.pathname`. It does **not** carry a
percent-encoded name unchanged under Hono: `c.req.path` is
`decodeURI`'d. A probe route read the following for the scoped path:
| requested name | `req.path` segment | after the parse | dispatcher's
`requestedItemPath` |
|---|---|---|---|
| `lead_all` | `lead_all` | `lead_all` | `lead_all` |
| `lead%20all` | `lead all` | `lead%20all` | `lead%20all` |
| `lead%2Fall` | `lead%2Fall` | `lead%2Fall` | `lead%2Fall` |
| `lead%25all` | `lead%25all` | `lead%25all` | `lead%25all` |
| `l%C3%A9ad` | `léad` | `l%C3%A9ad` | `l%C3%A9ad` |
| `lead%5Fall` | `lead_all` | `lead_all` | `lead%5Fall` |
| env `env%201` | `env 1` | `env%201` | `env%201` |
After the parse, six of the seven rows match the dispatcher byte for
byte. The seventh is `%5F`, a percent-encoded unreserved character,
which Hono decodes to `_`. The two spellings are equivalent under RFC
3986 section 6.2.2.2, and they name the same route.
- **H2 (ablation): the red set is larger than predicted.** The mutation
put back the template argument
(`${metaPath}/${req.params.type}/${req.params.name}`) through
`scripts/ablation-replace.mjs`, which confirmed it landed (anchor 1 to
0, blob `cb57de8250e2` to `004a11651dba`). The result was 3 red and 13
green of 16: the scoped pin, the scoped encoded-name pin, and the mock
no-path pin. The PM predicted the scoped pin alone. The two extra reds
are pins this PR adds, and each is a scoped or no-path case the template
argument gets wrong. Both unscoped controls (Hono and mock) stayed
green. Restore was proven: blob after restore `cb57de8250e2` equals the
blob at `HEAD`, and `git diff HEAD` was empty.
- **Second ablation, of the parse.** It replaced the parse with the raw
`req.path`. The first attempt was a no-op: the tool refused because the
replacement text `requestPath` already occurred inside the anchor, so
the count did not rise, and it restored without running the tests. The
re-run used a unique marker and landed (blob `cb57de8250e2` to
`48fa04adf547`). Exactly 1 test went red: the encoded-name pin, which
received a raw space in `lead all/layers`. Restore was proven the same
way.
## Verification
At `439dd81bd3`:
- `pnpm --filter @objectstack/rest exec vitest run --project local
--maxWorkers=2`: 222 files, 4235 passed, 40 skipped.
- `pnpm --filter @objectstack/rest typecheck`: exit 0, which includes
`check:test-typecheck`. `tsc -p tsconfig.test.json --listFiles` lists
both edited test files.
- `pnpm lint` (full repo, not narrowed): exit 0.
- `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
--commands` derived 62 commands. 60 exited 0. Two exited 3 with
`PREREQUISITE NOT MET`, because both need the whole workspace built:
`check:dual-build-cjs-loads` and `check:type-check-debt`. Those two are
**NOT MEASURED**. The whole-workspace build was not run on this shared
host. `--ran` reconciliation: 62 derived, 60 run, 2 NOT-MEASURED, 0
UNRUN.
- Roster gates whose roster lives under a changed directory:
`check-changeset-fixed`, `check:error-code-casing` and
`check:filter-alias-parity` all exited 0.
- `node scripts/check-issue-citations.mjs --base origin/main`, after
merging `origin/main` (already up to date at `fb386074f5`): exit 0.
**Declared narrowing — verification ran UNLOCKED.**
`scripts/pm/os-verify-lock.sh`
could not take the shared verify lock on this host: no usable `flock`.
The shared
verify lock is declared Linux-only (`flock` is util-linux, and a stock
macOS does
not ship it), so the command below was run directly, without the lock —
a declared narrowing, not a silent one. No serialization guarantee held
for this
run, nor for any sibling agent in this container while it ran.
pnpm --workspace-concurrency=2 --filter '@objectstack/rest^...' build
pnpm --filter @objectstack/rest build
pnpm --filter @objectstack/rest typecheck
pnpm --filter @objectstack/rest exec vitest run --project local
--maxWorkers=2
pnpm --filter @objectstack/rest exec vitest run --maxWorkers=2
src/meta-item-layers-deprecation-link.test.ts
src/meta-item-layered-route.test.ts
## Acceptance notes
- **The unscoped answer changes for encoded names, and only for them.**
Unfixed, the unscoped `Link` was assembled from the decoded route
parameters. Measured on `fb386074f5` through Hono: `lead%2Fall` named
`/api/v1/meta/view/lead/all/layers`, a different path; `lead%25all`
named `lead%all`, an invalid percent-encoding; `lead%20all` and
`l%C3%A9ad` put a raw space and a raw non-ASCII character in the header.
After the fix, each keeps its encoding. For every name that needs no
encoding, the unscoped `Link` is byte-identical. The changeset says so.
- **The path parse now exists twice.** Once is the runtime dispatcher's
`requestedItemPath` in `packages/runtime/src/domains/meta.ts`, and once
is inline at this call site. Moving one copy into the shared seam,
`meta-item-read-gate.ts`, is outside this claim's file surface: that
file and `packages/runtime/**` are read-only here, and PR #20527 is
editing the seam. carrier: none. Noted, not filed.
- **Both transports name the successor from the path their adapter
reports.** A host that mounts the app under a parent that strips a
prefix would name the inner path, on both transports alike. NOT
MEASURED; no such host was composed here.
---
_Generated by [Claude
Code](https://claude.ai/code/session_local_1d2a197c-c20e-4e90-9be8-413d4d432289)_
Co-authored-by: Jack Zhuang <50353452+hotlong@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 0368a33 commit 1378ec7
4 files changed
Lines changed: 170 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 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
98 | 98 | | |
99 | 99 | | |
100 | 100 | | |
101 | | - | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
102 | 107 | | |
103 | 108 | | |
104 | 109 | | |
105 | 110 | | |
106 | 111 | | |
107 | 112 | | |
108 | | - | |
| 113 | + | |
109 | 114 | | |
110 | 115 | | |
111 | 116 | | |
| |||
204 | 209 | | |
205 | 210 | | |
206 | 211 | | |
| 212 | + | |
207 | 213 | | |
208 | 214 | | |
209 | 215 | | |
210 | 216 | | |
211 | 217 | | |
212 | 218 | | |
213 | 219 | | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
214 | 231 | | |
215 | 232 | | |
216 | 233 | | |
| |||
Lines changed: 107 additions & 0 deletions
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
6295 | 6295 | | |
6296 | 6296 | | |
6297 | 6297 | | |
| 6298 | + | |
| 6299 | + | |
| 6300 | + | |
| 6301 | + | |
| 6302 | + | |
| 6303 | + | |
| 6304 | + | |
| 6305 | + | |
| 6306 | + | |
| 6307 | + | |
| 6308 | + | |
| 6309 | + | |
| 6310 | + | |
| 6311 | + | |
| 6312 | + | |
6298 | 6313 | | |
6299 | 6314 | | |
| 6315 | + | |
6300 | 6316 | | |
6301 | | - | |
| 6317 | + | |
| 6318 | + | |
| 6319 | + | |
6302 | 6320 | | |
6303 | 6321 | | |
6304 | 6322 | | |
| |||
0 commit comments