Skip to content

Commit 5a23096

Browse files
fix(objectql,service-automation,runtime): the card's named warnings and endpoint hints state each decision in words instead of a tracker number (#20738)
Part of #20513 Clause-②: no The card's named producers are 11 author- and operator-shown messages in 4 files. Each one pointed the reader at a tracker number for the reason behind it. Each now says what the cited decision was, in the sentence being read (form D, as the migration-entry rewrite applied it). **Text only:** no error `code`, field name, HTTP status or behaviour changes. Every changed line in the four source files is a string-literal line. This PR covers the card's first step only. The rest of the family is the maintainer's burn-down decision, and the census below is the input to it, so 20513 stays open. ## What each sentence now says | Where (about) | Cited | The sentence now says | |---|---|---| | objectql `engine.ts` data.record.* warning (7077) | 4639, 4626 | It refuses to fabricate a per-record event with an empty `recordId`. A predicate (`multi: true`) write publishes its own `data.records.*` event carrying the affected-row count, so reaching this line is a driver defect. | | objectql `engine.ts` data.records.* warning (7171) | 4639 | A bulk event states only the count (no records, no predicate), so a driver result that is not a count publishes nothing. | | service-automation `engine.ts` resumeAuthority warning (3276) | 3801, 5561, 3823 | The resume-authority gate refuses this type's pauses. An undeclared `resumeAuthority` resolves to `'service'`, fail-closed, because guessing `'any'` is how a raw resume once walked past an approval decision no service had recorded. | | same file, resume refusal log line (6268) and error text (6275) | 5561 | An undeclared `resumeAuthority` resolves to `'service'`, fail-closed. | | same file, undeclared-suspension refusal (6410) | 5561 | An undeclared `resumeAuthority` resolves to `'service'`, which the generic route refuses. | | runtime `api-endpoint-step.ts` no-policy-context hint (245) | 5040 E5b | Execution is reachable only on the far side of the policy chain, so the composed runtime always threads the policy context. | | same file, no-execution-wiring hint (264) | 5040 E5b | The composed runtime supplies the execution wiring together with the policy context; only a hand-mounted step can omit it. | | same file, `NOT_IMPLEMENTED` message (346) | 5040 | No wiring was supplied, so nothing was executed. | | runtime `api-mapping.ts` path hint (233) | 5040 E7 | The publish gate rejects the same shapes, so a declaration that reaches this check was stored without passing that gate. | | same file, `transform` refusal (265) | 5040 §3.4, E7 | The sentence already said `transform` is rejected at publish rather than parsed and ignored; only the citation goes. | The integration pin in `dispatcher-plugin.endpoint-fallback.integration.test.ts` asserts `not.toContain('without a policy context')` and `not.toContain('no wiring')`. Both phrases are kept verbatim, so that negative pin still means what it meant. ## Pins re-pinned: 6, one more than the order counted Each of these now asserts the words that carry the decision instead of the number: - `resume-authority-declaration.test.ts` 84-85 - `engine-data-events.test.ts` 374 - `api-endpoint-step.test.ts` 143, 232 - `api-mapping.test.ts` 230: a `mustMention` fragment list. The matcher-line grep behind the order's count of 5 cannot see it. It pins the `transform` refusal, one of the 11 named messages, so it had to move. The first full runtime run showed it red; after the re-pin it is green. ## Ledger burn-down `scripts/doc-authoring-prose-id.baseline.json` was regenerated with `node scripts/check-doc-authoring.mjs --census-ledger`. It was generated into a scratch file first, so the growth refusal ran against the checked-in baseline, and then copied into place. 14 id occurrences leave in 7 (file, id) pairs, and 2 files leave the ledger entirely. Pairs go 618 to 611, occurrences 945 to 931, files 229 to 227, and the gate's printed "pinned site(s)" (id-bearing string literals) 808 to 794. Nothing else in the file moved. `pnpm check:doc-authoring` is green on it. ## Census: the input to the maintainer's burn-down decision **Instrument.** A TypeScript-compiler AST walk over every non-test `src/` file of every public package under `packages/` (69 manifests, 2,582 files). It works per message: - **Folding.** Before matching, each message is folded into one text: a maximal chain of pieces joined by `+`, a template literal, parentheses, and a string array whose parent is `.join(sep)`. A string nested inside a span, or inside a call that is an operand of the chain, is folded into the outermost message. - **No comments.** A non-string operand renders only its string leaves, never raw source, so comments are never read. - **Match.** A hash plus 3-5 digits, not after an ampersand, a hash or a digit, and not followed by an alphanumeric. Word forms (issue, PR or card plus a number) were scanned too: 0 hits. - **Classification.** By syntactic context: logger calls, thrown errors, envelope fields (message, hint, reason, error, detail(s) and the like), error-factory calls, and refusal prose built in a const or return. Outside the population: test-facing strings shipped under `src/` (testkits, bench, contract-suite case labels), metadata text (description, help or label, and generated translations), and one excluded false positive (CSS colours in the CLI's inline HTML). **Controls.** - **Lit, single line:** objectql `engine.ts` 7077-7080 gives one logger message citing 4639 and 4626. - **Lit, multi-line:** the service-automation warning at 3276-3283 is one `+` chain over 8 lines and is read as ONE message citing 3801, 5561 and 3823. - **Dark:** the service-automation docblock at 3231 gives 0 hits, and so does a `//` comment interleaved inside a `+` chain in lint's `validate-action-body-writes.ts` (426). Across the whole tree, 1,125 hit lines were checked and 0 fall on a comment line. - **Independent cross-check** against the gate's own ledger: on all 211 files both read, per-file id-occurrence counts are identical. The totals reconcile exactly by scope. **The doc-authoring ledger finding.** The card says `check:doc-authoring` does not read these positions. It does. Its ledgered sibling-package leg, added in 3f54efd, holds every one of them in a shrink-only baseline. It exits 0 because they are baselined, not because they are unread. So the gate's reach does not need to move: any burn-down is monotone, and each burn-down PR regenerates that one shared file. That makes parallel stages serialise on it. **Totals** (tree 36d043b, before this PR): - 743 population messages cite 554 distinct ids in 39 of the 69 public packages. - The engine lane holds 201 of them, citing 128 ids: 145 author-facing refusal or prescription prose and 56 log lines. - Outside the population: 84 test-facing strings, 75 metadata texts and 1 excluded false positive. This PR removes 11 messages. On the merged head the census reads 892 messages with an id where it read 903, with 0 new. Per package (bold = this lane; "id occurrences" counts the ids the population messages cite): | Package | Messages | logger | thrown | envelope | factory | prose | id occurrences | test-facing | metadata text | |---|---:|---:|---:|---:|---:|---:|---:|---:|---:| | spec | 175 | 0 | 0 | 26 | 0 | 149 | 373 | 45 | 3 | | lint | 83 | 0 | 1 | 63 | 0 | 19 | 104 | 0 | 0 | | runtime | 53 | 7 | 2 | 9 | 1 | 34 | 72 | 0 | 0 | | **drivers/driver-sql** (lane) | 52 | 15 | 6 | 5 | 20 | 6 | 66 | 7 | 0 | | **metadata-protocol** (lane) | 40 | 19 | 5 | 3 | 11 | 2 | 49 | 1 | 0 | | **objectql** (lane) | 40 | 14 | 6 | 0 | 3 | 17 | 44 | 3 | 2 | | plugins/plugin-security | 34 | 20 | 1 | 3 | 2 | 8 | 39 | 0 | 4 | | services/service-automation | 28 | 12 | 2 | 2 | 2 | 10 | 34 | 0 | 5 | | plugins/plugin-auth | 27 | 10 | 3 | 0 | 2 | 12 | 28 | 0 | 0 | | **drivers/driver-turso** (lane) | 24 | 3 | 6 | 0 | 12 | 3 | 44 | 0 | 0 | | rest | 23 | 1 | 0 | 1 | 0 | 21 | 33 | 0 | 1 | | plugins/plugin-webhooks | 20 | 5 | 5 | 0 | 0 | 10 | 33 | 0 | 0 | | cli | 19 | 0 | 2 | 2 | 1 | 14 | 22 | 0 | 2 | | services/service-analytics | 15 | 1 | 3 | 0 | 9 | 2 | 34 | 0 | 0 | | **core** (lane) | 12 | 0 | 2 | 3 | 2 | 5 | 13 | 0 | 0 | | **drivers/driver-mongodb** (lane) | 10 | 0 | 5 | 0 | 5 | 0 | 12 | 3 | 0 | | plugins/plugin-approvals | 10 | 8 | 1 | 0 | 0 | 1 | 10 | 0 | 15 | | **drivers/driver-memory** (lane) | 9 | 0 | 1 | 0 | 8 | 0 | 12 | 0 | 0 | | plugins/plugin-sharing | 7 | 3 | 2 | 0 | 0 | 2 | 7 | 0 | 0 | | verify | 7 | 0 | 2 | 2 | 0 | 3 | 8 | 0 | 2 | | cloud-connection | 6 | 0 | 0 | 0 | 0 | 6 | 6 | 0 | 0 | | **metadata** (lane) | 6 | 3 | 1 | 0 | 0 | 2 | 6 | 0 | 0 | | services/service-datasource | 6 | 0 | 0 | 1 | 0 | 5 | 9 | 0 | 0 | | services/service-messaging | 6 | 0 | 4 | 0 | 0 | 2 | 8 | 0 | 15 | | **metadata-core** (lane) | 4 | 0 | 0 | 0 | 0 | 4 | 4 | 25 | 0 | | **platform-objects** (lane) | 3 | 2 | 0 | 1 | 0 | 0 | 3 | 0 | 17 | | plugins/plugin-dev | 3 | 1 | 2 | 0 | 0 | 0 | 3 | 0 | 0 | | services/service-storage | 3 | 1 | 0 | 0 | 0 | 2 | 4 | 0 | 0 | | connectors/connector-mcp | 2 | 0 | 2 | 0 | 0 | 0 | 2 | 0 | 0 | | plugins/plugin-email | 2 | 1 | 1 | 0 | 0 | 0 | 2 | 0 | 4 | | services/service-i18n | 2 | 0 | 0 | 0 | 0 | 2 | 2 | 0 | 0 | | services/service-knowledge | 2 | 2 | 0 | 0 | 0 | 0 | 3 | 0 | 0 | | services/service-queue | 2 | 1 | 1 | 0 | 0 | 0 | 2 | 0 | 0 | | services/service-sms | 2 | 1 | 0 | 0 | 0 | 1 | 2 | 0 | 0 | | types | 2 | 0 | 0 | 0 | 0 | 2 | 2 | 0 | 0 | | **formula** (lane) | 1 | 0 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | | plugins/plugin-audit | 1 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 5 | | plugins/plugin-hono-server | 1 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | | triggers/trigger-record-change | 1 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | The full per-hit list (file, line and cited ids for all 903 messages) is in the os-dev-report comment on the card (5900801368). ## Verification Final head `f636206f33`: this branch plus a merge of `main` at 1940afd. That merge touched none of this PR's files. - **Build.** `turbo run build` over the closure of objectql, service-automation and runtime: 31/31 tasks. Then the whole workspace except docs: 72/72 tasks. - **Built output.** The replaced fragments appear in neither the ESM nor the CJS bundle of the three packages. The new sentences appear in both. - **Tests, run before the merge:** - `@objectstack/service-automation`: 155 files, 1,942 tests passed. - `@objectstack/runtime`: 289 of 290 files passed. The red one was the api-mapping pin above; after its re-pin, `api-mapping.test.ts` and `api-endpoint-step.test.ts` pass 59/59. - `@objectstack/objectql`: 336 files, 6,679 tests passed. - After the merge, the closure was rebuilt, and the incoming objectql test file plus `engine-data-events.test.ts` passed 50/50. That is AGENTS.md's scoped re-check: the incoming commits touch spec and metadata-protocol, not these packages or this behaviour. - **Typecheck.** `typecheck` for the three packages exits 0 each; each includes `check:test-typecheck`. - **Derived gates.** `node scripts/pm/dispatch-gates.mjs --commands` on `f636206f33` names 75 commands, and all 75 exit 0. `check:dual-build-cjs-loads` first answered PREREQUISITE NOT MET on the partial build. It was re-run after the full build: exit 0, 104 entries measured. `--ran` reconciliation: 75 derived, 75 run, 0 NOT-MEASURED, 0 UNRUN. - **Lint, narrowed.** `eslint --no-inline-config --format json` over the 8 touched TypeScript files: 8 files reported, 0 errors, 0 warnings, on `f636206f33`. The narrowing is safe because `eslint.config.mjs` enables no type-aware linting (no `parserOptions.project` or `projectService` anywhere), so this diff cannot move any untouched file's verdict. The repo-wide `pnpm lint` is CI's. - **NOT MEASURED here (CI's):** the Test Core shards, Temporal Conformance, the Dogfood jobs, Build Core's own steps and the workspace type-check lanes. ## Acceptance notes - **Other pins, other producers.** `service-knowledge`'s `event-sync-data-events.test.ts` pins service-knowledge's OWN warning (`knowledge-service-plugin.ts`, ids 4639 and 4672), not the objectql string. It is untouched here. Runtime also has 5040 citations beyond this order's five sites: `endpoint-executor.ts` 255 (pinned by `endpoint-executor.test.ts` 212) and `route-ledger.ts` 574. Both are census hits left for the burn-down decision. - **Comments untouched.** Comments and docblocks citing the same numbers in these files are not runtime strings and are not changed here. - **A correction to the round-1 report.** It called the gate's printed "808 pinned site(s)" file-id pairs. That figure counts id-bearing string literals; the ledger's pairs were 618. --- _Generated by [Claude Code](https://claude.ai/code/session_01DEvba2nBuD4tWzfq8r8NFY)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent b785c3b commit 5a23096

10 files changed

Lines changed: 62 additions & 36 deletions
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
'@objectstack/objectql': patch
3+
'@objectstack/service-automation': patch
4+
'@objectstack/runtime': patch
5+
---
6+
7+
Warnings, refusals and hints that cited a tracker number now say what was decided
8+
9+
Clause-②: no
10+
11+
Several runtime strings an author or operator reads sent the reader to an issue-tracker number for
12+
the reason behind them. Each now states that reason in the sentence itself:
13+
14+
- `@objectstack/objectql`: the two data-event warnings. A write that names no single record publishes
15+
no per-record event rather than one with an empty `recordId`; a predicate (`multi: true`) write
16+
publishes its own `data.records.*` event carrying the affected-row count and nothing else, so a
17+
driver result that is not a count publishes no bulk event either.
18+
- `@objectstack/service-automation`: the warning for a pausing node type that never declares
19+
`resumeAuthority`, the generic-route resume refusal (its log line and its error text), and the
20+
refusal of a suspension from a type that declares `supportsPause: false`. An undeclared
21+
`resumeAuthority` resolves to `'service'` (fail-closed), so the generic resume route refuses those
22+
pauses; guessing `'any'` is how a raw resume once walked past an approval decision no service had
23+
recorded.
24+
- `@objectstack/runtime`: the endpoint step's `NOT_IMPLEMENTED` message and its two hints (the
25+
composed runtime always threads the policy context and the execution wiring, because execution is
26+
reachable only past the policy chain), and the endpoint mapping refusals (the publish gate rejects
27+
the same shapes, so a declaration that reaches the runtime check was stored without passing it).
28+
29+
Text only: no error code, field name, status or behaviour changes.

‎packages/objectql/src/engine-data-events.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,7 @@ describe('#4639 — predicate writes publish aggregate BulkDataEvents', () => {
371371
expect(published).toHaveLength(0);
372372
const logged = offWarn.mock.calls.map((c) => String(c[0])).join('\n');
373373
expect(logged).toContain('data.records.updated');
374-
expect(logged).toContain('#4639');
374+
expect(logged).toContain('it carries no records and no predicate');
375375
});
376376

377377
it('a by-id delete still takes the PER-RECORD path even with multi: true', async () => {

‎packages/objectql/src/engine.ts‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7075,9 +7075,10 @@ export class ObjectQL implements IObjectQLEngine {
70757075
if (!recordId) {
70767076
this.logger.warn(
70777077
`No data.record.${action} event published for '${object}': the write names no single record, ` +
7078-
`and DataEvent.recordId is required — refusing to publish an off-contract event. ` +
7079-
`A predicate write publishes data.records.${action} instead (#4639), so reaching this ` +
7080-
`means a single-id write whose driver returned no usable primary key (#4626)`,
7078+
`and DataEvent.recordId is required — refusing to publish an off-contract event rather than ` +
7079+
`fabricate one with an empty recordId. A predicate (multi: true) write publishes its own ` +
7080+
`data.records.${action} event, carrying the affected-row count, instead — so reaching this ` +
7081+
`means a single-id write whose driver returned no usable primary key, which is a driver defect`,
70817082
{ object },
70827083
);
70837084
return;
@@ -7169,8 +7170,8 @@ export class ObjectQL implements IObjectQLEngine {
71697170
this.logger.warn(
71707171
`No data.records.${action} event published for '${object}': the driver's multi-row result is ` +
71717172
`not an affected-row count (IDataDriver.updateMany/deleteMany are contracted to resolve ` +
7172-
`a number). The count is the only thing a bulk event states, so publishing one here would ` +
7173-
`assert something unverified (#4639)`,
7173+
`a number). The count is the only thing a bulk event states — it carries no records and no ` +
7174+
`predicate — so publishing one here would assert something unverified`,
71747175
{ object },
71757176
);
71767177
return;

‎packages/runtime/src/api-endpoint-step.test.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ describe('a match with no wiring answers an honest 501', () => {
140140
// "matched but not executed" must never read as "executed and empty".
141141
expect(body.error.message).toContain('showcase_tasks');
142142
expect(body.error.message).toContain('no wiring');
143-
expect(String(body.error.hint)).toContain('#5040');
143+
expect(String(body.error.hint)).toContain('execution is reachable only on the far side of the policy chain');
144144
});
145145

146146
it('passes the request coordinates through untouched', async () => {
@@ -229,7 +229,7 @@ describe('the policy chain runs between the match and the answer', () => {
229229
expect(answer?.status).toBe(501);
230230
const hint = String((answer!.body as { error: { hint: unknown } }).error.hint);
231231
expect(hint).toContain('enforced');
232-
expect(hint).toContain('#5040');
232+
expect(hint).toContain('supplies it together with the policy context');
233233
});
234234

235235
it('never puts the cacheTtlSeconds header on the 501 — but the verdict still carries it', async () => {

‎packages/runtime/src/api-endpoint-step.ts‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -243,8 +243,9 @@ export async function runAppEndpointStep(
243243
// about what ran is worse than no report.
244244
return notImplemented(match, method, path,
245245
'This request reached the step without a policy context, so authRequired / rateLimit / cacheTtlSeconds '
246-
+ 'were not evaluated — and nothing was executed either. The composed runtime always threads one '
247-
+ '(#5040 E5b), so reaching this answer means a host mounted the step by hand and omitted it.');
246+
+ 'were not evaluated — and nothing was executed either. The composed runtime always threads one, '
247+
+ 'because execution is reachable only on the far side of the policy chain, so reaching this answer '
248+
+ 'means a host mounted the step by hand and omitted it.');
248249
}
249250

250251
const verdict = await applyEndpointPolicies({ ...input.policy, endpoint: match.endpoint, method });
@@ -263,7 +264,7 @@ export async function runAppEndpointStep(
263264
return notImplemented(match, method, path,
264265
'Policies (authRequired / rateLimit / cacheTtlSeconds) were enforced and this request passed them, but no '
265266
+ 'execution wiring was supplied, so the target was not run. The composed runtime always supplies '
266-
+ 'it (#5040 E5b).');
267+
+ 'it together with the policy context; only a host that mounts the step by hand can leave it out.');
267268
}
268269

269270
const { request, deps, executionContext, environmentId, dataDriver } = input.execution;
@@ -344,7 +345,7 @@ function notImplemented(
344345
httpStatus: 501,
345346
message:
346347
`Declarative endpoint '${match.endpoint.name}' claims ${method} ${path}, but the caller of the `
347-
+ 'endpoint step supplied no wiring to serve it with (#5040).',
348+
+ 'endpoint step supplied no wiring to serve it with, so nothing was executed.',
348349
extra: { hint },
349350
});
350351
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,8 @@ describe('a declaration this runtime cannot serve is refused, never ignored', ()
227227
});
228228
const error = expectRefusal(
229229
rejectionOf(applyInputMapping(endpoint, { price: '3' })),
230-
'inputMapping[1].transform', 'convertToInt', 'showcase_inquiries', '#5040',
230+
'inputMapping[1].transform', 'convertToInt', 'showcase_inquiries',
231+
'rejected at publish rather than parsed and ignored',
231232
);
232233
// The prescription, not just the verdict: an author has to be told what
233234
// to do instead, or the refusal is only half a signal.

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,8 @@ function reject(message: string, hint: string): EndpointMappingRejection {
232232
const PATH_HINT =
233233
"`source` and `target` are dot-separated field paths ('user.profile.email'). An empty path, an empty "
234234
+ "segment ('a..b') and the JavaScript prototype keys (__proto__, prototype, constructor) are refused; "
235-
+ 'the publish gate rejects the same shapes (#5040 E7).';
235+
+ 'the publish gate rejects the same shapes, so a declaration that reaches this check was stored without '
236+
+ 'passing that gate (for example through a direct metadata register() call).';
236237

237238
/**
238239
* Whether this runtime can serve a key's declaration AT ALL — data-independent,
@@ -263,8 +264,8 @@ export function mappingDeclarationRejection(
263264
`Endpoint '${endpoint.name}' declares ${at}.transform ('${entry.transform}'), which this runtime `
264265
+ 'does not execute.',
265266
'A mapping entry moves and renames fields by dot path; there is no transformation-function '
266-
+ "registry in this runtime, so `transform` is rejected at publish (#5040 §3.4, E7) rather than "
267-
+ 'parsed and ignored. Drop the key, or shape the value where it is produced.',
267+
+ "registry in this runtime, so `transform` is rejected at publish rather than parsed and "
268+
+ 'ignored. Drop the key, or shape the value where it is produced.',
268269
);
269270
}
270271

‎packages/services/service-automation/src/engine.ts‎

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3274,9 +3274,10 @@ export class AutomationEngine implements IAutomationService {
32743274
this.resumeAuthorityOmissionWarned.add(descriptor.type);
32753275
this.logger.warn(
32763276
`[automation] node type '${descriptor.type}' declares supportsPause but never declares ` +
3277-
`resumeAuthority, so the #3801 resume gate REFUSES every pause it creates on the generic route ` +
3278-
`(POST /automation/:name/runs/:runId/resume) — an unclaimed pause is fail-closed since #5561, ` +
3279-
`because the opposite guess is how #3823 walked past an unrecorded approval decision. ` +
3277+
`resumeAuthority, so the resume-authority gate REFUSES every pause it creates on the generic route ` +
3278+
`(POST /automation/:name/runs/:runId/resume) — an undeclared resumeAuthority resolves to ` +
3279+
`'service', fail-closed, because guessing 'any' is how a raw resume once walked past an approval ` +
3280+
`decision no service had recorded. ` +
32803281
`Declare it on the descriptor: 'any' if that route IS the intended door (a screen's collected ` +
32813282
`inputs, a signal wait's external producer), or 'service' if resuming is the tail of a decision ` +
32823283
`some service must authorize and record first. Declaring 'any' is what RESTORES the generic ` +
@@ -6266,14 +6267,16 @@ export class AutomationEngine implements IAutomationService {
62666267
const why = declared === 'service'
62676268
? `which is resumable only through its owning service (resumeAuthority: 'service')`
62686269
: `whose type never declares resumeAuthority, so it is closed to the generic route until it does ` +
6269-
`(#5561) — declare resumeAuthority: 'any' on its descriptor if this route IS the intended door`;
6270+
`(an undeclared resumeAuthority resolves to 'service', fail-closed) — declare ` +
6271+
`resumeAuthority: 'any' on its descriptor if this route IS the intended door`;
62706272
this.logger.warn(`[automation] refused resume of run '${runId}': parked on ${nodeType} node ${at}, ${why}`);
62716273

62726274
// The fix, identical in both the direct and the linked-run phrasing —
62736275
// what has to change is a descriptor, not the call that just failed.
62746276
const undeclaredFix =
62756277
`and that node type never declares resumeAuthority, so the generic resume route is closed to the ` +
6276-
`pauses it creates (#5561). If that route IS the intended door — a screen's collected inputs, a ` +
6278+
`pauses it creates: an undeclared resumeAuthority resolves to 'service', fail-closed. If that ` +
6279+
`route IS the intended door — a screen's collected inputs, a ` +
62776280
`signal wait's external producer — declare resumeAuthority: 'any' on its action descriptor; declare ` +
62786281
`'service' if resuming is the tail of a decision some service must authorize and record first`;
62796282
return {
@@ -6409,7 +6412,8 @@ export class AutomationEngine implements IAutomationService {
64096412
`node type '${nodeType}' suspended the run but its action descriptor declares ` +
64106413
`supportsPause: false, so the pause is refused — a run that paused here could not be ` +
64116414
`continued on the generic resume route anyway: a type that declares no pause declares no ` +
6412-
`resumeAuthority either, and an unclaimed pause is fail-closed since #5561. Declare ` +
6415+
`resumeAuthority either, and an undeclared resumeAuthority resolves to 'service', which the ` +
6416+
`generic route refuses. Declare ` +
64136417
`supportsPause: true on the descriptor together with the resumeAuthority the pauses need ` +
64146418
`('any' if POST /automation/:name/runs/:runId/resume is the intended door, 'service' if ` +
64156419
`resuming is the tail of a decision some service must authorize and record first) — or stop ` +

‎packages/services/service-automation/src/resume-authority-declaration.test.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ describe('resumeAuthority omission warning (#5561)', () => {
8181
// one field that restores their resume route.
8282
expect(line).toContain("'any'");
8383
expect(line).toContain("'service'");
84-
expect(line).toContain('#3801');
85-
expect(line).toContain('#3823');
84+
expect(line).toContain("an undeclared resumeAuthority resolves to 'service', fail-closed");
85+
expect(line).toContain('walked past an approval decision no service had recorded');
8686
expect(line).toContain('REFUSES');
8787
expect(line).toContain("Declaring 'any' is what RESTORES the generic route");
8888
expect(line).not.toContain('changes no behaviour');

‎scripts/doc-authoring-prose-id.baseline.json‎

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -408,8 +408,6 @@
408408
"#3617": 2,
409409
"#4371": 1,
410410
"#4419": 1,
411-
"#4626": 1,
412-
"#4639": 2,
413411
"#4769": 2,
414412
"#4797": 2,
415413
"#5158": 2,
@@ -835,12 +833,6 @@
835833
"#11519": 1,
836834
"#5933": 1
837835
},
838-
"packages/runtime/src/api-endpoint-step.ts": {
839-
"#5040": 3
840-
},
841-
"packages/runtime/src/api-mapping.ts": {
842-
"#5040": 2
843-
},
844836
"packages/runtime/src/app-plugin.ts": {
845837
"#8686": 1
846838
},
@@ -957,13 +949,10 @@
957949
"#3017": 1,
958950
"#3528": 1,
959951
"#3760": 1,
960-
"#3801": 1,
961-
"#3823": 1,
962952
"#4045": 1,
963953
"#4277": 1,
964954
"#4414": 1,
965-
"#5393": 4,
966-
"#5561": 4
955+
"#5393": 4
967956
},
968957
"packages/services/service-automation/src/plugin.ts": {
969958
"#1928": 1,

0 commit comments

Comments
 (0)