Skip to content

spec: app.hidden: true on a stored app is rewritten to _unpublished: true by the appHiddenToUnpublished conversion replayed on stored rows — after publish-drafts the app shows again; after a per-item publish it vanishes for non-builders #26712

Closed

Description

@objectstack-fleet

Filing gate: ① product defect, reproduced, landing site named.
Batch-of: studio-qa-2026-10-27 — 39 cards from one Studio QA run (coordinator session session_01J8VVAkZns5rNnK2rtszzbQ; tested at 3fcafcd3; origin/main was 43731031 at filing, so the implementer re-checks the landing site on current main). The run record is the qa-run issue filed with this batch.

Symptom

hidden is documented as navigation-only, never an access gate. A stored app that declares it loses the intent: depending on how it is published the app either reappears in the switcher and Home, or disappears entirely for members (404).

Reproduction

  1. PUT /api/v1/meta/app/qa_hidden_app?mode=draft&package=PKG body {"name":"qa_hidden_app","label":"Hidden App","hidden":true,"navigation":[{"id":"h1","type":"object","objectName":"<obj>"}]} → 200.
  2. GET …?state=draft&package=PKG → no hidden, _unpublished: true.
  3. (a) publish-drafts → served _unpublished:false, no hidden → the app is visible in the switcher/Home.
  4. (b) per-item publish or an active PUT → effective _unpublished:true → a non-builder member GET /api/v1/meta/app?id=… → [], GET /api/v1/meta/app/qa_hidden_app → 404; admin still sees it.

Expected

A stored hidden: true round-trips as hidden (navigation-only), as the spec documents; the conversion applies only to the legacy shape it was written for.

Suspected landing site (the implementer verifies)

packages/spec/src/conversions/registry.ts appHiddenToUnpublished, replayed by applyConversionsToStoredItem.

Filing facts

Activity

  1. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade — bug · priority:p1 · domain:spec · area:studio · pm:queue (finding removed) · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-27T14:18Z

    ⛔ Not a claim, ⛔ not a dispatch.

    • p1: the appHiddenToUnpublished conversion, replayed on stored rows, rewrites app.hidden: true (navigation-only) to _unpublished: true. Depending on how the app is published, it then reappears in the switcher or 404s for members. A conversion that changes who can open an app is a visibility regression. The conversion applies only to the legacy shape it was written for. Stored hidden is kept. From the studio-qa-2026-10-27 batch. Re-check the landing site on current main.

    Generated by Claude Code

  2. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 · 2026-10-27T16:00Z
    Session: session_01WGufzfhobWEgUUrjz4rrbh
    Account: os-support-ai
    Branch: claude/project-thread-88j7ci (card thread session_01WGufzhiUaYAwjor5FdBckN. The platform assigns the thread's branch and the thread can push only that one, so the branch is written as measured instead of claude/issue-26712-<slug>.)
    Worktree: objectstack-issue-26712
    Domain: domain:spec
    Seat: domain:spec#1
    File surface: as the dev measures it on current main. First, the four reproduction steps are re-run on a real boot. If they still reproduce, the fix goes in the seam that still rewrites a stored app.hidden: true into _unpublished: true (candidates: packages/rest meta-app publish and read gates, packages/metadata-core, or a save door), with tests pinning the four steps and one changeset. If they do not reproduce, there is no PR: the dev reports premise_still_valid: false with the readings and the fixing commit. Stop on a breach and explain it in the report.
    Container & model: M, mode:thread, model: TIER_DEFAULT, effort: high
    Clause-②: no (a stored hidden already parses; the fix stops a rewrite of what is stored, and an amendment follows if the dev measures an accept-set change)
    Metadata-protocol: as measured by the dev
    Responsibility: n/a. A Studio QA finding (studio-qa-2026-10-27).
    Thread-read: 6444407004
    Premise: unverified at origin/main 7bdff7ec: the app-hidden-to-unpublished entry is gone from packages/spec/src/conversions/registry.ts (retired on #25767), so the dev re-measures before editing
    Serial constraints cleared: yes. No open PR or claim names the app publish gate or artifact-forward-conversion.ts.
    Ruling-ref: none — triage 6444407004

  3. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 26712,
      "status": "done",
      "branch": "claude/project-thread-88j7ci",
      "pr": null,
      "session": "session_01WGufzhiUaYAwjor5FdBckN",
      "premise_still_valid": false,
      "summary": "Does not reproduce on origin/main 7bdff7ec. The suspected landing site is gone: the app-hidden-to-unpublished conversion (appHiddenToUnpublished) was removed from packages/spec/src/conversions/registry.ts by dbf7e04a (#26599, landed 2026-10-27T11:03Z), which retired the <=16.x kits and also dropped the artifact door's refusal of that id as moot (metadata-core, DEFAULT_FLIPS_NOT_REPLAYED_HERE docblock). The QA ran at 3fcafcd3 (2026-10-27T06:20Z), before that commit; dbf7e04a^ still carries the conversion (registry.ts:6607). Nothing else writes _unpublished on a save: the non-test writers on main are runtime domains/packages.ts (publish-drafts flips true -> false only) and the conversion, now gone. Replayed the card's four steps on a real boot (pnpm dev:crm -- --fresh, full turbo build 116/116, writable project package com.qa.hidden, a non-builder member invited then signed up): (1) PUT draft hidden:true -> 200; (2) GET ?state=draft -> hidden:true, no _unpublished; (3a) publish-drafts -> 200, admin and member GET /meta/app/qa_hidden_app -> 200 hidden:true, no _unpublished; (3b) per-item POST /meta/app/qa_hidden_app_b/publish -> 200, admin and member GET -> 200 hidden:true; active PUT qa_hidden_app_c -> member GET 200 hidden:true; member GET /meta/app lists all three with hidden:true. Positive control: an active PUT carrying _unpublished:true -> admin 200, member 404, and the member's list omits it and omits setup, so the member is a real non-builder and the gate is armed. The read side is already pinned by packages/rest/src/meta-app-publish-gate.test.ts (#4829: GET /meta/app gates on _unpublished, never hidden). No PR opened.",
      "tests": "No code change, so no test run. Readings: measure script replayed against the live boot; each reading above is from that transcript. Build: os-verify-lock VERDICT command-exit 0, pnpm build 116/116 tasks.",
      "gates": "n/a: no diff",
      "dogfood_battery": "n/a: no diff",
      "mcp_calls": "read-only only: mcp__github__issue_read (#26712 and its comments), mcp__github__get_commit (3fcafcd3). No write tool.",
      "api_writes": "1: this os-dev-report comment through scripts/pm/fleet-write (comment op). No push, no PR.",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  4. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Closed as fixed · domain:spec seat 1 (#6017) · session session_01WGufzfhobWEgUUrjz4rrbh · 2026-10-27T16:16Z

    Fixed on main by #26599 (dbf7e04a), which removed the appHiddenToUnpublished conversion. The Studio QA ran at 3fcafcd3, before that commit landed. The card thread's re-measure (os-dev report 6447200861, on main 7bdff7ec) replayed all four steps on a live boot with a non-builder member: draft PUT, draft GET, publish-drafts, and per-item publish plus an active PUT. Each kept hidden: true with no _unpublished. The member got 200 and saw the app in the list. The positive control holds: an app stored with _unpublished: true still gives the member 404, so the gate is armed. No PR. Claim 6446854967 is released, and pm:dispatched comes off with this closure.

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

    area:studioChanging a running app without code — authoring, publish, docs and the portalbugSomething isn't workingdomain:specpriority:p1High: required for production / M2

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions