@tenonhq/dovetail-servicenow 0.0.49 → 0.0.51
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/README.md +61 -30
- package/dist/cli.js +82 -15
- package/dist/cliUsage.js +32 -6
- package/dist/index.d.ts +2 -2
- package/dist/index.js +14 -4
- package/dist/mcp/registry.d.ts +1 -1
- package/dist/mcp/registry.js +71 -24
- package/dist/mcp/schemas.d.ts +22 -0
- package/dist/mcp/schemas.js +18 -3
- package/dist/table/addColumn.d.ts +36 -0
- package/dist/table/addColumn.js +119 -2
- package/dist/table/addIndex.d.ts +7 -1
- package/dist/table/addIndex.js +8 -2
- package/dist/table/createIndex.d.ts +106 -51
- package/dist/table/createIndex.js +548 -138
- package/dist/table/designAccess.d.ts +75 -0
- package/dist/table/designAccess.js +250 -0
- package/dist/table/index.d.ts +4 -2
- package/dist/table/index.js +15 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -736,7 +736,7 @@ admin, and `sys_index_column` does not exist at all (HTTP 400 `Invalid table`),
|
|
|
736
736
|
is no two-table index model to join and nothing to cross-check against.
|
|
737
737
|
|
|
738
738
|
Each row comes back as `{ name, columns, type, rawColumns }`. `columns` is the view's
|
|
739
|
-
bracketed `column_names` cell (`"[phone]"`, `"[a
|
|
739
|
+
bracketed `column_names` cell (`"[phone]"`, `"[a;b]"` - semicolon-separated when composite) **parsed** into a list - never
|
|
740
740
|
substring-matched, because `"[owner_id]"` contains `"owner"`. `type` is `access_method`.
|
|
741
741
|
|
|
742
742
|
**Uniqueness is not readable.** `v_db_index` has no uniqueness field, so a unique index
|
|
@@ -747,49 +747,73 @@ is wrong than that the table is unindexed - every physical table has a `PRIMARY`
|
|
|
747
747
|
|
|
748
748
|
### Create an index (composite and non-unique included)
|
|
749
749
|
|
|
750
|
-
> **
|
|
751
|
-
>
|
|
752
|
-
>
|
|
753
|
-
>
|
|
750
|
+
> **An index IS captured in an update set.** The platform's build job writes a
|
|
751
|
+
> `sys_update_xml` row (`type=Indexes`, name `sys_index_<table>_<col>_<col>…`) into the
|
|
752
|
+
> session user's **current** update set - the Database Indexes dialog says so itself, and
|
|
753
|
+
> it was read back live on tenonworkstudio 2026-10-08. `--update-set` is therefore
|
|
754
|
+
> **required** on the live path: the verb pins that set as current first and reads the
|
|
755
|
+
> capture row back from it afterwards. The physical index is still built per instance;
|
|
756
|
+
> committing the set elsewhere rebuilds it there.
|
|
754
757
|
|
|
755
758
|
```bash
|
|
756
|
-
# Dry-run (the DEFAULT) - sends nothing and reads nothing
|
|
759
|
+
# Dry-run (the DEFAULT) - sends nothing and reads nothing; no update set needed
|
|
757
760
|
npx dove-sn index-create --table x_cadso_journey_instance --columns state,created_on
|
|
758
761
|
|
|
759
762
|
# Send it
|
|
760
763
|
npx dove-sn index-create \
|
|
761
|
-
--table x_cadso_journey_instance --columns state,created_on
|
|
764
|
+
--table x_cadso_journey_instance --columns state,created_on \
|
|
765
|
+
--update-set <sys_id> --confirm --json
|
|
762
766
|
```
|
|
763
767
|
|
|
764
768
|
This is what `add-index` cannot do. `sys_dictionary.unique` - the only record-shaped lever
|
|
765
769
|
- is **per-column and unique-only**, so composite and plain indexes have no record path at
|
|
766
|
-
all
|
|
767
|
-
|
|
768
|
-
`
|
|
769
|
-
|
|
770
|
-
|
|
770
|
+
all (`sys_index` is API-level-ACL 403, `sys_index_column` does not exist). `index-create`
|
|
771
|
+
instead replays the two GlideAjax calls the platform's own Database Indexes dialog makes
|
|
772
|
+
on `xmlhttp.do` over a form-login session - taken from a HAR of the Studio dialog plus the
|
|
773
|
+
`dialog_index_create` UI page's client script, both read live:
|
|
774
|
+
|
|
775
|
+
1. `IndexCreatorErrorChecker` / `canCreate` - the dialog's pre-flight (`sysparm_table_name`,
|
|
776
|
+
`sysparm_field_names`, `sysparm_unique`, `sysparm_access_method`). A `canCreate:false`
|
|
777
|
+
verdict is returned verbatim with its `errorCode` and nothing is scheduled.
|
|
778
|
+
2. `ScheduleCreator` / `createSchedule` - schedules the build (`sysparm_table`,
|
|
779
|
+
`sysparm_fields`, `sysparm_access_method` - default `btree`, exactly as the dialog's
|
|
780
|
+
client JS does - `sysparm_unique` `true|false`, empty `sysparm_email` = no
|
|
781
|
+
notification, empty `sysparm_schedule_name`).
|
|
782
|
+
|
|
783
|
+
(The `index_creator_dialog` page's `sys_action=create_index` form is inert - the browser
|
|
784
|
+
never submits it, and the UI page has no processing script. The earlier form-POST replay
|
|
785
|
+
was a silent no-op for that reason.)
|
|
771
786
|
|
|
772
787
|
- **Dry-run by default.** Without `--confirm` nothing is sent *and nothing is read*;
|
|
773
788
|
`--dry-run` forces a plan even with `--confirm`.
|
|
789
|
+
- **Target checks before anything else.** The table must exist; the update set must exist,
|
|
790
|
+
be `in progress`, and belong to the table's application scope - a set in another scope
|
|
791
|
+
is refused, because the capture row would land in the wrong scope.
|
|
774
792
|
- **Idempotent.** On the live path `v_db_index` is read first, and an index over *exactly*
|
|
775
|
-
these columns short-circuits to `already-exists` with no form session and no
|
|
776
|
-
Column **order** is part of an index's identity - `[a
|
|
777
|
-
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
793
|
+
these columns short-circuits to `already-exists` with no pin, no form session and no
|
|
794
|
+
write. Column **order** is part of an index's identity - `[a;b]` is not `[b;a]`.
|
|
795
|
+
- **The pin is read back.** The set is pinned with Dovetail's own `changeUpdateSet` and
|
|
796
|
+
`currentUpdateSet` is read; a pin that did not take stops the run before the session
|
|
797
|
+
opens.
|
|
798
|
+
- **`--name` is refused.** The dialog has no name input; ServiceNow names the index after
|
|
799
|
+
its leading column (live: `[sys_created_on;status;version_step;version]` → index
|
|
800
|
+
`sys_created_on`). The created index's *real* name is returned in `name`.
|
|
801
|
+
- **The read-back is the proof.** After scheduling, the index is polled for in `v_db_index`
|
|
802
|
+
(default 10 checks, 3 s apart). If it never appears the status is `failed`: an accepted
|
|
803
|
+
schedule is not evidence an ALTER ran, and a unique index cannot build over duplicate
|
|
804
|
+
values (EMPTY counts). Then the capture row is looked for in the pinned set:
|
|
805
|
+
`captured:true` only when it was read back; an index that exists but was not captured
|
|
806
|
+
is `created:true, captured:false` with `update-set-capture` in `unverified` - and
|
|
807
|
+
exit code 2, because it will not travel.
|
|
784
808
|
- **Uniqueness is still never claimed.** `uniqueness-enforced` stays in `unverified` on
|
|
785
809
|
every status.
|
|
786
810
|
|
|
787
|
-
**Requires a username+password identity that can form-log-in.**
|
|
788
|
-
API-key-only
|
|
789
|
-
|
|
790
|
-
replay (including `create-table`'s) can work in that state.
|
|
811
|
+
**Requires a username+password identity that can form-log-in.** `xmlhttp.do` ignores
|
|
812
|
+
Basic auth and API keys, so an API-key-only, SSO or MFA identity fails at the session with
|
|
813
|
+
that diagnosis (TenonHQ/Dovetail#292).
|
|
791
814
|
|
|
792
|
-
Exit codes: `0` created / already-exists / dry-run, `1` bad args, `2` failed
|
|
815
|
+
Exit codes: `0` created / already-exists / dry-run, `1` bad args, `2` failed - and `2`
|
|
816
|
+
when the index was created but its capture row was not found in the pinned set.
|
|
793
817
|
|
|
794
818
|
### Set a field on a record
|
|
795
819
|
|
|
@@ -1154,15 +1178,22 @@ Claude Code and agents: `create_view`, `set_list_layout`, `set_form_layout`,
|
|
|
1154
1178
|
`add_column` (same-scope by default; `crossScope: true` with `scope` adds a column OWNED
|
|
1155
1179
|
by another app — a Journey field on an Automate table, element `<scope>_<name>` —
|
|
1156
1180
|
guarded by `sys_db_object.alter_access` and the update set's scope, with the stored
|
|
1157
|
-
element and `sys_scope` read back; the CLI flag is `--cross-scope
|
|
1181
|
+
element and `sys_scope` read back; the CLI flag is `--cross-scope`. A cross-scope
|
|
1182
|
+
result also carries `designAccess`: the platform UI refuses cross-scope authoring
|
|
1183
|
+
without a `sys_scope_design_access` record (column scope -> table scope), so a missing
|
|
1184
|
+
one is FLAGGED — not blocking, the headless insert works without it — and
|
|
1185
|
+
`ensureDesignAccess: true` / `--ensure-design-access` creates it first in the same
|
|
1186
|
+
update set) / `design_access` (ensure that record on its own — idempotent, created in
|
|
1187
|
+
the SOURCE app's update set and read back; CLI `dove-sn design-access --source <app>
|
|
1188
|
+
--target <app> --update-set <sys_id>`, `--dry-run` only reports exists/missing) /
|
|
1158
1189
|
`add_index` (a single-column unique index via `sys_dictionary.unique`,
|
|
1159
1190
|
read back from the `v_db_index` view - uniqueness enforcement is always reported
|
|
1160
1191
|
unverified) / `index_list` (read-only: a table's database indexes from `v_db_index`,
|
|
1161
1192
|
the only index read surface - `sys_index` is API-level-ACL 403 and `sys_index_column`
|
|
1162
1193
|
does not exist) / `index_create` (create an index, composite and non-unique included, by
|
|
1163
|
-
replaying the
|
|
1164
|
-
|
|
1165
|
-
|
|
1194
|
+
replaying the Database Indexes dialog's own processor calls; dry-run by default, idempotent,
|
|
1195
|
+
pinned to a required update set, read back from `v_db_index` and the capture row read back
|
|
1196
|
+
from `sys_update_xml`), the record-write verbs `set_field` (update scalar fields on an
|
|
1166
1197
|
existing record), `create_record` (insert one record) and `delete_record` (delete one
|
|
1167
1198
|
record — dry-run by default, `confirm:true` to apply, `updateSetSysId` required, the
|
|
1168
1199
|
record read back before AND after so success is only reported once it is confirmed
|
package/dist/cli.js
CHANGED
|
@@ -1340,15 +1340,60 @@ async function runCreateTable(flags) {
|
|
|
1340
1340
|
return 2;
|
|
1341
1341
|
return 0;
|
|
1342
1342
|
}
|
|
1343
|
+
/**
|
|
1344
|
+
* dove-sn design-access:
|
|
1345
|
+
* --source x_cadso_journey --target x_cadso_automate
|
|
1346
|
+
* [--update-set <sys_id in source>] [--dry-run] [--json]
|
|
1347
|
+
* Ensures the sys_scope_design_access record that lets the SOURCE app author in the
|
|
1348
|
+
* TARGET app's tables (required by the platform UI for cross-scope columns). Idempotent;
|
|
1349
|
+
* --dry-run only reports whether it exists. --update-set is required unless --dry-run.
|
|
1350
|
+
*/
|
|
1351
|
+
async function runDesignAccess(flags) {
|
|
1352
|
+
if (!flags.source || !flags.target) {
|
|
1353
|
+
process.stderr.write("design-access: --source (authoring app) and --target (app that owns the tables) are required\n");
|
|
1354
|
+
return 1;
|
|
1355
|
+
}
|
|
1356
|
+
var isDryRun = flags["dry-run"] === "true";
|
|
1357
|
+
if (!isDryRun && !flags["update-set"]) {
|
|
1358
|
+
process.stderr.write("design-access: --update-set (an update set in --source) is required on the live path (only --dry-run works without one)\n");
|
|
1359
|
+
return 1;
|
|
1360
|
+
}
|
|
1361
|
+
var result = await (0, table_1.ensureDesignAccess)({
|
|
1362
|
+
client: (0, client_1.createClient)({}),
|
|
1363
|
+
sourceScope: flags.source,
|
|
1364
|
+
targetScope: flags.target,
|
|
1365
|
+
updateSetSysId: flags["update-set"],
|
|
1366
|
+
dryRun: isDryRun,
|
|
1367
|
+
});
|
|
1368
|
+
if (flags.json === "true") {
|
|
1369
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
1370
|
+
}
|
|
1371
|
+
else {
|
|
1372
|
+
process.stdout.write("[" +
|
|
1373
|
+
result.status +
|
|
1374
|
+
"] " +
|
|
1375
|
+
result.sourceScope +
|
|
1376
|
+
" -> " +
|
|
1377
|
+
result.targetScope +
|
|
1378
|
+
(result.sysId ? " (" + result.sysId + ")" : "") +
|
|
1379
|
+
"\n" +
|
|
1380
|
+
result.note +
|
|
1381
|
+
"\n");
|
|
1382
|
+
}
|
|
1383
|
+
if (result.status === "failed")
|
|
1384
|
+
return 2;
|
|
1385
|
+
return 0;
|
|
1386
|
+
}
|
|
1343
1387
|
/**
|
|
1344
1388
|
* dove-sn add-column:
|
|
1345
1389
|
* --table x_cadso_journey --label URL --type url
|
|
1346
1390
|
* [--name url] [--max-length 1024] [--reference <table>]
|
|
1347
1391
|
* [--mandatory] [--default <value>] [--dependent-on-field <element>]
|
|
1348
|
-
* [--scope x_cadso_journey] [--cross-scope] [--
|
|
1349
|
-
* [--from-json <spec.json>] [--dry-run] [--debug] [--json]
|
|
1392
|
+
* [--scope x_cadso_journey] [--cross-scope] [--ensure-design-access]
|
|
1393
|
+
* [--update-set <sys_id>] [--from-json <spec.json>] [--dry-run] [--debug] [--json]
|
|
1350
1394
|
* --update-set is required unless --dry-run. --cross-scope opts in to a column
|
|
1351
|
-
* OWNED by --scope when that differs from the table's scope
|
|
1395
|
+
* OWNED by --scope when that differs from the table's scope; a missing Design Access
|
|
1396
|
+
* record is flagged, and --ensure-design-access creates it first.
|
|
1352
1397
|
*/
|
|
1353
1398
|
async function runAddColumn(flags) {
|
|
1354
1399
|
var spec = {};
|
|
@@ -1388,6 +1433,10 @@ async function runAddColumn(flags) {
|
|
|
1388
1433
|
if (flags["cross-scope"] === "true" || spec.crossScope === true) {
|
|
1389
1434
|
params.crossScope = true;
|
|
1390
1435
|
}
|
|
1436
|
+
if (flags["ensure-design-access"] === "true" ||
|
|
1437
|
+
spec.ensureDesignAccess === true) {
|
|
1438
|
+
params.ensureDesignAccess = true;
|
|
1439
|
+
}
|
|
1391
1440
|
var us = flags["update-set"] || spec.updateSetSysId;
|
|
1392
1441
|
if (us)
|
|
1393
1442
|
params.updateSetSysId = us;
|
|
@@ -1539,16 +1588,19 @@ async function runIndexList(flags) {
|
|
|
1539
1588
|
}
|
|
1540
1589
|
/**
|
|
1541
1590
|
* dove-sn index-create:
|
|
1542
|
-
* --table x_cadso_core_u_smoke --columns a,b
|
|
1543
|
-
* [--confirm] [--dry-run] [--poll-attempts <n>]
|
|
1544
|
-
* [--debug] [--json]
|
|
1591
|
+
* --table x_cadso_core_u_smoke --columns a,b --update-set <sys_id> [--unique]
|
|
1592
|
+
* [--access-method <m>] [--confirm] [--dry-run] [--poll-attempts <n>]
|
|
1593
|
+
* [--poll-interval-ms <n>] [--debug] [--json]
|
|
1545
1594
|
*
|
|
1546
1595
|
* DRY-RUN BY DEFAULT — nothing is sent (and nothing is even READ) without --confirm;
|
|
1547
|
-
* --dry-run forces a plan even with it.
|
|
1548
|
-
*
|
|
1596
|
+
* --dry-run forces a plan even with it. --update-set is REQUIRED on the live path:
|
|
1597
|
+
* the build job captures the index definition (sys_update_xml type=Indexes) into the
|
|
1598
|
+
* user's CURRENT update set, so the verb pins that set first and reads the capture
|
|
1599
|
+
* row back from it afterwards.
|
|
1549
1600
|
*
|
|
1550
1601
|
* Exit codes: 0 created / already-exists / dry-run, 1 bad args, 2 failed (which
|
|
1551
|
-
* includes "
|
|
1602
|
+
* includes "scheduled but no index was read back") — and 2 when the index was read
|
|
1603
|
+
* back but its capture row was NOT found in the pinned set.
|
|
1552
1604
|
*/
|
|
1553
1605
|
async function runIndexCreate(flags) {
|
|
1554
1606
|
var columns = splitList(flags.columns || "");
|
|
@@ -1556,10 +1608,12 @@ async function runIndexCreate(flags) {
|
|
|
1556
1608
|
process.stderr.write("index-create: --table <name> and --columns <a[,b,...]> are required\n");
|
|
1557
1609
|
return 1;
|
|
1558
1610
|
}
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1611
|
+
// DRY-RUN BY DEFAULT: --confirm is what sends; --dry-run forces a plan even with it.
|
|
1612
|
+
var indexDryRun = flags["dry-run"] === "true" || flags.confirm !== "true";
|
|
1613
|
+
if (!indexDryRun && !flags["update-set"]) {
|
|
1614
|
+
process.stderr.write("index-create: --update-set is required on the live path — the index " +
|
|
1615
|
+
"definition is captured into the user's CURRENT update set, so it must be " +
|
|
1616
|
+
"pinned first (only a dry-run works without one)\n");
|
|
1563
1617
|
return 1;
|
|
1564
1618
|
}
|
|
1565
1619
|
var params = {
|
|
@@ -1574,8 +1628,8 @@ async function runIndexCreate(flags) {
|
|
|
1574
1628
|
params.name = flags.name;
|
|
1575
1629
|
if (flags["access-method"])
|
|
1576
1630
|
params.accessMethod = flags["access-method"];
|
|
1577
|
-
if (flags["
|
|
1578
|
-
params.
|
|
1631
|
+
if (flags["update-set"])
|
|
1632
|
+
params.updateSetSysId = flags["update-set"];
|
|
1579
1633
|
if (flags.debug === "true")
|
|
1580
1634
|
params.debug = true;
|
|
1581
1635
|
// A non-integer poll setting would make the bounded wait unbounded (or zero).
|
|
@@ -1614,6 +1668,12 @@ async function runIndexCreate(flags) {
|
|
|
1614
1668
|
(result.name ? " -> " + result.name : "") +
|
|
1615
1669
|
(result.instance ? " on " + result.instance : "") +
|
|
1616
1670
|
(result.verified ? " — verified" : "") +
|
|
1671
|
+
(result.updateSet
|
|
1672
|
+
? " [update set " +
|
|
1673
|
+
(result.updateSet.name || result.updateSet.sysId) +
|
|
1674
|
+
(result.captured ? " — captured" : " — capture NOT verified") +
|
|
1675
|
+
"]"
|
|
1676
|
+
: "") +
|
|
1617
1677
|
"\n" +
|
|
1618
1678
|
result.note +
|
|
1619
1679
|
"\nUNVERIFIED: " +
|
|
@@ -1622,6 +1682,10 @@ async function runIndexCreate(flags) {
|
|
|
1622
1682
|
}
|
|
1623
1683
|
if (result.status === "failed")
|
|
1624
1684
|
return 2;
|
|
1685
|
+
// An index that exists but whose definition was not captured will not travel —
|
|
1686
|
+
// surface that as a non-zero exit, the same way an unread-back index is.
|
|
1687
|
+
if (result.status === "created" && !result.captured)
|
|
1688
|
+
return 2;
|
|
1625
1689
|
return 0;
|
|
1626
1690
|
}
|
|
1627
1691
|
/** Parse a CLI boolean flag. Bare `--mandatory` means true; `--mandatory false` means
|
|
@@ -2616,6 +2680,9 @@ async function dispatch(parsed) {
|
|
|
2616
2680
|
if (parsed.command === "add-column") {
|
|
2617
2681
|
return await runAddColumn(parsed.flags);
|
|
2618
2682
|
}
|
|
2683
|
+
if (parsed.command === "design-access") {
|
|
2684
|
+
return await runDesignAccess(parsed.flags);
|
|
2685
|
+
}
|
|
2619
2686
|
if (parsed.command === "add-index") {
|
|
2620
2687
|
return await runAddIndex(parsed.flags);
|
|
2621
2688
|
}
|
package/dist/cliUsage.js
CHANGED
|
@@ -209,6 +209,10 @@ exports.VERB_USAGE = {
|
|
|
209
209
|
flag: "cross-scope",
|
|
210
210
|
note: "Opt in to a column OWNED by --scope, which differs from the table's scope (element becomes <scope>_<name>; the table must allow new fields; --update-set must be in --scope).",
|
|
211
211
|
},
|
|
212
|
+
{
|
|
213
|
+
flag: "ensure-design-access",
|
|
214
|
+
note: "Cross-scope only: create the missing sys_scope_design_access record (--scope -> table's scope) first, in the same update set. Without it a missing record is only flagged.",
|
|
215
|
+
},
|
|
212
216
|
{ flag: "update-set", value: "<sys_id>", note: "REQUIRED on the live path (only --dry-run works without one)." },
|
|
213
217
|
DRY_RUN_FLAG,
|
|
214
218
|
DEBUG_FLAG,
|
|
@@ -222,6 +226,26 @@ exports.VERB_USAGE = {
|
|
|
222
226
|
"Cross-scope: dove-sn add-column --table x_cadso_automate_email_batch --label 'Instance Step' --name instance_step --type reference --reference x_cadso_journey_instance_step --scope x_cadso_journey --cross-scope --update-set <journey set>",
|
|
223
227
|
],
|
|
224
228
|
},
|
|
229
|
+
"design-access": {
|
|
230
|
+
summary: "Ensure the sys_scope_design_access record that lets one app author in another app's tables (required by the UI for cross-scope columns)",
|
|
231
|
+
required: [
|
|
232
|
+
{ flag: "source", value: "<x_scope>", note: "The AUTHORING app (owns the record and its update set)." },
|
|
233
|
+
{ flag: "target", value: "<x_scope>", note: "The app that OWNS the tables." },
|
|
234
|
+
],
|
|
235
|
+
optional: [
|
|
236
|
+
{ flag: "update-set", value: "<sys_id>", note: "Update set in --source. REQUIRED on the live path." },
|
|
237
|
+
DRY_RUN_FLAG,
|
|
238
|
+
JSON_FLAG,
|
|
239
|
+
],
|
|
240
|
+
gate: "dry-run-flag",
|
|
241
|
+
gateNote: "--update-set is required unless --dry-run; --dry-run reports exists/missing only.",
|
|
242
|
+
example: "dove-sn design-access --source x_cadso_journey --target x_cadso_automate --update-set <journey set>",
|
|
243
|
+
notes: [
|
|
244
|
+
"Idempotent: an existing record is reported and nothing is written.",
|
|
245
|
+
"Symptom it fixes: \"Invalid 'Table' selected on the Dictionary Entry record ... can only select '<app>' tables with read access enabled\" — the table's own access flags are NOT the gate.",
|
|
246
|
+
"Exit 2 when the record could not be created or verified on read-back.",
|
|
247
|
+
],
|
|
248
|
+
},
|
|
225
249
|
"set-column": {
|
|
226
250
|
summary: "Update an EXISTING column's schema (label/mandatory/default/read-only/max-length/dependent-on-field), then verify",
|
|
227
251
|
required: [
|
|
@@ -308,14 +332,15 @@ exports.VERB_USAGE = {
|
|
|
308
332
|
],
|
|
309
333
|
},
|
|
310
334
|
"index-create": {
|
|
311
|
-
summary: "Create a DATABASE index (composite and non-unique included) by replaying the platform
|
|
335
|
+
summary: "Create a DATABASE index (composite and non-unique included) by replaying the platform Database Indexes dialog's processor calls, pinned to an update set, then read it back",
|
|
312
336
|
required: [
|
|
313
337
|
{ flag: "table", value: "<name>" },
|
|
314
338
|
{ flag: "columns", value: "<a[,b,...]>", note: "Comma-separated column elements, in index order." },
|
|
339
|
+
{ flag: "update-set", value: "<sys_id>", note: "Required on the live path; the index definition is captured into it. Must be in the table's application scope." },
|
|
315
340
|
],
|
|
316
341
|
optional: [
|
|
317
342
|
{ flag: "unique" },
|
|
318
|
-
{ flag: "access-method", value: "<m>", note: "Platform access method (default: the
|
|
343
|
+
{ flag: "access-method", value: "<m>", note: "Platform access method (default: btree, as the dialog does)." },
|
|
319
344
|
CONFIRM_FLAG,
|
|
320
345
|
FORCE_DRY_RUN_FLAG,
|
|
321
346
|
{ flag: "poll-attempts", value: "<n>", note: "Positive integer." },
|
|
@@ -325,11 +350,12 @@ exports.VERB_USAGE = {
|
|
|
325
350
|
],
|
|
326
351
|
gate: "confirm",
|
|
327
352
|
gateNote: "Nothing is sent OR read without --confirm.",
|
|
328
|
-
example: "dove-sn index-create --table x_cadso_journey_instance --columns state,created_on --confirm",
|
|
353
|
+
example: "dove-sn index-create --table x_cadso_journey_instance --columns state,created_on --update-set <sys_id> --confirm",
|
|
329
354
|
notes: [
|
|
330
|
-
"
|
|
331
|
-
"Idempotent: an index over exactly those columns returns already-exists with no write. --name is refused (the
|
|
332
|
-
"Needs a username+password identity that can form-log-in.",
|
|
355
|
+
"An index IS captured in an update set: the build job writes a sys_update_xml row (type=Indexes) into the user's CURRENT set, so --update-set is pinned first and the capture row is read back from it. The physical index is still built per instance; committing the set elsewhere rebuilds it.",
|
|
356
|
+
"Replays the dialog's own xmlhttp.do calls: IndexCreatorErrorChecker.canCreate (pre-flight) then ScheduleCreator.createSchedule. Idempotent: an index over exactly those columns returns already-exists with no write. --name is refused (the dialog has no name input; the real name is returned).",
|
|
357
|
+
"Needs a username+password identity that can form-log-in (xmlhttp.do ignores Basic auth / API keys).",
|
|
358
|
+
"Exit 2 when the index was scheduled but never read back from v_db_index, and when it was read back but its capture row was not found in the pinned set.",
|
|
333
359
|
],
|
|
334
360
|
},
|
|
335
361
|
"set-field": {
|
package/dist/index.d.ts
CHANGED
|
@@ -21,8 +21,8 @@ export { sincPlugin } from "./plugin";
|
|
|
21
21
|
export { listTemplates, verifyArtifact, cloneSubflow, cloneActionType, resolveScope, slugInternalName, fetchActionSteps, findStepInput, triggerPublication, publishActionType, editActionType, defineActionType, planActionDefinition, validateDefineSpec, applyStepOps, verifySteps, summarizeSteps, formatStepPill, readFlow, readActionType, publishFlow, copyFlow, createFlow, buildPublishModel, editFlow, testFlow, DEFAULT_RUN_FLOW_PATH, generateSysId, topoSort, executeWritePlan, WriteOrderError, } from "./flowDesigner";
|
|
22
22
|
export type { TemplateRef, ListTemplatesParams, FlowKind, VerifyExpect, VerifyFound, VerifyFailure, VerifyReport, VerifyArtifactParams, CloneSubflowParams, CloneSubflowResult, CloneActionTypeParams, CloneActionTypeResult, CloneActionTypePlan, CloneActionTypeStepReport, TriggerPublicationParams, TriggerPublicationResult, PublishActionTypeParams, PublishActionTypeResult, EditActionTypeParams, EditActionTypeResult, EditActionTypeOps, DefineActionSpec, DefineActionInputSpec, DefineActionOutputSpec, DefineStepSpec, DefineStepVarSpec, DefineStepOutputSpec, DefineStepValue, DefineActionTypeParams, DefineActionTypeResult, DefineActionDiff, DefineActionPlan, PlanActionDefinitionParams, ActionView, StepView, StepOps, StepRecord, StepSummary, StepIoSummary, PatchStepScriptOp, SetStepInputOp, AddStepOutputOp, AddStepInputOp, ApplyStepOpsResult, VerifyStepsResult, ReadFlowParams, ReadFlowResult, FlowStep, FlowVariable, ReadActionTypeParams, ReadActionTypeResult, ActionIo, PublishFlowParams, PublishFlowResult, CopyFlowParams, CopyFlowResult, CreateFlowParams, CreateFlowResult, EditFlowParams, EditFlowResult, EditFlowOps, StepInputPatch, TestFlowParams, TestFlowResult, WriteOp, WriteOpResult, } from "./flowDesigner";
|
|
23
23
|
export type { ServiceNowClientConfig, ChoiceValue, ChoiceType, AddChoicesParams, AddChoicesResult, ChoiceActionResult, ChoiceFieldRef, RemoveChoicesParams, RemoveChoicesResult, ChoiceRemovalResult, DictionaryRecord, UpdateSetRecord, LayoutAction, LayoutRecordResult, LayoutResult, CreateViewParams, CreateViewResult, FormSectionSpec, SetFormLayoutParams, SetListLayoutParams, SetRelatedListsParams, ChunkRole, ChunkInfo, ChunkResult, PrunedResult, HostAssetsParams, HostAssetsResult, } from "./types";
|
|
24
|
-
export { createTable, projectTableGraph, buildColumnXml, normalizeColumns, resolveType, applyTableSaveOverlay, defaultAccessFlags, TYPE_MAP, DEFAULT_SUPER_CLASS, DEFAULT_SAVE_ACTION, addColumn, deriveElement, addIndex, parseIndexColumns, indexMatchesColumns, listIndexes, createIndex, validateCreateIndex,
|
|
25
|
-
export type { CreateTableParams, CreateTableResult, AddColumnParams, AddColumnResult, AddIndexParams, AddIndexResult, AddIndexVerification, ListIndexesParams, ListIndexesResult, TableIndex, CreateIndexParams, CreateIndexResult, SetColumnParams, SetColumnResult, ColumnAttributes, AttributeChange, SetTableParams, SetTableResult, TableAttributes, TableGraph, NormalizedColumn, ColumnSpec, AccessFlags, OverlaySpec, } from "./table";
|
|
24
|
+
export { createTable, projectTableGraph, buildColumnXml, normalizeColumns, resolveType, applyTableSaveOverlay, defaultAccessFlags, TYPE_MAP, DEFAULT_SUPER_CLASS, DEFAULT_SAVE_ACTION, addColumn, deriveElement, ensureDesignAccess, findDesignAccess, DESIGN_ACCESS_TABLE, addIndex, parseIndexColumns, indexMatchesColumns, listIndexes, createIndex, validateCreateIndex, captureRowName, parseAjaxAnswer, parseCanCreate, buildCanCreateFields, buildCreateScheduleFields, XMLHTTP_PATH, DEFAULT_ACCESS_METHOD, CAPTURE_NOTE, setColumn, resolveAttributes, toStoredValue, setTable, resolveTableAttributes, } from "./table";
|
|
25
|
+
export type { CreateTableParams, CreateTableResult, AddColumnParams, AddColumnResult, DesignAccessFlag, EnsureDesignAccessParams, EnsureDesignAccessResult, AddIndexParams, AddIndexResult, AddIndexVerification, ListIndexesParams, ListIndexesResult, TableIndex, CreateIndexParams, CreateIndexResult, SetColumnParams, SetColumnResult, ColumnAttributes, AttributeChange, SetTableParams, SetTableResult, TableAttributes, TableGraph, NormalizedColumn, ColumnSpec, AccessFlags, OverlaySpec, } from "./table";
|
|
26
26
|
export { setField } from "./setField";
|
|
27
27
|
export { createRecord } from "./createRecord";
|
|
28
28
|
export type { RecordWriteResult, SetFieldParams, SetFieldResult, } from "./setField";
|
package/dist/index.js
CHANGED
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.editFlow = exports.buildPublishModel = exports.createFlow = exports.copyFlow = exports.publishFlow = exports.readActionType = exports.readFlow = exports.formatStepPill = exports.summarizeSteps = exports.verifySteps = exports.applyStepOps = exports.validateDefineSpec = exports.planActionDefinition = exports.defineActionType = exports.editActionType = exports.publishActionType = exports.triggerPublication = exports.findStepInput = exports.fetchActionSteps = exports.slugInternalName = exports.resolveScope = exports.cloneActionType = exports.cloneSubflow = exports.verifyArtifact = exports.listTemplates = exports.sincPlugin = exports.formatCreateViewResult = exports.formatLayoutResult = exports.setRelatedLists = exports.setFormLayout = exports.setListLayout = exports.createView = exports.formatRemoveChoicesResult = exports.formatAddChoicesResult = exports.formatHostAssetsResult = exports.classifyChunks = exports.hostAssets = exports.ChoiceWriteError = exports.removeChoicesFromField = exports.addChoicesToField = exports.resolveConfigFromEnvFile = exports.createClientFromEnvFile = exports.assertWriteAllowed = exports.evaluateWriteGate = exports.isCiEnvironment = exports.resolveExecutionContext = exports.PROCESSFLOW_PATH_PREFIX = exports.isProcessflowPath = exports.resolveFlowAuth = exports.createClient = void 0;
|
|
10
|
-
exports.
|
|
11
|
-
exports.
|
|
10
|
+
exports.publishApp = exports.INVOKE_REST_METHODS = exports.invokeRest = exports.verifyReadBack = exports.readBackMatches = exports.hasHtmlEntity = exports.decodeHtmlEntities = exports.deleteRecord = exports.createRecord = exports.setField = exports.resolveTableAttributes = exports.setTable = exports.toStoredValue = exports.resolveAttributes = exports.setColumn = exports.CAPTURE_NOTE = exports.DEFAULT_ACCESS_METHOD = exports.XMLHTTP_PATH = exports.buildCreateScheduleFields = exports.buildCanCreateFields = exports.parseCanCreate = exports.parseAjaxAnswer = exports.captureRowName = exports.validateCreateIndex = exports.createIndex = exports.listIndexes = exports.indexMatchesColumns = exports.parseIndexColumns = exports.addIndex = exports.DESIGN_ACCESS_TABLE = exports.findDesignAccess = exports.ensureDesignAccess = exports.deriveElement = exports.addColumn = exports.DEFAULT_SAVE_ACTION = exports.DEFAULT_SUPER_CLASS = exports.TYPE_MAP = exports.defaultAccessFlags = exports.applyTableSaveOverlay = exports.resolveType = exports.normalizeColumns = exports.buildColumnXml = exports.projectTableGraph = exports.createTable = exports.WriteOrderError = exports.executeWritePlan = exports.topoSort = exports.generateSysId = exports.DEFAULT_RUN_FLOW_PATH = exports.testFlow = void 0;
|
|
11
|
+
exports.secretFieldsFromDictionary = exports.mergeSecretRules = exports.loadSecretRules = exports.defaultSecretRules = exports.escapeRegExp = exports.encodeXmlText = exports.encodeXmlEntities = exports.stripJsonValue = exports.stripField = exports.plannedStrips = exports.recordFieldNames = exports.readRecordTable = exports.readField = exports.verifyStripped = exports.stripSecrets = exports.DEFAULT_EXPORT_APP_TIMEOUT_MS = exports.buildPublishFields = exports.buildCreateSetFields = exports.exportApp = exports.DEFAULT_MAX_ROWS = exports.MAX_PAGE_SIZE = exports.DEFAULT_PAGE_SIZE = exports.UPDATE_XML_FIELDS = exports.xmlEscape = exports.formatUnloadDate = exports.parseStatsCount = exports.refreshTypeFields = exports.fetchUpdateXmlRows = exports.countUpdateXml = exports.countUnloadRecords = exports.renderUnload = exports.renderRemoteUpdateSet = exports.renderUpdateXmlRow = exports.exportUpdateSet = exports.PUBLISH_POLL_DELAYS_MS = exports.DEFAULT_PUBLISH_TIMEOUT_MS = exports.parseCicdProgress = exports.parseCicdPublishResponse = exports.harvestProgressResults = exports.flattenSteps = exports.classifyProgress = exports.parseProgressTree = exports.parseXmlAnswer = exports.PUBLISH_TARGETS = exports.parsePublishTargets = exports.describeTarget = exports.resolveUpdateSetNaming = exports.buildPublishToUpdateSetFields = exports.buildCreateUpdateSetFields = exports.buildStartFields = void 0;
|
|
12
|
+
exports.SECRET_INTERNAL_TYPES = exports.SENTINEL = exports.isCapturable = void 0;
|
|
12
13
|
var client_1 = require("./client");
|
|
13
14
|
Object.defineProperty(exports, "createClient", { enumerable: true, get: function () { return client_1.createClient; } });
|
|
14
15
|
Object.defineProperty(exports, "resolveFlowAuth", { enumerable: true, get: function () { return client_1.resolveFlowAuth; } });
|
|
@@ -91,14 +92,23 @@ Object.defineProperty(exports, "DEFAULT_SUPER_CLASS", { enumerable: true, get: f
|
|
|
91
92
|
Object.defineProperty(exports, "DEFAULT_SAVE_ACTION", { enumerable: true, get: function () { return table_1.DEFAULT_SAVE_ACTION; } });
|
|
92
93
|
Object.defineProperty(exports, "addColumn", { enumerable: true, get: function () { return table_1.addColumn; } });
|
|
93
94
|
Object.defineProperty(exports, "deriveElement", { enumerable: true, get: function () { return table_1.deriveElement; } });
|
|
95
|
+
Object.defineProperty(exports, "ensureDesignAccess", { enumerable: true, get: function () { return table_1.ensureDesignAccess; } });
|
|
96
|
+
Object.defineProperty(exports, "findDesignAccess", { enumerable: true, get: function () { return table_1.findDesignAccess; } });
|
|
97
|
+
Object.defineProperty(exports, "DESIGN_ACCESS_TABLE", { enumerable: true, get: function () { return table_1.DESIGN_ACCESS_TABLE; } });
|
|
94
98
|
Object.defineProperty(exports, "addIndex", { enumerable: true, get: function () { return table_1.addIndex; } });
|
|
95
99
|
Object.defineProperty(exports, "parseIndexColumns", { enumerable: true, get: function () { return table_1.parseIndexColumns; } });
|
|
96
100
|
Object.defineProperty(exports, "indexMatchesColumns", { enumerable: true, get: function () { return table_1.indexMatchesColumns; } });
|
|
97
101
|
Object.defineProperty(exports, "listIndexes", { enumerable: true, get: function () { return table_1.listIndexes; } });
|
|
98
102
|
Object.defineProperty(exports, "createIndex", { enumerable: true, get: function () { return table_1.createIndex; } });
|
|
99
103
|
Object.defineProperty(exports, "validateCreateIndex", { enumerable: true, get: function () { return table_1.validateCreateIndex; } });
|
|
100
|
-
Object.defineProperty(exports, "
|
|
101
|
-
Object.defineProperty(exports, "
|
|
104
|
+
Object.defineProperty(exports, "captureRowName", { enumerable: true, get: function () { return table_1.captureRowName; } });
|
|
105
|
+
Object.defineProperty(exports, "parseAjaxAnswer", { enumerable: true, get: function () { return table_1.parseAjaxAnswer; } });
|
|
106
|
+
Object.defineProperty(exports, "parseCanCreate", { enumerable: true, get: function () { return table_1.parseCanCreate; } });
|
|
107
|
+
Object.defineProperty(exports, "buildCanCreateFields", { enumerable: true, get: function () { return table_1.buildCanCreateFields; } });
|
|
108
|
+
Object.defineProperty(exports, "buildCreateScheduleFields", { enumerable: true, get: function () { return table_1.buildCreateScheduleFields; } });
|
|
109
|
+
Object.defineProperty(exports, "XMLHTTP_PATH", { enumerable: true, get: function () { return table_1.XMLHTTP_PATH; } });
|
|
110
|
+
Object.defineProperty(exports, "DEFAULT_ACCESS_METHOD", { enumerable: true, get: function () { return table_1.DEFAULT_ACCESS_METHOD; } });
|
|
111
|
+
Object.defineProperty(exports, "CAPTURE_NOTE", { enumerable: true, get: function () { return table_1.CAPTURE_NOTE; } });
|
|
102
112
|
Object.defineProperty(exports, "setColumn", { enumerable: true, get: function () { return table_1.setColumn; } });
|
|
103
113
|
Object.defineProperty(exports, "resolveAttributes", { enumerable: true, get: function () { return table_1.resolveAttributes; } });
|
|
104
114
|
Object.defineProperty(exports, "toStoredValue", { enumerable: true, get: function () { return table_1.toStoredValue; } });
|
package/dist/mcp/registry.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
10
10
|
import { z } from "zod";
|
|
11
11
|
import type { ToolAnnotations } from "@tenonhq/dovetail-mcp-kit";
|
|
12
12
|
import type { ServiceNowClient } from "../client";
|
|
13
|
-
export declare var TOOL_NAMES: readonly ["create_view", "set_list_layout", "set_form_layout", "set_related_lists", "add_choices_to_field", "remove_choices_from_field", "flow_view", "action_view", "action_edit", "action_clone", "action_define", "flow_publish", "flow_copy", "flow_create", "flow_test", "flow_edit", "create_table", "add_column", "add_index", "index_list", "index_create", "set_column", "set_table", "set_field", "create_record", "delete_record", "host_assets", "invoke_rest", "app_publish", "update_set_export", "app_export"];
|
|
13
|
+
export declare var TOOL_NAMES: readonly ["create_view", "set_list_layout", "set_form_layout", "set_related_lists", "add_choices_to_field", "remove_choices_from_field", "flow_view", "action_view", "action_edit", "action_clone", "action_define", "flow_publish", "flow_copy", "flow_create", "flow_test", "flow_edit", "create_table", "add_column", "design_access", "add_index", "index_list", "index_create", "set_column", "set_table", "set_field", "create_record", "delete_record", "host_assets", "invoke_rest", "app_publish", "update_set_export", "app_export"];
|
|
14
14
|
export type ToolName = (typeof TOOL_NAMES)[number];
|
|
15
15
|
export interface RegistryDeps {
|
|
16
16
|
/** Optional client injection for tests; defaults to createClient({}). */
|
package/dist/mcp/registry.js
CHANGED
|
@@ -57,6 +57,7 @@ exports.TOOL_NAMES = [
|
|
|
57
57
|
"flow_edit",
|
|
58
58
|
"create_table",
|
|
59
59
|
"add_column",
|
|
60
|
+
"design_access",
|
|
60
61
|
"add_index",
|
|
61
62
|
"index_list",
|
|
62
63
|
"index_create",
|
|
@@ -445,7 +446,11 @@ function buildDescriptors(deps = {}) {
|
|
|
445
446
|
"<scope>_<name>, the dictionary row and its update-set capture land in scope); the table " +
|
|
446
447
|
"must allow new fields from other scopes (sys_db_object.alter_access) and updateSetSysId " +
|
|
447
448
|
"must belong to scope — both checked on dryRun too, and the stored element + sys_scope are " +
|
|
448
|
-
"read back.
|
|
449
|
+
"read back. A cross-scope result carries designAccess { present, sysId, created }: the " +
|
|
450
|
+
"platform UI refuses cross-scope authoring without a sys_scope_design_access record " +
|
|
451
|
+
"(scope -> the table's scope), so a missing one is FLAGGED (not blocking — the headless " +
|
|
452
|
+
"insert works without it); ensureDesignAccess:true creates it first in the same update " +
|
|
453
|
+
"set. updateSetSysId is required on the live path. dryRun:true returns the plan with " +
|
|
449
454
|
"no writes.",
|
|
450
455
|
shape: schemas_1.addColumnSchema.shape,
|
|
451
456
|
handler: async function (args) {
|
|
@@ -465,12 +470,43 @@ function buildDescriptors(deps = {}) {
|
|
|
465
470
|
column: p.column,
|
|
466
471
|
scope: p.scope,
|
|
467
472
|
crossScope: p.crossScope,
|
|
473
|
+
ensureDesignAccess: p.ensureDesignAccess,
|
|
468
474
|
updateSetSysId: p.updateSetSysId,
|
|
469
475
|
dryRun: p.dryRun,
|
|
470
476
|
debug: p.debug,
|
|
471
477
|
});
|
|
472
478
|
},
|
|
473
479
|
},
|
|
480
|
+
{
|
|
481
|
+
name: "design_access",
|
|
482
|
+
annotations: dovetail_mcp_kit_1.WRITE_CREATE,
|
|
483
|
+
description: "Ensure the sys_scope_design_access record that lets sourceScope (the AUTHORING app) " +
|
|
484
|
+
"design in targetScope's tables (the app that OWNS them). The platform UI requires it " +
|
|
485
|
+
"for cross-scope columns — the symptom is \"Invalid 'Table' selected on the Dictionary " +
|
|
486
|
+
"Entry record ... can only select '<app>' tables with read access enabled\", and the " +
|
|
487
|
+
"table's own access flags are NOT the gate. Scopes are names or sys_scope sys_ids. " +
|
|
488
|
+
"Idempotent: an existing record returns status 'exists' and nothing is written. Live " +
|
|
489
|
+
"creates it via the scope-aware createRecord op switched to sourceScope, captured in " +
|
|
490
|
+
"updateSetSysId (must belong to sourceScope), then reads it back and asserts " +
|
|
491
|
+
"source/target. updateSetSysId is required on the live path; dryRun:true only reports " +
|
|
492
|
+
"exists / missing.",
|
|
493
|
+
shape: schemas_1.designAccessSchema.shape,
|
|
494
|
+
handler: async function (args) {
|
|
495
|
+
var p = schemas_1.designAccessSchema.parse(args);
|
|
496
|
+
if (p.dryRun !== true &&
|
|
497
|
+
(!p.updateSetSysId || !p.updateSetSysId.trim())) {
|
|
498
|
+
throw new Error("design_access: updateSetSysId (an update set in sourceScope) is required on " +
|
|
499
|
+
"the live path — set dryRun:true to only check whether the record exists.");
|
|
500
|
+
}
|
|
501
|
+
return (0, table_1.ensureDesignAccess)({
|
|
502
|
+
client: client(),
|
|
503
|
+
sourceScope: p.sourceScope,
|
|
504
|
+
targetScope: p.targetScope,
|
|
505
|
+
updateSetSysId: p.updateSetSysId,
|
|
506
|
+
dryRun: p.dryRun,
|
|
507
|
+
});
|
|
508
|
+
},
|
|
509
|
+
},
|
|
474
510
|
{
|
|
475
511
|
name: "add_index",
|
|
476
512
|
annotations: dovetail_mcp_kit_1.WRITE_OVERWRITE,
|
|
@@ -539,7 +575,7 @@ function buildDescriptors(deps = {}) {
|
|
|
539
575
|
"admin (an ACL that refuses GET refuses POST), and sys_index_column does not exist at " +
|
|
540
576
|
"all (HTTP 400 'Invalid table') — there is no two-table index model to join. Each row " +
|
|
541
577
|
"returns { name, columns, type, rawColumns }: `columns` is v_db_index's bracketed " +
|
|
542
|
-
"`column_names` cell ('[phone]', '[a
|
|
578
|
+
"`column_names` cell ('[phone]', '[a;b]' — semicolon-separated when composite) PARSED into a list, never substring-matched, " +
|
|
543
579
|
"and `type` is access_method (btree for essentially everything). UNIQUENESS IS NOT " +
|
|
544
580
|
"READABLE: v_db_index carries no uniqueness field, so a unique index and an ordinary " +
|
|
545
581
|
"one are indistinguishable in it — `unique` is therefore left ABSENT rather than " +
|
|
@@ -556,28 +592,38 @@ function buildDescriptors(deps = {}) {
|
|
|
556
592
|
name: "index_create",
|
|
557
593
|
annotations: dovetail_mcp_kit_1.WRITE_ADDITIVE_IDEMPOTENT,
|
|
558
594
|
description: "Create a DATABASE INDEX on an EXISTING ServiceNow table — including the COMPOSITE and " +
|
|
559
|
-
"NON-UNIQUE indexes add_index cannot build.
|
|
560
|
-
"
|
|
561
|
-
"
|
|
562
|
-
"
|
|
563
|
-
"
|
|
564
|
-
"
|
|
565
|
-
"
|
|
566
|
-
"
|
|
567
|
-
"
|
|
568
|
-
"
|
|
569
|
-
"
|
|
570
|
-
"
|
|
571
|
-
"
|
|
572
|
-
"
|
|
573
|
-
"
|
|
574
|
-
"
|
|
575
|
-
"
|
|
576
|
-
"
|
|
577
|
-
"
|
|
578
|
-
"
|
|
579
|
-
"
|
|
580
|
-
"
|
|
595
|
+
"NON-UNIQUE indexes add_index cannot build. AN INDEX IS CAPTURED IN AN UPDATE SET: the " +
|
|
596
|
+
"platform's build job writes a sys_update_xml row (type=Indexes, name " +
|
|
597
|
+
"sys_index_<table>_<col>_…) into the session user's CURRENT update set, so " +
|
|
598
|
+
"updateSetSysId is REQUIRED on the live path — the tool pins that set as current " +
|
|
599
|
+
"(Dovetail changeUpdateSet, read back) BEFORE scheduling, refuses a set outside the " +
|
|
600
|
+
"table's application scope or not 'in progress', and reads the capture row back " +
|
|
601
|
+
"from that set afterwards. The physical index is still built per instance; committing " +
|
|
602
|
+
"the set elsewhere rebuilds it. There is no record path to an index (sys_index is " +
|
|
603
|
+
"API-level-ACL 403, sys_index_column does not exist, and sys_dictionary.unique — the " +
|
|
604
|
+
"add_index lever — is per-column and unique-only), so this replays the two GlideAjax " +
|
|
605
|
+
"calls the platform's own Database Indexes dialog makes on xmlhttp.do over a " +
|
|
606
|
+
"form-login session: IndexCreatorErrorChecker.canCreate (the dialog's pre-flight; a " +
|
|
607
|
+
"canCreate:false verdict is returned verbatim with its errorCode and nothing is " +
|
|
608
|
+
"scheduled) then ScheduleCreator.createSchedule (sysparm_table, sysparm_fields, " +
|
|
609
|
+
"sysparm_access_method — default btree — sysparm_unique true|false, no email, no " +
|
|
610
|
+
"name). DRY-RUN BY DEFAULT: without confirm:true NOTHING is sent and nothing is read; " +
|
|
611
|
+
"dryRun:true forces a dry-run even with confirm. IDEMPOTENT: on the live path " +
|
|
612
|
+
"v_db_index is read first, and an index over exactly these columns returns " +
|
|
613
|
+
"'already-exists' with no write. `columns` is ORDERED — order is part of an " +
|
|
614
|
+
"index's identity and is preserved verbatim. `name` is REFUSED: the dialog has no " +
|
|
615
|
+
"name input (ServiceNow names the index after its leading column), so accepting one " +
|
|
616
|
+
"would mean reporting a name the instance does not carry — the real name comes back " +
|
|
617
|
+
"in `name`. After scheduling, the index is polled for in v_db_index (default 10 " +
|
|
618
|
+
"checks, 3s apart); if it never appears the result is 'failed', because an accepted " +
|
|
619
|
+
"schedule is not evidence an ALTER ran — and a unique index cannot build over " +
|
|
620
|
+
"duplicate values, EMPTY included. verified:true means a matching row was READ BACK; " +
|
|
621
|
+
"captured:true means the sys_update_xml row was READ BACK from the pinned set (an " +
|
|
622
|
+
"index that exists but was not captured is created:true, captured:false with " +
|
|
623
|
+
"'update-set-capture' in unverified). 'uniqueness-enforced' is ALWAYS in " +
|
|
624
|
+
"unverified: v_db_index has no uniqueness field. Requires a username+password identity " +
|
|
625
|
+
"that can form-log-in; xmlhttp.do ignores Basic auth and API keys, so an API-key-only " +
|
|
626
|
+
"or SSO/MFA identity fails at the session with a diagnosis (TenonHQ/Dovetail#292).",
|
|
581
627
|
shape: schemas_1.createIndexSchema.shape,
|
|
582
628
|
handler: async function (args) {
|
|
583
629
|
var p = schemas_1.createIndexSchema.parse(args);
|
|
@@ -585,6 +631,7 @@ function buildDescriptors(deps = {}) {
|
|
|
585
631
|
client: client(),
|
|
586
632
|
table: p.table,
|
|
587
633
|
columns: p.columns,
|
|
634
|
+
updateSetSysId: p.updateSetSysId,
|
|
588
635
|
unique: p.unique,
|
|
589
636
|
name: p.name,
|
|
590
637
|
accessMethod: p.accessMethod,
|