Skip to content

fix(platform-objects,plugin-audit): nav entries for the console's Audit Log and Integrations & APIs pages - #20699

Merged
objectstack-fleet[bot] merged 8 commits into
mainfrom
claude/issue-20142-nav-entries-console-pages
Sep 29, 2026
Merged

objectstack-fleet[bot] merged 8 commits into
mainfrom
claude/issue-20142-nav-entries-console-pages

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #20142

Clause-②: no

Summary

The console retired its System Hub card wall and its Developer Hub (objectui#10520, PR objectui#10576). Those had been the only in-app links to three console pages, so the pages were reachable by typed URL only. The console now registers each page under a component-registry key. This PR adds two of the three navigation entries that name those keys:

Entry App / group componentRef Contributed by Gate
nav_audit_log_browser ("Audit Log Browser") Setup / group_diagnostics, directly under nav_audit_logs audit:log @objectstack/plugin-audit navigationContributions none: it lives and dies with the plugin that owns sys_audit_log
nav_integrations ("Integrations & APIs") Studio / group_developer, after nav_public_forms developer:integrations @objectstack/platform-objects STUDIO_APP none beyond Studio's studio.access

The third key, ai:approvals (the AI pending-action queue), is the AI capability's entry to contribute under ADR-0029 D7, so this repo ships none. The seat answered the dev's placement question C (5896257587) and handed the entry to cloud's @objectstack/service-ai (knock 5896271533 on #6026).

content/docs/ui/setup-app.mdx's "Group / Filled by" table now reads Audit Logs · Audit Log Browser — plugin-audit on its Diagnostics row (patch round 2, d4744797b, after contract review 5896816094). No other hand-written page enumerates these slots' entries.

Labels are in packages/platform-objects/src/apps/translations/{en,zh-CN,ja-JP,es-ES}.ts, with source hashes recorded in the three hand-maintained *.source-hashes.ts tables. No plugin-audit/src/translations/* file is touched: Setup nav labels live in platform-objects' apps.setup.navigation subtree, the same as nav_audit_logs's.

Measured first: the three keys at the console pin

Read-only from objectui over git, at the objectstack .objectui-sha pin: git init, then git fetch --depth 1 https://github.com/objectstack-ai/objectui.git dd3f7e1be3561d63267d7162f3fc0ac52e72834d (exit 0, FETCH_HEAD = that sha), then git grep.

Key File Symbol Loaded by
audit:log apps/console/src/registerSystemComponents.tsx:80 registerAppComponent({ ref: 'audit:log', label: 'Audit Log', ... AuditLogPage }) apps/console/src/main.tsx:45 import './registerSystemComponents'
ai:approvals apps/console/src/registerSystemComponents.tsx:91 registerAppComponent({ ref: 'ai:approvals', label: 'AI Approvals', ... AiPendingActionsPage }) same import
developer:integrations apps/console/src/registerDeveloperComponents.tsx:86 registerAppComponent({ ref: 'developer:integrations', label: 'Integrations & APIs', ... IntegrationsPage }) apps/console/src/main.tsx:33 import './registerDeveloperComponents'
positive control: approvals:inbox (the ref nav_account_approvals names) apps/console/src/registerApprovalsComponents.tsx:59 registerAppComponent({ ref: 'approvals:inbox', ... }) apps/console/src/main.tsx:42

SystemHubPage.tsx is absent from apps/console/src/pages/system/ at the pin.

Mechanism hypotheses: which held

  • H1 held. A nav item reaches a console page only through type: 'component' plus a registry key. requiresService is the item-level gate, stripped server-side by filterAppForUserWithReason / filterNav in packages/rest/src/meta-item-read-gate.ts (ADR-0057 D10). It is variant-agnostic, so it gates a component item.
  • H2 held for Studio and for audit, but was falsified for group_approvals. platform-objects.test.ts pins "does not contribute slots owned by capability plugins" over ['group_integrations', 'group_approvals'] (ADR-0029 K2.b, commit 4cc2ceddce). Round 1 split that pin to place ai:approvals there. The seat answered C (5896257587) by ADR-0029 D7: no platform-objects entry in group_approvals. Patch round 1 (085c4ddb1) removed the entry, and platform-objects.test.ts is byte-identical to main.
  • H3 held. Nav labels are hand-authored leaves in packages/platform-objects/src/apps/translations/*.ts, not generated bundles. check:i18n reports all nine packages' generated bundles in sync, so a --write run had nothing to write. Translated values: zh-CN 审计日志浏览器 / 集成与 API, ja-JP 監査ログブラウザー / 連携と API, es-ES Explorador de registros de auditoría / Integraciones y API. None is a byte copy of the English source.
  • H4: both choices settled from measurement. See below.

The two open choices

Keep both audit entries: kept

The two surfaces were compared at the pin and on this tree, and neither is a superset of the other.

  • The object view (nav_audit_logs, unchanged) has six named list views on sys_audit_log: recent, writes_only, auth_events, record_views, config_changes and all_events. It is searchable (enable.searchable: true), and it renders user_id / tenant_id as lookups. Its record page shows old_value / new_value as Field.textarea, which is raw text.
  • The page (audit:log, AuditLogPage.tsx) is one table (50 per page) with an action / object / actor-id / date filter bar. Its drawer pretty-prints Before and After JSON (tryPrettyJson) and the metadata. The actor is shown as a raw user id. Its action filter (ACTION_OPTIONS in auditLogActions.ts) lists create, update, delete, login, logout, config_change, import, and has no read. So the read rows are listed only by the object view's record_views view.

The page is added; it does not replace the object entry. The page's label, "Audit Log Browser", differs from "Audit Logs" so the two rows are distinguishable in the same group.

FOLLOW-UPS.md row K2: re-read, not edited

Row K2 is left as written. This repo ships no entry that links the AI Approvals page, so the row's "ungated" half is for the cloud-side entry (the knock on #6026) to close. The "error-blind inbox" half is untouched by this PR.

Tests (at head 085c4ddb1, after turbo run build over ./packages/*)

  • packages/platform-objects/src/apps/console-page-nav-entries.test.ts (new): pins nav_integrations, its group and position, and checks its ref against the measured key list and the key shape.
  • packages/plugins/plugin-audit/src/audit-nav-contribution.test.ts (new): runs the real AuditPlugin.init, and asserts item order ['nav_audit_logs', 'nav_audit_log_browser'], componentRef: 'audit:log', and that no gate is set.
  • packages/cli/test/console-page-nav-entries.pin.test.ts (new): a wire pin over the real STUDIO_APP fold, SchemaRegistry, and RestServer GET /api/v1/meta/:type/:name. The Studio entry is served last in group_developer.
  • packages/platform-objects/src/platform-objects.test.ts and packages/cli/test/connect-agent-both-halves-wire.pin.test.ts are unchanged from main. The Setup tree count re-measures 34.
  • Runs: platform-objects pnpm test 59 files / 947 tests pass; plugin-audit pnpm test 26 / 366 pass; the two cli pin files, 4 tests pass. The full cli unit tier last ran at round 1's 1d5520a076 (235 files / 3358 tests). The cli integration tier is declared to CI. Typecheck exits 0 for platform-objects, plugin-audit and cli.
  • Round 1's ablation of the ai gate is moot: that gate no longer exists.

Gates

node scripts/pm/dispatch-gates.mjs --commands at d4744797b derived 93 commands: 085c4ddb1's 64 plus 29 docs, link and spec-docs families that the docs row brings in. All 93 exit 0, and --ran reads 93 derived, 93 run, 0 NOT-MEASURED, 0 UNRUN. At 085c4ddb1 the 64 all exited 0. That includes check:i18n (9 packages in sync), check:test-source-alias, check:type-check-debt, check:dual-build-cjs-loads, check:nul-bytes, check:doc-authoring and check:cross-package-test-inputs. The --ran reconciliation reads 64 derived, 64 run, 0 NOT-MEASURED, 0 UNRUN. pnpm check:app-nav-i18n reports OK with setup at 55 merged nav ids, every id labelled in 4 locales. pnpm check:i18n-coverage was run in round 1 only.

One gate went red mid-branch and was answered by narrowing, not by widening a ledger. check:test-source-alias refused a first draft of the cli pin that imported @objectstack/plugin-audit and @objectstack/plugin-approvals (new unaliased dist/ imports). The cli pin now folds only platform-objects' entries. The audit ref is pinned in plugin-audit's own test.

Lint, as a proven narrowing: eslint --no-inline-config --format json over the branch's 12 changed .ts files reported 0 errors and 0 warnings at 085c4ddb1 (the 13th changed file is the .md changeset). ① Population: eslint.config.mjs's files: ['**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}'] minus NEVER_LINTED covers all 12. ② The count is read from the JSON. ③ Invariance: eslint.config.mjs states that it "never enables type-aware linting (no parserOptions.project, no typed @typescript-eslint rules) for ANY file". So this diff cannot move any untouched file's verdict. The repo-wide pnpm lint is CI's.

Acceptance notes

  • objectui observation, not filed: the page's action filter offers no read option, although sys_audit_log.action declares it (auditLogActions.ts at the pin). Its docblock still points the object at platform-objects/src/audit/sys-audit-log.object.ts, which moved to plugin-audit. Carrier: none.
  • Icons (file-diff, plug-zap) are lucide names; no gate validates icon names.
  • Pre-existing, not made false here: content/docs/ui/setup-app.mdx's Apps row names Packages only, while setup-nav.contributions.ts on main also contributes Packaged Automation (nav_packaged_automation) to group_apps. Carrier: none.

Generated by Claude Code

@github-actions

github-actions Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/platform-objects, @objectstack/plugin-audit, touching 6 documentable anchor(s).

3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/permissions/record-view-auditing.mdx (via AuditPlugin (symbol, a top-level class))
  • content/docs/plugins/packages.mdx (via AuditPlugin (symbol, a top-level class))
  • content/docs/protocol/kernel/index.mdx (via AuditPlugin (symbol, a top-level class))

⛔ 1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17/17-1.mdx (via AuditPlugin (symbol, a top-level class))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see

Coarse fallback — 11 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 ed54768703b393cec280c249b549b01b14b44080 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 282451af4831aa1bf93e3e943abd65ca67baee8b — the merge of head d4744797b9c99714e475160eafd7afcc4d85552d into base ed54768703b393cec280c249b549b01b14b44080, 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 282451af4831aa1bf93e3e943abd65ca67baee8b && git checkout 282451af4831aa1bf93e3e943abd65ca67baee8b
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin ed54768703b393cec280c249b549b01b14b44080 d4744797b9c99714e475160eafd7afcc4d85552d && git checkout -B drift-repro ed54768703b393cec280c249b549b01b14b44080 && git merge --no-ff d4744797b9c99714e475160eafd7afcc4d85552d

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

⚠️ 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 ed54768703b393cec280c249b549b01b14b44080 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

…ability's to contribute (ADR-0029 D7)

Restores platform-objects.test.ts's K2.b slot pin and the Connect-an-Agent
Setup tree count to main, removes nav_ai_approvals with its label leaves and
source hashes, and narrows the new pins to the two entries this repo ships.

Claude-Session: https://claude.ai/code/session_01DEvba2nBuD4tWzfq8r8NFY
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 085c4ddb1db1e44315c5c07da0d4b5a77652feda
Local-runs: probe — recomputed the two hand-recorded source digests (f6932710da7ec071 for "Audit Log Browser", edbd97d7f401a5b8 for "Integrations & APIs") with hashSource extracted verbatim from origin/main:packages/platform-objects/src/apps/translations/source-hash.ts, because source-hash.test.ts states by ruling that no gate asserts a shipped leaf is current, and a wrong digest would silently serve the English source in place of each translation. Both match. Nothing was built, tested or re-run.

Inputs: card #20142 (body and its six comments, seat answer 5896257587 and both os-dev-reports included), PR #20699 (body, 13-file list, net diff origin/main...refs/review/20699 fetched into a ref of my own; origin/main read at a8acee28dd, merge-base 6981abfd26), the 41 check-runs on the head at the moment read, ADR-0029 D7 and K2, and the hand-written pages named below.

Check-runs at read time: every one of the seven required contexts is success (Lint & Repo Gates, TypeScript Type Check, Test Core and its 6 shards, Dogfood Regression Gate and its 3 shards, Build Core, Temporal Conformance (live PG + MySQL), Governed Surface Queue Guard). Type Check · consumer gates (the job that runs check:i18n, check:i18n-coverage, check:i18n-walk-parity, check:app-nav-i18n) is success; check:pm-widening-tells runs in the lint job, success. Console Pin Gate is skipped (the pin did not move). One of two Check Changeset runs was still in_progress when read; its sibling completed success. Not polled again.

① Derived judgments

Accept set: unchanged — right. The diff adds no Zod key, no closed-set member, no api-surface row and no registry registration (the four tells of check-widening-tells.mjs). What grows is the SERVED navigation, not what the runtime accepts from an author: STUDIO_APP (an existing export) gains one item in its value, and AuditPlugin.navigationContributions[0].items gains one. The widening-tells gate found no tell on this head.

Setup group_diagnostics gains nav_audit_log_browser (type: 'component', componentRef: 'audit:log', icon file-diff), contributed by @objectstack/plugin-audit's navigationContributions, appended directly after nav_audit_logs — right by ADR-0029 D7. D7 has each capability plugin contribute its nav entries into a named slot, and plugin-audit owns sys_audit_log (K2). The item carries no requiresService, requiresObject or requiredPermissions; the anchor it lands in carries requiredPermissions: ['manage_platform_settings'] on main (setup.app.ts:100), so the entry inherits the anchor's gate and lives and dies with the plugin. setup-nav.contributions.ts is untouched (byte-identical to origin/main), so the base shell enumerates no capability object — the D7 line the seat's answer drew.

Studio group_developer gains nav_integrations (type: 'component', componentRef: 'developer:integrations', icon plug-zap), last after nav_api_console, nav_flow_runs, nav_public_forms, no gate beyond studio.access — right. The group's own comment says it hosts first-party developer tooling registered under the console's developer:* namespace; this entry is the fourth of that kind beside three of the same shape. D7's "does not enumerate capability objects" is not implicated: the page is not a capability plugin's object, it is the console's own developer tool.

ai:approvals: no entry, no remnant — right by seat answer 5896257587 (option C, D7). grep over the net diff for ai:approvals, nav_ai_approvals, ai_approvals, AiPendingActions and 'bot' finds no code, no label leaf, no source hash and no icon; the only three hits are prose lines stating the exclusion (the changeset body, and the headers of the two new test files), each attributing the entry to the AI capability's owner under D7. packages/platform-objects/src/platform-objects.test.ts and packages/cli/test/connect-agent-both-halves-wire.pin.test.ts are byte-identical to origin/main (git diff --quiet exits 0 on each), so the K2.b pin "does not contribute slots owned by capability plugins" stands as landed in 4cc2ceddce, and the Setup-tree toHaveLength(34) assertion is the one Test Core ran green.

Both componentRef keys named are literals the tests pin against a measured list, and the pin on main is the sha the measurement names — accepted as the dev's reading. .objectui-sha on origin/main reads dd3f7e1be3561d63267d7162f3fc0ac52e72834d, the commit the PR body and all three test headers say the keys were read from. I did not re-read objectui (outside the read-only shape); the Console Pin Gate did not run because the pin did not move, and the tests carry the keys as MEASURED_CONSOLE_KEYS so a later pin bump that renames one must edit the list in the same PR, as the headers say.

Three new pins, each on the half its package owns — right. console-page-nav-entries.test.ts (platform-objects) pins the declaration, order, absence of gates and AppSchema validity; audit-nav-contribution.test.ts (plugin-audit) runs the real AuditPlugin.init and pins order ['nav_audit_logs', 'nav_audit_log_browser'], the audit:log ref, no gate of either kind, the object entry unchanged, and NavigationContributionSchema validity; console-page-nav-entries.pin.test.ts (cli) reads the Studio group back over the real SchemaRegistry fold and RestServer GET /api/v1/meta/:type/:name. The cli pin was narrowed to platform-objects imports after check:test-source-alias refused a draft that imported two plugins via dist/ — a narrowing, no ledger widened; right.

Shipped prose, the four locales — right, with one observation. en Audit Log Browser / Integrations & APIs; zh-CN 审计日志浏览器 / 集成与 API; ja-JP 監査ログブラウザー / 連携と API (matching its sibling group_integration: 連携); es-ES Explorador de registros de auditoría / Integraciones y API. Each says what the en label says and none is an English copy. Observation, not wrong: es-ES's new leaf uses sentence case where its immediate neighbour nav_audit_logs reads Registros de Auditoría in title case (the Studio siblings use sentence case, so both conventions already coexist on the page). The two recorded source hashes are correct (the probe above); the hash tables' own header says a new entry is optional and strictly better, and these are.

Docblocks on exported symbols — right. The STUDIO_APP group comment adds "integrations & APIs" to the list it already carried; the AuditPlugin contribution comment states why two doors exist and what each renders, consistent with the PR body's measurement and with the test that pins it.

Hand-written docs the drift bot listed — no sentence goes false. content/docs/permissions/record-view-auditing.mdx:205 says the Record Views list view is reachable "via the Audit Logs entry the plugin contributes": nav_audit_logs is still contributed, unchanged, and the view is on the object entry. content/docs/protocol/kernel/index.mdx:313 (hook subscription) and content/docs/plugins/packages.mdx:341,546 (feature bullets, kernel.use) describe nothing this diff touches. content/docs/releases/v17/17-1.mdx is release-owned and was not read for edits.

Hand-written doc the drift bot could not list — one row goes false: FAIL item. content/docs/ui/setup-app.mdx:56, in the table headed "Group (anchor id) / Filled by", enumerates Diagnostics as Sessions · Notification Events — platform-objects; Audit Logs — plugin-audit. Every other row of that table lists every entry its contributors fill the slot with; after this diff plugin-audit fills group_diagnostics with two entries, and the row names one. The bot's own "what this run could not see" says a page that restates a rule by its inputs shares no token with the emitter and must be re-read by hand; this is that page for this diff. Fix: Audit Logs · Audit Log Browser — plugin-audit on that row. Lines 78–81 of the same page stay true (the object entry is still contributed; sys_activity and sys_comment are still not). No page in content/docs enumerates Studio's group_developer, so the Studio entry falsifies nothing.

PR body and title, judged as the squash message — three statements the diff falsifies: FAIL item. (a) The title still reads "nav entries for the console's Audit Log, AI Approvals and Integrations & APIs pages"; the head ships no AI Approvals entry, the body itself says "adds two of the three", and the title is the first line that lands in main's history. (b) Acceptance notes: "Icons (file-diff, bot, plug-zap) are lucide names" — bot was the removed entry's icon and is absent from the net diff. (c) Gates: "over patch round 1's 13 changed .ts files reports 13 files" — the branch changes 13 files of which 12 are .ts and one is the .md changeset; the repo-wide lint is CI's and green, so nothing turns on it, but the sentence is not true as written. Every other factual sentence read true against the diff and the head's check-runs: the two-entry table and its gates; the H2 paragraph (entry removed, platform-objects.test.ts byte-identical to main); H3's check:i18n finding and the translated values (verbatim in the diff); the two restored files and the 34 re-measure; the objectui pin sha; the check:test-source-alias narrowing; the honest statements that check:i18n-coverage and the full cli unit tier were not re-run at this head.

② Semver level

.changeset/20142-console-page-nav-entries.md: @objectstack/platform-objects: patch, @objectstack/plugin-audit: patch; PR body Clause-②: no — right, and the pair agree. Clause ② is directional (check-widening-tells.mjs header): no is wrong only when the diff widens the accept set or the public surface, and this diff does neither (① first paragraph). The change restores in-app reachability the console's retirement removed — a fix in two released packages, so patch, never skip-changeset. Nothing is removed or renamed, so no migration and no ADR-0087 marker is owed, and the changeset carries none. The CHANGELOG text names both entries with their contributor and gate, states the two-audit-entries choice, and says why ai:approvals is not here (D7, owner in Cloud/Enterprise) — accurate at this head; its last paragraph ("keys are the ones the console registers at the objectui commit this release's console is built from") is true at pin dd3f7e1be356 and is the statement the MEASURED_CONSOLE_KEYS pins hold. Both Check Changeset runs that completed read success.

③ Boundary flags

  • H4 — keep both audit entries: answered, right. The dev measured the object view (six named list views incl. record_views, searchable, lookup-rendered actor and tenant) against the page (one filterable table, pretty-printed before/after JSON drawer, action filter without read) and found neither a superset of the other; the seat confirmed (5896257587). The labels differ ("Audit Logs" / "Audit Log Browser") so the rows are distinguishable in one group, and the plugin comment records the reasoning where the next reader edits it.
  • K2 row not edited: answered, right. The card conditioned an edit on ai:approvals landing gated by requiresService: 'ai'; no entry for that page ships from this repo, so the condition never triggered. The row's subject is objectui's card and inbox (SystemHubPage.tsx, since deleted at the pin, and AiPendingActionsInbox.tsx); its "ungated" half is for the cloud-side entry's lander (knock 5896271533 on [PM seat] repo:cloud#1 — 🟢 os-zhuang · session_011jobP72PwN3whNm55GetXQ · R45 #6026) and its "error-blind" half is untouched by anything here. The seat confirmed the no-edit.
  • Label leaves and source hashes removed by hand: answered, right. app-nav-translation-parity.test.ts's header says the apps.* half is hand-authored in each LOCALE.ts and nothing regenerates it; each *.source-hashes.ts header says it is hand-maintained; scripts/check-i18n-bundles.mjs contains no source-hashes reference, so --write never owned those tables. The removal left en.ts, the three LOCALE.ts and the three hash tables carrying exactly the two new keys, and the round-2 check:app-nav-i18n count (55 merged Setup ids, down one from 56) is consistent with one Setup leaf gone.
  • One prose line changed in audit-nav-contribution.test.ts: answered, right. The header now reads "beside Setup and Studio navigation: add component entries for the console's audit-log, AI-approvals and integrations pages, left without an in-app entry since the System Hub card wall retired (objectui#10520) #20142's Studio entry in @objectstack/platform-objects", true at this head; no assertion changed.
  • Out-of-scope objectui observation (carrier none): escalated to the seat. The finding is reproducible and in the sibling repo: AuditLogPage's ACTION_OPTIONS omits read although sys_audit_log.action declares it, and its docblock names platform-objects/src/audit/sys-audit-log.object.ts, which moved to plugin-audit. Prime Directive 10 files a reproducible defect rather than leaving it in acceptance notes; the carrier is an objectui card, which is the seat's to file, not this PR's to fix. Not a blocker for this head.
  • Full cli unit tier not re-run at this head: answered, right. Test Core and all six shards are success on 085c4ddb1d, which is the tier the local re-run would have repeated; the two cli pin files were run locally at the head.
  • open_questions: round 2 declares none. Round 1's single question (landing of nav_ai_approvals) was answered C by the seat and the head conforms to that answer (① third paragraph).

FAIL items, both fixable in one patch round on this branch:

  1. Retitle the PR to name the two pages it ships (drop "AI Approvals"), and correct the two body sentences (bot icon; "13 changed .ts files"), since title and body land as the squash message.
  2. content/docs/ui/setup-app.mdx:56 — add Audit Log Browser to the Diagnostics row's plugin-audit entries.

Implemented-by: claude/issue-20142-nav-entries-console-pages
Reviewed-by: session_01DEvba2nBuD4tWzfq8r8NFY

VERDICT: FAIL


Generated by Claude Code

@objectstack-fleet objectstack-fleet Bot changed the title fix(platform-objects,plugin-audit): nav entries for the console's Audit Log, AI Approvals and Integrations & APIs pages fix(platform-objects,plugin-audit): nav entries for the console's Audit Log and Integrations & APIs pages Sep 29, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: d4744797b9c99714e475160eafd7afcc4d85552d
Local-runs: none

Delta record, superseding 5896816094 (FAIL at 085c4ddb1d). Inputs: card #20142 (body and all eight comments — seat answer 5896257587, amendment 5896829449 and the three os-dev-reports included), PR #20699 (title and body as edited after the prior record, the 14-file list, and the net diff 6981abfd26..refs/review/20699d fetched into a ref of my own; origin/main read at defc7f7b50, merge-base unchanged at 6981abfd26), the prior record itself, and the 41 check-runs on the head at the moment read. Nothing built, tested or re-run; every reading below is git show / git grep / git diff over the fetched refs.

What moved since the prior record. One commit, d4744797b ("docs(ui): the Setup Diagnostics row names both plugin-audit entries", model-free trailer pair), touching content/docs/ui/setup-app.mdx alone, 1 insertion / 1 deletion. git diff --quiet 085c4ddb1 d4744797b over every path except that file exits 0: the other 13 paths of the net diff are byte-identical to what 5896816094 judged, the merge-base is the same commit, and main's advance since (a8acee28dd..defc7f7b50) touches none of the PR's 14 paths. Every ①/②/③ judgment of the prior record not restated below therefore carries unchanged onto this head; this record re-judges the two FAIL items, every sentence the seat's title/body edit added, and the delta report's new observation.

Check-runs at read time: Lint & Repo Gates, TypeScript Type Check, Build Core, Dogfood Regression Gate (and its 3 shards), Temporal Conformance (live PG + MySQL) and Governed Surface Queue Guard are success — six of the seven required contexts. Test Core shards 2/6, 3/6 and 6/6 are success; shards 1/6, 4/6 and 5/6 were in_progress and the aggregate Test Core run had not yet been posted. Not polled again: the landing seat reads that conclusion, not this record. Both Check Changeset runs success; Type Check · consumer gates (check:i18n, check:i18n-coverage, check:app-nav-i18n) success; Console Pin Gate skipped (the pin dd3f7e1be356 is the same at merge-base, origin/main and head); Packed-tarball smoke (opt-in) skipped. The later edited event re-ran Auto Label and Check PR Size as skipped; their earlier runs on this head are success. Not a governed-surface PR: no path under docs/adr/, .claude/, skills/, AGENTS.md or CLAUDE.md; 411 / 6 lines over 14 files.

① Derived judgments

Prior FAIL item 2 — content/docs/ui/setup-app.mdx:56: discharged, right. The Diagnostics row now reads Sessions · Notification Events — platform-objects; Audit Logs · Audit Log Browser — plugin-audit, the exact text the prior record prescribed, and nothing else on the page moved (the file's diff against the merge-base is that one hunk). Read against the head's audit-plugin.ts, plugin-audit's navigationContributions[0].items are nav_audit_logs ("Audit Logs") then nav_audit_log_browser ("Audit Log Browser"), both into group_diagnostics: the row enumerates exactly what the contributor fills the slot with, in order. Lines 64 and 78–81 of the page still read true (the object entry is still contributed; sys_activity and sys_comment still are not).

Prior FAIL item 1 — title and body as the squash message: discharged, right. (a) Title now fix(platform-objects,plugin-audit): nav entries for the console's Audit Log and Integrations & APIs pages — the two pages the diff ships, AI Approvals gone, and the scope names the two packages the changeset bumps. (b) The acceptance note now reads Icons (file-diff, plug-zap) are lucide names — the two icons the net diff carries (audit-plugin.ts, studio.app.ts); bot has 0 hits in the net diff. (c) The lint sentence now reads over the branch's 12 changed .ts files ... at 085c4ddb1 (the 13th changed file is the .md changeset) — true: report 5896610958's 13-file list at 085c4ddb1 is 12 .ts files plus .changeset/20142-console-page-nav-entries.md; the head's 14th file is the .mdx, outside the eslint population, and the sentence is scoped to 085c4ddb1. The "13 files reports 13" miscount is gone.

Every sentence the seat's edit added, judged.

  • "adds two of the three navigation entries" and the two-row table. nav_audit_log_browser / Setup group_diagnostics directly under nav_audit_logs / audit:log / plugin-audit navigationContributions / no gate — matches audit-plugin.ts (no requiresService, requiresObject or requiredPermissions on the item; the group_diagnostics anchor carries the gate on main). nav_integrations / Studio group_developer after nav_public_forms / developer:integrations / STUDIO_APP / none beyond studio.access — matches studio.app.ts (last of four in the group, no gate keys). Right.
  • "The third key, ai:approvals ... this repo ships none. The seat answered ... C (5896257587) and handed the entry to cloud's @objectstack/service-ai (knock 5896271533 on [PM seat] repo:cloud#1 — 🟢 os-zhuang · session_011jobP72PwN3whNm55GetXQ · R45 #6026)". The net diff carries ai:approvals only in prose stating the exclusion (the changeset and the two test headers); 5896257587 on the card says exactly this and names [PM seat] repo:cloud#1 — 🟢 os-zhuang · session_011jobP72PwN3whNm55GetXQ · R45 #6026. The knock comment itself is not among this brief's inputs and is not judged.
  • "setup-app.mdx's 'Group / Filled by' table now reads ... (patch round 2, d4744797b, after contract review 5896816094)": true (above); "Group / Filled by" paraphrases the heading Group (anchor id) / Filled by. "No other hand-written page enumerates these slots' entries": git grep over content/docs at the head, release pages excluded — group_developer, nav_integrations, nav_api_console, nav_flow_runs, nav_public_forms, nav_audit_logs, Integrations & APIs, developer:integrations, audit:log: 0 hits each; group_diagnostics and Audit Logs only on setup-app.mdx (:56, :64, :78–79) and record-view-auditing.mdx:205, which names the one entry the plugin contributes and stays true. Right.
  • "Labels are in translations/{en,zh-CN,ja-JP,es-ES}.ts, with source hashes recorded in the three hand-maintained *.source-hashes.ts tables. No plugin-audit/src/translations/* file is touched: Setup nav labels live in platform-objects' apps.setup.navigation subtree, the same as nav_audit_logs's". The file list is exactly those seven translation files and no plugin-audit translation; en.ts:137 on main carries nav_audit_logs under apps.setup.navigation, and the new leaf sits beside it. Right.
  • The gates paragraph at d4744797b ("93 derived, 93 run, 0 NOT-MEASURED, 0 UNRUN"; 29 docs, link and spec-docs families over 085c4ddb1's 64), the "At 085c4ddb1 the 64 all exited 0 ... --ran reads 64 derived, 64 run" sentences, check:app-nav-i18n at 55 merged ids, and "check:i18n-coverage was run in round 1 only": each is the dev's own reading, restated from reports 5897104727, 5896610958 and 5896211341 without widening; the gate verdicts are the head's check-runs above, and no sentence claims more than its report. Consistent.
  • Tests heading "at head 085c4ddb1, after turbo run build": the run numbers (59 / 947, 26 / 366, two cli pin files / 4 tests, cli unit tier last at 1d5520a076 with 235 / 3358) match 5896610958 and 5896211341; the three test files are byte-identical to 085c4ddb1, so the scoping is honest. "Round 1's ablation of the ai gate is moot: that gate no longer exists" — requiresService appears nowhere in the net diff. Right.
  • The lint population sentences: eslint.config.mjs:971 reads files: ['**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}'] with ...NEVER_LINTED in its ignores; lines 327–329 read, verbatim, "never enables type-aware linting (no parserOptions.project, no typed @typescript-eslint rules) for ANY file". Right.
  • "no gate validates icon names": AppSchema's nav icon is z.string().optional().describe('Icon name') (app.zod.ts:329, :1191), and no script under scripts/ or packages/spec/scripts/ matches a lucide name list (check-docs-nav-label.mjs's two lucideIconsPlugin hits are fumadocs loader fixture strings). Right, on my reading.
  • Footer: the session-URL form AGENTS.md prescribes for PR bodies; no model identifier in the title or body.

Every other factual sentence in the body — carried from 5896816094 and spot-read here. H1 (filterAppForUserWithReason at meta-item-read-gate.ts:732, ADR-0057 D10 requiresService at :765 and :778); H2 (platform-objects.test.ts:418 "does not contribute slots owned by capability plugins" over ['group_integrations', 'group_approvals'], landed by 4cc2ceddce "ADR-0029 K2.b ... D7", and the file absent from the PR's file list, so byte-identical to main); H3 (the four locale leaves verbatim in the diff, none an English copy; source hashes f6932710da7ec071 / edbd97d7f401a5b8 recorded in all three tables — the digests the prior record's probe confirmed); the object-view measurement (sys-audit-log.object.ts: six named list views recent, writes_only, auth_events, record_views with filter action in ['read'], config_changes, all_events; enable.searchable: true at :385; user_id / tenant_id as Field.lookup at :224 / :350; old_value / new_value as Field.textarea at :317 / :325). The objectui-side readings (the four registerAppComponent sites, AuditLogPage's filter) are the dev's, at the pinned commit, outside this brief's inputs and not re-read.

Accept set and public surface: unchanged — carried. No Zod key, closed-set member, api-surface row or registry registration; two nav items in the SERVED value of existing exports. Lint & Repo Gates (which runs check:pm-widening-tells) is success on this head.

② Semver level

Carried: .changeset/20142-console-page-nav-entries.md bumps @objectstack/platform-objects: patch and @objectstack/plugin-audit: patch; the PR body's Clause-②: no — right, and the pair agree. The delta commit is a hand-written docs row under content/docs/ui/, which publishes from no released package and owes no changeset line; the changeset body is byte-identical to 085c4ddb1 and still names only the two entries, the two-audit-entries choice, and the D7 reason ai:approvals is absent. Nothing removed or renamed; no migration and no ADR-0087 marker owed, none present. Both Check Changeset runs success.

③ Boundary flags

  • Delta report 5897104727's new observation — the Apps row of setup-app.mdx:51 omits nav_packaged_automation: this diff does NOT make it false. setup-nav.contributions.ts on main aims nav_packages ("Packages", developer:packages) and nav_packaged_automation ("Packaged Automation", automation:packaged) at group_apps; the row names Packages only. The file is untouched by the PR (byte-identical to the merge-base) and the row is untouched; the omission is on main at 6981abfd26 and, by the source comment, dates from the entry's own landing. Left unfixed under "fix only a sentence your change makes false" — right. Escalated to the seat as a carrier question: the finding sits in a card comment's out_of_scope_findings with carrier: none and not in the PR's Acceptance notes; by Prime Directive 10 it wants either one line there or a docs-only card. Not a blocker for this head.
  • Test Core not concluded at read time. Three shards in_progress, aggregate absent. The tests under them are byte-identical to 085c4ddb1, where all six shards and the aggregate were success (5896816094); that is context, not a pass. The landing waits for the conclusion.
  • Fixes #20142 with the card's third proposed entry re-homed: carried. The card offered its three entries "for this repo's seat to confirm"; the seat confirmed two and re-homed the third by ADR-0029 D7 with the knock recorded on the card (5896257587). The card's pin line ("ai:approvals absent when the ai service is not") holds trivially with no entry shipped.
  • H4 (both audit entries kept), the K2 no-edit, the hand-removed label leaves and hashes, the cli-pin narrowing, the objectui AuditLogPage observation (escalated to the seat for an objectui card), and the full cli unit tier not re-run at this head: carried from 5896816094, unchanged on this head. Round 2 declares no deviations and no open_questions.
  • The delta report's pr_body_changes_needed (name the setup-app.mdx row; restate the 93-command gates line): both applied in the body as read.

Implemented-by: claude/issue-20142-nav-entries-console-pages
Reviewed-by: session_01DEvba2nBuD4tWzfq8r8NFY

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 29, 2026 19:37
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 29, 2026
Merged via the queue into main with commit fa0a4b6 Sep 29, 2026
51 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-20142-nav-entries-console-pages branch September 29, 2026 19:54
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 7, 2026
…tradict their current en source (objectstack-ai#20707)

Fixes objectstack-ai#20666

Clause-②: no

## What changed

A translated leaf in
`packages/platform-objects/src/apps/translations/{ja-JP,es-ES,zh-CN}.metadata-forms.generated.ts`
that a translator wrote by hand keeps its value when its `en` source
changes later. This PR measures that set first, with PR objectstack-ai#20652's
instrument and the two widening checks the services seat named, then
re-translates the leaves whose meaning now **contradicts** the current
`en`: **12 leaves** (ja-JP 4, es-ES 4, zh-CN 4).

- Values only. No key is added or dropped, no `en` file is edited, and
no provenance companion or echo-decision ledger changes (measured below;
the tooling requires none).
- `.changeset/20666-stale-authored-metadata-form-leaves.md`:
`@objectstack/platform-objects` `patch`.
- No new gate, per triage. No test pins any of the old or new strings.
- File surface held: the three translated `metadata-forms` bundles and
the changeset. Not the object bundles, not the plugin bundles, and not
`{en,ja-JP,es-ES,zh-CN}.ts` or `*.source-hashes.ts` (PR objectstack-ai#20699).

## The measurement (base `a8acee28d`, before any edit)

### Instruments

The clone is not shallow (`git rev-parse --is-shallow-repository`
answers `false`, 15122 commits), so every walk below reads full history.

1. **Since last edit** (PR objectstack-ai#20652's condensed instrument, ported with
two edits: the bundle kind `objects.generated.ts` becomes
`metadata-forms.generated.ts`, and the recorded-copy test reads
`"metadataForms.PATH"` in both `LOCALE.source-hashes.generated.ts` and
`LOCALE.source-hashes.ts`). Population: every string leaf of the
locale's metadata-forms bundle, minus echoes of the current `en` and
recorded byte copies. Last edit: the first-parent commit where the
parsed value last changed. A leaf is a candidate when `en` at that
commit differs from `en` today. Meaning is then judged by hand.
2. **Renamed key.** A leaf whose path was born at its last edit, while
the same value sat under a path (any path in the bundle, not only a
sibling) that disappeared in that commit. For each, `en` at the old
path's last edit is compared with `en` at the new path today.
3. **Penultimate edit.** For each non-candidate, `en` at its previous
edit, and whether `en` moved in the last-edit commit itself. Every flag
was read by hand, including the ones where `en` moved in the same
commit.
4. **Merge side.** For a leaf last edited by a merge commit, `en` is
also read at the second parent.
5. **Retired term.** The locale's word for `project`, `department`,
`role` (ADR-0090) or `profile` (ADR-0090 D2), where `en` at the same
path does not carry the English term. The raw ja-JP `role` pattern hit
four leaves, all false positives (ロールアップ roll-up three times, コントロール
control once); the refined pattern excludes both and hits 0.
6. **Retired concept** (added here; the concept-level twin of PR
objectstack-ai#20652's retired-term scan). For each re-model the contradicting
candidates surfaced, the locale's words for the retired concept, flagged
where `en` at the same path does not carry it: agent tools (ADR-0109
removed `agent.tools[]`, scope `agent.*`);
`PermissionSet.contextVariables` (removed by the ADR-0105 commit
`879ea1304`); sharing rules named for RLS; an email template sent by
`id` with a content type (`47a92f427` re-modelled the type); a report
block that joins objects (ADR-0021 dataset-bound reports, `18178454c`,
scope `report.*`).
7. **Carve-in continuity.** The zh-CN leaves last edited at `e0077ea36`
were carried in from a hand overlay,
`packages/platform-objects/src/metadata-translations/zh-CN.ts`, born at
`dc721729a` and consolidated at `e0077ea36` the same day. So the
instrument dates them at the carve-in. Between those two commits
`packages/spec/src` changed no form label, description or help string
(`git diff dc72172 e0077ea -- packages/spec/src`: two changed
`label` / `description` lines, both schema declarations, no string). The
carve-in dating therefore hides no source move inside the overlay's
lifetime.

### Known-positive controls

- `permission.sections.tab_and_row_level_security.description` (line
2251 of all four bundles) and `agent.sections.capabilities.description`
(line 2347). The since-last-edit instrument flags both in **all three**
locales. In ja-JP and es-ES both promise what `en` dropped (custom
context variables; tools). zh-CN's agent leaf promises tools too, but
zh-CN's permission leaf never promised context variables: it names the
section after sharing rules, read below.
- Renamed-key check: `dashboard.fields.refreshIntervalSeconds.label`,
renamed from `refreshInterval` at `e9fcd6bbd` with the value carried. It
fires in all three locales.
- Retired-concept scan: it fires on every one of the four contradicting
paths in ja-JP and es-ES, and on the three in zh-CN.
- Provenance (for H2): with ja-JP
`agent.sections.capabilities.description` set to the `en` string,
`check-i18n-bundles --filter=platform-objects` goes red,
`platform-objects DRIFTED (1)` (through `scripts/ablation-replace.mjs`:
anchor 1 to 0, blob `1b912e07c279` to `70cb7a0e1ba3`, restored: blob
equals HEAD and `git diff HEAD` is empty). In hold mode, `--write` adds
exactly one row,
`"metadataForms.agent.sections.capabilities.description"`, to
`ja-JP.source-hashes.generated.ts`. Both files were restored with `git
checkout HEAD --`, and their blobs equal HEAD's (`1b912e07c279`,
`2429910c839c`).

### Population and radius

- **Covered:** the `metadata-forms` bundles of
`@objectstack/platform-objects` for ja-JP, es-ES and zh-CN. There are
1116 leaves per locale. Echoes of the current `en` number 0 / 0 / 0,
recorded byte copies 0, and authored leaves **1116 / 1116 / 1116**.
History: every first-parent commit since the bundles were created at
`ae2da1e7d` (69 / 70 / 70 commits touching each locale file, 56 for
`en`), plus the zh-CN overlay's lifetime (instrument 7).
- **Not covered:** a leaf that was wrong when it was authored and whose
`en` never moved, outside the vocabularies of instruments 5 and 6; where
the zh-CN overlay's strings came from before `dc721729a` (the file was
born with them); the object bundles, the plugin bundles and the other
packages' bundle sets.

### Counts per locale

| | ja-JP | es-ES | zh-CN |
|---|---|---|---|
| authored leaves | 1116 | 1116 | 1116 |
| candidates (`en` moved since last edit), before | 9 | 9 | 6 |
| of which contradicting, before | 4 | 4 | 3 |
| penultimate-edit flags, before (contradicting) | 18 (0) | 18 (0) | 22
(0) |
| renamed-key flags, before (contradicting) | 1 (0) | 1 (0) | 1 (0) |
| merge-side flags, before | 0 | 0 | 0 |
| retired-term flags, before | 0 | 0 | 0 |
| retired-concept flags outside the candidates, before (contradicting) |
0 | 0 | 1 (1) |
| **re-translated here** | **4** | **4** | **4** |
| candidates after (at `aaee46778`) | 5 | 5 | 3 |
| contradicting after | 0 | 0 | 0 |
| retired-concept flags after | 0 | 0 | 0 |

The raw candidate counts equal the objectstack-ai#20653 dev's 9 / 9 / 6 exactly.
After, both walks re-run at `aaee46778`: each after-candidate set is
exactly the before set minus the re-translated paths (0 added), the echo
count is still 0 (no new value equals its `en` leaf), and the
renamed-key flag is the same one. The penultimate-edit flags after are
the before set plus the 11 re-translated candidates (their last edit is
now this PR, after `en` moved), which is the expected shape.

### The judgment rule

As in PR objectstack-ai#20652 and PR objectstack-ai#20684. A leaf **contradicts** the current `en`
when a reader who acts on it would believe something about the current
form that `en` now says is false: `en` now denies what the leaf asserts;
the type was re-modelled, so the leaf describes a different thing; or
the leaf names a mechanism the section does not hold. Added detail,
narrowing, rewording, punctuation and title-casing are not
contradictions.

## Re-translated (12): before and after

**`agent.sections.capabilities.description`**, all three. ADR-0109
removed `agent.tools[]` (`e2616e0cf`); an agent's tools come from its
skills. `en`: `Skills and knowledge sources the agent can use.`

- ja-JP: 「エージェントが使用できるスキル、ツール、ナレッジソース。」 → 「エージェントが使用できるスキルとナレッジソース。」
- es-ES: 「Skills, herramientas y fuentes de conocimiento que puede usar
el agente.」 → 「Skills y fuentes de conocimiento que puede usar el
agente.」
- zh-CN: 「代理可使用的技能、工具与知识来源」 → 「代理可使用的技能与知识来源」

**`permission.sections.tab_and_row_level_security.description`**, all
three. `879ea1304` removed `PermissionSet.contextVariables`
(enforce-or-remove, zero consumers) and its form row. `en`: `Tab
visibility and RLS policies.`

- ja-JP: 「タブ表示、RLS ポリシー、述語評価用カスタムコンテキスト変数。」 → 「タブ表示と RLS ポリシー。」
- es-ES: 「Visibilidad de pestañas, políticas RLS y variables de contexto
personalizadas para evaluar predicados.」 → 「Visibilidad de pestañas y
políticas RLS.」
- zh-CN: 「导航可见性与共享规则」 → 「标签页可见性与行级安全策略」. The old value named the section
after **sharing rules**. In this repository 共享规则 is the zh-CN name of a
different mechanism, the `sys_sharing_rule` object (`plugin-sharing`)
and its own Setup entry `nav_sharing_rules`. The section holds
`tabPermissions` and `rowLevelSecurity`. The new value uses the bundle's
own words: 标签页 (this section's label), 行级安全策略 (the bundle's rendering of
an RLS policy, as in `object.fields["access.default"].helpText`).

**`report.fields.blocks.helpText`**, all three. The 9.0 single-form
cutover (ADR-0021, `18178454c`) made every report block a dataset-bound
sub-report; no block joins objects. `en`: `Dataset-bound sub-reports
(joined report only)`

- ja-JP: 「複数オブジェクトを結合(joined レポートのみ)」 → 「データセットにバインドされたサブレポート(joined
レポートのみ)」
- es-ES: 「Une varios objetos (solo informe joined)」 → 「Subinformes
vinculados a un conjunto de datos (solo informe joined)」
- zh-CN: 「joined 报表的联合查询块」 → 「绑定数据集的子报表(仅 joined 报表)」

**`email_template.sections.identity.description`**, ja-JP and es-ES.
`47a92f427` promoted `email_template` to a first-class metadata type and
rewrote its form. The Identity section now holds `name` / `label` /
`category` / `locale` / `description`, with no `id` and no content type,
and `IEmailService.sendTemplate` resolves the template by `name`. A
reader who copied the old sample would pass `template: id`. zh-CN
already said what `en` says. `en`: `Template identifier resolved by
IEmailService.sendTemplate({ template: name, locale, ... }).`

- ja-JP: 「識別子とコンテンツ型。id は sendTemplate({ template: id, ... }) で参照される。」 →
「IEmailService.sendTemplate({ template: name, locale, ... })
が解決するテンプレート識別子。」
- es-ES: 「Identificador y tipo de contenido. El id se referencia con
sendTemplate({ template: id, ... }).」 → 「Identificador de plantilla que
resuelve IEmailService.sendTemplate({ template: name, locale, ... }).」

**`report.sections.joined_blocks.label`**, zh-CN only. It was found by
the retired-concept scan, not by the since-last-edit instrument, because
`en` (`Joined blocks`) never moved. The zh-CN label said 关联对象 ("related
objects"), the pre-9.0 model in which blocks joined objects. The section
holds the `blocks` repeater of dataset-bound sub-reports. ja-JP 結合ブロック
and es-ES Bloques unidos render `en` literally and are unchanged.

- zh-CN: 「关联对象」 → 「joined 报表分块」 (分块 is `report.fields.blocks.label`, and
`joined` stays the report-type token, as in 仅 joined 报表).

## Stale but not contradicting (listed, left as written)

| path | locales | what `en` did |
|---|---|---|
| `object.sections.fields.description` | all three | "each row becomes a
column" became "each entry becomes a column" (`9f8ec35c8`). Rewording. |
| `hook.fields.condition.helpText` | all three | "Optional formula —
skip the hook when this evaluates to false" became "CEL predicate — the
hook runs only when TRUE" (`48efe915b`, which changed the row's editor
language from `javascript` to `expression`). The runtime gate is `if
(!conditionFn(ctx))` in `packages/objectql/src/hook-wrappers.ts`, whose
own docblock says "skip when the formula evaluates FALSE", and the leaf
makes no JavaScript claim. This is the closest call among the leaves
left alone. |
| `app.fields.defaultAgent.helpText` | ja-JP, es-ES | "AI agent" became
"Platform agent", with the `ask` / `build` defaults added. Narrowing and
detail. zh-CN already says what `en` says. |
| `report.sections.joined_blocks.description` | ja-JP, es-ES | "blocks
joined into a single report" became "dataset-bound blocks stacked into a
single report". Combined into one report is still true, and the `en`
label still says Joined blocks. The second closest call. zh-CN already
says what `en` says. |
| `report.sections.filter_and_chart.description` | all three |
"Report-level filters" became "Render-time scope filter". The filter is
still the report's. |
| `dashboard.fields.refreshIntervalSeconds.label` (renamed key) | all
three | The rename added the unit to the label. Each locale's `helpText`
on the same field already says seconds (自動更新(秒), Actualización
automática (segundos), 自动刷新间隔(秒)). |

Penultimate-edit flags, all read by hand. Every one already says what
`en` says:

- `en` moved EARLIER than the last edit:
`object.fields.isSystem.helpText`,
`object.fields.fields.reference.helpText`,
`object.fields.fields.trackHistory.helpText`,
`view.fields.filter.helpText`, `page.fields.type.helpText`,
`dashboard.fields.gap.helpText`, `action.sections.advanced.description`,
`action.fields.body.helpText`, `report.sections.basics.description`,
`report.fields.columns.helpText` and `dataset.fields.measures.helpText`
(all three locales); plus, zh-CN only,
`app.fields.defaultAgent.helpText`,
`report.sections.joined_blocks.description`,
`email_template.sections.identity.description` and
`email_template.fields.variables.helpText`.
- `en` moved IN the last-edit commit:
`object.fields.indexes.fields.helpText`,
`field.fields.precision.helpText`, `field.fields.maskingRule.helpText`,
`page.fields.variables.source.label`,
`app.sections.content.description`,
`app.sections.access_and_sharing.description` and
`permission.sections.identity.description` (all three locales).

## Dispatch hypotheses, measured

- **H1 held, and widened.** The targets are exactly the three translated
bundles against `en.metadata-forms.generated.ts`. Both known leaves are
candidates in all three locales, and the raw counts reproduce as 9 / 9 /
6 at `a8acee28d`. Judged, 4 / 4 / 3 contradict. Two more paths besides
the known two contradict: `report.fields.blocks.helpText` (all three)
and `email_template.sections.identity.description` (ja-JP, es-ES).
zh-CN's permission leaf contradicts for a different reason than the
known one (sharing rules). The retired-concept scan added one zh-CN
label.
- **H2 held: values only.** The companions work as they do for the
object bundles. `collectFilledFromHashes` records a `metadataForms` leaf
only while it is a byte copy of the current `en` or of a recorded
digest. Today that is 0 rows in all three locales, and a hand
translation records nothing. At `aaee46778`, `node
scripts/check-i18n-bundles.mjs --write --filter=platform-objects`
printed `regenerated` and rewrote all eleven bundle and companion files
on disk (every mtime moved), and `git status --porcelain` stayed empty.
The control that shows the tool really reads these leaves is in
"Known-positive controls" above. No echo-decision ledger row and no test
names any of the 12 paths (`git grep` over the ledgers).
`metadata-forms-vocabulary.test.ts` asserts group keys only.
- **H3 held: both checks ran, and their hits are reported separately.**
Renamed key: 1 / 1 / 1 hit
(`dashboard.fields.refreshIntervalSeconds.label`), stale but not
contradicting. Penultimate edit: 18 / 18 / 22 flags, 0 contradicting.

## Verification (all at `aaee46778`)

- Build: `turbo run build --filter=@objectstack/cli...
--filter=@objectstack/platform-objects... --concurrency=2`: 59/59 tasks,
`VERDICT command-exit 0`.
- `pnpm --filter @objectstack/platform-objects test`: 58 files, 942
tests passed. `pnpm --filter @objectstack/platform-objects typecheck`:
exit 0, `check:test-typecheck: OK`.
- The new values ship: the built `dist/metadata-translations/index.mjs`
and `index.js` were imported, and `MetadataFormsTranslations` was read
by path after `withSourceFallback`. In both, 12 of 12 re-translated rows
equal the HEAD source and differ from the base, and 9 of 9
unchanged-leaf controls equal both.
- Gates: `node scripts/pm/dispatch-gates.mjs --commands` (no paths)
derived 55 commands from the real diff, and all 55 exit 0. `--ran`
printed "55 derived famil(ies) accounted for — 55 run, 0 NOT-MEASURED (a
DERIVED zero — all 55 recorded an exit code and none of them is 3)".
`pnpm check:dual-build-cjs-loads` first refused with exit 3
(`PREREQUISITE NOT MET`: nine packages outside this diff had no
`dist/`). Those were built and the gate re-ran: exit 0.
- Named in the verdicts: `check:i18n` "OK (9 package(s) — all bundles in
sync, no undeclared authoring keys)"; `check:i18n-stale-fill` "OK (10
bundle set(s) — no new stale fills, 0 baselined)"; `check:nul-bytes`
exit 0.
- Lint, narrowed to the three edited bundles: `eslint --no-inline-config
--format json` read 3 files, 0 errors, 0 warnings.
`ESLint#isPathIgnored` answers `false` for all three, read from the
repo's own config. The config enables no type-aware linting (no
`parserOptions.project` or `projectService`, `eslint.config.mjs` states
it at line 328), so a change to string values in these files cannot move
the verdict on any other file. The full `pnpm lint` is left to CI.
- `origin/main` moved two commits past the base (`defc7f7b5`). Neither
touches a form declaration or a translation bundle, so the measurement
stands on it.

## Acceptance notes

- `dataset-panel-echo-decisions.test.ts`, the reason on the `include`
label row, cites "report.sections.joined_blocks.label is 关联对象" as a
precedent for the word 关联. After this PR that label reads joined 报表分块,
so the citation is out of date. It is prose: no assertion reads it, and
the `include` decision stands on its own, since 关联 keeps authored
precedents such as `sys_email.fields.related_object.label` 关联对象 in the
objects bundle. The ledger is outside this card's file surface and is
not edited here.
- zh-CN `page.sections.data_context.description` reads 关联对象与变量 ("related
objects and variables") against `en` "Record binding and page-local
state." It is loose but does not contradict: the bound record's object
is the related object, and the variables are the page-local state. No
instrument flags it, and it is left as written.
- The since-last-edit instrument cannot see a leaf that was wrong when
it was authored while `en` never moved. Instruments 5 and 6 cover only
the vocabularies they name. Instruments were run from the session
scratchpad.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01DEvba2nBuD4tWzfq8r8NFY)_

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

2 participants