Skip to content

Commit 8644de0

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-19986-explain-read-scope
2 parents 5d700f7 + 615c085 commit 8644de0

25 files changed

Lines changed: 1479 additions & 169 deletions

‎.changeset/15705-mcp-resume-run.md‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
'@objectstack/mcp': minor
3+
'@objectstack/runtime': minor
4+
---
5+
6+
feat(mcp): `resume_run` continues a flow run that paused on a screen, behind the same gates as `run_action` (#15705)
7+
8+
Clause-②: yes
9+
10+
**What changed.** `run_action` on a flow action whose flow stops on a `screen` node answers `status: "paused"` with a `runId` and the `screen` to fill in. Until now nothing on the MCP surface could submit that screen, so the run stayed parked: an agent could start such an action but never finish it. The new MCP tool `resume_run({ runId, values?, confirm? })` submits the screen's field values (keyed by the names in `screen.fields`) and the run continues. It answers with `run_action`'s envelope, `{ ok, action, objectName, recordId?, result }`. A run that pauses on its next screen comes back paused again, so a multi-screen wizard is walked by calling `resume_run` once per screen.
11+
12+
**Which runs it continues, and no others.** The runtime's bridge admits a call only where `run_action` would admit starting the same flow on the same record for this caller now:
13+
14+
- **Only the caller's own run.** The run's trigger identity must be the caller. Another user's run, an unknown id and a finished run all answer the same `404 RESOURCE_NOT_FOUND`. A resumed run continues under the identity of the user who started it, so only that user may continue it.
15+
- **`run_action`'s gates, with `run_action`'s helpers.** A `type: 'flow'` action whose `target` is the run's flow, on the run's object, must be AI-exposed (`ai.exposed`), must pass the caller's `requiredPermissions` and must not be switched off (`ACTION_DISABLED`, `409`). An action flagged `ai.requiresConfirmation` needs `confirm: true` on the resume too (`ACTION_CONFIRMATION_REQUIRED`, `428`), because the flow's writes happen after the screen. The exposure and permission refusals answer `403 PERMISSION_DENIED`. So does a run that no flow action targets.
16+
- **The subject record is read again as the caller.** A record the caller can no longer read is refused `404 RECORD_NOT_FOUND`, which is how `run_action` refuses it.
17+
- **Screen pauses only.** A run waiting on anything else (a timer `wait`, an approval) is refused `409 RESOURCE_CONFLICT` and left as it is.
18+
19+
Every refusal happens before the engine is asked, so the run stays parked. The engine's own answers (a screen submission missing a required field, a concurrent resume, a run that resumed and then failed) reach the caller with the code, status, message and `details` that `POST /api/v1/automation/:name/runs/:runId/resume` gives for the same result. The two doors now share one classification of the engine's answer. It was moved out of the REST route unchanged, and the route's answers are byte-identical.
20+
21+
**For hosts.** `McpActionBridge` gains an OPTIONAL member, `resumeRun(runId, { values?, confirm? })`. A bridge that implements it gets `resume_run` beside `run_action`, under the same `actions:execute` OAuth scope, on both the HTTP and the stdio transport. A bridge without it is unchanged and does not list the tool. `run_action`'s description names `resume_run` only where it is registered. `@objectstack/runtime`'s MCP bridge implements the member.

‎.changeset/18975-connector-retry-config-and-request-timeout.md‎

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ declared policy onto those options — one execution site, not one per connector
2828
package.
2929

3030
**How the authored value gets there.** `ConnectorProviderContext` gains
31-
`retryConfig`, `connectionTimeoutMs` and `requestTimeoutMs`, read-only and
31+
`retryConfig` and `requestTimeoutMs`, read-only and
3232
resolved from the entry (the automation service parses `retryConfig` so a
3333
factory reads real values instead of re-deriving the schema's defaults), so a
3434
custom provider that does its own I/O can honour them. The built-in HTTP
@@ -56,19 +56,20 @@ policy could never reach. They now go through the same wrapper as
5656
`connector-rest` and `connector-slack`, which gives them the 30s per-attempt
5757
timeout and bounded retry those two already had.
5858

59-
**⚠️ `connectionTimeoutMs` is NOT enforced, deliberately, and is the one thing
59+
**⚠️ `connectionTimeoutMs` is NOT made live, deliberately, and is the one thing
6060
the ruling assumed that measurement refused.** A connector's call is a WHATWG
6161
`fetch`, whose only cancellation surface is one `AbortSignal` over the whole
6262
operation; nothing in that interface observes the connection phase separately.
6363
Bounding time-to-response with it would kill a slow-but-connected upstream the
6464
author meant to allow with a large `requestTimeoutMs` — breaking the very
65-
promise the key makes. So it is carried onto `ConnectorProviderContext` (a
66-
custom provider on a transport that *can* separate the phases may honour it)
67-
and left unenforced by the platform, with the reason recorded at the mapping and
68-
in `packages/spec/liveness/connector.json`, which keeps that one row `dead`. It
69-
is owed a second, narrower ADR-0049 decision: retire it, or re-describe it as
70-
something the platform can enforce.
65+
promise the key makes. So this change leaves it unenforced, with the reason
66+
recorded at the mapping and in `packages/spec/liveness/connector.json`, whose
67+
row for it stays `dead`. That left it owed a second, narrower ADR-0049
68+
decision, and this same release takes it: `connector.connectionTimeoutMs` is
69+
**retired**, and its own entry in this release says what to write instead. The
70+
key never reaches `ConnectorProviderContext` in any release.
7171

72-
Nine of the ten ledger rows flip `dead` → `live` with the consumer site named.
73-
No declaration moves: the connector schema keeps every key, every bound and
74-
every default it had.
72+
Nine of the ten ledger rows flip `dead` → `live` with the consumer site named;
73+
the tenth is `connectionTimeoutMs`, above. This change itself moves no
74+
declaration: it leaves every key, every bound and every default on the
75+
connector schema as it found them.

‎.changeset/19580-retire-connector-connection-timeout-ms.md‎

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,20 @@ The key was bounded (`min(1000).max(300000)`), defaulted (`30000`),
2525
| removed | what to write instead |
2626
| --- | --- |
2727
| `connector.connectionTimeoutMs` (on `Connector` and on `DeclarativeConnectorEntry`, so `stack.connectors[]` and `PUT /meta/connector/:name`) | `requestTimeoutMs` — the deadline the platform keeps, applied as `resilientFetch`'s per-attempt timeout. For a connect-only bound, configure it at a connector provider or upstream gateway on a transport that can separate the phases. |
28-
| `ConnectorProviderContext.connectionTimeoutMs` (handed to every `ConnectorProviderFactory`) | `ctx.requestTimeoutMs`, or the factory's own `providerConfig` where the provider owns the vocabulary. |
28+
| `ConnectorProviderContext.connectionTimeoutMs` (handed to every `ConnectorProviderFactory` — added after `@objectstack/spec@17.4.0` and never in a release, see below) | `ctx.requestTimeoutMs`, or the factory's own `providerConfig` where the provider owns the vocabulary. |
2929
| The `ZodObject` combinators on `ConnectorSchema` and `DeclarativeConnectorEntrySchema` — `.extend()`, `.omit()`, `.pick()`, `.partial()`, `.merge()`, `.strict()`, `.keyof()`, `.safeExtend()` | Both exports are now `z.preprocess` **pipes** (the residue stage below), so those methods no longer exist on them. **Build on the object and re-wrap:** `acceptRetiredDefaultResidue(<your extended object>, { connectionTimeoutMs: 30000 })`, the `EffectiveObjectPermissionSchema` route. ⚠️ `.superRefine()` still *exists* on a pipe but returns a schema with no read-through `shape`, so refine before wrapping, not after. Parsing, `z.input` / `z.infer`, and the read-through `.shape` are unchanged. |
3030

31-
**The one-line fix: delete the key** — and, for a custom provider factory, stop
32-
reading `ctx.connectionTimeoutMs`. `os migrate meta --from 17` lists the
31+
**The one-line fix: delete the key.** `os migrate meta --from 17` lists the
3332
mechanical edits for existing sources; apply them by hand.
3433

34+
The three interface members withdrawn with it were **never in a release**:
35+
`ConnectorProviderContext.connectionTimeoutMs`,
36+
`RestConnectorOptions.connectionTimeoutMs` and
37+
`OpenApiConnectorConfig.connectionTimeoutMs` all entered with `b929e0a662`,
38+
after the `@objectstack/*@17.4.0` tag, and leave in this same release. A factory
39+
or caller built against a released version never saw them; only code written
40+
against an unreleased `main` in between can read them, and it stops.
41+
3542
⚠️ Runtime behaviour is **unchanged for every shipped provider**, because none
3643
ever applied the value: a connector that authored `connectionTimeoutMs: 1000`
3744
made exactly the same calls, with exactly the same deadlines, as one that did
@@ -41,7 +48,8 @@ not. What does change is observable and intended: the def served by
4148
### ⭐ This is NOT the zero-mention retirement shape
4249

4350
Measured with `git grep -n connectionTimeoutMs SHA -- . ':!packages/spec'` at
44-
`origin/main`: **thirteen** non-test source occurrences over seven files in five
51+
`e07843b5a6`, the tree this retirement landed on: **thirteen** non-test source
52+
occurrences over seven files in five
4553
packages — **six reads** (`openapi-connector.ts:242`, `openapi-provider.ts:193`,
4654
`rest-connector.ts:134`, `rest-provider.ts:64`, `plugin.ts:307`,
4755
`plugin.ts:1589`), **four type declarations**, and **three** surviving hardcoded
@@ -73,7 +81,8 @@ ruling that made the siblings live forbids.)
7381
both channels — `tsc` (input type `never`) and the parse, which raises the
7482
prescription itself. `DeclarativeConnectorEntrySchema` carries it too — both
7583
published carriers wrap the same private `ConnectorBaseSchema` — so
76-
`stack.connectors[]` and the `/meta/connector` door refuse it too.
84+
`stack.connectors[]` and the `/meta/connector` door refuse it too: every value
85+
but the retired default `30000`, which the residue stage below strips first.
7786
- **A D2 conversion, `connector-connection-timeout-ms-removed`** — one strip per
7887
`connectors[]` entry, a pure lossless delete. ⭐ The ruling left whether one was
7988
owed to be **measured** ("a D2 conversion only if a stored connector row can
@@ -85,9 +94,11 @@ ruling that made the siblings live forbids.)
8594
written on 17.x therefore replay clean.
8695
- **A D3 semantic entry,
8796
`connector-provider-context-connection-timeout-ms-retired`**, for the withdrawn
88-
`ConnectorProviderContext` member. A provider factory is code: there is no
89-
authored source and no `sys_metadata` row for a conversion to rewrite, so the
90-
removal reaches a factory author as a `tsc` error and as that entry.
97+
`ConnectorProviderContext` member (never in a release, above). A provider
98+
factory is code: there is no authored source and no `sys_metadata` row for a
99+
conversion to rewrite, so the removal reaches a factory author who read it —
100+
possible only against an unreleased `main` — as a `tsc` error and as that
101+
entry.
91102
- **No def leaves.** The key was a bare `z.number()`, never a `ConfigSchema`
92103
shape, so `RETIRED_DEFS_BY_MAJOR[18]` gains nothing — and `api-surface/` and
93104
`json-schema.manifest/` are byte-identical, which is the correct reading for a
@@ -103,19 +114,26 @@ ruling that made the siblings live forbids.)
103114
carries both readings with their trees rather than one undated claim.
104115
- **An `acceptRetiredDefaultResidue` stage** (#12840), `{ connectionTimeoutMs: 30000 }`
105116
on both carriers. The key was `.optional().default(30000)`, so a 17.x parse
106-
materialized it into **every** connector — measured across two builds: the base
107-
build emits it for an entry that authored only `name`/`label`/`type`, and the
108-
tombstoned build refuses that exact object at `connectors.0.connectionTimeoutMs`.
117+
materialized it into **every** connector — measured on both sides of the
118+
retirement: the released
119+
`@objectstack/spec@17.4.0` emits `connectionTimeoutMs: 30000` for an entry that
120+
authored only `name`/`label`/`type`, and the tombstone **without the stage**
121+
refuses that exact object at `connectionTimeoutMs`. With the stage, as it
122+
ships, that object is **accepted and the key stripped** before the tombstone
123+
reads it — on `ConnectorSchema`, `DeclarativeConnectorEntrySchema`, the
124+
`/meta/connector` schema and `stack.connectors[]` alike.
109125
The D2 does **not** discharge the obligation, and the precedent shows it:
110126
`ObjectPermission:allowPurge` carries a D2 **and** the residue stage, for its
111127
own reason (a released toolchain materialized its default into every built
112128
artifact's entries). The reason *here* is a different one — this schema has a
113129
second door: `AutomationEngine.registerConnector` parses `ConnectorSchema` for
114130
a def a plugin or provider factory builds **in code**, where no conversion
115-
ever runs, and all four shipped connector packages put the materialized value
116-
straight into that def literal. So the emitted `30000`
117-
is accepted-and-stripped while `15000` keeps the tombstone's refusal, and
118-
nothing is un-retired: `z.input` stays `never` and the `[RETIRED]` row stays.
131+
ever runs, and in 17.4.0 all four shipped connector packages put that `30000`
132+
straight into the def literal. So the emitted `30000` is accepted-and-stripped,
133+
while every other value (`15000`, `1000`, the string `"30000"`) keeps the
134+
tombstone's refusal — at `connectionTimeoutMs`, or at
135+
`connectors.0.connectionTimeoutMs` inside a stack — and nothing is un-retired:
136+
`z.input` stays `never` and the `[RETIRED]` row stays.
119137
- **No deprecation window** (maintainer 2026-08-27: 「项目在创业阶段,用户也很少,短期不考虑渐进」),
120138
and no staged retirement.
121139

@@ -126,8 +144,7 @@ occurrences of the name at objectui `87af769e`, against a lit control on the sam
126144
command and scope, so no sibling fix or pin bump rides with this.
127145

128146
`Clause-②: yes (narrowing)` — a published authorable key is removed on two
129-
carriers and a published interface member leaves `ConnectorProviderContext`, so
130-
the accept set a consumer writes against narrows. Nothing is widened and nothing
131-
is renamed. Contract-review tier.
147+
carriers, so the accept set a consumer writes against narrows. Nothing is
148+
widened and nothing is renamed. Contract-review tier.
132149

133150
<!-- adr-0087: registered connector-connection-timeout-ms-removed, connector-provider-context-connection-timeout-ms-retired -->

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# 契约复核细则(按面)
22

3-
见 landing-operations.md 〈入队与落地〉的条款②闸门;本文只放复核归属与资格。
3+
见 landing-operations.md 〈入队与落地〉的条款②闸门;本文只放复核归属、资格与等复核标记。
44

55
## 复核归属与资格(按面)
66

@@ -26,3 +26,11 @@
2626
- 例外:纯重生成 head 后移原记录继续管;判据机读已提交树;PR 落 `Regen-provenance:` 行。
2727
- ② PR check 全绿,⛔ 非 required 子集;例外:merge-base 同签名的红不计、按设计而红见 SKILL.md。
2828
- 签名 = 失败步 + 首错行,读 base check runs 的 API ⛔ 不凭口述;主干红止血立单不变。
29+
30+
## 等复核标记 `needs:contract-review`
31+
32+
- 只作等复核标记,⛔ 不是闸门;单载体 = PR,卡上副本在规则外、不是要求。
33+
- 挂:ACCEPT 时条款②任一肢命中而现 head 无同形 PASS 在案 ⇒ 派发席同笔挂于 PR。
34+
- 摘:现 head 同形 PASS 在案 ⇒ 写记录的席同笔摘;PR 合并或关闭 ⇒ 派发席摘;FAIL 不摘。
35+
- 读者 = 维护者过滤 `is:pr is:open label:needs:contract-review` 与席位巡查、交接。
36+
- ⛔ 无 check、workflow、队列守卫或巡查脚本读它;入队只认同形记录,标记有无不改判。

‎.claude/skills/pm-dispatch/references/landing-operations.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
- 路径肢 = diff 触及契约面 `packages/spec/src/**` 非测试,含 error-code-ledger、`*.zod.ts` 契约 schema。
1111
- 声明肢 = 认领评论声明 `Clause-②: yes`,与路径无关;错误的 `no` 是可审计的假申报。
1212
- 交付后复核按面欠 ⛔ 不按车道(五面见 `references/contract-review.md`);双肢命中即 spec 车道。
13-
- 子代理起不来 ⇒ 复核缺席,PR 留 draft 队列外等档;唯一旁路是维护者亲审,逐次为准。
13+
- 子代理起不来 ⇒ 复核缺席,PR 带 `needs:contract-review` 留 draft 队列外;旁路仅维护者逐次亲审
1414
- PASS ⇒ ready、auto-merge;FAIL ⇒ 补丁轮;⛔ 免复核不放行。
1515
- DELIBERATE CORRECTION 红(`check-empty-changeset`):同 head 达档复核 PASS 记录即确认,⛔ 不等维护者。
1616
- 记录须点名被改 note、逐句判改写句,缺一不算;算即按 SKILL.md 三条件带红入队,门禁不改。

0 commit comments

Comments
 (0)