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
14 changes: 10 additions & 4 deletions packages/rest/src/rest-server.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -6296,10 +6296,16 @@ export class RestServer {
});
return;
}
// `next: null` = no FSM governs the field; `next: []` =
// a declared dead end. Same three-valued answer the
// dispatcher gives, because a UI asking "where can this
// record go" must be able to tell those apart.
// Three answer values — `next: null`, `next: []` (a
// declared dead end), and the legal-next list — the same
// answer the dispatcher gives, because a UI asking "where
// can this record go" must tell those apart. But `null`
// is overloaded across TWO input conditions: no FSM
// governs the field, or the caller omitted `?from=` (no
// `from` => no transition table to answer with), which
// the line below folds onto the same `null` without
// consulting the rule. A UI therefore cannot read `null`
// as "no state machine" unless it passed a `from`.
const next = from === undefined ? null : legalNextStates(schema, field, from);
res.json({ object: name, field, from: from ?? null, next });
} catch (error: any) {
Expand Down
9 changes: 7 additions & 2 deletions packages/runtime/src/domains/meta.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -232,8 +232,13 @@ export async function handleMetadataRequest(deps: DomainHandlerDeps, path: strin
// ADR-0020 D3.3 introspection: the legal next states declared by the
// object's `state_machine` validation rule for `:field`. Lets UIs /
// AI authors ask "from here, where can this record go?" instead of
// hard-coding the transition table. Returns `next: null` when no FSM
// governs the field, `next: []` for a declared dead-end state.
// hard-coding the transition table. `next: []` is a declared
// dead-end state. `next: null` has TWO causes: no FSM governs the
// field, or the caller omitted `?from=` (no `from` => no transition
// table to answer with) — the handler short-circuits on that before
// it ever consults the rule. So a `null` answered to a call
// that passed no `from` is not evidence the field has no state
// machine; re-ask with `?from=`.
if (parts.length === 4 && (parts[0] === 'objects' || parts[0] === 'object') && parts[2] === 'state' && (!method || method === 'GET')) {
const name = parts[1];
const field = parts[3];
Expand Down
Loading