Skip to content

fix(plugin-approvals): Setup → Approvals → Requests opens the tenant-wide list, and a merged-app pin closes the caller-scoped first-view family - #21991

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-21984-approvals-requests-all-first
Oct 6, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-21984-approvals-requests-all-first

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #21984
Clause-②: no

What changes

An administrator who opens Setup → Approvals → Requests now lands on the "All" list of approval requests. Before this, nav_approval_requests named no view, and sys_approval_request declared the caller-scoped "My Pending" view first (pending_approvers contains {current_user_id}). The console opens an object's first declared list view when a route names none, so the administrator saw only the requests pending on themselves.

This applies the family's rule from triage 6012877503: a caller-scoped list view is never an object's first, and every entry that wants one names it.

  • sys_approval_request declares all_requests first. my_pending, submitted_by_me and completed follow it in their previous order. No view is added, removed or edited. A short comment at listViews states why the position is the contract.
  • nav_approval_requests names viewName: 'all_requests'. That is the key the spec already declares on an object navigation item, so there is no new key.
  • The four generated translation bundles follow the new order. They were regenerated with node scripts/check-i18n-bundles.mjs --write. The change is a pure reorder of _views keys (+12 / −12 over four files), and no translated text changed.
  • Unit pin: nav-contribution.test.ts gains one case for this entry.
  • Merged-app pin (new): packages/qa/dogfood/test/platform-app-object-entry-views.test.ts, with 56 cases. Triage 6015714713 requires it; it is described below. It closes the family, because the platform-objects pins cannot see plugin entries.
  • One changeset: @objectstack/plugin-approvals at patch, carrying Clause-②: no.

⛔ No new key, no packages/spec edit, no platform-objects edit, no objectui change, no new check:* gate, and no governed surface.

Supporting edits outside the declared file surface (so the pin can exist)

  • packages/qa/dogfood/vitest.config.ts: anchored source aliases for @objectstack/setup, @objectstack/account and @objectstack/plugin-sharing in the isolated project.
    • The pin imports all three as values.
    • Without an alias, each would resolve through dist/ and would have to be added to dogfood's check:test-source-alias row, which is shrink-only and set-equal. An alias is that gate's prescribed fix.
    • The other contributors are already aliased (plugin-approvals, service-datasource, cloud-connection) or already in that row (plugin-security, plugin-audit, plugin-webhooks, service-messaging, mcp, objectql, platform-objects).
  • packages/qa/dogfood/package.json gains @objectstack/setup and @objectstack/account as devDependencies, and pnpm-lock.yaml changes by 6 lines.
    • With these, turbo ls --affected reaches this pin when either app shell changes.
    • Without them, the pin would read two packages outside its dependency graph.

The dispatch's hypotheses, measured

At origin/main 1c563af40e. PR #21983 merged at 12:31Z, before this branch was cut.

  • H1 holds. listViews declared my_pending (:62), submitted_by_me (:76), completed (:86) and all_requests (:99). nav_approval_requests (approvals-plugin.ts:147) named no view.
  • H2: no reader in this repository relies on sys_approval_request's first view.
    • Account: nav_account_approvals (account.app.ts:114–:117) routes to the approvals:inbox component. No Account entry names sys_approval_request; the merged pin enumerates all six Account object entries.
    • Setup: nav_approval_requests is the only entry naming the object.
    • Code: no code reads this object's first list-view key. packages/cli/src/commands/lint.ts:168 (firstListViewKey) reads a first key only to place a label diagnostic. No view on this object sets isDefault.
    • objectui at the .objectui-sha pin 0abd4f9f87: read, not edited.
      • ApprovalsInboxPage.tsx reads the approvals REST path (services/approvalsApi) and names sys_approval_request only for its declared actions (:2494, :2541).
      • recordApprovalActions.ts:36 and deadRecordReference.ts:66 name the object, not a view.
      • No objectui file names my_pending, submitted_by_me or all_requests.
      • The generic views[0] doors (the object breadcrumb and the object switcher) are fixed by the reorder itself.
  • H3 holds: the reorder moved four generated files. These are plugin-approvals/src/translations/{en,es-ES,ja-JP,zh-CN}.objects.generated.ts. check-i18n-bundles first read "plugins/plugin-approvals: 4 bundle(s) drifted", and after --write it exits 0. The *.source-hashes.generated.ts files did not move.
  • H4 holds. The merged pin is red on main for this entry only; see "Red on main" below.

Stop conditions (from 6012877503)

  • Access difference: not met. This was read from source; no real-door read was run.
    • The view filter is a presentation predicate added to the generic data query. Row-level security and sharing decide which rows a caller reads, whichever view is open.
    • all_requests was already a tab on this page, and the diff changes neither who can read nor which rows they get.
    • The Setup entry also sits behind group_approvals' manage_platform_settings gate.
  • No unscoped view: not met. all_requests carries no filter at all.

The merged-app pin

platform-app-object-entry-views.test.ts boots the composition the way packages/cli/scripts/check-app-nav-i18n.mjs does, which was read and not edited:

  • the same 11 contributors;
  • a fake context whose only real service is manifest;
  • each manifest handed to ObjectQL.registerApp;
  • the apps read back through registry.getApp, which applies the same applyNavContributions merge that /api/v1/meta/app serves.

Its population is every type: 'object' entry of the merged apps, with nothing hand-listed: 26 Setup entries and 6 Account entries, naming 27 objects.

  • (a), 25 cases: every object an entry names declares a first list view without {current_user_id}.
  • (b), 14 cases: every entry whose object declares a caller-scoped view names a viewName that the object declares. On a Setup entry, that view must not be caller-scoped.
  • Composition and non-vacuity, 17 cases:
    • both apps are merged;
    • each contributor lands at least one id in each app it declares;
    • the population contains plugin entries (nav_approval_requests, nav_record_shares);
    • the eight objects this family reordered are judged by both rules;
    • no named object is unresolved;
    • the objects that declare only caller-scoped views are exactly sys_inbox_message and sys_member, and only Account entries name them, each with mine.
  • Where an object's definition is read:
    • first, the composition's own registry (registry.getObject);
    • otherwise, @objectstack/platform-objects/identity, which is the barrel plugin-auth registers its identity objects from (authIdentityObjects). AuthPlugin cannot boot without a secret, which is also why check-app-nav-i18n leaves it out.
    • At this head, 10 named objects come from the identity barrel and 17 from the composition.
  • Reach:
    • The roster mirrors check-app-nav-i18n's CONTRIBUTORS by hand, so a contributor added there and not here is not seen. Reading that script's text from this test would have needed a CROSS_PACKAGE_TEST_INPUTS declaration and a turbo.json edit, so that was not done.
    • plugin-auth's nav_sso_providers is merged only when an external IdP is wired, so no boot here merges it. Its object sys_sso_provider declares no caller-scoped view.

Red on main, green on the head

  • Main (1c563af40e): the exact base blobs of the two subject files were restored into the tree with git restore --source. The pin reads plugin-approvals through its source alias.
  • Head (10ff7b0044): pin 56 passed (56); unit 2 passed (2).

Ablation (on committed 079304d6ec, through scripts/ablation-replace.mjs, restores proven by blob)

The direction of each leg was predicted before it ran: one red pin case and one red unit case per leg.

leg mutation pin unit
A literal swap of the adjacent all_requests / my_pending blocks (blob 17a36501e7fd → 014a8acaa16d) 1 failed / 55: (a) › sys_approval_request 1 failed: "declares all_requests first"
B drop viewName: 'all_requests' (blob 70ec3ecd12af → 9fb934b3be07) 1 failed / 55: (b) › setup/nav_approval_requests "names no viewName" 1 failed: "names its view"
C viewName: 'my_pending' 1 failed / 55: (b) "is an administrator's entry and names the caller-scoped view" 1 failed
D viewName: 'all_requestz' 1 failed / 55: (b) "names "all_requestz", which the object does not declare" 1 failed
  • No dist/ sits between the mutation and the run: the pin reaches @objectstack/plugin-approvals through the source alias (vitest.config.ts), and the unit test imports it relatively.
  • plugin-approvals was rebuilt afterwards for its built readers. ablation-dist-preflight found the marker in both built files.
  • The composition guards (a contributor landing nothing, an unresolved object) were not ablated.

Tests and gates (head 10ff7b0044)

  • pnpm --filter @objectstack/plugin-approvals test: 61 files, 899 tests passed.
  • typecheck passed for both packages.
    • plugin-approvals: its main program excludes tests. check:test-typecheck compiles nav-contribution.test.ts through tsconfig.test.json (--listFiles: 1), and the debt ledger has no entry for it.
    • dogfood: its tsc compiles the pin (--listFiles: 1).
  • pnpm check:app-nav-i18n reports OK: 11 contributors, setup 55 and account 12 merged nav ids.
  • Gates: node scripts/pm/dispatch-gates.mjs --commands was re-derived on this change with no paths, giving 79 commands. That is the dispatch's 51 plus 28 from the changeset, manifest, lockfile and dogfood files. All 79 exit 0.
    • --ran reconciliation: "79 derived, 79 run, 0 NOT-MEASURED, 0 UNRUN", a derived zero.
    • check:dual-build-cjs-loads first exited 3 (PREREQUISITE NOT MET: 8 packages outside this closure had no dist/). Those were built (41 tasks, all turbo cache hits) and it re-ran to exit 0.
  • Lint, narrowed: eslint --no-inline-config --format json over the 9 touched lintable files reported 9 files, 0 ignored, 0 errors and 0 warnings.
    • The population is the repo's one eslint.config.mjs, and none of the 9 is ignored.
    • That config never enables type-aware linting (no parserOptions.project), so this diff cannot move a verdict on an untouched file.
    • The repo-wide pnpm lint is CI's.

Acceptance notes (observed, not filed)

  • packages/platform-objects/src/apps/account.app.ts:79 says the Account inbox entries "rely on pre-existing *.mine / *.my_pending listViews". The Approvals entry has opened the inbox component instead. This is comment drift in a file outside this card.
  • docs/qa/platform-checklist/areas/platform-core.json:525 lists the Account destination as "Approvals (sys_approval_request/my_pending)", but that entry is the approvals:inbox component. This is checklist drift.
  • The pin's roster and check-app-nav-i18n's CONTRIBUTORS are two hand-kept copies of one composition, and nothing mechanical holds them equal.

Implemented by the os-dev run of session session_01WMQprn46CND82KmY8sZWBu on branch claude/issue-21984-approvals-requests-all-first.


Generated by Claude Code

claude added 3 commits October 6, 2026 12:47
…wide list; all_requests is declared first

sys_approval_request declared the caller-scoped my_pending first, and
nav_approval_requests named no view, so the console opened my_pending:
an administrator saw only the requests pending on themselves.

- all_requests moves to first in listViews; the other views keep their
  relative order and nothing in them changes.
- nav_approval_requests names viewName: 'all_requests'.
- The four generated translation bundles follow the new order
  (node scripts/check-i18n-bundles.mjs --write): a pure reorder of the
  _views keys, no translated text changed.
- nav-contribution.test.ts pins both halves for this entry.

Claude-Session: https://claude.ai/code/session_01WMQprn46CND82KmY8sZWBu
Co-authored-by: Claude <noreply@anthropic.com>
…-merged Setup and Account apps

platform-objects pins the rule over its own Setup contributions and
Account app, and cannot see a plugin's entries: the plugins depend on it,
and their Setup entries arrive only at runtime. nav_approval_requests
opened the caller-scoped my_pending inside that blind spot.

platform-app-object-entry-views.test.ts boots the composition the way
packages/cli/scripts/check-app-nav-i18n.mjs does (same roster, same
manifest-service seam, read back through ObjectQL's registry getApp merge)
and judges every type: 'object' entry of the merged Setup and Account
apps: (a) the named object's first declared list view is not
caller-scoped; (b) an entry whose object declares a caller-scoped view
names a declared view, and a Setup entry names an unscoped one.

The Setup and Account shells and plugin-sharing are aliased to source in
the isolated project (check:test-source-alias is shrink-only), and the two
shells become devDependencies so the affected graph reaches this pin.

Claude-Session: https://claude.ai/code/session_01WMQprn46CND82KmY8sZWBu
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/l dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation tests tooling labels Oct 6, 2026
@github-actions

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/plugin-approvals, @objectstack/dogfood, touching 13 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/qa/dogfood/vitest.config.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

30 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json aa09db58c9d688ce01e2abc1cbf543a6ecf193e4.

⛔ 5 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/qa/dogfood/vitest.config.ts) — pages documenting those are invisible to this run
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 8 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json aa09db58c9d688ce01e2abc1cbf543a6ecf193e4 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 36a65b1eb82b5e5ef7d7939977b881459ca44f0e — the merge of head 10ff7b00440e9c25d1cc38873e4ddf1940c55193 into base aa09db58c9d688ce01e2abc1cbf543a6ecf193e4, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 36a65b1eb82b5e5ef7d7939977b881459ca44f0e && git checkout 36a65b1eb82b5e5ef7d7939977b881459ca44f0e
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin aa09db58c9d688ce01e2abc1cbf543a6ecf193e4 10ff7b00440e9c25d1cc38873e4ddf1940c55193 && git checkout -B drift-repro aa09db58c9d688ce01e2abc1cbf543a6ecf193e4 && git merge --no-ff 10ff7b00440e9c25d1cc38873e4ddf1940c55193

node scripts/docs-audit/affected-docs.mjs --json aa09db58c9d688ce01e2abc1cbf543a6ecf193e4

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs aa09db58c9d688ce01e2abc1cbf543a6ecf193e4 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 6, 2026 14:07
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 6, 2026
Merged via the queue into main with commit f0022c4 Oct 6, 2026
38 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-21984-approvals-requests-all-first branch October 6, 2026 14:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

2 participants