Repository navigation
QA run · platform-core (FULL area) · a86db175 · 2026-08-11 · 3 PASS / 1 PARTIAL / 7 FAIL #7514
Description
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 insettings-hub-roundtripis described below at defect+root-cause level, with the step-by-step recipe withheld pending the maintainer's call (same handling as theFOLLOW-UPS.mdD1 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/typesis swallowed by the generic/meta/:typehandler →{"type":"types","items":[]}, shape-identical to/meta/zzz_not_a_type./meta/diagnosticsand/_draftsdo work, so literal routes registered before:typeare 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/typesin its header comment but actually calls/meta— the drift is unpinned.GET /meta/:type/:name/publishedis 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-serverpre-registers/references,/layers,/history,/audit,/diffbefore 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/objectsstill 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 withpersisted:{success:false,deletedCount:0}; the package vanishes from/packagesbut 7sys_metadatarows 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:falseVERSION_NOT_FOUND: No history row at version 2whileGET …/historyshows 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 andDELETE→ 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/diagnosticsreports 94/94 registry entries INVALID with only two error shapes —fields.__search: Unrecognized key 'index'(the platform's own auto-added search column) andconfig: expected record, received undefined— affectingsys_*andshowcase_*alike.- Wrong-panel warning: the console home header renders a different inbox that polls
sys_inbox_messageinstead 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
huangyiirene commentedon Aug 11, 2026
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.builtin-apps-nav-render(a)domain:metadatabuiltin-apps-nav-render(b)member_default— 10 nav ids served, 7/7 objects 403domain:identitymetadata-registry-serving·metadata-authoring-roundtrip/meta/types+/meta/:type/:name/publishedswallowed by the/meta/:typecatch-alldomain:climetadata-authoring-roundtripdomain:metadatametadata-authoring-roundtriprecordChromestrictly rejected)settings-hub-roundtripdomain:servicespackage-lifecycle(A)+(B)sys_metadatarowsdomain:engine-corepackage-lifecycle(C)VERSION_NOT_FOUNDwhile/historyshows that very row; package revert 500sdomain:metadatapackage-lifecycle(D)domain:cliapp-management-toggleisDefault; Disable / Set-as-default are client-only stubsshell-nav-personalizationnav-surfaces-render(PARTIAL)boundingBoxinstead of a fixed 1500 ms waitdomain:devx/meta/diagnosticsreports 94/94 registry entries INVALID, including on the platform's own__searchcolumndomain:engine-coresys_inbox_messageGET /meta/app?id=…filter is inertdomain:cliThe 3 PASS items produced no cards, as expected.
Two things a reader of this run needs to know
1.
#7526is a cross-run card. The two shadowed/metaroutes here are the same defect class as/meta/objects/:name/state/:fieldfrom 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
#7555implementer must resolve rather than inherit. This run measures the platformmember_defaultas granting 23 objects; open issue #7344 (framework88154bee1) states it grants only the better-auth identity tables plussys_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
objectstacksince 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 builda86db175was 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 currentmainbefore implementing.Generated by Claude Code
docs(qa): deliver the two security-sensitive write-ups (D9, D10) per …
huangyiirene commentedon Aug 11, 2026
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
fix(runtime): refuse to disable or delete a read-only package (object…