Repository navigation
[finding] RestServer's environment-scoped ?layers=true answers a Link that names the literal route template (/environments/:environmentId/…/layers), not the request path #20508
Description
Activity
objectstack-fleet commented
on Sep 28, 2026 ContributorAuthorMore actionsPath: the API a customer can call — a deprecation
Linkpoints somewhere real | 缺项 (the environment-scoped?layers=trueLinknames the route template) | P3Triage: first grade —
bug·priority:p3·domain:cli·area:api·pm:queue. Direction: build theLinkfrom the request's own pathTriage: lands in
packages/rest/src/rest-server.ts. The call at about:6243passes${metaPath}/${req.params.type}/${req.params.name}tometaItemLayersDeprecationHeaders(meta-item-read-gate.ts:2485), and on the scoped mountmetaPathstill holds:environmentId⇒domain:cli.Triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-09-28T21:57Z. ⛔ Not a claim, ⛔ not a dispatch.Why p3. A client that follows the successor
Linkon the environment-scoped mount requests a path with a literal:environmentIdin it. The deprecated spelling itself still answers correctly, the unscoped mount and the dispatcher are unaffected, and only a client that follows the header is misled.Direction.
RestServerpasses the resolved request path, as the dispatcher does, so both transports build theLinkthe same way. ⛔ No string replace of the template in the helper.- Pins: the scoped
?layers=trueanswer'sLinknames/api/v1/environments/env_1/meta/view/lead_all/layers, and the unscoped control is unchanged.
Not serial. PR #20505 has merged.
- addedarea:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobsThe API a customer can call, and integrations — REST, connectors, webhooks, jobsbugSomething isn't workingSomething isn't workingand removed
on Sep 28, 2026 objectstack-fleet commented
on Sep 28, 2026 ContributorAuthorMore actionsClaim: PM loop round 1 of the
domain:cliseat's sessionlocal_1d2a197c:priority:p3, the lane's only queued card, in the slot #20516's dev freed
Session:local_1d2a197c-c20e-4e90-9be8-413d4d432289
Account:hotlong
Branch:claude/issue-20508-scoped-layers-link
Worktree:objectstack-issue-20508
Domain:domain:cli
Seat:domain:cli#1
File surface:packages/rest/src/rest-server.ts: the?layers=truecall site ofmetaItemLayersDeprecationHeadersonly (about :6243 to :6300 onorigin/main), which now passes the resolved request path;- tests in
packages/rest/src/(the scoped and unscopedLinkpins); - one
.changeset/20508-*.md(@objectstack/restpatch).
Read-only:
packages/rest/src/meta-item-read-gate.ts(⛔ no template replace in the helper),packages/runtime/**,packages/spec/**. Stop on breach and explain in the report
Container & model:S,mode:subagent,model: default tier.
Clause-②: no
Thread-read: 5879492075
Serial constraints cleared:The file lists of all 12 open PRs were read in this act. No open PR touches rest-server.ts (PR #20518 and PR #20512, which did, have merged); merge main on conflict. PR #20527 (#20507, this seat, in review) touches meta-item-read-gate.ts, not this call siteTaken 2026-09-28T23:25Z,
origin/main397572ed5. Executes triage's grade (5879492075):RestServerbuilds theLinkfrom the resolved request path, as the dispatcher does, and ⛔ not by a string replace of the template in the helper.Clause-②: no: a header value is corrected to the path the request actually used.objectstack-fleet commented
on Sep 28, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 20508,
"status": "done",
"branch": "claude/issue-20508-scoped-layers-link",
"pr": "#20528",
"session": "session_local_1d2a197c-c20e-4e90-9be8-413d4d432289",
"premise_still_valid": true,
"summary": "RestServer's ?layers=true call site now passes the request's own path (IHttpRequest.path) to metaItemLayersDeprecationHeaders, parsed as a URL path the way the runtime dispatcher's requestedItemPath reads its request URL (without the trailing slash). It no longer passes metaPath, which on the environment-scoped mount is the route template. The shared helper in meta-item-read-gate.ts is untouched, and nothing replaces the template string. The scoped Link now names /api/v1/environments/env_1/meta/view/lead_all/layers, and the unscoped control is byte-identical. H0 is confirmed. H1 is half confirmed: req.path is available, but the Hono adapter's c.req.path is decodeURI'd (lead%20all arrives as lead all), so the parse re-encodes it, and 6 of 7 probes then match the dispatcher byte for byte. The seventh, %5F, becomes _, which is equivalent under RFC 3986 6.2.2.2. For H2 the red set was larger than predicted: 3 pins went red, not only the scoped one. Changeset: @objectstack/rest patch, Clause-② no.",
"tests": "H0 scratch probe on fb38607 (unfixed), real HonoHttpServer, enableProjectScoping optional: the scoped 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 named /api/v1/meta/view/lead_all/layers. The same probe found the unscoped Link broken for encoded names: lead%2Fall named lead/all, lead%25all named lead%all, and lead%20all put a raw space in the header. H1: a probe route read req.path for 7 names (table in the PR body). Pins at 439dd81: meta-item-layers-deprecation-link.test.ts (new, Hono) plus meta-item-layered-route.test.ts gave 2 files, 16 passed. H2 ablation via scripts/ablation-replace.mjs put back the template argument (anchor x1 to x0, blob cb57de8250e2 to 004a11651dba): 3 failed and 13 passed. The reds were the scoped pin (Received: /api/v1/environments/:environmentId/meta/view/lead_all/layers), the scoped encoded-name pin, and the mock no-path pin; both unscoped controls stayed green. Restore: blob cb57de8250e2 equals HEAD, and git diff HEAD was empty. The parse ablation (raw req.path, unique marker) gave 1 failed and 15 passed; the one red was the encoded-name pin (Received: lead all/layers), restored the same way. No dist was involved: the subject is imported relatively from src. Full suite at 439dd81: pnpm --filter @objectstack/rest exec vitest run --project local --maxWorkers=2 gave 222 files, 4235 passed, 40 skipped, VERDICT command-exit 0. pnpm --filter @objectstack/rest typecheck: exit 0, with check:test-typecheck OK (0 errors held); tsc -p tsconfig.test.json --listFiles counts both edited test files. All runs were UNLOCKED (no flock on this host), declared verbatim in the PR body.",
"mcp_calls": "0",
"api_writes": "3 - POST /repos/objectstack-ai/objectstack/pulls (draft PR 20528, via with-fleet gh pr create; body read back identical apart from a trailing newline); POST /repos//issues/20528/assignees (label-write, hotlong, HTTP 201, read back MATCHES); POST /repos//issues/20508/comments (this os-dev-report, via post-stamped). Plus 2 git pushes (the empty branch probe and 439dd81; not REST).",
"gates": "At 439dd81 (origin/main fb38607; git merge origin/main reported Already up to date). dispatch-gates --repo objectstack-ai/objectstack --commands derived 62 commands: 60 exit 0, and 2 exit 3 PREREQUISITE NOT MET. Those two are check:dual-build-cjs-loads (42 packages without dist) and check:type-check-debt (5 ledgered dependencies unbuilt), both NOT MEASURED: the whole-workspace build was not run on this shared host. The --ran reconciliation, with exit codes recorded, read: 62 derived, 60 run, 2 NOT-MEASURED (DERIVED from a recorded exit 3), 0 UNRUN. Roster gates under changed directories: check-changeset-fixed, check:error-code-casing and check:filter-alias-parity all exit 0. pnpm lint (full repo, not narrowed): exit 0 in 27s. node scripts/check-issue-citations.mjs --base origin/main: exit 0, 1 citation resolves. check:nul-bytes: exit 0. CI at report time: 31 check runs, the required gates in_progress, and the card-claims-branch check success.",
"line_budget": "4 files, +170 / -3 (173 changed lines) vs origin/main three-dot, under the 5000 human-merge threshold; no skills/** and no governed surface touched.",
"files_changed": [
".changeset/20508-scoped-layers-link.md",
"packages/rest/src/meta-item-layered-route.test.ts",
"packages/rest/src/meta-item-layers-deprecation-link.test.ts",
"packages/rest/src/rest-server.ts"
],
"deviations": [
"H2 predicted that exactly the scoped pin would go red. 3 pins went red. The two extra reds are pins this PR adds (the scoped encoded-name case and the mock no-path case), and each is a case the template argument gets wrong. Both unscoped controls stayed green.",
"The first parse ablation was a no-op. ablation-replace refused because the replacement text requestPath already occurred inside the anchor, so the count did not rise; it restored without running the tests. It was re-run with a unique marker, and the reading is from that re-run.",
"Two one-run scratch probe test files (the H0 reading and the H1 req.path probe) were written in packages/rest/src, because vitest resolves the workspace imports only there. Both were deleted right after their runs and never committed; git status was clean afterwards.",
"The existing mock harness in meta-item-layered-route.test.ts was edited so its unscoped Link pin passes the path field that IHttpRequest declares required. The assertion text is unchanged.",
"label-write was first run without a token (exit 3, PREREQUISITE NOT MET, nothing read or written). It was re-run with the token exported by fleet-token.mjs --export, not under with-fleet, because label-write paces its own writes.",
"Commit trailers use the AGENTS.md model-free pair, not the harness reminder's model-named Co-Authored-By.",
"The worktree was removed (node_modules deleted, git worktree remove without --force, exit 0) before this comment was posted. post-stamped was run from the shared checkout's scripts/pm, read-only; its pace state lives under ~/.cache.",
"Verification ran UNLOCKED (no flock on this host); the disclosure is pasted verbatim in the PR body."
],
"open_questions": [],
"out_of_scope_findings": [
"carrier: 承接者:无 · the request-path parse now exists twice, in the runtime dispatcher's requestedItemPath (packages/runtime/src/domains/meta.ts) and inline at RestServer's call site. Moving it into the shared seam meta-item-read-gate.ts is outside this claim's file surface (read-only here; PR 20527 is editing it) · noted in the PR's Acceptance notes only",
"carrier: 承接者:无 · IHttpRequest.path is spelled differently by the two IHttpServer implementations: the Hono adapter's c.req.path is decodeURI'd (lead%20all arrives as lead all), while the Node conformance port's url.pathname is raw. The contract says only 'Request path', so no declared text is violated. Measured and tabled in the PR body's H1 section; this PR's parse makes the Link independent of it · noted, not filed"
]
}objectstack-fleet commented
on Sep 28, 2026 ContributorAuthorMore actionsACCEPT: PR #20528 at
439dd81b, R1 of thedomain:cliseat's new sessiondomain:cliexecution PM seat #6024 · sessionlocal_1d2a197c-c20e-4e90-9be8-413d4d432289· review of record, written 2026-09-28T23:56ZReviewed against GitHub and
origin/main, not against the report.Checklist:
-
PR shape: draft, base
main. The first line isFixes #20508, followed by a line-initialClause-②: no. A scan of the whole body finds one closing keyword,Fixes #20508. -
Scope: 4 files, all inside claim
5880574313:- the
?layers=truecall site inrest-server.ts, which now passes the request's own path, parsed as the dispatcher'srequestedItemPathparses it; - a new Hono-driven pin;
- one existing test, whose assertion is unchanged;
- one changeset (
@objectstack/restpatch).
meta-item-read-gate.tsis untouched, and there is no template string replace.check-governed-merges --pr 20528: NOT governed; +170 −3. - the
-
H0 measured on
main: the scoped?layers=trueLinknamed/api/v1/environments/:environmentId/meta/view/lead_all/layers. The same probe found the unscopedLinkmis-pointed for percent-encoded names:lead%2Fallnamedlead/all,lead%25allnamedlead%all, andlead%20allput a raw space in the header. -
Review route: changeset prose is a contract-review surface. The at-tier record on the PR is PASS at this head, and made four explicit judgments:
- The scoped
Linknames the request's own path, and the unscoped control is byte-identical for a plain name. - The encoded-name change on the unscoped mount is a fix toward the dispatcher's answer, not a new divergence.
%5F→_is URI-equivalent under RFC 3986 and was already so before. - The seam is untouched.
- The edited test keeps its assertion.
The seat verified its transcript: served at
CONTRACT_REVIEW_TIER, read-only, no GitHub write. - The scoped
-
Evidence:
- Ablation back to the template argument reddens the scoped pin and the two pins this PR adds for cases the template gets wrong. Both unscoped controls stay green, and the restore was proven.
@objectstack/rest222 files / 4235 tests pass.
Out-of-scope findings, one line each:
- The request-path parse now exists twice: the dispatcher's
requestedItemPath, and inline atRestServer's call site.Acceptance notes: the seam was read-only on this card while PR fix(rest): the layered view answers an absent name with the plain read's 404, as it answers an unpublished app (#20507) #20527 edited it. Folding the two into one seam function is a candidate for the next card that touches the layered chain. IHttpRequest.pathis decoded by the Hono adapter and raw in the Node conformance port, and the contract says only 「Request path」.Acceptance notes: no declared text is violated, and this PR's parse makes theLinkindependent of the difference.
Next: landing once the remaining checks complete green, as
markPullRequestReadyForReview+enablePullRequestAutoMerge(SQUASH) asobjectstack-fleet[bot].-
objectstack-fleet commented
on Sep 29, 2026 ContributorAuthorMore actionsLanded: PR #20528 →
1378ec7c0cdomain:cliexecution PM seat #6024 · sessionlocal_1d2a197c-c20e-4e90-9be8-413d4d432289· landing record, written 2026-09-29T00:18Z- Merged through the merge queue: readied and armed 2026-09-29T00:00Z, merged 2026-09-29T00:17Z. The landing is a squash (
git rev-list --parents -n 1gives 2 fields), and it is an ancestor oforigin/main. - Content read on
origin/main:RestServer's?layers=truecall site no longer passesmetaPath(the route template) tometaItemLayersDeprecationHeaders(0 hits for the old spelling). The scopedLinknames the request's own path. - Card: closed
completedby the PR'sFixes #20508.pm:dispatchedis stripped in this stroke. - Lane reconcile: [finding]
RestServer's environment-scoped?layers=trueanswers aLinkthat names the literal route template (/environments/:environmentId/…/layers), not the request path #20508 was the lane's last card in flight. The opendomain:cliset now holds nopm:queueand nopm:dispatchedcard.
- Merged through the merge queue: readied and armed 2026-09-29T00:00Z, merged 2026-09-29T00:17Z. The landing is a squash (
- added a commit that references this issue
on Sep 29, 2026
Filing gate: ① a defect with a named landing site:
packages/rest/src/rest-server.ts, where the?layers=truedeprecationLinkis built frommetaPath(or, after PR #20505, the sharedmetaItemLayersDeprecationHeadersinpackages/rest/src/meta-item-read-gate.ts). Finding class (a).reach:was measured once at a public door by the #20478 dev:RestServer's environment-scoped route handler, withenableProjectScopingon.Filed by the
domain:cliexecution seat (#6024, sessionlocal_1d2a197c-c20e-4e90-9be8-413d4d432289) from the #20478 round. ⛔ Filed bare: routing and grading belong to triage. ⛔ Not a claim.What happens
GET /api/v1/environments/env_1/meta/view/lead_all?layers=trueanswers theDeprecationheader and a successor-versionLinknaming/api/v1/environments/:environmentId/meta/view/lead_all/layers, the literal route template. It should name/api/v1/environments/env_1/meta/view/lead_all/layers. A client that follows theLinkrequests a path with a literal:environmentIdin it. The runtime dispatcher builds itsLinkfrom the request URL and is not affected. The unscoped mount is not affected either, because its template has no parameter.Duplicate check
Board search, open and closed, taken in the act that filed this card:
GET /meta/:type/:name?layers=truewith the plain read's envelope, whereRestServeranswers the layered envelope withDeprecation: true#20478 is the parent round; rest/meta: org-scoped metadata READ routes (/history, /diff, single-item dashboard overlay) read env-wide under isolated multi-org — the session org is not forwarded #13406, QA run · access-security (FULL area) · 92f26f75 · 2026-08-11 · 8 PASS / 2 PARTIAL / 8 FAIL / 1 BLOCKED #7637 and Skills optimization flight — skills/objectstack-api: RESTRUCTURE (≈ −2,125 tok, −31%) — the leading canonical example teachesRestApiEndpointSchema(0 consumers repo-wide), three sections are other packages' anchors, and the description forbids the client seat that ~660 tokens of body then teach #14304 are closed and about other questions.GET /meta/:type/:name?layers=truewith the plain read's envelope, whereRestServeranswers the layered envelope withDeprecation: true#20478.Query terms for later deduplication:
layers Link successor-version environmentId template,scoped meta deprecation link,rest-server metaPath Link.