Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .changeset/24654-endpoint-text-version-free.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
'@objectstack/spec': patch
'@objectstack/runtime': patch
---

fix(spec, runtime): the endpoint publish refusal, the runtime's `unsupported` hint and the endpoint schema describes no longer say "in 17.x"

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)

- 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.
- 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.
10 changes: 5 additions & 5 deletions content/docs/references/api/endpoint.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ const result = ApiEndpointSchema.parse(data);
| **method** | `Enum<'GET' \| 'POST' \| 'PUT' \| 'DELETE' \| 'PATCH' \| 'HEAD' \| 'OPTIONS'>` | ✅ | HTTP Method |
| **summary** | `string` | optional | |
| **description** | `string` | optional | |
| **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 |
| **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'` |
| **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 |
| **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'` |
| **objectParams** | `{ object?: string; operation?: Enum<'find' \| 'get' \| 'create' \| 'update' \| 'delete'> }` | optional | For object_operation type |
| **inputMapping** | `{ source: string; target: string; transform?: string }[]` | optional | Map Request Body to Internal Params |
| **outputMapping** | `{ source: string; target: string; transform?: string }[]` | optional | Map Internal Result to Response Body |
Expand All @@ -56,15 +56,15 @@ const result = ApiEndpointSchema.parse(data);
| :--- | :--- | :--- | :--- |
| **source** | `string` | ✅ | Source field/path |
| **target** | `string` | ✅ | Target field/path |
| **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) |
| **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) |

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

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **source** | `string` | ✅ | Source field/path |
| **target** | `string` | ✅ | Target field/path |
| **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) |
| **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) |

### Nested Shape: `ApiEndpoint.rateLimit`

Expand All @@ -85,7 +85,7 @@ const result = ApiEndpointSchema.parse(data);
| :--- | :--- | :--- | :--- |
| **source** | `string` | ✅ | Source field/path |
| **target** | `string` | ✅ | Target field/path |
| **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) |
| **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) |


---
Expand Down
2 changes: 1 addition & 1 deletion packages/runtime/src/api-mapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
* | `outputMapping` | *Map Internal Result to Response Body* |
* | `ApiMapping.source` | *Source field/path* |
* | `ApiMapping.target` | *Target field/path* |
* | `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)* |
* | `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)* |
*
* Five short sentences, and everything below is the MINIMAL faithful reading of
* them — `transform`'s now says out loud, at the point of authoring, what this
Expand Down
4 changes: 2 additions & 2 deletions packages/runtime/src/endpoint-executor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ describe('buildEndpointExecutionContext — a projection, not an interpretation'
expect(ctx.remoteAddress).toBe('203.0.113.9');
});

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

it.each(['script', 'proxy'])('refuses `%s` — declared by the vocabulary, not executed in 17.x', (type) => {
it.each(['script', 'proxy'])('refuses `%s` — declared by the vocabulary, not executed by this runtime', (type) => {
const plan = planEndpointTarget(endpoint({ type, objectParams: undefined }));
expect(plan.kind).toBe('unsupported');
// The reason names the type so an author is not left guessing which of
Expand Down
8 changes: 4 additions & 4 deletions packages/runtime/src/endpoint-executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ import type { HttpProtocolContext } from './http-dispatcher.js';
*
* `params` is deliberately NOT here: the transport's own route params are
* meaningless for a path no route matched, and the endpoint's path parameters
* arrive on {@link ApiEndpointMatch.params} instead (always `{}` in 17.x — the
* arrive on {@link ApiEndpointMatch.params} instead (always `{}` — the
* frozen vocabulary defines no template syntax).
*/
export interface EndpointExecutionRequest {
Expand Down Expand Up @@ -133,7 +133,7 @@ export interface EndpointExecutionContext {
/** Matched endpoint, schema defaults materialized. */
endpoint: ApiEndpoint;
/**
* Path parameters from the match. **Always `{}` in 17.x** and deliberately
* Path parameters from the match. **Always `{}`** and deliberately
* NOT consulted when resolving a record id — the id comes from `query.id`
* (#5040 §2), because reading it from a template segment would require a
* template syntax the vocabulary does not define.
Expand Down Expand Up @@ -225,7 +225,7 @@ export type EndpointTargetPlan =
/**
* Classify a declaration into the pipeline that will run it.
*
* `script` and `proxy` are declared by the vocabulary and NOT executed in 17.x,
* `script` and `proxy` are declared by the vocabulary and NOT executed by this runtime,
* on purpose (#5040 §7-3, restated in the E5 card): the automation contract's
* `execute` doc mentions "flow or script" but nothing in this repo verifies a
* script target is reachable that way, and `proxy` is an entirely new outbound
Expand Down Expand Up @@ -265,7 +265,7 @@ export function planEndpointTarget(endpoint: ApiEndpoint): EndpointTargetPlan {
return {
kind: 'unsupported',
reason: `Endpoint '${endpoint.name}' declares type '${endpoint.type}', which this runtime does not execute.`,
hint: "Only 'object_operation' and 'flow' endpoints execute in 17.x. 'script' and 'proxy' "
hint: "This runtime executes only 'object_operation' and 'flow' endpoints. 'script' and 'proxy' "
+ 'are rejected at publish pending their own rulings — script reachability through the '
+ 'automation service is unverified, and proxy is an outbound (SSRF) surface.',
};
Expand Down
2 changes: 1 addition & 1 deletion packages/spec/src/api/endpoint-publish-gate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ function targetGate(
path: at('type'),
message:
`${named} declares \`type: '${endpoint.type}'\`, which this runtime does not execute. `
+ "Only 'object_operation' and 'flow' endpoints execute in 17.x. `script` is refused because "
+ "This runtime executes only 'object_operation' and 'flow' endpoints. `script` is refused because "
+ 'no path in this repo verifies that a script target is reachable through the automation '
+ 'service — express the logic as a flow (`type: \'flow\'`) whose script node runs your '
+ 'registered function. `proxy` is refused because forwarding to an arbitrary outbound URL is '
Expand Down
6 changes: 3 additions & 3 deletions packages/spec/src/api/endpoint.zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { retiredKey } from '../shared/retired-key';
export const ApiMappingSchema = lazySchema(() => z.object({
source: z.string().describe('Source field/path'),
target: z.string().describe('Target field/path'),
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)'),
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)'),
}));
export type ApiMapping = z.input<typeof ApiMappingSchema>;

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

/** Execution Logic */
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"),
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'`"),
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"),
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'`"),

/** Logic Config */
objectParams: z.object({
Expand Down
Loading