Skip to content

QA run · platform-core (FULL area) · a86db175 · 2026-08-11 · 3 PASS / 1 PARTIAL / 7 FAIL #7514

Description

@baozhoutao

Full platform-core area run of the checklist-test skill — all 11 items driven against a live showcase (2 opus subagents, isolated boot each). Labels: qa-run + bug.

Result: 3 PASS · 1 PARTIAL · 7 FAIL. Text-only per RUNNER.md.

⚠️ One security-hardening finding in settings-hub-roundtrip is described below at defect+root-cause level, with the step-by-step recipe withheld pending the maintainer's call (same handling as the FOLLOW-UPS.md D1 and api-backend #7463 findings). Ask for the private write-up.

Environment — framework a86db175 (branch claude/platform-test-checklist-ocwugl, PR #7304) · vendored console 09987b68 · showcase · isolated file DB + port per batch · admin + purpose-signed-up member.

✅ The P0 smoke is clean

boot-health (5/5 — 1142-line boot log with zero ERROR/FATAL/warn, seed 130 rows/0 errored, and the served nav diffed line-by-line against the authored source: 58 nodes, id-set diff empty both directions) · console-login (5/5 + negatives — session survives reload, a cleared session gives a real server 401 on a forged authed call, expiry preserves ?redirect and re-auth returns to the original route; wrong password gives a named error with no session cookie) · notification-center (7/7 — the badge is topic-coalesced, refuting #2765: 11 unread rows in one topic contribute exactly 1; group mark-read fires one POST carrying all 11 ids).

🔴 FAIL — 7 items

1. builtin-apps-nav-render — two nav defects

(a) Dead nav entry. Setup › Advanced › Signing Keys (JWKS) can never load for any persona: sys_jwks declares enable.apiEnabled:false/apiMethods:[], so the list 404s OBJECT_API_DISABLED — and the console masks it as a generic "No identity records" empty state. The entry is contributed with only a requiredPermissions gate, and a permission gate cannot prune an API-disabled object. Control: nav_api_keys→sys_api_key on the same machinery, no 4xx.

(b) The Account app is served whole to a member denied every object in it. All 10 nav ids are served to a fresh member, then 7/7 backing objects answer 403. Root cause chain: the showcase binds its own baseline to the everyone anchor ([security] baseline set bound to everyone anchor (ADR-0090 D5)) and that replaces rather than composes with the platform member_default — platform grants 23 objects including all the Account ones, showcase_member_default grants 7. So every built-in Account destination dies for members on any app that declares a baseline; and because the Account nav entries use requiresObject (not requiredPermissions), the server has no signal to prune them. Reproduced 2× with two independent member sessions.

(47 of 49 destinations render fine; Studio clauses are blocked(fixture) — @objectstack/studio is intentionally no longer default-loaded, so the item should target /_console/studio/<pkg>/<pillar>, which was verified to render.)

2. metadata-registry-serving + 3. metadata-authoring-roundtrip — a shadowed-meta-route class, now 3 instances

  • GET /meta/types is swallowed by the generic /meta/:type handler → {"type":"types","items":[]}, shape-identical to /meta/zzz_not_a_type. /meta/diagnostics and /_drafts do work, so literal routes registered before :type are honored — this is a missing registration. The dispatcher implements the branch; REST never registers it. The pinned dogfood test that should guard this says /meta/types in its header comment but actually calls /meta — the drift is unpinned.
  • GET /meta/:type/:name/published is dead the same way: it returns a protection-envelope stub named <name>/published, identical before publish and for a bogus name (so it can never 404). rest-server pre-registers /references, /layers, /history, /audit, /diff before the compound-name route but never /published.
  • (Third instance, from QA run · api-backend (FULL area) · a86db175 · 2026-08-10 · 6 PASS / 2 PARTIAL / 3 FAIL #7463: GET /meta/objects/:name/state/:field.) A route-ledger↔live-mount parity gate would catch all three.

Object-extension overlay missing from the by-name route. The account extension's 3 fields appear in GET /meta/object (list) but not in GET /meta/object/showcase_account (nor in either layer with ?layers=true). The edit/new forms derive from the by-name response, so the overlay fields can never be set through the UI — even though they are real columns that round-trip through /api/v1/data and do render on the read-only detail page.

Studio page-create never completes. The console PUTs a body containing recordChrome and items, which the server strictly rejects (ADR-0089 D3a) — no page is stored. Reproduced 2×; not a stale bundle: current objectui HEAD still emits {type:'page:header',recordChrome:true} from buildDefaultPageSchema, pinned by its own test.

4. settings-hub-roundtrip — encrypted settings are echoed in plaintext on read ⚠️

Storage is correct (sys_setting.value null, encrypted:true, value_enc:'sec_…', aes-256-gcm ciphertext in sys_secret), but GET /api/settings/:namespace returns the secret's plaintext in values.<key>.value and repeats it inside cascadeChain. Both specifier flavours affected (type:'password' and encrypted:true). Root cause: settings-service.materialiseRow() dereferences sec_ handles and decrypts, getNamespace() copies that plaintext into every cascade entry, and settings-routes applies no redaction on the way out. The service-layer round-trip is deliberately pinned by settings-service.test.ts, so the missing boundary is the REST read. The endpoint requires setup.access (anonymous → 403), so this is defense-in-depth, not privilege escalation.

Everything else in this item passes, including the item's own anti-pattern guard: effect was proven on a real consumer (analytics date-bucket boundaries and row labels move with the timezone), never on a saved form; env-lock gives source:'env', locked:true, PUT → 409 SETTINGS_LOCKED, and the console disables exactly that control.

5. package-lifecycle-enable-disable — four defects

  • (A) Disable is not an enforcement for objects. With the owning package status:'disabled', GET /api/v1/data/<pkg_object> still answers 200 with all rows and /meta/objects still lists it — while nav and views are correctly dropped. Reproduced over two cycles; the console renders the live list too.
  • (B) Uninstall leaves orphaned metadata. DELETE /packages/:id → 200 with persisted:{success:false,deletedCount:0}; the package vanishes from /packages but 7 sys_metadata rows survive and /meta/* keeps serving them. Reproduced with a second single-view package.
  • (C) Revert does not restore. Commits grow correctly per publish (and a failed publish adds none), but commit-revert returns success:false VERSION_NOT_FOUND: No history row at version 2 while GET …/history shows exactly that version-2 publish row — the revert's lookup key disagrees with what the history writer stores. The package-level revert route answers 500.
  • (D) The ADR-0070 read-only gate is missing on package lifecycle. PATCH /packages/<platform code package>/disable → 200 and DELETE → 200, with the code package actually removed from the registry listing. Reproduced on two platform packages. They return after a restart (code-loaded), but the running deployment loses them until then.

(Disable destroys no data — 3 rows before/during/after.)

6. app-management-toggle — post-login landing ignores isDefault

With an isDefault:true app published, four cold sign-ins in fresh contexts all landed on /_console/home. A control isolates it: navigating to /_console/ while already authenticated redirects correctly to the default app. On the unauthenticated first paint /meta/app 401s, RootLandingRedirect resolves with an empty app list and falls through to /home, and the auth guard then captures that already-wrong path into ?redirect=%2Fhome and honors it after sign-in — so the isDefault branch never re-runs. Not a stale bundle (the pin contains the routing).

Also confirmed: AppManagementPage's Disable app and Set as default are client-only stubs — both show a success toast with zero non-GET requests and no server change on reload. Nothing was credited off the toasts.

7. shell-nav-personalization — sidebar collapse does not survive reload

The cookie is written (sidebar_state=false, 7-day max-age) and is still present after reload, yet the sidebar renders expanded (255px, data-state=expanded) — measured at +2s/+4s/+8s to exclude a settling race, reproduced 3×. Root cause: objectui sidebar.tsx only ever writes the cookie and never reads it back; SidebarProvider initialises from defaultOpen = true and no SPA caller passes a cookie-derived defaultOpen (the shadcn original relies on a server component, which a pure SPA has no equivalent of).

Pins, favorites and recents all persist correctly with real sys_user_preference rows. Reorder is blocked(fixture) — no drag affordance is rendered at all. Recents capture record visits but not object-list visits.

🟡 PARTIAL

nav-surfaces-render — 30/31 pinned surfaces pass; the one red assertion is the test's own timing race, disproven as a product defect (3 fresh loads each rendered 5 real charts; polling boundingBox every 400 ms returned null only at t=1.5s — the recharts ResponsiveContainer initial-layout window the suite measures in — then non-null for all 19 later samples; the same test re-run in isolation is 4/4 green). Suggested fix: poll boundingBox until non-null instead of a fixed 1500 ms wait.

Smaller observations

  • GET /api/v1/meta/diagnostics reports 94/94 registry entries INVALID with only two error shapes — fields.__search: Unrecognized key 'index' (the platform's own auto-added search column) and config: expected record, received undefined — affecting sys_* and showcase_* alike.
  • Wrong-panel warning: the console home header renders a different inbox that polls sys_inbox_message instead of /api/v1/notifications, shows no badge, and claims "You're all caught up" with 9 unread. (Corroborates the bell-panel defect filed from the approvals run.)
  • GET /api/v1/meta/app?id=… — the ?id= filter is inert; the same 3 apps come back for any value.

Activity

  1. huangyiirene commented on Aug 11, 2026

    @huangyiirene
    Collaborator

    Extraction ledger — COMPLETE

    Every FAIL, PARTIAL and smaller observation in this run now has a card. This record stays open as the aggregation node; it carries tracking, so the triage seat correctly skips it and it is not itself a dispatch candidate.

    Run item Finding Card Lane
    builtin-apps-nav-render (a) Signing Keys (JWKS) is a dead nav entry — a permission gate cannot prune an API-disabled object #7544 domain:metadata
    builtin-apps-nav-render (b) App baseline replaces rather than composes with platform member_default — 10 nav ids served, 7/7 objects 403 #7555 domain:identity
    metadata-registry-serving · metadata-authoring-roundtrip /meta/types + /meta/:type/:name/published swallowed by the /meta/:type catch-all #7526 domain:cli
    metadata-authoring-roundtrip Object-extension overlay missing from the by-name meta route — overlay fields unsettable through the UI #7556 domain:metadata
    metadata-authoring-roundtrip Studio page-create never completes (recordChrome strictly rejected) objectstack-ai/objectui#4232 objectui
    settings-hub-roundtrip SECURITY — encrypted settings echoed as plaintext on read #7522 → PR #7554 domain:services
    package-lifecycle (A)+(B) Disable is not an enforcement for objects; uninstall leaves 7 orphaned sys_metadata rows #7557 domain:engine-core
    package-lifecycle (C) Commit-revert VERSION_NOT_FOUND while /history shows that very row; package revert 500s #7559 domain:metadata
    package-lifecycle (D) ADR-0070 read-only gate missing — platform packages can be disabled and deleted #7560 domain:cli
    app-management-toggle Post-login landing ignores isDefault; Disable / Set-as-default are client-only stubs objectstack-ai/objectui#4233 objectui
    shell-nav-personalization Sidebar collapse cookie is written but never read back objectstack-ai/objectui#4234 objectui
    nav-surfaces-render (PARTIAL) The red assertion is the test's own timing race, disproven as a product defect — poll boundingBox instead of a fixed 1500 ms wait #7569 domain:devx
    Smaller obs /meta/diagnostics reports 94/94 registry entries INVALID, including on the platform's own __search column #7561 domain:engine-core
    Smaller obs Console home header renders a second inbox polling sys_inbox_message objectstack-ai/objectui#4235 objectui
    Smaller obs GET /meta/app?id=… filter is inert #7566 domain:cli

    The 3 PASS items produced no cards, as expected.

    Two things a reader of this run needs to know

    1. #7526 is a cross-run card. The two shadowed /meta routes here are the same defect class as /meta/objects/:name/state/:field from the api-backend run #7463 — three instances, one root cause. The card's deliverable is therefore not the three routes but the missing route-ledger ↔ live-mount parity gate: the existing route audit (#3642 / PR #3658) treats the ledger as ground truth for what is mounted, which is precisely the assumption these defects falsify, so that audit is green on this class by construction.

    2. A measurement conflict the #7555 implementer must resolve rather than inherit. This run measures the platform member_default as granting 23 objects; open issue #7344 (framework 88154bee1) states it grants only the better-auth identity tables plus sys_user_preference. Both cannot be right. #7555 instructs the dev to measure rather than trust either number.

    Stale-premise status

    The whole batch was checked against all 191 PRs merged into objectstack since 2026-08-10; none of these defects appears already fixed. That check was not optional theatre — a sibling card from this batch (#7524, approvals run) was closed precisely because PR #7395 had fixed it hours after build a86db175 was cut.

    One residual risk is recorded on #7556: PR #7306 (ADR-0029 D9, tenant overlay becomes its own contributor layer) merged 2026-08-10T09:34Z, right around the cut. It targets tenant _provenance:'org' overlays rather than a code-declared extension, so it is not a fix — but re-measure on current main before implementing.


    Generated by Claude Code

  2. huangyiirene commented on Aug 11, 2026

    @huangyiirene
    Collaborator

    Closing — extraction is complete (see the ledger comment above: 15 findings → 15 cards, each with its own lifecycle in its lane's queue). A closed run record stays fully readable; nothing actionable remains anchored here.

    Provenance, per the acting-on-instruction discipline: maintainer instruction of 2026-08-11 (chat, verbatim): 「QA run 相关的 issue 如果任务已经分拆出去了,是不是应该直接关闭」 — ruled yes for every run card whose extraction ledger is complete. Same exit the two all-pass run records (#7399, #7326) already took.


    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

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions