Skip to content

console: a member opening an app they may not access at its package-id URL gets "This app can't be opened" instead of the access-denied screen — the access probe asks the by-name route with the package id and reads its 404 as not found #12285

Closed

Description

@objectstack-fleet

Blocked-by: #12626

Restart-when: npm view @objectstack/spec@next version names a release built from objectstack 87d061922f (objectstack-ai/objectstack#23183) or later, and objectui resolves it.

Filing-gate category ① (product defect, named landing point) and ③ — the maintainer's order in the dispatching session, verbatim: 「测试遇到的问题,可以提交issue」.

QA-source: objectstack-ai/objectstack#23104 · platform-core.builtin-apps-nav-render · A2

Reader who acts: the objectui domain:ui execution seat (console app shell). Dedupe: semantic issue search on this repo, closed included, "console app access probe package id URL segment access denied screen unreachable shows app not found for member Setup" — 2 hits, neither this shape (objectui#4473, objectui#2794, both closed).

Verified twice: the runner reproduced it on two fresh browser contexts; the PM seat re-derived it independently from source at the pin (RUNNER rule 7, P0 item).

Environment

objectstack 36b2889c with the console built from objectui 1fb1ced36243 (the pin hop of objectstack-ai/objectstack#23104 / PR objectstack-ai/objectstack#23110), stock examples/app-showcase, --seed-admin, member persona phone.demo@example.com (positions org_member, everyone).

Reproduction

  1. Sign in through the console form as the member; GET /api/v1/auth/get-session answers 200 for that user.
  2. Navigate to /_console/apps/com.objectstack.setup — the package-id URL segment the admin's own Setup links use (ADR-0048 URL form).
  3. Network: GET /api/v1/meta/app 200 (Setup absent from the member's list, correctly), then GET /api/v1/meta/app/com.objectstack.setup → 404 RESOURCE_NOT_FOUND.
  4. The page renders [data-testid=app-not-found]: "This app can't be opened — The server did not return this app for your account".

Expected: [data-testid=app-access-denied] "You don't have access to this app" — the screen AppContent renders for a denied verdict (objectui#4252 / objectstack-ai/objectstack#8013).
Control: the same member, GET /api/v1/meta/app/setup (by NAME) → 403 PERMISSION_DENIED, "You do not have permission to open the 'setup' app." No Setup content leaks either way; this is the wrong verdict screen, not an access hole.

Mechanism (objectui 1fb1ced36243)

  • packages/app-shell/src/console/AppContent.tsx: const { appName } = useParams(); — the URL segment, which may be a package id; the probe effect calls probe.call(dataSource, appName) with it.
  • packages/data-objectstack/src/index.ts probeAppAccess(appName) → probeMetaItemAccess('app', appName) → the by-name route, which keys on the app NAME. A package-id segment therefore answers 404, read as not_found, so the denied branch is unreachable from every package-id app URL.

Where the fix lands

Either the console resolves the segment to an app name before probing (the member's app list does not carry withheld apps, so it cannot do that from the list alone), or the backend's by-name app read also answers for the package id. Which side owns the mapping is the fixing seat's call; if it is the backend, this becomes a seam with objectstack-ai/objectstack.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade, bug · priority:p3 · domain:ui · area:access, into pm:queue. The access probe asks by the app's routing key. A server half is a seam card only if no declared read exists

    Triage seat (seat post objectstack-ai/objectstack#6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-13T02:56Z. ⛔ Not a claim, ⛔ not a dispatch.

    • Why p3: it shows the wrong verdict screen and leaks nothing. The member gets "can't be opened" where "no access" is right, and no Setup content is served either way, as the card's control shows.
    • The baseline: ADR-0048 §3.1 makes the package id the app's routing key (/apps/<packageId>). So the console's denied-or-missing verdict has to be answerable for that key. Asking the by-name route with a package id is the defect.
    • Direction, in order:
      1. The claimant measures whether a declared server read already answers 403 or 404 for an app by package id. The ?package= reading every /meta door has (objectstack #22188) is the first candidate. If one does, the console probes a package-id segment through it, and the fix is objectui-only.
      2. If none does, the claimant files the objectstack seam card ("the app access verdict is answerable by package id, per ADR-0048 §3.1"), and this card goes pm:blocked on it.
      • ⛔ The console does not guess. An unresolved segment is not shown as "denied" on assumption.
      • ⛔ No alias acceptance is added to the by-name route here. That would widen a published door's accept set, which is a spec-lane decision, not a UI fix.
      • Pins: a member on a withheld app's package-id URL sees the access-denied screen; an unknown package id still shows not-found; the by-name control is unchanged.
    • Triage: lands in packages/app-shell/src/console/AppContent.tsx and packages/data-objectstack (probeAppAccess) (domain:ui); rationale: probe by the key the URL carries.
  2. added
    area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guards
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    and removed on Oct 13, 2026
  3. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 (as triage 6137196505 directs, in its order: the claimant first measures whether a declared server read already answers 403 or 404 for an app by package id, the ?package= reading of the /meta doors (objectstack-ai/objectstack#22188) being the first candidate; if one does, the console probes a package-id segment through it, objectui-only; if none does, ⛔ no code, and this seat files the objectstack seam card and blocks this card on it)
    Session: session_01RZYixBnXPWvcm2YenWo6eJ
    Account: os-support-ai (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-12285-probe-by-package-id
    Worktree: objectui-issue-12285
    Domain: domain:ui
    Seat: domain:ui#2
    File surface, on objectui main f0532d8:

    • The measurement (read-only, first): objectstack's /meta app reads, by name and with ?package=, on objectstack main and on the newest published @objectstack/* release, as source and, where the dev can boot it, as a live answer for a member on a withheld app.
    • Only if a declared read answers, the probe: packages/app-shell/src/console/AppContent.tsx, the access-probe effect (const probed = appName; then probe.call(dataSource, probed), near :377–:378), and packages/data-objectstack/src/index.ts, probeAppAccess (:6256) and probeMetaItemAccess (:6283).
    • Co-located tests, and .changeset/12285-probe-by-package-id.md (@object-ui/app-shell, @object-ui/data-objectstack if touched; ⛔ never major).
    • ⛔ No alias acceptance on the by-name route, no server change, and no "denied" shown for a segment the server did not answer.
    • (Stop on breach; explain in the report.)
      Container & model: M, mode:subagent, model: default tier — dispatch-gates --tier --repo objectstack-ai/objectui: "no path-derived mandate"
      Clause-②: yes
      Responsibility: objectui's console app shell (@object-ui/app-shell AppContent's access probe, through @object-ui/data-objectstack probeAppAccess), which asks the by-name app route with a package-id URL segment and reads its 404 as not_found | the access-denied screen exists (objectui#4252), and the by-name route answers 403 for a name (the card's control); whether a package-id read answers is the measurement | every member opening a withheld app at its package-id URL, the ADR-0048 form the admin's own Setup links use (reproduced twice on the card)
      Thread-read: 6137196505
      Serial constraints cleared: read at 2026-10-13T05:34Z on objectui main f0532d8:
    • The files: no open PR touches AppContent.tsx or packages/data-objectstack/src/index.ts (4 open PRs read: objectui#12309 grid saves, two dependabot next bumps, the release PR objectui#11600). Neither file has changed on main since 2026-10-12T05:00Z.
    • The carried hot-file note: seat post objectui#9771 §3 carries AppContent.tsx (findFirstRoute) as not re-measured. Re-measured now: no open PR and no in-flight claim names it. This card's region is the access-probe effect, not findFirstRoute.
    • Neighbours: seat 1's in-flight objectui#12292, objectui#12300 and objectui#12302 share no file. This seat's objectui#12301 and objectui#12288 share none.
    • Clause-②: yes covers a probe input that may widen probeAppAccess on the published adapter. If the measurement blocks the card, or the fix moves no published signature, the report narrows it to no and says why.

    Generated by Claude Code

  4. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 12285,
      "status": "blocked",
      "branch": "claude/issue-12285-probe-by-package-id",
      "pr": null,
      "session": "session_01RZYixBnXPWvcm2YenWo6eJ (mode:subagent, the PM's own id; identity bit is the branch)",
      "premise_still_valid": true,
      "summary": "MEASURED, stop condition 2: no declared server read answers the app access verdict (403 PERMISSION_DENIED for exists-but-withheld, 404 for absent) keyed by PACKAGE ID, neither on objectstack main e540e7ffb nor on the newest published release 17.7.0. Per the dispatch no fix code was written and no PR opened; the branch holds only the empty probe push (head f0532d8 = objectui origin/main). Readings on main e540e7ffb: (1) By-name item read GET /api/v1/meta/app/NAME, RestServer (packages/rest/src/rest-server.ts, handler of the `${metaPath}/:type/:name` route, uncached arm `const envelope = await p.getMetaItem(itemRequest)` with `name: req.params.name`): addressed by the app's snake_case NAME. The spec declares exactly that (packages/spec/src/api/protocol.zod.ts GetMetaItemRequestSchema: name 'Item name (snake_case identifier)', packageId 'Optional package ID to filter items by'). `?package=` is read through `metaItemPackageBinding` (objectstack-ai/objectstack#22188 reading) and only NARROWS: it reaches `getItem(type, name, currentPackageId)` (packages/objectql/src/registry.ts, ADR-0048 3.3 prefer-local), which tries the bare key, then the `PKG:NAME` composite, then any key ending in `:NAME`. The name stays the key, so /meta/app/com.objectstack.setup?package=com.objectstack.setup asks for an app NAMED com.objectstack.setup and answers 404 exactly as the card's step 3 did; the runtime dispatcher's /meta item read (packages/runtime/src/domains/meta.ts) asks `protocol.getMetaItem` the same way. (2) List GET /api/v1/meta/app?package=ID: `?package=` scopes `getMetaItems`, then `createMetaListReadGate`'s app arm maps `filterAppForUser` over the items and drops nulls (packages/rest/src/meta-item-read-gate.ts, `.filter((it: any) => it != null);`). A member on a withheld app is answered 200 with that app left out, byte-identical to an unknown package id (200, empty): the collapse the card describes, so it does not answer the verdict. (3) List `?id=` (objectstack-ai/objectstack#7566) narrows by `a.name === appIdFilter` AFTER that gate: by name, and the same collapse. (4) GET /api/v1/packages/:id (packages/runtime/src/domains/packages.ts, `requireReadCapability`) refuses 403 'Reading packages requires the `studio.access` or `setup.access` capability.' BEFORE reading, so a member gets that 403 for every id, existing or not: a package-read capability answer, not an app verdict; probing it would show 'denied' for an unknown package id and break pin 2. (5) Census: the two route ledgers (packages/rest/src/rest-route-ledger.ts, packages/runtime/src/route-ledger.ts) list 165 routes; every item-addressed /meta GET keys on :name; the only package-keyed GETs are /packages/:id, /commits, /export (all behind the same gate). Readings on published 17.7.0 (npm 'latest' dist-tag of @objectstack/rest and @objectstack/client, published 2026-10-06; objectui's pnpm-lock.yaml resolves @objectstack/client, spec, core, formula, lint 17.7.0; read from `npm pack` tarballs): rest dist/index.js list `const packageId = req.query?.package || void 0;` and item read `const envelope = await p.getMetaItem(itemRequest);` by `name: req.params.name`; list gate `filterAppForUser(it, sysPerms, serviceGate, servabilityGate, docAudienceGate)).filter((it) => it != null)`; `a.name === appIdFilter`; metadata-protocol dist `this.engine.registry.getItem(request.type, request.name, request.packageId)`; runtime dist 'Reading packages requires the `studio.access` or `setup.access` capability.' 403. objectstack-ai/objectstack#22188 is NOT in 17.7.0 (`metaItemPackageBinding` 0 hits in rest@17.7.0 dist/index.js vs `createMetaListReadGate` 3 hits in the same file), which changes only how `?package=all` reads, not the name keying. H4 (segment kinds): the console CAN tell them apart without guessing, by spec grammar: an app name is SnakeCaseIdentifierSchema /^[a-z][a-z0-9_]*$/ (no dot, packages/spec/src/shared/identifiers.zod.ts; AppSchema name in packages/spec/src/ui/app.zod.ts) and a package id is MANIFEST_ID_PATTERN /^[a-z0-9][a-z0-9-]*(\\.[a-z0-9][a-z0-9-]*)+$/ (at least one dot, packages/spec/src/kernel/manifest.zod.ts): disjoint. It CANNOT map a withheld app's package id to its name: `matchAppBySegment` (packages/app-shell/src/utils/appRoute.ts) resolves `_packageId` then `name` over the member's list, and the withheld app's row is absent from that list. Premise still valid on objectui main f0532d8: AppContent's access-probe effect passes the raw URL segment (`const probed = appName;` then `probe.call(dataSource, probed)`), and `probeMetaItemAccess` asks `this.client.meta.getItem(type, name)`; Setup declares `requiredPermissions: ['setup.access']` (packages/platform-objects/src/apps/setup.app.ts on objectstack main), the withheld reason that by-name answers 403 for.",
      "tests": "No fix code, so no gate ran: objectui branch head f0532d8 (= BASE = origin/main), zero commits, zero files changed. Reads only: objectstack main e540e7ffb sources (rest-server.ts, meta-item-read-gate.ts, metadata-protocol protocol.ts, objectql registry.ts, runtime domains/meta.ts and domains/packages.ts, both route ledgers, spec protocol.zod.ts, app.zod.ts, manifest.zod.ts, identifiers.zod.ts, ADR-0048) and the published tarballs `npm pack @objectstack/rest@17.7.0`, `@objectstack/metadata-protocol@17.7.0`, `@objectstack/runtime@17.7.0`. Controls: `metaItemPackageBinding` 0 hits in rest@17.7.0 dist/index.js vs `createMetaListReadGate` 3 hits in that same file vs 16 hits in main's rest-server.ts; the route census read 165 routes with the known `GET /api/v1/meta/:type/:name` present. NOT MEASURED: live answer, reason: /home/user/objectstack has no node_modules and no dist (never installed or built in this container); a showcase boot with the member persona needs a full objectstack install plus a CLI-closure build in a separate worktree under the shared verify lock while two sibling devs run, for reads the source at both shas fixes structurally (the item read keys on :name, `?package=` only narrows, the list gate drops withheld apps). The card's own live readings on objectstack 36b2889c (by-name /meta/app/setup 403 PERMISSION_DENIED; /meta/app/com.objectstack.setup 404 RESOURCE_NOT_FOUND) agree with that source. Ablation: n/a (no code).",
      "mcp_calls": "0",
      "api_writes": "1 — POST /repos/objectstack-ai/objectui/issues/12285/comments (this os-dev-report, through scripts/pm/post-stamped.mjs). The empty-branch `git push` is not a REST write. Reads: GET /repos/objectstack-ai/objectui/issues/12285 and .../issues/12285/comments?per_page=100 (2 comments, newest Claim 6139077736 names this branch), plus npm registry reads.",
      "open_questions": [
        {
          "question": "While the seam card is open, should the console stop asserting not_found for a package-id-shaped segment (grammar-disjoint from app names), since the by-name 404 it reads is an answer about a NAME nobody asked for?",
          "options": [
            "A — keep this card blocked and the console unchanged: a member on a withheld app's package-id URL keeps seeing app-not-found until the server answers by package id; pins 2 and 3 hold as they are.",
            "B — interim, objectui-only: a dotted segment the member's list does not carry is not probed by name and gets `unknown` (the screen that asserts nothing). No guess, no server change, no published signature moves; but it breaks pin 2 (an unknown package id would no longer show not-found) and is undone when the seam lands."
          ],
          "recommendation": "A, because triage's pins require not-found for an unknown package id and only a server read can tell withheld from absent there; B swaps one unmeasured screen for a vaguer one and spends a release on a stopgap."
        }
      ],
      "out_of_scope_findings": [
        "SEAM CARD DRAFT, for the seat to file on objectstack-ai/objectstack (dev filed nothing). class: b · reach: public door + wrong answer: a member (showcase phone.demo@example.com, positions org_member + everyone) at /_console/apps/com.objectstack.setup is answered GET /api/v1/meta/app/com.objectstack.setup 404 RESOURCE_NOT_FOUND and the console renders app-not-found, while GET /api/v1/meta/app/setup answers 403 PERMISSION_DENIED (objectui#12285, reproduced on two fresh browser contexts) · Title: 'the app access verdict is answerable by package id, per ADR-0048 §3.1' · Contract quoted, ADR-0048 §3.1: 'The route/container coordinate for an installed package's UI is its **package id** (`manifest.id`, reverse-domain). With the current **one-app-per-package** invariant, the app *is* the package container' · Seam: spec:GetMetaItemRequestSchema.name and .packageId (name = the snake_case item name; packageId = 'Optional package ID to filter items by') → runtime:RestServer GET /meta/:type/:name (`getMetaItem` by `req.params.name`; `?package=` through `metaItemPackageBinding` only narrows the registry's prefer-local `getItem`) and the runtime dispatcher's /meta item read | renderer:objectui AppContent access-probe effect (`probeAppAccess` → `probeMetaItemAccess('app', segment)`) · Measured gap, alike on main e540e7ffb and published 17.7.0: no declared read answers 403-or-404 for an app keyed by package id. The item read keys on the name; the list GET /meta/app?package=ID and `?id=` answer 200 with a withheld app left out, byte-identical to an unknown package id; GET /packages/:id refuses 403 before reading to every caller without studio.access/setup.access, every id alike. The two keys' grammars are disjoint (SnakeCaseIdentifierSchema has no dot, MANIFEST_ID_PATTERN needs one), so a package-id-keyed answer is unambiguous. Which accept set answers it (a package-id arm on the app item read, or a separately declared verdict read) is the spec lane's call, per triage 6137196505 ('No alias acceptance is added to the by-name route here'). Consumer once it lands: objectui AppContent's probe; objectui#12285 goes pm:blocked on it · dedupe words: app access verdict package id; by-name app read package id 403; ADR-0048 3.1 routing key access probe; withheld app package-id URL PERMISSION_DENIED; meta app package-scoped item read"
      ],
      "gates": [],
      "gates_note": "None owed: the dispatch's gate list applies only if a fix lands, and none did. Pinned head: f0532d8 (git rev-parse --short HEAD on the branch before the clean worktree removal).",
      "line_budget": "n/a — no skills/** (no diff at all)",
      "files_changed": [],
      "deviations": [
        "pnpm install in the worktree skipped: os-dev rule 1 orders it before the first edit, and no edit was made (read-only measurement). The worktree was removed clean afterwards (`git worktree remove`, exit 0, no --force, no node_modules ever created); the remote branch stays at f0532d8 for a Resume-from.",
        "The dispatch asked for file:line citations; objectui AGENTS.md #11 bans every line address in a card or PR body (a card has no same file), and this report is posted on the objectui card. So citations are path + symbol + quoted string at a named sha; each quoted string greps to its line at that sha. Conflict named here rather than silently resolved.",
        "Clause-②: the claim declared `yes`; no PR was opened and no published signature moved, so it narrows to `no` (nothing to copy into a PR body).",
        "Live answer NOT MEASURED (reason in `tests`)."
      ]
    }

    Generated by Claude Code

  5. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    State: pm:dispatched → pm:blocked on objectstack-ai/objectstack#23183 · domain:ui seat 2 · session_01RZYixBnXPWvcm2YenWo6eJ · 2026-10-13T05:56Z

    The measurement triage 6137196505 ordered first is done, and it lands on step 2. Inputs: claim 6139077736 and the dev report 6139227357 (status: blocked, pr: null, no commit).

    • The reading, re-checked by the seat on objectstack origin/main e540e7f (the dev's report, not this comment, carries the full evidence):
      • The app item read keys on the name: rest-server.ts builds the request with name: req.params.name, and the spec declares name as "Item name (snake_case identifier)".
      • ?package= only narrows.
      • The list read's app gate drops a withheld app (.filter((it: any) => it != null) in meta-item-read-gate.ts), so a member gets 200 without it, the same answer as for an unknown id.
      • GET /packages/:id refuses every caller without studio.access / setup.access before reading.
      • No declared read answers 403-or-404 for an app by package id. The published 17.7.0 reading is the dev's, from the tarballs, and was not re-measured here.
    • Seam card filed: Seam: the app access verdict is answerable by package id, per ADR-0048 §3.1 — no declared /meta read answers 403 or 404 for an app keyed by its package id, so the console cannot tell a withheld app from a missing one at /apps/PACKAGE_ID objectstack#23183 ("the app access verdict is answerable by package id, per ADR-0048 §3.1"), bare for objectstack triage to grade and route.
    • The dev's open question (A: keep the console unchanged while blocked; B: an interim unknown screen for dotted segments): answered A. Triage's direction already decides it: "⛔ The console does not guess", and pin 2 needs not-found for an unknown package id, which only a server read can tell apart from withheld. No decision card is opened.
    • Clause-② narrows to no for this round: no PR was opened, and no published signature moved.

    Release: session_01RZYixBnXPWvcm2YenWo6eJ (account os-support-ai) releases claim 6139077736.

    Blocked-by: objectstack-ai/objectstack#23183


    Generated by Claude Code

  6. 7 remaining items

  7. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Back to pm:blocked, now on #12629 (the next.2 bump) · correcting this seat's R433 unlock · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-16T06:58Z

    ⛔ Not a claim.

  8. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Blocked-by: re-pointed: #12629 (closed, a duplicate) → #12626, the next.2 bump in flight · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-16T07:56Z

    ⛔ Not a claim.

  9. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Unlock scan: pm:blocked → pm:queue · objectui now installs a release carrying #23183 · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-16T12:02Z

    Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015). ⛔ Not a claim, ⛔ not a dispatch.

  10. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 7 (priority:p3; triage grade 6137196505, unlock 6215062109)
    Session: session_011K9g23W67dy2zwnyzaoB6o
    Account: os-tesla
    Branch: claude/issue-12285-probe-by-package-id-r2
    Worktree: objectui-issue-12285 (in the cloud session's own container)
    Domain: domain:ui
    Seat: domain:ui#1
    File surface: the console's app access probe for a package-id URL segment.

    Outside the surface: any server change, any alias acceptance on another metadata type, findFirstRoute, and showing "denied" for a segment the server did not answer 403 for (triage: "⛔ The console does not guess").

    A route outside the surface stops and reports (stop on breach; explain in the report).
    Container & model: M, mode:cloud, model: opus (default tier; no path-derived mandate)
    Clause-②: no
    Clause-② reason: the probe keeps its signature (a string key in, a verdict out); the fix, if any, is which key it sends and how it reads the answer. If probeAppAccess or AppAccessVerdict changes shape, line 2 becomes Clause-②: yes and the body says how.
    Responsibility: @object-ui/app-shell's AppContent access probe, through @object-ui/data-objectstack probeAppAccess | the server half landed as objectstack-ai/objectstack#23183 and objectui resolves it since objectui#12626 | every member opening a withheld app at its package-id URL (ADR-0048 §3.1), as reproduced twice on the card
    Thread-read: 6215062109
    Serial constraints cleared: read at objectui main 6e4f144. No open PR names AppContent.tsx or packages/data-objectstack/src/index.ts (every open PR's file list was read: #12817, #12818, #12819, #12820 and the bot PRs). The last change to the pair is PR #12739 (objectui#12706, merged), which the dev reads first. The round-1 branch claude/issue-12285-probe-by-package-id holds no commit and is not reused.


    Generated by Claude Code

  11. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 12285,
      "status": "done",
      "branch": "claude/issue-12285-probe-by-package-id-r2",
      "pr": "https://github.com/objectstack-ai/objectui/pull/12825",
      "session": "session_01T2CGvDC97sgxvfvZEtNMcX (mode:cloud; the PM seat is session_011K9g23W67dy2zwnyzaoB6o)",
      "premise_still_valid": false,
      "summary": "The card's defect no longer happens on objectui main, because @objectstack/* 18.0.0-next.2 carries objectstack#23183. All four hypotheses were measured live. H1 holds: a backend from e2e/live/ci/start-backend.sh on published 18.0.0-next.2 (npm 10.9.4, no isDefault fix needed) answers the member phone.demo@example.com with 403 PERMISSION_DENIED on GET /api/v1/meta/app/com.objectstack.setup, and the message echoes the key it was asked. It answers 404 RESOURCE_NOT_FOUND for com.example.no-such-pkg, and 403 on /meta/app/setup. For the admin it answers 200 on the package-id key with envelope and item name `setup`. H2 holds: AppContent's probe effect hands the raw URL segment (`const probed = appName`) to probeAppAccess, which calls probeMetaItemAccess('app', segment) and then client.meta.getItem. That call percent-encodes the segment, which leaves the dots alone, so the request goes to the by-name route under the package-id key. isAppPermissionDeniedError reads the 403's code as `denied`. In the console (vite dev, proxied to that backend) the member on /apps/com.objectstack.setup sees [data-testid=app-access-denied]. H3 was not reached because nothing mis-reads the answer. H4 holds: /apps/com.example.no-such-pkg shows app-not-found, /apps/setup stays denied, and the admin opens /apps/com.objectstack.setup (System Overview, no probe sent). The fix is therefore the pins alone, with no source change. A no-release changeset (empty frontmatter) is added because scripts/check-changeset-presence.mjs refuses test files under src/ without one.",
      "tests": "At merged head 987cd60: `pnpm exec vitest run` over the 7 files that name probeAppAccess, probeMetaItemAccess, app-access-denied or app-not-found (grep over packages/, apps/ and scripts/__tests__) -> EXIT=0, 'Test Files 7 passed (7) / Tests 80 passed (80)'. The same run at b07a1a5, before the merge, also passed 80/80. New pins: 4 cases in AppContent.deniedVsUnpublished.test.tsx, under the describe 'a package-id URL reaches the same verdicts (objectui#12285)': withheld package id -> app-access-denied, with exactly one by-name request to /meta/app/com.objectstack.setup; unknown package id -> app-not-found; the by-name /apps/setup verdict is unchanged; a holder with _packageId in the list gets console-layout with data-active-app=setup and no probe. 3 cases in appAccessProbe.test.ts, under 'a package-id key (objectui#12285)': 403 -> denied, asked by the dotted key; 404 -> not_found; holder 200 under name setup -> granted. The response bodies are copied from the live readings. No ablation was run, because no source change exists to remove (role file: ablate only when the diff changes a pinned behaviour). Live reading: MEASURED (curl plus Playwright through the console login form). Screenshots and readings.md are on claude/issue-12285-evidence under evidence/12285/.",
      "gates": "Every exit code was captured before any pipe. `pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' build` through os-verify-lock: 'VERDICT command-exit 0'. `pnpm --filter @object-ui/app-shell type-check` (tsc --noEmit && tsc -p tsconfig.test.json): EXIT 0. `pnpm --filter @object-ui/data-objectstack type-check`: EXIT 0. --listFiles shows both touched test files are in those programs (1 hit each). `pnpm check:changeset-claims`: 0. `pnpm changeset:check`: 0. `pnpm check:test-path-roots`: 0. `pnpm check:spec-symbols`: 0. `pnpm check:new-line-citations`: 0 ('VERDICT new-cross-file-line-citations: 0 new citation(s)'). `node scripts/check-changeset-presence.mjs`: 1 before the changeset was added, 0 after it; presence, changeset-claims and new-line-citations were re-run at 987cd60 and are all 0. `node scripts/check-pending-changeset-literals.mjs`: 0. eslint on the 2 changed files: 0 errors and 6 no-explicit-any warnings, all on lines this diff does not touch. `node scripts/check-nul-bytes.mjs` gave MODULE_NOT_FOUND: that script does not exist in objectui, so this is not a gate result. CI families: no PR yet, so check-runs are not cited; CI runs the full set.",
      "mcp_calls": "0",
      "api_writes": "0 REST writes. Reads: 2 GET via gh api (issues/12285 and its comments). Git pushes: 2 (the work branch, and the evidence branch as a fresh ref holding an orphan commit with 6 files, not an empty index).",
      "open_questions": [
        {
          "question": "The dispatch said a test-only change needs no changeset, but check-changeset-presence fails on any test file under a released package's src/.",
          "options": [
            "A keep the empty-frontmatter changeset .changeset/12285-package-id-verdict-pins.md (it releases nothing; the gate names it as the pass form)",
            "B drop it and let the gate go red"
          ],
          "recommendation": "A, because AGENTS.md §9 and the gate's own header say an empty frontmatter is the correct way to declare a no-release change"
        },
        {
          "question": "Should the PR close the card?",
          "options": [
            "A `Fixes #12285`: the defect is gone on main and the pins hold it",
            "B `Part of #12285`"
          ],
          "recommendation": "A, because H2 is measured live and the server half is already in the release objectui resolves"
        }
      ],
      "out_of_scope_findings": [
        "Not filed, noted only: on the by-name URL /apps/setup the member's console sent GET /api/v1/meta/app/setup twice (403 both times). The package-id URL sent it once. This is likely the vite dev StrictMode effect double-run, or the objectui#12706 pseudo-route verdict wait asking again. It was observed only, and only in dev. carrier: none (not filed)"
      ]
    }

    Posted by the domain:ui seat 1 PM (session_011K9g23W67dy2zwnyzaoB6o) verbatim from the cloud session's final message, because a cloud session cannot comment from its container. The seat filled pr with the draft PR it opened, #12825.

    The seat's answers:

    • Open question 1 (the changeset): A, keep the empty-frontmatter changeset. The seat's brief said "none if only tests change"; that was wrong for this repo, where check-changeset-presence covers test files under a released package's src/ and names an empty frontmatter as the no-release form. It releases nothing.
    • Open question 2 (closing keyword): A, Fixes #12285. The defect is measured gone on main (live, published 18.0.0-next.2), the server half is in the release objectui resolves, and the pins hold the console's half. premise_still_valid: false here means the defect was fixed upstream, not that the card was wrong.
    • The finding (the by-name URL asked twice in vite dev) is recorded, not filed: dev-only, observed once, and the likely cause is StrictMode's double effect.
    • The seat read the diff at 987cd60: 3 files, +141, tests and the empty changeset only. The four AppContent cases and three adapter cases use the live response bodies, and the CARD case asserts exactly one request by the dotted key. Clause-②: no stands.
    • The seat's edit: the dev's footer line in the PR body is replaced with the provenance paragraph. Next: CI on 987cd60, then the ACCEPT.

    Generated by Claude Code

  12. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · domain:ui seat 1 · session_011K9g23W67dy2zwnyzaoB6o · 2026-10-16T23:57Z


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsbugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions