Skip to content

Commit 729ed87

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-6860-database-driver-allowlist
2 parents 611619e + 25ab57b commit 729ed87

58 files changed

Lines changed: 2951 additions & 160 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: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
---
2+
'@objectstack/spec': major
3+
'@objectstack/service-automation': patch
4+
'@objectstack/plugin-approvals': patch
5+
---
6+
7+
refactor(spec)!: retire `ActionDescriptor.isAsync` — a second spelling of `supportsPause` that nothing ever read (#6748, ADR-0049)
8+
9+
<!-- adr-0087: registered action-descriptor-is-async-retired -->
10+
11+
**FROM → TO:** `isAsync: true` → delete the key; declare `supportsPause: true` (plus the
12+
`resumeAuthority` its pauses need) and return `suspend: true` from `execute()`.
13+
`isAsync: false` → delete the key; there was never anything to preserve.
14+
15+
`ActionDescriptor.isAsync` declared "suspends the flow awaiting an external reply" and no
16+
execution path read it. Measured fresh before removal across all three repos — objectstack,
17+
objectui and cloud — with zero property reads: every hit was the declaration itself, a
18+
generated baseline, one of five shipped descriptors WRITING it, a fixture pinning the
19+
shape, or prose. Declaring it never made a node suspend; omitting it never stopped one.
20+
21+
This is the remove leg of the ADR-0049 disposition its sibling took the other way. The two
22+
keys said the same thing — "this node type can suspend the run" — and #6667 split them by
23+
evidence: `supportsPause` became an enforced fact (`AutomationEngine` now refuses a
24+
suspension whose type does not declare it, at the one seam every suspension passes
25+
through), while `isAsync` had no consumer to grow into. Keeping both would leave the
26+
platform publishing two names for one capability with only one of them honoured — and
27+
`screen` declared BOTH, so a plugin author copying it had no way to tell which.
28+
29+
The retirement kit:
30+
31+
- **Tombstone, not deletion** (`retiredKey()`): `ActionDescriptorSchema` is not `.strict()`,
32+
so a plain delete would let existing descriptors parse clean and lose the key in silence
33+
(the ADR-0104 shape). Authoring `isAsync` now fails `tsc` at the descriptor literal and
34+
fails the parse inside `defineActionDescriptor()` — with the prescription in the message.
35+
- **ADR-0087 D3 `SemanticMigration`** (`action-descriptor-is-async-retired`) plus the exact
36+
`RETIRED_KEYS_BY_MAJOR` entry. No D2 conversion, deliberately: a descriptor is published
37+
from an executor's TypeScript and never stored in stack metadata, so there is no source
38+
for `os migrate meta` to rewrite — the `EnhancedApiError.fieldErrors` disposition.
39+
- The five shipped writers stop writing it (`screen`, `map`, `wait`, `approval`,
40+
`approval_revise`); the descriptors they publish lose the key, which is why the two
41+
runtime packages appear here.
42+
- Generated baselines (`authorable-surface/automation.json` gains `[RETIRED]`,
43+
`authorable-defaults/automation.json` loses the default line), `spec-changes.json`, the
44+
upgrade guide and the reference docs regenerated.
45+
46+
No runtime behaviour changes — that impossibility is the reason for the removal. The same
47+
commit also corrects `supportsPause`'s TSDoc, which still described itself as a declaration
48+
no execution path reads; #6667 made that false (#6749).
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
---
2+
"@objectstack/spec": minor
3+
---
4+
5+
<!-- adr-0087: not-required (no-migration-prescription) this change retires NO key. The endpoint vocabulary is byte-identical and only the unknown-key POSTURE moves, from strip to reject. Nothing exists for `objectstack migrate meta` to rewrite, because an undeclared key was never honoured in the first place: it was dropped at parse and so never reached storage, the matcher or the executor. No stored shape carries one, and no authored shape that ever WORKED becomes invalid. There is also no single FROM/TO rule a ledger entry could state, since what is now refused is an open set of author typos rather than a renamed key. The upgrade channel is the schema rejection itself, which is strictly more specific than any ledger line: it names the offending key at the author's own path and carries either the canonical spelling or a wrong-layer pointer at the surface that really owns the key. The one ADR-0087 entry that DOES govern this surface, `declarative-apis-endpoints-live`, is already registered for protocol 17 and is updated by this PR rather than duplicated. Measured blast radius: 0 affected entries across `examples/*` (2 endpoints) and the `cloud` repo (0 endpoints). -->
6+
7+
`ApiEndpointSchema` rejects undeclared keys (#5384), and the author-state type is named on the upgrade path (#5227)
8+
9+
`api` became a registered metadata type at #5312, which made
10+
`packages/spec/src/api/endpoint.zod.ts` an AUTHORING surface — `defineStack({ apis })`,
11+
the Studio metadata-admin form, and `PUT /meta/api/:name`'s 422 — while it was still a
12+
plain open `z.object`. An undeclared key was therefore dropped on every path: a
13+
`cacheTTL` / `objectParam` / `outputMappings` typo parsed green, published green, and the
14+
endpoint then served without the policy or projection its author wrote. That direction is
15+
fail-safe for `authRequired` alone (an unrecognized spelling leaves the default `true`
16+
standing); it was never fail-safe for the mapping, cache and rate-limit blocks.
17+
18+
The shape is now `strictObject`, so an undeclared key is a named rejection carrying the
19+
surface, the offending key and a rename. Two curated wrong-layer prescriptions ship with
20+
it:
21+
22+
- **`namespace`** — ADR-0121 D2 derives the namespace segment of `path` from
23+
`manifest.namespace`; it has never been per-endpoint, so the rejection points at the
24+
manifest instead of suggesting a rename.
25+
- **the six stored-envelope bookkeeping keys** (`packageId`, `state`, `version`,
26+
`published*`) — written onto the stored ROW by `register` / `publishPackage`, never onto
27+
a declaration.
28+
29+
**The order this landed in is the part worth keeping.** Closing the shape was measured and
30+
REFUSED first: the same schema parsed STORED rows at `buildEndpointIndex` and
31+
`gateApiItemsForPublish`, so a naked `strictObject` failed every row with
32+
`unrecognized_keys: ['packageId', 'state']` — the load-time backstop excluded the endpoint
33+
(404) and the publish gate reported a schema error in place of the ADR-0121 D6 verdict it
34+
exists to give. The debt was real and it was not in this vocabulary, so #5309 (PR #6576)
35+
paid it at the layer that owned it (`peelStoredEnvelope`). `ApiEndpointSchema` never
36+
learned a bookkeeping key.
37+
38+
**Breaking for metadata that was already silently broken.** An `apis:` entry carrying an
39+
undeclared key now fails `objectstack validate`, `objectstack build` and the metadata write
40+
path instead of publishing with the key discarded. Measured before landing: the example
41+
corpus (2 endpoints) and the `cloud` repository (0 endpoints) carry zero undeclared keys,
42+
so nothing in-tree changes verdict.
43+
44+
`api` also leaves the #4001 campaign's `STILL_STRIP` list — closed registered types 24 → 25
45+
of 26, with `view` the only entry left — and the CLI metadata gate's row moves from
46+
`NOT_YET_CLOSED` into `GATED_AT`.
47+
48+
**#5227** — no schema change. `ApiEndpoint` already denotes the AUTHOR state after ADR-0122
49+
phase 2, so omitting `authRequired` compiles; what was missing was anywhere saying so. The
50+
`declarative-apis-endpoints-live` upgrade-guide entry, whose whole safety argument is that
51+
"an omission is SAFE", now carries the type annotation that makes the omission writable:
52+
annotate declarations `ApiEndpoint`, hold parse results as `ApiEndpointParsed`.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
docs(spec): D3 迁移账本里 `resumeAuthority` 那条的验收标准不再要求手工排查 `supportsPause` 不一致 —— #6746 之后引擎自己会拒绝 (#6844)
6+
7+
`MIGRATIONS_BY_MAJOR[17].semantic``action-descriptor-resume-authority-default-flip`
8+
一条的 `acceptanceCriteria`,结尾的 ⚠️ 子句写的是:
9+
10+
> `supportsPause` is a declaration nothing enforces (#5703), so an executor whose
11+
> `execute()` returns `suspend: true` while leaving `supportsPause` false is warned
12+
> about by NEITHER channel — check those by hand against the same rule.
13+
14+
写下时属实,PR #6746(#6667,2026-08-08 合入)之后不再属实:
15+
`AutomationEngine.refuseUndeclaredSuspension` 正是拒绝这一类执行器的,拒绝点在
16+
`executeNode` 里每个 `result.suspend === true` 都要经过的那一个接缝
17+
(`packages/services/service-automation/src/engine.ts`)。于是一个 v17 升级者被告知
18+
去手工审计一类引擎已经自己拒绝的错误,而且被告知这个不一致「没有声音」,实际上它很响。
19+
20+
这不是内部注释:该字符串被 `gen:upgrade-guide` 逐字投影进
21+
`docs/protocol-upgrade-guide.md` 的 "Done when" 行,而那正是升级作者(依 ADR-0033,
22+
往往是 AI)读的那一行,所以本 PR 一并提交了重新生成的产物。
23+
24+
**改后的说法,以及为什么不是「什么都不用查了」**:两条告警通道(启动告警、
25+
`check:resume-authority-declared`)确实仍然都以 `supportsPause: true` 为触发条件,
26+
所以「不被任何告警通道覆盖」这半句保留;变化的是它现在会在运行时被拒绝,且属于
27+
guard-class —— `fault` 边路由不了(`refuseNode` 打的是 `errorClass: 'guard'`,
28+
`executeNode``errorClass === 'guard'` 时不去找 `fault` 边)。同时刻意没有过度
29+
更正:该守卫明确不判「完全没有注册描述符」的执行器(engine.ts 中
30+
"What it does NOT judge → Silence",由 `supports-pause-runtime-enforcement.test.ts`
31+
的 descriptor-less 用例钉住),这类执行器的暂停照样会被创建,只在 resume 路由上才
32+
被拒绝,所以这一条作为仍需留意的残留被写进了新文案。
33+
34+
受理面(schema 接受什么)逐字节未变 —— 本条只动账本散文与其生成产物。
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
'@objectstack/rest': patch
3+
---
4+
5+
rest 的异步导入行数上限改为直接读取 spec 的 `IMPORT_JOB_MAX_ROWS` 导出,不再自己声明一份同值字面量(#6535)。
6+
7+
**行为没有任何变化**:两处此前都是 `50_000`,改后仍是 `50_000`,上限、`413` 文案、拒绝边界全部不动。
8+
这是一次一致性收敛,不是缺陷修复——因此按 patch 计。
9+
10+
收敛掉的是一处漂移面:`packages/spec/src/api/export.zod.ts` 的那份导出带着 TSDoc,是这个
11+
上限的**对外说明**(喂给生成的 reference 表面);而真正执行拒绝的是 `packages/rest`,它此前
12+
读的是自己那份本地 `const`,两者之间只有一句 "mirrors spec" 注释相连。没有任何 gate 比较这
13+
两个数——`api-surface/api.json` 只记下 `"IMPORT_JOB_MAX_ROWS (const)"` 这个**名字**,不记它的
14+
****——所以把 spec 那份改成 20_000、执行侧纹丝不动,`pnpm test` 与全部 `check:*` 依然全绿
15+
(本 PR 实测过)。失效方向是文档说一套、系统做一套,而 `413` 文案里内插的又是 rest 那一份,
16+
连报错都会自洽地说谎。现在一处定义、两处读点(`maxRows:``413` 文案)同源。
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
---
2+
"@objectstack/spec": minor
3+
"@objectstack/cli": minor
4+
---
5+
6+
feat(spec,cli): carry the package namespace on the publish payload (#6760)
7+
8+
ADR-0048's addendum defines a publish-time namespace exclusivity registry
9+
(`namespace → publisher`), so a cross-vendor namespace collision is caught while
10+
exactly one party can still fix it cheaply — the publisher, before anything ships
11+
— instead of surfacing at install time, where the tenant who suffers it can do
12+
nothing. That gate is enterprise-side (Phase A2), and it could not be built
13+
because the namespace never left the artifact: `PackageSchema` had no
14+
`namespace` field at all, `CreatePackageRequestSchema` did not accept one, and
15+
`objectstack package publish` transmitted `manifest_id` only. This is Phase A1,
16+
the open-side half that gives the gate an input.
17+
18+
**`PackageSchema` and `CreatePackageRequestSchema` gain an optional
19+
`namespace`.** It mirrors `manifest.namespace` exactly — same 2-20 character
20+
rule (`/^[a-z][a-z0-9_]{1,19}$/`), same optionality — so the publish payload and
21+
the artifact manifest cannot disagree about what a namespace is. Optional is the
22+
ruled shape, not a convenience: the addendum's algorithm opens with
23+
`if (namespace is absent) -> allow`, and a package that declares no namespace
24+
makes no reservation and is not gated. A parity test judges both fields against
25+
one table of values, so a change to either side fails.
26+
27+
**`objectstack package publish` sends it, read off the compiled artifact's
28+
`manifest.namespace`** — the same place the command already reads
29+
`manifest.id`. Three behaviours, matching the addendum's algorithm:
30+
31+
- namespace present → it travels on the `POST /cloud/packages` body as
32+
`namespace`, and is echoed in the publish summary;
33+
- namespace absent → the key is omitted entirely (not `null`, not `''`), so
34+
"declares no namespace" never becomes a value the gate has to interpret;
35+
- namespace malformed → the publish is refused before any network call, naming
36+
the rule and the fix.
37+
38+
The namespace is deliberately **not** overridable by a flag or by
39+
`objectstack.manifest.json`, unlike `manifestId`: a reservation is only
40+
meaningful if it names the object-name prefix the package actually ships, and a
41+
second declaration surface would let a publisher reserve `foo` while installing
42+
`bar_*` objects. For the same reason `TemplateManifestSchema` omits the field
43+
rather than inheriting it.
44+
45+
Nothing about install-time behaviour changes. The in-process install gate,
46+
`NamespaceConflictError`, the shareable `base`/`system`/`sys` set and the
47+
`OS_METADATA_COLLISION=warn` downgrade are untouched, and the install path
48+
acquires no network dependency.

0 commit comments

Comments
 (0)