Skip to content

Commit 4a1e80a

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-22877-locale-bundle-overlay
2 parents 4d6105d + eede467 commit 4a1e80a

21 files changed

Lines changed: 1086 additions & 118 deletions
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
'create-objectstack': minor
3+
---
4+
5+
The blank starter ships a test story: `pnpm test` boots the scaffolded app's own stack with `bootStack` from `@objectstack/verify` and asserts one business fact through its in-process handle
6+
7+
Clause-②: no
8+
9+
A project scaffolded with `npx create-objectstack` used to start with no `test` script and no way to assert its own behaviour, so the first test an author wrote was a hand-assembled kernel or a faked `ctx`. The blank template now carries:
10+
11+
- **`note.test.ts`**, one starter test. It boots the app the project declares (its objects, its plugins, the services it requires) on the real engine, in-process, with an in-memory database and no server, and asserts that a note without a title is refused with the engine's own `VALIDATION_FAILED` error naming `title`, beside the same note with a title being saved. It addresses the object as `Note.name`, so it follows the namespace scaffolding writes.
12+
- **`vitest.config.ts`**: a 60 s hook timeout for the boot, vitest's console capture switched off, so a log line the stack writes while a worker shuts down cannot fail a green run, and `OS_REGISTRY_LOG: 'warn'`, which drops the engine's one-line-per-registered-item boot log.
13+
- **`package.json`**: a `test` script (`vitest run`), and `@objectstack/verify` (at the project's `@objectstack/*` range) and `vitest` as devDependencies. The vitest range is `~4.0.0`, the one `objectstack init -t plugin` and `objectstack create plugin` declare, and it is generated from that shared policy rather than written by hand, so the three scaffolders cannot drift apart. `~4.0.0` is the range that installs on npm 10, the npm Node 22 bundles; `^4.0.0` does not.
14+
- **`tsconfig.json`** includes every root `*.ts` (`"*.ts"`, as `objectstack init` writes it), so `pnpm typecheck` checks the starter test and the vitest config.
15+
- **`README.md`** lists `pnpm test` beside `validate`, `lint` and `typecheck`.
16+
17+
The project's CI workflow is unchanged: its steps are the script set every scaffolder's project must declare, and `objectstack init -t app` declares no `test` script.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@objectstack/runtime': patch
3+
---
4+
5+
fix(runtime): on a `kernel-resolver` host, a dispatcher door resolves a service on the request's own environment kernel before the host kernel. `HttpDispatcher.resolveService` used to ask the host kernel's scoped lookup first, and the host kernel's singleton `protocol` / `metadata` / `objectql` answered it whatever the scope. So `POST /api/v1/actions/:object/:action` loaded its subject row through a protocol that registers none of the environment's objects, and it answered `404 RECORD_NOT_FOUND` for a row the caller can read. Hosts with no request kernel of their own (single-environment, SharedProjectPlugin) resolve exactly as before, and an id that names nothing is still refused with 404.
6+
7+
Clause-②: no

‎.github/workflows/ci.yml‎

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1066,17 +1066,27 @@ jobs:
10661066
# second red on wall time against `timeout-minutes`. Both constants, their
10671067
# measured basis and a self-test battery each live in the script.
10681068
#
1069+
# THE RED IS A `schedule` RED ALONE (C2, both timing steps, ruling
1070+
# https://github.com/objectstack-ai/objectstack/issues/22949#issuecomment-6119186511):
1071+
# the dataset's weights are measured on the hourly `schedule` runs, so
1072+
# only those exit 1 on DRIFT. On `merge_group`, `pull_request`, `push`
1073+
# and `workflow_dispatch` the step prints the same verdict, raises the
1074+
# DRIFT as a `::warning::` annotation and exits 0. The event reaches the
1075+
# script as `--event "$GITHUB_EVENT_NAME"`, which it requires; the rule
1076+
# and its self-test live there (TIMING_GRADED_EVENT, timingGateMode).
1077+
#
10691078
# EXECUTED WINDOWS ONLY. Measurements come from samplesFromSummary() in
10701079
# measure-test-shard-timings.mjs, which drops cache HITs and failed
10711080
# suites, and the prediction is summed over the same executed packages.
10721081
# A replay's near-zero window would read as a shard far FASTER than
10731082
# predicted and vouch for a rotted dataset, so a shard whose packages all
10741083
# replayed is NOT MEASURED, never "fast".
10751084
#
1076-
# NO `if:` and NO `continue-on-error` (the red is the point), and ABOVE
1077-
# the attestation pair for the #6082 reason documented on the upload
1078-
# above: a failed unguarded step skips that pair, so a drift red also
1079-
# withholds this shard's attestation, the fail-closed direction. A shard
1085+
# NO `if:` and NO `continue-on-error` (the red is the point, and the
1086+
# `schedule`-only red is the script's `--event`, so neither is needed),
1087+
# and ABOVE the attestation pair for the #6082 reason documented on the
1088+
# upload above: a failed unguarded step skips that pair, so a drift red
1089+
# also withholds this shard's attestation, the fail-closed direction. A shard
10801090
# with no packages runs no turbo and writes no summary; that is NOT
10811091
# MEASURED here rather than a usage error from the script.
10821092
#
@@ -1109,7 +1119,7 @@ jobs:
11091119
exit 0
11101120
fi
11111121
node scripts/partition-test-shards.mjs --check-drift "${SUMMARIES[@]}" \
1112-
--label "Test Core (${{ matrix.shard }}/6)"
1122+
--label "Test Core (${{ matrix.shard }}/6)" --event "$GITHUB_EVENT_NAME"
11131123
11141124
# Runs even when the suite failed — that is when it earns its keep. It
11151125
# answers TWO questions about a red suite, and needs both to be able to
@@ -1486,12 +1496,12 @@ jobs:
14861496
# ── The per-package suite-duration ratchet (#16468) ──────────────────
14871497
#
14881498
# A GATE, unlike the two reporting steps above: NO `continue-on-error`,
1489-
# so a package whose suite ran past its ceiling reds `Test Core`, naming
1490-
# the package and its slowest test files. The ceiling is the package's
1491-
# slowest executed run in the refresh window x 1.25, generated into
1492-
# scripts/test-shard-timings.json (`ceilings`) by the refresh lane and
1493-
# held by every later refresh; it rises only by a ruled entry in the
1494-
# script's RULED_CEILING_RAISES. A `provisional` package prints "no
1499+
# so on a `schedule` run a package whose suite ran past its ceiling reds
1500+
# `Test Core`, naming the package and its slowest test files. The
1501+
# ceiling is the package's slowest executed run in the refresh window
1502+
# x 1.25, generated into scripts/test-shard-timings.json (`ceilings`) by
1503+
# the refresh lane and held by every later refresh; it rises only by a
1504+
# ruled entry in the script's RULED_CEILING_RAISES. A `provisional` package prints "no
14951505
# ceiling: provisional" and is not red; a package absent from the dataset
14961506
# prints "no ceiling: new package" and is not red until the first refresh
14971507
# after it lands gives it a ceiling, while a package the dataset knows
@@ -1505,6 +1515,20 @@ jobs:
15051515
# samplesFromSummary, and every vitest file line); a capture that did not
15061516
# arrive leaves its packages ungraded and says so, never red.
15071517
#
1518+
# EXIT-GRADED ON `schedule` ALONE (C2, both timing steps, ruling
1519+
# https://github.com/objectstack-ai/objectstack/issues/22949#issuecomment-6119186511):
1520+
# the ceilings come from the hourly `schedule` runs, so only those red
1521+
# this job on a package over its ceiling. On `merge_group`,
1522+
# `pull_request`, `push` and `workflow_dispatch` the step prints the same
1523+
# table, raises each package over its ceiling as a `::warning::`
1524+
# annotation and exits 0; a real slowdown merged to `main` reds the next
1525+
# hourly run. A package whose ceiling is under the absolute floor (20s,
1526+
# CEILING_FLOOR_SECONDS) prints "below floor" and is never red. The
1527+
# event reaches the script as `--event "$GITHUB_EVENT_NAME"`, which it
1528+
# requires; a refusal (a malformed table or raise record) is red on every
1529+
# event. The rule, the floor's reading and their self-tests live in the
1530+
# script.
1531+
#
15081532
# NOT MEASURED, exit 0, while the dataset carries no `ceilings`: they are
15091533
# generated only where the run-summary artifacts are downloadable (the
15101534
# refresh lane), so the first refresh after this lands writes them, and
@@ -1515,7 +1539,7 @@ jobs:
15151539
# captured, so a red shard's packages are graded too.
15161540
- name: Check the per-package suite-duration ceilings
15171541
if: always()
1518-
run: node scripts/check-test-suite-ceilings.mjs --captures "$RUNNER_TEMP/test-timings" --label 'Test Core'
1542+
run: node scripts/check-test-suite-ceilings.mjs --captures "$RUNNER_TEMP/test-timings" --label 'Test Core' --event "$GITHUB_EVENT_NAME"
15191543

15201544

15211545
# ── Temporal conformance against live, non-UTC servers (ADR-0053 D-A3) ─────

‎.github/workflows/scaffold-e2e.yml‎

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
#
1010
# Job 1 (PRs touching the scaffolder / docker example) scaffolds with the
1111
# repo-built scaffolder and runs the generated project against the registry:
12-
# install → validate → build → boot → health probes → docker build/run. That
12+
# install → validate → build → test → boot → health probes → docker build/run. That
1313
# run scaffolds with `--skip-install` and installs the project itself, so it
1414
# exercises the scaffolder's `--skip-install` shape; a second, throwaway
1515
# project is scaffolded WITHOUT it purely to assert the shape a real user
@@ -148,6 +148,16 @@ jobs:
148148
npm run validate
149149
npm run build
150150
151+
# The starter's own test (`note.test.ts`): it boots the generated
152+
# project's stack in-process through @objectstack/verify, as installed
153+
# from the registry, and asserts one business fact on the real engine.
154+
# A scaffold whose `test` script is red out of the box teaches its user
155+
# to ignore tests, so the starter ships it green or not at all.
156+
- name: Test the generated project
157+
run: |
158+
cd "$RUNNER_TEMP/e2e-app"
159+
npm test
160+
151161
# Two questions this step must answer about its own server, and used to
152162
# answer neither (#9779). Both belong here rather than in the CLI: `os
153163
# start` is already correct — the shell around it was not.
@@ -486,14 +496,18 @@ jobs:
486496
487497
# `build` is the gate every template must pass; `validate` is run only
488498
# when the scaffolded project defines it (the now-retired remote
489-
# templates did not always ship it).
499+
# templates did not always ship it), and `test` likewise: a published
500+
# scaffolder older than the starter's `note.test.ts` declares none.
490501
- name: Gate the generated project
491502
run: |
492503
cd "$RUNNER_TEMP/canary-app"
493504
if node -e "process.exit(JSON.parse(require('fs').readFileSync('package.json','utf8')).scripts?.validate ? 0 : 1)"; then
494505
npm run validate
495506
fi
496507
npm run build
508+
if node -e "process.exit(JSON.parse(require('fs').readFileSync('package.json','utf8')).scripts?.test ? 0 : 1)"; then
509+
npm test
510+
fi
497511
498512
# Same two guards, same reasoning, as `Boot from the artifact and probe
499513
# health` in scaffold-local — see the measurement written out there. Kept

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

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import Link from 'next/link';
33
import { ArrowRight, Check } from 'lucide-react';
44
import { Bricolage_Grotesque, IBM_Plex_Mono } from 'next/font/google';
55
import { HomeLayout } from 'fumadocs-ui/layouts/home';
6-
import { baseOptions, gitConfig } from '@/lib/layout.shared';
6+
import { baseOptions, ESSAY_URL, gitConfig } from '@/lib/layout.shared';
77
import { absoluteUrl, HERO_COVER } from '@/lib/site';
88
import {
99
APACHE_2_0_URL,
@@ -16,6 +16,7 @@ import {
1616
type JsonLdNode,
1717
} from '@/lib/structured-data';
1818
import { YouTubeEmbed } from '@/components/youtube-embed';
19+
import { OntologyRunsFigure } from '@/components/figures';
1920

2021
/** The 90-second overview — the same video the README's hero cover links to. */
2122
const OVERVIEW_VIDEO_ID = 'CX_FlOoOtr0';
@@ -122,6 +123,13 @@ function homeGraph(): JsonLdNode[] {
122123
];
123124
}
124125

126+
/** Section 00's three sentences, verbatim from the maintainer's ruling. */
127+
const IDEA: string[] = [
128+
'An enterprise is definition plus execution. When execution is nearly free, it is its definition.',
129+
'What the model lacks is not intelligence: a world, hands, and a fence.',
130+
'An ontology written clearly enough is software. Models change; the ontology stays.',
131+
];
132+
125133
const VOCABULARY: { tag: string; title: string; copy: string }[] = [
126134
{ tag: 'object', title: 'Objects & fields', copy: 'Typed schemas with relations, validation, formulas, and files.' },
127135
{ tag: 'permission', title: 'Permissions', copy: 'RBAC plus row- and field-level security, enforced by the runtime.' },
@@ -223,6 +231,15 @@ export default function HomePage() {
223231
metadata small enough for AI to hold whole: agents read it whole, reason it whole,
224232
refactor it whole.
225233
</p>
234+
<p className="mt-5">
235+
<Link
236+
href={ESSAY_URL}
237+
className="inline-flex items-center gap-1 font-medium text-fd-foreground underline underline-offset-4 transition-colors hover:text-fd-primary"
238+
>
239+
Why the ontology is the software
240+
<ArrowRight className="size-4" />
241+
</Link>
242+
</p>
226243
<div className="mt-8 flex flex-wrap items-center justify-center gap-3">
227244
<Link
228245
href="/docs/getting-started"
@@ -306,6 +323,40 @@ export default function HomePage() {
306323
</p>
307324
</section>
308325

326+
{/* ── 00 · the idea ────────────────────────────────────── */}
327+
<section className="relative mx-auto w-full max-w-6xl px-6 py-16 md:py-20">
328+
<div className="flex items-baseline gap-4 border-t border-fd-border pt-8">
329+
<span className="text-xs tracking-[0.18em] text-fd-muted-foreground uppercase" style={{ fontFamily: 'var(--l-mono)' }}>
330+
00 · The idea
331+
</span>
332+
</div>
333+
<h2 className="mt-4 max-w-2xl text-3xl font-bold tracking-tight md:text-4xl" style={{ fontFamily: 'var(--l-display)' }}>
334+
An ontology written clearly enough runs directly
335+
</h2>
336+
{/* The figure's caption takes its spacing and colour from the blog's prose
337+
styles there; this page has no prose wrapper, so the wrapper sets them. */}
338+
<div className="mx-auto mt-10 max-w-4xl [&_figcaption]:mt-3 [&_figcaption]:text-sm [&_figcaption]:text-fd-muted-foreground">
339+
<OntologyRunsFigure caption="The ontology is not the design document for the software; the runtime runs it directly." />
340+
</div>
341+
<div className="mt-10 grid gap-8 md:grid-cols-3">
342+
{IDEA.map((line) => (
343+
<div key={line}>
344+
<div className="h-px bg-fd-border" aria-hidden />
345+
<p className="mt-4 text-lg font-semibold text-pretty" style={{ fontFamily: 'var(--l-display)' }}>
346+
{line}
347+
</p>
348+
</div>
349+
))}
350+
</div>
351+
<Link
352+
href={ESSAY_URL}
353+
className="mt-8 inline-flex items-center gap-2 text-sm font-medium underline underline-offset-4 transition-colors hover:text-fd-primary"
354+
>
355+
Read the essay
356+
<ArrowRight className="size-4" />
357+
</Link>
358+
</section>
359+
309360
{/* ── 01 · the loop ────────────────────────────────────── */}
310361
<section className="relative mx-auto w-full max-w-6xl px-6 py-16 md:py-20">
311362
<div className="flex items-baseline gap-4 border-t border-fd-border pt-8">
@@ -455,14 +506,7 @@ export default function HomePage() {
455506
The ontology is the software. Your objects, relations, actions, permissions, and
456507
flows are your business ontology — views, dashboards, apps, and translations are
457508
projections of it, and the definition layer of the AI era should be an open
458-
protocol you own. Read the long form:{' '}
459-
<Link
460-
href="/blog/the-ontology-is-the-software"
461-
className="inline-flex items-center gap-1 font-medium text-fd-foreground underline underline-offset-4 transition-colors hover:text-fd-primary"
462-
>
463-
The Ontology Is the Software
464-
<ArrowRight className="size-3.5" />
465-
</Link>
509+
protocol you own.
466510
</p>
467511
<p className="mt-3 text-sm text-fd-muted-foreground">
468512
ObjectStack is build &amp; ask with Claude Code. Rather build &amp; ask online —

‎apps/docs/lib/layout.shared.tsx‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ export const gitConfig = {
1212
branch: 'main',
1313
};
1414

15+
/** The essay's route: the nav's `Why` entry, and the home page's hero link and section 00. */
16+
export const ESSAY_URL = '/blog/the-ontology-is-the-software';
17+
1518
export function baseOptions(): BaseLayoutProps {
1619
return {
1720
nav: {
@@ -24,8 +27,11 @@ export function baseOptions(): BaseLayoutProps {
2427
),
2528
},
2629
// Every layout spreads baseOptions() — DocsLayout (docs sidebar), and HomeLayout on
27-
// the home page and on /blog — so this one entry is the blog's link on all of them.
28-
links: [{ text: 'Blog', url: '/blog', active: 'nested-url' }],
30+
// the home page and on /blog — so these entries are the nav's links on all of them.
31+
links: [
32+
{ text: 'Why', url: ESSAY_URL, active: 'url' },
33+
{ text: 'Blog', url: '/blog', active: 'nested-url' },
34+
],
2935
githubUrl: `https://github.com/${gitConfig.user}/${gitConfig.repo}`,
3036
};
3137
}
9.62 KB
Binary file not shown.

‎content/docs/getting-started/your-first-project.mdx‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ my-app/
8989
├── objectstack.manifest.json # name, namespace, specVersion
9090
├── package.json
9191
├── tsconfig.json
92+
├── note.test.ts # the starter test `npm test` runs
93+
├── vitest.config.ts # its test-runner config
9294
├── AGENTS.md # conventions for coding agents
9395
└── src/
9496
├── objects/
@@ -232,6 +234,7 @@ framework surface you install:
232234
| `@objectstack/plugin-hono-server` | The HTTP server that mounts the generated REST API. |
233235
| `@objectstack/connector-rest` · `-openapi` · `-mcp` | The three generic connector executors — register the `rest` / `openapi` / `mcp` provider factories so declarative `connectors:` entries materialize ([ADR-0097](/docs/automation/flows)). |
234236
| `@objectstack/cli` *(dev)* | The `os` / `objectstack` CLI: `dev`, `validate`, `build`, `start`. |
237+
| `@objectstack/verify` *(dev)* | The test harness: `bootStack` boots this app's own stack in-process, on an in-memory database and with no server port, and returns the handle a test asserts through. `npm test` runs the starter test `note.test.ts` with it. |
235238

236239
No driver package is listed because none is installed directly: `@objectstack/runtime`
237240
depends on `driver-sql`, `driver-sqlite-wasm` and `driver-memory`, and the CLI carries

0 commit comments

Comments
 (0)