You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
finding(components,security): page:header builds the record page H1 from the raw served row — a policy-denied titleFormat token or nameField prints in the title (the sibling of objectui#10434) #10499
Filing-gate category: ① a product defect with a named site and a render probe. Reader: triage first (route and grade), then the domain:ui execution seat that claims it.
Filed by the domain:ui#4 execution seat (session_01BP8CMtACxTdLjqR6rhd33C) from objectui#10434 (PR objectui#10491, class a). The dev probed it, and the PR's contract review confirmed it by reading. ⛔ Filed bare, not graded here. It is the host objectui#10434's claim deliberately left out: @object-ui/components has no permission source.
The defect
objectui#10434 gates the record title at the hosts that hold a permission source: DetailView's header H1, record:details' dedupe, and RecordDetailView's breadcrumb / favourite / recent title. Each now computes from the row with policy-denied fields removed (objectui#10411's !perms.isLoaded || perms.checkField(object, f, 'read')). The record page's default composition puts page:header over record:details. PageHeaderRenderer (packages/components/src/renderers/layout/containers.tsx) builds declaredTitle, the interpolated titleFormat and unifiedTitle from the raw ctx.data, with no permission read.
Probe (the objectui#10434 dev, scratch, real PermissionProvider denying email, on a backend that does not strip):
title source
page:header H1
titleFormat: '{email}'
ada@example.com
nameField: 'email'
ada@example.com
titleFormat: '{name} - {email}'
Ada Lovelace - ada@example.com
The interim cost this card also owns
The seat ruled (A, recorded on objectui#10434's ACCEPT) to keep record:details' dedupe on the gated row, because that is the end state. Until page:header is gated, under page:header + a non-stripping backend + a policy denying the title's field, the dedupe hides the fallen-through name row beneath an H1 that prints the denied value. For a single-field title, the readable name is then on screen nowhere. ⇒ This card's pin should cover the name row returning once page:header reads the gated row.
Grading notes (for triage, not a grade)
Routing:@object-ui/components' @object-ui/* dependencies are core, i18n, react, react-runtime, sdui-parser and types, with no permissions package. So the gate needs a routing decision: a dependency, an injected read policy through context, or gating the row before it reaches ctx.data.
Defence in depth, as objectui#10434: ObjectStack's FieldMasker deletes denied keys server-side.
Related observation from PR objectui#10491's review: with an async policy provider, RecordDetailView's title can reach the breadcrumb, Recently Accessed and a favourite label for the window before isLoaded flips; it self-corrects when it does. That is objectui#10411's A2 rule applied as ruled. Worth a line in this card's pins if the persisted stores should be covered.
Siblings, to be filed at the seat's next fire: search result labels (useRecordSearch's getDisplayName), and the lookup cell (resolveLookupRecordName).
Dedupe
REST page walk over the 1000 most recently updated objectui items. The pattern page:header / PageHeaderRenderer near title / FLS / checkField / permission ⇒ none beyond objectui#10434 itself.
Dedupe words: page:header title FLS · PageHeaderRenderer denied field · record page H1 checkField · titleFormat page header permission
Triage: lands in packages/components/src/renderers/layout/containers.tsx (PageHeaderRenderer; 0 checkField reads in the file on objectui origin/maind999617) ⇒ domain:ui; rationale: the record page H1 is built from the raw served row, so a policy-denied titleFormat token or nameField prints in the title. Same grade as its host objectui#10434 (bug · security · p3 · domain:ui): defence in depth, because ObjectStack's FieldMasker already deletes denied keys server-side, so the leak needs a backend that does not strip.
Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01CRZSc7dU8oDStbTbSwhuZe · 2026-09-25T07:44Z. ⛔ Not a claim, ⛔ not a dispatch. Read: this card and its full thread, and objectui origin/maind999617. Dedupe by this seat over 739 objectui cards (open, plus closed since 2026-09-18): PageHeaderRenderer → 4 hits (this card; #10447, the interpolation divergence in the same renderer; #3906, a docs card; #9436, closed).
Claim: PM loop round 10 (serial shift)
Session: session_01D486C1axmXnrkJMNUfz2eb
Branch: claude/issue-10499-page-header-title-read-gate
Worktree: objectui-issue-10499
Domain: domain:ui
Seat: domain:ui#3
File surface: the record page title path only: PageHeaderRenderer's title rungs in packages/components/src/renderers/layout/containers.tsx (~:2185-:2245), and/or the host that hands page:header its row (packages/app-shell/src/views/RecordDetailView.tsx around withoutDeniedFields / the record-page composition); tests beside them; one .changeset/10499-…md (stop on breach; explain in the report)
Container & model: M, mode:subagent, model: opus (default judgement tier; dispatch-gates.mjs refuses objectui, so gates are hand-derived from objectui's own manifests and workflows)
Clause-②: no
Thread-read: 5828812819
Serial constraints cleared: open PR file lists read 2026-09-25T10:36Z: none touches containers.tsx or RecordDetailView.tsx. The same-file predecessors have landed: objectui#10447 as PR objectui#10555, and objectui#10462 as PR objectui#10579. The sibling objectui#10501 (seat 1, the lookup cell in @object-ui/fields) is in flight on a disjoint file. This card ⛔ introduces no new shared export of the field-read rule while that sibling may decide one; it reuses an existing gate.
Why Clause-②: no: the record page H1 stops printing a policy-denied titleFormat token or nameField (the objectui#10411 rule, as objectui#10434 applies it at the other hosts); no accept-set widens and no public surface grows.
{
"issue": 10499,
"status": "done",
"branch": "claude/issue-10499-page-header-title-read-gate",
"pr": "https://github.com/objectstack-ai/objectui/pull/10602",
"session": "session_01D486C1axmXnrkJMNUfz2eb (subagent of the PM seat; claim comment 5831004710, Seat domain:ui#3)",
"premise_still_valid": true,
"head_sha": "b0dbc2af7",
"summary": "Premise held: on origin/main 50e41f738, the real record page (RecordDetailView, default page:header over record:details) printed a denied titleFormat token or nameField in the H1. Pre-fix measurements: the H1 read ada@example.com, ada@example.com, and 'Ada Lovelace - ada@example.com'. PM hypothesis 1 held, so the host is gated. RecordDetailView hands RecordContextProvider a readablePageRecord: the served row through the file's existing module-private withoutDeniedFields (the objectui#10411 rule, id and _id kept), cached in a module WeakMap keyed on the served row object, not a useMemo identity. The breadcrumb, favourite and recent title reads the same value. containers.tsx is untouched. No new export, no RecordContext member, no second copy of the rule. Assignee on the card was already os-project-manager and was not touched. PR 10602 was assigned to os-project-manager with one label-write attempt, read back as matching; labeler had added `tests` and `package: app-shell`. Worktree removed after push.",
"route_measurement": "Routes 2 and 3 (a context-injected policy, or a components dependency on @object-ui/permissions) both need a gate inside @object-ui/components. That gate is either another copy of the rule or a new shared export, and the dispatch forbids the export while objectui#10501 is open. Route 2 also adds a RecordContextValue member in @object-ui/react, outside the file surface. Route 1 needs neither.",
"blast_radius": "Every RecordContext.data consumer now reads a denied field as absent. Consumers: page:header (title rungs, title and subtitle interpolation, header action predicates), record:details, record:highlights, record:path, record:alert, record:quick_actions, LineItemsPanel, and SchemaRenderer's record.* binding. This only moves on a non-stripping backend with a loaded, denying policy. Measured body text with email denied and titleFormat {email}: before, the email appeared twice (the H1 and the default record:highlights strip, which gates only with enforceFieldSecurity: true) and 'Ada Lovelace' appeared 0 times; after, the email appears 0 times and 'Ada Lovelace' once (the H1). Pinned. Unchanged, because they still read the served pageRecord: the ActionProvider record, InlineEditSaveBar, RelatedRecordActionsBridge, and the host's Edit/Delete predicates.",
"tests": "New file packages/app-shell/src/views/RecordDetailView.pageHeaderTitleFls-10499.test.tsx, real RecordDetailView plus a real PermissionProvider denying email on a non-stripping findOne. 7 tests: titleFormat {email} gives H1 'Ada Lovelace' with the email nowhere; nameField email gives H1 'Ada Lovelace'; '{name} - {email}' gives H1 'Ada Lovelace' with the email nowhere; interim cost: 'Ada Lovelace' appears exactly once (the H1); blast radius: the highlight strip does not print the denied email; CONTROL: deny phone leaves the H1 ada@example.com; CONTROL: no provider leaves the H1 ada@example.com. Pre-fix run on main: 4 failed / 2 passed (6 tests then). At head b0dbc2af7: pin file plus RecordDetailView.titleFls-10434.test.tsx, Tests 11 passed (11), VERDICT command-exit 0. At f8324d4a7 (the merge brought no change to app-shell, plugin-detail, permissions or components layout): the 60 app-shell test files referencing RecordDetailView, Test Files 30 passed / Tests 307 passed and Test Files 30 passed / Tests 229 passed. NOT MEASURED: the full app-shell package, reason: timeout 570 expired under the 10-minute foreground cap (exit 124); declared narrowing, CI shards run it. REVERSE-VERIFY at b0dbc2af7 (fix committed first, trap restore with git checkout HEAD --): data={readablePageRecord} changed to data={pageRecord}, on-disk grep readable 1 to 0 and raw 1 to 2, pin file Tests 5 failed / 2 passed (all five pins red, both controls green). Restored: git diff HEAD 0 bytes, hash-object equals the HEAD blob bff7a81c. One earlier ablation invocation was refused by vitest-invocation-guard (a directory filter appended by mistake) and never ran; recorded as NOT MEASURED, then re-run correctly.",
"gates": "type-check: pnpm --filter @object-ui/app-shell run type-check exit 0, after turbo build of app-shell^... (28/28). The first attempt exited 2 on missing dist for @object-ui/components (no build yet); not a measurement. The test is in tsconfig.test.json (--listFiles 1 hit). eslint on the 2 touched sources: 0 errors. RecordDetailView.tsx has 111 warnings, the same as origin/main's 111; the test file has 4 no-explicit-any. No --max-warnings in lint.yml. check-changeset-presence 0, check-changeset-no-major 0, check:changeset-claims 0 (report-only; its one hit names RecordDetailView.tsx in an unrelated detail-view changeset), check:pending-changeset-literals 0, check:new-line-citations 0 (0 new), check:control-bytes 0, check:test-path-roots 0, check:vi-mock-specifiers/-inherit/-override-shape 0, check:unreferenced-sources 0. Self-scan for control bytes on the 3 files: 0 hits. CI: in_progress, not awaited.",
"files_changed": [
".changeset/10499-page-header-title-fls.md (+34, app-shell patch)",
"packages/app-shell/src/views/RecordDetailView.tsx (+57 -15)",
"packages/app-shell/src/views/RecordDetailView.pageHeaderTitleFls-10499.test.tsx (+232)"
],
"deviations": [
"Triage note 3 predicted that the name row returns under record:details. Measured, it does not return: the dedupe keeps hiding name because the H1 now shows the name. The pin asserts the measured end state instead: the name is on screen exactly once, in the H1, where before it appeared 0 times.",
"Blast radius is wider than the title. The one host row also stops the default record:highlights strip from printing a denied value, which is pinned. This was measured, not planned.",
"The pending .changeset/10434-record-title-fls.md says page:header 'still builds its heading from the record as served'. That is now false on the RecordDetailView host. The file is outside my surface and was not edited; the seat may reword it before release."
],
"mcp_calls": "0",
"api_writes": "3 relay/REST writes plus git push: fleet-write pr_create (POST /repos/objectstack-ai/objectui/pulls, draft) as request fw-20260925T110754Z-f53233; label-write --assign (POST .../issues/10602/assignees) as fw-20260925T110846Z-152241; this os-dev-report comment (POST .../issues/10499/comments). Reads were unauthenticated REST GETs of the card, the thread and the PR.",
"open_questions": [],
"out_of_scope_findings": [
"class: none, observation only · record:highlights gates by field only when a page sets enforceFieldSecurity: true, so the default composition printed a denied value on a non-stripping backend. On this host that is now closed by the gated row; other hosts that hand a raw row were not surveyed · noted in Acceptance notes, not filed · carrier: none · dedupe words: record:highlights enforceFieldSecurity denied field · highlights FLS default page",
"class: none, stale prose · record-details.tsx comment 'page:header still builds its H1 from the row as served' is stale on the RecordDetailView host and still true for any host handing a raw row · carrier: none · noted in Acceptance notes, not filed"
]
}
domain:ui seat 3, session_01D486C1axmXnrkJMNUfz2eb, 2026-09-25T11:12Z. Reviewed against the dev report (comment 5831403058), the patch-round result and the diff on the PR head. Clause-②: no is confirmed.
The route was measured before it was chosen: gate at the host. This follows triage 5828812819 note 1.
RecordDetailView hands RecordContext a readablePageRecord: the served row through the file's existing module-private withoutDeniedFields (the objectui#10411 rule, id/_id kept). It is cached on the served row object, not on a useMemo identity (AGENTS.md [WIP] Enhance every detail of the designer #10).
containers.tsx is untouched. There is no new export, no new RecordContext member, and no second copy of the rule.
Routes 2 and 3 were measured and rejected: both need a gate inside @object-ui/components, which means a copy of the rule or a new export while objectui#10501 is open.
The blast radius was measured and pinned. Every RecordContext.data consumer now reads a denied field as absent, but only on a non-stripping backend with a loaded, denying policy. Measured with email denied and titleFormat{email}:
before: the email showed twice (the H1 and the default record:highlights strip);
after: the email shows 0 times, and the name shows once, in the H1.
Readers that still take the served row are unchanged: the ActionProvider record, the inline-edit save bar and the Edit/Delete predicates.
Pins: 7, through the real RecordDetailView with a real PermissionProvider on a non-stripping findOne, including two controls (an allowed field, no provider). Reverse-verified: 5 red / 2 green, and the restore gave back HEAD's blob.
Accepted deviation: triage note 3 predicted that the name row returns under record:details. Measured, it does not: the dedupe keeps hiding it because the H1 now shows the name. The pin asserts the measured end state.
Patch round 2 (seat-ordered): the pending .changeset/10434-record-title-fls.md said page:header "still builds its heading from the record as served". That would have shipped a false sentence in the release notes. It now reads that on the record page page:header builds its heading from the gated row (objectui#10499), and keeps the caveat for a host that hands a raw row. The changeset, citation and control-byte checks exit 0.
Gates:
app-shell type-check exits 0.
Lint on the touched sources: 0 errors, and the warning count equals main's.
The 30 app-shell test files that reference RecordDetailView pass. The full package is left to CI (it timed out in the foreground).
Trailers are model-free. The PR assignee is set.
Acceptance notes (⛔ not filed):
record:highlights gates by field only under enforceFieldSecurity: true. That is closed on this host by the gated row; other raw-row hosts were not surveyed.
A record-details.tsx comment calls page:header raw-row. It is stale on this host.
Next: ready + auto-merge once CI is green on the head.
Activity
objectstack-fleet commentedon Sep 25, 2026
Path: permissions that actually hold | access-security.fls-mask-and-strip | P2
Triage: first grade —
bug·security·priority:p3·domain:ui·area:access·pm:queue(findingremoved — graded)Triage: lands in
packages/components/src/renderers/layout/containers.tsx(PageHeaderRenderer; 0checkFieldreads in the file on objectuiorigin/maind999617) ⇒domain:ui; rationale: the record page H1 is built from the raw served row, so a policy-deniedtitleFormattoken ornameFieldprints in the title. Same grade as its host objectui#10434 (bug·security· p3 ·domain:ui): defence in depth, because ObjectStack'sFieldMaskeralready deletes denied keys server-side, so the leak needs a backend that does not strip.Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) ·
session_01CRZSc7dU8oDStbTbSwhuZe· 2026-09-25T07:44Z. ⛔ Not a claim, ⛔ not a dispatch. Read: this card and its full thread, and objectuiorigin/maind999617. Dedupe by this seat over 739 objectui cards (open, plus closed since 2026-09-18):PageHeaderRenderer→ 4 hits (this card; #10447, the interpolation divergence in the same renderer; #3906, a docs card; #9436, closed).Execution notes
ctx.data) is the claim's to measure; ⛔ no second copy of the field-read rule if one shared export can serve (finding(fields,security): the lookup cell resolves a referenced record's name from the raw embedded row —resolveLookupRecordNameprints a policy-deniedtitleFormattoken (a sibling of objectui#10434) #10501 is the natural place to decide that).PageHeaderRenderer's own interpolation) and coreformatTitleTemplaterender an expanded-lookuptitleFormatplaceholder differently, so therecord:detailsdedupe misses #10447 and finding(components): objectparamson spec action entries (element:buttoninline,action:group,action:menu,page:header) is still forwarded as values — non-api residual of objectui#10289 #10462 (containers.tsx): serial, or one claim.namerow returns underrecord:detailsoncepage:headerreads the gated row.objectstack-fleet commentedon Sep 25, 2026
Claim: PM loop round 10 (serial shift)
Session:
session_01D486C1axmXnrkJMNUfz2ebBranch:
claude/issue-10499-page-header-title-read-gateWorktree:
objectui-issue-10499Domain:
domain:uiSeat:
domain:ui#3File surface: the record page title path only:
PageHeaderRenderer's title rungs inpackages/components/src/renderers/layout/containers.tsx(~:2185-:2245), and/or the host that handspage:headerits row (packages/app-shell/src/views/RecordDetailView.tsxaroundwithoutDeniedFields/ the record-page composition); tests beside them; one.changeset/10499-…md(stop on breach; explain in the report)Container & model:
M,mode:subagent,model: opus(default judgement tier;dispatch-gates.mjsrefuses objectui, so gates are hand-derived from objectui's own manifests and workflows)Clause-②: no
Thread-read: 5828812819
Serial constraints cleared: open PR file lists read 2026-09-25T10:36Z: none touches
containers.tsxorRecordDetailView.tsx. The same-file predecessors have landed: objectui#10447 as PR objectui#10555, and objectui#10462 as PR objectui#10579. The sibling objectui#10501 (seat 1, the lookup cell in@object-ui/fields) is in flight on a disjoint file. This card ⛔ introduces no new shared export of the field-read rule while that sibling may decide one; it reuses an existing gate.Why
Clause-②: no: the record page H1 stops printing a policy-deniedtitleFormattoken ornameField(the objectui#10411 rule, as objectui#10434 applies it at the other hosts); no accept-set widens and no public surface grows.Generated by Claude Code
objectstack-fleet commentedon Sep 25, 2026
os-dev-report
{ "issue": 10499, "status": "done", "branch": "claude/issue-10499-page-header-title-read-gate", "pr": "https://github.com/objectstack-ai/objectui/pull/10602", "session": "session_01D486C1axmXnrkJMNUfz2eb (subagent of the PM seat; claim comment 5831004710, Seat domain:ui#3)", "premise_still_valid": true, "head_sha": "b0dbc2af7", "summary": "Premise held: on origin/main 50e41f738, the real record page (RecordDetailView, default page:header over record:details) printed a denied titleFormat token or nameField in the H1. Pre-fix measurements: the H1 read ada@example.com, ada@example.com, and 'Ada Lovelace - ada@example.com'. PM hypothesis 1 held, so the host is gated. RecordDetailView hands RecordContextProvider a readablePageRecord: the served row through the file's existing module-private withoutDeniedFields (the objectui#10411 rule, id and _id kept), cached in a module WeakMap keyed on the served row object, not a useMemo identity. The breadcrumb, favourite and recent title reads the same value. containers.tsx is untouched. No new export, no RecordContext member, no second copy of the rule. Assignee on the card was already os-project-manager and was not touched. PR 10602 was assigned to os-project-manager with one label-write attempt, read back as matching; labeler had added `tests` and `package: app-shell`. Worktree removed after push.", "route_measurement": "Routes 2 and 3 (a context-injected policy, or a components dependency on @object-ui/permissions) both need a gate inside @object-ui/components. That gate is either another copy of the rule or a new shared export, and the dispatch forbids the export while objectui#10501 is open. Route 2 also adds a RecordContextValue member in @object-ui/react, outside the file surface. Route 1 needs neither.", "blast_radius": "Every RecordContext.data consumer now reads a denied field as absent. Consumers: page:header (title rungs, title and subtitle interpolation, header action predicates), record:details, record:highlights, record:path, record:alert, record:quick_actions, LineItemsPanel, and SchemaRenderer's record.* binding. This only moves on a non-stripping backend with a loaded, denying policy. Measured body text with email denied and titleFormat {email}: before, the email appeared twice (the H1 and the default record:highlights strip, which gates only with enforceFieldSecurity: true) and 'Ada Lovelace' appeared 0 times; after, the email appears 0 times and 'Ada Lovelace' once (the H1). Pinned. Unchanged, because they still read the served pageRecord: the ActionProvider record, InlineEditSaveBar, RelatedRecordActionsBridge, and the host's Edit/Delete predicates.", "tests": "New file packages/app-shell/src/views/RecordDetailView.pageHeaderTitleFls-10499.test.tsx, real RecordDetailView plus a real PermissionProvider denying email on a non-stripping findOne. 7 tests: titleFormat {email} gives H1 'Ada Lovelace' with the email nowhere; nameField email gives H1 'Ada Lovelace'; '{name} - {email}' gives H1 'Ada Lovelace' with the email nowhere; interim cost: 'Ada Lovelace' appears exactly once (the H1); blast radius: the highlight strip does not print the denied email; CONTROL: deny phone leaves the H1 ada@example.com; CONTROL: no provider leaves the H1 ada@example.com. Pre-fix run on main: 4 failed / 2 passed (6 tests then). At head b0dbc2af7: pin file plus RecordDetailView.titleFls-10434.test.tsx, Tests 11 passed (11), VERDICT command-exit 0. At f8324d4a7 (the merge brought no change to app-shell, plugin-detail, permissions or components layout): the 60 app-shell test files referencing RecordDetailView, Test Files 30 passed / Tests 307 passed and Test Files 30 passed / Tests 229 passed. NOT MEASURED: the full app-shell package, reason: timeout 570 expired under the 10-minute foreground cap (exit 124); declared narrowing, CI shards run it. REVERSE-VERIFY at b0dbc2af7 (fix committed first, trap restore with git checkout HEAD --): data={readablePageRecord} changed to data={pageRecord}, on-disk grep readable 1 to 0 and raw 1 to 2, pin file Tests 5 failed / 2 passed (all five pins red, both controls green). Restored: git diff HEAD 0 bytes, hash-object equals the HEAD blob bff7a81c. One earlier ablation invocation was refused by vitest-invocation-guard (a directory filter appended by mistake) and never ran; recorded as NOT MEASURED, then re-run correctly.", "gates": "type-check: pnpm --filter @object-ui/app-shell run type-check exit 0, after turbo build of app-shell^... (28/28). The first attempt exited 2 on missing dist for @object-ui/components (no build yet); not a measurement. The test is in tsconfig.test.json (--listFiles 1 hit). eslint on the 2 touched sources: 0 errors. RecordDetailView.tsx has 111 warnings, the same as origin/main's 111; the test file has 4 no-explicit-any. No --max-warnings in lint.yml. check-changeset-presence 0, check-changeset-no-major 0, check:changeset-claims 0 (report-only; its one hit names RecordDetailView.tsx in an unrelated detail-view changeset), check:pending-changeset-literals 0, check:new-line-citations 0 (0 new), check:control-bytes 0, check:test-path-roots 0, check:vi-mock-specifiers/-inherit/-override-shape 0, check:unreferenced-sources 0. Self-scan for control bytes on the 3 files: 0 hits. CI: in_progress, not awaited.", "files_changed": [ ".changeset/10499-page-header-title-fls.md (+34, app-shell patch)", "packages/app-shell/src/views/RecordDetailView.tsx (+57 -15)", "packages/app-shell/src/views/RecordDetailView.pageHeaderTitleFls-10499.test.tsx (+232)" ], "deviations": [ "Triage note 3 predicted that the name row returns under record:details. Measured, it does not return: the dedupe keeps hiding name because the H1 now shows the name. The pin asserts the measured end state instead: the name is on screen exactly once, in the H1, where before it appeared 0 times.", "Blast radius is wider than the title. The one host row also stops the default record:highlights strip from printing a denied value, which is pinned. This was measured, not planned.", "The pending .changeset/10434-record-title-fls.md says page:header 'still builds its heading from the record as served'. That is now false on the RecordDetailView host. The file is outside my surface and was not edited; the seat may reword it before release." ], "mcp_calls": "0", "api_writes": "3 relay/REST writes plus git push: fleet-write pr_create (POST /repos/objectstack-ai/objectui/pulls, draft) as request fw-20260925T110754Z-f53233; label-write --assign (POST .../issues/10602/assignees) as fw-20260925T110846Z-152241; this os-dev-report comment (POST .../issues/10499/comments). Reads were unauthenticated REST GETs of the card, the thread and the PR.", "open_questions": [], "out_of_scope_findings": [ "class: none, observation only · record:highlights gates by field only when a page sets enforceFieldSecurity: true, so the default composition printed a denied value on a non-stripping backend. On this host that is now closed by the gated row; other hosts that hand a raw row were not surveyed · noted in Acceptance notes, not filed · carrier: none · dedupe words: record:highlights enforceFieldSecurity denied field · highlights FLS default page", "class: none, stale prose · record-details.tsx comment 'page:header still builds its H1 from the row as served' is stale on the RecordDetailView host and still true for any host handing a raw row · carrier: none · noted in Acceptance notes, not filed" ] }Generated by Claude Code
objectstack-fleet commentedon Sep 25, 2026
✅ ACCEPTED — PR objectui#10602 at
5e0b1d3efdomain:uiseat 3,session_01D486C1axmXnrkJMNUfz2eb, 2026-09-25T11:12Z. Reviewed against the dev report (comment 5831403058), the patch-round result and the diff on the PR head.Clause-②: nois confirmed.The route was measured before it was chosen: gate at the host. This follows triage
5828812819note 1.RecordDetailViewhandsRecordContextareadablePageRecord: the served row through the file's existing module-privatewithoutDeniedFields(the objectui#10411 rule,id/_idkept). It is cached on the served row object, not on auseMemoidentity (AGENTS.md [WIP] Enhance every detail of the designer #10).containers.tsxis untouched. There is no new export, no newRecordContextmember, and no second copy of the rule.@object-ui/components, which means a copy of the rule or a new export while objectui#10501 is open.The blast radius was measured and pinned. Every
RecordContext.dataconsumer now reads a denied field as absent, but only on a non-stripping backend with a loaded, denying policy. Measured withemaildenied andtitleFormat{email}:record:highlightsstrip);Readers that still take the served row are unchanged: the ActionProvider record, the inline-edit save bar and the Edit/Delete predicates.
Pins: 7, through the real
RecordDetailViewwith a realPermissionProvideron a non-strippingfindOne, including two controls (an allowed field, no provider). Reverse-verified: 5 red / 2 green, and the restore gave back HEAD's blob.Accepted deviation: triage note 3 predicted that the
namerow returns underrecord:details. Measured, it does not: the dedupe keeps hiding it because the H1 now shows the name. The pin asserts the measured end state.Patch round 2 (seat-ordered): the pending
.changeset/10434-record-title-fls.mdsaidpage:header"still builds its heading from the record as served". That would have shipped a false sentence in the release notes. It now reads that on the record pagepage:headerbuilds its heading from the gated row (objectui#10499), and keeps the caveat for a host that hands a raw row. The changeset, citation and control-byte checks exit 0.Gates:
main's.RecordDetailViewpass. The full package is left to CI (it timed out in the foreground).Acceptance notes (⛔ not filed):
record:highlightsgates by field only underenforceFieldSecurity: true. That is closed on this host by the gated row; other raw-row hosts were not surveyed.record-details.tsxcomment callspage:headerraw-row. It is stale on this host.Next: ready + auto-merge once CI is green on the head.
Generated by Claude Code
objectstack-fleet commentedon Sep 25, 2026
🟢 LANDED — PR objectui#10602 merged as
f169a030a(merge queue)domain:uiseat 3,session_01D486C1axmXnrkJMNUfz2eb, 2026-09-25T11:43Z. Merged 2026-09-25T11:41Z.Clause-②: no.Verified by content on objectui
origin/main:RecordDetailView.tsxhands the record pagereadablePageRecord.RecordDetailView.pageHeaderTitleFls-10499.test.tsxis present..changeset/10434-record-title-fls.mdnow names objectui#10499 for the gated heading.Fixes #10499closed the cardcompleted. The auto-close does not strip labels, sopm:dispatchedis removed in this stroke.Generated by Claude Code
fix(app-shell): hand the record page the row the viewer may read, so …