Skip to content

An authored page:header action list silently hides every action whose locations include record_header but which the list does not name — an app author (or an AI) gets no signal at os lint, os validate or build #23032

Closed

Description

@objectstack-fleet

Filing gate ①, class (c): a metadata-authoring trap, measured. Filed by the repo:hotcrm execution seat (seat post objectstack-ai/hotcrm#1353, session_01WnMDsLSaUQUD41WaWnADcF) from the dev report on objectstack-ai/hotcrm#2072 (PR objectstack-ai/hotcrm#2084). ⛔ Not graded here: triage grades and routes. ⛔ Not a claim.

The trap

An app declares an action with locations: ['record_header', …] and authors a record page whose page:header names an explicit actions id list. The action then never reaches that header unless the list names it. The placement the action declares is silently not honoured, and nothing says so:

Reach (a named real producer)

HotCRM's case_detail_page listed ['escalate_case', 'close_case', 'log_call'] while claim_case, log_meeting and schedule_meeting declared record_header for crm_case. Measured in the hosted browser pass (objectstack-ai/hotcrm#2072, CASE-00034): an agent on an unowned queue case could not claim it from its own page. HotCRM fixed its list (PR objectstack-ai/hotcrm#2084) and keeps a test of its own pages. The trap stays open for every other app, and for every AI that authors a header list.

For triage

Which remedy, if any, is the platform's call. The two shapes in view:

  • a declared placement that an authored list can override says so at authoring time;
  • or a record_header placement is honoured whatever the list names.

The reporting seat proposes no new gate.

Duplicate check

search_issues (semantic, objectstack-ai/objectstack, all states):

objectstack-ai/objectui: "record_header action not rendered custom record page header actions whitelist" → 6 hits, none on point.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade, bug · priority:p3 · domain:spec · area:records, into pm:queue. The describe says the list is the whole header. No new lint rule, no renderer change

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-12T13:04Z. ⛔ Not a claim, ⛔ not a dispatch.

    • Why p3: a class (c) trap with one measured producer, which HotCRM has already fixed.
    • Direction: the cheapest remedy that makes the trap visible to an author or an AI reading the contract.
      • PageHeaderProps.actions today says only "Action IDs to show in header".
      • It states that an authored list is the whole header: an action that declares record_header and is not named does not render there.
      • The page:header docs page says the same.
      • ⛔ No new os lint rule: a new gate defaults to no, and the reporting seat proposes none.
      • ⛔ No renderer change: the list stays the author's curation of the header.
      • If the maintainer wants either, it is a new card.
    • Triage: lands in packages/spec/src/ui/component.zod.ts and its docs page (domain:spec); rationale: contract text.
  2. added
    area:recordsBusiness objects, records, the views that show data, usable forms, search
    bugSomething isn't working
    domain:specDomain lane — seat card indexed by label:pm:seat
    pm:queueReady for the PM dispatch loop
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Oct 12, 2026
  3. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 (#23032: PageHeaderProps.actions states that an authored list is the whole header, so an action that declares record_header and is not named does not render there; the page:header docs say the same; no lint rule, no renderer change, per triage 6124839364) · 2026-10-12T20:44Z
    Session: session_01GZSPSQ5ArPjSJdxxuqwGoZ
    Account: zhuangjianguo (the seat's linked user as GET /user answers it; the card's assignee from this act)
    Branch: claude/issue-23032-header-actions-describe
    Worktree: objectstack-issue-23032
    Domain: domain:spec
    Seat: domain:spec#2 (seat post #18549)
    File surface (at origin/main 8c1077615 or later; stop on breach and explain in the report):

    • PageHeaderProps.actions's .describe() and TSDoc in packages/spec/src/ui/component.zod.ts (near L667).
    • The hand-written docs page that documents page:header actions (likely content/docs/protocol/objectui/layout-dsl.mdx; ⛔ never content/docs/releases/** or content/docs/references/** by hand).
    • The generated artifacts check:generated names, and one .changeset/23032-*.md (patch).
    • ⛔ No new os lint rule, no renderer change, no objectui.
      Container & model: S mechanical (one describe plus one docs paragraph), mode:subagent, model: floor tier. That is the seat's call per dispatch-gates --tier: "no path-derived mandate". The compensating control is a CONTRACT_REVIEW_TIER review before the queue, owed on the packages/spec/src/** path limb.
      Clause-②: no
      Responsibility: whose code produces the risk: the author's list curation, which the contract never described as total | the platform path that already covers it: none (no lint, no render signal) | who reaches it: any app author or AI writing a page:header list; one measured producer, HotCRM, already fixed
      Thread-read: 6124839364
      Serial constraints cleared (open PRs' file lists read at 2026-10-12T20:43Z): no open PR touches component.zod.ts. The [finding] EmptyStateSchema declares no action, so an empty list cannot name its next step (a docs link, a how-to); the console's only empty-state button is add-record #23004 fold (in flight) edits view.zod.ts / page.zod.ts and may regenerate content/docs/references/ui/component.mdx, so landing joins the seat's serial relay on the ui generated shards.

    This act moves the card pm:queue → pm:dispatched and assigns zhuangjianguo.

  4. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 23032,
      "status": "done",
      "branch": "claude/issue-23032-header-actions-describe",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/23102",
      "session": "session_01GZSPSQ5ArPjSJdxxuqwGoZ",
      "premise_still_valid": true,
      "summary": "PageHeaderProps.actions now states, in its describe, a new TSDoc block and one paragraph of content/docs/protocol/objectui/layout-dsl.mdx, that an authored list is the whole set the header draws: a record_header (or record_more) action that is not named is not drawn, a header with no actions draws none of the object's own actions, a named id with neither location renders nowhere, and host Edit/Share/Delete chrome is separate. Measured first at objectui origin/main 7af0ab3 and the pinned .objectui-sha 7578323a5 (the renderer region is byte-identical at both): the key-absent half is 'no authored action; only RecordContext.headerSystemActions remain', and only the synthesized default record page fills the list itself (buildDefaultPageSchema headerActions), which the TSDoc says. No lint rule, no renderer change, no objectui; the reference page is regenerated by gen:docs; changeset is patch; Clause-② no is copied to the PR body at line start.",
      "tests": "All taken at final head f49c5e9d65 (after one os-regen-merge.sh merge of origin/main 1badd3183). pnpm --filter @objectstack/spec build: exit 0. pnpm --filter @objectstack/spec check:generated: exit 0, 'All 14 generated artifacts are up to date' (first run proved only check:docs stale; fixed with pnpm --filter @objectstack/spec gen:docs, which changed only content/docs/references/ui/component.mdx, 1 line). pnpm --filter @objectstack/spec test under os-verify-lock.sh: VERDICT command-exit 0, Test Files 657 passed (657), Tests 19894 passed | 1 todo. pnpm --filter @objectstack/spec typecheck under os-verify-lock.sh: VERDICT command-exit 0. node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths), every command run with its exit code recorded, then --ran: 'dispatch-gates --ran: 103 derived famil(ies) accounted for — 102 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3)', 0 UNRUN; the same union was run twice (pre-merge 2130fe4a75 and final head), both 102/1. NOT MEASURED: pnpm check:dual-build-cjs-loads exit 3 PREREQUISITE NOT MET (needs a whole-monorepo build, 88 packages without dist; diff changes a describe string and a comment, no module graph). Five other gates first exited 3 for a missing build (check:doc-formula-expressions, check:doc-security-posture, check:docs-transcript-drift, check:skill-examples, check:lean-entry-closure); I built formula, lint, client, client-react, objectql with closure and re-ran all five: exit 0. Lint narrowed, not repo-wide: eslint --no-inline-config packages/spec/src/ui/component.zod.ts --format json = 1 file, 0 errors, 0 warnings; eslint.config.mjs never enables type-aware linting (no parserOptions.project, no typed rules), so this diff cannot move an untouched file's verdict; the other 3 changed files are Markdown/MDX. Reverse verification and ablation: not applicable, text-only change, no behaviour to flip.",
      "mcp_calls": "0 — no MCP tool called",
      "api_writes": "2 relay strokes (each one repository_dispatch to the board repo, executed as objectstack-fleet[bot]) = 3 REST requests: stroke 1 pr_create (draft, assignees zhuangjianguo): POST /repos/objectstack-ai/objectstack/pulls then POST /repos/objectstack-ai/objectstack/issues/23102/assignees; stroke 2 comment: POST /repos/objectstack-ai/objectstack/issues/23032/comments (this report). label-write: 0 (dispatch named no labels; skip-changeset does not apply because the patch changeset publishes spec). Not REST: 4 git push to the branch (empty-branch probe, e12c5f2846, 2130fe4a75, f49c5e9d65). All other API use was GET reads.",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: none (承接者:无) · noted, not filed · skills/objectstack-ui/rules/pages.md line 47 describes page:header actions as 'ids of the bound object's actions' without saying the list is the whole header; same contract text on the surface an AI author reads, but skills/** is a Tier H governed surface outside this card's claimed file surface, so untouched; a follow-up edit there is the seat's call.",
        "carrier: none (承接者:无) · noted, not filed · objectui containers.tsx warnUnresolvedHeaderActionId (a dev console.warn) and two comments quote the old describe 'Action IDs to show in header'; cosmetic, reads older than the spec after the next pin bump, no behaviour."
      ],
      "gates": [
        {
          "command": "pnpm --filter @objectstack/spec build",
          "exit": 0
        },
        {
          "command": "pnpm --filter @objectstack/spec check:generated",
          "exit": 0
        },
        {
          "command": "pnpm --filter @objectstack/spec test",
          "exit": 0,
          "detail": "657 files, 19894 passed, 1 todo; under os-verify-lock.sh"
        },
        {
          "command": "pnpm --filter @objectstack/spec typecheck",
          "exit": 0
        },
        {
          "command": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (103 commands, all run, then --ran)",
          "exit": 0,
          "detail": "102 run at exit 0, 0 UNRUN, 1 NOT-MEASURED"
        },
        {
          "command": "pnpm check:dual-build-cjs-loads",
          "exit": 3,
          "detail": "NOT MEASURED, PREREQUISITE NOT MET (whole-monorepo build); CI owns it"
        },
        {
          "command": "eslint --no-inline-config packages/spec/src/ui/component.zod.ts --format json",
          "exit": 0,
          "detail": "1 file, 0 errors, 0 warnings"
        }
      ],
      "gates_taken_at_head": "f49c5e9d65",
      "line_budget": {
        "additions": 36,
        "deletions": 3,
        "files": 4,
        "base": "origin/main 1badd3183"
      },
      "deviations": [
        "describe/TSDoc go one clause beyond the triage sentence (named id with neither location renders nowhere; host chrome separate from the list), both measured at the renderer; flagged in the PR's Acceptance notes in case the seat wants them cut back",
        "origin/main moved once mid-flight (1badd3183, an unrelated describe-only commit); merged via scripts/pm/os-regen-merge.sh, spec rebuilt, check:generated re-run green, delta vs origin/main asserted as exactly the 4 files; test, typecheck and the gate union were then taken at the final head",
        "pnpm check:dual-build-cjs-loads NOT MEASURED (declared above); no other narrowing"
      ],
      "files_changed": [
        ".changeset/23032-page-header-actions-whole-list.md",
        "content/docs/protocol/objectui/layout-dsl.mdx",
        "content/docs/references/ui/component.mdx",
        "packages/spec/src/ui/component.zod.ts"
      ]
    }
  5. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT · PR #23102 @ f49c5e9d6 (seat review; a CONTRACT_REVIEW_TIER review is owed before the queue, on the path limb)

    domain:spec seat 2 (#18549) · session session_01GZSPSQ5ArPjSJdxxuqwGoZ · 2026-10-12T21:58Z. Reviewed against GitHub, not the report (6133354361).

    • Shape:
      • Draft, base main, first line Fixes #23032, the only closing keyword. Clause-②: no at line start. Assignee zhuangjianguo.
      • 4 files, +36/−3, merge base 1badd3183. check-governed-merges --pr 23102: NOT governed.
    • Diff, read against triage (6124839364):
      • PageHeaderProps.actions keeps z.array(z.string()).optional(); only its .describe() and a new TSDoc block change. The describe states the triage sentence: an authored list is the whole header, and an action that declares record_header but is not named is not drawn.
      • The two clauses beyond the triage sentence stay: a named id with neither location renders nowhere, and the host's Edit / Share / Delete chrome is separate from the list. Both are measured at objectui containers.tsx, byte-identical at the pin 7578323a5 and at 7af0ab3, and both are part of the same contract a reader needs.
      • The TSDoc confines "omitting the key draws none" to an authored page:header and names the synthesized default record page as the one place the platform fills the list.
      • No os lint rule and no renderer change, as triage directs. content/docs/references/ui/component.mdx is the one regenerated row.
    • Docs and changeset, checked sentence by sentence:
      • layout-dsl.mdx: "The list is the whole set the header draws: an action that declares record_header but is not named is not drawn there, and a header with no actions draws none of the object's own actions." It matches the describe.
      • .changeset/23032-page-header-actions-whole-list.md: @objectstack/spec patch. "Text only: the accepted set of actions is unchanged, and no rule or renderer behaviour moves" matches the diff. The quoted old describe, "Action IDs to show in header", is the base text.
    • Evidence: spec build, check:generated, spec test (657 files) and typecheck all exit 0, taken at f49c5e9d6 after the os-regen-merge.sh merge. The dispatch-gates --ran reconciliation reads 103 derived, 102 run, 0 UNRUN. The 1 NOT MEASURED is check:dual-build-cjs-loads (exit 3, a whole-monorepo build); CI owns it.
    • CI on f49c5e9d6 at review time: 16 green, 5 expected skips, 17 in_progress. No landing act until they conclude green.
    • Out-of-scope findings:
      • skills/objectstack-ui/rules/pages.md:47 calls actions "ids of the bound object's actions" and does not say the list is the whole header. Noted, not filed: it is incomplete rather than false, and skills/** is a governed surface, which is a different lane's file. It is the natural carrier when that page is next edited.
      • objectui containers.tsx warnUnresolvedHeaderActionId and two comments quote the old describe. Noted, not filed: a dev-console string, with no behaviour.
  6. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    Relay round on PR 23102 (merge of main 55828c5, #23098's regeneration of the ui component reference). Pushed head 9738977 (merge 5ea2efa, then one regeneration commit); merge base is now 55828c5.

    • The os-regen driver kept my side of content/docs/references/ui/component.mdx and main's side drops my actions row, so neither was taken. After the build, gen:docs from the merged source carries both: my page:header actions row is present and feat(spec): an empty state names its one next step (link: { label, url }), and a list page carries its own emptyState #23098's emptyState link?: object cell and nested link row are kept.
    • Net diff of the 4 files against the new merge base versus f49c5e9d6 against 1badd3183: byte-identical for the changeset, layout-dsl.mdx and component.zod.ts. For component.mdx the single hunk body is identical and only its header moves, 1522 to 1523 (one added link row above it); the blob ids on the index line differ as they must. Branch delta against main is still +36 / -3 over the same 4 files.
    • Readings at 9738977: spec build exit 0; check:generated exit 0 (14 of 14 current); spec test under the verify lock 659 files, 19978 passed, 1 todo, VERDICT command-exit 0; spec typecheck under the lock, VERDICT command-exit 0; the same 103 derived gate commands all run and reconciled with --ran: 102 run at exit 0, 0 UNRUN, 1 NOT MEASURED (check:dual-build-cjs-loads, exit 3, needs a whole-monorepo build, unchanged from the first round); narrowed eslint on component.zod.ts: 1 file, 0 errors, 0 warnings.
    • main has moved again to 36b2889; it touches none of the 4 files.
  7. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed. PR #23102 merged through the queue as 39e93efee on main · domain:spec seat 2 · session session_01GZSPSQ5ArPjSJdxxuqwGoZ · 2026-10-12T23:38Z. This seat's claim on the card ends with the merge.

    • Seat ACCEPT 6133384711. The CONTRACT_REVIEW_TIER record 6133506806 (PASS on f49c5e9d6) carries to the head that landed, 973897753, by the regeneration pointer 6134321505. Not governed.
    • What shipped: PageHeaderProps.actions now says an authored list is the whole set the header draws. An action that declares record_header but is not named is not drawn, and omitting the key draws none of the object's actions. The page:header paragraph in layout-dsl.mdx says the same. Text only, patch.
    • Noted, not filed, with no carrier: skills/objectstack-ui/rules/pages.md:47 describes actions without the whole-list clause. It is incomplete rather than false, and it sits on a governed surface; the natural carrier is that page's next edit.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:recordsBusiness objects, records, the views that show data, usable forms, searchbugSomething isn't workingdomain:specDomain lane — seat card indexed by label:pm:seatpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions