Skip to content

Commit 37a4374

Browse files
committed
Merge origin/main into feat/adr-0091-l2-delegation — resolve proof-registry conflicts
Both this branch and the merged ADR-0057 lifecycle work (#2791) appended a HIGH_RISK_CLASSES entry and extended the BOUND_PROOF_PATHS expectation: kept BOTH ('delegation' → position/delegatable, 'data-lifecycle' → object/lifecycle). proof-registry tests and the spec-liveness gate pass on the merged tree. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BNBzMWmSECrbiEDdVzwBt3
2 parents 6ddf80e + ba2075d commit 37a4374

49 files changed

Lines changed: 3100 additions & 142 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/objectql': minor
4+
'@objectstack/driver-sql': minor
5+
'@objectstack/driver-sqlite-wasm': minor
6+
'@objectstack/platform-objects': minor
7+
'@objectstack/metadata-core': minor
8+
'@objectstack/service-messaging': minor
9+
'@objectstack/service-automation': minor
10+
'@objectstack/plugin-audit': minor
11+
---
12+
13+
ADR-0057 data lifecycle P1–P4 (#2786): platform-generated data is now bounded by construction.
14+
15+
- **P1 — contract**: new `lifecycle` object property (`class: record | audit | telemetry | transient | event` + `retention` / `ttl` / `storage(rotation)` / `archive` / `reclaim`), enforced by the platform-owned **LifecycleService** registered by `ObjectQLPlugin` (default-on; disable via `OS_LIFECYCLE_DISABLED=1` or plugin `lifecycle.enabled=false`). The Reaper batch-deletes rows past `retention.maxAge` / `ttl` under a system context and reclaims space (`SqlDriver.reclaimSpace()` → SQLite `PRAGMA incremental_vacuum`). Non-`record` classes must declare a bounding policy (parse-time invariant + spec-liveness gate + dogfood storage-growth gate).
16+
- **P2 — rotation**: `storage: { strategy: 'rotation', shards, unit }` physically time-shards the table on SQLite — writes land in the current shard, reads go through a UNION-ALL view under the base name, expiry is an O(1) `DROP` of shards past the window. A legacy table is adopted as the first shard on upgrade. Other dialects fall back to an equivalent age-based reap.
17+
- **P3 — separation + Archiver**: registering a datasource named `telemetry` routes telemetry/event/audit objects to it (opt-in by existence; `transient` deliberately stays on the primary). Audit objects with `archive` declared get retain → archive → delete once the archive datasource exists; without it rows are retained, never dropped unarchived.
18+
- **P4 — governance**: new `lifecycle` settings namespace — runtime enable switch, per-object retention overrides (tenant-scoped: regulated tenants set years, dev sets days), per-object/per-class row quotas and growth alerts (observe-and-alert only).
19+
20+
**Behavior change**: 11 platform objects now carry lifecycle declarations and their telemetry is bounded by default — `sys_activity` 14d (rotated), `sys_audit_log` 90d hot → archive (retained forever until an `archive` datasource is registered), `sys_metadata_audit` 365d → archive, `sys_job_run` / `sys_automation_run` / `sys_http_delivery` 30d, notification pipeline (`sys_notification`, delivery, receipt, inbox) 90d, `sys_device_code` expires_at + 1d. Extend windows per environment/tenant via the `lifecycle.retention_overrides` setting.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
"@objectstack/spec": patch
3+
---
4+
5+
Agent capability — open-edition honesty pass (docs + liveness annotation), no
6+
behavior change:
7+
8+
- The `agent`/`skill`/`tool`/`action` liveness files cite
9+
`packages/services/service-ai/...` as evidence, but that tree is a stale,
10+
untracked build artifact — the real runtime is the closed cloud
11+
`@objectstack/service-ai`. Each file's `_note` now says so explicitly, so an
12+
auditor reading the ledger understands these props are `live` because a
13+
CLOUD/EE runtime consumes them and the OPEN framework edition does not.
14+
- Docs (`content/docs/ai`): removed the `aggregate_data` over-claim from
15+
Natural Language Queries — the open MCP surface registers 9 tools and
16+
`query_records` has no aggregation args; `aggregate_data` is a cloud data
17+
tool. And disambiguated the two things called "skill" (authoring `SKILL.md`
18+
modules vs. runtime `defineSkill` agent capability bundles) with cross-linked
19+
callouts on both pages.

.changeset/docs-sms-phone-otp.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
---
3+
4+
Docs-only: `services.sms` runtime-service reference + phone-number
5+
authentication (SMS OTP) section in the authentication guide. No package
6+
changes.

content/docs/ai/agents.mdx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,19 @@ API-key-only, fail-closed.
8585

8686
## You extend the platform with **skills**, not agents
8787

88+
<Callout type="warn">
89+
**"Agent skill" ≠ "authoring skill."** The `*.skill.ts` / `defineSkill` files
90+
here are **agent skills** — runtime capability bundles the `ask` / `build`
91+
agents consume. They are a different thing from the `SKILL.md` **authoring
92+
skills** on the [AI Skills System](/docs/ai/skills) page, which teach a coding
93+
assistant to *write* your metadata and never run. Same word, two layers.
94+
</Callout>
95+
8896
`*.agent.ts` is **closed to third parties** — the `agent` metadata type is
8997
`allowRuntimeCreate:false, allowOrgOverride:false`, reserved for the two platform
9098
agents and platform-owned subagents (ADR-0063 §2). To give the `ask` agent a new
91-
capability you author a **skill** (`*.skill.ts`) whose `tools` reference your
92-
Actions / Flows / queries; it then attaches to `ask`. Every skill declares
99+
capability you author an agent **skill** (`*.skill.ts`) whose `tools` reference
100+
your Actions / Flows / queries; it then attaches to `ask`. Every skill declares
93101
`surface: 'ask' | 'build' | 'both'`, and an agent's tool set is the **union of its
94102
surface-compatible skills' tools** — there is no global fall-through, so a skill
95103
reaches an agent only when their surfaces match

content/docs/ai/natural-language-queries.mdx

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
title: Natural Language Queries
3-
description: How agents query live data through the built-in data tools (query_records, get_record, aggregate_data) under RLS
3+
description: How agents query live data through the open MCP data tools (query_records, get_record) under RLS
44
---
55

66
# Natural Language Queries
@@ -14,15 +14,25 @@ row-level security as the REST API. The data tools and the ObjectQL engine that
1414
back this are part of the open framework — no `@objectstack/service-ai` and no
1515
cloud studio are required.
1616

17-
Your AI queries your data through the built-in **data tools**
18-
`query_records`, `get_record`, and `aggregate_data` — which the model calls with
19-
structured arguments. These run as ordinary [ObjectQL](/docs/protocol/objectql) queries over
17+
Your AI queries your data through the open MCP **data tools**`query_records`
18+
(filter, field selection, sort, pagination) and `get_record`, plus the
19+
discovery tools `list_objects` and `describe_object` — which the model calls
20+
with structured arguments. These run as ordinary [ObjectQL](/docs/protocol/objectql) queries over
2021
your objects (ObjectStack uses ObjectQL, not SOQL), and they execute under the
2122
caller's `ExecutionContext`, so row-level security applies exactly as it does
2223
for the REST API.
2324

25+
<Callout type="info">
26+
**Aggregation is a cloud data tool.** The open MCP surface reads and filters
27+
rows; it does not ship a server-side `aggregate_data` (group-by / roll-up)
28+
tool — `query_records` has no aggregation arguments. A BYO agent aggregates by
29+
fetching the rows it's allowed to see and summing client-side; the built-in
30+
`aggregate_data` tool is part of the cloud in-product chat runtime (below).
31+
</Callout>
32+
2433
The open MCP server plugin exposes these tools automatically — it bridges the
25-
kernel's tool registry, metadata, and data engine to any connected MCP client:
34+
metadata and data engine to any connected MCP client, served per-request at
35+
`/api/v1/mcp` (default-on):
2636

2737
```typescript
2838
import { LiteKernel } from '@objectstack/core';
@@ -34,28 +44,20 @@ await kernel.bootstrap();
3444
```
3545

3646
Point any MCP client at the server and ask questions in natural language: the
37-
model discovers `query_records` / `get_record` / `aggregate_data` and calls them
38-
under RLS as the authenticated caller.
47+
model discovers `list_objects` / `describe_object` / `query_records` /
48+
`get_record` and calls them under RLS as the authenticated caller.
3949

4050
There is no separate natural-language-to-query metadata type to author — the
4151
model is prompted with the available objects and translates the user's question
42-
into `query_records` / `aggregate_data` calls at runtime.
52+
into `query_records` calls at runtime.
4353

4454
<Callout type="info">
45-
**Cloud / Enterprise — bundled in-product chat.** The cloud tier's in-UI AI
46-
runtime (`@objectstack/service-ai`, the `ask` data-query assistant, and the
47-
`/api/v1/ai/*` chat endpoints) registers these same data tools into its chat
48-
loop via `registerDataTools`:
49-
50-
```typescript
51-
import { registerDataTools } from '@objectstack/service-ai';
52-
53-
ctx.hook('ai:ready', async (ai) => {
54-
registerDataTools(ai.toolRegistry, { dataEngine: ctx.getService('data') });
55-
});
56-
```
57-
58-
This is the cloud runtime, not the open path. On the open edition, use
55+
**Cloud / Enterprise — bundled in-product chat.** The cloud tier ships an in-UI
56+
AI runtime (`@objectstack/service-ai`, closed) — the `ask` data-query assistant
57+
and the `/api/v1/ai/*` chat endpoints — that registers these same data tools,
58+
plus the group-by/roll-up `aggregate_data` tool, into its own chat loop. That
59+
package is not part of the open framework and is documented separately in the
60+
cloud docs; this page covers the open path only. On the open edition, use
5961
`@objectstack/mcp` above to get the same natural-language querying with your
6062
own AI.
6163
</Callout>

content/docs/ai/skills.mdx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,16 @@ ObjectStack introduces a **Skills System** — structured, domain-specific knowl
1313
Skills are **not runtime code**. They are machine-readable knowledge definitions that teach AI assistants the ObjectStack protocol — its schemas, patterns, constraints, and best practices.
1414
</Callout>
1515

16+
<Callout type="warn">
17+
**Two different things share the word "skill."** This page is about **authoring
18+
skills**`SKILL.md` knowledge modules (from `skills.sh`) that teach a coding
19+
assistant to *write* correct ObjectStack metadata, at development time. They are
20+
unrelated to **agent skills** (`defineSkill` / `SkillSchema`) — the *runtime*
21+
capability bundles (tools + instructions + trigger conditions) attached to the
22+
`ask` / `build` platform agents, which are the cloud in-product chat runtime.
23+
See [Agents](/docs/ai/agents#you-extend-the-platform-with-skills-not-agents) for those.
24+
</Callout>
25+
1626
---
1727

1828
## Installing skills

content/docs/kernel/runtime-services/index.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ Each page links the canonical TypeScript source used to derive signatures.
3939
- Sharing: `packages/spec/src/contracts/sharing-service.ts`
4040
- Queue: `packages/spec/src/contracts/queue-service.ts`
4141
- Email: `packages/spec/src/contracts/email-service.ts`
42+
- SMS: `packages/spec/src/contracts/sms-service.ts`
4243
- Storage: `packages/spec/src/contracts/storage-service.ts`
4344
- Settings: `packages/services/service-settings/src/settings-service.ts`
4445
- Audit bridge: `packages/services/service-settings/src/settings-service.types.ts`

content/docs/kernel/runtime-services/meta.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"audit-service",
88
"queue-service",
99
"email-service",
10+
"sms-service",
1011
"settings-service",
1112
"storage-service",
1213
"examples",
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
---
2+
title: services.sms
3+
description: Outbound SMS delivery through pluggable providers (Aliyun SMS, Twilio).
4+
---
5+
6+
# `services.sms`
7+
8+
- **Stability:** `stable`
9+
- **Canonical source:** `packages/spec/src/contracts/sms-service.ts`
10+
11+
## Methods
12+
13+
```ts
14+
services.sms.send(input: SendSmsInput): Promise<SendSmsResult>
15+
services.sms.isConfigured(): boolean
16+
```
17+
18+
`SendSmsInput` carries the recipient (`to`, E.164 recommended), the rendered
19+
`body`, and optional provider-template fields (`templateId`,
20+
`templateParams`) for template-only providers such as Aliyun SMS — which
21+
refuse free-form bodies and only deliver pre-registered templates.
22+
23+
`isConfigured()` is `false` while the service runs on the development
24+
`LogSmsTransport` fallback (messages are logged, never delivered). Consumers
25+
that advertise SMS-dependent features — phone OTP sign-in, SMS invitations —
26+
gate on it in production (`features.phoneNumberOtp`).
27+
28+
## Returns
29+
30+
`SendSmsResult`:
31+
32+
- `id: string` — correlation id for the attempt
33+
- `status: 'sent' | 'failed'`
34+
- `messageId?: string` — provider message id (Aliyun BizId, Twilio SID)
35+
- `error?: string` — transport detail when `status='failed'`
36+
37+
## No persistence, no body logging
38+
39+
Unlike `services.email` (which persists to `sys_email`), the SMS service
40+
deliberately has **no persistence surface and never logs message bodies**:
41+
SMS bodies routinely carry one-time passwords, so a message log would be a
42+
credential store. Diagnostics log masked recipients and statuses only.
43+
44+
## Configuration
45+
46+
Providers are configured through the `sms` settings namespace
47+
(Setup → Settings → SMS Delivery), with live rebinding — no restart needed:
48+
49+
- `provider``log` (default, no real delivery) / `aliyun` / `twilio`
50+
- Aliyun: `aliyun_access_key_id`, `aliyun_access_key_secret` (encrypted),
51+
`aliyun_sign_name`, `aliyun_template_code` (default catch-all template with
52+
a single `${content}` variable for generic notification SMS)
53+
- Twilio: `twilio_account_sid`, `twilio_auth_token` (encrypted), and
54+
`twilio_from_number` or `twilio_messaging_service_sid`
55+
56+
Every key accepts the standard settings env override (`OS_SMS_PROVIDER`,
57+
`OS_SMS_ALIYUN_ACCESS_KEY_ID`, …). The **Send test SMS** action exercises the
58+
live (or unsaved) provider configuration.
59+
60+
## Consumers
61+
62+
- **Phone-number OTP auth** — sign-in verification codes and self-service
63+
password-reset codes (`/phone-number/*`, see
64+
[Authentication](/docs/permissions/authentication)).
65+
- **Messaging `sms` channel**`notify(channels:['sms'])` resolves the
66+
recipient's `sys_user.phone_number`, renders the `(topic, 'sms', locale)`
67+
row from `sys_notification_template`, and delivers through this service
68+
with outbox retry/dead-letter.
69+
- **Identity import SMS invitations** — the `invite` password policy sends a
70+
credential-free invitation SMS to phone-only rows.

0 commit comments

Comments
 (0)