Skip to content

Commit bab28db

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-21565-hook-family-target-refused
dropped-refinements.baseline.json: both sides' sites kept (main's ui/ObjectFormProps entry and this branch's five hook sites); the measured counts stack to 218 schemas and 659 sites. registry.ts merged without a conflict; its generated regions are re-checked by the generator next. Claude-Session: https://claude.ai/code/session_01YDt3PzwfrkuFzUBF89WPmM Co-authored-by: Claude <noreply@anthropic.com>
2 parents 478e364 + d2e687f commit bab28db

22 files changed

Lines changed: 1088 additions & 138 deletions

File tree

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
---
2+
'@objectstack/spec': minor
3+
---
4+
5+
feat(spec)!: four members of an `object-form` page block take the shape the form reads instead of any value — `contentLayout`, `submitBehavior`, `navigateOnSuccess` and `mobile` (#21464)
6+
7+
Clause-②: yes (narrowing)
8+
9+
<!-- adr-0087: registered ui-object-form-members-typed -->
10+
11+
**BREAKING** — an accept-set narrowing on a published authoring surface, shipped as `minor` under the repo's launch-window convention for accept-set narrowings. What reads the row: the component-props gate on `objectstack validate`, `objectstack build` and `objectstack lint`, which reports a refused value as an advisory `component-props-invalid` / `component-props-unknown-key` finding. A stored page still saves and loads, because a page component's `properties` is not parsed on the metadata save or load path.
12+
13+
**`@objectstack/spec`**
14+
15+
- **Four members are typed.** `ComponentPropsMap['object-form']` declared `contentLayout`, `submitBehavior`, `navigateOnSuccess` and `mobile` as `z.unknown()`, although the form reads each with one shape. Any value passed, and an off-shape one was answered with a silent default: a `submitBehavior` whose `kind` the form does not know showed the thank-you panel; a misspelled `contentLayout` stacked the modal's sections; a `navigateOnSuccess` that is not a string failed the submit after the record had been written; a misspelled `mobile` member was ignored.
16+
- **`submitBehavior` is the form view's own block, by reference** — `{ kind: 'thank-you', title?, message? }`, `{ kind: 'redirect', url, delayMs? }`, `{ kind: 'continue' }` or `{ kind: 'next-record' }` — with the same rule on a `redirect` `url` a form view carries: a relative path, interpolating declared record fields as `{{record.field_name}}`.
17+
- **The measured shape, where no form view declares the member:** `contentLayout` is `'simple'` or `'tabbed'`; `navigateOnSuccess` is a relative path string (`{id}` / `{recordId}` interpolate the saved record's id); `mobile` is `{ stickyActions?, stepper?, stepperMinFields?, stepperFieldsPerStep?, fullscreenLongText? }`, with `stepper` `true`, `false` or `'auto'` and the two counts positive integers.
18+
- **`ObjectFormProps`** carries these types on the four members instead of `unknown`.
19+
- **The form's `fields` and `sections`, and the master-detail form's `fields` and `sections`, are not narrowed** and still accept any value. The form draws a top-level `fields` entry written as `{ name }` by that name, and it draws an inline runtime field (`{ name, type, … }`) written inside a section's `fields` as it stands — two shapes the typed members (field-name strings; the form view's section, whose field entry is keyed by `field`) would refuse. Each is held until that read is ruled. The master-detail form hands both members to its form unchanged, so they are held with the form's.
20+
- **`customFields` is not narrowed either.** Its entries are the console's runtime form field (keyed by `name`), which the spec has not declared; it is typed once the spec declares it.
21+
22+
## FROM → TO
23+
24+
| you wrote on an `object-form` | write instead |
25+
|:--|:--|
26+
| `submitBehavior: 'thank-you'` | `submitBehavior: { kind: 'thank-you' }` |
27+
| `submitBehavior: { kind: 'toast' }` (any `kind` outside the four) | one of `thank-you`, `redirect`, `continue`, `next-record` |
28+
| `submitBehavior: { kind: 'thank-you', heading: 'Done' }` | `{ kind: 'thank-you', title: 'Done' }` |
29+
| `submitBehavior: { kind: 'redirect', url: 'https://app.example.com/done' }` | a relative path: `url: '/done'` |
30+
| `contentLayout: 'tabs'` | `contentLayout: 'tabbed'` |
31+
| `navigateOnSuccess: { url: '/orders/{id}' }` | `navigateOnSuccess: '/orders/{id}'`, or `submitBehavior: { kind: 'redirect', url: '/orders/{{record.id}}' }` |
32+
| `mobile: { stepper: 'yes' }` | `mobile: { stepper: true }`, or `'auto'` for phone-width viewports only |
33+
| `mobile: { stepperFieldsPerStep: 0 }` | delete the key (one field a step is the default), or a positive integer |
34+
35+
The one-line fix: write each member as the table above shows. No conversion is registered, because an off-shape value has no rewrite that both keeps what the form shows today and honours what the author wrote; the D3 entry `ui-object-form-members-typed` carries that judgment.
36+
37+
## Who is affected, measured
38+
39+
A writer is a page-component node: an object literal naming the type, a literal annotated with the block's type, a `schema={{…}}` on the block's React component, a call into a local helper that builds the node, or a direct parse through the row. Each member's value is read through same-file constants and local helpers. The control is `objectName` on the same nodes.
40+
41+
- **objectstack** at `e909aa0a23`, over `examples/`, `packages/` (with `packages/apps/`), `content/`, `skills/` and `apps/`: 16 `object-form` nodes (the control on 13). Three values among the four members: the showcase's new-project wizard `submitBehavior` (a thank-you panel) and two copies of it in the lint and spec tests. All three parse.
42+
- **objectui** at the `.objectui-sha` pin `89cad75d55`: 539 `object-form` nodes (the control on 522). Across the four members there are 73 values: 60 are static, and 56 of them parse. The 4 that do not are test fixtures of a protocol-relative redirect (`//example.com/thanks`), each asserting that the form refuses it and navigates nowhere. Of the 13 values that are not static, 9 are relative redirects that parse by inspection, and 4 are redirect fixtures the form refuses (three same-origin absolute URLs and one protocol-relative one). No refused value is one the form draws.
43+
- **Deployed metadata** was not measured.

‎README.md‎

Lines changed: 47 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,11 @@
99
> **One executable business ontology.** AI writes it, the runtime runs it, agents
1010
> operate it, you own it.
1111
>
12-
> 本体即软件。一份可执行的业务本体。AI 写,运行时跑,Agent 用,归你所有。
12+
> 本体即软件。
1313
>
14-
> ObjectStack turns the whole app — data model, UI, workflows, permissions —
15-
> into typed metadata that fits in a single context window: apps small enough
16-
> for AI to hold whole. Agents read it whole, reason it whole, refactor it whole.
17-
>
18-
> That metadata is your **business ontology** — an open, versioned definition of
19-
> your objects, relations, actions, permissions, flows, and agent and tool
20-
> definitions that you own, not code scattered across a framework. It is
21-
> executable, not a knowledge-representation ontology: no inheritance, no
22-
> axioms, no reasoner. Strict TypeScript, Zod schemas, and a validation gate
23-
> catch the agent's mistakes at authoring time; the runtime derives the
24-
> database, REST API, UI, and MCP server, and enforces permissions and audit on
25-
> every call.
14+
> Apps small enough for AI to hold whole.
2615
27-
`Fits in an agent's context` · `Typed, validated, governed` · `Self-host anywhere` · Apache-2.0
16+
`Executable` · `AI-writable` · `Agent-operable` · `You own it` · Apache-2.0
2817

2918
<p align="center">
3019
<a href="https://youtu.be/CX_FlOoOtr0">
@@ -34,11 +23,9 @@
3423
<a href="https://youtu.be/CX_FlOoOtr0"><b>▶&nbsp; Watch: ObjectStack in 90 Seconds</b></a>
3524
</p>
3625

37-
**Everything in this repo is the open stack** — protocol, microkernel, SDK,
38-
CLI, and the production runtime, Apache-2.0 with no open-core asterisks
39-
([LICENSING.md](./LICENSING.md)). You build & ask with Claude Code or any coding
40-
agent: the agent writes the metadata in your repo and operates the running app
41-
over MCP. Want the same loop hosted, in the browser, nothing to install? That's
26+
You build & ask with Claude Code or any coding agent: the agent writes the
27+
metadata in your repo and operates the running app over MCP. Want the same loop
28+
hosted, in the browser, nothing to install? That's
4229
[ObjectOS](https://www.objectos.ai), the commercial runtime environment built on
4330
this stack.
4431

@@ -87,7 +74,21 @@ No install at all? Open a live app on
8774
</p>
8875
<p align="center"><sub>Prefer clicking? Studio authors the same metadata visually — same artifacts, same gate.</sub></p>
8976

90-
## What one definition gives you
77+
## What we mean by ontology
78+
79+
Your app's definition — objects and fields, relations, actions, permissions,
80+
flows, and agent and tool definitions — is a **business ontology**: open,
81+
versioned, and yours, not code scattered across a framework. It is executable,
82+
not a knowledge-representation ontology: no inheritance, no axioms, no reasoner —
83+
validated rather than reasoned over — and it is not a semantic layer over your
84+
existing systems (federating an external datasource is read-only by default and
85+
early). Views, dashboards, apps, and translations are projections of the
86+
ontology, not part of it, and code does not disappear: it moves into the
87+
runtime, as hooks, action bodies, CEL, and constrained JSX. The full account is
88+
[Business Ontology](https://objectstack.ai/docs/concepts/ontology); the short
89+
form is the [glossary entry](https://objectstack.ai/docs/getting-started/glossary#business-ontology).
90+
91+
## The runtime runs it
9192

9293
Point an agent at an empty repo and you get a one-off codebase: every screen
9394
hand-invented, every mistake yours to find at runtime. ObjectStack gives the
@@ -144,6 +145,24 @@ curl http://localhost:3000/api/v1/data/support_desk_ticket
144145
In the browser, the typed client SDK and React hooks (`useQuery`, `useMutation`,
145146
`usePagination`) live in [`@objectstack/client-react`](packages/client-react).
146147

148+
## Agents are the first users
149+
150+
Objects are tools, actions are tools, permissions decide what an agent may call,
151+
and audit records what it did. Because the app is typed metadata, the runtime
152+
serves it as an **MCP server** at `/api/v1/mcp` — on by default. Point any MCP
153+
client at it and an agent can inspect and *operate* the app you just built,
154+
under the same permissions and RLS as a human:
155+
156+
```bash
157+
claude mcp add --transport http my-app http://localhost:3000/api/v1/mcp
158+
```
159+
160+
The first tool call opens a browser to sign you in — each deployment is its own
161+
OAuth server, so there's no token to copy-paste. Headless setups (CI,
162+
containers) use an API key instead. Objects are exposed automatically; actions
163+
opt in with `ai: { exposed: true }`. See
164+
[Connect an MCP Client](https://objectstack.ai/docs/ai/connect-mcp) for both flows.
165+
147166
## Why the mistakes don't ship
148167

149168
"AI writes it" is only useful if AI's mistakes don't reach production. Four gates
@@ -171,35 +190,18 @@ hoping. Measure it yourself:
171190
find examples/app-crm/src -name '*.ts' -not -name '*.test.ts' | xargs cat | wc -l
172191
```
173192

193+
## You own it
194+
195+
**Everything in this repo is the open stack** — protocol, microkernel, SDK,
196+
CLI, and the production runtime, Apache-2.0 with no open-core asterisks
197+
([LICENSING.md](./LICENSING.md)). The definition lives in your repository as
198+
ordinary TypeScript, versioned in your VCS and reviewable as a diff — not a
199+
graph held inside a vendor's system.
200+
174201
> The ontology is the software. Your objects, relations, actions, permissions,
175202
> flows, and agent and tool definitions are your business ontology — and the
176203
> definition layer of the AI era should be an open protocol you own.
177204
> [Read why](https://www.objectos.ai/en/blog/ai-ontology-open-protocol/).
178-
>
179-
> What that does and does not mean: the ontology is *executable*, so it is
180-
> validated rather than reasoned over — there is no object inheritance, no
181-
> axioms and no reasoner. It is not a semantic layer over your existing systems
182-
> (federating an external datasource is read-only by default and early). Views,
183-
> dashboards, apps, and translations are projections of the ontology, not part
184-
> of it. And code does not disappear: it moves into the runtime — hooks, action
185-
> bodies, CEL, and constrained JSX.
186-
187-
## Your app is AI-operable, for free
188-
189-
Because the app is typed metadata, the runtime serves it as an **MCP server** at
190-
`/api/v1/mcp` — on by default. Point any MCP client at it and an agent can
191-
inspect and *operate* the app you just built, under the same permissions and RLS
192-
as a human:
193-
194-
```bash
195-
claude mcp add --transport http my-app http://localhost:3000/api/v1/mcp
196-
```
197-
198-
The first tool call opens a browser to sign you in — each deployment is its own
199-
OAuth server, so there's no token to copy-paste. Headless setups (CI,
200-
containers) use an API key instead. Objects are exposed automatically; actions
201-
opt in with `ai: { exposed: true }`. See
202-
[Connect an MCP Client](https://objectstack.ai/docs/ai/connect-mcp) for both flows.
203205
204206
## Ship it
205207

‎apps/docs/app/[lang]/page.tsx‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ const mono = IBM_Plex_Mono({
3131
variable: '--font-l-mono',
3232
});
3333

34-
const HOME_TITLE = 'Metadata framework for AI-written apps';
34+
const HOME_TITLE = 'The ontology is the software';
3535
const HOME_DESCRIPTION =
3636
'One executable business ontology. AI writes it, the runtime runs it, agents operate it, you own it. The whole app — data model, UI, workflows, permissions — is typed metadata small enough for AI to hold whole.';
3737

@@ -258,11 +258,13 @@ export default function HomePage() {
258258
className="mt-8 flex flex-wrap items-center justify-center gap-x-3 gap-y-2 text-[13px] text-fd-muted-foreground"
259259
style={{ fontFamily: 'var(--l-mono)' }}
260260
>
261-
<span>Fits in an agent&apos;s context</span>
261+
<span>Executable</span>
262262
<span aria-hidden className="text-fd-border">|</span>
263-
<span>Typed, validated, governed</span>
263+
<span>AI-writable</span>
264264
<span aria-hidden className="text-fd-border">|</span>
265-
<span>Self-host anywhere</span>
265+
<span>Agent-operable</span>
266+
<span aria-hidden className="text-fd-border">|</span>
267+
<span>You own it</span>
266268
</div>
267269
</section>
268270

‎apps/docs/lib/site.ts‎

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,16 +60,20 @@ export function absoluteUrl(path: string): string {
6060
*
6161
* ## Provenance — how to re-encode it
6262
*
63-
* The master is `docs/screenshots/hero-cover-dark.png` (2400x1200, 406,703 B),
63+
* The master is `docs/screenshots/hero-cover-dark.png` (2400x1200, 563,953 B),
6464
* which the README embeds directly from the repo and which is NOT served by this
65-
* site. This file is a lossy WebP derived from that master, and the derivation is
66-
* the whole reason it is small enough to sit above the fold:
65+
* site. The master is itself a render of `docs/screenshots/hero-cover-dark.html`
66+
* by `docs/screenshots/render-hero-cover.mjs` (the preinstalled Playwright
67+
* Chromium), so a copy change is an edit to that template and one re-render —
68+
* the script performs the derivation below as its second step. This file is a
69+
* lossy WebP derived from that master, and the derivation is the whole reason it
70+
* is small enough to sit above the fold:
6771
*
6872
* sharp('docs/screenshots/hero-cover-dark.png')
69-
* .webp({ quality: 80, effort: 6 }) // sharp 0.35.3 / libwebp 1.6.0
73+
* .webp({ quality: 80, effort: 6 }) // sharp 0.35.4 / libwebp 1.6.0
7074
* .toFile('apps/docs/public/hero-cover-dark.webp')
7175
*
72-
* 83,272 B — 20.5% of the PNG. Re-encode from the master, never from this file:
76+
* 85,472 B — 15.2% of the PNG. Re-encode from the master, never from this file:
7377
* a lossy re-encode of a lossy source compounds. And do not "restore" the PNG
7478
* alongside it — a `public/` holding both is how the next re-encode picks the
7579
* wrong one.
@@ -83,5 +87,5 @@ export const HERO_COVER = {
8387
url: '/hero-cover-dark.webp',
8488
width: 2400,
8589
height: 1200,
86-
alt: 'ObjectStack — the metadata framework for AI-written apps',
90+
alt: 'ObjectStack: the ontology is the software. One executable business ontology, written by AI, run by the runtime, operated by agents, owned by you.',
8791
} as const;
2.15 KB
Loading

‎content/docs/concepts/meta.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"pages": [
44
"architecture",
55
"metadata-driven",
6+
"ontology",
67
"metadata-lifecycle",
78
"design-principles",
89
"north-star"

‎content/docs/concepts/north-star.mdx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ description: "Where ObjectStack is headed: a metadata-native backend that humans
77
ObjectStack is the metadata-native backend for business software that humans and
88
AI agents can both operate safely. The platform makes the business system
99
explicit: data models, views, flows, permissions, actions, AI tools, and runtime
10-
requirements are typed metadata instead of scattered ad-hoc code.
10+
requirements are typed metadata instead of scattered ad-hoc code. In the
11+
enterprise-AI vocabulary, the ontology is the software.
1112

1213
The goal is a small, inspectable, governed application surface:
1314

0 commit comments

Comments
 (0)