node-autotask 0.17.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/ARCHITECTURE.md +25 -12
  2. package/CHANGELOG.md +574 -0
  3. package/MCP_TOOL_CATALOG.json +2 -2
  4. package/MCP_TOOL_MANIFEST.md +5 -5
  5. package/N8N_MCP_MANIFEST.json +2 -2
  6. package/N8N_MCP_MANIFEST.md +3 -3
  7. package/README.md +8 -4
  8. package/capabilities.json +1574 -1574
  9. package/dist/capabilities.cjs +2 -2
  10. package/dist/capabilities.js +1 -1
  11. package/dist/{chunk-UV2PECAL.js → chunk-4YKV7ORF.js} +2 -2
  12. package/dist/{chunk-NQVD2XLH.cjs → chunk-7RDR5DSI.cjs} +393 -393
  13. package/dist/{chunk-EGR2ZLYM.js → chunk-D5RAQ4DE.js} +2 -2
  14. package/dist/{chunk-5ZGXWS4E.cjs → chunk-JM52ZU7T.cjs} +1573 -1573
  15. package/dist/{chunk-HC6YWPIY.js → chunk-LB2X4X4B.js} +116 -24
  16. package/dist/{chunk-6SP6GJJR.js → chunk-NDK7WYWA.js} +1573 -1573
  17. package/dist/{chunk-4RJG73GY.cjs → chunk-QDYSHAU6.cjs} +177 -85
  18. package/dist/{chunk-DQIZUZWP.cjs → chunk-TIM75K3G.cjs} +135 -135
  19. package/dist/{index-BAFdPxjG.d.ts → index-DLugNSr7.d.ts} +59 -21
  20. package/dist/{index-CjC0oWZX.d.cts → index-aZ3tCh1h.d.cts} +59 -21
  21. package/dist/index.cjs +26 -22
  22. package/dist/index.d.cts +2 -2
  23. package/dist/index.d.ts +2 -2
  24. package/dist/index.js +8 -4
  25. package/dist/operations/index.cjs +178 -105
  26. package/dist/operations/index.d.cts +12 -3
  27. package/dist/operations/index.d.ts +12 -3
  28. package/dist/operations/index.js +77 -4
  29. package/dist/pool.cjs +8 -8
  30. package/dist/pool.d.cts +1 -1
  31. package/dist/pool.d.ts +1 -1
  32. package/dist/pool.js +4 -4
  33. package/dist/resources/index.cjs +4 -4
  34. package/dist/resources/index.d.cts +1 -1
  35. package/dist/resources/index.d.ts +1 -1
  36. package/dist/resources/index.js +3 -3
  37. package/docs/API.md +92 -26
  38. package/package.json +4 -2
package/docs/API.md CHANGED
@@ -93,7 +93,7 @@ auto-retry on all legs — plus a concurrency guard (`maxConcurrent`, default 10
93
93
  allows <= 10 concurrent), one correlation id per request (surfaced on every
94
94
  error and to `onAudit`), and the opt-in client-side hourly budget tracker
95
95
  (`rateLimit.perHour`, default off — a sliding one-hour window that waits for a slot within the call deadline, else throws `RateLimitError` with `retryAfter`; the API allows 10,000 req/hour). The tracker is PER PROCESS: it counts only this client's attempts and cannot see other instances or integrations on the tenant (`thresholdInformation.get()` is the only cross-integration signal; the local count is `client.hourlyBudget()`, section 3.2.1). Under the `agent` config profile the exhaustion `suggestedAction` says stop or narrow the query — never raise the limit.
96
- Redirects are never followed (a 3xx throws `REDIRECT_BLOCKED`, so auth headers cannot leave the configured origin). Retry waits are capped at 60 s and never outlast the call deadline: a larger `Retry-After` (or one that exceeds the remaining budget) throws the `RateLimitError` immediately. `RequestOptions` adds `signal` (caller `AbortSignal`, combined with the deadline; aborting throws `AutotaskAbortError`, code `ABORTED`, and cancels retry sleeps) and `idempotent` (retry a non-GET method on 5xx/network errors; set on the `/query` and `/query/count` POST reads; on a mutation it is the issue #105 opt-back into the pre-#105 auto-retry — never set on real writes by the SDK). `client.withSignal(signal, () => ...)` applies a caller `AbortSignal` to every request (and retry wait) made inside the callback — the way to propagate MCP request cancellation through `invoke` without a signal parameter on each method. A real write clears the read cache. A non-JSON 2xx body is typed by the response `Content-Type` (section 2.1): a non-JSON-ish header throws the retryable `UNEXPECTED_CONTENT_TYPE` (the edge/maintenance page — a maintenance notice is waited out up to `maintenanceMaxWaitMs`, default 1 h, for reads and writes; otherwise a GET or idempotent-flagged call retries and a write makes one call: section 2.1); a JSON-ish or ABSENT header keeps `INVALID_RESPONSE` (the #13/W-A7 shape-drift key); JSON under any other header still decodes (the decode attempt is unconditional). A blank 2xx on a call that expects a result (every method but `DELETE` by default — `RequestOptions.expectBody`) throws the typed `EMPTY_RESPONSE` (`DELETE` and 204 keep returning `undefined`). A response above `maxResponseBytes` throws `RESPONSE_TOO_LARGE`; HTTP 412 maps to `ConflictError` (category `conflict`).
96
+ Redirects are never followed (a 3xx throws `REDIRECT_BLOCKED`, so auth headers cannot leave the configured origin). Retry waits are capped at 60 s and never outlast the call deadline: a larger `Retry-After` (or one that exceeds the remaining budget) throws the `RateLimitError` immediately. `RequestOptions` adds `signal` (caller `AbortSignal`, combined with the deadline; aborting throws `AutotaskAbortError`, code `ABORTED`, and cancels retry sleeps) and `idempotent` (retry a non-GET method on 5xx/network errors; set on the `/query` and `/query/count` POST reads; on a mutation it is the issue #105 opt-back into the pre-#105 auto-retry — never set on real writes by the SDK). `client.withSignal(signal, () => ...)` applies a caller `AbortSignal` to every request (and retry wait) made inside the callback — the way to propagate MCP request cancellation through `invoke` without a signal parameter on each method. A real write clears the read cache. A non-JSON 2xx body is typed by the response `Content-Type` (section 2.1): a non-JSON-ish header throws the retryable `UNEXPECTED_CONTENT_TYPE` (the edge/maintenance page — a maintenance notice is waited out up to `maintenanceMaxWaitMs`, default 1 h, for reads and writes; the same wait recognises the page at any status for calls safe to re-send (a GET or an explicitly idempotent-flagged call — an unflagged mutation makes ONE send with `details.maintenanceDetected`): section 2.1; otherwise a GET or idempotent-flagged call retries and a write makes one call); a JSON-ish or ABSENT header keeps `INVALID_RESPONSE` (the #13/W-A7 shape-drift key); JSON under any other header still decodes (the decode attempt is unconditional). A blank 2xx on a call that expects a result (every method but `DELETE` by default — `RequestOptions.expectBody`) throws the typed `EMPTY_RESPONSE` (`DELETE` and 204 keep returning `undefined`). A response above `maxResponseBytes` throws `RESPONSE_TOO_LARGE`; HTTP 412 maps to `ConflictError` (category `conflict`).
97
97
  Per-tenant 429 cooldown gate (`cooldown`, ON by default): once a 429 surfaces from the transport (retries spent, a `Retry-After` too long to wait out, or a non-retriable method), the client records a cooldown deadline for that tenant (lowercased `UserName` + lowercased host — impersonation does not change the key), and every further call for that tenant fails LOCALLY — before the semaphore, the hourly limiter and fetch, with zero upstream calls — with a `RateLimitError` (`code: 'RATE_LIMITED_COOLDOWN'`, `notSent: true`, `retryAfter` = remaining seconds; no credentials appear in the message) until the deadline passes. Duration is `Retry-After` clamped to `cooldown.maxMs` (default 15 minutes), or `cooldown.defaultMs` (default 60 s) when the header is missing or unparseable; the thread-threshold 429 (vendor code `Itgenatr005`, which clears in seconds) arms only a short window: min(`Retry-After`, 10 s), default 5 s. A 2xx from a call that got through clears the gate early; expiry is lazy (the first call after the deadline deletes the entry and proceeds); a hostile `Retry-After` (huge, zero, negative, non-numeric) is clamped to the cap or falls back to the default, so a lock-out is always bounded. `thresholdInformation.get()` bypasses the gate so a throttled tenant can still read its own headroom (a 2xx from it clears the gate; a 429 re-arms it). The default store is the in-process in-memory map shared by every client instance (capped at 1024 entries, evicting the earliest deadline first — failing open); `cooldown: { enabled: false }` disables the gate and `cooldown.store` injects a custom `CooldownStore` (the interface leaves room for a shared/Redis-backed store). Gated calls emit an audit event marked `notSent` and do not count toward `rateLimit.perHour`. Limits worth knowing: the gate is per process — other processes and integrations on the same tenant are invisible to it (`thresholdInformation` is the only cross-integration signal); it can mask early recovery if the vendor window clears before `Retry-After`; a process that loads both the ESM and the CJS build gets two default stores (the default store is deliberately not anchored on `globalThis`) — inject one shared `CooldownStore` if you need cross-build sharing.
98
98
 
99
99
  Credential-shaped fields are redacted in audit payloads by default.
@@ -166,8 +166,10 @@ served payload verbatim).
166
166
 
167
167
  `buildFilter(conditions)` wraps caller conditions (multiple top-level
168
168
  conditions are `and`-wrapped); `validateFilter` enforces the vendor caps
169
- (section 5 below). `buildQueryRequest` produces the `QueryModel` wire body
170
- (`{ MaxRecords, IncludeFields?, filter }`). `normalizedConditions` is the
169
+ (section 5 below). `buildQueryBody` produces the `QueryModel` wire body
170
+ (`{ MaxRecords, IncludeFields?, filter }`, `id` appended to a caller
171
+ `includeFields`), shared by page 1 (`buildQueryRequest`) and every cursor
172
+ continuation of a walk (issue #118). `normalizedConditions` is the
171
173
  shared filter-shape gate of the query family (every query-family entry
172
174
  point routes through it pre-request): the `filter` param accepts a
173
175
  conditions array or `{ filter: [...] }`, and `undefined`/`null` are the
@@ -257,7 +259,7 @@ status → what the vendor does that triggers it.
257
259
  | `RateLimitError` | `RATE_LIMITED` | rate_limit | 429 | hourly/secondly budget exceeded — `retryAfter` from `Retry-After` — retryable |
258
260
  | `RateLimitError` | `RATE_LIMITED_COOLDOWN` | rate_limit | — (call not sent) | per-tenant cooldown after a surfaced 429 (issue #6) — `notSent: true`, `retryAfter` = remaining seconds — not retryable before the deadline |
259
261
  | `ServerError` | `SERVER_ERROR` | server | 5xx | vendor 5xx — retryable — EXCEPT a deterministic field-validation 500 (a known phrase, `isDeterministicVendor500`): same class/code/status, `retryable: false` + `details.deterministic: true` (issue #23) |
260
- | `UnexpectedContentTypeError` | `UNEXPECTED_CONTENT_TYPE` | server | 2xx (non-JSON body + a non-JSON-ish `Content-Type`) | the edge serves a non-JSON page (maintenance) — the vendor never processed the request; `details = { httpStatus, contentType, bodySnippet (256 B) }` — retryable (advisory). A maintenance-notice page is waited out up to `maintenanceMaxWaitMs` (default 1 h) for reads AND writes; on exhaustion `details.maintenance` reports the wait (section 2.1) |
262
+ | `UnexpectedContentTypeError` | `UNEXPECTED_CONTENT_TYPE` | server | 2xx (non-JSON body + a non-JSON-ish `Content-Type`) | the edge serves a non-JSON page (maintenance) — the vendor never processed the request; `details = { httpStatus, contentType, bodySnippet (256 B) }` — retryable (advisory). A non-JSON `2xx` maintenance page is waited out up to `maintenanceMaxWaitMs` (default 1 h) for reads AND writes; at `4xx`/`5xx` the page is waited out only for calls safe to re-send (GET / explicitly idempotent-flagged) — an unflagged mutation makes one send and carries `details.maintenanceDetected` (issue #114); a wait that ran is reported on the surfaced error as `details.maintenance` (section 2.1) |
261
263
  | `EmptyResponseError` | `EMPTY_RESPONSE` | server | 2xx (empty body, `expectBody`) | a blank 2xx on a call that expects a result (non-DELETE by default — section 2.1); `details = { httpStatus, method }` — retryable for non-DELETE (advisory: the loop never retries 2xx) |
262
264
  | `CircuitBreakerError` | `CIRCUIT_BREAKER_OPEN` | policy | — (call not sent) | the host circuit breaker (issue #26, opt-in) is open — refused LOCALLY before the request (zero upstream calls, no hourly stamp) — `notSent: true`, `retryAfter` = remaining open seconds — **retryable: true** (the deliberate divergence from #6's not-retryable cooldown refusal: the open period is bounded and advertised; section 3.2.2) |
263
265
  | `ResolutionError` | `RESOLUTION_AMBIGUOUS` / `RESOLUTION_TRUNCATED` / `NOT_FOUND` | resolution | — | `resolve`: multiple exact matches (candidates in `resourceIds`, labeled in `details.idRefs`); a cap-tripped bounded client scan (`resolution.scanOnMiss` only; never a silent `null`); a numeric-id miss |
@@ -340,21 +342,41 @@ transport `RequestOptions` flag) or raises `maxRetries`. An
340
342
  contract break is not transient.
341
343
 
342
344
  **Maintenance-window auto-retry (weekly window).** Autotask runs a weekly
343
- maintenance window (typically under 30 minutes) during which the edge answers
344
- an HTML page — the non-JSON 2xx above — before the application ever sees the
345
- request. When that page reads as a maintenance notice, the transport waits
346
- `maintenancePollMs` (default 30 s) and retries, for up to
347
- `maintenanceMaxWaitMs` (default 1 hour) from the first such response. This
348
- applies to **reads and writes**: the page is a pre-execution edge signal, which
349
- is the one case where a bounded auto-retry of a write is safe — the general
350
- rule is untouched (a 5xx / network / timeout error never auto-retries a write;
351
- §2.2). The wait does not consume `maxRetries`, each post-wait attempt gets a
352
- fresh `timeoutMs`, and a caller `AbortSignal` interrupts the wait. A page that
353
- does not look like a maintenance notice (for example a wrong `baseUrl` serving
354
- a web site) is surfaced at once. On exhaustion the error carries
355
- `details.maintenance = { waits, waitedMs, maxWaitMs, exhausted: true }` and a
356
- suggestedAction naming the window. Disable with `maintenanceMaxWaitMs: 0`
357
- (config or per call).
345
+ maintenance window during which the edge answers an HTML page before the
346
+ application ever sees the request. The transport recognises that page from the
347
+ **first 256 bytes** of its body at any status (`2xx`, `4xx`, `5xx`) — issue
348
+ #114: the edge picks the status line, and a 503/404 HTML page used to burn the
349
+ retry budget or surface as `NOT_FOUND`. The gate is the body: a **JSON** body
350
+ never enters (a JSON 503 is the application answering; it keeps the normal
351
+ rules, and a write still makes one send). When the page reads as a maintenance
352
+ notice:
353
+
354
+ - a non-JSON **`2xx`** page is waited out and retried for **reads and writes**
355
+ (a 2xx HTML page is edge-served before execution — the 0.16.0 premise);
356
+ - a non-JSON **`4xx`/`5xx`** page is waited out and retried only for calls that
357
+ are **safe to re-send** (`retriable5xx`: a `GET` or an explicitly
358
+ `idempotent`-flagged call). An UNFLAGGED mutation answered with such a page
359
+ makes **exactly one send** and surfaces the status's own error with
360
+ `details.maintenanceDetected = true` and a verify-before-reissuing action — a
361
+ proxy can serve that page AFTER forwarding the write, so the outcome is
362
+ unknown (the §2.2 write rule is intact).
363
+
364
+ The wait polls `maintenancePollMs` (default 30 s) for up to
365
+ `maintenanceMaxWaitMs` (default 1 hour) from the first such response. The
366
+ observed ceiling is 4 h (`MAINTENANCE_WINDOW_MAX_MS`; 19 live windows:
367
+ 30–240 min, median ≈ 2 h) — an unattended worker should pass
368
+ `maintenanceMaxWaitMs: MAINTENANCE_WINDOW_MAX_MS`. The wait does not consume
369
+ `maxRetries`, each post-wait attempt gets a fresh `timeoutMs`, and a caller
370
+ `AbortSignal` interrupts the wait. A non-JSON HTML-ish page that does not look
371
+ like a maintenance notice (for example a wrong `baseUrl` serving a web site, or
372
+ a stock proxy page) is surfaced at once with
373
+ `details.maintenancePatternMiss = true` and the 256-byte snippet logged at
374
+ `debug` (issue #114 — the next occurrence supplies the wording). When a wait
375
+ ran, the surfaced error always carries
376
+ `details.maintenance = { waits, waitedMs, maxWaitMs, exhausted }` (`exhausted`
377
+ is true when the budget was spent; a wording change on a later page surfaces
378
+ that page's own error with `exhausted: false`). Disable with
379
+ `maintenanceMaxWaitMs: 0` (config or per call).
358
380
  Zone discovery rides the same transport, so a startup failure during
359
381
  maintenance is recognisable by the code. `details.bodySnippet` is the first
360
382
  256 BYTES of the raw body — TRUNCATION ONLY (the name-based redaction is a
@@ -386,7 +408,10 @@ write that answered nothing is now a typed failure (strict default: no
386
408
 
387
409
  **Deterministic validation 500 (#23).** A 500 whose `errors[]` entries match
388
410
  the documented heuristic phrases (`isDeterministicVendor500` — `exceeds
389
- maximum length` to start) is PERMANENT: the transport makes exactly ONE
411
+ maximum length`, `Missing Required Field`, `Value does not exist for the
412
+ required field`, and since issue #119 `Unable to find <field> in the <entity>
413
+ Entity.` — the live field-existence signature of an unknown `includeFields`
414
+ name) is PERMANENT: the transport makes exactly ONE
390
415
  upstream call (it breaks out before the retry decision — the parsed body is
391
416
  already in hand at the retry site), and the mapped error keeps the
392
417
  `ServerError` class + `SERVER_ERROR` code + status 500 but carries
@@ -486,7 +511,7 @@ Host form: `https://<name>.autotask.net/atservicesrest` (lowercase
486
511
  | `pageSize` | 10 | 500 (vendor max) | page size for query walks; `maxRecords` on the wire |
487
512
  | `timeoutMs` | 30 000 | — | whole-call budget, incl. retries |
488
513
  | `maxRetries` | 3 | — | the client's retry budget, overridable per call (`InvokeOptions.maxRetries` / `MutationOpts.maxRetries`; `0` = exactly one send per request the call performs). The budget caps three legs (issues #105/#109): the 429 leg (retried on GET/PUT/DELETE + flagged calls — unchanged) and the 5xx/network + non-JSON 2xx legs (GET + flagged only) |
489
- | `maintenanceMaxWaitMs` | 3 600 000 (1 h) | — | **maintenance-window auto-retry** (weekly Autotask window): when the edge answers a non-JSON HTML page that reads as a maintenance notice, the transport waits `maintenancePollMs` and retries for up to this long from the FIRST such page — for reads AND writes (the edge answered before the vendor application saw the request; the weekly window is typically under 30 min). Does not consume `maxRetries`; each post-wait attempt gets a fresh `timeoutMs`. `0` disables the wait. A page that does not look like maintenance (e.g. a wrong `baseUrl`) never enters the loop |
514
+ | `maintenanceMaxWaitMs` | 3 600 000 (1 h) | — | **maintenance-window auto-retry** (weekly Autotask window): when the first 256 bytes of a non-JSON body read as a maintenance notice, the transport waits `maintenancePollMs` and retries for up to this long from the FIRST such page. A `2xx` page is waited out for reads AND writes (edge-served before execution); a `4xx`/`5xx` page only for calls safe to re-send (GET / explicitly idempotent-flagged) — an unflagged mutation makes ONE send and carries `details.maintenanceDetected` (a proxy can serve the page after forwarding). JSON bodies never enter. Does not consume `maxRetries`; each post-wait attempt gets a fresh `timeoutMs`. `0` disables the wait. A non-matching HTML-ish page stamps `details.maintenancePatternMiss`. Observed ceiling 4 h: `MAINTENANCE_WINDOW_MAX_MS` (19 windows: 30–240 min, median ≈ 2 h) |
490
515
  | `maintenancePollMs` | 30 000 | — | poll interval while waiting out a maintenance page |
491
516
  | `maxConcurrent` | 10 | 10 (vendor) | concurrency guard |
492
517
  | `profile` | `library` | — | `library` = today's defaults (changes nothing); `agent` = the conservative `AGENT_PROFILE` limits for LLM-driven workloads (below). Resolution: explicit config > profile > library default |
@@ -675,9 +700,16 @@ carry paging parameters.
675
700
 
676
701
  - **Page walk — cursor-follow on `pageDetails.nextPageUrl`** (node-style): page 1
677
702
  POSTs the `QueryModel` body (`MaxRecords = pageSize`) to `/V1.0/<E>/query`;
678
- later pages POST the preserved filter to the ABSOLUTE `nextPageUrl`, rebased
679
- to the client base URL (same-origin cursors verbatim; cross-origin re-based —
680
- the node's custom-zone behaviour).
703
+ later pages POST the SAME body (`buildQueryBody` — `MaxRecords`,
704
+ `IncludeFields` with the `id` append, the preserved `filter`) to the ABSOLUTE
705
+ `nextPageUrl`, rebased to the client base URL (same-origin cursors verbatim;
706
+ cross-origin re-based — the node's custom-zone behaviour). Issue #118: before
707
+ the fix later pages POSTed `{ filter }` alone and returned the FULL vendor
708
+ model (live-reproduced 2026-10-19: page 1 of a three-field subset walk —
709
+ `id`, `title`, `status` — carried 4 keys, page 2 carried 76). The vendor HONOURS `IncludeFields` on a continuation and
710
+ IGNORES `MaxRecords` (it serves its own 500-row page; the walk slices to its
711
+ limit, so the RESULT is exact and the wire page size on page 2+ is the
712
+ vendor's).
681
713
  - `Page<T>`: `{ items, page (1-based), pageSize, requestCount (the vendor
682
714
  value verbatim — the MaxRecords echo, not a total), hasMore (nextPageUrl
683
715
  !== null), totalPages }`. `totalPages` is the pages KNOWN so far —
@@ -926,6 +958,12 @@ the mode pinned (§7.4). The governor rules (pinned):
926
958
  the SDK; the caller passes `limit` as the TOTAL cap (default 500, max
927
959
  2500) and never owns a stream. `queryAll` keeps its raw behavior (the
928
960
  unbounded walk; the catalog text steers the caller away for normal work);
961
+ - a query's `includeFields` NAMES are validated **pre-request** (issue
962
+ #119b): the live field metadata is the authority (the vendored spec when
963
+ that read faults), every unknown name is named in one typed
964
+ `CONFIG_ERROR` before the query POST, and `fieldCheck: false` in the query
965
+ bag disables the name check (the `string[]` / non-empty shape check always
966
+ runs);
929
967
  - `dryRun` must be a real boolean: anything else (`'true'`, `0`, `null`) is
930
968
  refused before any request — only an explicit `false` applies a write;
931
969
  - `<resource>.createIfNotExists` takes its match fields as an optional
@@ -1426,6 +1464,16 @@ drift). Records exist only for plan rows that are implemented/tested; the
1426
1464
  plan (`capabilities.plan.json`) is the single source of truth and the
1427
1465
  registry is its compiled form.
1428
1466
 
1467
+ **Consumer-defaulted arguments (issue #115).** A manifest CONSUMER may fill an
1468
+ argument this registry marks required when the caller omits it — the manifest
1469
+ describes the SDK operation, not the consumer's tool contract. The one
1470
+ instance today is `contracts.profitability`: the SDK requires
1471
+ `dateFrom`/`dateTo`, and iit-mcp-autotask defaults both to the last full
1472
+ calendar month (UTC) when the caller omits them. The served
1473
+ `metadata.args`/`metadata.usage` and the record's
1474
+ `inputSchema.opts.description` state the caveat; a consumer that trusts the
1475
+ raw manifest should apply the same contract.
1476
+
1429
1477
  ### 9.1 Generated artifact #5: `src/relationships.ts` (the relationship map + zone facts)
1430
1478
 
1431
1479
  The regen chain's 5th generated module (plan S12 §2.4: the 4 generated
@@ -1648,7 +1696,8 @@ retrying with the same body fails identically; fix the field instead
1648
1696
  httpStatus >= 500). **Resolution (issue #23):** the *deterministic*
1649
1697
  validation 500 is no longer labelled retryable — a 500 whose `errors[]` text
1650
1698
  matches the documented heuristic phrases (`isDeterministicVendor500`:
1651
- `exceeds maximum length`, `Missing Required Field`, `Value does not exist for the required field`) makes exactly ONE upstream call (the
1699
+ `exceeds maximum length`, `Missing Required Field`, `Value does not exist for the required field`,
1700
+ `Unable to find <field> in the <entity> Entity.` — issue #119, live-verified 2026-10-19) makes exactly ONE upstream call (the
1652
1701
  transport skips the retry), keeps the `ServerError` class + `SERVER_ERROR`
1653
1702
  code + status 500, but carries `retryable: false`, `details.deterministic:
1654
1703
  true`, and a `suggestedAction` pointing at the field catalog
@@ -2027,7 +2076,11 @@ the TTL is the other expiry path.
2027
2076
 
2028
2077
  **The on-demand op:** `operations.fieldCatalog(client, opts?)`
2029
2078
  (`{ resources?, include?, fields?, labels?, fresh? }` — `fields` filters the
2030
- fields-kind rows to the named fields and reports `unmatchedFields`; `labels`
2079
+ fields-kind rows to the named fields and reports `unmatchedFields`; a
2080
+ projected row is marked `projected: true` (issue #121 — the four derived
2081
+ lists then describe the PROJECTED set only, so a consumer can never read a
2082
+ projected `required`/`queryable` list as the resource's full picture; a full
2083
+ row omits the key); `labels`
2031
2084
  adds the picklist labels; default: the five workflow
2032
2085
  resources, `include: ['fields']`; the closed opts bag is documented in
2033
2086
  the registry record). One bounded entityInformation GET per resource ×
@@ -2630,6 +2683,19 @@ query-verb-ONLY and zero-wire or vendor-column-selection-only:
2630
2683
  billing item to its company / ticket / project / time entry / contract
2631
2684
  service / contract / billing code / expense item, plus the date and the
2632
2685
  amount fields — the "common narrow read" the issue's example targets.
2686
+ - **`fieldCheck: false` — the includeFields NAME check's opt-out (issue
2687
+ #119b).** By default the bounded `query` op validates every `includeFields`
2688
+ name BEFORE the query POST: the authority is the entity's LIVE field
2689
+ metadata (`getFieldInfo()` through the cache-aware channel of section
2690
+ 11.14 — warm = zero wire), degrading to the vendored spec
2691
+ (`src/field-sets.ts`) when the metadata read faults; with neither an
2692
+ authority the check is skipped. The live set wins when present — a
2693
+ live-only field (`time_entries.postedDate`) is accepted and a
2694
+ vendored-only field the tenant no longer has is refused. Every unknown
2695
+ name is named in ONE typed `CONFIG_ERROR` (zero query wire), with
2696
+ `fieldCheck: false` as the escape hatch and `operations.fieldCatalog` as
2697
+ the discovery pointer. The shape check (`string[]`, non-empty entries)
2698
+ always runs.
2633
2699
  - **The live check (the AC test for the presets).** Each preset's fields
2634
2700
  are asserted to EXIST on the live resource against the LIVE field
2635
2701
  catalogue (`operations.fieldCatalog` — the entityInformation/fields
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-autotask",
3
- "version": "0.17.0",
3
+ "version": "0.19.0",
4
4
  "description": "Fully-typed TypeScript SDK for the Autotask (Datto) PSA REST API. Zero runtime dependencies. Built for building MCP servers and integrations.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -87,6 +87,7 @@
87
87
  "bin",
88
88
  "dist",
89
89
  "README.md",
90
+ "CHANGELOG.md",
90
91
  "LICENSE",
91
92
  "ARCHITECTURE.md",
92
93
  "capabilities.json",
@@ -112,7 +113,7 @@
112
113
  "coverage": "vitest run --coverage",
113
114
  "lint": "eslint src test",
114
115
  "lint:fix": "eslint src test --fix",
115
- "verify": "npm run typecheck && npm run lint && npm run generate:check && npm run build && node scripts/check-capabilities.mjs --ship && npm run mcp:project:check && npm run n8n:project:check && npm run catalog:check && npm run mcp:check-surface && npm run public-surface && npm run verify:pack && npm test",
116
+ "verify": "npm run lockfile:check && npm run typecheck && npm run lint && npm run generate:check && npm run build && node scripts/check-capabilities.mjs --ship && npm run mcp:project:check && npm run n8n:project:check && npm run catalog:check && npm run mcp:check-surface && npm run public-surface && npm run verify:pack && npm test",
116
117
  "plan:derive": "node scripts/derive-plan.mjs",
117
118
  "generate:check": "node scripts/generate-batch.mjs --check && node scripts/generate-batch.mjs --check --group B",
118
119
  "capabilities:build": "node scripts/generate-capabilities.mjs",
@@ -131,6 +132,7 @@
131
132
  "n8n:project:check": "node scripts/project-n8n-mcp.mjs --check",
132
133
  "verify:pack": "node scripts/verify-pack.mjs",
133
134
  "release:check": "node scripts/check-release-plan-hash.mjs",
135
+ "lockfile:check": "node scripts/check-lockfile-sync.mjs",
134
136
  "capabilities:check:ship": "node scripts/check-capabilities.mjs --ship",
135
137
  "prepack": "npm run build",
136
138
  "prepublishOnly": "npm run release:check && npm run clean && npm run verify && npm run capabilities:check:ship",