Skip to content

Commit dadb43f

Browse files
os-zhuangclaude
andauthored
refactor(spec,client,metadata-protocol,runtime)!: 退役 workflow 服务槽位与 graphql 残留 (#4451) (#4473)
* refactor(spec,client,metadata-protocol,runtime)!: retire the workflow service slot and the stray graphql entry (#4451) The `workflow` slot was ADR-0078's silently-inert declaration at every layer at once, and had been since it was written: a `CoreServiceName` nothing ever registered or resolved, an `IWorkflowService` contract with zero implementations, a `WorkflowProtocol` whose three methods no code ever provided, an `ApiRoutes.workflow` field no builder could truthfully populate, and an `/api/v1/workflow` advertisement for a path no host ever mounted. The pre-#3586 `DEFAULT_DISPATCHER_ROUTES` already listed that path among "routes that never existed"; ADR-0115 Evidence 5 verified the slot itself across both repositories — "no code in this repository resolves either slot", the only touches being plugin-dev's since-retired stub probe and the generic discovery walk. Nothing here is being taken away from anyone, because the capability the slot promised has been live elsewhere for majors: record state machines are enforced by the `state_machine` validation rule (`StateMachineSchema` stays authorable on the object), approvals are first-class flow nodes on the approvals runtime (ADR-0019 folded the standalone approval process into Flow), and record-triggered automation is lifecycle hooks + `record_change` flows. That is why this is a removal rather than an enforcement: there is no feature to build, only a second name for three that exist. Removed with it: the `graphql` entry in `CORE_SERVICE_PROVIDER` and the `graphql: { route: '/graphql' }` discovery entry. `graphql` was never a `CoreServiceName` — so nothing could occupy the slot and the entry was unreachable — and it declared a path the dispatcher had already dropped as out of the product plan (#2462 follow-on). The provider guard only checks that every SLOT has an entry, never that every entry is a slot, which is how the stray sat unchallenged. Direct cut inside the 17.x rc window, per ADR-0115 D5. The retirement kit: a `workflow-service-slot-retired` SemanticMigration on the major-17 step carries the FROM -> TO into spec-changes.json, the generated upgrade guide and the `spec_changes` MCP tool. These are TS/API surfaces and discovery RESPONSE fields — never stored in stack metadata — so there is no load-path conversion and nothing for `os migrate meta` to rewrite. The 21 `authorable-surface.json` baseline lines and 7 `json-schema.manifest.json` entries are dropped deliberately in the same change, following the plugin-runtime precedent: a `retiredKey()` prescription earns its keep at a parse the author reaches, and nothing parses these shapes any more. `os explain workflow` is kept as a redirect topic rather than deleted, mirroring content/docs/automation/workflows.mdx. It had been teaching a shape the spec never had (`states[]` / `transitions[]` / `approvers`); it now names the three live mechanisms instead. * docs: retire the workflow slot from four more hand-written pages (#4451) The docs-drift check on PR #4473 earned its keep: my symbol-shaped grep (`IWorkflowService`, `WorkflowProtocol`, `api/v1/workflow`) found three pages and missed four PROSE mentions that describe the same retired slot in words. - `api/plugin-endpoints.mdx` documented three `/workflow/*` routes under a "not yet mounted … return 404 today" caveat. The caveat was already the tell: routes that 404 for the whole life of the declaration are not "not yet", and the slot behind them is gone now. The section becomes a redirect naming the three live mechanisms. - `kernel/services-checklist.mdx` carried it in three more places — the legend's 36-method count (now 33), the `null`-provider explanation, and a full "6. workflow Service" section still describing the three methods as pending rather than removed. The remaining `workflow` hits in `content/docs` are the ordinary English word (approval workflow, build workflow, GitHub Actions workflows) and stay. --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 9ca2d85 commit dadb43f

35 files changed

Lines changed: 327 additions & 597 deletions
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
---
2+
"@objectstack/spec": major
3+
"@objectstack/client": major
4+
"@objectstack/metadata-protocol": minor
5+
"@objectstack/runtime": minor
6+
---
7+
8+
refactor(spec,client,metadata-protocol,runtime)!: retire the workflow service slot — declared end to end, implemented nowhere (#4451)
9+
10+
The `workflow` slot was ADR-0078's silently-inert declaration at every layer at
11+
once: a `CoreServiceName` nothing ever registered or resolved (ADR-0115
12+
Evidence 5 — "no code in this repository resolves either slot", verified across
13+
both repositories), an `IWorkflowService` contract with zero implementations, a
14+
`WorkflowProtocol` whose three methods no code ever provided, a discovery
15+
`routes.workflow` field no builder could truthfully populate, and a
16+
`/api/v1/workflow` advertisement for a path no host ever mounted (the
17+
pre-#3586 `DEFAULT_DISPATCHER_ROUTES` already listed it among routes that
18+
never existed). The capability it promised is live elsewhere and has been for
19+
majors: record state machines are enforced by the `state_machine` validation
20+
rule, approvals are first-class flow nodes on the approvals runtime
21+
(ADR-0019), and record-triggered automation is lifecycle hooks +
22+
`record_change` flows (`service-automation`).
23+
24+
FROM → TO:
25+
26+
- `CoreServiceName 'workflow'` / `ServiceRequirementDef.workflow` /
27+
`CORE_SERVICE_PROVIDER['workflow']` → removed; there is no slot to fill.
28+
- `IWorkflowService` (`@objectstack/spec/contracts`) → removed; no
29+
implementation ever existed. Register nothing — use the mechanisms above.
30+
- `WorkflowProtocol` + `GetWorkflowConfigRequest/Response`,
31+
`WorkflowState`, `GetWorkflowStateRequest/Response`,
32+
`WorkflowTransitionRequest/Response` (`@objectstack/spec/api`) → removed,
33+
along with the seven published JSON schemas. Delete the import; nothing
34+
ever answered these shapes.
35+
- Discovery `routes.workflow` / `services.workflow` / `features.workflow`
36+
(metadata-protocol + runtime builders) → absent. A reader keying on them
37+
only ever saw `unavailable` / `false`; delete the read.
38+
- `RouterConfig.mounts.workflow` → removed; there was never a surface to
39+
mount at it.
40+
- `RestApiRouteCategory 'workflow'` → removed; categorize automation-adjacent
41+
routes as `'automation'`.
42+
- `@objectstack/client` re-exports of the four workflow types → removed with
43+
their source. (The `client.workflow.*` methods were already removed earlier
44+
in the v17 cycle — this retires the types they returned.)
45+
- Also removed: the stray `graphql` entry in `CORE_SERVICE_PROVIDER` and the
46+
`graphql: { route: '/graphql' }` discovery entry — `graphql` was never a
47+
`CoreServiceName`, and the dispatcher had already dropped `/graphql` as out
48+
of the product plan (#2462 follow-on).
49+
50+
The retirement kit: the `workflow-service-slot-retired` semantic migration
51+
(major 17) carries this prescription into `spec-changes.json`, the generated
52+
upgrade guide and the `spec_changes` MCP tool. These are TS/API surfaces and a
53+
discovery response field — never stored in stack metadata — so there is no
54+
load-path conversion and nothing for `os migrate meta` to rewrite; the
55+
21 `authorable-surface.json` baseline lines and 7 `json-schema.manifest.json`
56+
entries for the deleted schemas are dropped deliberately in the same change
57+
(the plugin-runtime precedent: a prescription nobody can receive is noise —
58+
nothing parses these shapes any more).

content/docs/api/plugin-endpoints.mdx

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,21 @@ Authenticate with email and password (better-auth's email sign-in route, mounted
2626

2727
The following endpoints become available when the corresponding plugin is installed and registered with the kernel. Use the discovery `services` map to check availability.
2828

29-
### Workflow (`/workflow`) — Plugin Required
29+
### Workflow (`/workflow`) — removed in v17
3030

3131
<Callout type="warn">
32-
Not yet mounted. These routes are declared in the API protocol but the core dispatcher registers no `/workflow` handler and no bundled plugin provides a `workflow` service (only an in-memory dev stub), so they return **404** today. `discovery.services.workflow` reports `unavailable` in a standard install.
32+
There is no workflow endpoint, and there is no `workflow` service slot. The
33+
three routes documented here were declared in the API protocol and served by
34+
nothing — no dispatcher handler, no plugin — so they 404'd for the whole life
35+
of the declaration. The slot, the `WorkflowProtocol` methods behind it and the
36+
discovery fields that reported it were all retired in v17 ([#4451](https://github.com/objectstack-ai/objectstack/issues/4451)).
37+
Use the live mechanisms instead: an object validation rule of type
38+
`state_machine` for lifecycle transitions, an `approval` flow node for human
39+
approval pauses (ADR-0019), and lifecycle hooks / `record_change` flows for
40+
record-triggered automation.
3341
</Callout>
3442

35-
| Method | Endpoint | Description |
36-
|:-------|:---------|:------------|
37-
| GET | `/workflow/:object/config` | Get workflow configuration |
38-
| GET | `/workflow/:object/:recordId/state` | Get record's workflow state |
39-
| POST | `/workflow/:object/:recordId/transition` | Execute state transition |
40-
41-
Approve/reject are **not** workflow routes (ADR-0019): approval is a flow node, and decisions are recorded on the approvals runtime via `POST /approvals/requests/:id/approve` and `POST /approvals/requests/:id/reject`.
43+
Approve/reject were never workflow routes (ADR-0019): approval is a flow node, and decisions are recorded on the approvals runtime via `POST /approvals/requests/:id/approve` and `POST /approvals/requests/:id/reject`.
4244

4345
### Automation (`/automation`) — Plugin Required
4446

content/docs/kernel/services-checklist.mdx

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,14 @@ The ObjectStack protocol defines **16 kernel services** registered via the `Core
2424
- ✅ Implemented — the 17 kernel-provided protocol methods (`DataProtocol` 9 + `MetadataProtocol` 8)
2525
- ⚠️ Framework — the slot is filled by the kernel's in-memory fallback: real reads and
2626
writes, no persistence (self-declares `degraded`, ADR-0076 D12)
27-
- ❌ Plugin Required — the remaining 36 methods declared across the other per-domain
27+
- ❌ Plugin Required — the remaining 33 methods declared across the other per-domain
2828
contracts (`analytics` 2, `automation` 1, `packages` 6, `views` 5, `permissions` 3,
29-
`workflow` 3, `realtime` 6, `notification` 7, `i18n` 3). *Declared* is not *routed*:
29+
`realtime` 6, `notification` 7, `i18n` 3). *Declared* is not *routed*:
3030
`packages` is answered kernel-side by the `/packages` dispatcher domain over the
31-
ObjectQL registry, `i18n` has a kernel in-memory fallback, and `views` / `permissions` /
32-
`workflow` have no implementation anywhere — the rest wait on whatever fills the slot
31+
ObjectQL registry, `i18n` has a kernel in-memory fallback, and `views` /
32+
`permissions` have no implementation anywhere — the rest wait on whatever fills
33+
the slot. (`workflow`'s 3 methods were the fourth such group; the whole slot
34+
retired in v17, [#4451](https://github.com/objectstack-ai/objectstack/issues/4451).)
3335
</Callout>
3436

3537
---
@@ -90,10 +92,12 @@ installable optional package, so they need no remedy (`NO_REMEDY_SLOTS` in the
9092
guard script). And `null` there does **not** always mean "nothing ships" — it
9193
means "no name belongs in an `Install X` sentence", which covers two cases:
9294

93-
- **Nothing provides the slot at all**`search`, `workflow` (and the retired
94-
`graphql`). Discovery says exactly that rather than naming a plausible
95-
package: `No implementation ships for the '<slot>' slot — register a service
96-
under it to enable`.
95+
- **Nothing provides the slot at all**`search`. Discovery says exactly that
96+
rather than naming a plausible package: `No implementation ships for the
97+
'<slot>' slot — register a service under it to enable`. (`workflow` and the
98+
never-real `graphql` sat here too until both were retired outright in v17,
99+
[#4451](https://github.com/objectstack-ai/objectstack/issues/4451) — a slot
100+
nothing fills and nothing consumes is better removed than explained.)
97101
- **A provider exists but cannot be installed**`ai`. `@objectstack/service-ai`
98102
registers the slot in `objectstack-ai/cloud` and is `private: true`.
99103

@@ -333,13 +337,18 @@ the domain answers **501** with that remedy spelled out, not a generic "install
333337
plugin".
334338
</Callout>
335339

336-
### 6. workflow Service — 3 methods ❌ Nothing ships
337-
`getWorkflowConfig`, `getWorkflowState`, `workflowTransition`
338-
State machine transitions. No package registers the `workflow` slot
339-
(`CORE_SERVICE_PROVIDER.workflow` is `null`). Approve/reject are not workflow
340-
methods — per ADR-0019 they moved to the request-id-based approvals API under
341-
`/api/v1/approvals` (`POST /requests/:id/{approve,reject,recall}`, served by
342-
`@objectstack/plugin-approvals`).
340+
### 6. workflow Service — retired in v17
341+
The slot, its `IWorkflowService` contract and the three `WorkflowProtocol`
342+
methods (`getWorkflowConfig`, `getWorkflowState`, `workflowTransition`) were
343+
removed in [#4451](https://github.com/objectstack-ai/objectstack/issues/4451):
344+
nothing ever registered or resolved the slot (ADR-0115 Evidence 5), no method
345+
ever had an implementation, and no host ever mounted `/api/v1/workflow`. The
346+
three capabilities it named are live elsewhere — state-machine transitions are
347+
an object validation rule of type `state_machine`, approvals are `approval`
348+
flow nodes on the approvals runtime (ADR-0019 — decisions via
349+
`POST /api/v1/approvals/requests/:id/{approve,reject,recall}`, served by
350+
`@objectstack/plugin-approvals`), and record-triggered automation is lifecycle
351+
hooks + `record_change` flows.
343352

344353
### 7. automation Service — 1 method ✅ `@objectstack/service-automation`
345354
`triggerAutomation`
@@ -502,11 +511,16 @@ a package that cannot be installed is a dead end, which is why
502511
| Slot | State |
503512
|:-------|:------------|
504513
| **ui** | Nothing registers the slot. `ViewProtocol`'s five methods are declared and unrouted; view CRUD runs through `/api/v1/meta`, and `/api/v1/ui/view/:object` is served by the `protocol` service. |
505-
| **workflow** | Nothing ships. `WorkflowProtocol`'s three methods have no implementation and no consumer. |
506514
| **search** | Nothing ships. Contract and engine enum exist in `@objectstack/spec` only. |
507515
| **ai** | Nothing in this repo — `service-ai` (chat, completion, models, conversations) is Cloud/EE. |
508516
| **realtime transport** | The service exists but no WebSocket/SSE route is mounted, so `routes.realtime` is deliberately never advertised. |
509517

518+
The `workflow` slot used to sit in this table ("nothing ships, no consumer").
519+
It was retired outright in v17 (#4451, per ADR-0115 Evidence 5): the
520+
capability lives in `state_machine` validation rules, approval flow nodes
521+
(ADR-0019) and `record_change` flows, so there is nothing left for a slot to
522+
promise.
523+
510524
---
511525

512526
## Plugin Implementation Pattern

content/docs/protocol/kernel/http-protocol.mdx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,13 @@ GET /api/v1/discovery HTTP/1.1
4747
"packages": "/api/v1/packages",
4848
"auth": "/api/v1/auth",
4949
"ui": "/api/v1/ui",
50-
"storage": "/api/v1/storage",
51-
"graphql": "/api/v1/graphql"
50+
"storage": "/api/v1/storage"
5251
},
5352
"services": {
5453
"data": { "enabled": true, "status": "available", "route": "/api/v1/data", "provider": "objectql" },
5554
"metadata": { "enabled": true, "status": "available", "route": "/api/v1/meta", "provider": "objectql" },
5655
"auth": { "enabled": true, "status": "available", "route": "/api/v1/auth", "provider": "@objectstack/plugin-auth" },
57-
"workflow": { "enabled": false, "status": "unavailable", "message": "No implementation ships for the 'workflow' slot — register a service under it to enable" },
56+
"search": { "enabled": false, "status": "unavailable", "message": "No implementation ships for the 'search' slot — register a service under it to enable" },
5857
"ai": { "enabled": false, "status": "unavailable", "message": "Provided by @objectstack/service-ai in ObjectStack Cloud/Enterprise — no implementation ships in the open framework" }
5958
},
6059
"locale": {

content/docs/references/api/discovery.mdx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ const result = ApiRoutes.parse(data);
5252
| **storage** | `string` | optional | e.g. /api/v1/storage |
5353
| **analytics** | `string` | optional | e.g. /api/v1/analytics |
5454
| **packages** | `string` | optional | e.g. /api/v1/packages |
55-
| **workflow** | `string` | optional | e.g. /api/v1/workflow |
5655
| **approvals** | `string` | optional | e.g. /api/v1/approvals |
5756
| **realtime** | `string` | optional | e.g. /api/v1/realtime |
5857
| **notifications** | `string` | optional | e.g. /api/v1/notifications |

content/docs/references/api/dispatcher.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ const result = DispatcherConfig.parse(data);
5151

5252
| Property | Type | Required | Description |
5353
| :--- | :--- | :--- | :--- |
54-
| **routes** | `{ prefix: string; service: Enum<'metadata' \| 'data' \| 'auth' \| 'file-storage' \| 'search' \| 'cache' \| 'queue' \| 'automation' \| 'analytics' \| 'realtime' \| 'job' \| 'notification' \| 'ai' \| 'i18n' \| 'ui' \| 'workflow'>; authRequired: boolean; criticality: Enum<'required' \| 'core' \| 'optional'>; … }[]` || Route-to-service mappings |
54+
| **routes** | `{ prefix: string; service: Enum<'metadata' \| 'data' \| 'auth' \| 'file-storage' \| 'search' \| 'cache' \| 'queue' \| 'automation' \| 'analytics' \| 'realtime' \| 'job' \| 'notification' \| 'ai' \| 'i18n' \| 'ui'>; authRequired: boolean; criticality: Enum<'required' \| 'core' \| 'optional'>; … }[]` || Route-to-service mappings |
5555
| **fallback** | `Enum<'404' \| 'proxy' \| 'custom'>` || Behavior when no route matches |
5656
| **proxyTarget** | `string` | optional | Proxy target URL when fallback is "proxy" |
5757

@@ -91,7 +91,7 @@ Route-resolution failure mode emitted in `error.code`
9191
| Property | Type | Required | Description |
9292
| :--- | :--- | :--- | :--- |
9393
| **prefix** | `string` || URL path prefix for routing (e.g. /api/v1/data) |
94-
| **service** | `Enum<'metadata' \| 'data' \| 'auth' \| 'file-storage' \| 'search' \| 'cache' \| 'queue' \| 'automation' \| 'analytics' \| 'realtime' \| 'job' \| 'notification' \| 'ai' \| 'i18n' \| 'ui' \| 'workflow'>` || Target core service name |
94+
| **service** | `Enum<'metadata' \| 'data' \| 'auth' \| 'file-storage' \| 'search' \| 'cache' \| 'queue' \| 'automation' \| 'analytics' \| 'realtime' \| 'job' \| 'notification' \| 'ai' \| 'i18n' \| 'ui'>` || Target core service name |
9595
| **authRequired** | `boolean` || Whether authentication is required |
9696
| **criticality** | `Enum<'required' \| 'core' \| 'optional'>` || Service criticality level for unavailability handling |
9797
| **permissions** | `string[]` | optional | Required permissions for this route namespace |

0 commit comments

Comments
 (0)