Skip to content

site: non-blog copy sweep — canonical ObjectOS strings, scoped existing-systems claim, approvals path, localized FAQ kicker - #252

Merged
hotlong merged 48 commits into
mainfrom
claude/pm-dispatch-objectos-ju9td1
Oct 15, 2026
Merged

hotlong merged 48 commits into
mainfrom
claude/pm-dispatch-objectos-ju9td1

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #222

Branch. This PR carries 4 commit(s) from the seat's linear stack, replayed onto main @ 78d820e. The replay is patch-equal to git diff 2fcd27a lin/issue-222, with byte-identical diffs. Head: fe39b43. The staged SHAs quoted below are the pre-replay commits; their content is unchanged.

Non-blog copy sweep beyond #209. Every page that says what ObjectStack or ObjectOS is now reads it from src/lib/positioning.ts, every number and capability claim comes from a public source, the "works on your existing systems" claim carries the README's limit everywhere it appears, the agent CTA is the README path, and the capability pages stop answering the same templated FAQ. The three post phrasings carried from the #209 sweep are fixed in all seven authored locales; zh-Hant is regenerated.

Staged on stack/www-wave1 as staged/issue-222 (base 7f69d97d, head dee44d2b), three self-contained commits, replayed with rebase --onto after wave 2 as lin/issue-222 (922605d), plus one follow-up commit on the linear stack, 3a531e1 (head 3a531e1f).

Sources

Facts come from these public sources only; the source codes are used in the tables below.

  • S0 src/lib/positioning.ts — T1, T2, T4, the descriptor and E1 (the objectstack README quoted verbatim).
  • S1 objectstack README at origin/main 6befe19c: "Try it in five minutes" (npm create objectstack@latest, the scaffolder writes an AGENTS.md, npx os dev --ui), "The runtime runs it" (the 12-row capability table), "Agents are the first users" (MCP on by default at /api/v1/mcp), "Why the mistakes don't ship" (the four gates, "apps small enough for AI to hold whole", the bundled examples/app-crm loads end-to-end), "You own it" (Apache-2.0, the CLI os compile / os serve artifact), "Console renders the real app … Studio authors the same metadata visually".
  • S3 objectstack README "What we mean by ontology" + docs.objectos.ai extend-existing-systems: "federating an external datasource is read-only by default and early"; start with a readOnly datasource or a read-only database user and enable writes deliberately, per object; "expect the read path to be the mature one".
  • S4 docs.objectos.ai resources/license: the editions table (in-UI AI from Free as a taste, AI governance from Business, clustering / air-gap / SCIM on Enterprise), "Cloud or self-managed" (Business Self-Managed single node, bring your own model or key; Enterprise full private deployment), "The open-source alternative" (Apache-2.0: protocol, kernel, CLI, production runtime, Console UI, MCP server, no feature gate between development and production), contact sales → sales@objectstack.ai.
  • S5 docs.objectos.ai build/ai-builder: the AI Builder builds packages, objects, fields, actions, flows from plain-language chat; its built-in tools (create_package, create_object, add_field, modify_field, delete_field, …); every metadata mutation routes through a pending-action queue where approvers see the diff; ai:approve is held by Setup Administrator by default and can be scoped per package in a permission set; every approve/reject lands in the audit log with the conversation id.
  • S6 this site's pricing page (src/lib/pricing.ts): Business lists "SSO / SAML".
  • S7 docs.objectos.ai configure/ai: the MCP server ships in the open framework, on by default at /api/v1/mcp; the in-product AI runtime (ask, build) ships with ObjectOS; on Self-Managed a licence unlocks it.

Item 1 — retired token figures

zh-Hans carries the same change on every row (the zh-Hans mirror is in the same file under content/pages/zh-Hans/).

Where Before After Source
ai.ts metric under-150k-tokens tile: "A complete CRM, including UI, held in one context window" Held whole — "Apps small enough for AI to hold whole — the bundled CRM loads end-to-end" S1
ai.ts build copy "A complete CRM is under 150k tokens: business logic … under 100k, and UI metadata adds about 50k." "The definition stays small enough for an agent to hold whole: the objectstack README's bundled CRM — objects, views, a dashboard, a lead-conversion flow, permission sets, actions, translations — loads end-to-end and refactors in one change." S1
agent-developer.ts metrics under-150k / under-100k / about-50k token tiles AGENTS.md "Written by the scaffolder, so the agent starts with the protocol's rules loaded" · 4 gates "Typed, validated, reviewed, governed — between the agent and your users" · Held whole S1
agent-developer.ts workflow item 1 "Under 150k tokens for a full CRM including UI, the app can be inspected as a whole" "The app stays small enough for AI to hold whole — the README's bundled CRM loads end-to-end — so it can be inspected as a whole" S1
forward-deployed-engineers.ts metrics the same three token tiles Apache-2.0 "No seat to resell — the client self-hosts the runtime at no license cost" · 1 artifact "os compile builds one deployable JSON artifact; any ObjectStack runtime serves it" · Held whole S1, S4
forward-deployed-engineers.ts "Security review reads the diff" "under 150k tokens for a full CRM, with business logic under 100k" "small enough to hold whole, permissions and audit included" S1
forward-deployed-engineers.ts checklist "Keep the whole definition under the 150k-token complete-app budget" "Keep the whole definition small enough for your agent to hold whole, so it can reason about and refactor it in one change." S1
platform.ts metric under-150k-tokens tile 4 gates S1
product-tour.ts metrics the same three token tiles 4 gates · 1 definition "Database, REST API, client SDK, UI, and MCP tools all follow from it" · Held whole S1
glossary application-metadata.ts "A complete CRM expressed as metadata fits in roughly 150k tokens, so a coding agent can hold the whole system in one context window" "An application expressed as metadata stays small enough for AI to hold whole — the objectstack README's bundled CRM, objects through translations, loads end-to-end — so a coding agent reasons about the whole system" S1

The product-tour and platform tiles were listed under #209's sweep, but the sweep branch on the stack is blog-only, so they are taken here; otherwise the grep proof below could not read zero.

Item 2 — old descriptors

Where Before After Source
product-tour.ts connect copy "ObjectOS gives teams a production platform for operating apps built on it." "ObjectOS is the commercial runtime environment built on ObjectStack, where teams run and operate the apps built on that model." S0
platform.ts FAQ 1 "part of the open-source ObjectStack target format and runtime" "part of open-source ObjectStack, Apache-2.0: the protocol, kernel, CLI, production runtime, Console UI, and MCP server, with no feature gate between development and production" S4
agent-developer.ts lead "ObjectStack is the open target format and runtime for AI-written enterprise software, designed for the agent as writer and the human as reviewer. A complete CRM … 150k tokens…" T2, then "The agent is the writer and you are the reviewer: the scaffolder writes an AGENTS.md so the agent starts with the protocol's rules loaded, Strict TypeScript, Zod schemas, and os validate reject invalid output before it reaches the runtime, and the whole app stays small enough for AI to hold whole." S0, S1
forward-deployed-engineers.ts description + lead + FAQ 2 "the optional commercial production platform" / "open-source target format and runtime … commercial production platform" the descriptor from positioning.en, with what clients buy spelled out from the licence page (in-app AI Builder and Ask, AI governance from Business, Cloud or self-managed delivery — Business Self-Managed on a single node; Enterprise for clustering, air-gapped operation, SCIM — and official support) S0, S4
src/lib/home.ts kicker, all seven authored locales "For AI-written enterprise software" (and its translations) positioning[locale].t1 — "The ontology is the software." S0
src/glossary/ui.ts heading "The vocabulary of AI-written enterprise software." / "AI 编写的企业软件,用的是这套词汇。" "The vocabulary of an executable business ontology." / "一份可执行的业务本体,用的是这套词汇。" S0 (T2)
src/lib/i18n.ts articles lead "on AI-written enterprise software, runtime governance, and agent-ready applications" "on executable business ontologies, runtime governance, and agent-operated applications" S0
src/lib/terms.ts topic-hub description "with practical notes on AI-written enterprise software" "with practical notes on executable business ontologies" S0 — one line outside the claimed file list, same string family as i18n.ts, added so the grep proof reads zero

Item 3 — "works on your existing systems", with the limit

Where Before After Source
home.ts capability card, seven locales "Extend systems without replacing them — Add APIs, permissions, workflows, and intelligence on top of databases, ERP, CRM, and custom systems." "Extend what already runs — read-only first — Federate a database you already run as an external datasource and model its tables as objects. Federation is read-only by default and early: the system of record keeps writing the rows, and writes are enabled deliberately, per object." S3
home.ts closing copy, seven locales "Connect one existing system, define its key business objects, …" "Federate one database you already run — read-only by default — define its key business objects, …" S3
product-tour.ts connect section title "Add an object layer without replacing systems of record"; item "Map existing records into business objects … without forcing a rip-and-replace migration." title "Add an object layer beside systems of record — read-only by default"; copy "ObjectStack can federate a database you already run as an external datasource … read-only by default, and early; the system of record keeps writing the rows"; item "Connect the database you already run — a readOnly datasource or a read-only database user to start … writes are enabled deliberately, per object, once you trust the model." S3
data-modeling.ts description "federates existing databases in place"; metric "In place — Existing databases federate without migration"; datasource-sync item; table row "A federation definition"; FAQ "Federation can additionally mount tables … without moving the data." "federates a database you already run, read-only by default"; metric "Read-only first — A database you already run federates in place — read-only by default, writes opt-in, and early"; "mount its tables as governed objects, read-only by default; writes are enabled deliberately, per object"; "A datasource binding, read-only to start"; FAQ adds "read-only by default, and early: expect the read path to be the mature one." S3
src/lib/clusters.ts ai-native-app-platform capability "Integration with existing systems so the platform can extend what already runs instead of forcing a migration." "Federation of a database you already run — read-only by default, and early — so the platform can read what already runs instead of forcing a migration." S3
clusters.ts legacy-system-modernization lead, definition, capability 1, FAQ 1 "connect existing systems" / "Connect to existing databases and applications without moving every record first." / "can connect to the existing system of record" "connect existing systems read-only first" / "Connect an existing database as a read-only datasource first — federation is read-only by default, and early" / "read-only by default, with writes enabled deliberately per object … Federation is early: expect the read path to be the mature one." S3
clusters.ts manufacturing-ai capability 1, FAQ 2 "Connect production, inventory, equipment, quality, and work order data as business objects." / "connects existing systems and models the relevant records" "… federated read-only by default from the databases that already run production." / "connects the existing databases as read-only datasources — federation is read-only by default, and early — … so AI can read and analyze" S3
src/lib/cluster-i18n.ts template capability 4 and FAQ 3, zh-Hans / ja / de / es / fr / ko "Connect CRM, ERP, databases and custom systems without forcing a migration." / "Usually not. The safer path is to connect existing systems first…" each locale now says the federation is read-only by default and early, writes enabled deliberately per object S3

Item 4 — the README agent path

Where Before After Source
agent-developer.ts artifact "A good rule file makes the target format explicit" with an invented six-line rule file "The README path — Three commands, and your agent has the rules": the scaffolder installs the AI skills bundle and writes an AGENTS.md; the code block is npm create objectstack@latest my-app && cd my-app, the README's support-desk prompt, npx os dev --ui S1
agent-developer.ts primary CTA "Read the docs" → docs.objectos.ai root "Follow the README path" → https://github.com/objectstack-ai/objectstack#try-it-in-five-minutes S1
agent-developer.ts description "Teach a coding agent the open ObjectStack target format, Strict TypeScript definitions, Zod schemas, validation gate, and whole-context proof…" "The README path for a coding agent: npm create objectstack@latest writes an AGENTS.md, the agent writes typed metadata, os validate rejects what would fail at runtime, and npx os dev --ui renders the app for review." (this line is also the llms.txt entry) S1
ai.ts "Agent writes source files" "Claude Code, Cursor, or any coding agent edits ObjectStack definitions in your repo; review happens as ordinary git diffs." "npm create objectstack@latest scaffolds a project and writes an AGENTS.md, so Claude Code, Cursor, or any coding agent starts with the protocol's rules loaded; it edits ObjectStack definitions in your repo, npx os dev --ui renders them, and review happens as ordinary git diffs." S1
mcp.ts CTAs primary "Compare editions and pricing"; secondary "Read the docs" → docs root primary "Read the MCP docs" → docs.objectos.ai/docs/configure/mcp; secondary "Point your agent at ObjectStack" → /agent-developer/ S7
platform.ts CTAs primary "Compare editions and pricing"; secondary "Take the product tour" primary "Point your agent at ObjectStack" → /agent-developer/; secondary "Compare editions and pricing" AGENTS.md rule 8
home.ts AI link, seven locales "View the AI security model" (the link goes to /agent-developer/) "Point your agent at ObjectStack" — the label now says where the existing link goes; the href is in MarketingHome.astro (markup, held out) —
clusters.ts self-hosted-ai FAQ 4 (new) — ends with "To start: npm create objectstack@latest, point your coding agent at the generated project and its AGENTS.md, then npx os dev --ui." S1

Item 5 — capability pages that repeated each other

Six pages (data-modeling, app-ui, automation, approvals, permissions, analytics) in en and zh-Hans. Each now names the open runtime in its lead, says concretely what ObjectOS adds for that capability, answers a page-specific FAQ instead of the templated one, and opens on a page-specific CTA.

Page Title before → after Lead: "In ObjectOS …" → FAQ "Is X in the open-source ObjectStack? Yes…" → "What is open source here, and what does ObjectOS add?" Primary CTA Source
data-modeling "ObjectOS Data Modeling: …" → "Data Modeling: From Business Objects to Database and APIs" "On the open-source ObjectStack runtime that is one compact object definition … and on ObjectOS the in-app AI Builder drafts the same objects and fields from a plain-language request." "All of the data layer … and the object designer, ER diagram, and datasource sync in the Console — Apache-2.0, with no feature gate between development and production. ObjectOS adds the in-app AI Builder, whose built-in tools create packages and objects and add, modify, and delete fields from a chat, queued for human approval before they go live — on Cloud, Business Self-Managed, and Enterprise." "Read the data modeling docs" → docs/build/data S4, S5
app-ui "ObjectOS App Interfaces: …" → "App Interfaces: Permission-Aware Screens Rendered from Metadata" "On the open-source ObjectStack runtime that is one view definition … On ObjectOS, the in-app AI Builder registers the same views from a plain-language request." "… all ship in the open-source ObjectStack Console, Apache-2.0. ObjectOS adds the in-app AI Builder, which registers views — a kanban grouped by status, for instance — from a chat request and queues the change for approval; the open runtime leaves that work to your own coding agent and MCP client." "Read the interface docs" → docs/build/interface S4, S5
automation "ObjectOS Automation: …" → "Process Automation: Flows, Triggers, Jobs, and Webhooks for Business Processes" "exactly the shape ObjectStack flows are built for … and on ObjectOS drafted by the in-app AI Builder from a plain-language request." (also ¥100k → $100k on the English page, four places) "Flows, all three trigger types, background queues, webhook delivery, and the flow canvas … ship in the open-source ObjectStack Console, Apache-2.0. ObjectOS adds the in-app AI Builder, which drafts flows and actions from a plain-language request and queues every metadata change for human approval, plus AI governance — action audit, guardrails, permission scoping — from Business." "Read the flows docs" → docs/build/automation/flows S4, S5
approvals "ObjectOS Approvals: …" → "Approvals: Multi-Step Sign-Off for Business Changes and AI Actions" unchanged (no edition claim in the lead) "… ship in the open-source ObjectStack Console, Apache-2.0. ObjectOS adds a second thing to approve: the in-app AI Builder's metadata changes land in a pending-action queue where approvers see the proposed diff before clicking Approve — only Setup Administrators hold that permission by default — and every approve or reject lands in the audit log with the originating conversation." "Read the approvals docs" → docs/build/automation/approvals S4, S5
permissions "ObjectOS Permissions: …" → "Permissions: Role, Row, and Field-Level Control for AI-Written Apps" "On the ObjectStack runtime that boundary is one reviewable definition — enforced on every query, API call, and AI tool call, whether the agent is your own over MCP or ObjectOS's in-app AI." Also "ObjectOS never does" → "The ObjectStack runtime never does". "… are all open-source ObjectStack, Apache-2.0, and apply equally to MCP tool access. ObjectOS adds AI governance for its in-app AI from Business — action audit, guardrails, and permission scoping — and makes approving the AI Builder's changes its own permission, one you can scope per package in a permission set." "Read the permissions docs" → docs/configure/permissions S4, S5
analytics "ObjectOS Analytics: …" → "Analytics and Reporting: Dashboards and Reports over Governed Objects" "The ObjectStack runtime runs the same questions over the governed objects themselves … and on ObjectOS the "ask your data" assistant answers them in chat, inside the same permissions." "… are part of the open-source ObjectStack runtime, Apache-2.0 … ObjectOS adds the "ask your data" assistant: a question asked in chat is answered from the same datasets, inside the asking user's permissions, with the query audited and guardrailed from Business. On the open runtime you ask the same questions through any MCP client with your own model." "Read the dashboards docs" → docs/build/interface/dashboards S4

Also in item 5:

Where Before After Source
platform.ts ai-native copy "ObjectOS adds the in-app AI and production experience for teams." "ObjectOS, the commercial runtime environment built on ObjectStack, adds the in-app AI Builder and Ask on top of that open mechanism, with AI governance from Business and hosted or self-managed delivery." S0, S4
platform.ts FAQ 2 "Yes. The open-source ObjectStack runtime can be self-hosted. ObjectOS Enterprise also supports private deployment…" "Yes, three ways. The open-source ObjectStack runtime self-hosts free, with bring-your-own AI over MCP. ObjectOS Business Self-Managed is a licensed single-node deployment that puts the in-app AI on your own infrastructure, bring your own model or key. ObjectOS Enterprise is the full private deployment — a VPC, local servers, or an air-gapped network — with clustering, local models, internal identity, your own secret management, and enterprise support." S4
mcp.ts lead "ObjectOS adds the production operations around that same app." "The MCP server ships in the open framework and is on by default in every edition; ObjectOS is the commercial runtime environment built on ObjectStack, and runs the same server." S0, S7
clusters.ts self-hosted-ai definition and capability 1 "A self-hosted AI application platform lets enterprises run the governed business runtime in their own infrastructure. The model can be local or external…" / "Run the business object layer, tool registry, approvals, and audit logs in controlled infrastructure." adds "Two things answer to that description here: the free, Apache-2.0 ObjectStack framework, and licensed self-managed ObjectOS — Business Self-Managed or ObjectOS Enterprise — which adds the in-product AI on top of it." / "… free on the open-source ObjectStack framework, or licensed as self-managed ObjectOS when you want the in-product AI Builder and Ask on your own infrastructure." S4
clusters.ts self-hosted-ai FAQ 4 (new), and cluster-i18n.ts zh-Hans via a per-cluster EXTRA_FAQ hook — "Is self-hosting free, and what does ObjectOS add?" — the licence page's answer (Apache-2.0 framework with MCP-only AI; Business Self-Managed single node with the in-product AI; Enterprise with clustering, air-gap, SCIM, attestations, SLA), E1's export clause, and the README path as the product CTA in text, because the cluster template has no CTA slot (markup, held out) S4, S0, S1
src/content-pages/ui.ts FAQ heading (all 15 pages) "Questions this page should answer" / "这一页应该回答的问题" "Frequently asked questions" / "常见问题" —

Item 6 — placeholder and copy slips

Where Before After Source
SecurityPage.astro + src/lib/security.ts, seven locales the five section eyebrows printed the h2 under them ("Data residency / Data residency" …) new aiKicker, residencyKicker, permissionKicker, deploymentKicker, faqKicker fields — en "How agents act / Where data lives / Who may do what / Where it runs / FAQ", localized in zh-Hans, ja, de, es, fr, ko; the template prints the kicker field in the existing eyebrow element (five expression swaps, no element, attribute or CSS change) —
SecurityPage.astro + security.ts CTA en and the other Latin/ja/ko locales linked extend-existing-systems-with-ai as "Read how to connect existing systems", zh linked ai-agent-business-data-security-boundaries one target in every locale, the agent security-boundaries article (published in all eight); labels follow: "Read the agent security boundaries article" and its translations —
home.ts AI link see item 4
automation.ts (en) "¥100k" in the lead, section title, item and table row "$100k" —
customer-stories.ts table four rows under a "5 patterns" tile fifth row "Employee service — Policy, Request, Approval, Employee — Employee-data visibility, request routing, policy answers", from the page's own fifth story —
platform.ts metric "25+ Runtime services behind every deployed application" "12 — Typed primitives in the runtime vocabulary, from objects and permissions to AI tools and datasources" (the README's capability table has twelve rows); no public page counts runtime services, so the figure is dropped S1
platform.ts "Designers & console" "16+ open-source ObjectStack admin surfaces — …" "The open-source Console renders the real app and Studio authors the same metadata visually — the object designer, flow canvas, approvals inbox, permission matrix, and audit viewer — …" (no public page counts admin surfaces) S1
forward-deployed-engineers.ts table "AI access" "Platform-native assistants only" (an unsourced claim about Foundry) "Set by the vendor's roadmap" / "Open MCP, on by default — Claude, Cursor, local models" S1

Item 7 — plan details

Where Before After Why this value
ai.ts table — already lists Cloud Free's taste of AI and "Business (Cloud or Self-Managed)" on the stack (#216); unchanged here —
platform.ts FAQ 2 omits Business Self-Managed names all three self-hosting paths (see item 5) S4
trust-center.ts identity row "Project identity and permission metadata" / "SSO, SCIM, admin roles, session policy mapping" — SSO under the Enterprise packet "Project identity and permission metadata; SSO / SAML from ObjectOS Business" / "SCIM and identity lifecycle, admin roles, session policy mapping" — the pricing page (S6) puts SSO / SAML on Business; the licence page (S4) gates SCIM and identity lifecycle on Enterprise and does not gate SSO; the trust center follows both S4, S6
src/lib/pricing.ts Enterprise contact mailto:support@objectstack.ai mailto:sales@objectstack.ai — the licence page (S4) names sales@ for "contact sales" on Enterprise, the Runtime Subscription and OEM terms three times; support@ is the billing and refunds address the refund policy names S4
src/pages/llms.txt.ts pricing line "Open-source ObjectStack and ObjectOS Cloud and Enterprise plans." "Open-source ObjectStack, the ObjectOS Cloud plans (Free, Team, Business), Business Self-Managed, and ObjectOS Enterprise." The FDE and agent-developer lines pick up the new page descriptions. S4

Prices, the annual-billing wording and the plan feature lists in pricing.ts are untouched (#214); the signup and "Start" CTA targets are untouched (#215).

Carried post phrasings (first comment on #222)

Post Before After Locales
ai-ontology-open-protocol line 145 "ObjectOS adds the commercial production experience around the same application — browser-based Build and Ask, …" "ObjectOS, the commercial runtime environment built on ObjectStack, adds browser-based Build and Ask, team review and approval, managed cloud or private deployment, SSO, operations, and support around the same application — …" en, zh-Hans, ja, de, es, fr, ko; zh-Hant regenerated. Line 180 of the card's reading already carried the canonical form on the stack after #209's sweep, so only this sentence moved.
self-hosted-ai-app-platform section heading "ObjectStack Runtime and the Optional ObjectOS Layer" "The ObjectStack Runtime, and ObjectOS Built on It" (and the six translations) all seven
extend-existing-systems-with-ai description and the opening claim "Connect ObjectOS to the database you already run, …" "On self-managed ObjectOS (Enterprise), connect the database you already run — read-only by default — …"; the TL;DR / one-sentence paragraph states the assumption: a self-managed ObjectOS deployment, ObjectOS Enterprise on your own infrastructure, where the database is reachable all seven; de/es/fr descriptions shortened to stay under the 230-char lint ceiling

All three posts already carried updated: 2026-10-06T10:00:00+08:00 from the stack, so no frontmatter date moved.

Approvals: AI metadata changes are their own reviewed path (seat review, commit dee44d2)

The approvals page, the approval-workflow glossary entry and llms.txt said AI-proposed structural changes ride "the same queue" / "the same approvals inbox" as record approvals, which the new approvals FAQ already contradicted. Source: docs.objectos.ai build/ai-builder (objectos origin/main 5d2f837, lines 67–91, "Human-in-the-Loop approval"): tool calls that mutate metadata route through a pending-action queue (/api/v1/ai/pending-actions), a separate queue from flow approval requests; approvers see the proposed diff; ai:approve is held only by Setup Administrator by default and can be scoped per package in a permission set; every approve/reject lands in sys_audit_log with the originating conversation id; it is an ObjectOS (in-app AI Builder) feature, not the open-source console's. The record-approvals inbox is the open-source console's (Apache-2.0). en and zh-Hans; zh-Hant derived.

Where Before After Source
approvals.ts description (:8) "… a three-tab approvals inbox, escalation, and record locking — the same queue that gates AI-written structural changes before they ship." "… a three-tab approvals inbox, escalation, and record locking in the open-source console — and, on ObjectOS, a separate reviewed path for the AI Builder's metadata changes: a pending-action queue with the diff, its own approve permission, and an audit entry." ai-builder.mdx 67–91; license (console is Apache-2.0)
approvals.ts lead (:12) "The schema change an AI just proposed rides the exact same queue." "A schema change the ObjectOS AI Builder just proposed waits the same way — in its own pending-action queue, where an approver reads the diff before it goes live." ai-builder.mdx 67–75
approvals.ts section title (:133) "The same queue that reviews people reviews AI" "People and AI are both reviewed before anything ships" —
approvals.ts section copy (:135) "… structural changes from an agent do not ship — they queue." "… a structural change from an agent does not ship — it waits for a person. On the open runtime that is your git diff; on ObjectOS it is the AI Builder's pending-action queue." ai-builder.mdx 67–69
approvals.ts "Structural changes queue" (:139) "When an agent proposes new objects, fields, flows, or permission changes, the change lands in the same approvals inbox as a compact diff." "When the ObjectOS AI Builder proposes new packages, objects, fields, actions, or flows, the change lands in a pending-action queue — separate from record approvals — where approvers see the proposed diff before clicking Approve; every approve or reject lands in the audit log with the originating conversation." ai-builder.mdx 7–9, 67–75, 89–91
approvals.ts "The diff is the request" (:143) "Reviewers see exactly what would change — schema, authority, automation — not a description of it." + "Approving is its own permission, ai:approve: Setup Administrators hold it by default, and a permission set can scope it per package." ai-builder.mdx 77–87
approvals.ts table row (:158) "The approval queue in front of deployment" "The AI Builder's pending-action queue, in front of deployment" ai-builder.mdx 67
approvals.ts checklist (:167) "AI-proposed structural changes cannot bypass the queue." "AI-proposed structural changes cannot bypass review — the pending-action queue on ObjectOS, the git diff on the open runtime." ai-builder.mdx 67
zh-Hans approvals.ts :8 :12 :133 :135 :139 :143 :158 :167 "…也要过同一个队列" / "走的也是这同一个队列" / "审人的队列,同样审 AI" / "落入同一个审批收件箱" / "部署之前的那道审批队列" / "无法绕过队列" "…都在开源控制台里;在 ObjectOS 上,AI Builder 的元数据变更另走一条受审路径:带 diff 的待审动作队列、独立的批准权限与审计记录" / "在它自己的待审动作队列里,审批人读完 diff 才会生效" / "人和 AI 都要过审,才有东西上线" / "落入一个独立于记录审批的待审动作队列 … 每一次批准或驳回都连同发起它的对话写入审计日志" / "批准是一项独立权限 ai:approve:默认只有 Setup Administrator 持有,也可以在权限集里按包限定范围" / "AI Builder 的待审动作队列,横在部署之前" / "无法绕过审阅——ObjectOS 上是待审动作队列,开源运行时上是 git diff" same
glossary approval-workflow.ts en/zh-Hans (:13) "This is also the queue that AI-proposed structural changes land in — the diff itself becomes the request —" / "这也是 AI 提出的结构性变更所进入的同一个队列——那份 diff 本身就是请求——" "AI-proposed structural changes get a reviewed path of their own: on ObjectOS the AI Builder's metadata changes wait in a pending-action queue where the diff itself is the request and approving is its own permission, ai:approve —" / "AI 提出的结构性变更另有一条受审路径:在 ObjectOS 上,AI Builder 的元数据变更在待审动作队列里等人,那份 diff 本身就是请求,批准是一项独立权限 ai:approve——" ai-builder.mdx 67–87
src/pages/llms.txt.ts (:84) "and a structural change routes to the same approval queue a human change would." "and a structural change it proposes waits for a person too: on open-source ObjectStack as a git diff, on ObjectOS in the AI Builder's pending-action queue, where an approver reads the diff and approving is its own permission." ai-builder.mdx 67–87

Sweep: the same claim grepped over every non-blog locale source (same approvals inbox|same queue|same approval queue|同一个审批收件箱|同一个队列|…) reads 0 after the change; the built output reads 0 across all eight locales; no blog post carries it (0 files), so there is no post-side finding to report. The approvals page was screenshotted again at 1440 and 390 in en and zh-Hans (looked at: the longer "Structural changes queue" card and the new section title fit; 12 overflow probes, 0 overflow).

Security kickers: localized FAQ kicker in ja, de, es, fr, ko (linear-stack follow-up, commit 3a531e1)

A side effect that only shows when #219 and #222 are combined: #219's securityEnglishFields marks any field whose locale value equals the English value as lang="en", and #222 had left faqKicker: 'FAQ' in the five translated locales, so each of /{de,es,fr,ja,ko}/security/ rendered one English eyebrow. The kicker now follows the site's own per-locale FAQ wording (es keeps the cluster label; the others get the plain phrase). The other four kickers were already translated in every locale — a field-by-field comparison of the ja/de/es/fr/ko blocks against en finds faqKicker as the only equal string.

Locale Before After Source
ja faqKicker: 'FAQ' よくある質問 the page's own FAQ wording ("セキュリティレビュー FAQ" heading stays)
de faqKicker: 'FAQ' Häufige Fragen —
es faqKicker: 'FAQ' Preguntas frecuentes src/lib/cluster-i18n.ts es label faq: 'Preguntas frecuentes'
fr faqKicker: 'FAQ' Questions fréquentes —
ko faqKicker: 'FAQ' 자주 묻는 질문 —

Built-HTML check over /{de,es,fr,ja,ko}/security/ at 3a531e1 against the 922605d build: eyebrows with lang="en" 0 on each page (was 1 each); meta robots (noindex, nofollow) and link canonical (/en/security/) byte-identical to 922605d — the pages still inherit other English parts, so their fallback status is unchanged by design. /de/security/ and /ja/security/ screenshotted at 1440 and 390 and looked at; 12 overflow probes, 0 overflow.

Proofs

Source side, non-blog files (content/pages, content/glossary, src/lib, src/content-pages, src/glossary, src/components, src/pages), every locale:

retired token figures  (150k, under-100k, ~50k, 约 50k, 100k tokens, 50k tokens, 15 万 token, 150k-token, roughly 150k)  hits=0
old descriptors        (… target format and runtime|production platform|commercial production|AI-written enterprise software|面向 AI 所写企业软件|商业生产平台|…)  hits=0  (positioning.ts's own comment excluded)
carried post phrasings (commercial production experience|optional ObjectOS|…|Connect ObjectOS to the database you already run)  hits=0 across the three posts, all eight files each
item 6/7 strings       (Questions this page should answer|¥100k|'25+'|16+ …|Platform-native|仅平台自带|support@objectstack)  hits=0

Built output (dist/, 49 non-blog pages per locale × 8 locales): old descriptors 0, item-6 strings 0. The retired-figure grep finds 31 non-blog HTML files, every one of them the business-app-in-16k-tokens post card (its title "A Complete CRM Under 150k" and its description) rendered on three glossary term pages and the locale homes through article cards. That post is blog content and is not touched here: the #209 ruling retired the figures from positioning copy, and a post's own measurement stays, as the #209 sweep recorded ("these are the posts' own measurements"). The authored non-blog copy is clean.

Gates

  • pnpm content:lint (default and --published): ✓ content lint passed (339 files, 44 glossary terms checked).
  • pnpm check: Result (143 files): 0 errors, 0 warnings, 0 hints — VERDICT command-exit 0.
  • pnpm build (gen:zh-hant → content:lint --published → svg:lint → astro build → content:lint --dist): [build] Complete!, ✓ content lint --dist passed (478 built blog pages checked), ✓ content lint --dist links passed (89519 internal links in 880 built pages resolve to built files) — VERDICT command-exit 0.
  • pnpm seo:smoke: SEO smoke test passed (870 HTML pages checked).
  • pnpm svg:lint: svg-text-fit: OK (112 SVGs, 2244 text boxes).

Browser check

Playwright 1.56.1 with the preinstalled Chromium against astro preview of the build: home, product-tour, agent-developer, data-modeling, security × en, zh-Hans. Full-page screenshots at 1440×750 and 390×844 (20 files, looked at); horizontal-overflow probe at all six AGENTS.md viewports (1366, 1440, 1512, 1920, 390, 360): 60 probes, 0 with document wider than the viewport, all 200. The artifact code blocks scroll inside their own box at phone width, as before. No copy was shortened for layout and no CSS was touched.

Held out, on purpose


Generated by Claude Code

🤖 Generated with Claude Code

https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr

claude added 30 commits October 5, 2026 12:39
… definition post (slot 2026-10-08)

Post 1 of the positioning wave: the executable business ontology defined
from the objectstack README and the Business Ontology concepts page,
quoted rather than paraphrased — the six-kind core mapped onto the
bundled CRM example, the four projections with a removal test to tell
core from projection, the three things it is not, where code goes, and
the four promises led by Executable. ObjectOS appears only as the
canonical T4 + E1. Eight locales (zh-Hant generated by gen:zh-hant), one
cover and two diagrams, in English text and in Chinese text for the
zh-Hans/zh-Hant pair.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
The post was written for the Thursday 2026-10-08 slot and dated for it,
but it was never merged that day, so the slot passed with the post still
unpublished. Under the publishing-cadence rules in AGENTS.md, a post's
`date` is its planned slot and merge time is publish time; the
forward-only rule protects the dates of posts that are already live,
and this one is not. The seat therefore moves it to the next free slot,
today, Thursday 2026-10-15, and merges it today.

Only the `date` frontmatter line changes, in the seven hand-maintained
locales (en, zh-Hans, ja, de, es, fr, ko), bare-date format kept and no
`updated` added; zh-Hant is regenerated from zh-Hans with
`pnpm gen:zh-hant`. No other file changes.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
Certification, AI governance tiers, MCP setup, templates, Cloud data,
Business Self-Managed, and two locale overstatements, each re-sourced
from objectos and objectstack docs at origin/main:

1. Enterprise card: "SCIM + compliance (SOC 2 / ISO)" -> "SCIM, compliance
   attestations" (license.mdx:72-73; security.mdx:142 says ObjectOS itself
   is not certified).
2. AI page: governance claims written per edition (license.mdx:42-43):
   a taste on Free, in-app AI on Team, AI action audit / guardrails /
   permission scoping from Business. The "0 bypasses" tile, the Ask audit
   item, the "never how it is governed" item, the edition table and the
   two FAQs now say that.
3. MCP page: the npx/--url snippet (no bin, no such flag) is replaced by the
   documented `claude mcp add --transport http` command and the
   `{"type":"http","url":".../api/v1/mcp"}` client config
   (connect-mcp.mdx:26-32, 85-94).
4. Templates page: the real eight-template catalog (todo, contracts,
   procurement, compliance, helpdesk, content, hr, project;
   templates.mdx:19-28) replaces the six invented starters; the decision
   table now carries the documented install / fork / publish paths
   (templates.mdx:33-62). llms.txt follows the page description.
5. Trust center: the lead quotes E1 (Cloud / self-managed split), the
   "Self-hosted" tile becomes "2 editions", the data-residency and
   licence-check items follow index.mdx:63-77 and license.mdx:150-152,
   and the model-training FAQ says only what the Privacy Policy says
   (legal.ts:414-415).
6. Pricing: Business Self-Managed (single node, 10-25 AI seats,
   $540 / AI seat / yr, bring your own model; license.mdx:41, 64-69) is a
   labelled option under Business, the billing note no longer says
   self-managed is Enterprise-only, and the Business governance line names
   the AI governance row. Team/Business headline prices untouched (#214).
7. zh home tile "数据不出网" -> "数据驻留" (= en "Data residency"); ja
   security tile "0 既定の外部テレメトリ" -> the en "Agent inherits user
   identity" tile.

zh-Hans changed with en; zh-Hant for marketing pages and pricing derives
at build time (registry deepS2t / toHant), so no generated file changes.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
Article tables on a phone no longer run each row onto one line. Cells
wrap, each column keeps a 6.5rem floor (three columns fit the 328px
column of a 360px phone), and overflow-wrap: anywhere keeps an
overflowing table at exactly its column count x 104px. The widest
English table goes from 2,035px to 328px at 360x800; the 12 tables with
four to six columns still scroll, to at most 625px, with the always-on
scrollbar and trailing-edge fade they already had.

Code blocks get the same two scroll affordances at every width, since
they overflow at desktop sizes too. The marketing decision table drops
its 760px floor on a phone and takes the same wrap, floor and
affordances.

Every scroll container is keyboard-reachable and named: post tables get
tabindex="0" and aria-labelledby their section heading (the article
title when no heading precedes them); code blocks keep Shiki's tabindex
and gain role="group" with a "Code (lang)" name; the decision-table
wrapper becomes a named, focusable group. group, not region, because a
region is a landmark and duplicate names fail axe landmark-unique.

Body images become links to their own asset: one tap opens the diagram
in the browser, where pinch zoom takes its smallest text past 11px. The
link's name is the image's alt text. No SVG changes; desktop geometry of
tables and images is unchanged.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
The marketing artifact code block is a scroll container; it gets
tabindex="0" and role="group" named by its section heading, like the
other scroll containers, which clears the last axe
scrollable-region-focusable finding on the marketing pages.

The security page's data-residency table had the same 620px floor as the
decision table, which put its third column ("Leaves boundary?") wholly
off-screen on a phone. It takes the same phone block: cells wrap, a
6.5rem column floor, tighter padding, type size and tracking, and the
scrollbar and trailing-edge fade if it still overflows. Its wrapper is
focusable and named by the section heading. Desktop is unchanged.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…e build

Six visible defects from the 2026-10-06 site audit, plus the gate that
would have caught the broken links.

- Home "Latest insights": the cover's height attribute became CSS
  height:500px and beat aspect-ratio, cutting each 16:9 cover to a 344x500
  sliver. height:auto, a 16:9 box (the covers' own ratio), contain for the
  odd 16:10 cover.
- Blog index: the feature card spanned three rows beside three cards
  (~1470px) with ~670px of content, leaving an ~800px band. It now takes
  its own row, cover beside text from 1024px, the other three below.
- 404: a top-level 404.html (Cloudflare Pages otherwise runs the site as
  an SPA and answers every missing URL 200 with the language redirect) and
  a localized one per locale at <locale>/404.html, moved there by a small
  build hook. noindexed, no canonical, out of the sitemap.
- Cluster pages linked topic hubs not built in de/es/fr/ja/ko. They now
  link the hub really built (this locale's, else English), and
  content-lint --dist fails on any internal link to a page the build did
  not produce.
- Footer links and the drawer's close label come from the i18n copy.
- Alignment: .item > span; align-content:start on capability cards and
  next-page links; no min-height floor on template/compare cards; pricing
  cards on a subgrid; footer brand nowrap; the phone rule now clears the
  odd capability items' border; non-breaking space before card arrows.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
The browser check of the German drawer showed a visible English "Theme"
button beside the localized menu, the same defect as the footer links and
the drawer's "Close" label: hard-coded English in the shared chrome. The
theme toggle's name and tooltip, the language switcher's "Language:"
prefix, the three nav landmark names and the home page's topic-cluster nav
name move into the i18n copy for all eight locales. English output is
byte-identical.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…e to 4.5:1, fix three near-misses

The 11 text lines the audit measured running past their cards, now inside
them with room left for wider system faces:

- self-hosted-runtime-en: the Runtime and AI Agent subtitles and the
  "Only approved minimum context is sent" line wrap to two lines (the
  Runtime line no longer runs over the green connector).
- ai-wrote-your-app-dare-to-merge cover (en and zh): the two captions beside
  the Merge pills wrap to two lines centred on the pill; "+8,142 -0" is
  right-aligned 22px inside its panel in both locales.
- ai-ontology-open-protocol cover-en: the two dark pills widen to 264px and
  the lock moves to the panel's lower right.
- tool-call-lifecycle-en: card 1 rewrapped ("change as Zhang, not as / an
  anonymous superuser").
- natural-language-to-app-metadata cover (en and zh): the spec card widens
  to 350px with 282px pills and sits 14px lower, so it no longer clips the
  subtitle's descenders; the arrows shorten to keep their gaps.
- one-definition-four-surfaces-en: the audit line is reworded to fit.

Contrast: defect-layers-en .none goes from #8a9c95 (2.7:1) to the house
.soft #5d736b.

Near-misses: review-gate-doors-en gives the gap card 12px more height and
the 29/16 numerals 6px more air; self-hosted cover paints the ObjectOS label
after the connectors (both locales), as the runtime diagram already does;
review-loop centres its card row (40px each side, both locales) so card 4's
shadow is no longer clipped.

Also objectos-automation-engine cover-en: the run's meta line ran 40px
under the "selected" tile, hiding "runAs: user"; it drops the "duration:"
label to clear the tile.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…their alt text

Four English and five zh-Hans posts used AI-illustration webp images whose
alt text was only "Concept diagram" / 「概念示意图」. Each alt now says what
the picture shows (customer hub, read-only connection into a governed object
layer, tangled vs. modelled architecture, factory systems feeding an object
layer, legacy stack to structured model). The images are unchanged.
zh-Hant regenerated with pnpm gen:zh-hant.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…ed over it

scripts/svg-text-fit.mjs measures every <text> in content/blog's SVGs and
fails when its box runs more than 2px past the filled shape under its centre,
or when a filled shape painted after it covers more than 2px of it both ways.
The #193 sweep only caught overflow that also dropped contrast, and a
tightest-rectangle measure sees an overflowing line's whole background as
its container; this gate measures each line against the shape it sits on.

It runs without a browser, since CI installs none: text is laid out with
Arial advance widths (scripts/lib/svg-text-metrics.mjs, generated from the
metric-compatible Liberation faces by scripts/gen-svg-text-metrics.mjs) and
the browser's text-box rules. Against headless Chromium on the same files
it agreed to within Chromium's whole-pixel rounding and found the same
crossings. A deliberate crossing goes in the documented allowlist; a stale
entry fails the gate.

Wired into `pnpm build` after content-lint --published (so CI's Build step
runs it) and exposed as `pnpm svg:lint`.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
Measured with DejaVu Sans (the default sans-serif on Debian/Ubuntu
desktops, about 15% wider than Arial), two of the refitted lines still ran
past their shapes: the spec pills on the natural-language cover widen to
290px (both locales), and tool-call-lifecycle card 1 keeps its original
words, rewrapped as 'change as "Zhang"—not / an anonymous superuser'.

The gate's header states its Chromium agreement as "2px", not "2.0px".

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…E1 attribution across 18 posts

Post-side update sweep for the 2026-10-03 positioning (work-order item 3):

- CTA: the stale `npm i -g @objectstack/cli && os start` block in 15 en
  posts and their authored locales becomes the objectstack README path —
  `npm create objectstack@latest my-app && cd my-app`, point a coding
  agent at it, `npx os dev --ui`. `how-to-self-host-objectstack` keeps
  `os start` (the production server), as does the mid-article operator
  line in `enterprise-ontology-race-open-vs-closed`.
- Descriptor: "the commercial production platform" / 「生产平台」 and the
  ja/de/es/fr/ko equivalents in ai-ontology-open-protocol,
  beyond-agentforce-copilot-open-runtime,
  enterprise-ontology-race-open-vs-closed, forward-deployed-engineer-tools
  and self-hosted-ai-app-platform become positioning[locale].descriptor
  verbatim ("the commercial runtime environment built on ObjectStack").
- Attribution: extend-existing-systems-with-ai scopes "ObjectOS runs in
  your environment" to self-managed (ObjectOS Enterprise) or the open
  runtime per E1 and objectos license.mdx (Cloud operates the model);
  objectos-action-tools attributes the Action mechanism (opt-in exposure,
  confirmation, permissions, audit) to the open ObjectStack runtime and
  positions ObjectOS with the descriptor. objectos-agent-permission-
  boundaries already attributed everything to ObjectStack; untouched.
- `updated: 2026-10-06T10:00:00+08:00` on every corrected file; no `date`
  touched, no slug changed. zh-Hant regenerated with gen:zh-hant.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…ullet matches the FAQ

The bullet ended "Business data and prompts never leave your perimeter",
which is false once an external AI provider is configured — the FAQ
answer in the same post already says so. It now states the same claim
as the FAQ: business data stays in your database; prompts go only to the
AI provider you configure (on ObjectOS Cloud the model is operated for
you); permissions decide what the model sees. en and zh-Hans carried the
false sentence; ja, de, es, fr and ko had no sentence about prompts and
now carry the corrected claim too. zh-Hant regenerated.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…re menus, tap targets

Contrast. --muted is #626c66 light (5.43:1 on --surface) and #8a968d dark
(5.42:1); the light accent moves to #3d58f0 (4.67:1 on --surface-sunk);
the focus ring is the solid accent (5.44:1 light, 7.06:1 dark) instead of
a 28% tint (1.5:1). The home, evidence, security, glossary and cluster
accents get dark-theme overrides (green #3fbf8f, blue #8fa3ff, amber
#e3a54a, red #f08a8a, all >= 6.9:1 on the dark surface) and the light
amber darkens to #93600f (5.34:1). The status pill reads theme tokens
(published 5.03:1 light, 6.19:1 dark on its tint), the pricing Team CTA
is --surface on --accent (7.06:1 dark, was 2.36:1), and code blocks use
Shiki's github-dark-default (comments 6.15:1, were 3.04:1).

Keyboard. The mobile drawer moves focus to its close button, makes the
page behind it inert, keeps Tab inside it and returns focus to the menu
button on close. The header dropdowns drop role=menu/menuitem for the
disclosure pattern (aria-controls, plain links, Escape returns focus,
focus leaving a menu closes it).

Tap targets. The hamburger, drawer close, header CTA and wordmark are
44px tall below 900px; footer links 24px (44px on a phone); the glossary
and post back links 44px without moving the text; card title links cover
their cards.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…posts

`[ObjectOS](https://www.objectos.ai)` in business-app-in-16k-tokens (1 per
locale) and forward-deployed-engineer-tools (2 per locale) pointed at the
root language-redirect page, so every click took a meta-refresh hop before
landing. Each locale now links its own home (`/en/`, `/de/`, ...); zh-Hant
regenerated with gen:zh-hant (it rewrote `/zh-Hans/` to `/zh-Hant/`). 24
links in 16 files; no `date` or `updated` touched — a link target is not a
substantive revision.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…hubs, OG

The site-wide audit on #219 measured, on a build of this tree's base:

- Sitemap: 190 noindexed fallback pages listed. The sitemap filter now
  reads each built page and lists it only when it is indexable and its own
  canonical (astro.config.mjs) — one rule for every fallback producer
  instead of a second copy of each registry.
- Pricing/privacy/terms/refunds in ja/de/es/fr/ko rendered the English
  copy, self-canonical and in every locale's hreflang (20 pages); security
  in those five locales inherits five English sections. They now follow
  the marketing fallback rule through src/lib/fallback.ts: noindex,
  canonical to /en/, hreflang only for authored locales, the switcher
  still offering every locale. Authored-ness is derived, not listed: a
  copy table entry that is the English object is a fallback, and a
  security locale that inherits any English field is one.
- Base takes `contentLocale`: a fallback body gets `<main lang="en">`
  (marketing, glossary, pricing, legal; 210 pages had English under
  lang="de"/"ja"/...), og:locale follows it. The security page marks each
  inherited English part `lang="en"`; glossary fallbacks mark the
  reader-locale article links with their locale.
- The 35 non-English cluster pages are one template per locale: noindex,
  canonical to the English cluster, out of hreflang and llms.txt.
- Pillar pages: every footer links the five clusters (FooterClusters),
  and each post a cluster lists as its reading path links back to it.
- Topic hubs listing fewer than 3 posts (51) are noindexed and out of
  hreflang; every hub gets BreadcrumbList and ItemList JSON-LD and a
  "<topic> · Articles · ObjectOS" title.
- Root `/`: absolute hreflang, canonical /en/, no noindex beside the
  meta refresh. SecurityPage links its post with the trailing slash.
- public/_headers caches the hashed /_astro/* files as immutable.
- og:locale uses en_US/zh_CN/zh_TW/...; og:image:alt everywhere and
  width/height when known; Organization gets @id and sameAs (the GitHub
  organization only); SoftwareApplication gets offers from the pricing
  page's own dollar plans.
- Titles: marketing titles that already name ObjectOS no longer append
  " · ObjectOS" (27 indexable doubles); the blog index takes the home
  page's heading for it instead of "Articles · ObjectOS".

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
… lint warns at 60/160

seo:smoke reads every built page once and asserts invariants over the
whole build instead of per-registry URL lists, so a new fallback producer
that gets the rule wrong fails without this script learning about it:

1. every sitemap URL is a built, indexable, self-canonical page;
2. every canonical and hreflang target is indexable and self-canonical,
   and a page that canonicalizes elsewhere is noindexed;
3. every block of prose in <main> is written in the language its nearest
   `lang` declares (scripts/lib/text-lang.mjs: script for CJK, distinctive
   stopwords for en/de/es/fr; too little text is "cannot tell", never a
   pass), with a per-locale control so it cannot measure nothing;
4. no internal link, canonical, og:url or hreflang redirects (missing
   trailing slash, `/`, `.../index.html`, a `_redirects` source).

Plus the root page (canonical /en/, absolute hreflang, no noindex beside
the refresh), `_headers` (rule present, every /_astro/ name hashed),
og:locale values, og:image:alt, the Organization @id/sameAs, hub
BreadcrumbList/ItemList, and the clusters: English in llms.txt and the
sitemap, the templated locales noindexed and canonical to English.

On the base build this reports 190 noindexed sitemap URLs, 215 pages
whose <main> is in another language than declared (25 indexable), and 9
redirecting link targets on 24 pages; on this branch's build it passes.

content-lint: a title wider than 60 columns or a description wider than
160 (CJK counted double) is a warning. Posts first published before the
check are counted in one summary line; `--snippets` lists them all.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…uled

The home page's SoftwareApplication listed Offers derived from the pricing
page ($20/$45 per AI seat billed annually). That price is under a pending
maintainer decision (www#214: $20/$45 annual on www vs $24/$54 monthly on
docs), so structured data must not assert either until it is ruled. The
rest of the item-10 changes stay: the publisher still references the
Organization @id.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…ith srcset

The post header cover is the LCP element on a phone, and 80 posts used a
1.5-1.9 MB PNG for it (ai-agent-workbench: 9.96 s LCP at 390x844, 4x CPU,
slow 4G). Card thumbnails on the blog index, topic hubs and the home page
downloaded the same full-size file, about 18.8 MB for one scroll of
/en/blog/.

A PNG or JPG cover now goes through <Image> as WebP with a width list and
`sizes` per slot:

- post header: 480-1600w, `(min-width: 908px) 860px, calc(100vw - 2rem)`;
  still eager with fetchpriority=high, since it is the LCP element;
- ArticleCard: 360-1280w, sized for the three-column grid, the two-column
  tablet grid and the phone column; the feature card's cover is eager, as it
  opens the blog index above the fold;
- the home page's latest-posts grid: 360-1080w.

The ai-agent-workbench cover at 1280w is 58 KB (the original is 1.62 MB);
a 1:1 crop of the 1280w WebP against the original resized to 1280 shows no
visible loss (PSNR 39 dB). SVG covers are a few KB and scale themselves, so
they keep a plain <img> on the original.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
209 posts (41 English) named an SVG as og:image, twitter:image and
BlogPosting.image, and X, LinkedIn, Facebook and Slack draw no card for an
SVG; the PNG-cover posts named a 1.5-1.9 MB 1672x941 file.

`scripts/og-images.mjs` runs in `pnpm build` after `astro build` and
renders one 1200x630 JPEG per cover into dist/og/blog/<post>/. The URL rule
lives in scripts/lib/og-image.mjs, shared with BlogPost.astro, which now
names that file in og:image, twitter:image and BlogPosting.image, with
og:image:width/height 1200x630 and the post title as og:image:alt. The URL
carries a hash of the cover's bytes, so an edited cover gets a new URL the
networks re-fetch. The step is driven from the built HTML and fails the
build when a page names a share image no cover maps to, or declares a size
that is not the file's.

SVG text: sharp draws it through librsvg/Pango/fontconfig, which use the
machine's fonts. The covers ask for -apple-system, Segoe UI, Inter, Arial
and Menlo, and CI's ubuntu-latest has no CJK font, so the Chinese covers
would have shipped as rows of boxes. The rasterizer now sees only bundled
OFL faces (scripts/lib/og-fonts.mjs): Arimo and Cousine (the Arial and
Courier New metrics `pnpm svg:lint` measures with), Noto Sans SC, Noto Sans
Symbols 2, Noto Sans Math and Noto Emoji. Fontsource ships them as WOFF2,
which the FreeType in sharp's libvips cannot read (every glyph drew as a
box), so wawoff2 decodes them to TrueType in a temp dir that a generated
fonts.conf points at. Before rendering, every character in each SVG cover's
text is checked against the faces' cmaps; one that no face has fails the
build with the cover, code point and line. On this tree that check first
found U+21B6 and U+2298, which is why Noto Sans Math is in the set.

An SVG cover is drawn whole at 1120x630 and widened by repeating its edge
columns (two covers put a caption in the bottom 23px a centre crop would
cut; no cover draws in its outer 40px). A PNG/JPG cover is an illustration
without text and is centre-cropped. Rendering SVG text on several libvips
threads in one process crashed (SIGSEGV or "SVG rendering failed" in 4 of 6
runs), so SVGs are rendered in three worker processes, one at a time each.

Result: 87 images, 5.2 MB, 12 s of the build; zh-Hans, zh-Hant, ja and ko
rasters looked at, no missing glyph.

New devDependencies, all build-time only: wawoff2 (MIT) and six Fontsource
packages (OFL-1.1). sharp was already a dependency.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…, dim it in dark mode

The 1,732,355-byte hero PNG was loaded eagerly by the 120 marketing
evidence pages, where it renders in a 415x340 box, and lazily by the home
page; it was also the default og:image of every page without its own.

- src/assets/objectos-home-hero.png (moved from public/images/) goes
  through <Image> as WebP with srcset (400-1586w). The box crops the 1.6:1
  image with object-fit: cover, so `sizes` asks for the drawn width, not the
  box's: 545px on a desktop, about 1.2x the box on a phone.
- Marketing evidence pages: measured at all six AGENTS.md viewports, the
  hero's top is at 172px on a desktop (the LCP element there) and at
  713-781px on an 800-844px phone, so it is in the first screen everywhere
  and stays eager, now with fetchpriority=high. Home: it sits under the
  hero and the video at every viewport, so it stays lazy.
- Dark theme: the white render was a glaring block on the dark page. There
  is no dark variant of the render, so the image itself is dimmed
  (brightness 0.6, contrast 1.12; three strengths compared side by side in
  the hero panel). No layout CSS changes.
- Base.astro: the default share image was the 1586x992 PNG by its public
  path, which the move removes. It is now getImage() of the same asset, a
  1200x630 JPEG (94 KB). og-images.mjs now checks every og:image that is a
  file of this site against the size its page declares, the default
  included, and fails on one that is not in dist/.

The hero component code no longer reads the import's width, so Astro drops
the 1.7 MB original from dist/ (nothing references it).

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
claude added 18 commits October 15, 2026 12:18
…m at

A standard card's 16:10 thumb crops the 16:9 cover with object-fit: cover,
which draws the image 1.11x the card's width: a 343px card at 1366 needs
381px of image, and `sizes` said 360px, so the 360w candidate was
stretched and visibly soft next to the original. `sizes` now names the
drawn width (400px on a desktop, 55vw - 2rem on the two-column tablet
grid; the phone's 2:1 thumb is drawn at the card's width) and the list
starts at 400w.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
The reading-path cards on the 40 cluster pages still used the original
cover: /en/ai-native-app-platform/ loaded four original JPGs (240-370 KB
each) into 180px-wide slots. A raster cover now goes through Image as
WebP, 400-1080w. `sizes` is the drawn width, the same way as the
ArticleCard thumbs: above 640px the image is a 180px (150px at 641-900px)
column as tall as the card's text, measured 190-227px, and object-fit:
cover fills it by height, so a 1.6:1 or 16:9 cover is drawn 304-364px
wide; `sizes` says 400px. On a phone the card stacks and the 16:9 image
is drawn at the column width. SVG covers keep the plain img.

Only the frontmatter (the import and the width list replacing coverSrc)
and the card markup change; no CSS.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…ns to hold theirs

A filter paints nothing outside its region, and 27 blog SVGs set regions
(x/y -20%, width 140%, height 150%) too small for the shadow they cast:
81 of 181 filtered elements had part of their feDropShadow on the canvas
but outside the region, 35 of them within 1.5σ of the shadow's edge.
The worst left a hard horizontal edge, a 15-of-255-level step, across
the shadow just under its card.

svg-text-fit gains rule 3: for every filtered element it takes the object
bounding box, the painted box, and the shadow's offset and blur, and fails
unless the shadow's 3σ reach lies inside the filter region wherever it
lands on the canvas. Anything but a single feDropShadow fails as
unmeasurable. Red on stack/www-wave1 (81 cut shadows in 27 SVGs), green
after.

Each cut filter's region now holds the full 3σ reach of every element
that uses it, rounded out to 10% and only ever grown. Headless Chromium
renders every filtered SVG identically to the same SVG with unbounded
regions; 14 SVGs change pixels, all outside the old regions.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
… de, es, fr, ja and ko

The de, es, fr, ja and ko versions of crm-ai-understands-customers,
extend-existing-systems-with-ai, low-code-vs-ai-native-app-platform and
manufacturing-legacy-systems-ai still gave their AI illustrations the
placeholder alt "Konzeptdiagramm" / "Diagrama conceptual" / "Schéma
conceptuel" / 「概念図」 / "개념 다이어그램". Each now carries the
descriptive alt the English and zh-Hans posts got, translated, in the
post's own terms (AI, governte / gobernada / gouvernée objects). The
images are unchanged.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…card

In natural-language-to-app-metadata's cover-en.svg and cover.svg, the
Runtime card's gold cycle ring (radius 90, stroke 5, centre 172px down
the card) reached 264.5px down a 248px card: it ran 16.5px past the
card's bottom edge onto the page background. The card is now 290px
tall, which centres the ring in the space under the card title with
about 26px clear above and below. The ring, the title, the two lines
inside the ring and the card's top edge do not move.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…K characters

A soft line break in a hard-wrapped zh-Hans paragraph reaches the HTML as a
segment break, and Chromium and Safari render it as a space mid-sentence
(CSS Text 3 removes it; those engines do not yet). The built site had 202 of
them: four zh-Hans posts and their generated zh-Hant copies.

A rehype step (scripts/lib/cjk-segment-break.mjs, wired in
`markdown.rehypePlugins`, inherited by MDX) removes a break whose neighbours
are both East Asian Wide/Fullwidth/Halfwidth and not Hangul, which is the
CSS rule itself. Code, pre, kbd and samp are opaque; a break between CJK and
Latin, after an ambiguous-width dash, or between Hangul words stays a space.
The sources stay as written, and every future post in every CJK locale is
covered.

`content:lint --dist` gains the guard: it reads the built HTML with its own
scanner and fails on any segment break left between two such characters in
prose, on every built page, so it also catches the step not running or CJK
prose arriving by a road the step never sees.
A build with no zh or ja page (by `html lang`) fails as unmeasured.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
Astro's smartypants curls an ASCII quote by its neighbours: after a letter it
is a closing mark. Chinese puts no space before a quotation, so the opening
mark of a pair after a character came out closing, 「大多数”给你的业务加上
AI”的说辞」. It also reads the whole document as one string, so a heading or
table cell that starts with a quote took its direction from the block before
it, which is the Korean case. The built site had 664 such marks on 41 pages:
324 in 16 zh-Hans posts, the same in their zh-Hant copies, 15 in 8 ko posts
and 1 in ja.

A remark step (scripts/lib/cjk-quotes.mjs, wired in `markdown.remarkPlugins`,
inherited by MDX) runs after smartypants on zh-Hans, zh-Hant, ja and ko files
only, chosen by file name. In one paragraph, heading or table cell it sets each
mark by its position in its pair: the first opens, the next closes, whether
the source typed it straight or curly. Single marks pair the same way except
an apostrophe (after a Latin letter or digit, or `'26`), which stays ’. Code is
opaque, the marks stay “ ” / ‘ ’ as the sources type them (AGENTS.md records
no zh style), and 「」 is never touched. The sources stay as written and every
future post in those locales is covered; an English, German, Spanish or French
page is not touched (whole-site byte diff: only the 41 pages above change, and
only quotation marks in their prose).

`content:lint --dist` gains the guard: it reads the built HTML of every zh, ja
and ko page with its own scanner and fails on a curly mark whose direction
disagrees with its position, or a pair left open in its block, so it also
catches the step not running or CJK prose arriving by another road. A build
with no such page, or no curly mark on one, fails as unmeasured.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…xport

`pnpm wechat` / `wechat:draft` render through marked, not Astro, so neither
the rehype step that removes a line break between two CJK characters nor the
quote step reaches the export. The four hard-wrapped zh-Hans posts exported
with 20, 40, 23 and 18 such breaks (counted by the `content:lint --dist`
scanner), which the WeChat reader shows as spaces mid-sentence, and marked
leaves ASCII quotes straight.

`toWechatHtml` now walks marked's tokens (`walkTokens`) and, per paragraph,
heading, list-item text and table cell, applies `collapseCjkSegmentBreaks`
with neighbours read across bold, emphasis and links, and `directQuotes` when
the block holds a CJK letter. Code spans, images and raw HTML bound the first
and are opaque to the second; fenced code is never seen. An export of the four
posts now differs from before only by the 101 removed breaks and six straight
pairs curled “ ”, all opening marks first; an English export is byte-identical.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
#226's quote step ran on zh, ja and ko only. The same fault shows in the
Latin locales, because smartypants reads a document as one string: a
heading or table cell that opens on a quote takes its direction from the
block before it, so an English table printed ”What exists in our
business?”, and an elided century came out as Next ‘26. German types
„Wort" and smartypants curls the ASCII closer as the English ”, which German
never prints after „. The built site had 366 such marks on 29 pages: de 344
on 17, en 16 on 7, es 5 on 4 and fr 1 on 1.

The step is now `remarkQuotes` and runs on every Markdown and MDX file. It
keeps #226's rule: in a paragraph, heading or table cell, the first mark of a
pair opens and the next closes, and an apostrophe, including '26, is ’. It
adds one rule with no per-locale branch: a low mark („ or ‚) only opens, and
its pair closes with “ or ‘. The author's choice of mark is kept, so « » and
„ “ stay as typed, and an ASCII pair stays “ ” as smartypants prints it. Code
is opaque, as before.

`content:lint --dist` now reads every built page, not only zh, ja and ko,
with the same rule, including the low-mark pairs. Its summary lists the
pages and marks it read per `<html lang>`. A build with no page, or no
curly mark, fails as unmeasured.

The whole-site byte diff shows exactly those 366 characters on those 29
pages. Each one is a quotation mark replaced by another, outside tags and
outside code. No zh, ja or ko page changes.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…strings, scope the existing-systems claim, README agent path, per-page edition answers

Every non-blog page now reads what ObjectStack and ObjectOS are from
src/lib/positioning.ts and from public sources only (objectstack README
at origin/main 6befe19c; docs.objectos.ai license, extend-existing-systems,
ai-builder and configure/ai pages).

1. Token figures retired on ai, agent-developer, forward-deployed-engineers,
   platform, product-tour and the application-metadata glossary entry, in
   en and zh-Hans. The tiles now carry README facts: AGENTS.md written by
   the scaffolder, the four gates, "apps small enough for AI to hold whole",
   one definition, one compiled artifact, Apache-2.0.
2. Old descriptors replaced with positioning[locale]: product-tour's
   "production platform", platform's "target format and runtime", the home
   kicker (now T1 in all seven authored locales), the glossary heading, the
   articles lead and the topic-hub description in src/lib/terms.ts.
3. "Works on your existing systems" carries the README limit everywhere it
   appears: federation is read-only by default and early; writes are enabled
   deliberately per object. Home (seven locales), product-tour, data-modeling,
   the four cluster texts in src/lib/clusters.ts and the six per-locale
   cluster templates.
4. The README agent path (npm create objectstack@latest, point a coding agent
   at the generated project and its AGENTS.md, npx os dev --ui) replaces the
   invented rule file on agent-developer and is linked from platform, mcp,
   ai and home; the docs-root links become deep links.
5. Capability pages stop repeating each other: six titles drop the "ObjectOS"
   prefix, each lead names the open runtime and what ObjectOS adds, the
   templated "ships in the open-source console" FAQ becomes "What is open
   source here, and what does ObjectOS add?" with a page-specific answer,
   and the hero CTAs vary (docs deep links, the agent path) instead of nine
   copies of "Compare editions and pricing". The self-hosted-ai cluster now
   separates the free ObjectStack framework from licensed self-managed
   ObjectOS, in en and zh-Hans, and names the product path.
6. The FAQ heading reads "Frequently asked questions"; the five security
   section kickers no longer repeat their headings; the home AI link says
   where it goes; $100k on the English automation page; the fifth reference
   story gets its table row; "25+" and "16+" replaced with sourced facts
   (12 README primitives; Console renders, Studio authors); "Platform-native
   assistants only" dropped; the security blog CTA is one article in every
   locale.
7. Plan details: platform's self-hosting FAQ names Business Self-Managed;
   the trust-center identity row puts SSO / SAML on Business (pricing page)
   and keeps SCIM on Enterprise (licence page); the Enterprise contact is
   sales@ (licence page); llms.txt's pricing line lists every delivery.

Prices, the annual-billing wording, the signup and Start targets, layout,
CSS, astro.config.mjs and scripts/lib are untouched.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
Carried here from the #209 post sweep. ai-ontology-open-protocol: "ObjectOS
adds the commercial production experience" becomes the canonical descriptor
("ObjectOS, the commercial runtime environment built on ObjectStack, adds…")
in all seven authored locales. self-hosted-ai-app-platform: the section
heading no longer calls ObjectOS "the optional operations layer"; it names
ObjectOS as built on the runtime. extend-existing-systems-with-ai: the
description and the opening claim state the E1 assumption — a self-managed
ObjectOS deployment (Enterprise, on your own infrastructure) where the
database is reachable — and the read-only default. zh-Hant regenerated.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
… the record-approvals inbox

The approvals page, the approval-workflow glossary entry and llms.txt said
AI-proposed structural changes ride "the same queue" / "the same approvals
inbox" as record approvals. docs.objectos.ai build/ai-builder (objectos
origin/main 5d2f837, "Human-in-the-Loop approval") describes something else:
tool calls that mutate metadata route through a pending-action queue
(/api/v1/ai/pending-actions), approvers see the proposed diff, ai:approve is
held by Setup Administrator by default and can be scoped per package, and
every approve or reject lands in the audit log with the conversation id. It
is an ObjectOS (in-app AI Builder) feature; the record-approvals inbox is the
open-source console's.

The page now says both: the inbox is what it is, and the AI Builder's changes
wait in their own pending-action queue (diff, ai:approve, audit). The section
"People and AI are both reviewed before anything ships" keeps the idea without
claiming one shared queue. en and zh-Hans; zh-Hant derived at build. Grep of
every non-blog locale source for the same claim reads zero; no blog post
carries it.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
#222 added five section kickers to the security page and left faqKicker as
'FAQ' in the five translated locales. #219's helper marks any field whose
locale value equals the English value as lang="en", so those pages rendered
one English eyebrow each — harmless now, but it would keep the page counted
as partly English, and noindexed, once the rest of the locale is translated.

The kicker now follows the site's own per-locale FAQ wording: es keeps the
cluster label 'Preguntas frecuentes'; ja, de, fr and ko get 'よくある質問',
'Häufige Fragen', 'Questions fréquentes', '자주 묻는 질문'. The other four
kickers were already translated in every locale.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants