Skip to content

Commit 356cc7a

Browse files
committed
Merge origin/main into claude/project-thread-4ejvqq
# Conflicts: # skills/objectstack-data/evals/hooks-security-seeds-datasources.json
2 parents bc879f0 + 56baf78 commit 356cc7a

39 files changed

Lines changed: 1465 additions & 168 deletions
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
'@objectstack/cli': patch
3+
---
4+
5+
feat(cli): `os deploy` and the `d` key in `os dev` deploy to a self-hosted runtime (Docker Community or Enterprise) that `os environments use-runtime` remembered; `os package install` falls back to it
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
**`os environments use-runtime <url> --token <key>`** (new) remembers a self-hosted runtime and an API key it issued, and makes it the deploy target. The key is tried on the runtime's install-local door first (`--no-verify` skips that); a rejected key, a runtime that does not serve install-local, or one that cannot be reached is refused and nothing is recorded. Both are stored in `~/.objectstack/cloud.json` (0600) as their own record beside the cloud session: `os cloud login` and `os cloud logout` keep it, and the key is only ever sent to that url. `os environments switch <id>` makes ObjectOS Cloud the deploy target again (the runtime stays remembered); `os environments use-runtime --forget` drops it.
11+
12+
**`os deploy`** with a runtime as the active target (and no `--env`) builds the app, asks the runtime's install-local dry run and prints the upgrade plan (declared `renamedFrom` renames included), then installs the build inline — no catalog publish and no `-dev` version — and reports it live from the runtime's answer. A refusal from the dry run or the install stops the deploy with exit 1 and the runtime's sentence; a runtime that does not serve install-local (one booted from a composed artifact) or that predates the dry run is named, with what to do, exit 1. `--confirm-access-upgrade <finding-set>` passes the access-upgrade confirmation through (and is refused when the target is ObjectOS Cloud). **The `d` key** names the remembered runtime and deploys there without asking for a cloud login.
13+
14+
**`os package install`** without `-u` / `$OS_RUNTIME_URL` installs into the remembered runtime, and without `-t` / `--email` uses its key — only when the url is that runtime's. With nothing remembered the default stays `http://localhost:3000`. Its 404 answer now names the composed-artifact case and what to do.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@objectstack/mcp': patch
3+
---
4+
5+
feat(mcp): a host passes per-request server instructions on `McpAuthoringInjection`, answered at `initialize` only where the authoring family registers
6+
7+
Clause-②: yes
8+
Metadata-protocol: none
9+
10+
`McpAuthoringInjection` gains an optional `instructions` string. `MCPServerRuntime.handleHttpRequest` answers `initialize` with it when the request's grant registers the injected authoring family (a scope list naming `metadata:write`, the same predicate that admits the registry). Every other request — a `data:read`, `data:write` or `actions:execute` token, an API key or a session — is answered with the runtime's configured `instructions`, or the default sentence, and never sees the injected text; an authoring request whose injection carries no `instructions` gets the configured or default text too. A host that serves a build door can now tell the MCP client where its authoring rules live without widening what a read-only connection is told. Nothing existing changes: a host that passes no `instructions` answers exactly as before.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@objectstack/skills': patch
3+
---
4+
5+
docs(skills): validation examples guard nullable fields, hooks teach `body`, the `requires:` table names `mailbox` and `package-registry`, `os generate` drops `agent`
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
`objectstack-data` `rules/validation.md` now guards every ordering or arithmetic operand in its examples with `!= null` (the due-date, date-range, amount, discount and order-total predicates) and states the rule once: an unguarded `<` or `+` on a field with no `required: true` / `defaultValue` is refused at publish. Its external-validation hook and the `examples-objects.md` lifecycle hook are written as a sandboxed `body`, and the `SKILL.md` index names the inline `handler` only as the deprecated form; the validation section says a `body` has no outbound HTTP, so an external-API check goes through a Connector recipe. `objectstack-platform` lists `mailbox` (`@objectstack/service-mailbox`) and `package-registry` (`@objectstack/service-package`) in the `requires:` table, and `references/operations.md` no longer offers the retired `os generate agent`. The platform behaviour already shipped; this release only teaches it.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@objectstack/spec': patch
3+
'@objectstack/cli': patch
4+
---
5+
6+
docs(spec,cli): the seed mode text now says what the loader does. `replace` deletes nothing: it inserts every record with no match, for a table the caller cleared, and `insert` inserts every record again on each replay. The `mode` docstring and `.describe()` name the landed-keys rule: the loads AppPlugin runs for an organization (the single-tenant boot seed, the `seed-replayer` replay, the dev hot reload) do not insert again an `ignore` or `upsert` row that landed once and was later deleted, a seed publish or package install keeps no such record, and `seed-replayer(organizationId, { reseed: true })` inserts every missing row. `os diff`'s note on a `replace` dataset no longer says a replay deletes every row of the object.
7+
8+
Clause-②: no
9+
Metadata-protocol: none

‎.claude/skills/pm-dispatch/references/platform-readings.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -385,9 +385,9 @@
385385
- 强制档不因临缺授权降档:临缺非退役,退役唯维护者裁;不达档起隔离达档子代理或等档。
386386
- 本车道强制多是过宽的回忆:`dispatch-gates.mjs --tier PATH` 逐路径现推,路径线是下限非放行。
387387
- 该脚本只答自己那棵树:姊妹仓路径回 absent from this tree,姊妹仓的档位与条款②只能手推。
388-
- required checks 的名单是每仓事实,objectstack 十个:
389-
`TypeScript Type Check` · `Lint & Repo Gates` · `Test Core` · `Build Core` · `Lint UI` ·
390-
`Governed Surface Queue Guard` · `Type Check UI` · `Test UI` · `Build & E2E UI` · `Live E2E UI`。
388+
- required checks 的名单是每仓事实,objectstack 十一个:
389+
`TypeScript Type Check` · `Lint & Repo Gates` · `Test Core` · `Build Core` · `Lint UI` · `Test UI` ·
390+
`Governed Surface Queue Guard` · `Type Check UI` · `Build & E2E UI` · `Live E2E UI` · `Dogfood Regression Gate`。
391391
- ⭐ 本表的 `mustName` 声明 `statesTheSet` ⇒ 必须逐字等于注册表,加注册行不跟改本表即红。
392392
- ⇒ 门绿即名单逐字齐全的读数;⛔ 但数量词未钉:实测 七→六 照绿,只能手跟改。
393393
- ⛔ 聚合命令同样不作判定:`check:type-check-debt` 可在包级 typecheck 绿时红。

‎.claude/skills/pm-dispatch/references/review-checklist.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
- ⛔ 非当前 head 上的 `cancelled` 零动作、永不重跑:新推送自带全套 run。
4747
- 席位核对门禁证据 = check-runs(名+结论);本地并集只证本地子集,⛔ 不替缺席者。
4848
- `!` 或 `(narrowing)`:入队前核 `dogfood_battery` 钉 head、全量、红已修/已报;缺或收窄 ⇒ REWORK。
49-
- 把发送或写挪到应答之后的 PR:dogfood 门禁不跑于 PR 与队列,全绿照样红 `main` 的 push 全量。
49+
- 把发送或写挪到应答之后的 PR:dogfood 门禁不跑于 PR、跑于队列,全绿 PR 照样被队列弹出。
5050
- ⇒ 入队前 grep `packages/qa/dogfood/test` 里应答后紧读该副作用的用例,同一 PR 改为有界等待。
5151

5252
## 断言与收益

‎.github/workflows/ci.yml‎

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,8 @@ concurrency:
122122
# github.event_name != 'pull_request'
123123
#
124124
# in its `if:`, so a dogfood red shows on the queue build of the entry that
125-
# carries it rather than first on `main`'s push run after it. It is advisory
126-
# there: the context is outside the required set (below), so the queue may
127-
# merge the entry before or despite the shards.
125+
# carries it rather than first on `main`'s push run after it, and the red
126+
# ejects that entry: the context is in the required set again (below).
128127
# `main`'s 4-hourly `schedule` run, its `push` runs and an on-demand
129128
# `workflow_dispatch` carry the FULL BATTERY: all of that plus the three jobs
130129
# below that spell
@@ -139,9 +138,9 @@ concurrency:
139138
# `scripts/ci/scheduled-full-run.mjs --release-gate`). ⛔ No affected-only
140139
# dogfood filter on the queue: the shards run on every entry, through the
141140
# `core` output, which is 'true' on `merge_group` (letter C was not ruled).
142-
# The queue enforces only the ruleset's required set, and `Dogfood Regression
143-
# Gate` is outside it since #22998 (check:required-contexts' tombstone); this
144-
# file does not decide whether it rejoins.
141+
# The queue enforces only the ruleset's required set. `Dogfood Regression
142+
# Gate` left it by #22998 and rejoined it by the #30461 follow-up ruling
143+
# (ruling record 6669982756); check:required-contexts pins its row.
145144
#
146145
# Spelled as a DENY-list of the per-PR events on purpose — the filter
147146
# contract's half 1 below, applied to events: a trigger added to `on:` later
@@ -2724,9 +2723,9 @@ jobs:
27242723
overwrite: true
27252724

27262725
dogfood-gate:
2727-
# Stable check name for a SHARDED job (#3622 follow-up). Since #22998 the
2728-
# context is outside the required set (THE TWO SPEEDS above `jobs:`). On a
2729-
# PR both legs skip and this gate counts zero expected attestations; on a
2726+
# Stable check name for a SHARDED job (#3622 follow-up), a required
2727+
# context again since #30461 (THE TWO SPEEDS above `jobs:`). On a PR both
2728+
# legs skip and this gate counts zero expected attestations; on a
27302729
# queue build (since #30461) the three dogfood shards run and are counted,
27312730
# while `dogfood-verify` skips and is judged with zero expected; the full
27322731
# battery runs and counts both. The history below is why the name and the
@@ -2738,10 +2737,9 @@ jobs:
27382737
# EVERY pull request in the repo sat permanently BLOCKED (mergeable, all
27392738
# checks green, merge button dead). #3622's own comment called for updating
27402739
# branch protection; keeping the contract HERE instead means a future
2741-
# shard-count change cannot deadlock the repo a second time. From #6865
2742-
# until #22998 the bare name was pinned by `check:required-contexts`; it
2743-
# left that registry with the required set, so a rename now falsifies the
2744-
# prose that names it without reddening anything — treat it as contract.
2740+
# shard-count change cannot deadlock the repo a second time. The bare name
2741+
# is pinned by `check:required-contexts` (since #6865; out of that registry
2742+
# from #22998 until #30461) — treat it as contract.
27452743
#
27462744
# Also aggregates dogfood-verify (the CLI pass that used to ride shard 1),
27472745
# so the one context still covers everything it covered before the split.

‎AGENTS.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -505,11 +505,11 @@ Even inside your own worktree, operate defensively:
505505
yet"; `in_progress` is not a pass, and arming a red PR hides it
506506
([why](docs/agents/multi-agent-discipline.md#arming-a-red-pr-hides-it)). Always read *two* things:
507507
the queue branch **and** `origin/main`. And **the queue enforces only the required
508-
set** — ten contexts block: `Lint & Repo Gates` (all `check:*` gates), `TypeScript Type Check`,
508+
set** — eleven contexts block: `Lint & Repo Gates` (all `check:*` gates), `TypeScript Type Check`,
509509
`Test Core`, `Build Core`, `Governed Surface Queue Guard`, `Lint UI`, `Type Check UI`, `Test UI`,
510-
`Build & E2E UI` and `Live E2E UI`; a check outside those ten is advisory
510+
`Build & E2E UI`, `Live E2E UI` and `Dogfood Regression Gate`; a check outside those eleven is advisory
511511
([why that matters](docs/agents/multi-agent-discipline.md#advisory-checks-ride-through)). A required context is
512-
matched by check-run name, so a rename detaches its gate silently — treat those ten names as contract.
512+
matched by check-run name, so a rename detaches its gate silently — treat those eleven names as contract.
513513
**Two speeds** — PR runs carry the fast set, queue runs add the dogfood shards; `main`'s scheduled and push runs
514514
carry the full battery ([detail](docs/agents/multi-agent-discipline.md#two-speeds-which-runs-carry-which-battery)).
515515
**Main-red:** a full-battery red is its lane's first work — triage files it under

‎content/docs/references/api/package-api.mdx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ Package manifest installed through POST /api/v1/packages (AUTHORING stage; scope
243243
| :--- | :--- | :--- | :--- |
244244
| **object** | `string` | ✅ | Target Object Name |
245245
| **externalId** | `string \| string[]` | optional (default: `"name"`) | Field (or composite list of fields) matched for the uniqueness check |
246-
| **mode** | `Enum<'insert' \| 'update' \| 'upsert' \| 'replace' \| 'ignore'>` | optional (default: `"ignore"`) | What a load does with a record whose `externalId` matches an existing row. `ignore` (default) is create-only: a missing row is inserted and an existing row is left as it is, so a replay (every boot, a package install or upgrade, a publish) never writes a seeded value back over a later edit. `upsert` also writes every declared column back onto the matched row on each replay; declare it for reference data the package keeps current. `update` writes matched rows only, `insert` inserts every record with no match, and `replace` inserts into a table the caller cleared. |
246+
| **mode** | `Enum<'insert' \| 'update' \| 'upsert' \| 'replace' \| 'ignore'>` | optional (default: `"ignore"`) | What a load does with a record whose `externalId` matches an existing row. `ignore` (default) is create-only: a missing row is inserted and an existing row is left as it is, so a replay (every boot, a package install or upgrade, a publish) never writes a seeded value back over a later edit. `upsert` also writes every declared column back onto the matched row on each replay; declare it for reference data the package keeps current. `update` writes matched rows only, `insert` inserts every record with no match, so each replay inserts them again, and `replace` does the same and deletes nothing: it is for a table the caller cleared. On the loads AppPlugin runs for an organization (the boot seed, the seed-replayer replay, the dev hot reload), an `ignore` or `upsert` row that landed once and was later deleted is not inserted again; a publish or package install keeps no such record, and seed-replayer called with `{ reseed: true }` inserts every missing row. |
247247
| **columns** | `Record<string, { applyOn?: Enum<'create' \| 'always'> }>` | optional | Per-column write policy, keyed by field name: `{ status: { applyOn: 'create' } }` writes `status` only when the seed creates the row. Omitted, every column is written on create and update. |
248248
| **env** | `Enum<'prod' \| 'dev' \| 'test'>[]` | optional (default: `["prod","dev","test"]`) | Applicable environments |
249249
| **locale** | `string[]` | optional | Applicable locales (BCP-47 tags); omitted applies to every locale. The boot seed filters on the app's i18n.defaultLocale; the marketplace install, sample-data reseed, restart heal and per-organization replay filter on the locale resolved for the load (the localization.locale setting when set, then the locale of the installing, reseeding or founding user, then the app's i18n.defaultLocale where the load names one); package apply and draft publish do not filter by locale — they load every dataset and warn |
@@ -351,7 +351,7 @@ Package manifest installed through POST /api/v1/packages (AUTHORING stage; scope
351351
| :--- | :--- | :--- | :--- |
352352
| **object** | `string` | ✅ | Target Object Name |
353353
| **externalId** | `string \| string[]` | optional (default: `"name"`) | Field (or composite list of fields) matched for the uniqueness check |
354-
| **mode** | `Enum<'insert' \| 'update' \| 'upsert' \| 'replace' \| 'ignore'>` | optional (default: `"ignore"`) | What a load does with a record whose `externalId` matches an existing row. `ignore` (default) is create-only: a missing row is inserted and an existing row is left as it is, so a replay (every boot, a package install or upgrade, a publish) never writes a seeded value back over a later edit. `upsert` also writes every declared column back onto the matched row on each replay; declare it for reference data the package keeps current. `update` writes matched rows only, `insert` inserts every record with no match, and `replace` inserts into a table the caller cleared. |
354+
| **mode** | `Enum<'insert' \| 'update' \| 'upsert' \| 'replace' \| 'ignore'>` | optional (default: `"ignore"`) | What a load does with a record whose `externalId` matches an existing row. `ignore` (default) is create-only: a missing row is inserted and an existing row is left as it is, so a replay (every boot, a package install or upgrade, a publish) never writes a seeded value back over a later edit. `upsert` also writes every declared column back onto the matched row on each replay; declare it for reference data the package keeps current. `update` writes matched rows only, `insert` inserts every record with no match, so each replay inserts them again, and `replace` does the same and deletes nothing: it is for a table the caller cleared. On the loads AppPlugin runs for an organization (the boot seed, the seed-replayer replay, the dev hot reload), an `ignore` or `upsert` row that landed once and was later deleted is not inserted again; a publish or package install keeps no such record, and seed-replayer called with `{ reseed: true }` inserts every missing row. |
355355
| **columns** | `Record<string, { applyOn?: Enum<'create' \| 'always'> }>` | optional | Per-column write policy, keyed by field name: `{ status: { applyOn: 'create' } }` writes `status` only when the seed creates the row. Omitted, every column is written on create and update. |
356356
| **env** | `Enum<'prod' \| 'dev' \| 'test'>[]` | optional (default: `["prod","dev","test"]`) | Applicable environments |
357357
| **locale** | `string[]` | optional | Applicable locales (BCP-47 tags); omitted applies to every locale. The boot seed filters on the app's i18n.defaultLocale; the marketplace install, sample-data reseed, restart heal and per-organization replay filter on the locale resolved for the load (the localization.locale setting when set, then the locale of the installing, reseeding or founding user, then the app's i18n.defaultLocale where the load names one); package apply and draft publish do not filter by locale — they load every dataset and warn |

0 commit comments

Comments
 (0)