@sonisoft/now-sdk-ext-core 5.3.0 → 6.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +86 -3
  3. package/dist/comm/http/HTTPRequest.d.ts +12 -0
  4. package/dist/comm/http/RequestHandler.d.ts +13 -0
  5. package/dist/comm/http/RequestHandler.js +47 -0
  6. package/dist/comm/http/RequestHandler.js.map +1 -1
  7. package/dist/comm/http/ServiceNowProcessorRequest.d.ts +9 -2
  8. package/dist/comm/http/ServiceNowProcessorRequest.js +17 -5
  9. package/dist/comm/http/ServiceNowProcessorRequest.js.map +1 -1
  10. package/dist/index.d.ts +6 -0
  11. package/dist/index.js +6 -0
  12. package/dist/index.js.map +1 -1
  13. package/dist/policy/Policy.d.ts +40 -0
  14. package/dist/policy/Policy.js +161 -0
  15. package/dist/policy/Policy.js.map +1 -0
  16. package/dist/policy/PolicyEnv.d.ts +46 -0
  17. package/dist/policy/PolicyEnv.js +92 -0
  18. package/dist/policy/PolicyEnv.js.map +1 -0
  19. package/dist/policy/PolicyRefusal.d.ts +17 -0
  20. package/dist/policy/PolicyRefusal.js +69 -0
  21. package/dist/policy/PolicyRefusal.js.map +1 -0
  22. package/dist/policy/PolicyTypes.d.ts +65 -0
  23. package/dist/policy/PolicyTypes.js +15 -0
  24. package/dist/policy/PolicyTypes.js.map +1 -0
  25. package/dist/policy/ScanScript.d.ts +22 -0
  26. package/dist/policy/ScanScript.js +171 -0
  27. package/dist/policy/ScanScript.js.map +1 -0
  28. package/dist/policy/internal/Classify.d.ts +16 -0
  29. package/dist/policy/internal/Classify.js +121 -0
  30. package/dist/policy/internal/Classify.js.map +1 -0
  31. package/dist/sn/Application.js +18 -8
  32. package/dist/sn/Application.js.map +1 -1
  33. package/dist/sn/BackgroundScriptExecutor.d.ts +12 -0
  34. package/dist/sn/BackgroundScriptExecutor.js +60 -1
  35. package/dist/sn/BackgroundScriptExecutor.js.map +1 -1
  36. package/dist/sn/amb/AMBClient.js +24 -11
  37. package/dist/sn/amb/AMBClient.js.map +1 -1
  38. package/dist/sn/amb/SubscriptionCommandSender.js +19 -2
  39. package/dist/sn/amb/SubscriptionCommandSender.js.map +1 -1
  40. package/dist/sn/amb/XMLHttpRequest.d.ts +1 -0
  41. package/dist/sn/amb/XMLHttpRequest.js +5 -1
  42. package/dist/sn/amb/XMLHttpRequest.js.map +1 -1
  43. package/dist/sn/atf/ATFTestExecutor.js +3 -1
  44. package/dist/sn/atf/ATFTestExecutor.js.map +1 -1
  45. package/dist/sn/scripttracer/ScriptTracer.js +6 -2
  46. package/dist/sn/scripttracer/ScriptTracer.js.map +1 -1
  47. package/dist/sn/syslog/SyslogReader.js +6 -1
  48. package/dist/sn/syslog/SyslogReader.js.map +1 -1
  49. package/dist/util/LogConfig.d.ts +83 -0
  50. package/dist/util/LogConfig.js +387 -0
  51. package/dist/util/LogConfig.js.map +1 -0
  52. package/dist/util/Logger.d.ts +40 -18
  53. package/dist/util/Logger.js +78 -84
  54. package/dist/util/Logger.js.map +1 -1
  55. package/dist/util/redact.d.ts +12 -0
  56. package/dist/util/redact.js +76 -0
  57. package/dist/util/redact.js.map +1 -1
  58. package/package.json +10 -9
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Process-wide permission policy.
3
+ *
4
+ * INERT UNTIL CONFIGURED. With no policy installed every check allows, so importing
5
+ * this library changes nothing for existing consumers — `new TableAPIRequest(i).post()`
6
+ * behaves exactly as it did. The applications that are actually driven by agents (the
7
+ * `nex` CLI and the MCP server) install a policy in their entrypoints.
8
+ *
9
+ * That split is deliberate. The risk being managed is "an agent drives the CLI or the
10
+ * MCP server", not "a program imports this library"; gating here by default would break
11
+ * every embedder to mitigate a risk they do not have.
12
+ *
13
+ * Those applications currently install a ladder whose LAST layer grants both verbs, so
14
+ * changes are permitted unless something above revokes them — `NEX_POLICY_DENY`, or a
15
+ * CLI deny flag. Flipping to deny-by-default is deleting that trailing layer; nothing
16
+ * in this file assumes either direction.
17
+ */
18
+ /**
19
+ * Layers in priority order. The FIRST layer to answer anything other than `abstain`
20
+ * decides.
21
+ *
22
+ * Ordered layers rather than an escalating if-chain because not every source is a
23
+ * grant: `--read-only` (NEX-76) REVOKES, and an escalate-only model cannot express a
24
+ * source that lowers permission. Getting this shape right now is what lets NEX-76 add
25
+ * `ApprovalToken` and `--approve-all` later without a rewrite.
26
+ *
27
+ * Index 0 is the only layer an agent cannot reach — see `denyFromEnvironment`.
28
+ */
29
+ const DEFAULT_LAYER = "default";
30
+ let layers = [];
31
+ let installed = false;
32
+ /** True once an application has installed a policy. Until then, everything is allowed. */
33
+ export function isPolicyInstalled() {
34
+ return installed;
35
+ }
36
+ /**
37
+ * Installs the process policy. Call once, from the application that owns the entry
38
+ * point — never from a library.
39
+ *
40
+ * Layers are supplied in priority order (highest first).
41
+ */
42
+ export function installPolicy(orderedLayers) {
43
+ layers = [...orderedLayers];
44
+ installed = true;
45
+ }
46
+ /** Removes the policy, returning to the permissive default. Test seam. */
47
+ export function resetPolicyForTests() {
48
+ layers = [];
49
+ installed = false;
50
+ }
51
+ /** The layers currently installed, highest priority first. Diagnostics and `nex policy status`. */
52
+ export function currentLayers() {
53
+ return layers;
54
+ }
55
+ /**
56
+ * Resolves a single verb against the ladder.
57
+ *
58
+ * Returns the deciding layer's name alongside the answer so refusals can say which one
59
+ * spoke. Without that, a precedence bug is invisible: the user sees "refused" and has
60
+ * no way to tell whether the environment denied it or nothing granted it.
61
+ */
62
+ function resolveVerb(verb) {
63
+ for (const layer of layers) {
64
+ const answer = layer.answer(verb);
65
+ if (answer === "grant") {
66
+ return { granted: true, decidingLayer: layer.name };
67
+ }
68
+ if (answer === "deny") {
69
+ return { granted: false, decidingLayer: layer.name };
70
+ }
71
+ }
72
+ return { granted: false, decidingLayer: DEFAULT_LAYER };
73
+ }
74
+ /**
75
+ * Decides whether an operation may proceed.
76
+ *
77
+ * Only `target: "instance"` is gated. A local file write or a session-preference change
78
+ * is not what this exists to prevent, and refusing them would produce false refusals on
79
+ * operations that never touch instance data (`script-sync pull` being the clearest).
80
+ */
81
+ export function checkRequirement(requirement) {
82
+ if (!installed || requirement.verbs.length === 0 || requirement.target !== "instance") {
83
+ return {
84
+ allowed: true,
85
+ verbs: requirement.verbs,
86
+ decidingLayer: installed ? "not-gated" : "no-policy",
87
+ };
88
+ }
89
+ // Every required verb must be granted. A script needing execute AND write is not
90
+ // satisfied by execute alone.
91
+ for (const verb of requirement.verbs) {
92
+ const { granted, decidingLayer } = resolveVerb(verb);
93
+ if (!granted) {
94
+ return {
95
+ allowed: false,
96
+ verbs: requirement.verbs,
97
+ decidingLayer,
98
+ remediation: remediationFor(requirement.verbs, verb, decidingLayer),
99
+ };
100
+ }
101
+ }
102
+ // Report EVERY layer that granted, not just the first verb's.
103
+ //
104
+ // A requirement like ["execute", "write"] can be satisfied by two different layers
105
+ // — execute from the environment, write from the default — and naming only the
106
+ // first makes the audit log quietly wrong about how a mutation was permitted. That
107
+ // log is the thing meant to surface a missing floor rule, so it has to be accurate.
108
+ const granting = [...new Set(requirement.verbs.map((v) => resolveVerb(v).decidingLayer))];
109
+ return {
110
+ allowed: true,
111
+ verbs: requirement.verbs,
112
+ decidingLayer: granting.join(" + "),
113
+ };
114
+ }
115
+ /**
116
+ * How the application explains a refusal.
117
+ *
118
+ * Set by the CLI to name its flags. Left unset by the MCP server, whose caller is the
119
+ * model — naming the parameter there would advertise the escape hatch.
120
+ */
121
+ let remediationWriter;
122
+ export function setRemediationWriter(writer) {
123
+ remediationWriter = writer;
124
+ }
125
+ function remediationFor(verbs, missing, layer) {
126
+ if (remediationWriter) {
127
+ return remediationWriter(verbs, missing, layer);
128
+ }
129
+ return `This operation requires ${missing} permission on the instance, which has not been granted.`;
130
+ }
131
+ /**
132
+ * A layer that denies the listed verbs outright.
133
+ *
134
+ * Placed at the top of the ladder so nothing below can grant past it. This is the layer
135
+ * that makes pointing the tool at production meaningful.
136
+ *
137
+ * The guarantee has a precise limit, and the docs must state it: this only holds when
138
+ * the variable is set somewhere the agent cannot write — a shell profile, a systemd
139
+ * unit, a container environment. A project-local `.mcp.json` is a file in the workspace,
140
+ * and an agent with file-write access can edit it.
141
+ */
142
+ export function denyLayer(name, denied) {
143
+ const set = new Set(denied);
144
+ return {
145
+ name,
146
+ answer: (verb) => (set.has(verb) ? "deny" : "abstain"),
147
+ };
148
+ }
149
+ /** A layer that grants the listed verbs and abstains on the rest. */
150
+ export function grantLayer(name, granted) {
151
+ const set = new Set(granted);
152
+ return {
153
+ name,
154
+ answer: (verb) => (set.has(verb) ? "grant" : "abstain"),
155
+ };
156
+ }
157
+ /** Convenience for tests and for embedders that want the old unrestricted behaviour. */
158
+ export function allowAllLayer() {
159
+ return grantLayer("allow-all", ["write", "execute"]);
160
+ }
161
+ //# sourceMappingURL=Policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Policy.js","sourceRoot":"","sources":["../../src/policy/Policy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ import { PolicyLayer, Verb } from "./PolicyTypes.js";
2
+ /**
3
+ * Environment-variable policy layers.
4
+ *
5
+ * `NEX_POLICY_DENY` sits at the top of the ladder and cannot be granted past.
6
+ * `NEX_POLICY_ALLOW` sits near the bottom and exists for backward compatibility, so an
7
+ * existing CI job can restore its previous behaviour with one variable rather than
8
+ * editing every command line.
9
+ *
10
+ * Named `NEX_POLICY_*` rather than `NEX_ALLOW_WRITE` because `write` and `execute`
11
+ * collide with ServiceNow ACL vocabulary, and a reader should not have to work out
12
+ * whether this is about ACLs.
13
+ */
14
+ export declare const DENY_ENV = "NEX_POLICY_DENY";
15
+ export declare const ALLOW_ENV = "NEX_POLICY_ALLOW";
16
+ export interface ParsedVerbs {
17
+ readonly verbs: readonly Verb[];
18
+ /** Tokens that were not recognised. Non-empty means the value was malformed. */
19
+ readonly unknown: readonly string[];
20
+ }
21
+ /**
22
+ * Parses a comma-separated verb list.
23
+ *
24
+ * `all` expands to every verb. Unrecognised tokens are REPORTED rather than dropped, so
25
+ * the caller can fail closed — see `denyFromEnvironment`.
26
+ */
27
+ export declare function parseVerbList(raw: string | undefined): ParsedVerbs;
28
+ /**
29
+ * The deny layer, built from `NEX_POLICY_DENY`.
30
+ *
31
+ * FAILS CLOSED on a malformed value. `NEX_POLICY_DENY=wrtie` denies everything and
32
+ * warns, rather than silently denying nothing — a typo in the variable that is supposed
33
+ * to protect production must not quietly disable the protection. That is the whole
34
+ * reason this function reports `unknown` instead of filtering it away.
35
+ *
36
+ * Returns `undefined` when the variable is unset, so the layer is simply absent.
37
+ */
38
+ export declare function denyFromEnvironment(env?: NodeJS.ProcessEnv, warn?: (message: string) => void): PolicyLayer | undefined;
39
+ /**
40
+ * The backward-compatibility grant layer, built from `NEX_POLICY_ALLOW`.
41
+ *
42
+ * Unrecognised tokens here are dropped with a warning rather than failing closed: this
43
+ * variable only ever *grants*, so a typo already fails safe — the operation is refused
44
+ * and the operator sees why.
45
+ */
46
+ export declare function allowFromEnvironment(env?: NodeJS.ProcessEnv, warn?: (message: string) => void): PolicyLayer | undefined;
@@ -0,0 +1,92 @@
1
+ import { denyLayer, grantLayer } from "./Policy.js";
2
+ /**
3
+ * Environment-variable policy layers.
4
+ *
5
+ * `NEX_POLICY_DENY` sits at the top of the ladder and cannot be granted past.
6
+ * `NEX_POLICY_ALLOW` sits near the bottom and exists for backward compatibility, so an
7
+ * existing CI job can restore its previous behaviour with one variable rather than
8
+ * editing every command line.
9
+ *
10
+ * Named `NEX_POLICY_*` rather than `NEX_ALLOW_WRITE` because `write` and `execute`
11
+ * collide with ServiceNow ACL vocabulary, and a reader should not have to work out
12
+ * whether this is about ACLs.
13
+ */
14
+ export const DENY_ENV = "NEX_POLICY_DENY";
15
+ export const ALLOW_ENV = "NEX_POLICY_ALLOW";
16
+ const ALL = "all";
17
+ const VALID = ["write", "execute"];
18
+ /**
19
+ * Parses a comma-separated verb list.
20
+ *
21
+ * `all` expands to every verb. Unrecognised tokens are REPORTED rather than dropped, so
22
+ * the caller can fail closed — see `denyFromEnvironment`.
23
+ */
24
+ export function parseVerbList(raw) {
25
+ if (raw === undefined) {
26
+ return { verbs: [], unknown: [] };
27
+ }
28
+ const tokens = raw
29
+ .split(",")
30
+ .map((t) => t.trim().toLowerCase())
31
+ .filter((t) => t.length > 0);
32
+ if (tokens.includes(ALL)) {
33
+ return { verbs: [...VALID], unknown: [] };
34
+ }
35
+ const verbs = [];
36
+ const unknown = [];
37
+ for (const token of tokens) {
38
+ if (VALID.includes(token)) {
39
+ verbs.push(token);
40
+ }
41
+ else {
42
+ unknown.push(token);
43
+ }
44
+ }
45
+ return { verbs, unknown };
46
+ }
47
+ /**
48
+ * The deny layer, built from `NEX_POLICY_DENY`.
49
+ *
50
+ * FAILS CLOSED on a malformed value. `NEX_POLICY_DENY=wrtie` denies everything and
51
+ * warns, rather than silently denying nothing — a typo in the variable that is supposed
52
+ * to protect production must not quietly disable the protection. That is the whole
53
+ * reason this function reports `unknown` instead of filtering it away.
54
+ *
55
+ * Returns `undefined` when the variable is unset, so the layer is simply absent.
56
+ */
57
+ export function denyFromEnvironment(env = process.env, warn = () => undefined) {
58
+ const raw = env[DENY_ENV];
59
+ if (raw === undefined || raw.trim().length === 0) {
60
+ return undefined;
61
+ }
62
+ const { verbs, unknown } = parseVerbList(raw);
63
+ if (unknown.length > 0) {
64
+ warn(`${DENY_ENV} contains unrecognised value(s): ${unknown.join(", ")}. ` +
65
+ `Denying all changes. Valid values are: ${VALID.join(", ")}, all.`);
66
+ return denyLayer(`${DENY_ENV} (malformed, failing closed)`, VALID);
67
+ }
68
+ return denyLayer(DENY_ENV, verbs);
69
+ }
70
+ /**
71
+ * The backward-compatibility grant layer, built from `NEX_POLICY_ALLOW`.
72
+ *
73
+ * Unrecognised tokens here are dropped with a warning rather than failing closed: this
74
+ * variable only ever *grants*, so a typo already fails safe — the operation is refused
75
+ * and the operator sees why.
76
+ */
77
+ export function allowFromEnvironment(env = process.env, warn = () => undefined) {
78
+ const raw = env[ALLOW_ENV];
79
+ if (raw === undefined || raw.trim().length === 0) {
80
+ return undefined;
81
+ }
82
+ const { verbs, unknown } = parseVerbList(raw);
83
+ if (unknown.length > 0) {
84
+ warn(`${ALLOW_ENV} contains unrecognised value(s): ${unknown.join(", ")}, ignored. ` +
85
+ `Valid values are: ${VALID.join(", ")}, all.`);
86
+ }
87
+ if (verbs.length === 0) {
88
+ return undefined;
89
+ }
90
+ return grantLayer(ALLOW_ENV, verbs);
91
+ }
92
+ //# sourceMappingURL=PolicyEnv.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PolicyEnv.js","sourceRoot":"","sources":["../../src/policy/PolicyEnv.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import { Decision, Verb } from "./PolicyTypes.js";
2
+ export interface PolicyRefusalError extends Error {
3
+ readonly decision: Decision;
4
+ }
5
+ /** True when `value` is a refusal thrown by ANY copy of this library. */
6
+ export declare function isPolicyRefusal(value: unknown): value is PolicyRefusalError;
7
+ /**
8
+ * Builds the error thrown when an operation is refused.
9
+ *
10
+ * The message states what was needed and what to do about it. It deliberately does NOT
11
+ * name an MCP tool parameter: on that surface the caller is the model, and a refusal
12
+ * that advertises its own escape hatch just teaches the model to set it and retry.
13
+ * The CLI remediation names its flags, because there a human typed the command.
14
+ */
15
+ export declare function policyRefusal(decision: Decision): PolicyRefusalError;
16
+ /** Convenience for the common single-verb refusal. */
17
+ export declare function refusalFor(verb: Verb, decidingLayer: string, remediation?: string): PolicyRefusalError;
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Marks an error as a policy refusal.
3
+ *
4
+ * `Symbol.for`, not a class and not `instanceof`. This package is published, and a
5
+ * consumer can easily end up with two copies in `node_modules` — a transitive
6
+ * dependency on an older major, a pruned install, a bundler that duplicated it. With a
7
+ * class, `instanceof` then returns false for an error thrown by the *other* copy, and
8
+ * the failure is silent: `BackgroundScriptExecutor.executeScriptAuto` would stop
9
+ * recognising a refusal and fall back to `sys_trigger`, executing the very script that
10
+ * was just refused.
11
+ *
12
+ * The registry symbol is global to the process, so it identifies correctly across
13
+ * copies. This repo already carries the dual-copy exposure with `StaleInstanceError`
14
+ * (see `RequestHandler.toThrowable`, which goes out of its way to preserve identity);
15
+ * this is that lesson applied rather than repeated.
16
+ */
17
+ const POLICY_REFUSAL = Symbol.for("now-sdk-ext.policy-refusal");
18
+ /** True when `value` is a refusal thrown by ANY copy of this library. */
19
+ export function isPolicyRefusal(value) {
20
+ if (!value || typeof value !== "object") {
21
+ return false;
22
+ }
23
+ return value[POLICY_REFUSAL] === true;
24
+ }
25
+ /**
26
+ * Builds the error thrown when an operation is refused.
27
+ *
28
+ * The message states what was needed and what to do about it. It deliberately does NOT
29
+ * name an MCP tool parameter: on that surface the caller is the model, and a refusal
30
+ * that advertises its own escape hatch just teaches the model to set it and retry.
31
+ * The CLI remediation names its flags, because there a human typed the command.
32
+ */
33
+ export function policyRefusal(decision) {
34
+ const verbs = decision.verbs.join(" and ");
35
+ const detail = decision.remediation
36
+ ? `${decision.remediation}`
37
+ : `This operation requires ${verbs} permission, which has not been granted.`;
38
+ const error = new Error(detail);
39
+ error.name = "PolicyRefusalError";
40
+ error[POLICY_REFUSAL] = true;
41
+ // Non-enumerable AND writable, both load-bearing.
42
+ //
43
+ // `stripSecretsFromError` walks `Object.keys(err)` and reassigns every object-valued
44
+ // child through the redactor. An enumerable, non-writable `decision` made that throw
45
+ // `Cannot assign to read only property 'decision'` — which replaced the refusal with
46
+ // a TypeError on the way out, so callers saw a crash instead of a refusal and
47
+ // `isPolicyRefusal` returned false. Found by the live integration run, not by any
48
+ // unit test, because nothing in isolation puts a refusal through the redactor.
49
+ //
50
+ // Non-enumerable also keeps it out of JSON.stringify and out of log serialisation,
51
+ // which is where it would otherwise show up as noise.
52
+ Object.defineProperty(error, "decision", {
53
+ value: decision,
54
+ enumerable: false,
55
+ writable: true,
56
+ configurable: true,
57
+ });
58
+ return error;
59
+ }
60
+ /** Convenience for the common single-verb refusal. */
61
+ export function refusalFor(verb, decidingLayer, remediation) {
62
+ return policyRefusal({
63
+ allowed: false,
64
+ verbs: [verb],
65
+ decidingLayer,
66
+ remediation,
67
+ });
68
+ }
69
+ //# sourceMappingURL=PolicyRefusal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PolicyRefusal.js","sourceRoot":"","sources":["../../src/policy/PolicyRefusal.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;GAeG;AACH,MAAM,cAAc,GAAG,MAAM,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC;AAMhE,yEAAyE;AACzE,MAAM,UAAU,eAAe,CAAC,KAAc;IAC1C,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACtC,OAAO,KAAK,CAAC;IACjB,CAAC;IACD,OAAQ,KAAiC,CAAC,cAAc,CAAC,KAAK,IAAI,CAAC;AACvE,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAAC,QAAkB;IAC5C,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC3C,MAAM,MAAM,GAAG,QAAQ,CAAC,WAAW;QAC/B,CAAC,CAAC,GAAG,QAAQ,CAAC,WAAW,EAAE;QAC3B,CAAC,CAAC,2BAA2B,KAAK,0CAA0C,CAAC;IAEjF,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,MAAM,CAAiD,CAAC;IAChF,KAAK,CAAC,IAAI,GAAG,oBAAoB,CAAC;IAClC,KAAK,CAAC,cAAc,CAAC,GAAG,IAAI,CAAC;IAC7B,kDAAkD;IAClD,EAAE;IACF,qFAAqF;IACrF,qFAAqF;IACrF,qFAAqF;IACrF,8EAA8E;IAC9E,kFAAkF;IAClF,+EAA+E;IAC/E,EAAE;IACF,mFAAmF;IACnF,sDAAsD;IACtD,MAAM,CAAC,cAAc,CAAC,KAAK,EAAE,UAAU,EAAE;QACrC,KAAK,EAAE,QAAQ;QACf,UAAU,EAAE,KAAK;QACjB,QAAQ,EAAE,IAAI;QACd,YAAY,EAAE,IAAI;KACrB,CAAC,CAAC;IACH,OAAO,KAAK,CAAC;AACjB,CAAC;AAED,sDAAsD;AACtD,MAAM,UAAU,UAAU,CAAC,IAAU,EAAE,aAAqB,EAAE,WAAoB;IAC9E,OAAO,aAAa,CAAC;QACjB,OAAO,EAAE,KAAK;QACd,KAAK,EAAE,CAAC,IAAI,CAAC;QACb,aAAa;QACb,WAAW;KACd,CAAC,CAAC;AACP,CAAC"}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Vocabulary for gating instance mutations.
3
+ *
4
+ * This is a GUARDRAIL, not a security boundary. Anything holding the credential can
5
+ * reach the instance directly — by importing this library and calling the SDK, or with
6
+ * curl. What it buys is that *inadvertent* mutation stops being the default when an AI
7
+ * agent is driving, and that an operator can hard-deny from an environment the agent
8
+ * cannot write to. Do not describe it as anything stronger.
9
+ */
10
+ /**
11
+ * What an operation does. Two verbs, deliberately.
12
+ *
13
+ * CRUD was considered and rejected: `nex exec`, ATF runs and flow execution all mutate
14
+ * without being create, edit or delete, so a CRUD vocabulary leaves them homeless.
15
+ */
16
+ export type Verb = "write" | "execute";
17
+ /**
18
+ * WHERE the effect lands. Not every write is a write to the instance.
19
+ *
20
+ * `pull_script` overwrites a local file, `scope set` changes session state, and the
21
+ * `auth` commands write the credential store. Gating those as instance writes would
22
+ * produce false refusals on operations that never touch ServiceNow data, so the target
23
+ * is carried alongside the verb rather than inventing more verbs.
24
+ *
25
+ * Only `instance` is gated today; the rest exist so the distinction is recorded at the
26
+ * call site rather than rediscovered later.
27
+ */
28
+ export type Target = "instance" | "local" | "session";
29
+ /** What an operation needs before it may proceed. */
30
+ export interface Requirement {
31
+ readonly verbs: readonly Verb[];
32
+ readonly target: Target;
33
+ }
34
+ /** A requirement that needs no permission at all. */
35
+ export declare const READ_ONLY: Requirement;
36
+ /**
37
+ * A layer's answer.
38
+ *
39
+ * `abstain` is distinct from `deny` on purpose — it means "I have no opinion", so a
40
+ * lower-priority layer still gets to speak. Collapsing the two would make an unset
41
+ * environment variable behave like an explicit denial.
42
+ */
43
+ export type LayerAnswer = "grant" | "deny" | "abstain";
44
+ /** One rung of the precedence ladder. */
45
+ export interface PolicyLayer {
46
+ /** Appears in refusals and the audit log, so make it recognisable to an operator. */
47
+ readonly name: string;
48
+ answer(verb: Verb): LayerAnswer;
49
+ }
50
+ /**
51
+ * The result of a check.
52
+ *
53
+ * An object rather than a boolean so the deciding layer can be reported — "why was this
54
+ * refused" is otherwise unanswerable — and so a future `ApprovalToken` (NEX-76) can be
55
+ * added as a grant layer without changing this signature.
56
+ */
57
+ export interface Decision {
58
+ readonly allowed: boolean;
59
+ /** The verbs actually required. Empty means the operation needed no permission. */
60
+ readonly verbs: readonly Verb[];
61
+ /** Name of the layer that decided. `"default"` when nothing granted. */
62
+ readonly decidingLayer: string;
63
+ /** Present only when refused. Written for a human, never naming an MCP parameter. */
64
+ readonly remediation?: string;
65
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Vocabulary for gating instance mutations.
3
+ *
4
+ * This is a GUARDRAIL, not a security boundary. Anything holding the credential can
5
+ * reach the instance directly — by importing this library and calling the SDK, or with
6
+ * curl. What it buys is that *inadvertent* mutation stops being the default when an AI
7
+ * agent is driving, and that an operator can hard-deny from an environment the agent
8
+ * cannot write to. Do not describe it as anything stronger.
9
+ */
10
+ /** A requirement that needs no permission at all. */
11
+ export const READ_ONLY = Object.freeze({
12
+ verbs: Object.freeze([]),
13
+ target: "instance",
14
+ });
15
+ //# sourceMappingURL=PolicyTypes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PolicyTypes.js","sourceRoot":"","sources":["../../src/policy/PolicyTypes.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA6BH,qDAAqD;AACrD,MAAM,CAAC,MAAM,SAAS,GAAgB,MAAM,CAAC,MAAM,CAAC;IAChD,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,CAAoB;IAC3C,MAAM,EAAE,UAAU;CACrB,CAAC,CAAC"}
@@ -0,0 +1,22 @@
1
+ import { Verb } from "./PolicyTypes.js";
2
+ export interface ScriptScanResult {
3
+ /** Verbs the script needs BEYOND `execute`. Today this is `["write"]` or `[]`. */
4
+ readonly verbs: readonly Verb[];
5
+ /** Why, in the order found. Surfaced to the user so a refusal is explainable. */
6
+ readonly reasons: readonly string[];
7
+ /** True when the scan could not resolve something and escalated as a result. */
8
+ readonly escalated: boolean;
9
+ }
10
+ /**
11
+ * Reports the extra permission a script needs.
12
+ *
13
+ * Call this with the script that will ACTUALLY be sent — after any `{param}`
14
+ * substitution. Scanning the pre-substitution text is a hole you could drive a
15
+ * parameter value of `gr.insert()` through.
16
+ */
17
+ export declare function scanScript(script: string): ScriptScanResult;
18
+ /** Convenience: what a background script needs in total. */
19
+ export declare function requirementForScript(script: string): {
20
+ verbs: Verb[];
21
+ reasons: readonly string[];
22
+ };