Skip to content

Commit 41edf73

Browse files
fix(spec, runtime): endpoint refusal, hint and describes no longer name 17.x as the running major (#24699)
Closes #24654 Clause-②: no ## What changed Four customer-facing sentences said "in 17.x", but 18.x is the running major and the behaviour they describe has not changed. Each one now says what the runtime does without naming a version, so it cannot go stale again at 19.0. ⛔ No behaviour change, no new gate. | where | reader | now reads | |:--|:--|:--| | `packages/spec/src/api/endpoint-publish-gate.ts` | the `os build` / `os validate` refusal for a `script` / `proxy` endpoint | "This runtime executes only 'object_operation' and 'flow' endpoints. `script` is refused because ..." | | `packages/runtime/src/endpoint-executor.ts` (`hint`) | the API caller, in the `unsupported` answer | "This runtime executes only 'object_operation' and 'flow' endpoints. 'script' and 'proxy' are rejected at publish ..." (word for word the same first sentence as the spec refusal) | | `packages/spec/src/api/endpoint.zod.ts` `type` describe | readers of the published schema | "Implementation type — this runtime EXECUTES only 'object_operation' and 'flow'. ..." | | `packages/spec/src/api/endpoint.zod.ts` `ApiMapping.transform` describe | the same | "Transformation function name — NOT EXECUTED by this runtime, and publish REJECTS the key ..." | Same PR, maintainer-facing JSDoc: `packages/runtime/src/api-mapping.ts` (the mirrored `transform` describe row) and `packages/runtime/src/endpoint-executor.ts` (the `params` "always `{}`" notes and the `planEndpointTarget` doc). **Bounded in-place fix, named here:** `packages/spec/src/api/endpoint.zod.ts`, `ApiEndpoint.target` describe, said the Script Name / Proxy URL spellings "stay unreachable in 17.x". It is the same defect class, in the same file, on the same schema, and the same gate family covers it (`check:docs`). It now reads "stay unreachable on this runtime". Evidence: the regenerated `content/docs/references/api/endpoint.mdx` row for `target`. Pins: no test asserted the old text (`git grep -nE "execute in 17|EXECUTED in 17|EXECUTE in 17"` over `*.test.ts` returns 0 hits). Two test TITLES in `packages/runtime/src/endpoint-executor.test.ts` repeated "in 17.x". They were reworded and their assertions are unchanged. Regenerated with repo tooling: `pnpm --filter @objectstack/spec check:generated --fix` found `check:docs` stale (1 of 14), regenerated `content/docs/references/api/endpoint.mdx` and re-checked it green. Changeset: `.changeset/24654-endpoint-text-version-free.md`, `patch` for `@objectstack/spec` and `@objectstack/runtime`. ## Verification (head `f8e3a95abf`, which includes a merge of `origin/main` `0431002104`) - `pnpm --filter @objectstack/spec build && pnpm --filter @objectstack/spec check:generated`, post-merge: "All 14 generated artifacts are up to date", lock VERDICT command-exit 0. - `pnpm --filter @objectstack/spec test` at `f8e3a95abf`: 701 files passed, 1 skipped; 21347 tests passed. `pnpm --filter @objectstack/spec typecheck`: exit 0. - `pnpm --filter @objectstack/runtime test && pnpm --filter @objectstack/runtime typecheck` at `018b472bd5` (the change itself, before the merge): 425 files passed; 6678 tests passed, 19 skipped; typecheck exit 0. The merge brought no change to the files this PR edits. The runtime suite was not re-run on the merge head; CI runs it there. - `node scripts/pm/dispatch-gates.mjs --commands` derived 110 commands at `f8e3a95abf`. All of them were run, and `--ran` reconciles: "110 derived famil(ies) accounted for — 109 run, 1 NOT-MEASURED". - NOT MEASURED: `check:dual-build-cjs-loads`. Reason: PREREQUISITE NOT MET (exit 3). It needs a full workspace build, and this worktree only built the runtime dependency closure. - `check:lean-entry-closure` first exited 1 because `@objectstack/spec` dist was missing: a spec rebuild was running at the same time. Re-run after the build finished: exit 0. ## Acceptance notes Other "in 17.x" sites outside this card's file surface. None of them was edited here: - `packages/rest/src/openapi-endpoints.ts:218`: `unsupportedReason` says "which this runtime does not execute in 17.x". This is customer-facing OpenAPI output for the same endpoint family. It is reported to the seat as a finding. - `skills/objectstack-api/SKILL.md:163`: "`script` / `proxy` do not execute in 17.x". This is the published skill text, a Tier H surface. It is reported to the seat. - `packages/spec/src/stack.zod.ts:845` (JSDoc), `packages/spec/src/contracts/metadata-service.ts:210` (JSDoc, with the test title `metadata-service.test.ts:485`), `packages/spec/src/automation/flow-node-expression-paths.ts:126` (JSDoc), `packages/spec/liveness/api.json:15` (ledger note): maintainer-facing only. --- _Generated by [Claude Code](https://claude.ai/code/session_01D6F9axfoPzR7t8G1J7Ytzz)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 54159fd commit 41edf73

7 files changed

Lines changed: 27 additions & 16 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
'@objectstack/spec': patch
3+
'@objectstack/runtime': patch
4+
---
5+
6+
fix(spec, runtime): the endpoint publish refusal, the runtime's `unsupported` hint and the endpoint schema describes no longer say "in 17.x"
7+
8+
Clause-②: no (no accept set, served body shape or authz answer moves: only the wording of a refusal, a hint and three schema descriptions changes)
9+
10+
- These sentences named 17.x as the running major. On an 18.x runtime that made the refusal read as if the author were on the wrong version. Each one now states what the runtime does without naming a version: "This runtime executes only 'object_operation' and 'flow' endpoints." The behaviour is unchanged: `script` and `proxy` endpoints are still refused at publish, and `ApiMapping.transform` is still rejected.
11+
- Where it shows: the `os build` / `os validate` refusal for a `script` or `proxy` endpoint, the `hint` in the runtime's `unsupported` answer (word for word the same sentence), and the published `ApiEndpoint.type`, `ApiEndpoint.target` and `ApiMapping.transform` descriptions in the JSON Schema and reference docs.

‎content/docs/references/api/endpoint.mdx‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ const result = ApiEndpointSchema.parse(data);
3333
| **method** | `Enum<'GET' \| 'POST' \| 'PUT' \| 'DELETE' \| 'PATCH' \| 'HEAD' \| 'OPTIONS'>` | ✅ | HTTP Method |
3434
| **summary** | `string` | optional | |
3535
| **description** | `string` | optional | |
36-
| **type** | `Enum<'flow' \| 'script' \| 'object_operation' \| 'proxy'>` | ✅ | Implementation type — only 'object_operation' and 'flow' EXECUTE in 17.x. 'script' and 'proxy' stay in the frozen vocabulary and are rejected at publish, not parsed and ignored: express script logic as a flow whose script node runs your registered function, and an outbound call as a flow using a declared connector |
37-
| **target** | `string` | optional | Target Flow ID, per `type` — REQUIRED at publish for `type: 'flow'` (the gate refuses a flow endpoint that names no target flow) and UNREAD for `type: 'object_operation'`, so do not write it there: that endpoint is addressed by `objectParams.object` / `.operation`, and a `target` beside them is a dead string nothing checks against `objectParams.object` (made the key optional for exactly that reason). The vocabulary's other spellings — a Script Name or Proxy URL — stay unreachable in 17.x, since publish rejects `type: 'script'` and `type: 'proxy'` |
36+
| **type** | `Enum<'flow' \| 'script' \| 'object_operation' \| 'proxy'>` | ✅ | Implementation type — this runtime EXECUTES only 'object_operation' and 'flow'. 'script' and 'proxy' stay in the frozen vocabulary and are rejected at publish, not parsed and ignored: express script logic as a flow whose script node runs your registered function, and an outbound call as a flow using a declared connector |
37+
| **target** | `string` | optional | Target Flow ID, per `type` — REQUIRED at publish for `type: 'flow'` (the gate refuses a flow endpoint that names no target flow) and UNREAD for `type: 'object_operation'`, so do not write it there: that endpoint is addressed by `objectParams.object` / `.operation`, and a `target` beside them is a dead string nothing checks against `objectParams.object` (made the key optional for exactly that reason). The vocabulary's other spellings — a Script Name or Proxy URL — stay unreachable on this runtime, since publish rejects `type: 'script'` and `type: 'proxy'` |
3838
| **objectParams** | `{ object?: string; operation?: Enum<'find' \| 'get' \| 'create' \| 'update' \| 'delete'> }` | optional | For object_operation type |
3939
| **inputMapping** | `{ source: string; target: string; transform?: string }[]` | optional | Map Request Body to Internal Params |
4040
| **outputMapping** | `{ source: string; target: string; transform?: string }[]` | optional | Map Internal Result to Response Body |
@@ -56,15 +56,15 @@ const result = ApiEndpointSchema.parse(data);
5656
| :--- | :--- | :--- | :--- |
5757
| **source** | `string` | ✅ | Source field/path |
5858
| **target** | `string` | ✅ | Target field/path |
59-
| **transform** | `string` | optional | Transformation function name — NOT EXECUTED in 17.x, and publish REJECTS the key: there is no transformation-function registry anywhere in the platform, so it stays in the frozen vocabulary and is refused rather than parsed and ignored (E7). A mapping entry moves and renames fields by dot path and nothing more — shape the value where it is produced instead (a flow endpoint whose flow computes it, or a formula field on the object) |
59+
| **transform** | `string` | optional | Transformation function name — NOT EXECUTED by this runtime, and publish REJECTS the key: there is no transformation-function registry anywhere in the platform, so it stays in the frozen vocabulary and is refused rather than parsed and ignored (E7). A mapping entry moves and renames fields by dot path and nothing more — shape the value where it is produced instead (a flow endpoint whose flow computes it, or a formula field on the object) |
6060

6161
### Nested Shape: `ApiEndpoint.outputMapping[number]`
6262

6363
| Property | Type | Required | Description |
6464
| :--- | :--- | :--- | :--- |
6565
| **source** | `string` | ✅ | Source field/path |
6666
| **target** | `string` | ✅ | Target field/path |
67-
| **transform** | `string` | optional | Transformation function name — NOT EXECUTED in 17.x, and publish REJECTS the key: there is no transformation-function registry anywhere in the platform, so it stays in the frozen vocabulary and is refused rather than parsed and ignored (E7). A mapping entry moves and renames fields by dot path and nothing more — shape the value where it is produced instead (a flow endpoint whose flow computes it, or a formula field on the object) |
67+
| **transform** | `string` | optional | Transformation function name — NOT EXECUTED by this runtime, and publish REJECTS the key: there is no transformation-function registry anywhere in the platform, so it stays in the frozen vocabulary and is refused rather than parsed and ignored (E7). A mapping entry moves and renames fields by dot path and nothing more — shape the value where it is produced instead (a flow endpoint whose flow computes it, or a formula field on the object) |
6868

6969
### Nested Shape: `ApiEndpoint.rateLimit`
7070

@@ -85,7 +85,7 @@ const result = ApiEndpointSchema.parse(data);
8585
| :--- | :--- | :--- | :--- |
8686
| **source** | `string` | ✅ | Source field/path |
8787
| **target** | `string` | ✅ | Target field/path |
88-
| **transform** | `string` | optional | Transformation function name — NOT EXECUTED in 17.x, and publish REJECTS the key: there is no transformation-function registry anywhere in the platform, so it stays in the frozen vocabulary and is refused rather than parsed and ignored (E7). A mapping entry moves and renames fields by dot path and nothing more — shape the value where it is produced instead (a flow endpoint whose flow computes it, or a formula field on the object) |
88+
| **transform** | `string` | optional | Transformation function name — NOT EXECUTED by this runtime, and publish REJECTS the key: there is no transformation-function registry anywhere in the platform, so it stays in the frozen vocabulary and is refused rather than parsed and ignored (E7). A mapping entry moves and renames fields by dot path and nothing more — shape the value where it is produced instead (a flow endpoint whose flow computes it, or a formula field on the object) |
8989

9090

9191
---

‎packages/runtime/src/api-mapping.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
* | `outputMapping` | *Map Internal Result to Response Body* |
2323
* | `ApiMapping.source` | *Source field/path* |
2424
* | `ApiMapping.target` | *Target field/path* |
25-
* | `ApiMapping.transform` | *Transformation function name — NOT EXECUTED in 17.x, and publish REJECTS the key: there is no transformation-function registry anywhere in the platform, so it stays in the frozen vocabulary and is refused rather than parsed and ignored (#5040 E7). A mapping entry moves and renames fields by dot path and nothing more — shape the value where it is produced instead (a flow endpoint whose flow computes it, or a formula field on the object)* |
25+
* | `ApiMapping.transform` | *Transformation function name — NOT EXECUTED by this runtime, and publish REJECTS the key: there is no transformation-function registry anywhere in the platform, so it stays in the frozen vocabulary and is refused rather than parsed and ignored (#5040 E7). A mapping entry moves and renames fields by dot path and nothing more — shape the value where it is produced instead (a flow endpoint whose flow computes it, or a formula field on the object)* |
2626
*
2727
* Five short sentences, and everything below is the MINIMAL faithful reading of
2828
* them — `transform`'s now says out loud, at the point of authoring, what this

‎packages/runtime/src/endpoint-executor.test.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ describe('buildEndpointExecutionContext — a projection, not an interpretation'
135135
expect(ctx.remoteAddress).toBe('203.0.113.9');
136136
});
137137

138-
it('carries the match: the parsed endpoint, and `params` — always {} in 17.x', () => {
138+
it('carries the match: the parsed endpoint, and `params` — always {}', () => {
139139
const ep = endpoint({});
140140
const ctx = contextFor(ep);
141141
expect(ctx.endpoint).toBe(ep);
@@ -204,7 +204,7 @@ describe('planEndpointTarget — the unsupported subset is enumerated once', ()
204204
expect((plan as any).reason).toContain(expected);
205205
});
206206

207-
it.each(['script', 'proxy'])('refuses `%s` — declared by the vocabulary, not executed in 17.x', (type) => {
207+
it.each(['script', 'proxy'])('refuses `%s` — declared by the vocabulary, not executed by this runtime', (type) => {
208208
const plan = planEndpointTarget(endpoint({ type, objectParams: undefined }));
209209
expect(plan.kind).toBe('unsupported');
210210
// The reason names the type so an author is not left guessing which of

‎packages/runtime/src/endpoint-executor.ts‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ import type { HttpProtocolContext } from './http-dispatcher.js';
7575
*
7676
* `params` is deliberately NOT here: the transport's own route params are
7777
* meaningless for a path no route matched, and the endpoint's path parameters
78-
* arrive on {@link ApiEndpointMatch.params} instead (always `{}` in 17.x — the
78+
* arrive on {@link ApiEndpointMatch.params} instead (always `{}` — the
7979
* frozen vocabulary defines no template syntax).
8080
*/
8181
export interface EndpointExecutionRequest {
@@ -133,7 +133,7 @@ export interface EndpointExecutionContext {
133133
/** Matched endpoint, schema defaults materialized. */
134134
endpoint: ApiEndpoint;
135135
/**
136-
* Path parameters from the match. **Always `{}` in 17.x** and deliberately
136+
* Path parameters from the match. **Always `{}`** and deliberately
137137
* NOT consulted when resolving a record id — the id comes from `query.id`
138138
* (#5040 §2), because reading it from a template segment would require a
139139
* template syntax the vocabulary does not define.
@@ -225,7 +225,7 @@ export type EndpointTargetPlan =
225225
/**
226226
* Classify a declaration into the pipeline that will run it.
227227
*
228-
* `script` and `proxy` are declared by the vocabulary and NOT executed in 17.x,
228+
* `script` and `proxy` are declared by the vocabulary and NOT executed by this runtime,
229229
* on purpose (#5040 §7-3, restated in the E5 card): the automation contract's
230230
* `execute` doc mentions "flow or script" but nothing in this repo verifies a
231231
* script target is reachable that way, and `proxy` is an entirely new outbound
@@ -265,7 +265,7 @@ export function planEndpointTarget(endpoint: ApiEndpoint): EndpointTargetPlan {
265265
return {
266266
kind: 'unsupported',
267267
reason: `Endpoint '${endpoint.name}' declares type '${endpoint.type}', which this runtime does not execute.`,
268-
hint: "Only 'object_operation' and 'flow' endpoints execute in 17.x. 'script' and 'proxy' "
268+
hint: "This runtime executes only 'object_operation' and 'flow' endpoints. 'script' and 'proxy' "
269269
+ 'are rejected at publish pending their own rulings — script reachability through the '
270270
+ 'automation service is unverified, and proxy is an outbound (SSRF) surface.',
271271
};

‎packages/spec/src/api/endpoint-publish-gate.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ function targetGate(
368368
path: at('type'),
369369
message:
370370
`${named} declares \`type: '${endpoint.type}'\`, which this runtime does not execute. `
371-
+ "Only 'object_operation' and 'flow' endpoints execute in 17.x. `script` is refused because "
371+
+ "This runtime executes only 'object_operation' and 'flow' endpoints. `script` is refused because "
372372
+ 'no path in this repo verifies that a script target is reachable through the automation '
373373
+ 'service — express the logic as a flow (`type: \'flow\'`) whose script node runs your '
374374
+ 'registered function. `proxy` is refused because forwarding to an arbitrary outbound URL is '

‎packages/spec/src/api/endpoint.zod.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { retiredKey } from '../shared/retired-key';
1414
export const ApiMappingSchema = lazySchema(() => z.object({
1515
source: z.string().describe('Source field/path'),
1616
target: z.string().describe('Target field/path'),
17-
transform: z.string().optional().describe('Transformation function name — NOT EXECUTED in 17.x, and publish REJECTS the key: there is no transformation-function registry anywhere in the platform, so it stays in the frozen vocabulary and is refused rather than parsed and ignored (E7). A mapping entry moves and renames fields by dot path and nothing more — shape the value where it is produced instead (a flow endpoint whose flow computes it, or a formula field on the object)'),
17+
transform: z.string().optional().describe('Transformation function name — NOT EXECUTED by this runtime, and publish REJECTS the key: there is no transformation-function registry anywhere in the platform, so it stays in the frozen vocabulary and is refused rather than parsed and ignored (E7). A mapping entry moves and renames fields by dot path and nothing more — shape the value where it is produced instead (a flow endpoint whose flow computes it, or a formula field on the object)'),
1818
}));
1919
export type ApiMapping = z.input<typeof ApiMappingSchema>;
2020

@@ -171,8 +171,8 @@ export const ApiEndpointSchema = strictObject({
171171
description: z.string().optional(),
172172

173173
/** Execution Logic */
174-
type: z.enum(['flow', 'script', 'object_operation', 'proxy']).describe("Implementation type — only 'object_operation' and 'flow' EXECUTE in 17.x. 'script' and 'proxy' stay in the frozen vocabulary and are rejected at publish, not parsed and ignored: express script logic as a flow whose script node runs your registered function, and an outbound call as a flow using a declared connector"),
175-
target: z.string().optional().describe("Target Flow ID, per `type` — REQUIRED at publish for `type: 'flow'` (the gate refuses a flow endpoint that names no target flow) and UNREAD for `type: 'object_operation'`, so do not write it there: that endpoint is addressed by `objectParams.object` / `.operation`, and a `target` beside them is a dead string nothing checks against `objectParams.object` (made the key optional for exactly that reason). The vocabulary's other spellings — a Script Name or Proxy URL — stay unreachable in 17.x, since publish rejects `type: 'script'` and `type: 'proxy'`"),
174+
type: z.enum(['flow', 'script', 'object_operation', 'proxy']).describe("Implementation type — this runtime EXECUTES only 'object_operation' and 'flow'. 'script' and 'proxy' stay in the frozen vocabulary and are rejected at publish, not parsed and ignored: express script logic as a flow whose script node runs your registered function, and an outbound call as a flow using a declared connector"),
175+
target: z.string().optional().describe("Target Flow ID, per `type` — REQUIRED at publish for `type: 'flow'` (the gate refuses a flow endpoint that names no target flow) and UNREAD for `type: 'object_operation'`, so do not write it there: that endpoint is addressed by `objectParams.object` / `.operation`, and a `target` beside them is a dead string nothing checks against `objectParams.object` (made the key optional for exactly that reason). The vocabulary's other spellings — a Script Name or Proxy URL — stay unreachable on this runtime, since publish rejects `type: 'script'` and `type: 'proxy'`"),
176176

177177
/** Logic Config */
178178
objectParams: z.object({

0 commit comments

Comments
 (0)