node-autotask 0.18.0 → 0.20.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 (39) hide show
  1. package/ARCHITECTURE.md +15 -8
  2. package/CHANGELOG.md +590 -0
  3. package/MCP_TOOL_CATALOG.json +6 -6
  4. package/MCP_TOOL_MANIFEST.md +128 -128
  5. package/N8N_MCP_MANIFEST.json +44 -9
  6. package/N8N_MCP_MANIFEST.md +8 -8
  7. package/README.md +8 -4
  8. package/capabilities.json +1941 -1829
  9. package/capabilities.schema.json +6 -0
  10. package/dist/capabilities.cjs +2 -2
  11. package/dist/capabilities.js +1 -1
  12. package/dist/{chunk-VV2UUW2J.js → chunk-2T77J2ZM.js} +1940 -1828
  13. package/dist/{chunk-5O5EPYBO.cjs → chunk-FLYTKJKV.cjs} +1940 -1828
  14. package/dist/{chunk-VYUZZIEE.js → chunk-GA6UCX3Z.js} +292 -58
  15. package/dist/{chunk-W2WSZOZA.js → chunk-JEJUFXHX.js} +2 -2
  16. package/dist/{chunk-WRWX6WIK.cjs → chunk-L6V66V22.cjs} +360 -126
  17. package/dist/{chunk-G564CHXM.cjs → chunk-RMLGOCGA.cjs} +135 -135
  18. package/dist/{chunk-HXOWEYV3.js → chunk-XSYJB4IW.js} +3 -3
  19. package/dist/{chunk-7I3F3AJJ.cjs → chunk-YH5CPRDN.cjs} +394 -394
  20. package/dist/{index-D7x3MkLK.d.cts → index-AN-NjLi9.d.cts} +126 -21
  21. package/dist/{index-BqPY04ZG.d.ts → index-CvLLsOCP.d.ts} +126 -21
  22. package/dist/index.cjs +24 -22
  23. package/dist/index.d.cts +2 -2
  24. package/dist/index.d.ts +2 -2
  25. package/dist/index.js +6 -4
  26. package/dist/operations/index.cjs +184 -136
  27. package/dist/operations/index.d.cts +2 -2
  28. package/dist/operations/index.d.ts +2 -2
  29. package/dist/operations/index.js +79 -31
  30. package/dist/pool.cjs +8 -8
  31. package/dist/pool.d.cts +1 -1
  32. package/dist/pool.d.ts +1 -1
  33. package/dist/pool.js +4 -4
  34. package/dist/resources/index.cjs +4 -4
  35. package/dist/resources/index.d.cts +1 -1
  36. package/dist/resources/index.d.ts +1 -1
  37. package/dist/resources/index.js +3 -3
  38. package/docs/API.md +126 -40
  39. package/package.json +2 -1
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.
@@ -259,7 +259,7 @@ status → what the vendor does that triggers it.
259
259
  | `RateLimitError` | `RATE_LIMITED` | rate_limit | 429 | hourly/secondly budget exceeded — `retryAfter` from `Retry-After` — retryable |
260
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 |
261
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) |
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 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) |
263
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) |
264
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) |
265
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 |
@@ -342,21 +342,41 @@ transport `RequestOptions` flag) or raises `maxRetries`. An
342
342
  contract break is not transient.
343
343
 
344
344
  **Maintenance-window auto-retry (weekly window).** Autotask runs a weekly
345
- maintenance window (typically under 30 minutes) during which the edge answers
346
- an HTML page — the non-JSON 2xx above — before the application ever sees the
347
- request. When that page reads as a maintenance notice, the transport waits
348
- `maintenancePollMs` (default 30 s) and retries, for up to
349
- `maintenanceMaxWaitMs` (default 1 hour) from the first such response. This
350
- applies to **reads and writes**: the page is a pre-execution edge signal, which
351
- is the one case where a bounded auto-retry of a write is safe — the general
352
- rule is untouched (a 5xx / network / timeout error never auto-retries a write;
353
- §2.2). The wait does not consume `maxRetries`, each post-wait attempt gets a
354
- fresh `timeoutMs`, and a caller `AbortSignal` interrupts the wait. A page that
355
- does not look like a maintenance notice (for example a wrong `baseUrl` serving
356
- a web site) is surfaced at once. On exhaustion the error carries
357
- `details.maintenance = { waits, waitedMs, maxWaitMs, exhausted: true }` and a
358
- suggestedAction naming the window. Disable with `maintenanceMaxWaitMs: 0`
359
- (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).
360
380
  Zone discovery rides the same transport, so a startup failure during
361
381
  maintenance is recognisable by the code. `details.bodySnippet` is the first
362
382
  256 BYTES of the raw body — TRUNCATION ONLY (the name-based redaction is a
@@ -491,7 +511,7 @@ Host form: `https://<name>.autotask.net/atservicesrest` (lowercase
491
511
  | `pageSize` | 10 | 500 (vendor max) | page size for query walks; `maxRecords` on the wire |
492
512
  | `timeoutMs` | 30 000 | — | whole-call budget, incl. retries |
493
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) |
494
- | `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) |
495
515
  | `maintenancePollMs` | 30 000 | — | poll interval while waiting out a maintenance page |
496
516
  | `maxConcurrent` | 10 | 10 (vendor) | concurrency guard |
497
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 |
@@ -752,6 +772,30 @@ carry paging parameters.
752
772
  `limit >= latest` (above that the returned count is governed by
753
773
  `latest`; `limit` is the blind-branch walk window). `latest` is
754
774
  query-verb-ONLY (`queryAll` / `count` bags are unchanged).
775
+ **The count-probe cache (issue #127).** The walk's count probes (the
776
+ initial `count(F)` + every `id gt T` threshold probe) are cached per
777
+ client in a short-TTL in-process store: 30 s TTL, 4096-entry memory
778
+ guard, keyed by (resource plan key, canonical filter serialisation).
779
+ A re-asked "top N" within 30 s makes ZERO extra vendor count calls
780
+ (full-set AND threshold mode). A real write through the SAME client
781
+ clears the cache — and a probe still in flight across the write is
782
+ DISCARDED by the cache's invalidation epoch (issue #139: its pre-write
783
+ answer must not re-anchor a stale top-N within the TTL — the next
784
+ `latest` re-probes). Never cached: the blind-surface / pinned-empty
785
+ sentinels (the plan-pinned stamps stay fresh and re-derive on every
786
+ call), errors, and anything outside the probe path — the plain `count`
787
+ op and `operations.aggregate` call the vendor count directly and
788
+ promise fresh totals. A write made ELSEWHERE (another client, the
789
+ Autotask UI) shows up once the 30 s TTL expires — that is the
790
+ documented snapshot trade. The truncation guard covers the
791
+ growth-of-total direction; a stale-LOW total within the TTL is NOT
792
+ left to it: when a full-set walk truncates against a cached total,
793
+ the SDK re-probes the total ONCE fresh (invalidating the entry) and
794
+ continues the walk to a larger fresh total — the
795
+ `LATEST_WINDOW_INCOMPLETE` refusal stands only when the fresh probe
796
+ confirms the cached value (bounded: at most one extra count probe per
797
+ walk). A total of `0` is NEVER cached, so a repeated call after an
798
+ out-of-band insert re-probes instead of returning a false empty.
755
799
  - **`noteChars: N` (issue #61) — the opt-in per-field free-text cap.**
756
800
  `N` (integer >= 1, pre-request validated, zero wire) caps EVERY
757
801
  string value in the resource's untrusted-class free-text fields
@@ -938,12 +982,24 @@ the mode pinned (§7.4). The governor rules (pinned):
938
982
  the SDK; the caller passes `limit` as the TOTAL cap (default 500, max
939
983
  2500) and never owns a stream. `queryAll` keeps its raw behavior (the
940
984
  unbounded walk; the catalog text steers the caller away for normal work);
941
- - a query's `includeFields` NAMES are validated **pre-request** (issue
942
- #119b): the live field metadata is the authority (the vendored spec when
943
- that read faults), every unknown name is named in one typed
944
- `CONFIG_ERROR` before the query POST, and `fieldCheck: false` in the query
945
- bag disables the name check (the `string[]` / non-empty shape check always
946
- runs);
985
+ - a query's `includeFields` NAMES and its `filter` condition FIELD NAMES are
986
+ validated **pre-request** (issues #119b + #125): the authority is the
987
+ entity's LIVE field metadata (the vendored spec when that read faults —
988
+ the resource's plan-pinned live-only fields, `metadata.liveOnlyFields`,
989
+ are exempt from the vendored refusal: the live entity carries them and
990
+ the vendor model omits them (e.g. `time_entries.postedDate` /
991
+ `postedOnTime`); a truly unknown name is still refused), every unknown
992
+ name is named in one typed `CONFIG_ERROR` before the query POST (zero
993
+ query wire — the cold fieldCache may cost the one authority metadata GET
994
+ first), and `fieldCheck: false` in the query bag disables BOTH name
995
+ checks (the `string[]` / non-empty shape check and the filter grammar
996
+ always run). The `count` verb's bag (closed to `filter` + the
997
+ `fieldCheck` opt-out — issue #145) is name-checked the same way
998
+ (issue #140 — the documented unchecked scope is the direct
999
+ `queryBounded` / `queryAll` client paths AND the `operations.aggregate`
1000
+ filter, which is grammar-checked only — its groupBy/sum field names ARE
1001
+ checked; name-checking the aggregate filter is a separate follow-up, the
1002
+ op's own field whitelist being the different authority);
947
1003
  - `dryRun` must be a real boolean: anything else (`'true'`, `0`, `null`) is
948
1004
  refused before any request — only an explicit `false` applies a write;
949
1005
  - `<resource>.createIfNotExists` takes its match fields as an optional
@@ -1444,6 +1500,16 @@ drift). Records exist only for plan rows that are implemented/tested; the
1444
1500
  plan (`capabilities.plan.json`) is the single source of truth and the
1445
1501
  registry is its compiled form.
1446
1502
 
1503
+ **Consumer-defaulted arguments (issue #115).** A manifest CONSUMER may fill an
1504
+ argument this registry marks required when the caller omits it — the manifest
1505
+ describes the SDK operation, not the consumer's tool contract. The one
1506
+ instance today is `contracts.profitability`: the SDK requires
1507
+ `dateFrom`/`dateTo`, and iit-mcp-autotask defaults both to the last full
1508
+ calendar month (UTC) when the caller omits them. The served
1509
+ `metadata.args`/`metadata.usage` and the record's
1510
+ `inputSchema.opts.description` state the caveat; a consumer that trusts the
1511
+ raw manifest should apply the same contract.
1512
+
1447
1513
  ### 9.1 Generated artifact #5: `src/relationships.ts` (the relationship map + zone facts)
1448
1514
 
1449
1515
  The regen chain's 5th generated module (plan S12 §2.4: the 4 generated
@@ -1962,7 +2028,9 @@ into one predicted-failure-free attempt.
1962
2028
  - **`time_entries.postedWithin(from, to, opts?)`** — the working posted-state
1963
2029
  window read: an inclusive UTC `dateWorked` window (`from`/`to` are
1964
2030
  `YYYY-MM-DD` date strings — the S1c guard forbids Date positionals), ONE
1965
- bounded query (`limit` 1-500, default 25) returning
2031
+ bounded query (`limit` 1-500, default 500 — the hard cap, issue #128: a
2032
+ window with more than 500 matching rows needs `time_entries.query` with the
2033
+ same `dateWorked` filter, its 2500-row walk cap) returning
1966
2034
  `{ items, postedCount, unpostedCount, window }`; `opts.posted` narrows
1967
2035
  (true = posted rows only, false = unposted rows only, omitted = both
1968
2036
  counted in one pass) and `ticketID` / `resourceID` narrow further.
@@ -1978,7 +2046,7 @@ into one predicted-failure-free attempt.
1978
2046
  the posted/unposted counts then cover the returned rows ONLY (at most
1979
2047
  `limit`); re-query with a wider window or a narrowing filter to reconcile
1980
2048
  the totals. `time_entries.dateWorkedWindow(from, to, opts?)` is the
1981
- clearer-named alias: the same wire, the same result (incl. `truncated`),
2049
+ unambiguous-named alias: the same wire, the same result (incl. `truncated`),
1982
2050
  its own op identity (plan row / MCP tool
1983
2051
  `autotask_timeEntry_date_worked_window` / operation label) — the name
1984
2052
  names the `dateWorked` field the window is on; `postedWithin` keeps its
@@ -2618,8 +2686,11 @@ additive, and mirrors the family:
2618
2686
  with the identical contract, so the whole window family is uniformly
2619
2687
  timezone-anchored (default UTC unchanged).
2620
2688
  - **Shape and wire** — each op is exactly ONE bounded query (`MaxRecords`
2621
- 1-500, default 25; the `itemDate` / `invoiceDateTime` `gte`/`lte` filter
2622
- rides the wire), and the result is the plain `items[]` array — no counts
2689
+ 1-500, default 500 — the hard cap, issue #128: a call without `limit` takes
2690
+ the first 500 rows in id order; a window with more than 500 matching rows
2691
+ needs the bounded query op with the same date-window filter — its 2500-row
2692
+ walk cap; the `itemDate` / `invoiceDateTime` `gte`/`lte` filter rides the
2693
+ wire), and the result is the plain `items[]` array — no counts
2623
2694
  (the time-entry family shape).
2624
2695
 
2625
2696
  ### 11.23 Free-text caps and includeFields presets (issue #61)
@@ -2653,19 +2724,34 @@ query-verb-ONLY and zero-wire or vendor-column-selection-only:
2653
2724
  billing item to its company / ticket / project / time entry / contract
2654
2725
  service / contract / billing code / expense item, plus the date and the
2655
2726
  amount fields — the "common narrow read" the issue's example targets.
2656
- - **`fieldCheck: false` — the includeFields NAME check's opt-out (issue
2657
- #119b).** By default the bounded `query` op validates every `includeFields`
2658
- name BEFORE the query POST: the authority is the entity's LIVE field
2659
- metadata (`getFieldInfo()` through the cache-aware channel of section
2660
- 11.14 — warm = zero wire), degrading to the vendored spec
2661
- (`src/field-sets.ts`) when the metadata read faults; with neither an
2662
- authority the check is skipped. The live set wins when present — a
2663
- live-only field (`time_entries.postedDate`) is accepted and a
2664
- vendored-only field the tenant no longer has is refused. Every unknown
2665
- name is named in ONE typed `CONFIG_ERROR` (zero query wire), with
2727
+ - **`fieldCheck: false` — the field-NAME checks' opt-out (issues #119b +
2728
+ #125).** By default the bounded `query` op validates every `includeFields`
2729
+ name AND every `filter` condition field name BEFORE the query POST: the
2730
+ authority is the entity's LIVE field metadata (`getFieldInfo()` through
2731
+ the cache-aware channel of section 11.14 — warm = zero wire), degrading
2732
+ to the vendored spec (`src/field-sets.ts`) when the metadata read faults;
2733
+ with neither an authority the check is skipped. The live set wins when
2734
+ present — a live-only field (`time_entries.postedDate`) is accepted and
2735
+ a vendored-only field the tenant no longer has is refused. Under
2736
+ degradation the resource's plan-pinned live-only fields (the record's
2737
+ `metadata.liveOnlyFields` — the live entity carries them and the vendor
2738
+ model omits them: `time_entries.postedDate` / `postedOnTime`, the W-D3
2739
+ spec gap) are EXEMPT from the vendored refusal, so a caller-composed
2740
+ `postedDate` filter degrades to the pre-#125 behaviour (the query reaches
2741
+ the vendor and works on a zone that honours the field); a name unknown to
2742
+ BOTH the vendored spec and the pinned set is still refused. Every unknown
2743
+ name is named in ONE typed `CONFIG_ERROR` (zero query wire — the cold
2744
+ fieldCache may cost the one authority metadata GET first), with
2666
2745
  `fieldCheck: false` as the escape hatch and `operations.fieldCatalog` as
2667
- the discovery pointer. The shape check (`string[]`, non-empty entries)
2668
- always runs.
2746
+ the discovery pointer. The shape checks (`string[]` non-empty
2747
+ `includeFields`; the filter grammar) always run. The `count` verb's bag
2748
+ (closed to `filter` + the `fieldCheck` opt-out — issue #145) is
2749
+ name-checked the same way (issue #140 — the documented unchecked scope
2750
+ is the direct `queryBounded` / `queryAll` client paths AND the
2751
+ `operations.aggregate` filter, which is grammar-checked only — its
2752
+ groupBy/sum field names ARE checked; name-checking the aggregate filter
2753
+ is a separate follow-up, the op's own field whitelist being the
2754
+ different authority).
2669
2755
  - **The live check (the AC test for the presets).** Each preset's fields
2670
2756
  are asserted to EXIST on the live resource against the LIVE field
2671
2757
  catalogue (`operations.fieldCatalog` — the entityInformation/fields
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-autotask",
3
- "version": "0.18.0",
3
+ "version": "0.20.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",