Skip to content

Commit e499ada

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-23857-comment-parent-pair
2 parents 291f044 + 34b740f commit e499ada

51 files changed

Lines changed: 3575 additions & 441 deletions

File tree

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/runtime': patch
3+
'@objectstack/cli': patch
4+
---
5+
6+
fix(runtime,cli): `os start` and `os dev` refuse a SQLite database whose directory cannot be made, at once, instead of hanging
7+
8+
Clause-②: no
9+
10+
**What was wrong.** The standalone host (`createStandaloneStack`, which `os start`, `os dev`'s standalone boot and every embedder run) prepared the directory of a file-backed SQLite database with Node's recursive `mkdirSync`. Node's recursive mkdir never returns when a level answers `ENOENT` although its parent exists: for `OS_DATABASE_URL=file:/proc/os-x/ctl.db` it looped `mkdir("/proc/os-x")` and `mkdir("/proc")` without end, holding the event loop. `os start` printed `→ Starting server...` and hung with no timeout and no message. The `sqlite-wasm` arm (`wasm-sqlite://…`, or `OS_DATABASE_DRIVER=sqlite-wasm`) did the same. `os dev` hung the same way in its own process when its default database lived under a state directory that cannot be made (for example `OS_HOME=/proc/os-x`).
11+
12+
**What an operator sees now.** The boot exits non-zero within its normal start-up time, naming the database file, the directory and the operating system's error:
13+
14+
```text
15+
✗ [StandaloneStack] SQLite database file '/proc/os-x/ctl.db' cannot be created: its directory '/proc/os-x' could not be made: ENOENT: no such file or directory, mkdir '/proc/os-x'
16+
```
17+
18+
`os dev` with an unusable state directory prints `✗ The default database '…/data/objectstack.db' cannot be created: its directory '…/data' could not be made: …`, followed by the remedy: point `OS_HOME` at a directory that can be created, or choose the database with `--database`.
19+
20+
Nothing else changes. A creatable path is still made, every missing level of it. The read-only boot (`os migrate plan` over an absent database file) still creates no directory. The refusal names the path, never the database URL.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@objectstack/skills': patch
3+
---
4+
5+
docs(skills): the `objectstack-api` skill stops teaching an app to mount routes on `http.server` — an app declares `apis:`, computes in a flow, and registers code as a stack function or a connector (#23787)
6+
7+
Clause-②: no
8+
9+
`skills/objectstack-api/SKILL.md`'s decision table sent an endpoint that needs code to an imperative `http.server` mount on `kernel:ready`, and its "Code routes" section taught that mount. Ruling C′ item 2 on #23128 retired the mount as an app-author surface: the `http.server` service is the platform plugins' own door-building primitive. The table now points code at a `type: 'flow'` endpoint whose `script` node runs a stack function (`defineStack({ functions })`) or whose `connector_action` node calls a declared connector, names the inbound `type: 'api'` flow hook for a webhook signed with ObjectStack's own `x-objectstack-signature`, and states the three cases that have no app surface today (a third-party callback verified by its own signature over the raw body, a streaming response, a protocol the platform does not speak) instead of inventing one. The "Code routes" section is deleted, and eval 3 expects that answer. Skill text only; nothing an author writes changes.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@objectstack/runtime': minor
3+
'@objectstack/cli': patch
4+
---
5+
6+
fix(runtime,cli): an upper-case `FILE:` database URL opens the file its lower-case spelling opens, `file://NAME` opens `NAME` on both hosts, and the URL-to-filename rule is exported as `sqliteFilenameFromUrl`
7+
8+
Clause-②: yes (widening)
9+
10+
- **`FILE:` is stripped case-insensitively.** The standalone host classified `FILE:./app.db` as SQLite case-insensitively but stripped the prefix case-sensitively, so the prefix stayed in the file name: `OS_DATABASE_URL=FILE:./app.db` created a directory literally named `FILE:.` and put the database inside it, and `File:///tmp/app.db` was refused as an unsupported `File://` scheme. Both now open the file the lower-case spelling opens (`./app.db`, `/tmp/app.db`), as the `sqlite-wasm` path already did. Lower-case `file:` URLs and bare paths are unchanged.
11+
- **New export: `sqliteFilenameFromUrl(url, kind)`** from `@objectstack/runtime`, `kind` being `'sqlite'` or `'sqlite-wasm'`. It is the one rule that turns a SQLite database URL (after `normalizeDatabaseUrl`) into a file name: `file:` / `file://` stripped case-insensitively, plus `wasm-sqlite:` for the WASM kind. For `'sqlite'` it throws a plain `Error` on a URL that is not a file path, naming the URL's scheme and never the URL. Nothing is removed.
12+
- **`os serve`'s legacy storage path (`OS_MODE=off`, `bootMode: 'off'`, or a host config) turns a SQLite URL into a file by that same rule**, so both hosts open one file per URL. It used to strip only a lower-case `file:`: `OS_DATABASE_URL=file://NAME` opened `//NAME` at the filesystem root, where the standalone host opens `./NAME`, and `FILE:./app.db` made a `FILE:.` directory there too. `file:///abs/app.db` opens `/abs/app.db`; `sqlite:` / `sqlite://` URLs are unchanged.
13+
- **What that rule refuses, the legacy path now refuses too, and fatally**, with the standalone host's own sentence (it names the URL's scheme, never the URL): an explicit `OS_DATABASE_DRIVER=sqlite` over a server URL such as `postgres://user:password@host/db`, which used to create directories named after the URL, credentials included, and a `file:` URL that names no file, which used to declare a database with an empty file name. Fix: point `OS_DATABASE_URL` at a file (`file:./app.db`), or select the driver the URL is for. The undocumented `sql://` prefix that path also stripped under an explicit `sqlite` selection is refused the same way; write `file:` instead. A blank `OS_DATABASE_URL` under an explicit `sqlite` selection is read as absent (`:memory:`), as an absent one always was.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
'@objectstack/driver-turso': minor
3+
'@objectstack/cli': minor
4+
'@objectstack/runtime': minor
5+
---
6+
7+
feat(driver-turso,cli): the v18 organization-ownership ceremony runs on a Turso remote database
8+
9+
Clause-②: yes (widening)
10+
11+
`os migrate organization-ownership`, the plan and `--apply --backup-taken`, now completes against a Turso database reached over the remote transport (`libsql://…`), the transport hosted environments use. Before, the plan stopped at its boot with `Deferred schema DDL is not supported by the Turso REMOTE transport`, and the apply's column drops had no remote path either.
12+
13+
- **`@objectstack/driver-turso`**, remote face:
14+
- `detectManagedDrift()` answers instead of refusing with `NOT_IMPLEMENTED` / 501. The verdict is the shared differ's, the one the local face runs; the physical schema it diffs is read from the remote catalog (`pragma_table_info`, `sqlite_master`, `pragma_index_list` / `pragma_index_info`, and the ADR-0120 D4 duplicate probe). The declarations it diffs against are recorded by every remote schema door and by the DDL-free `registerObjectMetadata`, so a boot that skipped schema sync is covered too.
15+
- `applyMigrationEntries()` applies `drop_index`, `create_index`, `recreate_index`, `replace_unique_index` and `drop_column` instead of refusing. Each entry runs as one libSQL `write` batch, so it lands whole or not at all: a `drop_column` drops the column's indexes with it in the same transaction, and a re-created index that fails to build leaves the old one in place. An entry is reported `applied` only once a read-back shows its target state. Every other column entry (nullability, width, a dropped DEFAULT) needs the SQLite table rebuild, which this transport does not run, so it is reported `skipped` and logged, as the in-place dialects report an op they cannot perform.
16+
- The remote face declares `driftReportCaveat`: a sentence saying its drift report is for reading only and naming the reason. The OS_ARTIFACT_URL boot gate reads it (below). The local and embedded-replica faces declare `null`.
17+
- The remote driver's `config` now carries its `url`, so the CLI labels a remote database by its URL (credential-free) instead of `better-sqlite3`. The ceremony writes that label into its plan and refuses an apply whose plan names another database; that refusal can now tell two remote databases apart.
18+
- **`@objectstack/cli`**: the ceremony's plan and apply boots skip schema sync, instead of arming deferred DDL, when the primary datasource is a Turso remote one. No DDL is attempted, so the plan stays read-only, and every other datasource the boot connects is still deferred. The environment's own boot has already run the additive sync the ceremony reads. Every other `os migrate` command keeps refusing on that transport: their dry runs list held-back schema work, which a boot that holds nothing back cannot list.
19+
- **`@objectstack/runtime`**: `createStandaloneStack` takes `skipSchemaSync`, passed to `ObjectQLPlugin` as the option `OS_SKIP_SCHEMA_SYNC=1` names. Unset, the plugin reads the variable as before.
20+
21+
On a Turso remote primary, the `OS_ARTIFACT_URL` boot migration gate on `os serve` keeps its earlier answer: cannot judge, warn and boot. It applies no drift and refuses no boot. Only its warning's wording changes, from `Could not check the physical schema` to `Did not check the physical schema against the artifact: …`, followed by the driver's reason. The gate now decides this from a capability the driver declares (`driftReportCaveat`, a string on the remote face and `null` on the local and embedded-replica faces), not from a refusal it catches. Two follow-ups, filed separately and not closed by this change, have to land before that gate may act on a remote report:
22+
- #23847: the remote transport's schema sync honours `storage.notNull` on create and add column, so the report stops naming a tightening its own sync produced;
23+
- #23848: `os migrate apply` works on a Turso remote database, so the destructive drift the gate would refuse on has a remedy there.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@objectstack/service-automation': minor
3+
---
4+
5+
feat(automation): `getFlowRuntimeStates()` stamps `unboundCause` beside `reason`
6+
7+
Clause-②: yes (widening)
8+
9+
Each branch that decides why an enabled, triggered flow is not armed now answers its closed `FlowUnboundCause` together with its sentence: `deployment_policy`, `disabled_subflow`, `missing_trigger` or `binding_failure`. `GET /automation/_status` passes the rows through unchanged, so the field reaches the console with no route change. Bound, disabled and trigger-less rows carry neither `reason` nor `unboundCause`. The sentences and `getTriggerBindingAudit()` are unchanged.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@objectstack/spec': minor
3+
---
4+
5+
feat(spec): `FlowRuntimeState` gains `unboundCause`, a closed classifier beside `reason`
6+
7+
Clause-②: yes (widening)
8+
9+
`FlowRuntimeState.reason` is a sentence a consumer renders and must not parse, so until now a console could not tell a flow left unarmed by deployment policy from one whose binding is broken: both arrive as `enabled: true, bound: false` with a `reason`. The row now also carries an optional `unboundCause`, present on exactly the rows that carry `reason` and absent on every other row.
10+
11+
Its values are published as `FLOW_UNBOUND_CAUSES` (`@objectstack/spec/contracts`), with the derived type `FlowUnboundCause`:
12+
13+
- `deployment_policy`: package-authored scheduled work is switched off on this deployment. An operator remedy, not a defect.
14+
- `disabled_subflow`: the flow calls a packaged subflow that is disabled (ADR-0126 §7.3). Enable the subflow and the caller arms.
15+
- `missing_trigger`: no trigger of the declared type is registered. Depend on the trigger package (`requires: ['triggers']`).
16+
- `binding_failure`: the trigger is registered but binding the flow to it failed. The earlier boot warnings name the error.
17+
18+
Additive: existing producers and consumers keep working unchanged, and `reason` keeps its text.

‎.claude/agents/os-dev.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ model: opus
139139
- 仅 diff 改该项目测试或派生清单点名它时本地跑,否则 `deviations` 写明;席位 ACCEPT 读其 CI。
140140
- ③ 读到改动文件的门禁族 = `--commands` 派生清单(按 diff 收窄);派发词清单与全农场归 CI。
141141
- 新 fake engine ⇒ `check:engine-double-contract`;新错误码 ⇒ `check:error-code-casing`。
142+
- 改名或删除导出符号 ⇒ push 前跑 `node scripts/check-platform-checklist.mjs --changed-from origin/main`。
143+
- 判定行原样记 `gates`;它非派生族,不在 `--commands` 清单,`--ran` 只把它列为派生外、不报错。
142144
- ④ 报告 `gates`:CI 跑的每族引 PR check-runs(名+结论),⛔ 不本地重跑;`--ran` 只对账本地子集。
143145
- 推导用 `node scripts/pm/dispatch-gates.mjs --commands` 不传路径,脚本自己从 merge-base 取变更集。
144146
- 变更集含未提交与未跟踪;跑完把已跑清单交给 `--ran <file>` 对账,未跑的本地族它会点名。

‎.claude/skills/pm-dispatch/SKILL.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,6 @@ PM 的工作是循环:选卡 → 认领 → 派发 → 收集 → 复核 → 报
123123
- `pm:queue` 卡逾三天欠一次显式转换(派发/转箱/停放/撤单/改前提),⛔ 不是排期。
124124
- hold/blocked 的行契约、双通道与 `Restart-touch:` 触发文件细则见 `references/state-machine.md`。
125125
- hold 放行须双查:只放最近一次转换评论的条件,其后卡上有更新的 merged PR 即拒。
126-
- `Unlock-action:` 只认 `re-check PR #M` 与 `re-check #N when label <标签> <absent|present>`,余者静默回落。
127126
- `needs-user-decision` 是决定待做,`pm:on-hold` 是决定已做;`manual — <理由>` ⛔ 不是合法出口。
128127
- 无机制可唤醒的卡 ⛔ 不 hold:关 not planned,理由/出处载关单评论;重开免费,维护者可否决。
129128
- 缺陷卡 ⛔ 不藏进 hold 也不自行关闭:可复现且用户可达 ⇒ 回 `pm:queue`。
@@ -169,7 +168,8 @@ PM 的工作是循环:选卡 → 认领 → 派发 → 收集 → 复核 → 报
169168
| (无固定归属,按落点分诊) | `packages/apps/*`、`packages/console`(dist 由脚本生成 ⛔ 不手改;UI 缺陷走 `repo:objectui`)、`examples/*`(归它演练的子系统)、`docs/audits/**` |
170169

171170
- 表未覆盖的包首次分诊时归类并走 PR 更新本表;新增或退役 `domain:*` 必须同批改本表。
172-
- `domain:*` 只由分诊席产出,例外仅在飞卡衍生 sub-issue 承父卡域;无标签的卡 ⛔ 不得认领。
171+
- `domain:*` 只由分诊席产出,例外一:在飞卡衍生 sub-issue 承父卡域;无标签的卡 ⛔ 不得认领。
172+
- 例外二:立卡人自贴而车道合上表、正文齐立卡门三件 ⇒ 执行席直接认领,分诊事后抽查。
173173
- 跨域例外路径:拆不动的跨域单 PR 由分诊席指定一个车道 PM 认领,认领评论申报文件面。
174174
- 派发前在飞检查:读 `pm:dispatched` 卡认领评论申报的文件面,本地求交、不另发查询。
175175
- 跨域例外路径的范围加上该单文件面触及各域的在飞单;相交 ⇒ 排该 PR 之后串行或让行。

‎.claude/skills/pm-dispatch/references/execution-duties.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
- 整车道一次读全,本地求交:`list_issues` 带 `labels: [domain:X]` + 最小字段拿回全车道 open 集。
1010
- 候选 = open、未 assign、无 `needs-user-decision`、无 `pm:retriage`;⛔ 排除三门皆无的 `tooling` 卡。
11+
- 自贴 `domain:*` 卡:车道合域表且正文齐 `filing-gate.md` 三件 ⇒ 候选;缺一 ⇒ 挂 `pm:retriage`。
1112
- 已排队父单的 open sub-issue 自动是候选,`pm:epic` 父单的子树除外。
1213
- 每张候选读全文 + 全部评论,写派发词前做决策复读:评论读到最后一页。
1314
- 维护者裁决逐字引入派发词裁决分区作约束,无则写明无裁决。
@@ -38,7 +39,7 @@
3839
- 阻塞解除后延后单重定价:前一单派发词必答 #X 变简单、变难、不必要或无影响。
3940
- 派发后一单前用该回答重读它的选项与成本,⛔ 不沿用立单时那份。
4041
- 在飞卡衍生三分:不修它验收过不过;in-scope ⇒ 父卡 sub-issue,认领席自有、优先级继承。
41-
- 该 sub-issue 带父卡域与优先级直接派发,唯一绕过分诊;sweep 事后扫,重复关 `duplicate`。
42+
- 该 sub-issue 带父卡域与优先级直接派发,绕过分诊;sweep 事后扫,重复关 `duplicate`。
4243
- 排队卡含他车道项 ⇒ 发现席自拆:新卡无 `domain:*`,首行 `carries item k of #N; #N keeps items …`。
4344
- 分诊首触定域可改判;拆出卡 ⛔ 不承父卡域(承域只及本车道 sub-issue),父卡余项照 `Fixes`。
4445
- 自在飞派发升级出的卡首行一句:本卡承哪一半、父卡留哪一半;缺此行不入队不入箱。
@@ -136,7 +137,6 @@
136137
- 派发令写明 dev ⛔ 不另留认领:PM 那条即身份;核对最新一条点名本分支,不符停手回报。
137138
- 已认领、别动 assignee 收窄到 assignee 字段本身;认领协议已满足是另一句话且可能是假的。
138139
- 触 `skills/**` 的卡,派发词必带 PM 定的净增行数预算(按 feature 大小定,小功能给小数)。
139-
- dev 装不下预算 ⇒ 停手回报,⛔ 不自行扩写、不自行抬预算。
140140
- 对外发布的技能包是平台最大的对外价值,评估恒整包整体。
141141
- 翻转公开语义的裁决随卡带全仓 pin 清扫,两句缺一不可,原文见 runbook。
142142
- 拒收用例 ⇒ `code`+`status` 最低断言;过滤/谓词语义 ⇒ 编译面清单逐面申报,⛔ 静默略过。

‎.claude/skills/pm-dispatch/references/triage-duties.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
- 析取 ②:有 `pm:queue` 无 `domain:*`(仅多车道仓);析取 ③:有 `domain:*` 无 pm-state。
1515
- 析取 ④:有 `pm:queue` 与 `domain:*` 而无 `priority:*`;②③④ 只取 `updated_at` 早于 ~2 分钟。
1616
- 排除 `tracking`、`status:parked`、全部 `pm:seat` 贴、`qa-run` 记录。
17+
- 自贴卡抽查:窗内新派发且 `domain:*` 事件与建卡同分钟者,逐张核车道、三件;⛔ 不凭 actor。
1718
- 半状态治愈积压 ②③④ 最老优先、P0 嫌疑先行,⛔ 优先最新不适用。
1819
- 首触定级与三析取 sweep 每 fire 跑到清空:⛔ 无每轮总量预算,清得动就多清。
1920
- 窗口耗尽未跑完的,下一 fire 从标签状态断点续跑。
@@ -85,7 +86,6 @@
8586
- 车道席可附证据/前提重验,⛔ 不定级不改标;skills 车道 finding 由该席自分诊,全仓轮跳过。
8687
- 域分批与 sweep 打包晋级五条照用;每批约定与积压告警见 `references/dispatch-runbook.md`。
8788
- `pm:retriage` 每 fire 先答异议评论所求,答后同笔摘标;须维护者答的进收件箱,标照摘。
88-
- retriage 维持或改判皆由分诊席同笔摘标;挂标归异议席。
8989
- retriage 的异议判据认内容(有评论写明所求)⛔ 不认形状(有一条异议评论)。
9090
- 发版板 `target:<major>`:判据二元,⛔ 不做优先级渐变:不修它,当前 RC 能不能发。
9191
- 判阻塞四类:① 用户今天就撞的已发布面缺陷;② 公开契约 declared≠enforced。

0 commit comments

Comments
 (0)