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_keyssys_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.

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