node-autotask 0.41.0 → 0.43.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.
- package/CHANGELOG.md +35 -0
- package/MCP_TOOL_CATALOG.json +152 -132
- package/MCP_TOOL_MANIFEST.md +16 -15
- package/N8N_MCP_MANIFEST.json +3 -3
- package/N8N_MCP_MANIFEST.md +4 -4
- package/README.md +4 -5
- package/capabilities.json +3919 -3195
- package/dist/capabilities.cjs +2 -2
- package/dist/capabilities.js +1 -1
- package/dist/{chunk-P5655PGY.js → chunk-4EFXIE53.js} +2 -2
- package/dist/{chunk-MJ6J74F3.cjs → chunk-5BDUMDAQ.cjs} +3918 -3194
- package/dist/{chunk-ZVSSAULC.js → chunk-JY2BWYXY.js} +3918 -3194
- package/dist/{chunk-TCND5PFF.js → chunk-KTEDPS4X.js} +3 -3
- package/dist/{chunk-LR6FDJI3.js → chunk-Q23JULNN.js} +2736 -2749
- package/dist/{chunk-YAHFPOQF.cjs → chunk-SK3GYRNA.cjs} +143 -143
- package/dist/{chunk-DXEBPK55.cjs → chunk-TH7B5H4P.cjs} +286 -286
- package/dist/{chunk-DKAD7MOV.cjs → chunk-VFLAMMIJ.cjs} +2768 -2781
- package/dist/index-C3KFMnRT.d.cts +6860 -0
- package/dist/index-C3KFMnRT.d.ts +6860 -0
- package/dist/{index-CqO1HNHl.d.ts → index-C7rMJ4IV.d.ts} +132 -7184
- package/dist/{index-CqO1HNHl.d.cts → index-Cm_Xpt7X.d.cts} +132 -7184
- package/dist/index.cjs +23 -25
- package/dist/index.d.cts +58 -34
- package/dist/index.d.ts +58 -34
- package/dist/index.js +9 -11
- package/dist/operations/index.cjs +363 -242
- package/dist/operations/index.d.cts +281 -25
- package/dist/operations/index.d.ts +281 -25
- package/dist/operations/index.js +178 -57
- package/dist/pool.cjs +8 -8
- package/dist/pool.d.cts +2 -1
- package/dist/pool.d.ts +2 -1
- package/dist/pool.js +4 -4
- package/dist/resources/index.cjs +4 -4
- package/dist/resources/index.d.cts +2 -1
- package/dist/resources/index.d.ts +2 -1
- package/dist/resources/index.js +3 -3
- package/dist/types/index.d.cts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/docs/API.md +95 -120
- package/docs/LIVE-TESTING.md +9 -5
- package/docs/RELEASING.md +121 -0
- package/package.json +10 -3
package/docs/API.md
CHANGED
|
@@ -740,20 +740,11 @@ carry paging parameters.
|
|
|
740
740
|
query-verb-ONLY (`queryAll` / `count` bags are unchanged; the raw
|
|
741
741
|
`.query()` walker is uncapped — section 8's `queryBounded` is the MCP
|
|
742
742
|
query surface the issue targets).
|
|
743
|
-
- **`fieldsPreset
|
|
744
|
-
presets
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
`billing_items` (the 14 join/measure columns — section 11.23). The
|
|
749
|
-
closed-bag refusals are pre-request (zero wire): unknown preset name
|
|
750
|
-
(the valid names are named), preset/resource mismatch (the preset and
|
|
751
|
-
its resource are named), and `fieldsPreset` + an explicit
|
|
752
|
-
`includeFields` in the same bag (both are named — no silent
|
|
753
|
-
precedence). The preset FIELD EXISTENCE is verified against the LIVE
|
|
754
|
-
field catalogue (the live-smoke check — the review correction's
|
|
755
|
-
mandate; the vendored spec is a non-authoritative guard only), not
|
|
756
|
-
against `MODEL_FIELD_SETS`.
|
|
743
|
+
- **`fieldsPreset` — removed in 0.42.0 (issue #287).** The closed
|
|
744
|
+
includeFields presets are gone; the former `billing_join` column list is
|
|
745
|
+
documented as a plain `includeFields` example in section 11.23. A
|
|
746
|
+
`fieldsPreset` in the bag is refused pre-request as an unknown key
|
|
747
|
+
(the valid keys are named; `includeFields` is the named successor).
|
|
757
748
|
- Raw SDK reads are unchanged: `queryAll` (deterministic plain `T[]`,
|
|
758
749
|
unbounded, 1000-page guarded) and `query` (stream, `for await ...`). The
|
|
759
750
|
MCP surface steers the caller to the bounded `query` op; `queryAll` is for
|
|
@@ -1072,7 +1063,7 @@ be cross-mode-refused.
|
|
|
1072
1063
|
whose MCP annotation is `readOnlyHint !== true` — today exactly **267
|
|
1073
1064
|
tools**: the 265 dedicated write/delete tools (209 `effect=write` + 56
|
|
1074
1065
|
`effect=destructive`, every one of them has a dedicated tool) +
|
|
1075
|
-
`autotask_write` + `autotask_delete`. The remaining **
|
|
1066
|
+
`autotask_write` + `autotask_delete`. The remaining **635 tools** (the 622 dedicated read tools + the 10 core read tools + `autotask_read` +
|
|
1076
1067
|
`autotask_catalog` + `autotask_describe`) all carry `readOnlyHint: true`
|
|
1077
1068
|
and are safe ungated: every op they can route is `effect=read` (zero
|
|
1078
1069
|
mutating requests — pinned by the test inventory, §6 of the split design).
|
|
@@ -1405,7 +1396,7 @@ notes cannot be deleted, so they never use create-then-delete.
|
|
|
1405
1396
|
- `search` refuses an unknown option key (a misspelt `companyId` can't silently widen the search), and drops an
|
|
1406
1397
|
empty `userDefinedFields` the vendor appends to projected rows.
|
|
1407
1398
|
- **Hours per ticket for a resource** (`time_entries.recentTickets` was removed in 0.41.0, issue #290): aggregate
|
|
1408
|
-
the time entries grouped by ticket, using relation filters (§5) and
|
|
1399
|
+
the time entries grouped by ticket, using relation filters (§5) and label siblings (§11.27, opt-in):
|
|
1409
1400
|
|
|
1410
1401
|
```ts
|
|
1411
1402
|
const res = await aggregate(client, 'time_entries.query', [
|
|
@@ -1413,13 +1404,13 @@ notes cannot be deleted, so they never use create-then-delete.
|
|
|
1413
1404
|
{ field: 'dateWorked', op: 'gte', value: '2026-10-01' },
|
|
1414
1405
|
{ field: 'dateWorked', op: 'lte', value: '2026-10-28' },
|
|
1415
1406
|
{ field: 'ticket.companyID', op: 'eq', value: 123 }, // optional: one company, resolved server-side
|
|
1416
|
-
], { groupBy: ['ticketID'], sum: ['hoursWorked'] });
|
|
1407
|
+
], { groupBy: ['ticketID'], sum: ['hoursWorked'], names: true });
|
|
1417
1408
|
// res.groups[i] = { key: { ticketID, ticketIDName: '<untrusted_content>T20261001.0001 Printer down</untrusted_content>' },
|
|
1418
1409
|
// count, sums: { hoursWorked } }
|
|
1419
1410
|
```
|
|
1420
1411
|
|
|
1421
|
-
For per-ticket status / assignee / company use `operations.fetchByIds` on the group ticket ids
|
|
1422
|
-
|
|
1412
|
+
For per-ticket status / assignee / company use `operations.fetchByIds` on the group ticket ids
|
|
1413
|
+
(with `names: true`: `statusLabel`, `assignedResourceIDName`, `companyIDName`).
|
|
1423
1414
|
- **`tz` on the `dateWorked` ops** (`postedWithin`, `dateWorkedWindow`, `customRange` and the relative
|
|
1424
1415
|
windows): the vendor stores `dateWorked` as a date-only value at 00:00 UTC, so the wire bounds are always whole UTC days.
|
|
1425
1416
|
For `postedWithin` / `dateWorkedWindow` / `customRange` the `from`/`to` days ARE the days, so `tz` is
|
|
@@ -2171,11 +2162,14 @@ Field-validation failures ride on **HTTP 500**, not 4xx: the SDK surfaces a
|
|
|
2171
2162
|
typed `ServerError` (code `SERVER_ERROR`, httpStatus 500) with a structured
|
|
2172
2163
|
redacted `vendorError` naming the offending field and a deterministic
|
|
2173
2164
|
`suggestedAction` (rw-test.md: 9/9 create probes PASS the contract). **Known
|
|
2174
|
-
quirk**: the error contract labels 500s `retryable: true`
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2165
|
+
quirk**: the error contract labels 500s `retryable: true` even when the 500
|
|
2166
|
+
is a *deterministic* missing-field rejection — retrying with the same body
|
|
2167
|
+
fails identically; fix the field instead (`src/errors/` sets `retryable: true`
|
|
2168
|
+
for `SERVER_ERROR` and for every httpStatus >= 500). The plain-5xx
|
|
2169
|
+
`suggestedAction` is the honest SDK-voice hint (issue #287): the SDK already
|
|
2170
|
+
retried per its policy; a repeated 500 on one entity while others work
|
|
2171
|
+
usually means the API user lacks permission on that entity — ask the Autotask
|
|
2172
|
+
admin to grant it; otherwise retry once more at most. **Resolution (issue #23):** the *deterministic*
|
|
2179
2173
|
validation 500 is no longer labelled retryable — a 500 whose `errors[]` text
|
|
2180
2174
|
matches the documented heuristic phrases (`isDeterministicVendor500`:
|
|
2181
2175
|
`exceeds maximum length`, `Missing Required Field`, `Value does not exist for the required field`,
|
|
@@ -2848,24 +2842,20 @@ alone cannot fit) and the result carries `truncated: true` and
|
|
|
2848
2842
|
`truncation: { reason: 'maxBytes', maxBytes, fullBytes, omitted: [{ resource,
|
|
2849
2843
|
kind, count }], note }`, so it is always valid JSON and never over the cap (a
|
|
2850
2844
|
result whose envelope alone cannot fit, e.g. many resources under a small cap,
|
|
2851
|
-
is refused with `CONFIG_ERROR`).
|
|
2852
|
-
|
|
2853
|
-
`
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
row for a target the plan cannot enumerate reads `null` + `false`; the
|
|
2866
|
-
row carries no remedy, label or exposure claim (`plan.referenceTargets`
|
|
2867
|
-
is the only home for those). The same helper serves reference UDF rows —
|
|
2868
|
-
there is no second entity-type table.
|
|
2845
|
+
is refused with `CONFIG_ERROR`). Issue #287: an ABSENT `compact` is now
|
|
2846
|
+
compact BY DEFAULT — compact ONLY when the call passes no `fields` filter AND
|
|
2847
|
+
no `labels: true` (`labels: true` without `fields` keeps the full rows +
|
|
2848
|
+
picklist values — the ~137 KB case above, unchanged); an explicit
|
|
2849
|
+
`compact: false` restores the full rows. `maxBytes` still defaults off; a
|
|
2850
|
+
model-facing server can rely on the compact default and pass a `maxBytes`.
|
|
2851
|
+
|
|
2852
|
+
**Reference targets (issue #219 N1 — removed in issue #287).** The
|
|
2853
|
+
plan-static per-row extras `targetResource` / `targetEnumerable` and the
|
|
2854
|
+
`resourceForEntityType` helper are REMOVED (issue #287, breaking): a
|
|
2855
|
+
reference field row carries the vendor's `isReference` flag and
|
|
2856
|
+
`referenceEntityType` name, and the generated registry (the `apiEntity`
|
|
2857
|
+
join, via `autotask_catalog`) is where a caller resolves which plan
|
|
2858
|
+
resource enumerates a named entity — no second entity-type table ships.
|
|
2869
2859
|
|
|
2870
2860
|
**User-defined fields (issue #219 N7).** `udfCapable` entities carry a
|
|
2871
2861
|
parallel field system, and the served row states it rather than leaving it
|
|
@@ -3315,8 +3305,8 @@ picklist scope (owner-approved list: the ticket fields
|
|
|
3315
3305
|
rides ONE `operations.fieldCatalog` `labels: true` call (zero extra wire
|
|
3316
3306
|
on a warm fieldCache). The separate name-read budget counts COLD wire
|
|
3317
3307
|
name reads only (cache hits are free of it); over-budget ids serve
|
|
3318
|
-
`name: null
|
|
3319
|
-
|
|
3308
|
+
`name: null` on the composite's name row + the top-level `truncated`
|
|
3309
|
+
marker — never an error. An unresolvable id serves the blind
|
|
3320
3310
|
marker (`name: null, blind: true`); a picklist whose values the vendor
|
|
3321
3311
|
does not expose to this API user serves `null` + the fieldCatalog
|
|
3322
3312
|
reason row (the #64 W-A4 wall) — never an error. A picklist that IS served but does not
|
|
@@ -3396,12 +3386,12 @@ additive, and mirrors the family:
|
|
|
3396
3386
|
wire), and the result is the plain `items[]` array — no counts
|
|
3397
3387
|
(the time-entry family shape).
|
|
3398
3388
|
|
|
3399
|
-
### 11.23 Free-text caps
|
|
3389
|
+
### 11.23 Free-text caps (issue #61)
|
|
3400
3390
|
|
|
3401
3391
|
Issue #61 (the library remainder after the server-side split — TSV /
|
|
3402
3392
|
`output_path` / the untrusted-wrapper opt-out live in the server repo)
|
|
3403
|
-
|
|
3404
|
-
query-verb-ONLY and zero-wire
|
|
3393
|
+
added opt-in keys to the bounded-query bag (section 4), both
|
|
3394
|
+
query-verb-ONLY and zero-wire:
|
|
3405
3395
|
|
|
3406
3396
|
- **`noteChars: N` — the free-text cap.** One integer (>= 1) caps EVERY
|
|
3407
3397
|
string value in the resource's untrusted-class free-text fields (the
|
|
@@ -3413,20 +3403,6 @@ query-verb-ONLY and zero-wire or vendor-column-selection-only:
|
|
|
3413
3403
|
ordering note). Additive result field: `noteCharsTruncated` (the
|
|
3414
3404
|
truncated-field count; present only when > 0). Absent = the raw result
|
|
3415
3405
|
bit-for-bit.
|
|
3416
|
-
- **`fieldsPreset: name` — the closed preset list.** A preset is
|
|
3417
|
-
`{ resource, fields }` — hand-curated (NOT plan-derived: a curation
|
|
3418
|
-
choice, same class as n8n-mcp.curation.json), resolved pre-walk to the
|
|
3419
|
-
walk's `includeFields` (the vendor's column selection; `id` always
|
|
3420
|
-
appended). The v1 list is CLOSED at the one preset the issue names:
|
|
3421
|
-
|
|
3422
|
-
| preset | resource | fields (14) |
|
|
3423
|
-
|---|---|---|
|
|
3424
|
-
| `billing_join` | `billing_items` | `companyID, ticketID, projectID, timeEntryID, contractServiceID, contractID, billingCodeID, expenseItemID, itemDate, quantity, rate, extendedPrice, totalAmount, nonBillable` |
|
|
3425
|
-
|
|
3426
|
-
`billing_join` is the billing-item join read: the FK columns that join a
|
|
3427
|
-
billing item to its company / ticket / project / time entry / contract
|
|
3428
|
-
service / contract / billing code / expense item, plus the date and the
|
|
3429
|
-
amount fields — the "common narrow read" the issue's example targets.
|
|
3430
3406
|
- **`fieldCheck: false` — the field-NAME checks' opt-out (issues #119b +
|
|
3431
3407
|
#125).** By default the bounded `query` op validates every `includeFields`
|
|
3432
3408
|
name AND every `filter` condition field name BEFORE the query POST: the
|
|
@@ -3455,31 +3431,25 @@ query-verb-ONLY and zero-wire or vendor-column-selection-only:
|
|
|
3455
3431
|
groupBy/sum field names ARE checked; name-checking the aggregate filter
|
|
3456
3432
|
is a separate follow-up, the op's own field whitelist being the
|
|
3457
3433
|
different authority).
|
|
3458
|
-
- **
|
|
3459
|
-
|
|
3460
|
-
catalogue (`operations.fieldCatalog` — the entityInformation/fields
|
|
3461
|
-
channel; the #64 fieldCache idiom), NOT against `MODEL_FIELD_SETS`
|
|
3462
|
-
(the vendored-spec projection — a cheap NON-AUTHORITATIVE subset guard
|
|
3463
|
-
only; the spec can lag the live API). The check runs in the live round
|
|
3464
|
-
(`npm run live-smoke` — read-only, one field-catalogue read per preset;
|
|
3465
|
-
a missing field FAILS the run), not in `npm test`. The v1 field list's
|
|
3466
|
-
LIVE existence is verified there — the offline proof is the spec-subset
|
|
3467
|
-
guard only (the review correction's mandate, the `fetchMany.liveFields`
|
|
3468
|
-
live-first idiom).
|
|
3469
|
-
- **Refusals (pre-request, zero wire, typed):** unknown preset name (the
|
|
3470
|
-
valid names are named — the closed-set discipline), preset/resource
|
|
3471
|
-
mismatch (the preset and its resource are named), `fieldsPreset` + an
|
|
3472
|
-
explicit `includeFields` (both are named — no silent precedence), and
|
|
3473
|
-
`noteChars` `0` / negative / non-integer.
|
|
3434
|
+
- **Refusals (pre-request, zero wire, typed):** `noteChars` `0` /
|
|
3435
|
+
negative / non-integer.
|
|
3474
3436
|
- **Backwards compatibility:** both keys are OPT-IN on a closed bag —
|
|
3475
3437
|
absent = the pre-#61 path bit-for-bit (the default-path zero-move is
|
|
3476
|
-
pinned by the note-chars sidecar tests).
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3438
|
+
pinned by the note-chars sidecar tests). `UNTRUSTED_FIELDS_BY_RESOURCE`
|
|
3439
|
+
and `noteCharsTruncationMarker` are internal. `src/untrusted.ts` is
|
|
3440
|
+
byte-identical (the projection contract is untouched — the cap is a
|
|
3441
|
+
query-path mechanism, not a projection change).
|
|
3442
|
+
|
|
3443
|
+
**The `fieldsPreset` presets are removed (issue #287, 0.42.0).** The closed
|
|
3444
|
+
includeFields presets are gone — the `FIELD_PRESETS`, `FIELD_PRESET_NAMES`,
|
|
3445
|
+
`resolveFieldsPreset` and `FieldsPreset` exports and the bag key no longer
|
|
3446
|
+
exist, and a `fieldsPreset` in the bag is refused pre-request as an unknown
|
|
3447
|
+
key (the refusal names `includeFields`). The one preset v1 shipped —
|
|
3448
|
+
`billing_join` on `billing_items` — is now a plain column list:
|
|
3449
|
+
`companyID, ticketID, projectID, timeEntryID, contractServiceID,
|
|
3450
|
+
contractID, billingCodeID, expenseItemID, itemDate, quantity, rate,
|
|
3451
|
+
extendedPrice, totalAmount, nonBillable` — pass it as an explicit
|
|
3452
|
+
`includeFields` (`id` is always appended by the id-append).
|
|
3483
3453
|
|
|
3484
3454
|
|
|
3485
3455
|
### 11.24 The unbilled-work composite and the generic aggregate (issue #62)
|
|
@@ -3892,13 +3862,24 @@ returns the contract's reconciled P&L over a date window:
|
|
|
3892
3862
|
catch-all/start, the provided-without-rate and fully-missing `missing`
|
|
3893
3863
|
— never a silent zero — and `internalCost: 0` as a RESOLVED zero).
|
|
3894
3864
|
|
|
3895
|
-
### 11.27
|
|
3865
|
+
### 11.27 Label siblings on query, aggregate and fetch (opt-in, issue #287)
|
|
3896
3866
|
|
|
3897
3867
|
Rows served by the invoke `<resource>.query` op (the bounded query), the group
|
|
3898
3868
|
keys of `operations.aggregate`, and the found records of `operations.fetchMany`
|
|
3899
|
-
/ `operations.fetchByIds` carry label siblings **
|
|
3869
|
+
/ `operations.fetchByIds` carry label siblings **when opted in** (issue #287:
|
|
3870
|
+
OFF by default — the label pass costs a field-catalog read plus up to 20 cold
|
|
3871
|
+
name reads per call, so a plain read returns raw ids), through the same
|
|
3900
3872
|
`resolveCompositeNames` path the full-context composites use:
|
|
3901
3873
|
|
|
3874
|
+
- **The client option:** `labels: true` on the client config turns the label
|
|
3875
|
+
siblings on for every read surface of that client (the MCP server sets it,
|
|
3876
|
+
server-side).
|
|
3877
|
+
- **The per-call key:** `names: true` in the query bag / the aggregate opts /
|
|
3878
|
+
the `fetchMany` / `fetchByIds` opts turns them on for that call; `names:
|
|
3879
|
+
false` turns them off (even when the client has `labels: true`); ABSENT =
|
|
3880
|
+
the client's option (the off default). A non-boolean `names` is refused
|
|
3881
|
+
pre-request.
|
|
3882
|
+
|
|
3902
3883
|
| field on the row | sibling | example |
|
|
3903
3884
|
|---|---|---|
|
|
3904
3885
|
| a picklist field of the resource (catalog `isPickList`) | `<field>Label` | `status: 5` -> `statusLabel: "Complete"` |
|
|
@@ -3906,25 +3887,24 @@ keys of `operations.aggregate`, and the found records of `operations.fetchMany`
|
|
|
3906
3887
|
| `ticketID` | `ticketIDName` = `"<ticketNumber> <title>"` | `"T20261001.0001 Printer down"` |
|
|
3907
3888
|
| `resourceID` and every `*ResourceID` | `<field>Name` (staff name) | `assignedResourceIDName: "Ada Lovelace"` |
|
|
3908
3889
|
|
|
3909
|
-
- **Untrusted content:** every `*Name` value
|
|
3890
|
+
- **Untrusted content:** every `*Name` value is vendor free text
|
|
3910
3891
|
outside the untrusted field registry, so the SDK caps it at 200 chars (the
|
|
3911
3892
|
noteChars marker `… [+N chars truncated]`) and wraps it in
|
|
3912
3893
|
`<untrusted_content>…</untrusted_content>` (embedded tags neutralised, the
|
|
3913
3894
|
same `wrapUntrusted` the projection applies to `title`). Picklist `*Label`
|
|
3914
3895
|
values stay raw (picklists are outside the untrusted class by the registry
|
|
3915
3896
|
rule). Example: `contractIDName: "<untrusted_content>Managed Services 2026</untrusted_content>"`.
|
|
3916
|
-
- `assignedResourceName`
|
|
3917
|
-
|
|
3918
|
-
its removal is deferred to a later minor.
|
|
3897
|
+
- The `assignedResourceName` alias (0.39.0, issue #282) is removed (issue
|
|
3898
|
+
#287) — read `assignedResourceIDName`.
|
|
3919
3899
|
- Aggregate: `groups[].key.ticketIDName`, `groups[].key.statusLabel`, … (only the
|
|
3920
3900
|
returned `top` groups are labelled).
|
|
3921
3901
|
- A labelled field that is present in the row always gets its sibling (issue
|
|
3922
3902
|
#282): a `null` value, a `0` id on any FK except `companyID` (never resolved: the node skips it and does not read a record; this is a node rule, not a vendor statement that 0 means "no reference"; `companyID: 0` is the tenant owner's own company and IS resolved, issue #306, verified live on one tenant, no other FK target probed has an id 0), an unknown id
|
|
3923
|
-
(the vendor's not-found) or
|
|
3924
|
-
never an error. Only a field absent from the
|
|
3903
|
+
(the vendor's not-found) or an id the cold-read cap left unlabelled gets
|
|
3904
|
+
`null`, never a missing key and never an error. Only a field absent from the
|
|
3905
|
+
row gets no sibling. Example:
|
|
3925
3906
|
`{ contractID: null, contractIDName: null, assignedResourceID: null,
|
|
3926
|
-
assignedResourceIDName: null,
|
|
3927
|
-
statusLabel: null }`.
|
|
3907
|
+
assignedResourceIDName: null, status: null, statusLabel: null }`.
|
|
3928
3908
|
- A picklist value of `0` is looked up like any other value (issue #284): some
|
|
3929
3909
|
picklists define it (live: contracts and projects `status` 0 = "Inactive",
|
|
3930
3910
|
opportunities `status` 0 = "Not Ready To Buy", companies
|
|
@@ -3936,38 +3916,33 @@ keys of `operations.aggregate`, and the found records of `operations.fetchMany`
|
|
|
3936
3916
|
SDK cannot tell the two apart. On a picklist the vendor does not serve at
|
|
3937
3917
|
all, a 0 does get the reason row.) Any
|
|
3938
3918
|
other value the served list lacks keeps its `reasons` row.
|
|
3939
|
-
- A failed field-catalog read
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
- The resource `query()` iterator (the server's `output_path` walk) labels
|
|
3950
|
-
each page the same way but has no result envelope, so it carries no `names`
|
|
3951
|
-
summary: a capped id or a failed catalog read is not reported there. Use the
|
|
3952
|
-
invoke `<resource>.query` op when the summary matters.
|
|
3953
|
-
- Opt out per call: `names: false` (query bag key, aggregate opts, fetchMany /
|
|
3954
|
-
fetchByIds opts). A non-boolean `names` is refused pre-request.
|
|
3919
|
+
- A failed field-catalog read leaves the picklist fields UNKNOWN for the call
|
|
3920
|
+
(the live catalog is the only source of picklist-ness): the `*Label` siblings
|
|
3921
|
+
are simply absent and nothing is reported (the 0.39.x `catalogError` channel
|
|
3922
|
+
was removed in issue #287). `*Name` siblings do not depend on the catalog and
|
|
3923
|
+
are served as usual. Retry the call to label the picklists.
|
|
3924
|
+
- The resource `query()` iterator (the server's `output_path` walk) adds the
|
|
3925
|
+
siblings to each page the same way when opted in but has no result envelope,
|
|
3926
|
+
so it carries no `names` summary: the cold-read cap is not reported there
|
|
3927
|
+
(a budget-exhausted id simply serves `null`). Use the invoke
|
|
3928
|
+
`<resource>.query` op when the summary matters.
|
|
3955
3929
|
- Caches: picklists ride the field cache, staff and record names the record
|
|
3956
3930
|
cache (all default-on; invalidated as documented in §3 —
|
|
3957
3931
|
a write through the client clears the affected resource's entries, TTL
|
|
3958
3932
|
otherwise). A warm call costs zero extra vendor calls; a cold one costs one
|
|
3959
3933
|
field-metadata GET for the resource plus one GET per distinct uncached id.
|
|
3960
3934
|
- Cold-read cap: at most `DEFAULT_MAX_NAME_READS` (20) uncached name reads per
|
|
3961
|
-
call (per resource for fetch batches).
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
`
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3935
|
+
call (per resource for fetch batches). The `names` summary appears ONLY when
|
|
3936
|
+
something was left unlabelled — the cap was hit and/or a picklist label could
|
|
3937
|
+
not be resolved: `{ note, reasons? }` (issue #287 trimmed the structured
|
|
3938
|
+
`truncated` / `capped` / `catalogError` bookkeeping — the note is the
|
|
3939
|
+
summary). Cap note: `"ids left unlabelled (their *Name is null): the
|
|
3940
|
+
per-call cap of 20 uncached name reads was hit — names already read are
|
|
3941
|
+
cached, so repeating or narrowing the call labels more"`. Unresolvable
|
|
3942
|
+
picklists add a note clause and a `reasons` entry per field (the
|
|
3943
|
+
field-catalog honesty note / the value-not-served note); on `fetchMany` /
|
|
3944
|
+
`fetchByIds` the notes merge across resources and `reasons` keys the plain
|
|
3945
|
+
field name. Absent `names` = every sibling was resolved.
|
|
3971
3946
|
- Response size (full model, wrapped names): a tickets row grows ~40% (live,
|
|
3972
3947
|
100 rows, 31 siblings per row, some of them null: 341,178 B -> 477,325 B;
|
|
3973
3948
|
more when every FK is populated), a time_entries row ~77% (worst case, every
|
package/docs/LIVE-TESTING.md
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
# Live testing (the morning run)
|
|
2
2
|
|
|
3
|
-
The runbook for the REAL live run against an Autotask tenant.
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
The runbook for the REAL live run against an Autotask tenant. Credentials come
|
|
4
|
+
from the process environment: the scripts autoload `./.env` (`AUTOTASK_ENV_FILE`
|
|
5
|
+
overrides the path; variables already in the shell win), so the same values can
|
|
6
|
+
be exported in the shell instead. Nothing is committed, and nothing sensitive
|
|
7
|
+
is printed: the scripts emit env-var names only (verified by audit LIVE-1), and
|
|
8
|
+
the `--dry` preflight prints a value-free summary — a length plus a 2-character
|
|
9
|
+
prefix per value (shorter values print neither).
|
|
6
10
|
The scripts FAIL CLOSED without credentials (exit 2) — a green exit is never
|
|
7
11
|
a false pass.
|
|
8
12
|
|
|
@@ -13,8 +17,8 @@ a false pass.
|
|
|
13
17
|
test old bytes; the check is newest-src vs oldest-dist mtimes, the
|
|
14
18
|
`--allow-stale-dist` escape is explicit):
|
|
15
19
|
npm run build
|
|
16
|
-
- Credentials (values as placeholders —
|
|
17
|
-
tests):
|
|
20
|
+
- Credentials (values as placeholders — in `./.env`, or exported in the SAME
|
|
21
|
+
shell you run the tests; the shell wins):
|
|
18
22
|
export AUTOTASK_TRACKING_IDENTIFIER="<your Tracking Identifier — Admin > API User Security>"
|
|
19
23
|
export AUTOTASK_USERNAME="<your Username — API access must be enabled for that user>"
|
|
20
24
|
export AUTOTASK_SECRET="<your Password/Secret — Admin > API User Security>"
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Releasing node-autotask
|
|
2
|
+
|
|
3
|
+
Publishing happens only through `.github/workflows/release.yml`, which runs when a `v<version>` tag is pushed.
|
|
4
|
+
Nobody (owner or agent) runs `npm publish` by hand. The day-to-day steps (version bump, merge, tag, confirm,
|
|
5
|
+
record the plan hash) are the close-out checklist in [AGENTS.md](../AGENTS.md#5-close-out-autonomous-after-the-gates).
|
|
6
|
+
This page covers the setup only the owner can do, the first CI release, and rollback.
|
|
7
|
+
|
|
8
|
+
## Safeguards (no approval step)
|
|
9
|
+
|
|
10
|
+
**Pushing a `v*` tag is the release.** The owner has delegated merge, tag and publish to agents: no
|
|
11
|
+
per-release approval is requested. With approval removed, the only safeguards are the cadence gates in
|
|
12
|
+
[AGENTS.md](../AGENTS.md) (green verify and CI, `y-pragent` all-clear on the final head, stronger-model QA,
|
|
13
|
+
verified merge, the hard stops) and this workflow's own checks. A reviewer on the `release` environment, branch
|
|
14
|
+
protection and a `v*` tag ruleset would add a hard gate, but need a GitHub plan that supports them on a private
|
|
15
|
+
repo (see below). If agents are blocked from `gh pr merge` or tag pushes by the permission classifier, add
|
|
16
|
+
Bash allow rules to `.claude/settings.json`: `gh pr merge *`, `git tag *`, `git push origin v*`,
|
|
17
|
+
`gh release create *`.
|
|
18
|
+
|
|
19
|
+
## What the workflows do
|
|
20
|
+
|
|
21
|
+
- `ci.yml`: `npm ci`, then `npm run verify` with `COVERAGE=1`, so its single test run also enforces the coverage
|
|
22
|
+
thresholds. Runs on every pull request and push to `main`. On a pull request whose `package.json` version
|
|
23
|
+
differs from the base branch it also runs the release-consistency checks (`release:check`, plan hash
|
|
24
|
+
recorded, non-empty CHANGELOG section).
|
|
25
|
+
- `release.yml` (on tag `v*`):
|
|
26
|
+
1. `verify` job (`contents: read`, no publish rights): `npm ci --ignore-scripts`; checks the tag is
|
|
27
|
+
annotated, equals `v` + `package.json` version, sits on `main`, has its plan hash recorded and a non-empty
|
|
28
|
+
`## [<version>]` CHANGELOG section; runs `release:check`, `clean` and `verify`; checks the tree is
|
|
29
|
+
unchanged; packs the tarball, records its sha256 and uploads it with the release notes.
|
|
30
|
+
2. `publish` job (`id-token: write` only, environment `release`): no checkout, no project install, no project
|
|
31
|
+
scripts, so no dependency code runs where a publish credential can be minted. Downloads the tarball, checks
|
|
32
|
+
its hash, then guards (public registry, not a prerelease, not below npm `latest`, skip if already on npm)
|
|
33
|
+
and runs `npm publish <tarball> --ignore-scripts --access public` via npm trusted publishing (OIDC).
|
|
34
|
+
3. `github-release` job (`contents: write`, after `publish`): `gh release create v<version>` with the
|
|
35
|
+
CHANGELOG section as notes.
|
|
36
|
+
|
|
37
|
+
No `--provenance` while the repo is private: npm rejects it (E422), after already writing the signing entry to
|
|
38
|
+
the public Rekor transparency log. When the repo goes public, add `--provenance` to the publish command.
|
|
39
|
+
|
|
40
|
+
## Owner setup
|
|
41
|
+
|
|
42
|
+
Labels below are as of writing; if a screen differs, check the current npm / GitHub UI and docs.
|
|
43
|
+
|
|
44
|
+
### 1. npm trusted publishing (required)
|
|
45
|
+
|
|
46
|
+
`release.yml` publishes with npm trusted publishing (OIDC): no long-lived token, so nothing to rotate or leak.
|
|
47
|
+
Before the first CI release, on npmjs.com in the `node-autotask` package settings, add a GitHub Actions trusted
|
|
48
|
+
publisher: user `msoukhomlinov`, repository `node-autotask`, workflow filename `release.yml`, environment
|
|
49
|
+
`release`. npm must be >= 11.5.1 (the workflow installs exactly 11.5.1; bump it deliberately). Trusted publishing from a private repository works;
|
|
50
|
+
only the provenance attestation is unavailable there (check the current npm docs if a screen differs). Until
|
|
51
|
+
this is configured the publish step fails with an auth error before anything is published.
|
|
52
|
+
|
|
53
|
+
After the first release succeeds this way, delete the old `NPM_TOKEN` secret (`gh secret delete NPM_TOKEN`),
|
|
54
|
+
revoke the token on npmjs.com, and set the package's publishing access to disallow tokens.
|
|
55
|
+
|
|
56
|
+
### 2. The `release` environment (optional, plan-dependent)
|
|
57
|
+
|
|
58
|
+
`environment: release` is harmless when the environment does not exist: GitHub creates it, unprotected, on the
|
|
59
|
+
first run. To make it a second gate, in Settings > Environments > `release`:
|
|
60
|
+
|
|
61
|
+
1. Add yourself as a required reviewer. Leave "prevent self-review" off: agents push tags with your
|
|
62
|
+
credentials, so the run counts as triggered by you.
|
|
63
|
+
2. Restrict deployments to tags matching `v*`.
|
|
64
|
+
|
|
65
|
+
On a private repository, environment reviewers and deployment rules may need a paid plan: check the current
|
|
66
|
+
GitHub docs.
|
|
67
|
+
|
|
68
|
+
### 3. Branch and tag protection (needs GitHub Pro or a public repo)
|
|
69
|
+
|
|
70
|
+
Branch protection and rulesets are not available on the current plan for this private repo (the API answers
|
|
71
|
+
"Upgrade to GitHub Pro or make this repository public"). When they are:
|
|
72
|
+
|
|
73
|
+
1. `main`: require a pull request and the `verify` check from `ci.yml`; block force pushes and deletion.
|
|
74
|
+
2. Tags `v*`: restrict creation, update and deletion to yourself.
|
|
75
|
+
3. `y-pragent` cannot be a required check (it posts comments, not an approving review or status); its
|
|
76
|
+
final-head all-clear stays a process rule (AGENTS.md step 3).
|
|
77
|
+
|
|
78
|
+
**Residual risk until then:** anyone with write access can push a `v*` tag on a non-`main` commit and run
|
|
79
|
+
*that commit's* `release.yml`, which can mint an OIDC publish credential (the on-`main` check lives in the
|
|
80
|
+
workflow file, so a modified workflow can drop it). The npm trusted publisher is bound to the `release.yml`
|
|
81
|
+
filename and the `release` environment, not to a branch, so this stays open until a deployment tag restriction or
|
|
82
|
+
required reviewers exist on that environment. Keep write access to people and agents bound by AGENTS.md.
|
|
83
|
+
|
|
84
|
+
### 4. GitHub Actions minutes
|
|
85
|
+
|
|
86
|
+
CI runs on every PR and push to `main`; on a private repository that consumes the account's Actions minutes.
|
|
87
|
+
|
|
88
|
+
## The first CI-driven release
|
|
89
|
+
|
|
90
|
+
1. Confirm the npm trusted publisher is configured (setup 1); optionally set up the `release` environment.
|
|
91
|
+
2. Run the cadence to close-out: release PR with the version bump and CHANGELOG heading, `y-pragent`
|
|
92
|
+
all-clear on the final head, stronger-model QA SHIP, merged; then the annotated tag on the merge commit is pushed.
|
|
93
|
+
3. Watch the run (`gh run list --workflow release.yml`, `gh run watch`); approve the deployment if reviewers
|
|
94
|
+
are configured.
|
|
95
|
+
4. Check `npm view node-autotask@<version> version` and `gh release view v<version>`.
|
|
96
|
+
|
|
97
|
+
If the run fails:
|
|
98
|
+
|
|
99
|
+
- **Before `npm publish`** (nothing published): a re-run uses the workflow file *at the tagged commit*. A
|
|
100
|
+
transient failure can be re-run as is. If the fix is in the workflow or the code, merge it to `main`, delete
|
|
101
|
+
the unused tag (`git push origin :refs/tags/v<version>` and `git tag -d v<version>`), and tag the new merge
|
|
102
|
+
commit with the same version. Only ever do this when `npm view node-autotask@<version>` shows nothing.
|
|
103
|
+
- **After `npm publish`** (npm has the version but the run is red): re-run failed jobs; a re-run publish step sees the
|
|
104
|
+
version on npm and skips, then `github-release` runs. Or create the release by hand:
|
|
105
|
+
`gh release create v<version> --verify-tag --title v<version> --notes-file <CHANGELOG section file>`.
|
|
106
|
+
- **A cancelled run** (a newer tag queued behind it): re-run it.
|
|
107
|
+
- **Publish fails with an auth error**: the trusted publisher is not configured or does not match this repo,
|
|
108
|
+
workflow filename and environment (setup 1).
|
|
109
|
+
|
|
110
|
+
## Rollback
|
|
111
|
+
|
|
112
|
+
npm versions are immutable: never `npm unpublish` (it is restricted after 72 hours anyway, and the MCP server
|
|
113
|
+
pins exact versions). Instead:
|
|
114
|
+
|
|
115
|
+
1. Deprecate the bad version: `npm deprecate node-autotask@<bad> "<reason>; use <good>"`.
|
|
116
|
+
2. If `latest` points at it, move the tag back: `npm dist-tag add node-autotask@<good> latest`.
|
|
117
|
+
3. Ship the fix as a new version through the normal cadence (minor if the plan changed).
|
|
118
|
+
4. Edit the GitHub release notes to point at the fix; do not delete a published version's git tag.
|
|
119
|
+
5. Tell the MCP server repo (`msoukhomlinov/iit-mcp-autotask`) which version to pin.
|
|
120
|
+
|
|
121
|
+
`npm deprecate` and `npm dist-tag` need an authenticated npm session with 2FA; they are owner actions.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-autotask",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.43.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,
|
|
@@ -114,7 +114,7 @@
|
|
|
114
114
|
"coverage": "vitest run --coverage",
|
|
115
115
|
"lint": "eslint src test",
|
|
116
116
|
"lint:fix": "eslint src test --fix",
|
|
117
|
-
"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",
|
|
117
|
+
"verify": "npm run lockfile:check && npm run typecheck && npm run lint && npm run generate:check && npm run docs:render: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",
|
|
118
118
|
"plan:derive": "node scripts/derive-plan.mjs",
|
|
119
119
|
"generate:check": "node scripts/generate-batch.mjs --check && node scripts/generate-batch.mjs --check --group B",
|
|
120
120
|
"capabilities:build": "node scripts/generate-capabilities.mjs",
|
|
@@ -128,6 +128,7 @@
|
|
|
128
128
|
"catalog:build": "node scripts/build-tool-catalog.mjs",
|
|
129
129
|
"catalog:check": "node scripts/build-tool-catalog.mjs --check",
|
|
130
130
|
"docs:render": "node scripts/render-docs.mjs",
|
|
131
|
+
"docs:render:check": "node scripts/render-docs.mjs --check",
|
|
131
132
|
"mcp:check-surface": "node scripts/check-mcp-surface.mjs",
|
|
132
133
|
"n8n:project": "node scripts/project-n8n-mcp.mjs",
|
|
133
134
|
"n8n:project:check": "node scripts/project-n8n-mcp.mjs --check",
|
|
@@ -175,6 +176,12 @@
|
|
|
175
176
|
"zod": "^4.4.3"
|
|
176
177
|
},
|
|
177
178
|
"overrides": {
|
|
178
|
-
"esbuild": "^0.28.1"
|
|
179
|
+
"esbuild": "^0.28.1",
|
|
180
|
+
"rimraf": {
|
|
181
|
+
"glob": "^14.0.3"
|
|
182
|
+
},
|
|
183
|
+
"@typescript-eslint/typescript-estree": {
|
|
184
|
+
"minimatch": "^11.0.2"
|
|
185
|
+
}
|
|
179
186
|
}
|
|
180
187
|
}
|