Skip to content

feat(cli)!: kind:'react' pages are off unless the deployment turns them on - #25789

Merged
os-tesla merged 8 commits into
mainfrom
claude/project-thread-80dotk
Oct 25, 2026
Merged

os-tesla merged 8 commits into
mainfrom
claude/project-thread-80dotk

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Tesla · project thread

Clause-②: no
Metadata-protocol: none

What changes

The kind:'react' page tier, whose page source runs in the Console with no sandbox, is now off unless the deployment turns it on. ADR-0081 is amended to match.

  • The switch. packages/cli/src/utils/console.ts reads readReactPagesTier() once per Console document. It is on for OS_PAGE_REACT_ENABLED=true (also 1, on, yes, enabled) and off when the variable is unset or false, 0, off, no, disabled. Any other value leaves it off and logs one warning naming the variable and the accepted values. The earlier name, OS_PAGE_REACT, is still read through readEnvWithDeprecation while the new one is unset, with its one-time deprecation warning. The new name follows the OS_{DOMAIN}_{FEATURE}_ENABLED rule for an opt-in flag (AGENTS.md Prime Directive 9).
  • What the document carries. The server now writes the Console's capability global in both directions: the enable list (__OBJECTUI_CAPABILITIES__) when on, the disable list when off. So the served Console follows the server's setting whichever default its build carries. That makes this PR safe to land before the objectui half, which flips the renderer's own default.
  • The content-security-policy. 'unsafe-eval' stays tied to the same per-document read (unchanged logic from the nonce-CSP change), so by default the Console document refuses eval.
  • app-showcase opts in. examples/app-showcase/.env sets OS_PAGE_REACT_ENABLED=true. os dev, os serve and os start load that file from the project root (dotenv-flow), so pnpm dev, the showcase smoke and os start in that example keep running its three react pages.
  • Docs and contract text. content/docs/ui/react-pages.mdx, content/docs/ui/pages.mdx, content/docs/protocol/objectui/layout-dsl.mdx, the PageSchema.kind description in packages/spec (and the regenerated content/docs/references/ui/page.mdx), the published skill rule skills/objectstack-ui/rules/pages.md, and the platform-checklist item studio-authoring for the react tier (revision 4) all state the new default and switch.
  • ADR-0081 amendment. Status line and an amendment block: decision 3 / TL;DR 4 are superseded on the default only; the tier, the gate and its host-only control stand.

Landing order

  1. This PR (server side). With it merged, a deployment's setting decides, against the Console build objectstack currently pins and against the next one.
  2. objectui: the react-pages default flips to off in @object-ui/core and the disabled notice names OS_PAGE_REACT_ENABLED=true (separate draft PR, linked from the thread).

Known side effect until a follow-up lands

With the tier off, the Console's schema library probes for eval once at boot and falls back without it. Under this policy that probe is refused, so a browser that reports content-security-policy violations logs one script-src eval refusal per Console load. Nothing on the page is affected. Before this PR that happened only on deployments that set OS_PAGE_REACT=off; after it, on every deployment that leaves the tier off. objectui#13511 removes the probe (one line in the Console boot). The changeset says this to operators.

Tests

Run on head e6df92d2:

  • pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2 src/utils/console → Test Files 7 passed (7), Tests 142 passed (142).
  • console.content-security-policy.test.ts (real plugin on a real Hono app): default document refuses 'unsafe-eval' and carries the disable global under the nonce; OS_PAGE_REACT_ENABLED=true allows it and carries the enable global; legacy OS_PAGE_REACT=on / off in both directions; the new name wins over the legacy one; an unrecognized value stays off and warns exactly once over two documents.
  • console.branded-head.test.ts: the served-unchanged shell now includes the capability script (off by default).
  • pnpm --filter @objectstack/spec check:generated: check:docs regenerated (content/docs/references/ui/page.mdx). check:liveness is red on the base commit too, for translation/flows.screens (a citation this PR does not touch).

Derived gates (node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, 130 families, --ran reconciles all 130):

  • 126 exit 0, five of them after building the prerequisite they named (cli and client-react dists, the pinned teardown control commit): check-plugin-teardown-shape --self-test, check:skill-examples, check:i18n, check:i18n-walk-parity, check:type-check-debt.
  • check:liveness (and check:generated, which reports only that row) exit 1 on translation/flows.screens. The same command on the base commit f0cf33833 exits 1 with the same row.
  • check:i18n-coverage and check:dual-build-cjs-loads NOT MEASURED locally: both need a full workspace build. CI runs them.

Ablation: with readReactPagesTier returning on for an unset value, four cases in console.content-security-policy.test.ts go red; restored, git diff HEAD is empty.

Full dogfood (title carries !): in progress on e6df92d2; the result goes in a comment.

Governed surfaces

This diff touches docs/adr/0081-trusted-react-page-tier.md and skills/objectstack-ui/rules/pages.md, so it is Tier H and stays in draft for an authorized approval. ADR-0081's decision named the old default, so the default does not move without the amendment (Prime Directive 13).

skills/** readings: skills/objectstack-ui/rules/pages.md 428 → 428 lines; all skills/**/SKILL.md together 4383 → 4383 lines (unchanged; the edit rewords three lines in place).

维护者速读(草稿)

  • 改了什么:kind:'react' 页面(页面源码在 Console 里直接执行、无沙箱)从「默认开」改为「默认关」。部署方设 OS_PAGE_REACT_ENABLED=true 才开;旧变量 OS_PAGE_REACT=on/off 仍可用(带一次弃用提示)。服务端把开/关两个方向都写进 Console 文档,CSP 的 'unsafe-eval' 跟随同一开关。app-showcase 用自己的 .env 显式开启。ADR-0081 加修订说明。
  • 为什么改:作者是否可信是每个部署自己的判断,平台默认不应替所有部署做这个判断;默认关、显式开,把这个决定交给运维方。
  • 风险与代价(含回滚):已在用 react 页面的部署升级后,这些页面显示「已禁用」提示,直到设置 OS_PAGE_REACT_ENABLED=true(changeset 写明迁移)。默认关时,每次加载 Console 会多一条 zod 的 eval 探测 CSP 违规上报,不影响页面,objectui#13511 消除。回滚:revert 本 PR,或部署侧设该变量。
  • 席位意见:
  • 你要做的:审阅并批准本 PR(受管面:ADR 与 skills),然后看 objectui 那半的 draft PR。

Generated by Claude Code

🤖 Generated with Claude Code

https://claude.ai/code/session_01X4d6XVfcfxL17bMpptytcc

…m on

WIP: console serving reads OS_PAGE_REACT_ENABLED (legacy OS_PAGE_REACT),
defaults the tier off, and writes the capability global in both directions.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X4d6XVfcfxL17bMpptytcc
@github-actions

github-actions Bot commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/cli, @objectstack/spec, touching 12 documentable anchor(s).

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

  • content/docs/protocol/objectui/layout-dsl.mdx (via PageSchema (symbol, a top-level const), OS_PAGE_REACT_ENABLED (literal, a string literal in REACT_PAGES_ENV))
  • content/docs/ui/pages.mdx (via OS_PAGE_REACT (literal, a string literal in REACT_PAGES_LEGACY_ENV), OS_PAGE_REACT_ENABLED (literal, a string literal in REACT_PAGES_ENV))
  • content/docs/ui/react-pages.mdx (via OS_PAGE_REACT (literal, a string literal in REACT_PAGES_LEGACY_ENV), OS_PAGE_REACT_ENABLED (literal, a string literal in REACT_PAGES_ENV))
What this run could not see
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • 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 — 151 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 e2a4b6a06315959625748b44ed91b1f9731c67ee → packageMentionDocs.

Which tree this was computed on

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

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

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Full dogfood on head e6df92d2 (the PR head), run once as the ! title requires:

  • pnpm --filter '@objectstack/dogfood^...' build (declarations on), then pnpm --filter @objectstack/dogfood test under the shared verify lock: VERDICT command-exit 0.
  • Test Files 295 passed | 3 skipped (298), Tests 2520 passed | 18 skipped (2538). No failures.

Generated by Claude Code

… only

`utils/console.ts` is behind the published `./console` subpath, which the
packed-tarball pin runs with no other package installed, so the import of
`@objectstack/types` failed there with ERR_MODULE_NOT_FOUND. The legacy
`OS_PAGE_REACT` fallback is now spelled in the module, and its deprecation
warning goes through the plugin logger once. The reader and its constants are
no longer exported, so the module's export census is unchanged.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X4d6XVfcfxL17bMpptytcc
…translation/flows.screens row

The same ledger edit as #25751, carried here because `check:liveness` and
`check-liveness.test.ts` are red on main for every PR that reaches
packages/spec. The hunk is identical, so either landing order merges clean.

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

Copy link
Copy Markdown
Contributor Author

CI on e6df92d, and what 87ff23f changes:

  • Test Core (3/8): test/published-subpath-console.pin.test.ts failed. The published ./console entry is run from the packed tarball with nothing else installed, and utils/console.ts had gained an import of @objectstack/types (for readEnvWithDeprecation), which failed there with ERR_MODULE_NOT_FOUND. The module's export census also grew by two names. Fixed in 6903620: the legacy OS_PAGE_REACT fallback is written in the module, its deprecation warning goes through the plugin logger once, and the reader and its constants are not exported. So the PR body's sentence about readEnvWithDeprecation no longer holds; the behaviour it describes is unchanged. Local runs: the pin test passed 14/14 against a rebuilt dist, src/utils/console passed 142/142 (the legacy case now also asserts the warning), and tsc --noEmit exited 0.
  • Test Core (1/8) and Spec property liveness: both came from the translation/flows.screens citation of service-automation/src/engine.ts. That citation fails on main for every PR that reaches packages/spec. 87ff23f carries the same liveness/translation.json hunk as fix(spec/liveness): drop the comment-only engine.ts pointer from the translation/flows.screens row #25751, so either one can land first and the merge stays clean. Local runs: check:liveness exited 0 (761 … pair(s) asked, 760 anchored, 1 exempt), and check-liveness.test.ts passed 71/71.
  • Type Check · workspace and TypeScript Type Check: the runner received a shutdown signal and Turborepo force-killed the remaining tasks. No error TS appears in the log. They re-run on this push.

Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review October 25, 2026 13:21
@os-zhuang
os-zhuang added this pull request to the merge queue Oct 25, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Oct 25, 2026
main's #25796 rewrote the `translation/flows.screens` evidence to cite the
seam modules, superseding the ledger hunk this branch carried from #25751;
the conflict resolves to main's row, so this branch no longer touches the
ledger.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X4d6XVfcfxL17bMpptytcc
@os-tesla
os-tesla enabled auto-merge October 25, 2026 13:55
@os-tesla
os-tesla added this pull request to the merge queue Oct 25, 2026
Merged via the queue into main with commit 5e7cf63 Oct 25, 2026
48 checks passed
@os-tesla
os-tesla deleted the claude/project-thread-80dotk branch October 25, 2026 14:56
os-tesla pushed a commit that referenced this pull request Oct 25, 2026
…on (#13526)

<!-- claude-projects-attribution: {"github_login":"os-tesla"} -->
_Requested by **Tesla** · [project
thread](https://claude.ai/code/project/chan_01AXsQkLFKRdQCqQ3RTr6JaoBY54KcuzDWcTYXW18gG3bJ)_

## What changes

The `react-pages` capability in `@object-ui/core` (it gates `kind:
'react'` pages, whose source runs in the application with no sandbox)
now defaults to **off**. A host turns it on explicitly.

- `packages/core/src/runtime/capabilities.ts`: the default flips to
`false`. The two page globals keep their meaning:
`__OBJECTUI_CAPABILITIES__` turns a capability on,
`__OBJECTUI_CAPABILITIES_DISABLED__` turns it off and wins over a stale
enable list.
- `ReactKindPage`
(`packages/components/src/renderers/layout/react-page.tsx`) already drew
a named notice instead of a blank page while the capability is off. Its
text now says the tier is off unless the deployment turns it on and
names the switch, `OS_PAGE_REACT_ENABLED=true` (or
`enableCapability('react-pages')` for a host that embeds the renderer).
The title "React pages are disabled on this deployment" is unchanged.
- Studio's new-page dialog (`offeredPageSourceKinds`) already offered
`react` only while the capability is on; only its comment changes.
- `apps/console/src/sdui-workbench-preview.tsx` (a dev preview harness)
now sets the enable global itself, the way the server does, and
`?disable=1` leaves it unset.
- `content/docs/guide/react-pages.md`: the security-gate section states
the new default and how the server and an embedding host turn it on.

## Landing order

The ObjectStack server half is #25789: it
reads `OS_PAGE_REACT_ENABLED` and writes the enable global when on and
the disable global when off, so the Console follows the server whichever
default its build carries. Land that first. After it, this PR changes
nothing a server-hosted Console does; it changes the default for a host
that embeds the renderer without saying either way, and for a Console
served by an older server, where react pages stay off until the server
is upgraded.

This PR does not move `.objectstack-sha`: nothing here reads an
objectstack symbol the current pin lacks, and the pin move before the
merge window is already in flight on its own card.

## Known side effect until a follow-up lands

With the tier off, the server's content-security-policy refuses `eval`,
and zod's one-time `eval` probe at Console boot is reported as one
violation per load (nothing breaks). objectui#13511 sets zod `jitless`
in the Console boot and removes it.

## Tests

- New
`packages/components/src/__tests__/react-page-capability-gate.test.tsx`:
a real react page through the real `type: 'home'` PageRenderer draws the
notice naming `OS_PAGE_REACT_ENABLED=true` by default and never runs its
source; with the capability enabled the same source runs; disabled
again, it is closed.
- `packages/core/src/runtime/__tests__/capabilities.test.ts`, rewritten
to load a fresh module per case (the default is only observable before
any override): default off; the enable global turns it on; the disable
global wins over an enable list; host enable and disable; unknown
capabilities off.
- Nine test files that render react pages relied on the old default and
now enable the capability at module scope: `react-page-adapter`,
`react-page-content-identity`, `react-page-invalidation`,
`react-page-scope`, `react-page-state` (components), `interfaceCreate`
and `StudioDesignSurface.interfacesCreatePage-11823` (app-shell),
`ListView.objectProviderBinding-7477` (plugin-list),
`sdui-workbench-preview-listview-binding-8510` (console). Measured:
before the opt-in those nine files failed (27 tests) and nothing else
failed, in a run of 695 files across core runtime, components, app-shell
studio-design and previews, console, and the two plugin files.
- After: `pnpm exec vitest run packages/core/src/runtime/
packages/components/src/__tests__/react-page …` (the eleven files above
plus the gate test) → `Test Files 11 passed (11)`, `Tests 73 passed
(73)`.
- Ablation: with the default put back to `true`, the default-off cases
in the gate test and the capabilities test go red (2 failed, 6 passed);
restored, `git diff HEAD` is empty.
- `turbo run type-check --filter=@object-ui/core
--filter=@object-ui/components` → 10/10 tasks. `turbo run type-check
--filter=@object-ui/app-shell --filter=@object-ui/plugin-list
--filter=@object-ui/console` → 38/38 tasks. Head `f2792aa`.
- `eslint --no-inline-config` on every changed `.ts`/`.tsx` → 0 errors.
`check-changeset-presence`, `check-changeset-no-major`,
`check:new-line-citations`, `check:control-bytes` → exit 0.
`check-governed-queue-guard --test` on the file list → NOT GOVERNED.

## Changeset

`.changeset/react-pages-default-off.md`: `minor` for `@object-ui/core`,
`@object-ui/components`, `@object-ui/app-shell`, `@object-ui/console` (a
published default changes; `minor` under this repository's version
policy), with the FROM → TO for an embedding host.

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

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01X4d6XVfcfxL17bMpptytcc

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: os-zhuang <jack@objectstack.ai>
os-tesla pushed a commit that referenced this pull request Oct 25, 2026
…1, WIP)

Brings the branch up to main abebc4e for the cutover sitting. Clean merge;
main's #25789 (cli console.ts), #25757 (spec vitest.repo-tests.json) and #25780
(dispatch-gates) overlap files this PR touches and are re-checked after it.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0149BHSXfCdQywGaLdpRAtWe
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 protocol:ui size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants