@tenonhq/dovetail-servicenow 0.0.50 → 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 +53 -29
- package/dist/cli.js +27 -12
- package/dist/cliUsage.js +8 -6
- package/dist/index.d.ts +1 -1
- package/dist/index.js +11 -4
- package/dist/mcp/registry.js +34 -23
- package/dist/mcp/schemas.d.ts +3 -0
- package/dist/mcp/schemas.js +4 -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/index.d.ts +1 -1
- package/dist/table/index.js +10 -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
|
|
|
@@ -1167,9 +1191,9 @@ read back from the `v_db_index` view - uniqueness enforcement is always reported
|
|
|
1167
1191
|
unverified) / `index_list` (read-only: a table's database indexes from `v_db_index`,
|
|
1168
1192
|
the only index read surface - `sys_index` is API-level-ACL 403 and `sys_index_column`
|
|
1169
1193
|
does not exist) / `index_create` (create an index, composite and non-unique included, by
|
|
1170
|
-
replaying the
|
|
1171
|
-
|
|
1172
|
-
|
|
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
|
|
1173
1197
|
existing record), `create_record` (insert one record) and `delete_record` (delete one
|
|
1174
1198
|
record — dry-run by default, `confirm:true` to apply, `updateSetSysId` required, the
|
|
1175
1199
|
record read back before AND after so success is only reported once it is confirmed
|
package/dist/cli.js
CHANGED
|
@@ -1588,16 +1588,19 @@ async function runIndexList(flags) {
|
|
|
1588
1588
|
}
|
|
1589
1589
|
/**
|
|
1590
1590
|
* dove-sn index-create:
|
|
1591
|
-
* --table x_cadso_core_u_smoke --columns a,b
|
|
1592
|
-
* [--confirm] [--dry-run] [--poll-attempts <n>]
|
|
1593
|
-
* [--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]
|
|
1594
1594
|
*
|
|
1595
1595
|
* DRY-RUN BY DEFAULT — nothing is sent (and nothing is even READ) without --confirm;
|
|
1596
|
-
* --dry-run forces a plan even with it.
|
|
1597
|
-
*
|
|
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.
|
|
1598
1600
|
*
|
|
1599
1601
|
* Exit codes: 0 created / already-exists / dry-run, 1 bad args, 2 failed (which
|
|
1600
|
-
* 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.
|
|
1601
1604
|
*/
|
|
1602
1605
|
async function runIndexCreate(flags) {
|
|
1603
1606
|
var columns = splitList(flags.columns || "");
|
|
@@ -1605,10 +1608,12 @@ async function runIndexCreate(flags) {
|
|
|
1605
1608
|
process.stderr.write("index-create: --table <name> and --columns <a[,b,...]> are required\n");
|
|
1606
1609
|
return 1;
|
|
1607
1610
|
}
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
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");
|
|
1612
1617
|
return 1;
|
|
1613
1618
|
}
|
|
1614
1619
|
var params = {
|
|
@@ -1623,8 +1628,8 @@ async function runIndexCreate(flags) {
|
|
|
1623
1628
|
params.name = flags.name;
|
|
1624
1629
|
if (flags["access-method"])
|
|
1625
1630
|
params.accessMethod = flags["access-method"];
|
|
1626
|
-
if (flags["
|
|
1627
|
-
params.
|
|
1631
|
+
if (flags["update-set"])
|
|
1632
|
+
params.updateSetSysId = flags["update-set"];
|
|
1628
1633
|
if (flags.debug === "true")
|
|
1629
1634
|
params.debug = true;
|
|
1630
1635
|
// A non-integer poll setting would make the bounded wait unbounded (or zero).
|
|
@@ -1663,6 +1668,12 @@ async function runIndexCreate(flags) {
|
|
|
1663
1668
|
(result.name ? " -> " + result.name : "") +
|
|
1664
1669
|
(result.instance ? " on " + result.instance : "") +
|
|
1665
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
|
+
: "") +
|
|
1666
1677
|
"\n" +
|
|
1667
1678
|
result.note +
|
|
1668
1679
|
"\nUNVERIFIED: " +
|
|
@@ -1671,6 +1682,10 @@ async function runIndexCreate(flags) {
|
|
|
1671
1682
|
}
|
|
1672
1683
|
if (result.status === "failed")
|
|
1673
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;
|
|
1674
1689
|
return 0;
|
|
1675
1690
|
}
|
|
1676
1691
|
/** Parse a CLI boolean flag. Bare `--mandatory` means true; `--mandatory false` means
|
package/dist/cliUsage.js
CHANGED
|
@@ -332,14 +332,15 @@ exports.VERB_USAGE = {
|
|
|
332
332
|
],
|
|
333
333
|
},
|
|
334
334
|
"index-create": {
|
|
335
|
-
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",
|
|
336
336
|
required: [
|
|
337
337
|
{ flag: "table", value: "<name>" },
|
|
338
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." },
|
|
339
340
|
],
|
|
340
341
|
optional: [
|
|
341
342
|
{ flag: "unique" },
|
|
342
|
-
{ 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)." },
|
|
343
344
|
CONFIRM_FLAG,
|
|
344
345
|
FORCE_DRY_RUN_FLAG,
|
|
345
346
|
{ flag: "poll-attempts", value: "<n>", note: "Positive integer." },
|
|
@@ -349,11 +350,12 @@ exports.VERB_USAGE = {
|
|
|
349
350
|
],
|
|
350
351
|
gate: "confirm",
|
|
351
352
|
gateNote: "Nothing is sent OR read without --confirm.",
|
|
352
|
-
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",
|
|
353
354
|
notes: [
|
|
354
|
-
"
|
|
355
|
-
"Idempotent: an index over exactly those columns returns already-exists with no write. --name is refused (the
|
|
356
|
-
"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.",
|
|
357
359
|
],
|
|
358
360
|
},
|
|
359
361
|
"set-field": {
|
package/dist/index.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ 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, ensureDesignAccess, findDesignAccess, DESIGN_ACCESS_TABLE, addIndex, parseIndexColumns, indexMatchesColumns, listIndexes, createIndex, validateCreateIndex,
|
|
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
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";
|
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; } });
|
|
@@ -100,8 +101,14 @@ Object.defineProperty(exports, "indexMatchesColumns", { enumerable: true, get: f
|
|
|
100
101
|
Object.defineProperty(exports, "listIndexes", { enumerable: true, get: function () { return table_1.listIndexes; } });
|
|
101
102
|
Object.defineProperty(exports, "createIndex", { enumerable: true, get: function () { return table_1.createIndex; } });
|
|
102
103
|
Object.defineProperty(exports, "validateCreateIndex", { enumerable: true, get: function () { return table_1.validateCreateIndex; } });
|
|
103
|
-
Object.defineProperty(exports, "
|
|
104
|
-
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; } });
|
|
105
112
|
Object.defineProperty(exports, "setColumn", { enumerable: true, get: function () { return table_1.setColumn; } });
|
|
106
113
|
Object.defineProperty(exports, "resolveAttributes", { enumerable: true, get: function () { return table_1.resolveAttributes; } });
|
|
107
114
|
Object.defineProperty(exports, "toStoredValue", { enumerable: true, get: function () { return table_1.toStoredValue; } });
|
package/dist/mcp/registry.js
CHANGED
|
@@ -575,7 +575,7 @@ function buildDescriptors(deps = {}) {
|
|
|
575
575
|
"admin (an ACL that refuses GET refuses POST), and sys_index_column does not exist at " +
|
|
576
576
|
"all (HTTP 400 'Invalid table') — there is no two-table index model to join. Each row " +
|
|
577
577
|
"returns { name, columns, type, rawColumns }: `columns` is v_db_index's bracketed " +
|
|
578
|
-
"`column_names` cell ('[phone]', '[a
|
|
578
|
+
"`column_names` cell ('[phone]', '[a;b]' — semicolon-separated when composite) PARSED into a list, never substring-matched, " +
|
|
579
579
|
"and `type` is access_method (btree for essentially everything). UNIQUENESS IS NOT " +
|
|
580
580
|
"READABLE: v_db_index carries no uniqueness field, so a unique index and an ordinary " +
|
|
581
581
|
"one are indistinguishable in it — `unique` is therefore left ABSENT rather than " +
|
|
@@ -592,28 +592,38 @@ function buildDescriptors(deps = {}) {
|
|
|
592
592
|
name: "index_create",
|
|
593
593
|
annotations: dovetail_mcp_kit_1.WRITE_ADDITIVE_IDEMPOTENT,
|
|
594
594
|
description: "Create a DATABASE INDEX on an EXISTING ServiceNow table — including the COMPOSITE and " +
|
|
595
|
-
"NON-UNIQUE indexes add_index cannot build.
|
|
596
|
-
"
|
|
597
|
-
"
|
|
598
|
-
"
|
|
599
|
-
"
|
|
600
|
-
"
|
|
601
|
-
"
|
|
602
|
-
"
|
|
603
|
-
"
|
|
604
|
-
"
|
|
605
|
-
"
|
|
606
|
-
"
|
|
607
|
-
"
|
|
608
|
-
"
|
|
609
|
-
"
|
|
610
|
-
"
|
|
611
|
-
"
|
|
612
|
-
"
|
|
613
|
-
"
|
|
614
|
-
"
|
|
615
|
-
"
|
|
616
|
-
"
|
|
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).",
|
|
617
627
|
shape: schemas_1.createIndexSchema.shape,
|
|
618
628
|
handler: async function (args) {
|
|
619
629
|
var p = schemas_1.createIndexSchema.parse(args);
|
|
@@ -621,6 +631,7 @@ function buildDescriptors(deps = {}) {
|
|
|
621
631
|
client: client(),
|
|
622
632
|
table: p.table,
|
|
623
633
|
columns: p.columns,
|
|
634
|
+
updateSetSysId: p.updateSetSysId,
|
|
624
635
|
unique: p.unique,
|
|
625
636
|
name: p.name,
|
|
626
637
|
accessMethod: p.accessMethod,
|
package/dist/mcp/schemas.d.ts
CHANGED
|
@@ -1983,6 +1983,7 @@ export declare var listIndexesSchema: z.ZodObject<{
|
|
|
1983
1983
|
export declare var createIndexSchema: z.ZodObject<{
|
|
1984
1984
|
table: z.ZodString;
|
|
1985
1985
|
columns: z.ZodArray<z.ZodString, "many">;
|
|
1986
|
+
updateSetSysId: z.ZodOptional<z.ZodString>;
|
|
1986
1987
|
unique: z.ZodOptional<z.ZodBoolean>;
|
|
1987
1988
|
name: z.ZodOptional<z.ZodString>;
|
|
1988
1989
|
accessMethod: z.ZodOptional<z.ZodString>;
|
|
@@ -1998,6 +1999,7 @@ export declare var createIndexSchema: z.ZodObject<{
|
|
|
1998
1999
|
confirm?: boolean | undefined;
|
|
1999
2000
|
dryRun?: boolean | undefined;
|
|
2000
2001
|
debug?: boolean | undefined;
|
|
2002
|
+
updateSetSysId?: string | undefined;
|
|
2001
2003
|
unique?: boolean | undefined;
|
|
2002
2004
|
accessMethod?: string | undefined;
|
|
2003
2005
|
pollAttempts?: number | undefined;
|
|
@@ -2009,6 +2011,7 @@ export declare var createIndexSchema: z.ZodObject<{
|
|
|
2009
2011
|
confirm?: boolean | undefined;
|
|
2010
2012
|
dryRun?: boolean | undefined;
|
|
2011
2013
|
debug?: boolean | undefined;
|
|
2014
|
+
updateSetSysId?: string | undefined;
|
|
2012
2015
|
unique?: boolean | undefined;
|
|
2013
2016
|
accessMethod?: string | undefined;
|
|
2014
2017
|
pollAttempts?: number | undefined;
|
package/dist/mcp/schemas.js
CHANGED
|
@@ -413,11 +413,13 @@ exports.listIndexesSchema = zod_1.z.object({
|
|
|
413
413
|
// earns the explanation that the platform's index-creator form has no name input (the
|
|
414
414
|
// same contract set-column uses for internalType/element); createIndex refuses it.
|
|
415
415
|
// confirm is the write gate: without confirm:true the tool is a pure dry-run that sends
|
|
416
|
-
// nothing and reads nothing.
|
|
417
|
-
//
|
|
416
|
+
// nothing and reads nothing. updateSetSysId is optional here because a dry-run needs none;
|
|
417
|
+
// the live path requires it (createIndex refuses without one) because the build job
|
|
418
|
+
// captures the index definition into the user's CURRENT update set.
|
|
418
419
|
exports.createIndexSchema = zod_1.z.object({
|
|
419
420
|
table: zod_1.z.string().min(1),
|
|
420
421
|
columns: zod_1.z.array(zod_1.z.string().min(1)).min(1),
|
|
422
|
+
updateSetSysId: zod_1.z.string().min(1).optional(),
|
|
421
423
|
unique: zod_1.z.boolean().optional(),
|
|
422
424
|
name: zod_1.z.string().optional(),
|
|
423
425
|
accessMethod: zod_1.z.string().min(1).optional(),
|
package/dist/table/addIndex.d.ts
CHANGED
|
@@ -119,7 +119,13 @@ export interface DuplicateScan {
|
|
|
119
119
|
* `client.table.query` exposes no sysparm_offset.
|
|
120
120
|
*/
|
|
121
121
|
export declare function scanForDuplicates(client: ServiceNowClient, table: string, column: string): Promise<DuplicateScan>;
|
|
122
|
-
/**
|
|
122
|
+
/**
|
|
123
|
+
* Parse a v_db_index `column_names` cell into its column list. The live view
|
|
124
|
+
* separates a composite index's columns with a SEMICOLON — "[a;b;c]" (read live
|
|
125
|
+
* from tenonworkstudio 2026-10-08: "[sys_created_on;status;version_step;version]").
|
|
126
|
+
* A comma is tolerated too, so an older fixture or a differently-rendered cell
|
|
127
|
+
* still parses; a cell that is one bare name ("[a]") is a one-column list.
|
|
128
|
+
*/
|
|
123
129
|
export declare function parseIndexColumns(raw: string): Array<string>;
|
|
124
130
|
/**
|
|
125
131
|
* Does this row's column list match the requested one EXACTLY? Parsed, never
|
package/dist/table/addIndex.js
CHANGED
|
@@ -224,7 +224,13 @@ function describeDuplicates(scan) {
|
|
|
224
224
|
shown.join(", ") +
|
|
225
225
|
(scan.duplicates.length > shown.length ? ", ..." : ""));
|
|
226
226
|
}
|
|
227
|
-
/**
|
|
227
|
+
/**
|
|
228
|
+
* Parse a v_db_index `column_names` cell into its column list. The live view
|
|
229
|
+
* separates a composite index's columns with a SEMICOLON — "[a;b;c]" (read live
|
|
230
|
+
* from tenonworkstudio 2026-10-08: "[sys_created_on;status;version_step;version]").
|
|
231
|
+
* A comma is tolerated too, so an older fixture or a differently-rendered cell
|
|
232
|
+
* still parses; a cell that is one bare name ("[a]") is a one-column list.
|
|
233
|
+
*/
|
|
228
234
|
function parseIndexColumns(raw) {
|
|
229
235
|
var text = String(raw === undefined || raw === null ? "" : raw).trim();
|
|
230
236
|
if (text.length > 1 &&
|
|
@@ -235,7 +241,7 @@ function parseIndexColumns(raw) {
|
|
|
235
241
|
if (!text)
|
|
236
242
|
return [];
|
|
237
243
|
return text
|
|
238
|
-
.split(
|
|
244
|
+
.split(/[;,]/)
|
|
239
245
|
.map(function (part) {
|
|
240
246
|
return part.trim();
|
|
241
247
|
})
|