@alter-ai/cli 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1758 -199
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -28,7 +28,7 @@ import { join } from "path";
|
|
|
28
28
|
// package.json
|
|
29
29
|
var package_default = {
|
|
30
30
|
name: "@alter-ai/cli",
|
|
31
|
-
version: "0.
|
|
31
|
+
version: "0.6.0",
|
|
32
32
|
description: "Command-line interface for the Alter Vault dev portal \u2014 scripted dashboard automation.",
|
|
33
33
|
type: "module",
|
|
34
34
|
bin: {
|
|
@@ -593,9 +593,20 @@ var ReAuthRequiredError = class extends BackendError {
|
|
|
593
593
|
}
|
|
594
594
|
};
|
|
595
595
|
var GrantExpiredError = class extends ReAuthRequiredError {
|
|
596
|
-
|
|
596
|
+
providerId;
|
|
597
|
+
agentId;
|
|
598
|
+
appUserId;
|
|
599
|
+
// Recovery context appended AFTER `details` — same contract and
|
|
600
|
+
// rationale as GrantNotFoundError: the delegation-walk 403 carries
|
|
601
|
+
// provider/agent/app_user recovery fields; the root-path TTL 403
|
|
602
|
+
// leaves them undefined. Existing `new GrantExpiredError(msg, details)`
|
|
603
|
+
// callers keep compiling unchanged.
|
|
604
|
+
constructor(message, details, providerId, agentId, appUserId) {
|
|
597
605
|
super(message, details);
|
|
598
606
|
this.name = "GrantExpiredError";
|
|
607
|
+
this.providerId = providerId;
|
|
608
|
+
this.agentId = agentId;
|
|
609
|
+
this.appUserId = appUserId;
|
|
599
610
|
}
|
|
600
611
|
};
|
|
601
612
|
var GrantRevokedError = class extends ReAuthRequiredError {
|
|
@@ -649,6 +660,12 @@ var GrantNotFoundError = class extends BackendError {
|
|
|
649
660
|
this.appUserId = appUserId;
|
|
650
661
|
}
|
|
651
662
|
};
|
|
663
|
+
var AgentDelegationMissingError = class extends GrantNotFoundError {
|
|
664
|
+
constructor(message, details, providerId, agentId, appUserId) {
|
|
665
|
+
super(message, details, providerId, agentId, appUserId);
|
|
666
|
+
this.name = "AgentDelegationMissingError";
|
|
667
|
+
}
|
|
668
|
+
};
|
|
652
669
|
var AmbiguousGrantError = class extends BackendError {
|
|
653
670
|
providerId;
|
|
654
671
|
accountIdentifiers;
|
|
@@ -711,6 +728,20 @@ var PolicyViolationError = class extends BackendError {
|
|
|
711
728
|
this.policyError = policyError;
|
|
712
729
|
}
|
|
713
730
|
};
|
|
731
|
+
var StepUpRequiredError = class extends PolicyViolationError {
|
|
732
|
+
maxSessionAgeSeconds;
|
|
733
|
+
constructor(message, maxSessionAgeSeconds, details) {
|
|
734
|
+
super(message, "step_up_required", details);
|
|
735
|
+
this.name = "StepUpRequiredError";
|
|
736
|
+
this.maxSessionAgeSeconds = maxSessionAgeSeconds;
|
|
737
|
+
}
|
|
738
|
+
};
|
|
739
|
+
var RedactDischargeFailedError = class extends PolicyViolationError {
|
|
740
|
+
constructor(message, details) {
|
|
741
|
+
super(message, "redact_discharge_failed", details);
|
|
742
|
+
this.name = "RedactDischargeFailedError";
|
|
743
|
+
}
|
|
744
|
+
};
|
|
714
745
|
var RestrictedGrantRequiresProxyError = class extends BackendError {
|
|
715
746
|
constructor(message, details) {
|
|
716
747
|
super(message, details);
|
|
@@ -805,6 +836,14 @@ var TokenRefreshInProgressError = class extends BackendError {
|
|
|
805
836
|
this.grantId = grantId;
|
|
806
837
|
}
|
|
807
838
|
};
|
|
839
|
+
var QuotaExceededError = class extends BackendError {
|
|
840
|
+
retryAfter;
|
|
841
|
+
constructor(message, retryAfter, details) {
|
|
842
|
+
super(message, details);
|
|
843
|
+
this.name = "QuotaExceededError";
|
|
844
|
+
this.retryAfter = retryAfter;
|
|
845
|
+
}
|
|
846
|
+
};
|
|
808
847
|
var ConnectFlowError = class extends AlterSDKError {
|
|
809
848
|
constructor(message, details) {
|
|
810
849
|
super(message, details);
|
|
@@ -1391,13 +1430,10 @@ var AuthSession = class {
|
|
|
1391
1430
|
_assertString(data.session_token, "session_token", "AuthSession");
|
|
1392
1431
|
_assertString(data.auth_url, "auth_url", "AuthSession");
|
|
1393
1432
|
_assertString(data.expires_at, "expires_at", "AuthSession");
|
|
1394
|
-
if (
|
|
1395
|
-
throw new BackendError(
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
field: "expires_in"
|
|
1399
|
-
}
|
|
1400
|
-
);
|
|
1433
|
+
if (!Number.isInteger(data.expires_in)) {
|
|
1434
|
+
throw new BackendError("AuthSession: 'expires_in' must be an integer", {
|
|
1435
|
+
field: "expires_in"
|
|
1436
|
+
});
|
|
1401
1437
|
}
|
|
1402
1438
|
this.sessionToken = data.session_token;
|
|
1403
1439
|
this.authUrl = data.auth_url;
|
|
@@ -1490,6 +1526,15 @@ var OAuthGrantItem = class {
|
|
|
1490
1526
|
// shape and the Python SDK `needs_reconnect`.
|
|
1491
1527
|
needsReconnect;
|
|
1492
1528
|
expiresAt;
|
|
1529
|
+
// The grant's OWN TTL-policy expiry (`grant_policy.expires_at`), first-class.
|
|
1530
|
+
// Two DIFFERENT axes: `expiresAt` above is the provider TOKEN's expiry
|
|
1531
|
+
// (auto-refreshed, never a grant terminal); `grantExpiresAt` is when the
|
|
1532
|
+
// PERMISSION itself lapses (the Connect TTL picker). Null = no TTL
|
|
1533
|
+
// (perpetual until revoked). The backend projects `status = "expired"` the
|
|
1534
|
+
// moment this instant passes even if the DB row hasn't lazily flipped yet,
|
|
1535
|
+
// so filtering `status === "active"` never caches a TTL-dead grant id.
|
|
1536
|
+
// Mirrors the backend Pydantic shape and the Python SDK `grant_expires_at`.
|
|
1537
|
+
grantExpiresAt;
|
|
1493
1538
|
createdAt;
|
|
1494
1539
|
lastUsedAt;
|
|
1495
1540
|
// OAuth grants are ``user`` / ``system`` ROOTS, plus ``agent`` DELEGATION
|
|
@@ -1538,7 +1583,8 @@ var OAuthGrantItem = class {
|
|
|
1538
1583
|
this.status = data.status;
|
|
1539
1584
|
this.scopeMismatch = data.scope_mismatch ?? false;
|
|
1540
1585
|
this.needsReconnect = data.needs_reconnect ?? false;
|
|
1541
|
-
this.expiresAt = data.expires_at
|
|
1586
|
+
this.expiresAt = typeof data.expires_at === "string" ? data.expires_at : null;
|
|
1587
|
+
this.grantExpiresAt = typeof data.grant_expires_at === "string" ? data.grant_expires_at : null;
|
|
1542
1588
|
this.createdAt = data.created_at;
|
|
1543
1589
|
this.lastUsedAt = data.last_used_at ?? null;
|
|
1544
1590
|
if (data.principal_type !== "user" && data.principal_type !== "system" && data.principal_type !== "agent") {
|
|
@@ -1576,6 +1622,7 @@ var OAuthGrantItem = class {
|
|
|
1576
1622
|
scope_mismatch: this.scopeMismatch,
|
|
1577
1623
|
needs_reconnect: this.needsReconnect,
|
|
1578
1624
|
expires_at: this.expiresAt,
|
|
1625
|
+
grant_expires_at: this.grantExpiresAt,
|
|
1579
1626
|
created_at: this.createdAt,
|
|
1580
1627
|
last_used_at: this.lastUsedAt,
|
|
1581
1628
|
principal_type: this.principalType,
|
|
@@ -1604,6 +1651,10 @@ var ManagedSecretGrantItem = class {
|
|
|
1604
1651
|
accountIdentifier;
|
|
1605
1652
|
grantPolicy;
|
|
1606
1653
|
expiresAt;
|
|
1654
|
+
// The grant's OWN TTL-policy expiry (`grant_policy.expires_at`), first-class —
|
|
1655
|
+
// same semantics as `OAuthGrantItem.grantExpiresAt` (null = perpetual; the
|
|
1656
|
+
// backend projects `status = "expired"` once lapsed).
|
|
1657
|
+
grantExpiresAt;
|
|
1607
1658
|
createdAt;
|
|
1608
1659
|
lastUsedAt;
|
|
1609
1660
|
principalType;
|
|
@@ -1630,7 +1681,8 @@ var ManagedSecretGrantItem = class {
|
|
|
1630
1681
|
this.status = data.status;
|
|
1631
1682
|
this.accountIdentifier = data.account_identifier ?? null;
|
|
1632
1683
|
this.grantPolicy = typeof data.grant_policy === "object" && data.grant_policy !== null && !Array.isArray(data.grant_policy) ? data.grant_policy : null;
|
|
1633
|
-
this.expiresAt = data.expires_at
|
|
1684
|
+
this.expiresAt = typeof data.expires_at === "string" ? data.expires_at : null;
|
|
1685
|
+
this.grantExpiresAt = typeof data.grant_expires_at === "string" ? data.grant_expires_at : null;
|
|
1634
1686
|
this.createdAt = data.created_at;
|
|
1635
1687
|
this.lastUsedAt = data.last_used_at ?? null;
|
|
1636
1688
|
this.principalType = data.principal_type;
|
|
@@ -1660,6 +1712,7 @@ var ManagedSecretGrantItem = class {
|
|
|
1660
1712
|
account_identifier: this.accountIdentifier,
|
|
1661
1713
|
grant_policy: this.grantPolicy,
|
|
1662
1714
|
expires_at: this.expiresAt,
|
|
1715
|
+
grant_expires_at: this.grantExpiresAt,
|
|
1663
1716
|
created_at: this.createdAt,
|
|
1664
1717
|
last_used_at: this.lastUsedAt,
|
|
1665
1718
|
parent_grant_id: this.parentGrantId,
|
|
@@ -2029,8 +2082,40 @@ function _assertApprovalStatusValue(value, context) {
|
|
|
2029
2082
|
}
|
|
2030
2083
|
}
|
|
2031
2084
|
var APPROVAL_TERMINAL_STATUSES = /* @__PURE__ */ new Set(["denied", "expired", "executed", "failed"]);
|
|
2085
|
+
var ApprovalGate = class {
|
|
2086
|
+
approvalId;
|
|
2087
|
+
expiresAt;
|
|
2088
|
+
expiresIn;
|
|
2089
|
+
approvalUrl;
|
|
2090
|
+
constructor(args) {
|
|
2091
|
+
const ctx = "ApprovalGate";
|
|
2092
|
+
_assertString(args?.approval_id, "approval_id", ctx);
|
|
2093
|
+
if (!Number.isInteger(args?.expires_in)) {
|
|
2094
|
+
throw new BackendError(
|
|
2095
|
+
`${ctx}: 'expires_in' must be an integer, got ${typeof args?.expires_in}`,
|
|
2096
|
+
{ field: "expires_in" }
|
|
2097
|
+
);
|
|
2098
|
+
}
|
|
2099
|
+
_assertString(args?.approval_url, "approval_url", ctx);
|
|
2100
|
+
this.approvalId = args.approval_id;
|
|
2101
|
+
this.expiresAt = _parseDateOrThrow(args.expires_at, "expires_at", ctx);
|
|
2102
|
+
this.expiresIn = args.expires_in;
|
|
2103
|
+
this.approvalUrl = args.approval_url;
|
|
2104
|
+
Object.freeze(this);
|
|
2105
|
+
}
|
|
2106
|
+
toJSON() {
|
|
2107
|
+
return {
|
|
2108
|
+
approval_id: this.approvalId,
|
|
2109
|
+
expires_at: this.expiresAt.toISOString(),
|
|
2110
|
+
expires_in: this.expiresIn,
|
|
2111
|
+
approval_url: this.approvalUrl
|
|
2112
|
+
};
|
|
2113
|
+
}
|
|
2114
|
+
};
|
|
2032
2115
|
var PendingApproval = class {
|
|
2033
2116
|
approvalId;
|
|
2117
|
+
/** Shared execution unit for the gate group (N-of-N multi-party). */
|
|
2118
|
+
approvalGroupId;
|
|
2034
2119
|
// The backend's ``ApprovalPendingResponse`` always reports
|
|
2035
2120
|
// ``status: "pending"`` on the 202 path. Carrying it on the SDK
|
|
2036
2121
|
// model preserves the wire contract and matches the Python SDK so
|
|
@@ -2047,12 +2132,19 @@ var PendingApproval = class {
|
|
|
2047
2132
|
* ``EMAIL_PROVIDER`` configured.
|
|
2048
2133
|
*/
|
|
2049
2134
|
approvalUrl;
|
|
2135
|
+
/**
|
|
2136
|
+
* Every distinct approver gate (>=1). Each must be approved before execution
|
|
2137
|
+
* proceeds; any denial or expiry kills the whole group. The top-level fields
|
|
2138
|
+
* point at the group's executor gate (the id used for polling).
|
|
2139
|
+
*/
|
|
2140
|
+
gates;
|
|
2050
2141
|
constructor(args) {
|
|
2051
2142
|
const ctx = "PendingApproval";
|
|
2052
2143
|
_assertString(args?.approval_id, "approval_id", ctx);
|
|
2053
|
-
|
|
2144
|
+
_assertString(args?.approval_group_id, "approval_group_id", ctx);
|
|
2145
|
+
if (!Number.isInteger(args?.expires_in)) {
|
|
2054
2146
|
throw new BackendError(
|
|
2055
|
-
`${ctx}: 'expires_in' must be
|
|
2147
|
+
`${ctx}: 'expires_in' must be an integer, got ${typeof args?.expires_in}`,
|
|
2056
2148
|
{ field: "expires_in" }
|
|
2057
2149
|
);
|
|
2058
2150
|
}
|
|
@@ -2063,26 +2155,67 @@ var PendingApproval = class {
|
|
|
2063
2155
|
);
|
|
2064
2156
|
}
|
|
2065
2157
|
_assertString(args?.approval_url, "approval_url", ctx);
|
|
2158
|
+
if (!Array.isArray(args?.gates) || args.gates.length < 1) {
|
|
2159
|
+
throw new BackendError(`${ctx}: 'gates' must be a non-empty array`, {
|
|
2160
|
+
field: "gates"
|
|
2161
|
+
});
|
|
2162
|
+
}
|
|
2066
2163
|
this.approvalId = args.approval_id;
|
|
2164
|
+
this.approvalGroupId = args.approval_group_id;
|
|
2067
2165
|
this.status = "pending";
|
|
2068
2166
|
this.expiresAt = _parseDateOrThrow(args.expires_at, "expires_at", ctx);
|
|
2069
2167
|
this.expiresIn = args.expires_in;
|
|
2070
2168
|
this.approvalUrl = args.approval_url;
|
|
2169
|
+
this.gates = Object.freeze(
|
|
2170
|
+
args.gates.map(
|
|
2171
|
+
(g) => new ApprovalGate(g)
|
|
2172
|
+
)
|
|
2173
|
+
);
|
|
2071
2174
|
Object.freeze(this);
|
|
2072
2175
|
}
|
|
2073
2176
|
toJSON() {
|
|
2074
2177
|
return {
|
|
2075
2178
|
approval_id: this.approvalId,
|
|
2179
|
+
approval_group_id: this.approvalGroupId,
|
|
2076
2180
|
status: this.status,
|
|
2077
2181
|
expires_at: this.expiresAt.toISOString(),
|
|
2078
2182
|
expires_in: this.expiresIn,
|
|
2079
|
-
approval_url: this.approvalUrl
|
|
2183
|
+
approval_url: this.approvalUrl,
|
|
2184
|
+
gates: this.gates.map((g) => g.toJSON())
|
|
2185
|
+
};
|
|
2186
|
+
}
|
|
2187
|
+
};
|
|
2188
|
+
var ApprovalGateStatus = class {
|
|
2189
|
+
approvalId;
|
|
2190
|
+
status;
|
|
2191
|
+
expiresAt;
|
|
2192
|
+
decidedAt;
|
|
2193
|
+
constructor(args) {
|
|
2194
|
+
const ctx = "ApprovalGateStatus";
|
|
2195
|
+
_assertString(args?.approval_id, "approval_id", ctx);
|
|
2196
|
+
_assertApprovalStatusValue(args?.status, ctx);
|
|
2197
|
+
this.approvalId = args.approval_id;
|
|
2198
|
+
this.status = args.status;
|
|
2199
|
+
this.expiresAt = _parseDateOrThrow(args.expires_at, "expires_at", ctx);
|
|
2200
|
+
this.decidedAt = _parseOptionalDate(args.decided_at, "decided_at", ctx);
|
|
2201
|
+
Object.freeze(this);
|
|
2202
|
+
}
|
|
2203
|
+
toJSON() {
|
|
2204
|
+
return {
|
|
2205
|
+
approval_id: this.approvalId,
|
|
2206
|
+
status: this.status,
|
|
2207
|
+
expires_at: this.expiresAt.toISOString(),
|
|
2208
|
+
decided_at: this.decidedAt ? this.decidedAt.toISOString() : null
|
|
2080
2209
|
};
|
|
2081
2210
|
}
|
|
2082
2211
|
};
|
|
2083
2212
|
var ApprovalStatus = class {
|
|
2084
2213
|
approvalId;
|
|
2214
|
+
/** Shared execution unit; for a multi-party approval `status` is the DERIVED
|
|
2215
|
+
* group status and `gates` carries the per-gate breakdown. */
|
|
2216
|
+
approvalGroupId;
|
|
2085
2217
|
status;
|
|
2218
|
+
gates;
|
|
2086
2219
|
expiresAt;
|
|
2087
2220
|
decidedAt;
|
|
2088
2221
|
decisionReason;
|
|
@@ -2098,7 +2231,13 @@ var ApprovalStatus = class {
|
|
|
2098
2231
|
constructor(args) {
|
|
2099
2232
|
const ctx = "ApprovalStatus";
|
|
2100
2233
|
_assertString(args?.approval_id, "approval_id", ctx);
|
|
2234
|
+
_assertString(args?.approval_group_id, "approval_group_id", ctx);
|
|
2101
2235
|
_assertApprovalStatusValue(args?.status, ctx);
|
|
2236
|
+
if (!Array.isArray(args?.gates) || args.gates.length < 1) {
|
|
2237
|
+
throw new BackendError(`${ctx}: 'gates' must be a non-empty array`, {
|
|
2238
|
+
field: "gates"
|
|
2239
|
+
});
|
|
2240
|
+
}
|
|
2102
2241
|
if (typeof args?.has_result !== "boolean") {
|
|
2103
2242
|
throw new BackendError(
|
|
2104
2243
|
`${ctx}: 'has_result' must be a boolean, got ${typeof args?.has_result}`,
|
|
@@ -2112,7 +2251,13 @@ var ApprovalStatus = class {
|
|
|
2112
2251
|
);
|
|
2113
2252
|
}
|
|
2114
2253
|
this.approvalId = args.approval_id;
|
|
2254
|
+
this.approvalGroupId = args.approval_group_id;
|
|
2115
2255
|
this.status = args.status;
|
|
2256
|
+
this.gates = Object.freeze(
|
|
2257
|
+
args.gates.map(
|
|
2258
|
+
(g) => new ApprovalGateStatus(g)
|
|
2259
|
+
)
|
|
2260
|
+
);
|
|
2116
2261
|
this.expiresAt = _parseDateOrThrow(args.expires_at, "expires_at", ctx);
|
|
2117
2262
|
this.decidedAt = _parseOptionalDate(args.decided_at, "decided_at", ctx);
|
|
2118
2263
|
this.decisionReason = args.decision_reason ?? null;
|
|
@@ -2127,7 +2272,9 @@ var ApprovalStatus = class {
|
|
|
2127
2272
|
toJSON() {
|
|
2128
2273
|
return {
|
|
2129
2274
|
approval_id: this.approvalId,
|
|
2275
|
+
approval_group_id: this.approvalGroupId,
|
|
2130
2276
|
status: this.status,
|
|
2277
|
+
gates: this.gates.map((g) => g.toJSON()),
|
|
2131
2278
|
expires_at: this.expiresAt.toISOString(),
|
|
2132
2279
|
decided_at: this.decidedAt ? this.decidedAt.toISOString() : null,
|
|
2133
2280
|
decision_reason: this.decisionReason,
|
|
@@ -2145,9 +2292,9 @@ var ApprovalResult = class {
|
|
|
2145
2292
|
bodyTruncated;
|
|
2146
2293
|
constructor(args) {
|
|
2147
2294
|
const ctx = "ApprovalResult";
|
|
2148
|
-
if (
|
|
2295
|
+
if (!Number.isInteger(args?.status_code)) {
|
|
2149
2296
|
throw new BackendError(
|
|
2150
|
-
`${ctx}: 'status_code' must be
|
|
2297
|
+
`${ctx}: 'status_code' must be an integer, got ${typeof args?.status_code}`,
|
|
2151
2298
|
{ field: "status_code" }
|
|
2152
2299
|
);
|
|
2153
2300
|
}
|
|
@@ -2790,6 +2937,271 @@ var IdentityAssertion = class {
|
|
|
2790
2937
|
return this.toString();
|
|
2791
2938
|
}
|
|
2792
2939
|
};
|
|
2940
|
+
var ProviderSpec = class {
|
|
2941
|
+
/** `"oauth"` or `"managed"` (see {@link ProviderSpecKind}). */
|
|
2942
|
+
providerKind;
|
|
2943
|
+
providerId;
|
|
2944
|
+
/** Monotonic ingest version — bumps whenever the stored spec content changes. */
|
|
2945
|
+
version;
|
|
2946
|
+
/** Where the spec came from (e.g. an official published spec vs a curated one). */
|
|
2947
|
+
provenance;
|
|
2948
|
+
sourceUrl;
|
|
2949
|
+
contentHash;
|
|
2950
|
+
operationCount;
|
|
2951
|
+
/**
|
|
2952
|
+
* Parsed to a real `Date` (not a raw string) so callers can compare
|
|
2953
|
+
* freshness against `new Date()` / `Date.now()` without first
|
|
2954
|
+
* remembering to parse. Mirrors Python SDK
|
|
2955
|
+
* ``ProviderSpec.fetched_at: datetime``. Re-serialized to ISO 8601
|
|
2956
|
+
* by `toJSON()`.
|
|
2957
|
+
*/
|
|
2958
|
+
fetchedAt;
|
|
2959
|
+
/** When this spec version last changed; unlike fetchedAt, unchanged sweeps do not move it. */
|
|
2960
|
+
changedAt;
|
|
2961
|
+
title;
|
|
2962
|
+
/** The spec document's own declared version string (e.g. an OpenAPI `info.version`). */
|
|
2963
|
+
specVersion;
|
|
2964
|
+
constructor(data) {
|
|
2965
|
+
this.providerKind = _requireNonEmptyString(
|
|
2966
|
+
"ProviderSpec",
|
|
2967
|
+
"provider_kind",
|
|
2968
|
+
data?.provider_kind
|
|
2969
|
+
);
|
|
2970
|
+
this.providerId = _requireNonEmptyString(
|
|
2971
|
+
"ProviderSpec",
|
|
2972
|
+
"provider_id",
|
|
2973
|
+
data.provider_id
|
|
2974
|
+
);
|
|
2975
|
+
this.version = _requireNumber("ProviderSpec", "version", data.version);
|
|
2976
|
+
this.provenance = _requireNonEmptyString(
|
|
2977
|
+
"ProviderSpec",
|
|
2978
|
+
"provenance",
|
|
2979
|
+
data.provenance
|
|
2980
|
+
);
|
|
2981
|
+
this.sourceUrl = _optionalString(
|
|
2982
|
+
"ProviderSpec",
|
|
2983
|
+
"source_url",
|
|
2984
|
+
data.source_url
|
|
2985
|
+
);
|
|
2986
|
+
this.contentHash = _requireNonEmptyString(
|
|
2987
|
+
"ProviderSpec",
|
|
2988
|
+
"content_hash",
|
|
2989
|
+
data.content_hash
|
|
2990
|
+
);
|
|
2991
|
+
this.operationCount = _requireNumber(
|
|
2992
|
+
"ProviderSpec",
|
|
2993
|
+
"operation_count",
|
|
2994
|
+
data.operation_count
|
|
2995
|
+
);
|
|
2996
|
+
this.fetchedAt = _parseDateOrThrow(
|
|
2997
|
+
data.fetched_at,
|
|
2998
|
+
"fetched_at",
|
|
2999
|
+
"ProviderSpec"
|
|
3000
|
+
);
|
|
3001
|
+
this.changedAt = _parseDateOrThrow(
|
|
3002
|
+
data.changed_at,
|
|
3003
|
+
"changed_at",
|
|
3004
|
+
"ProviderSpec"
|
|
3005
|
+
);
|
|
3006
|
+
this.title = _optionalString("ProviderSpec", "title", data.title);
|
|
3007
|
+
this.specVersion = _optionalString(
|
|
3008
|
+
"ProviderSpec",
|
|
3009
|
+
"spec_version",
|
|
3010
|
+
data.spec_version
|
|
3011
|
+
);
|
|
3012
|
+
Object.freeze(this);
|
|
3013
|
+
}
|
|
3014
|
+
toJSON() {
|
|
3015
|
+
return {
|
|
3016
|
+
provider_kind: this.providerKind,
|
|
3017
|
+
provider_id: this.providerId,
|
|
3018
|
+
version: this.version,
|
|
3019
|
+
provenance: this.provenance,
|
|
3020
|
+
source_url: this.sourceUrl,
|
|
3021
|
+
content_hash: this.contentHash,
|
|
3022
|
+
operation_count: this.operationCount,
|
|
3023
|
+
// Serialize Date back to ISO 8601 wire format so toJSON() round-
|
|
3024
|
+
// trips identically to Python ``model_dump(mode="json")``.
|
|
3025
|
+
fetched_at: this.fetchedAt.toISOString(),
|
|
3026
|
+
changed_at: this.changedAt.toISOString(),
|
|
3027
|
+
title: this.title,
|
|
3028
|
+
spec_version: this.specVersion
|
|
3029
|
+
};
|
|
3030
|
+
}
|
|
3031
|
+
};
|
|
3032
|
+
var SpecOperation = class {
|
|
3033
|
+
/**
|
|
3034
|
+
* Spec-native operation id. May contain slashes (GitHub's
|
|
3035
|
+
* `repos/get`) or dots (Google's `gmail.users.messages.list`).
|
|
3036
|
+
*/
|
|
3037
|
+
operationId;
|
|
3038
|
+
method;
|
|
3039
|
+
/** URL path template with `{placeholders}` (e.g. `/repos/{owner}/{repo}`). */
|
|
3040
|
+
pathTemplate;
|
|
3041
|
+
summary;
|
|
3042
|
+
constructor(data) {
|
|
3043
|
+
this.operationId = _requireNonEmptyString(
|
|
3044
|
+
"SpecOperation",
|
|
3045
|
+
"operation_id",
|
|
3046
|
+
data?.operation_id
|
|
3047
|
+
);
|
|
3048
|
+
this.method = _requireNonEmptyString(
|
|
3049
|
+
"SpecOperation",
|
|
3050
|
+
"method",
|
|
3051
|
+
data.method
|
|
3052
|
+
);
|
|
3053
|
+
this.pathTemplate = _requireNonEmptyString(
|
|
3054
|
+
"SpecOperation",
|
|
3055
|
+
"path_template",
|
|
3056
|
+
data.path_template
|
|
3057
|
+
);
|
|
3058
|
+
this.summary = _optionalString("SpecOperation", "summary", data.summary);
|
|
3059
|
+
Object.freeze(this);
|
|
3060
|
+
}
|
|
3061
|
+
toJSON() {
|
|
3062
|
+
return {
|
|
3063
|
+
operation_id: this.operationId,
|
|
3064
|
+
method: this.method,
|
|
3065
|
+
path_template: this.pathTemplate,
|
|
3066
|
+
summary: this.summary
|
|
3067
|
+
};
|
|
3068
|
+
}
|
|
3069
|
+
};
|
|
3070
|
+
var SpecOperationsPage = class {
|
|
3071
|
+
items;
|
|
3072
|
+
total;
|
|
3073
|
+
limit;
|
|
3074
|
+
offset;
|
|
3075
|
+
hasMore;
|
|
3076
|
+
spec;
|
|
3077
|
+
constructor(data) {
|
|
3078
|
+
if (!Array.isArray(data?.items)) {
|
|
3079
|
+
throw _wireShapeError(
|
|
3080
|
+
"SpecOperationsPage",
|
|
3081
|
+
"items",
|
|
3082
|
+
"array",
|
|
3083
|
+
data?.items
|
|
3084
|
+
);
|
|
3085
|
+
}
|
|
3086
|
+
this.items = Object.freeze(
|
|
3087
|
+
data.items.map(
|
|
3088
|
+
(item) => item instanceof SpecOperation ? item : new SpecOperation(item)
|
|
3089
|
+
)
|
|
3090
|
+
);
|
|
3091
|
+
this.total = _requireNumber("SpecOperationsPage", "total", data.total);
|
|
3092
|
+
this.limit = _requireNumber("SpecOperationsPage", "limit", data.limit);
|
|
3093
|
+
this.offset = _requireNumber("SpecOperationsPage", "offset", data.offset);
|
|
3094
|
+
this.hasMore = _requireBoolean(
|
|
3095
|
+
"SpecOperationsPage",
|
|
3096
|
+
"has_more",
|
|
3097
|
+
data.has_more
|
|
3098
|
+
);
|
|
3099
|
+
this.spec = data.spec instanceof ProviderSpec ? data.spec : new ProviderSpec(data.spec);
|
|
3100
|
+
Object.freeze(this);
|
|
3101
|
+
}
|
|
3102
|
+
toJSON() {
|
|
3103
|
+
return {
|
|
3104
|
+
items: this.items.map((item) => item.toJSON()),
|
|
3105
|
+
total: this.total,
|
|
3106
|
+
limit: this.limit,
|
|
3107
|
+
offset: this.offset,
|
|
3108
|
+
has_more: this.hasMore,
|
|
3109
|
+
spec: this.spec.toJSON()
|
|
3110
|
+
};
|
|
3111
|
+
}
|
|
3112
|
+
};
|
|
3113
|
+
var SpecOperationDetail = class {
|
|
3114
|
+
operationId;
|
|
3115
|
+
method;
|
|
3116
|
+
pathTemplate;
|
|
3117
|
+
summary;
|
|
3118
|
+
paramsSchema;
|
|
3119
|
+
requestSchema;
|
|
3120
|
+
responseSchema;
|
|
3121
|
+
spec;
|
|
3122
|
+
constructor(data) {
|
|
3123
|
+
this.operationId = _requireNonEmptyString(
|
|
3124
|
+
"SpecOperationDetail",
|
|
3125
|
+
"operation_id",
|
|
3126
|
+
data?.operation_id
|
|
3127
|
+
);
|
|
3128
|
+
this.method = _requireNonEmptyString(
|
|
3129
|
+
"SpecOperationDetail",
|
|
3130
|
+
"method",
|
|
3131
|
+
data.method
|
|
3132
|
+
);
|
|
3133
|
+
this.pathTemplate = _requireNonEmptyString(
|
|
3134
|
+
"SpecOperationDetail",
|
|
3135
|
+
"path_template",
|
|
3136
|
+
data.path_template
|
|
3137
|
+
);
|
|
3138
|
+
this.summary = _optionalString(
|
|
3139
|
+
"SpecOperationDetail",
|
|
3140
|
+
"summary",
|
|
3141
|
+
data.summary
|
|
3142
|
+
);
|
|
3143
|
+
this.paramsSchema = _optionalObjectArray(
|
|
3144
|
+
"SpecOperationDetail",
|
|
3145
|
+
"params_schema",
|
|
3146
|
+
data.params_schema
|
|
3147
|
+
);
|
|
3148
|
+
this.requestSchema = _optionalObject(
|
|
3149
|
+
"SpecOperationDetail",
|
|
3150
|
+
"request_schema",
|
|
3151
|
+
data.request_schema
|
|
3152
|
+
);
|
|
3153
|
+
this.responseSchema = _optionalObject(
|
|
3154
|
+
"SpecOperationDetail",
|
|
3155
|
+
"response_schema",
|
|
3156
|
+
data.response_schema
|
|
3157
|
+
);
|
|
3158
|
+
this.spec = data.spec instanceof ProviderSpec ? data.spec : new ProviderSpec(data.spec);
|
|
3159
|
+
Object.freeze(this);
|
|
3160
|
+
}
|
|
3161
|
+
toJSON() {
|
|
3162
|
+
return {
|
|
3163
|
+
operation_id: this.operationId,
|
|
3164
|
+
method: this.method,
|
|
3165
|
+
path_template: this.pathTemplate,
|
|
3166
|
+
summary: this.summary,
|
|
3167
|
+
params_schema: this.paramsSchema,
|
|
3168
|
+
request_schema: this.requestSchema,
|
|
3169
|
+
response_schema: this.responseSchema,
|
|
3170
|
+
spec: this.spec.toJSON()
|
|
3171
|
+
};
|
|
3172
|
+
}
|
|
3173
|
+
};
|
|
3174
|
+
function _optionalString(modelName, field, value) {
|
|
3175
|
+
if (value === null || value === void 0) return null;
|
|
3176
|
+
if (typeof value !== "string") {
|
|
3177
|
+
throw _wireShapeError(modelName, field, "string or null", value);
|
|
3178
|
+
}
|
|
3179
|
+
return value;
|
|
3180
|
+
}
|
|
3181
|
+
function _optionalObject(modelName, field, value) {
|
|
3182
|
+
if (value === null || value === void 0) return null;
|
|
3183
|
+
if (typeof value !== "object" || Array.isArray(value)) {
|
|
3184
|
+
throw _wireShapeError(modelName, field, "object or null", value);
|
|
3185
|
+
}
|
|
3186
|
+
return value;
|
|
3187
|
+
}
|
|
3188
|
+
function _optionalObjectArray(modelName, field, value) {
|
|
3189
|
+
if (value === null || value === void 0) return null;
|
|
3190
|
+
if (!Array.isArray(value)) {
|
|
3191
|
+
throw _wireShapeError(modelName, field, "array of objects or null", value);
|
|
3192
|
+
}
|
|
3193
|
+
for (const entry of value) {
|
|
3194
|
+
if (entry === null || typeof entry !== "object" || Array.isArray(entry)) {
|
|
3195
|
+
throw _wireShapeError(
|
|
3196
|
+
modelName,
|
|
3197
|
+
field,
|
|
3198
|
+
"array of objects or null",
|
|
3199
|
+
entry
|
|
3200
|
+
);
|
|
3201
|
+
}
|
|
3202
|
+
}
|
|
3203
|
+
return value;
|
|
3204
|
+
}
|
|
2793
3205
|
var ALGORITHM = "AWS4-HMAC-SHA256";
|
|
2794
3206
|
var AWS_HOST_RE = (
|
|
2795
3207
|
// eslint-disable-next-line security/detect-unsafe-regex
|
|
@@ -3563,7 +3975,7 @@ function _requireStringArray(raw, key, context) {
|
|
|
3563
3975
|
}
|
|
3564
3976
|
return v;
|
|
3565
3977
|
}
|
|
3566
|
-
function
|
|
3978
|
+
function _optionalString2(raw, key, context) {
|
|
3567
3979
|
const v = raw[key];
|
|
3568
3980
|
if (v === null || v === void 0) return null;
|
|
3569
3981
|
if (typeof v !== "string") {
|
|
@@ -3626,11 +4038,11 @@ function rawToInfo(raw) {
|
|
|
3626
4038
|
const createdAt = _requireString(raw, "created_at", ctx);
|
|
3627
4039
|
const cidrAllowlist = _optionalStringArray(raw, "cidr_allowlist", ctx);
|
|
3628
4040
|
const rateLimitRpm = _optionalNumber(raw, "rate_limit_rpm", ctx);
|
|
3629
|
-
const expiresAt =
|
|
3630
|
-
const deprecatedAt =
|
|
3631
|
-
const revokedAt =
|
|
3632
|
-
const parentKeyId =
|
|
3633
|
-
const lastUsedAt =
|
|
4041
|
+
const expiresAt = _optionalString2(raw, "expires_at", ctx);
|
|
4042
|
+
const deprecatedAt = _optionalString2(raw, "deprecated_at", ctx);
|
|
4043
|
+
const revokedAt = _optionalString2(raw, "revoked_at", ctx);
|
|
4044
|
+
const parentKeyId = _optionalString2(raw, "parent_key_id", ctx);
|
|
4045
|
+
const lastUsedAt = _optionalString2(raw, "last_used_at", ctx);
|
|
3634
4046
|
const status = (() => {
|
|
3635
4047
|
if (revokedAt !== null) return "revoked";
|
|
3636
4048
|
if (deprecatedAt !== null) return "rotated";
|
|
@@ -3956,6 +4368,216 @@ function requireStringArray(fieldName, value) {
|
|
|
3956
4368
|
function catalogContractError(message) {
|
|
3957
4369
|
return new BackendError(message);
|
|
3958
4370
|
}
|
|
4371
|
+
var LIST_OPERATIONS_LIMIT_MAX = 500;
|
|
4372
|
+
var SEARCH_MAX_LENGTH = 200;
|
|
4373
|
+
function _requireKind(kind) {
|
|
4374
|
+
if (kind !== "oauth" && kind !== "managed") {
|
|
4375
|
+
throw new AlterValueError(
|
|
4376
|
+
`kind must be "oauth" or "managed", got ${JSON.stringify(kind)}`
|
|
4377
|
+
);
|
|
4378
|
+
}
|
|
4379
|
+
return kind;
|
|
4380
|
+
}
|
|
4381
|
+
function _requireProviderId(providerId) {
|
|
4382
|
+
if (typeof providerId !== "string" || providerId.trim().length === 0) {
|
|
4383
|
+
throw new AlterValueError("providerId must be a non-empty string");
|
|
4384
|
+
}
|
|
4385
|
+
return providerId;
|
|
4386
|
+
}
|
|
4387
|
+
function _strictEncodeSegment(segment) {
|
|
4388
|
+
return encodeURIComponent(segment).replace(
|
|
4389
|
+
/[!'()*]/g,
|
|
4390
|
+
(c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`
|
|
4391
|
+
);
|
|
4392
|
+
}
|
|
4393
|
+
var ProviderSpecsNamespace = class {
|
|
4394
|
+
#vault;
|
|
4395
|
+
constructor(vault) {
|
|
4396
|
+
this.#vault = vault;
|
|
4397
|
+
}
|
|
4398
|
+
/**
|
|
4399
|
+
* Every provider's active spec metadata (freshness + provenance),
|
|
4400
|
+
* optionally filtered to one provider family.
|
|
4401
|
+
*
|
|
4402
|
+
* Requires the `providers:read` key scope.
|
|
4403
|
+
*
|
|
4404
|
+
* @param kind Optional filter: `"oauth"` or `"managed"`.
|
|
4405
|
+
* @throws {AlterValueError} `kind` is not `"oauth"` / `"managed"`.
|
|
4406
|
+
* @throws {InsufficientScopeError} key lacks `providers:read`.
|
|
4407
|
+
* @throws {BackendError} backend reachability or response-shape failure.
|
|
4408
|
+
*/
|
|
4409
|
+
async list(kind) {
|
|
4410
|
+
let path = "/sdk/provider-specs";
|
|
4411
|
+
if (kind !== void 0) {
|
|
4412
|
+
const params = new URLSearchParams({ kind: _requireKind(kind) });
|
|
4413
|
+
path += `?${params.toString()}`;
|
|
4414
|
+
}
|
|
4415
|
+
const data = await this.#getJson(path);
|
|
4416
|
+
const obj = _requireResponseObject2("ProviderSpecListResponse", data);
|
|
4417
|
+
const items = obj.items;
|
|
4418
|
+
if (!Array.isArray(items)) {
|
|
4419
|
+
throw new BackendError(
|
|
4420
|
+
"Backend response did not match expected ProviderSpecListResponse shape: missing 'items' array",
|
|
4421
|
+
{ model: "ProviderSpecListResponse", received_keys: Object.keys(obj) }
|
|
4422
|
+
);
|
|
4423
|
+
}
|
|
4424
|
+
if (typeof obj.total !== "number") {
|
|
4425
|
+
throw new BackendError(
|
|
4426
|
+
"Backend response did not match expected ProviderSpecListResponse shape: 'total' must be a number",
|
|
4427
|
+
{ model: "ProviderSpecListResponse", received_keys: Object.keys(obj) }
|
|
4428
|
+
);
|
|
4429
|
+
}
|
|
4430
|
+
return items.map(
|
|
4431
|
+
(item) => new ProviderSpec(item)
|
|
4432
|
+
);
|
|
4433
|
+
}
|
|
4434
|
+
/**
|
|
4435
|
+
* One provider's active spec metadata.
|
|
4436
|
+
*
|
|
4437
|
+
* @throws {AlterValueError} invalid `kind` / empty `providerId`.
|
|
4438
|
+
* @throws {GrantNotFoundError} no active spec for this provider
|
|
4439
|
+
* (backend 404, `provider_spec_not_found`).
|
|
4440
|
+
* @throws {InsufficientScopeError} key lacks `providers:read`.
|
|
4441
|
+
* @throws {BackendError} backend reachability or response-shape failure.
|
|
4442
|
+
*/
|
|
4443
|
+
async get(kind, providerId) {
|
|
4444
|
+
const path = `/sdk/provider-specs/${_requireKind(kind)}/${_strictEncodeSegment(
|
|
4445
|
+
_requireProviderId(providerId)
|
|
4446
|
+
)}`;
|
|
4447
|
+
const data = await this.#getJson(path);
|
|
4448
|
+
const obj = _requireResponseObject2("ProviderSpec", data);
|
|
4449
|
+
return new ProviderSpec(
|
|
4450
|
+
obj
|
|
4451
|
+
);
|
|
4452
|
+
}
|
|
4453
|
+
/**
|
|
4454
|
+
* Page the provider's operations (in-band discovery). Each row is a
|
|
4455
|
+
* summary ({@link SpecOperation}); fetch the full schemas per
|
|
4456
|
+
* operation via {@link getOperation}.
|
|
4457
|
+
*
|
|
4458
|
+
* @param opts `search` (substring filter, <= 200 chars), `limit`
|
|
4459
|
+
* (1–500, default 100), `offset` (>= 0, default 0) — all validated
|
|
4460
|
+
* client-side before any network call, mirroring the backend gates.
|
|
4461
|
+
* @throws {AlterValueError} invalid `kind` / `providerId` / options.
|
|
4462
|
+
* @throws {GrantNotFoundError} no active spec for this provider
|
|
4463
|
+
* (backend 404, `provider_spec_not_found`).
|
|
4464
|
+
* @throws {InsufficientScopeError} key lacks `providers:read`.
|
|
4465
|
+
* @throws {BackendError} backend reachability or response-shape failure.
|
|
4466
|
+
*/
|
|
4467
|
+
async listOperations(kind, providerId, opts = {}) {
|
|
4468
|
+
const basePath = `/sdk/provider-specs/${_requireKind(kind)}/${_strictEncodeSegment(
|
|
4469
|
+
_requireProviderId(providerId)
|
|
4470
|
+
)}/operations`;
|
|
4471
|
+
const limit = opts.limit === void 0 ? 100 : opts.limit;
|
|
4472
|
+
const offset = opts.offset === void 0 ? 0 : opts.offset;
|
|
4473
|
+
if (typeof limit !== "number" || !Number.isInteger(limit) || limit < 1 || limit > LIST_OPERATIONS_LIMIT_MAX) {
|
|
4474
|
+
throw new AlterValueError(
|
|
4475
|
+
`limit must be an integer between 1 and ${LIST_OPERATIONS_LIMIT_MAX}`
|
|
4476
|
+
);
|
|
4477
|
+
}
|
|
4478
|
+
if (typeof offset !== "number" || !Number.isInteger(offset) || offset < 0) {
|
|
4479
|
+
throw new AlterValueError("offset must be an integer >= 0");
|
|
4480
|
+
}
|
|
4481
|
+
if (opts.search !== void 0) {
|
|
4482
|
+
if (typeof opts.search !== "string" || opts.search.length === 0) {
|
|
4483
|
+
throw new AlterValueError("search must be a non-empty string");
|
|
4484
|
+
}
|
|
4485
|
+
if (opts.search.length > SEARCH_MAX_LENGTH) {
|
|
4486
|
+
throw new AlterValueError(
|
|
4487
|
+
`search is capped at ${SEARCH_MAX_LENGTH} characters`
|
|
4488
|
+
);
|
|
4489
|
+
}
|
|
4490
|
+
}
|
|
4491
|
+
const params = new URLSearchParams();
|
|
4492
|
+
params.set("limit", String(limit));
|
|
4493
|
+
params.set("offset", String(offset));
|
|
4494
|
+
if (opts.search !== void 0) {
|
|
4495
|
+
params.set("search", opts.search);
|
|
4496
|
+
}
|
|
4497
|
+
params.sort();
|
|
4498
|
+
const data = await this.#getJson(`${basePath}?${params.toString()}`);
|
|
4499
|
+
const obj = _requireResponseObject2("SpecOperationsPage", data);
|
|
4500
|
+
return new SpecOperationsPage(
|
|
4501
|
+
obj
|
|
4502
|
+
);
|
|
4503
|
+
}
|
|
4504
|
+
/**
|
|
4505
|
+
* One operation with its full parameter/request/response schemas.
|
|
4506
|
+
*
|
|
4507
|
+
* @param operationId Spec-native operation id. May contain slashes
|
|
4508
|
+
* (GitHub's `repos/get`) — the backend route uses a `:path`
|
|
4509
|
+
* converter, so slashes are preserved as path structure and every
|
|
4510
|
+
* other segment character is strictly percent-encoded (RFC 3986,
|
|
4511
|
+
* byte-identical to the Python SDK's `quote`). Dot-segments (`.` /
|
|
4512
|
+
* `..`, including pre-encoded `%2e` spellings) and empty segments
|
|
4513
|
+
* are rejected (path-traversal guard).
|
|
4514
|
+
* @throws {AlterValueError} invalid `kind` / `providerId` / empty
|
|
4515
|
+
* `operationId` / an `operationId` containing a dot-segment or
|
|
4516
|
+
* empty path segment.
|
|
4517
|
+
* @throws {GrantNotFoundError} no active spec for this provider OR no
|
|
4518
|
+
* such operation (backend 404, `provider_spec_not_found` /
|
|
4519
|
+
* `operation_not_found` — disambiguate via `details.error`).
|
|
4520
|
+
* @throws {InsufficientScopeError} key lacks `providers:read`.
|
|
4521
|
+
* @throws {BackendError} backend reachability or response-shape failure.
|
|
4522
|
+
*/
|
|
4523
|
+
async getOperation(kind, providerId, operationId) {
|
|
4524
|
+
if (typeof operationId !== "string" || operationId.trim().length === 0) {
|
|
4525
|
+
throw new AlterValueError("operationId must be a non-empty string");
|
|
4526
|
+
}
|
|
4527
|
+
for (const segment of operationId.split("/")) {
|
|
4528
|
+
if (segment.length === 0) {
|
|
4529
|
+
throw new AlterValueError(
|
|
4530
|
+
"operationId must not contain empty path segments"
|
|
4531
|
+
);
|
|
4532
|
+
}
|
|
4533
|
+
const dotDecoded = segment.replace(/%2e/gi, ".");
|
|
4534
|
+
if (dotDecoded === "." || dotDecoded === "..") {
|
|
4535
|
+
throw new AlterValueError(
|
|
4536
|
+
'operationId must not contain path traversal segments ("." or "..")'
|
|
4537
|
+
);
|
|
4538
|
+
}
|
|
4539
|
+
}
|
|
4540
|
+
const encodedOperationId = operationId.split("/").map(_strictEncodeSegment).join("/");
|
|
4541
|
+
const path = `/sdk/provider-specs/${_requireKind(kind)}/${_strictEncodeSegment(
|
|
4542
|
+
_requireProviderId(providerId)
|
|
4543
|
+
)}/operations/${encodedOperationId}`;
|
|
4544
|
+
const data = await this.#getJson(path);
|
|
4545
|
+
const obj = _requireResponseObject2("SpecOperationDetail", data);
|
|
4546
|
+
return new SpecOperationDetail(
|
|
4547
|
+
obj
|
|
4548
|
+
);
|
|
4549
|
+
}
|
|
4550
|
+
/**
|
|
4551
|
+
* GET `sdkPath`, route non-2xx through the client's canonical typed
|
|
4552
|
+
* exception mapping, and parse the 2xx body as JSON.
|
|
4553
|
+
*/
|
|
4554
|
+
async #getJson(sdkPath) {
|
|
4555
|
+
const response = await this.#vault._authedBackendRequest("GET", sdkPath);
|
|
4556
|
+
if (!response.ok) {
|
|
4557
|
+
await this.#vault._handleErrorResponse(response);
|
|
4558
|
+
}
|
|
4559
|
+
try {
|
|
4560
|
+
return await response.json();
|
|
4561
|
+
} catch (e) {
|
|
4562
|
+
throw new BackendError(
|
|
4563
|
+
`${sdkPath.split("?")[0]} returned non-JSON response: ${e instanceof Error ? e.message : String(e)}`,
|
|
4564
|
+
{ status_code: response.status }
|
|
4565
|
+
);
|
|
4566
|
+
}
|
|
4567
|
+
}
|
|
4568
|
+
};
|
|
4569
|
+
function _requireResponseObject2(modelName, data) {
|
|
4570
|
+
if (data === null || typeof data !== "object" || Array.isArray(data)) {
|
|
4571
|
+
throw new BackendError(
|
|
4572
|
+
`Backend response did not match expected ${modelName} shape: response is not a JSON object`,
|
|
4573
|
+
{
|
|
4574
|
+
model: modelName,
|
|
4575
|
+
received_type: data === null ? "null" : Array.isArray(data) ? "array" : typeof data
|
|
4576
|
+
}
|
|
4577
|
+
);
|
|
4578
|
+
}
|
|
4579
|
+
return data;
|
|
4580
|
+
}
|
|
3959
4581
|
var ScopesNamespace = class {
|
|
3960
4582
|
#vault;
|
|
3961
4583
|
constructor(vault) {
|
|
@@ -4170,6 +4792,136 @@ async function ambientTraceparent(api) {
|
|
|
4170
4792
|
return void 0;
|
|
4171
4793
|
}
|
|
4172
4794
|
}
|
|
4795
|
+
var _alterContextStore = new AsyncLocalStorage();
|
|
4796
|
+
var RESERVED_CONTEXT_KEYS = /* @__PURE__ */ new Set([
|
|
4797
|
+
"agent",
|
|
4798
|
+
"parent_agent",
|
|
4799
|
+
"run_id",
|
|
4800
|
+
"thread_id",
|
|
4801
|
+
"tool",
|
|
4802
|
+
"tool_call_id",
|
|
4803
|
+
"framework"
|
|
4804
|
+
]);
|
|
4805
|
+
var MAX_RUN_METADATA_LENGTH = 4096;
|
|
4806
|
+
var MAX_RUN_METADATA_KEYS = 20;
|
|
4807
|
+
var MAX_RUN_METADATA_KEY_LENGTH = 64;
|
|
4808
|
+
var MAX_RUN_METADATA_VALUE_LENGTH = 512;
|
|
4809
|
+
function getCurrentAuditContext() {
|
|
4810
|
+
const value = _alterContextStore.getStore();
|
|
4811
|
+
if (value === void 0) {
|
|
4812
|
+
return null;
|
|
4813
|
+
}
|
|
4814
|
+
return { ...value };
|
|
4815
|
+
}
|
|
4816
|
+
function _stampAgentIdentity(ctx, agentId, options) {
|
|
4817
|
+
const ambientAgent = ctx.agent;
|
|
4818
|
+
if (!options.isAuthoritative && ambientAgent) {
|
|
4819
|
+
return;
|
|
4820
|
+
}
|
|
4821
|
+
if (ambientAgent && ambientAgent !== agentId) {
|
|
4822
|
+
ctx.parent_agent = ambientAgent;
|
|
4823
|
+
}
|
|
4824
|
+
ctx.agent = agentId;
|
|
4825
|
+
}
|
|
4826
|
+
function _validateRunMetadata(metadata) {
|
|
4827
|
+
if (metadata === null || typeof metadata !== "object" || Array.isArray(metadata)) {
|
|
4828
|
+
throw new AlterValueError(
|
|
4829
|
+
"trace() metadata must be an object of string key/value pairs"
|
|
4830
|
+
);
|
|
4831
|
+
}
|
|
4832
|
+
const keys = Object.keys(metadata);
|
|
4833
|
+
if (keys.length === 0) {
|
|
4834
|
+
return;
|
|
4835
|
+
}
|
|
4836
|
+
if (keys.length > MAX_RUN_METADATA_KEYS) {
|
|
4837
|
+
throw new AlterValueError(
|
|
4838
|
+
`trace() metadata has too many keys (${keys.length}); max is ${MAX_RUN_METADATA_KEYS}`
|
|
4839
|
+
);
|
|
4840
|
+
}
|
|
4841
|
+
const reserved = keys.filter((k) => RESERVED_CONTEXT_KEYS.has(k)).sort();
|
|
4842
|
+
if (reserved.length > 0) {
|
|
4843
|
+
throw new AlterValueError(
|
|
4844
|
+
`trace() metadata contains reserved key(s) ${JSON.stringify(reserved)}; these are owned by the SDK / framework bridges and cannot be set via metadata. Use the explicit \`runId\` / \`threadId\` kwargs for those. Reserved keys: ${JSON.stringify([...RESERVED_CONTEXT_KEYS].sort())}.`
|
|
4845
|
+
);
|
|
4846
|
+
}
|
|
4847
|
+
let totalSize = 0;
|
|
4848
|
+
for (const k of keys) {
|
|
4849
|
+
const keyBytes = Buffer.byteLength(k, "utf8");
|
|
4850
|
+
if (keyBytes > MAX_RUN_METADATA_KEY_LENGTH) {
|
|
4851
|
+
throw new AlterValueError(
|
|
4852
|
+
`trace() metadata key "${k.slice(0, 32)}" exceeds max length (${keyBytes} > ${MAX_RUN_METADATA_KEY_LENGTH} bytes)`
|
|
4853
|
+
);
|
|
4854
|
+
}
|
|
4855
|
+
const v = metadata[k];
|
|
4856
|
+
if (typeof v !== "string") {
|
|
4857
|
+
throw new AlterValueError(
|
|
4858
|
+
`trace() metadata value for key "${k}" must be a string, got ${typeof v}`
|
|
4859
|
+
);
|
|
4860
|
+
}
|
|
4861
|
+
const valueBytes = Buffer.byteLength(v, "utf8");
|
|
4862
|
+
if (valueBytes > MAX_RUN_METADATA_VALUE_LENGTH) {
|
|
4863
|
+
throw new AlterValueError(
|
|
4864
|
+
`trace() metadata value for key "${k}" exceeds max length (${valueBytes} > ${MAX_RUN_METADATA_VALUE_LENGTH} bytes)`
|
|
4865
|
+
);
|
|
4866
|
+
}
|
|
4867
|
+
totalSize += keyBytes + valueBytes;
|
|
4868
|
+
}
|
|
4869
|
+
if (totalSize > MAX_RUN_METADATA_LENGTH) {
|
|
4870
|
+
throw new AlterValueError(
|
|
4871
|
+
`trace() metadata total size ${totalSize} bytes exceeds ${MAX_RUN_METADATA_LENGTH} byte limit`
|
|
4872
|
+
);
|
|
4873
|
+
}
|
|
4874
|
+
}
|
|
4875
|
+
async function _withRunScope(options, callback) {
|
|
4876
|
+
const { agentId, runId, threadId, parent, metadata } = options;
|
|
4877
|
+
if (metadata !== void 0) {
|
|
4878
|
+
_validateRunMetadata(metadata);
|
|
4879
|
+
}
|
|
4880
|
+
const outer = _alterContextStore.getStore();
|
|
4881
|
+
const newContext = {};
|
|
4882
|
+
if (outer !== void 0) {
|
|
4883
|
+
for (const [k, v] of Object.entries(outer)) {
|
|
4884
|
+
if (!RESERVED_CONTEXT_KEYS.has(k)) {
|
|
4885
|
+
newContext[k] = v;
|
|
4886
|
+
}
|
|
4887
|
+
}
|
|
4888
|
+
if (outer.agent) {
|
|
4889
|
+
newContext.agent = outer.agent;
|
|
4890
|
+
if (outer.parent_agent) {
|
|
4891
|
+
newContext.parent_agent = outer.parent_agent;
|
|
4892
|
+
}
|
|
4893
|
+
}
|
|
4894
|
+
}
|
|
4895
|
+
_stampAgentIdentity(newContext, agentId, { isAuthoritative: true });
|
|
4896
|
+
if (parent === void 0) {
|
|
4897
|
+
} else if (typeof parent === "string" && parent.length > 0) {
|
|
4898
|
+
newContext.parent_agent = parent;
|
|
4899
|
+
} else {
|
|
4900
|
+
delete newContext.parent_agent;
|
|
4901
|
+
}
|
|
4902
|
+
if (runId !== void 0) {
|
|
4903
|
+
if (typeof runId !== "string") {
|
|
4904
|
+
throw new AlterValueError(`runId must be a string, got ${typeof runId}`);
|
|
4905
|
+
}
|
|
4906
|
+
newContext.run_id = runId;
|
|
4907
|
+
} else if (outer !== void 0 && outer.run_id !== void 0) {
|
|
4908
|
+
newContext.run_id = outer.run_id;
|
|
4909
|
+
}
|
|
4910
|
+
if (threadId !== void 0) {
|
|
4911
|
+
if (typeof threadId !== "string") {
|
|
4912
|
+
throw new AlterValueError(
|
|
4913
|
+
`threadId must be a string, got ${typeof threadId}`
|
|
4914
|
+
);
|
|
4915
|
+
}
|
|
4916
|
+
newContext.thread_id = threadId;
|
|
4917
|
+
} else if (outer !== void 0 && outer.thread_id !== void 0) {
|
|
4918
|
+
newContext.thread_id = outer.thread_id;
|
|
4919
|
+
}
|
|
4920
|
+
if (metadata !== void 0) {
|
|
4921
|
+
Object.assign(newContext, metadata);
|
|
4922
|
+
}
|
|
4923
|
+
return _alterContextStore.run(newContext, callback);
|
|
4924
|
+
}
|
|
4173
4925
|
function isRetryErrorInfoPayload(x) {
|
|
4174
4926
|
if (x == null || typeof x !== "object") {
|
|
4175
4927
|
return false;
|
|
@@ -4198,7 +4950,7 @@ function _extractAdditionalCredentials(token) {
|
|
|
4198
4950
|
return _additionalCredsStore.get(token);
|
|
4199
4951
|
}
|
|
4200
4952
|
var _fetch;
|
|
4201
|
-
var SDK_VERSION = "0.20.
|
|
4953
|
+
var SDK_VERSION = "0.20.1";
|
|
4202
4954
|
var SDK_USER_AGENT = `alter-sdk-node/${SDK_VERSION}`;
|
|
4203
4955
|
var AUTH_POLL_SERVER_WAIT_MS = 25e3;
|
|
4204
4956
|
var AUTH_POLL_HTTP_BUFFER_MS = 15e3;
|
|
@@ -4213,6 +4965,7 @@ var HTTP_UNAUTHORIZED = 401;
|
|
|
4213
4965
|
var HTTP_BAD_GATEWAY = 502;
|
|
4214
4966
|
var HTTP_INTERNAL_SERVER_ERROR = 500;
|
|
4215
4967
|
var HTTP_SERVICE_UNAVAILABLE = 503;
|
|
4968
|
+
var HTTP_TOO_MANY_REQUESTS = 429;
|
|
4216
4969
|
var MAX_SCOPE_CONSTRAINT_ATOM_LENGTH = 512;
|
|
4217
4970
|
var MAX_SCOPE_CONSTRAINT_ATOMS = 100;
|
|
4218
4971
|
var SCOPE_TOKEN_PATTERN = /^[\x21\x23-\x5B\x5D-\x7E]+$/;
|
|
@@ -4984,7 +5737,10 @@ request-rule:${effectiveRequestRule}`;
|
|
|
4984
5737
|
if (errorCode === "grant_expired") {
|
|
4985
5738
|
throw new GrantExpiredError(
|
|
4986
5739
|
errorData.message ?? "Grant expired per TTL policy",
|
|
4987
|
-
errorData.details
|
|
5740
|
+
errorData.details,
|
|
5741
|
+
typeof errorData.provider_id === "string" ? errorData.provider_id : void 0,
|
|
5742
|
+
typeof errorData.agent_id === "string" ? errorData.agent_id : void 0,
|
|
5743
|
+
typeof errorData.app_user_id === "string" ? errorData.app_user_id : void 0
|
|
4988
5744
|
);
|
|
4989
5745
|
}
|
|
4990
5746
|
if (errorCode === "grant_revoked") {
|
|
@@ -5029,6 +5785,22 @@ request-rule:${effectiveRequestRule}`;
|
|
|
5029
5785
|
details
|
|
5030
5786
|
);
|
|
5031
5787
|
}
|
|
5788
|
+
if (errorCode === "step_up_required") {
|
|
5789
|
+
const rawAge = errorData.max_session_age_seconds;
|
|
5790
|
+
throw new StepUpRequiredError(
|
|
5791
|
+
errorData.message ?? "This request requires a fresh (stepped-up) session \u2014 re-authenticate the user and retry.",
|
|
5792
|
+
// Number.isInteger mirrors the Python int-only guard
|
|
5793
|
+
// (fractional/bool/absent → undefined).
|
|
5794
|
+
Number.isInteger(rawAge) ? rawAge : void 0,
|
|
5795
|
+
errorData
|
|
5796
|
+
);
|
|
5797
|
+
}
|
|
5798
|
+
if (errorCode === "redact_discharge_failed") {
|
|
5799
|
+
throw new RedactDischargeFailedError(
|
|
5800
|
+
errorData.message ?? "The redact obligation could not be applied to the request body, so the request was denied.",
|
|
5801
|
+
errorData
|
|
5802
|
+
);
|
|
5803
|
+
}
|
|
5032
5804
|
throw new PolicyViolationError(
|
|
5033
5805
|
errorData.message ?? "Access denied by policy",
|
|
5034
5806
|
errorCode,
|
|
@@ -5167,6 +5939,20 @@ request-rule:${effectiveRequestRule}`;
|
|
|
5167
5939
|
errorData
|
|
5168
5940
|
);
|
|
5169
5941
|
}
|
|
5942
|
+
if (response.status === HTTP_TOO_MANY_REQUESTS) {
|
|
5943
|
+
const errorData = await __VaultClient.#safeParseJson(response);
|
|
5944
|
+
if (errorData.error === "quota_exceeded") {
|
|
5945
|
+
const retryAfterRaw = errorData.retry_after;
|
|
5946
|
+
throw new QuotaExceededError(
|
|
5947
|
+
errorData.message ?? "Request quota exceeded for the current window.",
|
|
5948
|
+
// The backend always emits an int; anything else (bool,
|
|
5949
|
+
// fractional number, string) degrades to undefined — parity with
|
|
5950
|
+
// the Python isinstance(int)-and-not-bool guard.
|
|
5951
|
+
Number.isInteger(retryAfterRaw) ? retryAfterRaw : void 0,
|
|
5952
|
+
errorData
|
|
5953
|
+
);
|
|
5954
|
+
}
|
|
5955
|
+
}
|
|
5170
5956
|
if (response.status === HTTP_INTERNAL_SERVER_ERROR || response.status === HTTP_SERVICE_UNAVAILABLE) {
|
|
5171
5957
|
const errorData = await __VaultClient.#safeParseJson(response);
|
|
5172
5958
|
throw new BackendError(
|
|
@@ -5536,7 +6322,7 @@ request-rule:${effectiveRequestRule}`;
|
|
|
5536
6322
|
}
|
|
5537
6323
|
const effectiveGrantId = grantId ?? null;
|
|
5538
6324
|
let currentUrl = url;
|
|
5539
|
-
const context = options?.context;
|
|
6325
|
+
const context = options?.context ?? getCurrentAuditContext() ?? void 0;
|
|
5540
6326
|
const methodStr = String(method).toUpperCase();
|
|
5541
6327
|
const urlLower = currentUrl.toLowerCase();
|
|
5542
6328
|
if (!ALLOWED_URL_SCHEMES.some((scheme) => urlLower.startsWith(scheme))) {
|
|
@@ -5989,7 +6775,9 @@ request-rule:${effectiveRequestRule}`;
|
|
|
5989
6775
|
*/
|
|
5990
6776
|
async _authedBackendRequest(method, sdkPath, body, extraHeaders) {
|
|
5991
6777
|
this.#assertNotClosed();
|
|
5992
|
-
const actorHeaders = await this.#getActorRequestHeaders(
|
|
6778
|
+
const actorHeaders = await this.#getActorRequestHeaders(
|
|
6779
|
+
getCurrentAuditContext() ?? void 0
|
|
6780
|
+
);
|
|
5993
6781
|
const bodyStr = body !== void 0 ? JSON.stringify(body) : void 0;
|
|
5994
6782
|
const hmac = this.#computeHmacHeaders(
|
|
5995
6783
|
method.toUpperCase(),
|
|
@@ -6099,6 +6887,19 @@ request-rule:${effectiveRequestRule}`;
|
|
|
6099
6887
|
return this.#oauthProvidersCache;
|
|
6100
6888
|
}
|
|
6101
6889
|
#oauthProvidersCache;
|
|
6890
|
+
/**
|
|
6891
|
+
* Provider API spec discovery: `vault.providerSpecs.list()` /
|
|
6892
|
+
* `.get()` / `.listOperations()` / `.getOperation()`.
|
|
6893
|
+
*
|
|
6894
|
+
* See `provider-specs.ts` for the full API surface.
|
|
6895
|
+
*/
|
|
6896
|
+
get providerSpecs() {
|
|
6897
|
+
if (this.#providerSpecsCache === void 0) {
|
|
6898
|
+
this.#providerSpecsCache = new ProviderSpecsNamespace(this);
|
|
6899
|
+
}
|
|
6900
|
+
return this.#providerSpecsCache;
|
|
6901
|
+
}
|
|
6902
|
+
#providerSpecsCache;
|
|
6102
6903
|
/**
|
|
6103
6904
|
* Scope catalog discovery: `vault.scopes.list()`.
|
|
6104
6905
|
*/
|
|
@@ -6142,13 +6943,11 @@ request-rule:${effectiveRequestRule}`;
|
|
|
6142
6943
|
* use the proxy surface, which carries the request method. Endpoints cannot
|
|
6143
6944
|
* be scoped by a rule (the URL is not a matchable attribute).
|
|
6144
6945
|
*
|
|
6145
|
-
* HITL
|
|
6146
|
-
* per-request rule is
|
|
6147
|
-
* execution
|
|
6148
|
-
*
|
|
6149
|
-
*
|
|
6150
|
-
* callers attenuating an approval-required flow should prefer a stored rule
|
|
6151
|
-
* when the constraint must hold at execute time.
|
|
6946
|
+
* HITL behavior: when a request is frozen for human approval (202), the
|
|
6947
|
+
* per-request rule is snapshotted on the approval and RE-APPLIED on the
|
|
6948
|
+
* deferred post-approval execution, so the same attenuation the caller sent
|
|
6949
|
+
* still binds the re-run (a corrupt snapshot fails closed). The rule can
|
|
6950
|
+
* only narrow, never widen.
|
|
6152
6951
|
*
|
|
6153
6952
|
* @example
|
|
6154
6953
|
* ```typescript
|
|
@@ -6365,7 +7164,9 @@ request-rule:${effectiveRequestRule}`;
|
|
|
6365
7164
|
requestBody.ttl_seconds = ttlSeconds;
|
|
6366
7165
|
}
|
|
6367
7166
|
const bodyStr = JSON.stringify(requestBody);
|
|
6368
|
-
const actorHeaders = await this.#getActorRequestHeaders(
|
|
7167
|
+
const actorHeaders = await this.#getActorRequestHeaders(
|
|
7168
|
+
getCurrentAuditContext() ?? void 0
|
|
7169
|
+
);
|
|
6369
7170
|
const hmacHeaders = this.#computeHmacHeaders("POST", sdkPath, bodyStr);
|
|
6370
7171
|
let response;
|
|
6371
7172
|
try {
|
|
@@ -6667,6 +7468,14 @@ request-rule:${effectiveRequestRule}`;
|
|
|
6667
7468
|
if (options.requiredScopes) {
|
|
6668
7469
|
sessionBody.required_scopes = options.requiredScopes;
|
|
6669
7470
|
}
|
|
7471
|
+
if (options.switchAccount !== void 0) {
|
|
7472
|
+
if (typeof options.switchAccount !== "boolean") {
|
|
7473
|
+
throw new AlterValueError("switchAccount must be a boolean");
|
|
7474
|
+
}
|
|
7475
|
+
if (options.switchAccount) {
|
|
7476
|
+
sessionBody.switch_account = true;
|
|
7477
|
+
}
|
|
7478
|
+
}
|
|
6670
7479
|
if (options.requestedGrant !== void 0) {
|
|
6671
7480
|
const rg = options.requestedGrant;
|
|
6672
7481
|
if (rg === null || typeof rg !== "object" || Array.isArray(rg)) {
|
|
@@ -7778,7 +8587,7 @@ request-rule:${effectiveRequestRule}`;
|
|
|
7778
8587
|
);
|
|
7779
8588
|
}
|
|
7780
8589
|
const actorHeaders = await this.#getActorRequestHeaders(
|
|
7781
|
-
args.context,
|
|
8590
|
+
args.context ?? getCurrentAuditContext() ?? void 0,
|
|
7782
8591
|
args.caller
|
|
7783
8592
|
);
|
|
7784
8593
|
const hmacHeaders = this.#computeHmacHeaders("POST", sdkPath, bodyStr);
|
|
@@ -8301,129 +9110,6 @@ async function _assertIdentity(client, body) {
|
|
|
8301
9110
|
);
|
|
8302
9111
|
}
|
|
8303
9112
|
}
|
|
8304
|
-
var _alterContextStore = new AsyncLocalStorage();
|
|
8305
|
-
var RESERVED_CONTEXT_KEYS = /* @__PURE__ */ new Set([
|
|
8306
|
-
"agent",
|
|
8307
|
-
"parent_agent",
|
|
8308
|
-
"run_id",
|
|
8309
|
-
"thread_id",
|
|
8310
|
-
"tool",
|
|
8311
|
-
"tool_call_id",
|
|
8312
|
-
"framework"
|
|
8313
|
-
]);
|
|
8314
|
-
var MAX_RUN_METADATA_LENGTH = 4096;
|
|
8315
|
-
var MAX_RUN_METADATA_KEYS = 20;
|
|
8316
|
-
var MAX_RUN_METADATA_KEY_LENGTH = 64;
|
|
8317
|
-
var MAX_RUN_METADATA_VALUE_LENGTH = 512;
|
|
8318
|
-
function _stampAgentIdentity(ctx, agentId, options) {
|
|
8319
|
-
const ambientAgent = ctx.agent;
|
|
8320
|
-
if (!options.isAuthoritative && ambientAgent) {
|
|
8321
|
-
return;
|
|
8322
|
-
}
|
|
8323
|
-
if (ambientAgent && ambientAgent !== agentId) {
|
|
8324
|
-
ctx.parent_agent = ambientAgent;
|
|
8325
|
-
}
|
|
8326
|
-
ctx.agent = agentId;
|
|
8327
|
-
}
|
|
8328
|
-
function _validateRunMetadata(metadata) {
|
|
8329
|
-
if (metadata === null || typeof metadata !== "object" || Array.isArray(metadata)) {
|
|
8330
|
-
throw new AlterValueError(
|
|
8331
|
-
"trace() metadata must be an object of string key/value pairs"
|
|
8332
|
-
);
|
|
8333
|
-
}
|
|
8334
|
-
const keys = Object.keys(metadata);
|
|
8335
|
-
if (keys.length === 0) {
|
|
8336
|
-
return;
|
|
8337
|
-
}
|
|
8338
|
-
if (keys.length > MAX_RUN_METADATA_KEYS) {
|
|
8339
|
-
throw new AlterValueError(
|
|
8340
|
-
`trace() metadata has too many keys (${keys.length}); max is ${MAX_RUN_METADATA_KEYS}`
|
|
8341
|
-
);
|
|
8342
|
-
}
|
|
8343
|
-
const reserved = keys.filter((k) => RESERVED_CONTEXT_KEYS.has(k)).sort();
|
|
8344
|
-
if (reserved.length > 0) {
|
|
8345
|
-
throw new AlterValueError(
|
|
8346
|
-
`trace() metadata contains reserved key(s) ${JSON.stringify(reserved)}; these are owned by the SDK / framework bridges and cannot be set via metadata. Use the explicit \`runId\` / \`threadId\` kwargs for those. Reserved keys: ${JSON.stringify([...RESERVED_CONTEXT_KEYS].sort())}.`
|
|
8347
|
-
);
|
|
8348
|
-
}
|
|
8349
|
-
let totalSize = 0;
|
|
8350
|
-
for (const k of keys) {
|
|
8351
|
-
const keyBytes = Buffer.byteLength(k, "utf8");
|
|
8352
|
-
if (keyBytes > MAX_RUN_METADATA_KEY_LENGTH) {
|
|
8353
|
-
throw new AlterValueError(
|
|
8354
|
-
`trace() metadata key "${k.slice(0, 32)}" exceeds max length (${keyBytes} > ${MAX_RUN_METADATA_KEY_LENGTH} bytes)`
|
|
8355
|
-
);
|
|
8356
|
-
}
|
|
8357
|
-
const v = metadata[k];
|
|
8358
|
-
if (typeof v !== "string") {
|
|
8359
|
-
throw new AlterValueError(
|
|
8360
|
-
`trace() metadata value for key "${k}" must be a string, got ${typeof v}`
|
|
8361
|
-
);
|
|
8362
|
-
}
|
|
8363
|
-
const valueBytes = Buffer.byteLength(v, "utf8");
|
|
8364
|
-
if (valueBytes > MAX_RUN_METADATA_VALUE_LENGTH) {
|
|
8365
|
-
throw new AlterValueError(
|
|
8366
|
-
`trace() metadata value for key "${k}" exceeds max length (${valueBytes} > ${MAX_RUN_METADATA_VALUE_LENGTH} bytes)`
|
|
8367
|
-
);
|
|
8368
|
-
}
|
|
8369
|
-
totalSize += keyBytes + valueBytes;
|
|
8370
|
-
}
|
|
8371
|
-
if (totalSize > MAX_RUN_METADATA_LENGTH) {
|
|
8372
|
-
throw new AlterValueError(
|
|
8373
|
-
`trace() metadata total size ${totalSize} bytes exceeds ${MAX_RUN_METADATA_LENGTH} byte limit`
|
|
8374
|
-
);
|
|
8375
|
-
}
|
|
8376
|
-
}
|
|
8377
|
-
async function _withRunScope(options, callback) {
|
|
8378
|
-
const { agentId, runId, threadId, parent, metadata } = options;
|
|
8379
|
-
if (metadata !== void 0) {
|
|
8380
|
-
_validateRunMetadata(metadata);
|
|
8381
|
-
}
|
|
8382
|
-
const outer = _alterContextStore.getStore();
|
|
8383
|
-
const newContext = {};
|
|
8384
|
-
if (outer !== void 0) {
|
|
8385
|
-
for (const [k, v] of Object.entries(outer)) {
|
|
8386
|
-
if (!RESERVED_CONTEXT_KEYS.has(k)) {
|
|
8387
|
-
newContext[k] = v;
|
|
8388
|
-
}
|
|
8389
|
-
}
|
|
8390
|
-
if (outer.agent) {
|
|
8391
|
-
newContext.agent = outer.agent;
|
|
8392
|
-
if (outer.parent_agent) {
|
|
8393
|
-
newContext.parent_agent = outer.parent_agent;
|
|
8394
|
-
}
|
|
8395
|
-
}
|
|
8396
|
-
}
|
|
8397
|
-
_stampAgentIdentity(newContext, agentId, { isAuthoritative: true });
|
|
8398
|
-
if (parent === void 0) {
|
|
8399
|
-
} else if (typeof parent === "string" && parent.length > 0) {
|
|
8400
|
-
newContext.parent_agent = parent;
|
|
8401
|
-
} else {
|
|
8402
|
-
delete newContext.parent_agent;
|
|
8403
|
-
}
|
|
8404
|
-
if (runId !== void 0) {
|
|
8405
|
-
if (typeof runId !== "string") {
|
|
8406
|
-
throw new AlterValueError(`runId must be a string, got ${typeof runId}`);
|
|
8407
|
-
}
|
|
8408
|
-
newContext.run_id = runId;
|
|
8409
|
-
} else if (outer !== void 0 && outer.run_id !== void 0) {
|
|
8410
|
-
newContext.run_id = outer.run_id;
|
|
8411
|
-
}
|
|
8412
|
-
if (threadId !== void 0) {
|
|
8413
|
-
if (typeof threadId !== "string") {
|
|
8414
|
-
throw new AlterValueError(
|
|
8415
|
-
`threadId must be a string, got ${typeof threadId}`
|
|
8416
|
-
);
|
|
8417
|
-
}
|
|
8418
|
-
newContext.thread_id = threadId;
|
|
8419
|
-
} else if (outer !== void 0 && outer.thread_id !== void 0) {
|
|
8420
|
-
newContext.thread_id = outer.thread_id;
|
|
8421
|
-
}
|
|
8422
|
-
if (metadata !== void 0) {
|
|
8423
|
-
Object.assign(newContext, metadata);
|
|
8424
|
-
}
|
|
8425
|
-
return _alterContextStore.run(newContext, callback);
|
|
8426
|
-
}
|
|
8427
9113
|
function _generateLocalRunUuid() {
|
|
8428
9114
|
const cryptoGlobal = globalThis.crypto;
|
|
8429
9115
|
if (cryptoGlobal?.randomUUID) {
|
|
@@ -8545,6 +9231,14 @@ var Agent = class _Agent {
|
|
|
8545
9231
|
get oauthProviders() {
|
|
8546
9232
|
return this.#client.oauthProviders;
|
|
8547
9233
|
}
|
|
9234
|
+
/**
|
|
9235
|
+
* Provider API spec discovery (in-band operation discovery):
|
|
9236
|
+
* `agent.providerSpecs.list()` / `.get()` / `.listOperations()` /
|
|
9237
|
+
* `.getOperation()`. Requires the `providers:read` key scope.
|
|
9238
|
+
*/
|
|
9239
|
+
get providerSpecs() {
|
|
9240
|
+
return this.#client.providerSpecs;
|
|
9241
|
+
}
|
|
8548
9242
|
/**
|
|
8549
9243
|
* User-defined trace spans: `agent.spans.emit()`.
|
|
8550
9244
|
*
|
|
@@ -8604,10 +9298,40 @@ var Agent = class _Agent {
|
|
|
8604
9298
|
"App.getAgent(...) is the impersonation surface, NOT delegation-scoped access. request({ grantId }) on this Agent uses the operator credentials and CAN reach grants the named agent has no delegation for. Use new Agent({ apiKey: 'alter_ak_...' }) for true delegation-scoped access, or request({ provider, userToken }) here to route through the agent's delegations."
|
|
8605
9299
|
);
|
|
8606
9300
|
}
|
|
8607
|
-
|
|
9301
|
+
try {
|
|
9302
|
+
return await this.#client.request(method, url, options);
|
|
9303
|
+
} catch (e) {
|
|
9304
|
+
throw this.#toAgentGrantError(e);
|
|
9305
|
+
}
|
|
8608
9306
|
}
|
|
8609
9307
|
async proxyRequest(...args) {
|
|
8610
|
-
|
|
9308
|
+
try {
|
|
9309
|
+
return await this.#client.proxyRequest(...args);
|
|
9310
|
+
} catch (e) {
|
|
9311
|
+
throw this.#toAgentGrantError(e);
|
|
9312
|
+
}
|
|
9313
|
+
}
|
|
9314
|
+
// On the agent path a bare `grant_not_found` 404 is ambiguous — the grant
|
|
9315
|
+
// may not exist, may not be delegated to THIS agent, or a user/base grant_id
|
|
9316
|
+
// was passed where the agent's own delegation id was required (an agent can
|
|
9317
|
+
// only address its own delegation by id). The generic
|
|
9318
|
+
// `GrantNotFoundError` message reads as "stale grant → reconnect", which
|
|
9319
|
+
// sends integrators into a loop. Re-map it to the actionable
|
|
9320
|
+
// `AgentDelegationMissingError` (a `GrantNotFoundError` subclass, so existing
|
|
9321
|
+
// `instanceof GrantNotFoundError` catches still fire). Leave every other
|
|
9322
|
+
// error untouched — including the already-typed `NoDelegatedGrantError`
|
|
9323
|
+
// (a distinct class, not a subclass) and an already-remapped subclass.
|
|
9324
|
+
#toAgentGrantError(e) {
|
|
9325
|
+
if (e instanceof GrantNotFoundError && !(e instanceof AgentDelegationMissingError)) {
|
|
9326
|
+
return new AgentDelegationMissingError(
|
|
9327
|
+
"No grant is resolvable for this agent by the grant_id supplied. The grant may exist but not be delegated to this agent, or a user/base grant_id was passed where the agent's own delegation id is required (get it from agent.listGrants). Run agent.createConnectSession(...) to delegate this agent, or resolve by provider (omit grantId) to route through the agent's existing delegations.",
|
|
9328
|
+
e.details,
|
|
9329
|
+
e.providerId,
|
|
9330
|
+
e.agentId,
|
|
9331
|
+
e.appUserId
|
|
9332
|
+
);
|
|
9333
|
+
}
|
|
9334
|
+
return e;
|
|
8611
9335
|
}
|
|
8612
9336
|
// ── trace — audit-context scope for nested calls (TS callback shape) ───
|
|
8613
9337
|
/**
|
|
@@ -8870,6 +9594,8 @@ var App = class _App {
|
|
|
8870
9594
|
keys;
|
|
8871
9595
|
/** OAuth provider catalog discovery. See {@link OAuthProvidersNamespace}. */
|
|
8872
9596
|
oauthProviders;
|
|
9597
|
+
/** Provider API spec discovery. See {@link ProviderSpecsNamespace}. */
|
|
9598
|
+
providerSpecs;
|
|
8873
9599
|
/** Scope catalog discovery. See {@link ScopesNamespace}. */
|
|
8874
9600
|
scopes;
|
|
8875
9601
|
/** User-defined trace spans. See {@link SpansNamespace}. */
|
|
@@ -8890,6 +9616,7 @@ var App = class _App {
|
|
|
8890
9616
|
this.agents = this.#client.agents;
|
|
8891
9617
|
this.keys = this.#client.keys;
|
|
8892
9618
|
this.oauthProviders = this.#client.oauthProviders;
|
|
9619
|
+
this.providerSpecs = this.#client.providerSpecs;
|
|
8893
9620
|
this.scopes = this.#client.scopes;
|
|
8894
9621
|
this.spans = this.#client.spans;
|
|
8895
9622
|
return;
|
|
@@ -8908,6 +9635,7 @@ var App = class _App {
|
|
|
8908
9635
|
this.agents = this.#client.agents;
|
|
8909
9636
|
this.keys = this.#client.keys;
|
|
8910
9637
|
this.oauthProviders = this.#client.oauthProviders;
|
|
9638
|
+
this.providerSpecs = this.#client.providerSpecs;
|
|
8911
9639
|
this.scopes = this.#client.scopes;
|
|
8912
9640
|
this.spans = this.#client.spans;
|
|
8913
9641
|
_appCredentials.set(this, {
|
|
@@ -9717,7 +10445,7 @@ var DEFAULT_BASE_URL = "https://backend.alterauth.com";
|
|
|
9717
10445
|
var PAT_API_PREFIX = "/api/v1/dev-portal";
|
|
9718
10446
|
var HTTP_ERROR_THRESHOLD = 400;
|
|
9719
10447
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
9720
|
-
var CLI_VERSION = "0.
|
|
10448
|
+
var CLI_VERSION = "0.6.0";
|
|
9721
10449
|
var USER_AGENT = buildUserAgent();
|
|
9722
10450
|
function buildUserAgent() {
|
|
9723
10451
|
let osTag = "";
|
|
@@ -9885,7 +10613,8 @@ var DashboardClient = class {
|
|
|
9885
10613
|
keys;
|
|
9886
10614
|
/** Managed-agent CRUD (CLI `alter agents` subcommands). */
|
|
9887
10615
|
agents;
|
|
9888
|
-
/** OAuth provider configs
|
|
10616
|
+
/** OAuth provider configs + the provider API spec store
|
|
10617
|
+
* (CLI `alter providers` subcommands). */
|
|
9889
10618
|
providers;
|
|
9890
10619
|
/** Identity-provider create + discover (CLI `alter identity-providers`
|
|
9891
10620
|
* subcommands). Requires ``dashboard_identity_providers:create``. */
|
|
@@ -10569,6 +11298,13 @@ function isProviderCatalogEntry(value) {
|
|
|
10569
11298
|
const v = value;
|
|
10570
11299
|
return typeof v.id === "string" && typeof v.name === "string" && typeof v.display_name === "string" && (v.category === null || typeof v.category === "string") && (v.supports_refresh === null || typeof v.supports_refresh === "boolean") && (v.supports_pkce === null || typeof v.supports_pkce === "boolean") && typeof v.status === "string";
|
|
10571
11300
|
}
|
|
11301
|
+
function isProviderSpecMeta(value) {
|
|
11302
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
11303
|
+
return false;
|
|
11304
|
+
}
|
|
11305
|
+
const v = value;
|
|
11306
|
+
return typeof v.provider_kind === "string" && typeof v.provider_id === "string" && typeof v.version === "number" && typeof v.provenance === "string" && (v.source_url === null || typeof v.source_url === "string") && typeof v.content_hash === "string" && typeof v.operation_count === "number" && typeof v.fetched_at === "string" && typeof v.changed_at === "string" && (v.title === null || typeof v.title === "string") && (v.spec_version === null || typeof v.spec_version === "string");
|
|
11307
|
+
}
|
|
10572
11308
|
var ProvidersNamespace = class {
|
|
10573
11309
|
#client;
|
|
10574
11310
|
constructor(client) {
|
|
@@ -10679,6 +11415,96 @@ var ProvidersNamespace = class {
|
|
|
10679
11415
|
query ? { query } : {}
|
|
10680
11416
|
);
|
|
10681
11417
|
}
|
|
11418
|
+
// ── Provider API spec store (in-band operation discovery) ──────────
|
|
11419
|
+
//
|
|
11420
|
+
// Read-only catalog data served by ``/provider-specs`` on the
|
|
11421
|
+
// dev-portal surface. Any authenticated PAT — no ``dashboard_*``
|
|
11422
|
+
// scope gate (the specs are public provider documentation, the gate
|
|
11423
|
+
// is just "be a signed-in operator").
|
|
11424
|
+
/**
|
|
11425
|
+
* List every provider's active spec metadata (the freshness /
|
|
11426
|
+
* provenance dashboard). ``GET /provider-specs[?kind=oauth|managed]``.
|
|
11427
|
+
*/
|
|
11428
|
+
async listSpecs(options = {}) {
|
|
11429
|
+
const query = {};
|
|
11430
|
+
if (options.kind !== void 0) query.kind = options.kind;
|
|
11431
|
+
const body = await this.#client._call(
|
|
11432
|
+
"GET",
|
|
11433
|
+
"/provider-specs",
|
|
11434
|
+
"providers.list_specs",
|
|
11435
|
+
Object.keys(query).length > 0 ? { query } : {}
|
|
11436
|
+
);
|
|
11437
|
+
const dict = expectDict(body, "providers.list_specs", 200);
|
|
11438
|
+
if (!Array.isArray(dict.items) || typeof dict.total !== "number") {
|
|
11439
|
+
throw makeBackendError(
|
|
11440
|
+
"Dashboard client received an unexpected response shape for providers.list_specs: expected { items, total }",
|
|
11441
|
+
200,
|
|
11442
|
+
body
|
|
11443
|
+
);
|
|
11444
|
+
}
|
|
11445
|
+
for (const [i, row] of dict.items.entries()) {
|
|
11446
|
+
if (!isProviderSpecMeta(row)) {
|
|
11447
|
+
throw makeBackendError(
|
|
11448
|
+
`Dashboard client received an unexpected provider-spec row at index ${i}: expected { provider_kind, provider_id, version, provenance, source_url, content_hash, operation_count, fetched_at, changed_at, title, spec_version }`,
|
|
11449
|
+
200,
|
|
11450
|
+
row
|
|
11451
|
+
);
|
|
11452
|
+
}
|
|
11453
|
+
}
|
|
11454
|
+
return dict;
|
|
11455
|
+
}
|
|
11456
|
+
/**
|
|
11457
|
+
* Page one provider's operations from its active spec.
|
|
11458
|
+
* ``GET /provider-specs/{kind}/{provider_id}/operations``.
|
|
11459
|
+
* ``search`` filters server-side; ``limit`` is server-clamped to
|
|
11460
|
+
* 1–500 (default 100).
|
|
11461
|
+
*/
|
|
11462
|
+
async listSpecOperations(kind, providerId, options = {}) {
|
|
11463
|
+
const k = encodePathParam(kind, "kind");
|
|
11464
|
+
const provider = encodePathParam(providerId, "providerId");
|
|
11465
|
+
const query = {};
|
|
11466
|
+
if (options.search !== void 0) query.search = options.search;
|
|
11467
|
+
if (options.limit !== void 0) query.limit = String(options.limit);
|
|
11468
|
+
if (options.offset !== void 0) query.offset = String(options.offset);
|
|
11469
|
+
const body = await this.#client._call(
|
|
11470
|
+
"GET",
|
|
11471
|
+
`/provider-specs/${k}/${provider}/operations`,
|
|
11472
|
+
"providers.list_spec_operations",
|
|
11473
|
+
Object.keys(query).length > 0 ? { query } : {}
|
|
11474
|
+
);
|
|
11475
|
+
const page = expectEnvelope(body, "providers.list_spec_operations", 200);
|
|
11476
|
+
const spec = page.spec;
|
|
11477
|
+
if (!isProviderSpecMeta(spec)) {
|
|
11478
|
+
throw makeBackendError(
|
|
11479
|
+
"Dashboard client received an unexpected response shape for providers.list_spec_operations: envelope is missing the spec metadata",
|
|
11480
|
+
200,
|
|
11481
|
+
body
|
|
11482
|
+
);
|
|
11483
|
+
}
|
|
11484
|
+
return page;
|
|
11485
|
+
}
|
|
11486
|
+
/**
|
|
11487
|
+
* One operation with its full parameter/request/response schemas.
|
|
11488
|
+
* ``GET /provider-specs/{kind}/{provider_id}/operations/{operation_id}``.
|
|
11489
|
+
*
|
|
11490
|
+
* Operation ids may contain slashes (GitHub's ``repos/get``) — the
|
|
11491
|
+
* backend route uses a ``:path`` converter, so each SEGMENT of the id
|
|
11492
|
+
* is encoded individually and the literal ``/`` separators are kept.
|
|
11493
|
+
* ``encodePathParam`` per segment preserves the traversal guard: an
|
|
11494
|
+
* id smuggling ``..`` (or an empty segment) is rejected client-side
|
|
11495
|
+
* instead of URL-normalizing onto a different route.
|
|
11496
|
+
*/
|
|
11497
|
+
async getSpecOperation(kind, providerId, operationId) {
|
|
11498
|
+
const k = encodePathParam(kind, "kind");
|
|
11499
|
+
const provider = encodePathParam(providerId, "providerId");
|
|
11500
|
+
const op = operationId.split("/").map((segment) => encodePathParam(segment, "operationId")).join("/");
|
|
11501
|
+
const body = await this.#client._call(
|
|
11502
|
+
"GET",
|
|
11503
|
+
`/provider-specs/${k}/${provider}/operations/${op}`,
|
|
11504
|
+
"providers.get_spec_operation"
|
|
11505
|
+
);
|
|
11506
|
+
return expectDict(body, "providers.get_spec_operation", 200);
|
|
11507
|
+
}
|
|
10682
11508
|
};
|
|
10683
11509
|
var IdentityProvidersNamespace = class {
|
|
10684
11510
|
#client;
|
|
@@ -10689,7 +11515,7 @@ var IdentityProvidersNamespace = class {
|
|
|
10689
11515
|
* Configure an end-user identity provider for an app. Requires
|
|
10690
11516
|
* `dashboard_identity_providers:create`. ``create`` and ``webhooks`` are the
|
|
10691
11517
|
* TWO identity-provider carve-outs on the PAT surface (update / delete /
|
|
10692
|
-
* config-reads stay
|
|
11518
|
+
* config-reads stay session-only). ``create`` is additive and conflict-guarded
|
|
10693
11519
|
* (one IDP per app → the backend returns 409 if one already exists, so it can
|
|
10694
11520
|
* never re-point an already-trusted issuer) and fail-closed on OIDC
|
|
10695
11521
|
* discovery. BOTH ``create`` and ``webhooks`` (the latter returns the IDP
|
|
@@ -10738,7 +11564,7 @@ var IdentityProvidersNamespace = class {
|
|
|
10738
11564
|
}
|
|
10739
11565
|
// ── Webhook lifecycle — requires `dashboard_identity_providers:webhooks`
|
|
10740
11566
|
// (catalog v7). Per-IDP, recoverable; does NOT touch the issuer/claim
|
|
10741
|
-
// trust config (update/delete stay
|
|
11567
|
+
// trust config (update/delete stay session-only).
|
|
10742
11568
|
/** Enable webhook integration and return the signing secret (shown once). */
|
|
10743
11569
|
async enableWebhook(appId, providerId, options = {}) {
|
|
10744
11570
|
const app = encodePathParam(appId, "appId");
|
|
@@ -10970,6 +11796,32 @@ var ApprovalsNamespace = class {
|
|
|
10970
11796
|
);
|
|
10971
11797
|
return expectDict(body, "approvals.defaults", 200);
|
|
10972
11798
|
}
|
|
11799
|
+
/** List an app's HITL approvals (newest first), group-aware (N-of-N). */
|
|
11800
|
+
async list(appId, opts = {}) {
|
|
11801
|
+
const app = encodePathParam(appId, "appId");
|
|
11802
|
+
const params = new URLSearchParams();
|
|
11803
|
+
if (opts.status) params.set("status", opts.status);
|
|
11804
|
+
if (opts.limit !== void 0) params.set("limit", String(opts.limit));
|
|
11805
|
+
if (opts.offset !== void 0) params.set("offset", String(opts.offset));
|
|
11806
|
+
const qs = params.toString();
|
|
11807
|
+
const body = await this.#client._call(
|
|
11808
|
+
"GET",
|
|
11809
|
+
`/apps/${app}/approvals${qs ? `?${qs}` : ""}`,
|
|
11810
|
+
"approvals.list"
|
|
11811
|
+
);
|
|
11812
|
+
return expectDict(body, "approvals.list", 200);
|
|
11813
|
+
}
|
|
11814
|
+
/** One approval with its full gate breakdown + request-rule snapshot. */
|
|
11815
|
+
async get(appId, approvalId) {
|
|
11816
|
+
const app = encodePathParam(appId, "appId");
|
|
11817
|
+
const id = encodePathParam(approvalId, "approvalId");
|
|
11818
|
+
const body = await this.#client._call(
|
|
11819
|
+
"GET",
|
|
11820
|
+
`/apps/${app}/approvals/${id}`,
|
|
11821
|
+
"approvals.get"
|
|
11822
|
+
);
|
|
11823
|
+
return expectDict(body, "approvals.get", 200);
|
|
11824
|
+
}
|
|
10973
11825
|
};
|
|
10974
11826
|
var PolicyNamespace = class {
|
|
10975
11827
|
#client;
|
|
@@ -10978,7 +11830,7 @@ var PolicyNamespace = class {
|
|
|
10978
11830
|
}
|
|
10979
11831
|
// NOTE: ``getOrgKeyPolicy`` + ``updateOrgKeyPolicy`` are NOT exposed.
|
|
10980
11832
|
// ``GET /organizations/current/key-policy`` AND ``PATCH
|
|
10981
|
-
// /organizations/current/key-policy`` are BOTH
|
|
11833
|
+
// /organizations/current/key-policy`` are BOTH session-only — per
|
|
10982
11834
|
// CLAUDE.md "Destructive-Action Policy", org-wide config (reads
|
|
10983
11835
|
// AND writes) is dashboard-only. The read carve-out that briefly
|
|
10984
11836
|
// existed was rolled back because the response body is a
|
|
@@ -11037,9 +11889,16 @@ var PolicyNamespace = class {
|
|
|
11037
11889
|
}
|
|
11038
11890
|
/**
|
|
11039
11891
|
* Dry-run the app's custom policy rules for one request shape (grant / method /
|
|
11040
|
-
* endpoint / agent / client-ip / time) and return the
|
|
11041
|
-
* trace. A read with no side effects (the route
|
|
11042
|
-
* `dashboard_app_policy:read`. Foreign/unknown grant or
|
|
11892
|
+
* endpoint / agent / client-ip / time / operation / params) and return the
|
|
11893
|
+
* composed verdict + per-rule trace. A read with no side effects (the route
|
|
11894
|
+
* is a GET). Requires `dashboard_app_policy:read`. Foreign/unknown grant or
|
|
11895
|
+
* agent ids → 404.
|
|
11896
|
+
*
|
|
11897
|
+
* ``operationId`` is the explicit attested-operation override the backend
|
|
11898
|
+
* accepts as the ``operation`` query param (service-layer ``operation_id``);
|
|
11899
|
+
* ``simulatedParams`` is the simulated projected parameter bag, JSON-encoded
|
|
11900
|
+
* onto the ``params`` query param (service-layer ``simulated_params``) —
|
|
11901
|
+
* same wire shape the dashboard's policy explorer sends.
|
|
11043
11902
|
*/
|
|
11044
11903
|
async simulate(appId, options) {
|
|
11045
11904
|
const query = {
|
|
@@ -11052,6 +11911,10 @@ var PolicyNamespace = class {
|
|
|
11052
11911
|
if (options.agentId !== void 0) query.agent_id = options.agentId;
|
|
11053
11912
|
if (options.clientIp !== void 0) query.client_ip = options.clientIp;
|
|
11054
11913
|
if (options.at !== void 0) query.at = options.at;
|
|
11914
|
+
if (options.operationId !== void 0)
|
|
11915
|
+
query.operation = options.operationId;
|
|
11916
|
+
if (options.simulatedParams !== void 0)
|
|
11917
|
+
query.params = JSON.stringify(options.simulatedParams);
|
|
11055
11918
|
const out = await this.#client._call(
|
|
11056
11919
|
"GET",
|
|
11057
11920
|
`/apps/${encodePathParam(appId, "appId")}/policies/simulate`,
|
|
@@ -12007,6 +12870,7 @@ var isBoolean = (v) => typeof v === "boolean";
|
|
|
12007
12870
|
var isOptionalString = (v) => v === null || typeof v === "string";
|
|
12008
12871
|
var isOptionalNumber = (v) => v === null || typeof v === "number";
|
|
12009
12872
|
var isOptionalBoolean = (v) => v === null || v === void 0 || typeof v === "boolean";
|
|
12873
|
+
var isAbsentOrOptionalString = (v) => v === null || v === void 0 || typeof v === "string";
|
|
12010
12874
|
var isOptionalStringArray = (v) => v === null || v === void 0 || Array.isArray(v) && v.every((x) => typeof x === "string");
|
|
12011
12875
|
function extractArrayField(body, field, context) {
|
|
12012
12876
|
const value = body[field];
|
|
@@ -12731,6 +13595,18 @@ function surfaceApproverWarnings(row) {
|
|
|
12731
13595
|
);
|
|
12732
13596
|
}
|
|
12733
13597
|
}
|
|
13598
|
+
function surfaceCatalogWarnings(row) {
|
|
13599
|
+
if (typeof row !== "object" || row === null) return;
|
|
13600
|
+
const warnings = row.catalog_warnings;
|
|
13601
|
+
if (!Array.isArray(warnings)) return;
|
|
13602
|
+
for (const warning of warnings) {
|
|
13603
|
+
if (typeof warning !== "string" || warning.length === 0) continue;
|
|
13604
|
+
process.stderr.write(
|
|
13605
|
+
`alter: catalog warning \u2014 ${sanitizeStderrText(warning)} (save was allowed)
|
|
13606
|
+
`
|
|
13607
|
+
);
|
|
13608
|
+
}
|
|
13609
|
+
}
|
|
12734
13610
|
|
|
12735
13611
|
// src/commands/agents.ts
|
|
12736
13612
|
var AGENT_COLUMNS = [
|
|
@@ -13646,21 +14522,114 @@ function buildAppsCommand() {
|
|
|
13646
14522
|
|
|
13647
14523
|
// src/commands/approvals.ts
|
|
13648
14524
|
import { Command as Command4 } from "commander";
|
|
14525
|
+
var APPROVAL_STATUSES = [
|
|
14526
|
+
"pending",
|
|
14527
|
+
"approved",
|
|
14528
|
+
"executing",
|
|
14529
|
+
"denied",
|
|
14530
|
+
"expired",
|
|
14531
|
+
"executed",
|
|
14532
|
+
"failed"
|
|
14533
|
+
];
|
|
14534
|
+
var APPROVAL_ROW_CHECKS = {
|
|
14535
|
+
approval_id: isString,
|
|
14536
|
+
group_status: isString,
|
|
14537
|
+
gate_count: (v) => typeof v === "number",
|
|
14538
|
+
grant_id: isString,
|
|
14539
|
+
grant_type: isString,
|
|
14540
|
+
provider_id: isOptionalString,
|
|
14541
|
+
created_at: isString,
|
|
14542
|
+
expires_at: isString
|
|
14543
|
+
};
|
|
14544
|
+
var APPROVAL_LIST_COLUMNS = [
|
|
14545
|
+
{ label: "ID", get: (a) => a.approval_id },
|
|
14546
|
+
{ label: "GROUP", get: (a) => a.group_status },
|
|
14547
|
+
{ label: "GATES", get: (a) => String(a.gate_count) },
|
|
14548
|
+
// provider_id is null for managed-secret grants — fall back to the
|
|
14549
|
+
// grant family so the target column is never blank.
|
|
14550
|
+
{
|
|
14551
|
+
label: "PROVIDER",
|
|
14552
|
+
get: (a) => a.provider_id ?? a.grant_type,
|
|
14553
|
+
maxWidth: 20
|
|
14554
|
+
},
|
|
14555
|
+
{ label: "GRANT", get: (a) => a.grant_id, maxWidth: 36 },
|
|
14556
|
+
{ label: "REQUESTED", get: (a) => a.created_at, maxWidth: 24 },
|
|
14557
|
+
{ label: "EXPIRES", get: (a) => a.expires_at, maxWidth: 24 }
|
|
14558
|
+
];
|
|
13649
14559
|
function buildApprovalsCommand() {
|
|
13650
14560
|
const approvals = new Command4("approvals").description(
|
|
13651
|
-
"Inspect HITL approval configuration (read-only)"
|
|
14561
|
+
"Inspect HITL approvals and approval configuration (read-only)"
|
|
14562
|
+
);
|
|
14563
|
+
approvals.command("defaults").description("Show HITL approval deployment defaults and per-grant bounds").option(
|
|
14564
|
+
"--output <format>",
|
|
14565
|
+
"Output format: json|jsonl|table (default: json)",
|
|
14566
|
+
"json"
|
|
14567
|
+
).action(async (options) => {
|
|
14568
|
+
const format = coerceOutputFormat(options.output);
|
|
14569
|
+
await withClient(async (client) => {
|
|
14570
|
+
const row = await client.approvals.defaults();
|
|
14571
|
+
emit2(format, row);
|
|
14572
|
+
});
|
|
14573
|
+
});
|
|
14574
|
+
approvals.command("list").description(
|
|
14575
|
+
"List an app's HITL approvals (newest first), with the N-of-N group status per row"
|
|
14576
|
+
).option("--app <id>", "App ID (or ALTER_APP_ID)").option(
|
|
14577
|
+
"--status <status>",
|
|
14578
|
+
`Filter by row status (${APPROVAL_STATUSES.join("|")})`
|
|
14579
|
+
).option(
|
|
14580
|
+
"--limit <n>",
|
|
14581
|
+
"Page size (1-200, default 50)",
|
|
14582
|
+
parseBoundedInt("--limit", 1, 200)
|
|
14583
|
+
).option("--offset <n>", "Page offset", parseNonNegativeInt("--offset")).option(
|
|
14584
|
+
"--output <format>",
|
|
14585
|
+
"Output format: json|jsonl|table (default: table)",
|
|
14586
|
+
"table"
|
|
14587
|
+
).action(
|
|
14588
|
+
async (options) => {
|
|
14589
|
+
const appId = resolveAppIdOrExit(options.app);
|
|
14590
|
+
const format = coerceOutputFormat(options.output);
|
|
14591
|
+
const status = options.status === void 0 ? void 0 : validateChoice("--status", options.status, APPROVAL_STATUSES);
|
|
14592
|
+
await withClient(async (client) => {
|
|
14593
|
+
const page = await client.approvals.list(appId, {
|
|
14594
|
+
...status ? { status } : {},
|
|
14595
|
+
...options.limit !== void 0 ? { limit: options.limit } : {},
|
|
14596
|
+
...options.offset !== void 0 ? { offset: options.offset } : {}
|
|
14597
|
+
});
|
|
14598
|
+
const items = extractArrayField(page, "items", "approvals.list");
|
|
14599
|
+
const rows = validateRows(
|
|
14600
|
+
items,
|
|
14601
|
+
APPROVAL_ROW_CHECKS,
|
|
14602
|
+
"approvals.list"
|
|
14603
|
+
);
|
|
14604
|
+
emit2(format, rows, APPROVAL_LIST_COLUMNS);
|
|
14605
|
+
const total = page.total;
|
|
14606
|
+
const offset = page.offset;
|
|
14607
|
+
if (page.has_more === true && typeof total === "number" && typeof offset === "number") {
|
|
14608
|
+
process.stderr.write(
|
|
14609
|
+
`alter: showing ${rows.length} of ${total} approvals \u2014 pass --offset ${offset + rows.length} for the next page
|
|
14610
|
+
`
|
|
14611
|
+
);
|
|
14612
|
+
}
|
|
14613
|
+
});
|
|
14614
|
+
}
|
|
13652
14615
|
);
|
|
13653
|
-
approvals.command("
|
|
14616
|
+
approvals.command("show").description(
|
|
14617
|
+
"Show one approval with its full N-of-N gate breakdown (per-gate status/approver) and the request-rule snapshot"
|
|
14618
|
+
).option("--app <id>", "App ID (or ALTER_APP_ID)").requiredOption("--approval <id>", "Approval ID (any gate of the group)").option(
|
|
13654
14619
|
"--output <format>",
|
|
13655
14620
|
"Output format: json|jsonl|table (default: json)",
|
|
13656
14621
|
"json"
|
|
13657
|
-
).action(
|
|
13658
|
-
|
|
13659
|
-
|
|
13660
|
-
const
|
|
13661
|
-
|
|
13662
|
-
|
|
13663
|
-
|
|
14622
|
+
).action(
|
|
14623
|
+
async (options) => {
|
|
14624
|
+
const appId = resolveAppIdOrExit(options.app);
|
|
14625
|
+
const format = coerceOutputFormat(options.output);
|
|
14626
|
+
validateUuidOrExit("--approval", options.approval);
|
|
14627
|
+
await withClient(async (client) => {
|
|
14628
|
+
const row = await client.approvals.get(appId, options.approval);
|
|
14629
|
+
emit2(format, row);
|
|
14630
|
+
});
|
|
14631
|
+
}
|
|
14632
|
+
);
|
|
13664
14633
|
return approvals;
|
|
13665
14634
|
}
|
|
13666
14635
|
|
|
@@ -14717,7 +15686,7 @@ var DASHBOARD_RESOURCE_VERBS = {
|
|
|
14717
15686
|
// signing secret. BOTH ``create`` and ``webhooks`` are in the backend's
|
|
14718
15687
|
// ``WILDCARD_EXCLUDED_VERBS`` — only the literal scope grants either, never
|
|
14719
15688
|
// ``*`` or ``dashboard_identity_providers:*``. Update, delete, and config
|
|
14720
|
-
// reads stay
|
|
15689
|
+
// reads stay session-only (CLAUDE.md Destructive-Action Policy). Backend source
|
|
14721
15690
|
// of truth: _DASHBOARD_RESOURCES_V7.
|
|
14722
15691
|
dashboard_identity_providers: ["create", "webhooks"],
|
|
14723
15692
|
// ``dashboard_analytics`` (catalog v7): read-only org usage observability
|
|
@@ -16193,6 +17162,13 @@ var GRANT_COLUMNS = [
|
|
|
16193
17162
|
{ label: "TYPE", get: (g) => g.grant_type },
|
|
16194
17163
|
{ label: "PROVIDER", get: (g) => g.provider_name, maxWidth: 24 },
|
|
16195
17164
|
{ label: "STATUS", get: (g) => g.status },
|
|
17165
|
+
{
|
|
17166
|
+
// The grant-TTL expiry (access duration chosen at consent) — the axis
|
|
17167
|
+
// STATUS's TTL-aware "expired" derives from. Date-only keeps the table
|
|
17168
|
+
// narrow; "—" = perpetual (no TTL) or a pre-field backend.
|
|
17169
|
+
label: "EXPIRES",
|
|
17170
|
+
get: (g) => g.grant_expires_at ? g.grant_expires_at.slice(0, 10) : "\u2014"
|
|
17171
|
+
},
|
|
16196
17172
|
{
|
|
16197
17173
|
label: "USER",
|
|
16198
17174
|
get: (g) => g.user_identifier ?? g.account_display_name ?? "\u2014",
|
|
@@ -16283,6 +17259,7 @@ function buildGrantsCommand() {
|
|
|
16283
17259
|
created_at: isString,
|
|
16284
17260
|
last_used_at: isOptionalString,
|
|
16285
17261
|
expires_at: isOptionalString,
|
|
17262
|
+
grant_expires_at: isAbsentOrOptionalString,
|
|
16286
17263
|
parent_grant_id: isOptionalString,
|
|
16287
17264
|
depth: isOptionalNumber,
|
|
16288
17265
|
delegable: isOptionalBoolean,
|
|
@@ -18911,8 +19888,20 @@ var RULE_TYPES = [
|
|
|
18911
19888
|
"ip_allowlist",
|
|
18912
19889
|
"time_window",
|
|
18913
19890
|
"require_approval",
|
|
18914
|
-
"restriction"
|
|
19891
|
+
"restriction",
|
|
19892
|
+
"quota",
|
|
19893
|
+
"content_match"
|
|
19894
|
+
];
|
|
19895
|
+
var TIME_WINDOW_DAYS = [
|
|
19896
|
+
"mon",
|
|
19897
|
+
"tue",
|
|
19898
|
+
"wed",
|
|
19899
|
+
"thu",
|
|
19900
|
+
"fri",
|
|
19901
|
+
"sat",
|
|
19902
|
+
"sun"
|
|
18915
19903
|
];
|
|
19904
|
+
var QUOTA_PERIODS = ["minute", "hour", "day", "month"];
|
|
18916
19905
|
var MATCHABLE_ATTRIBUTES = [
|
|
18917
19906
|
"app_id",
|
|
18918
19907
|
"environment",
|
|
@@ -18921,7 +19910,9 @@ var MATCHABLE_ATTRIBUTES = [
|
|
|
18921
19910
|
"provider_id",
|
|
18922
19911
|
"resource_kind",
|
|
18923
19912
|
"client_ip",
|
|
18924
|
-
"method"
|
|
19913
|
+
"method",
|
|
19914
|
+
"operation",
|
|
19915
|
+
"family"
|
|
18925
19916
|
];
|
|
18926
19917
|
var MAX_WHEN_LIST_VALUES = 100;
|
|
18927
19918
|
var MAX_WHEN_VALUE_LEN = 512;
|
|
@@ -18987,6 +19978,11 @@ function validateWhenMapOrExit(when) {
|
|
|
18987
19978
|
`"when.client_ip" matches EXACT IPs only \u2014 CIDR ranges belong in an ip_allowlist rule (--type ip_allowlist)`
|
|
18988
19979
|
);
|
|
18989
19980
|
}
|
|
19981
|
+
if (attr === "family" && !OPERATION_FAMILIES.includes(v)) {
|
|
19982
|
+
fail(
|
|
19983
|
+
`when.family names unknown operation family ${JSON.stringify(v)} (expected one of: ${OPERATION_FAMILIES.join(", ")})`
|
|
19984
|
+
);
|
|
19985
|
+
}
|
|
18990
19986
|
}
|
|
18991
19987
|
}
|
|
18992
19988
|
}
|
|
@@ -19004,6 +20000,33 @@ function validateJsonMatchBodyOrExit(body) {
|
|
|
19004
20000
|
}
|
|
19005
20001
|
validateWhenMapOrExit(body.when);
|
|
19006
20002
|
}
|
|
20003
|
+
function validateRequireApprovalWhenOrExit(when) {
|
|
20004
|
+
const fail = failRuleBody;
|
|
20005
|
+
if (typeof when !== "object" || when === null || Array.isArray(when)) {
|
|
20006
|
+
fail(`"when" must be an object of condition keys`);
|
|
20007
|
+
}
|
|
20008
|
+
const record = when;
|
|
20009
|
+
const metadata = {};
|
|
20010
|
+
let hasContent = false;
|
|
20011
|
+
for (const [key, value] of Object.entries(record)) {
|
|
20012
|
+
if (key === "match" || key === "params") {
|
|
20013
|
+
hasContent = true;
|
|
20014
|
+
} else {
|
|
20015
|
+
metadata[key] = value;
|
|
20016
|
+
}
|
|
20017
|
+
}
|
|
20018
|
+
if (Object.keys(metadata).length > 0) validateWhenMapOrExit(metadata);
|
|
20019
|
+
if (hasContent) {
|
|
20020
|
+
validateContentMatchBodyOrExit({
|
|
20021
|
+
match: record.match,
|
|
20022
|
+
...record.params !== void 0 ? { params: record.params } : {},
|
|
20023
|
+
effect: "deny"
|
|
20024
|
+
});
|
|
20025
|
+
}
|
|
20026
|
+
if (Object.keys(metadata).length === 0 && !hasContent) {
|
|
20027
|
+
fail(`"when" must contain at least one condition`);
|
|
20028
|
+
}
|
|
20029
|
+
}
|
|
19007
20030
|
var HITL_MAX_APPROVERS = 10;
|
|
19008
20031
|
var HITL_MIN_EXPIRY_SECONDS = 60;
|
|
19009
20032
|
var HITL_MAX_EXPIRY_SECONDS = 86400;
|
|
@@ -19048,7 +20071,7 @@ function validateRequireApprovalBodyOrExit(body) {
|
|
|
19048
20071
|
if (body.effect !== "require_approval") {
|
|
19049
20072
|
fail(`effect must be "require_approval"`);
|
|
19050
20073
|
}
|
|
19051
|
-
if (body.when !== void 0)
|
|
20074
|
+
if (body.when !== void 0) validateRequireApprovalWhenOrExit(body.when);
|
|
19052
20075
|
const approval = body.approval;
|
|
19053
20076
|
if (typeof approval !== "object" || approval === null || Array.isArray(approval)) {
|
|
19054
20077
|
fail(
|
|
@@ -19190,37 +20213,95 @@ function validateIpAllowlistBodyOrExit(body) {
|
|
|
19190
20213
|
);
|
|
19191
20214
|
}
|
|
19192
20215
|
}
|
|
19193
|
-
var TIME_WINDOW_KEYS = [
|
|
19194
|
-
|
|
19195
|
-
|
|
19196
|
-
"timezone"
|
|
19197
|
-
];
|
|
20216
|
+
var TIME_WINDOW_KEYS = ["windows", "timezone"];
|
|
20217
|
+
var TIME_WINDOW_WINDOW_KEYS = ["days", "start", "end"];
|
|
20218
|
+
var HHMM_RE = /^([01]\d|2[0-3]):[0-5]\d$/;
|
|
19198
20219
|
function validateTimeWindowBodyOrExit(body) {
|
|
19199
20220
|
const fail = failRuleBody;
|
|
19200
20221
|
for (const k of Object.keys(body)) {
|
|
19201
20222
|
if (!TIME_WINDOW_KEYS.includes(k)) {
|
|
19202
20223
|
fail(
|
|
19203
|
-
`unknown key ${JSON.stringify(k)} (expected: ${TIME_WINDOW_KEYS.join(", ")})`
|
|
20224
|
+
`unknown key ${JSON.stringify(k)} (expected: ${TIME_WINDOW_KEYS.join(", ")}). The legacy business_hours_only/weekdays_only grammar was removed \u2014 use {"windows": [{"days": [...], "start": "HH:MM", "end": "HH:MM"}], "timezone": "<IANA>"}`
|
|
19204
20225
|
);
|
|
19205
20226
|
}
|
|
19206
20227
|
}
|
|
19207
|
-
|
|
19208
|
-
|
|
19209
|
-
|
|
20228
|
+
const windows = body.windows;
|
|
20229
|
+
if (!Array.isArray(windows) || windows.length === 0) {
|
|
20230
|
+
fail(`"windows" must be a non-empty array`);
|
|
20231
|
+
return;
|
|
20232
|
+
}
|
|
20233
|
+
if (windows.length > 20) {
|
|
20234
|
+
fail(`"windows" may not exceed 20 entries`);
|
|
20235
|
+
}
|
|
20236
|
+
windows.forEach((w, i) => {
|
|
20237
|
+
if (typeof w !== "object" || w === null || Array.isArray(w)) {
|
|
20238
|
+
fail(`windows[${i}] must be an object`);
|
|
20239
|
+
return;
|
|
20240
|
+
}
|
|
20241
|
+
const win = w;
|
|
20242
|
+
for (const k of Object.keys(win)) {
|
|
20243
|
+
if (!TIME_WINDOW_WINDOW_KEYS.includes(k)) {
|
|
20244
|
+
fail(`windows[${i}] has unknown key ${JSON.stringify(k)}`);
|
|
20245
|
+
}
|
|
20246
|
+
}
|
|
20247
|
+
const days = win.days;
|
|
20248
|
+
if (!Array.isArray(days) || days.length === 0) {
|
|
20249
|
+
fail(`windows[${i}].days must be a non-empty array`);
|
|
20250
|
+
} else {
|
|
20251
|
+
for (const d of days) {
|
|
20252
|
+
if (typeof d !== "string" || !TIME_WINDOW_DAYS.includes(d)) {
|
|
20253
|
+
fail(
|
|
20254
|
+
`windows[${i}].days has invalid day ${JSON.stringify(d)} (use ${TIME_WINDOW_DAYS.join("/")})`
|
|
20255
|
+
);
|
|
20256
|
+
}
|
|
20257
|
+
}
|
|
20258
|
+
if (new Set(days).size !== days.length) {
|
|
20259
|
+
fail(`windows[${i}].days has duplicate day(s)`);
|
|
20260
|
+
}
|
|
20261
|
+
}
|
|
20262
|
+
const start = win.start;
|
|
20263
|
+
const end = win.end;
|
|
20264
|
+
if (typeof start !== "string" || !HHMM_RE.test(start)) {
|
|
20265
|
+
fail(`windows[${i}].start must be "HH:MM" (00:00\u201323:59)`);
|
|
20266
|
+
}
|
|
20267
|
+
if (typeof end !== "string" || !(HHMM_RE.test(end) || end === "24:00")) {
|
|
20268
|
+
fail(`windows[${i}].end must be "HH:MM" (00:00\u201323:59) or "24:00"`);
|
|
20269
|
+
}
|
|
20270
|
+
if (typeof start === "string" && start === end) {
|
|
20271
|
+
fail(`windows[${i}] start == end (a zero-length window can never match)`);
|
|
19210
20272
|
}
|
|
20273
|
+
});
|
|
20274
|
+
if (typeof body.timezone !== "string" || body.timezone.trim().length === 0) {
|
|
20275
|
+
fail(
|
|
20276
|
+
`"timezone" is required \u2014 a non-empty IANA timezone string (e.g. "America/New_York")`
|
|
20277
|
+
);
|
|
20278
|
+
return;
|
|
19211
20279
|
}
|
|
19212
|
-
|
|
20280
|
+
try {
|
|
20281
|
+
new Intl.DateTimeFormat(void 0, { timeZone: body.timezone });
|
|
20282
|
+
} catch {
|
|
19213
20283
|
fail(
|
|
19214
|
-
`
|
|
20284
|
+
`"timezone" ${JSON.stringify(body.timezone)} is not a valid IANA timezone name (e.g. "America/New_York")`
|
|
19215
20285
|
);
|
|
19216
20286
|
}
|
|
19217
|
-
|
|
19218
|
-
|
|
20287
|
+
}
|
|
20288
|
+
var QUOTA_KEYS = ["limit", "period"];
|
|
20289
|
+
function validateQuotaBodyOrExit(body) {
|
|
20290
|
+
const fail = failRuleBody;
|
|
20291
|
+
for (const k of Object.keys(body)) {
|
|
20292
|
+
if (!QUOTA_KEYS.includes(k)) {
|
|
19219
20293
|
fail(
|
|
19220
|
-
`
|
|
20294
|
+
`unknown key ${JSON.stringify(k)} (expected: ${QUOTA_KEYS.join(", ")})`
|
|
19221
20295
|
);
|
|
19222
20296
|
}
|
|
19223
20297
|
}
|
|
20298
|
+
const limit = body.limit;
|
|
20299
|
+
if (typeof limit !== "number" || !Number.isInteger(limit) || limit < 1 || limit > 1e6) {
|
|
20300
|
+
fail(`"limit" must be an integer between 1 and 1000000`);
|
|
20301
|
+
}
|
|
20302
|
+
if (typeof body.period !== "string" || !QUOTA_PERIODS.includes(body.period)) {
|
|
20303
|
+
fail(`"period" must be one of ${QUOTA_PERIODS.join("/")}`);
|
|
20304
|
+
}
|
|
19224
20305
|
}
|
|
19225
20306
|
var RESTRICTION_KEYS = ["allowed_methods", "allowed_endpoints"];
|
|
19226
20307
|
var MAX_RESTRICTION_METHODS = 7;
|
|
@@ -19281,6 +20362,195 @@ function validateRestrictionBodyOrExit(body) {
|
|
|
19281
20362
|
);
|
|
19282
20363
|
}
|
|
19283
20364
|
}
|
|
20365
|
+
var CONTENT_MATCH_KEYS = [
|
|
20366
|
+
"match",
|
|
20367
|
+
"params",
|
|
20368
|
+
"effect",
|
|
20369
|
+
"redact",
|
|
20370
|
+
"step_up"
|
|
20371
|
+
];
|
|
20372
|
+
var CONTENT_MATCH_MATCH_KEYS = ["operations", "families"];
|
|
20373
|
+
var CONTENT_MATCH_EFFECTS = ["deny", "redact", "step_up"];
|
|
20374
|
+
var CONTENT_NUMERIC_OPS = ["gt", "gte", "lt", "lte"];
|
|
20375
|
+
var CONTENT_PARAM_OPS = [
|
|
20376
|
+
"equals",
|
|
20377
|
+
"any_in",
|
|
20378
|
+
"not_subset_of",
|
|
20379
|
+
"gt",
|
|
20380
|
+
"gte",
|
|
20381
|
+
"lt",
|
|
20382
|
+
"lte"
|
|
20383
|
+
];
|
|
20384
|
+
var OPERATION_FAMILIES = [
|
|
20385
|
+
"send",
|
|
20386
|
+
"read",
|
|
20387
|
+
"write",
|
|
20388
|
+
"delete",
|
|
20389
|
+
"admin",
|
|
20390
|
+
"payment"
|
|
20391
|
+
];
|
|
20392
|
+
var MAX_CONTENT_MATCH_ENTRIES = 100;
|
|
20393
|
+
var MAX_CONTENT_OPERATION_ID_LEN = 255;
|
|
20394
|
+
var MAX_CONTENT_PARAM_CONDITIONS = 20;
|
|
20395
|
+
var MAX_CONTENT_PARAM_NAME_LEN = 120;
|
|
20396
|
+
var MAX_CONTENT_VALUE_ENTRIES = 200;
|
|
20397
|
+
var MAX_CONTENT_VALUE_LEN = 512;
|
|
20398
|
+
var MAX_CONTENT_REDACT_FIELDS = 50;
|
|
20399
|
+
var MAX_CONTENT_REDACT_FIELD_LEN = 120;
|
|
20400
|
+
var CONTENT_STEP_UP_MIN_SECONDS = 1;
|
|
20401
|
+
var CONTENT_STEP_UP_MAX_SECONDS = 86400;
|
|
20402
|
+
function validateContentMatchBodyOrExit(body) {
|
|
20403
|
+
const fail = failRuleBody;
|
|
20404
|
+
for (const k of Object.keys(body)) {
|
|
20405
|
+
if (!CONTENT_MATCH_KEYS.includes(k)) {
|
|
20406
|
+
fail(
|
|
20407
|
+
`unknown key ${JSON.stringify(k)} (expected: ${CONTENT_MATCH_KEYS.join(", ")})`
|
|
20408
|
+
);
|
|
20409
|
+
}
|
|
20410
|
+
}
|
|
20411
|
+
const match = body.match;
|
|
20412
|
+
if (typeof match !== "object" || match === null || Array.isArray(match)) {
|
|
20413
|
+
fail(
|
|
20414
|
+
`"match" object is required ({"operations"?: [...], "families"?: [...]})`
|
|
20415
|
+
);
|
|
20416
|
+
}
|
|
20417
|
+
const m = match;
|
|
20418
|
+
for (const k of Object.keys(m)) {
|
|
20419
|
+
if (!CONTENT_MATCH_MATCH_KEYS.includes(k)) {
|
|
20420
|
+
fail(
|
|
20421
|
+
`unknown match key ${JSON.stringify(k)} (expected: ${CONTENT_MATCH_MATCH_KEYS.join(", ")})`
|
|
20422
|
+
);
|
|
20423
|
+
}
|
|
20424
|
+
}
|
|
20425
|
+
const operations = m.operations ?? [];
|
|
20426
|
+
const families = m.families ?? [];
|
|
20427
|
+
if (!Array.isArray(operations) || !Array.isArray(families)) {
|
|
20428
|
+
fail(`"match.operations" and "match.families" must be lists`);
|
|
20429
|
+
}
|
|
20430
|
+
const ops = operations;
|
|
20431
|
+
const fams = families;
|
|
20432
|
+
if (ops.length === 0 && fams.length === 0) {
|
|
20433
|
+
fail(`"match" must name at least one operation or family`);
|
|
20434
|
+
}
|
|
20435
|
+
if (ops.length + fams.length > MAX_CONTENT_MATCH_ENTRIES) {
|
|
20436
|
+
fail(
|
|
20437
|
+
`"match" exceeds ${MAX_CONTENT_MATCH_ENTRIES} combined operations/families entries`
|
|
20438
|
+
);
|
|
20439
|
+
}
|
|
20440
|
+
for (const opId of ops) {
|
|
20441
|
+
if (typeof opId !== "string" || opId.length === 0 || opId.length > MAX_CONTENT_OPERATION_ID_LEN) {
|
|
20442
|
+
fail(
|
|
20443
|
+
`"match.operations" entries must be non-empty strings of at most ${MAX_CONTENT_OPERATION_ID_LEN} characters`
|
|
20444
|
+
);
|
|
20445
|
+
}
|
|
20446
|
+
}
|
|
20447
|
+
for (const fam of fams) {
|
|
20448
|
+
if (typeof fam !== "string" || !OPERATION_FAMILIES.includes(fam)) {
|
|
20449
|
+
fail(
|
|
20450
|
+
`"match.families" entry ${JSON.stringify(fam)} is not a known operation family (expected one of: ${OPERATION_FAMILIES.join(", ")})`
|
|
20451
|
+
);
|
|
20452
|
+
}
|
|
20453
|
+
}
|
|
20454
|
+
const effect = body.effect;
|
|
20455
|
+
if (typeof effect !== "string" || !CONTENT_MATCH_EFFECTS.includes(effect)) {
|
|
20456
|
+
fail(`"effect" must be one of ${CONTENT_MATCH_EFFECTS.join("/")}`);
|
|
20457
|
+
}
|
|
20458
|
+
if (effect === "redact") {
|
|
20459
|
+
const redact = body.redact;
|
|
20460
|
+
if (typeof redact !== "object" || redact === null || Array.isArray(redact)) {
|
|
20461
|
+
fail(`effect=redact requires a "redact" object ({"fields": [...]})`);
|
|
20462
|
+
}
|
|
20463
|
+
const r = redact;
|
|
20464
|
+
for (const k of Object.keys(r)) {
|
|
20465
|
+
if (k !== "fields") {
|
|
20466
|
+
fail(`unknown redact key ${JSON.stringify(k)} (expected: fields)`);
|
|
20467
|
+
}
|
|
20468
|
+
}
|
|
20469
|
+
const fields2 = r.fields;
|
|
20470
|
+
if (!Array.isArray(fields2) || fields2.length === 0 || fields2.length > MAX_CONTENT_REDACT_FIELDS) {
|
|
20471
|
+
fail(`"redact.fields" must name 1-${MAX_CONTENT_REDACT_FIELDS} fields`);
|
|
20472
|
+
}
|
|
20473
|
+
for (const f of fields2) {
|
|
20474
|
+
if (typeof f !== "string" || f.length === 0 || f.length > MAX_CONTENT_REDACT_FIELD_LEN) {
|
|
20475
|
+
fail(
|
|
20476
|
+
`"redact.fields" entries must be non-empty strings of at most ${MAX_CONTENT_REDACT_FIELD_LEN} characters`
|
|
20477
|
+
);
|
|
20478
|
+
}
|
|
20479
|
+
}
|
|
20480
|
+
} else if (body.redact !== void 0) {
|
|
20481
|
+
fail(`"redact" only applies to effect=redact`);
|
|
20482
|
+
}
|
|
20483
|
+
if (effect === "step_up") {
|
|
20484
|
+
const stepUp = body.step_up;
|
|
20485
|
+
if (typeof stepUp !== "object" || stepUp === null || Array.isArray(stepUp)) {
|
|
20486
|
+
fail(
|
|
20487
|
+
`effect=step_up requires a "step_up" object ({"max_session_age_seconds": <seconds>})`
|
|
20488
|
+
);
|
|
20489
|
+
}
|
|
20490
|
+
const s = stepUp;
|
|
20491
|
+
for (const k of Object.keys(s)) {
|
|
20492
|
+
if (k !== "max_session_age_seconds") {
|
|
20493
|
+
fail(
|
|
20494
|
+
`unknown step_up key ${JSON.stringify(k)} (expected: max_session_age_seconds)`
|
|
20495
|
+
);
|
|
20496
|
+
}
|
|
20497
|
+
}
|
|
20498
|
+
const maxAge = s.max_session_age_seconds;
|
|
20499
|
+
if (typeof maxAge !== "number" || !Number.isInteger(maxAge) || maxAge < CONTENT_STEP_UP_MIN_SECONDS || maxAge > CONTENT_STEP_UP_MAX_SECONDS) {
|
|
20500
|
+
fail(
|
|
20501
|
+
`"step_up.max_session_age_seconds" must be an integer between ${CONTENT_STEP_UP_MIN_SECONDS} and ${CONTENT_STEP_UP_MAX_SECONDS}`
|
|
20502
|
+
);
|
|
20503
|
+
}
|
|
20504
|
+
} else if (body.step_up !== void 0) {
|
|
20505
|
+
fail(`"step_up" only applies to effect=step_up`);
|
|
20506
|
+
}
|
|
20507
|
+
const params = body.params ?? [];
|
|
20508
|
+
if (!Array.isArray(params) || params.length > MAX_CONTENT_PARAM_CONDITIONS) {
|
|
20509
|
+
fail(
|
|
20510
|
+
`"params" must be a list of at most ${MAX_CONTENT_PARAM_CONDITIONS} conditions`
|
|
20511
|
+
);
|
|
20512
|
+
}
|
|
20513
|
+
params.forEach((cond, i) => {
|
|
20514
|
+
if (typeof cond !== "object" || cond === null || Array.isArray(cond)) {
|
|
20515
|
+
fail(`params[${i}] must be an object ({"name", "op", "value"})`);
|
|
20516
|
+
}
|
|
20517
|
+
const c = cond;
|
|
20518
|
+
const condKeys = Object.keys(c);
|
|
20519
|
+
if (condKeys.length !== 3 || condKeys.some((k) => k !== "name" && k !== "op" && k !== "value")) {
|
|
20520
|
+
fail(`params[${i}] needs exactly the keys name/op/value`);
|
|
20521
|
+
}
|
|
20522
|
+
const name = c.name;
|
|
20523
|
+
if (typeof name !== "string" || name.length === 0 || name.length > MAX_CONTENT_PARAM_NAME_LEN) {
|
|
20524
|
+
fail(
|
|
20525
|
+
`params[${i}].name must be a non-empty string of at most ${MAX_CONTENT_PARAM_NAME_LEN} characters`
|
|
20526
|
+
);
|
|
20527
|
+
}
|
|
20528
|
+
const op = c.op;
|
|
20529
|
+
if (typeof op !== "string" || !CONTENT_PARAM_OPS.includes(op)) {
|
|
20530
|
+
fail(`params[${i}].op must be one of ${CONTENT_PARAM_OPS.join("/")}`);
|
|
20531
|
+
}
|
|
20532
|
+
const value = c.value;
|
|
20533
|
+
if (CONTENT_NUMERIC_OPS.includes(op)) {
|
|
20534
|
+
if (typeof value !== "number") {
|
|
20535
|
+
fail(`params[${i}].op ${op} requires a numeric value`);
|
|
20536
|
+
}
|
|
20537
|
+
} else if (op === "equals") {
|
|
20538
|
+
if (typeof value !== "string" && typeof value !== "number") {
|
|
20539
|
+
fail(
|
|
20540
|
+
`params[${i}].op equals requires a scalar value (string or number)`
|
|
20541
|
+
);
|
|
20542
|
+
}
|
|
20543
|
+
} else {
|
|
20544
|
+
if (!Array.isArray(value) || value.length === 0 || value.length > MAX_CONTENT_VALUE_ENTRIES || value.some(
|
|
20545
|
+
(v) => typeof v !== "string" || v.length === 0 || v.length > MAX_CONTENT_VALUE_LEN
|
|
20546
|
+
)) {
|
|
20547
|
+
fail(
|
|
20548
|
+
`params[${i}].op ${op} requires a non-empty list of strings (max ${MAX_CONTENT_VALUE_ENTRIES} entries, ${MAX_CONTENT_VALUE_LEN} characters each)`
|
|
20549
|
+
);
|
|
20550
|
+
}
|
|
20551
|
+
}
|
|
20552
|
+
});
|
|
20553
|
+
}
|
|
19284
20554
|
function validateRuleBodyForTypeOrExit(ruleType, body) {
|
|
19285
20555
|
if (pythonCompactJsonLength(body) > MAX_RULE_BODY_BYTES) {
|
|
19286
20556
|
failRuleBody(
|
|
@@ -19303,14 +20573,23 @@ function validateRuleBodyForTypeOrExit(ruleType, body) {
|
|
|
19303
20573
|
case "restriction":
|
|
19304
20574
|
validateRestrictionBodyOrExit(body);
|
|
19305
20575
|
return;
|
|
20576
|
+
case "quota":
|
|
20577
|
+
validateQuotaBodyOrExit(body);
|
|
20578
|
+
return;
|
|
20579
|
+
case "content_match":
|
|
20580
|
+
validateContentMatchBodyOrExit(body);
|
|
20581
|
+
return;
|
|
19306
20582
|
}
|
|
19307
20583
|
}
|
|
19308
20584
|
function detectRuleBodyType(body) {
|
|
19309
20585
|
if (body.effect === "require_approval" || "approval" in body)
|
|
19310
20586
|
return "require_approval";
|
|
20587
|
+
if ("match" in body || body.effect === "redact" || body.effect === "step_up" || "redact" in body || "step_up" in body)
|
|
20588
|
+
return "content_match";
|
|
19311
20589
|
if ("when" in body || "effect" in body) return "json_match";
|
|
19312
20590
|
if ("allow" in body) return "ip_allowlist";
|
|
19313
20591
|
if (TIME_WINDOW_KEYS.some((k) => k in body)) return "time_window";
|
|
20592
|
+
if (QUOTA_KEYS.some((k) => k in body)) return "quota";
|
|
19314
20593
|
if (RESTRICTION_KEYS.some((k) => k in body)) return "restriction";
|
|
19315
20594
|
return null;
|
|
19316
20595
|
}
|
|
@@ -19396,6 +20675,9 @@ var SIMULATE_TRACE_CHECKS = {
|
|
|
19396
20675
|
matched: isBoolean,
|
|
19397
20676
|
would_deny: isBoolean
|
|
19398
20677
|
};
|
|
20678
|
+
var MAX_SIMULATE_OPERATION_ID_LEN = 255;
|
|
20679
|
+
var MAX_SIMULATE_PARAMS_ENTRIES = 20;
|
|
20680
|
+
var MAX_SIMULATE_PARAMS_LEN = 8192;
|
|
19399
20681
|
var SIMULATE_TRACE_COLUMNS = [
|
|
19400
20682
|
{ label: "LEVEL", get: (r) => r.level },
|
|
19401
20683
|
{ label: "TYPE", get: (r) => r.rule_type },
|
|
@@ -19495,6 +20777,7 @@ function buildRulesSubcommand() {
|
|
|
19495
20777
|
const row = await client.policy.createRule(appId, target, payload);
|
|
19496
20778
|
emit2(format, row);
|
|
19497
20779
|
surfaceApproverWarnings(row);
|
|
20780
|
+
surfaceCatalogWarnings(row);
|
|
19498
20781
|
});
|
|
19499
20782
|
}
|
|
19500
20783
|
);
|
|
@@ -19584,7 +20867,7 @@ function buildRulesSubcommand() {
|
|
|
19584
20867
|
const ruleBody = loadInputJsonBody(options.body, "--body");
|
|
19585
20868
|
if (detectRuleBodyType(ruleBody) === null) {
|
|
19586
20869
|
failRuleBody(
|
|
19587
|
-
`unrecognized rule body shape (expected json_match {"when", "effect"}, require_approval {"effect": "require_approval", "approval"}, ip_allowlist {"allow"}, time_window {${TIME_WINDOW_KEYS.map((k) => `"${k}"`).join(", ")}}, or restriction {${RESTRICTION_KEYS.map((k) => `"${k}"`).join(" / ")}})`
|
|
20870
|
+
`unrecognized rule body shape (expected json_match {"when", "effect"}, require_approval {"effect": "require_approval", "approval"}, content_match {"match", "effect"}, ip_allowlist {"allow"}, time_window {${TIME_WINDOW_KEYS.map((k) => `"${k}"`).join(", ")}}, quota {${QUOTA_KEYS.map((k) => `"${k}"`).join(", ")}}, or restriction {${RESTRICTION_KEYS.map((k) => `"${k}"`).join(" / ")}})`
|
|
19588
20871
|
);
|
|
19589
20872
|
}
|
|
19590
20873
|
pendingRuleBody = ruleBody;
|
|
@@ -19646,6 +20929,7 @@ function buildRulesSubcommand() {
|
|
|
19646
20929
|
);
|
|
19647
20930
|
emit2(format, row);
|
|
19648
20931
|
surfaceApproverWarnings(row);
|
|
20932
|
+
surfaceCatalogWarnings(row);
|
|
19649
20933
|
});
|
|
19650
20934
|
}
|
|
19651
20935
|
);
|
|
@@ -19720,7 +21004,7 @@ function buildPolicyCommand() {
|
|
|
19720
21004
|
});
|
|
19721
21005
|
});
|
|
19722
21006
|
policy.command("simulate").description(
|
|
19723
|
-
"Dry-run the app's policy rules for one request shape (grant / method / endpoint / agent / client-ip / time) and print the verdict + per-rule trace. Read-only, no side effects (requires dashboard_app_policy:read)"
|
|
21007
|
+
"Dry-run the app's policy rules for one request shape (grant / method / endpoint / agent / client-ip / time / operation / params) and print the verdict + per-rule trace. Read-only, no side effects (requires dashboard_app_policy:read)"
|
|
19724
21008
|
).option(
|
|
19725
21009
|
"--app <app-id>",
|
|
19726
21010
|
"App ID. Falls back to ALTER_APP_ID env or .alter/config.yaml"
|
|
@@ -19739,6 +21023,12 @@ function buildPolicyCommand() {
|
|
|
19739
21023
|
).option(
|
|
19740
21024
|
"--at <iso8601>",
|
|
19741
21025
|
"Instant for time-window rules (ISO 8601; e.g. 2026-05-21T09:00:00Z)"
|
|
21026
|
+
).option(
|
|
21027
|
+
"--operation <id>",
|
|
21028
|
+
"Explicit attested-operation id override for content_match rules (e.g. gmail.users.messages.send); omit to classify from --endpoint like the live gate"
|
|
21029
|
+
).option(
|
|
21030
|
+
"--params <json>",
|
|
21031
|
+
`Simulated projected parameter bag for content_match rules, as a JSON object (e.g. '{"recipients": ["bob@other.io"]}'). Values are used only for the dry-run evaluation and never stored`
|
|
19742
21032
|
).option(
|
|
19743
21033
|
"--output <format>",
|
|
19744
21034
|
"Output format: json|jsonl|table (default: table)",
|
|
@@ -19771,6 +21061,49 @@ function buildPolicyCommand() {
|
|
|
19771
21061
|
process.exit(EXIT_USAGE);
|
|
19772
21062
|
}
|
|
19773
21063
|
const at = options.at !== void 0 ? parseIsoDateArgument("--at")(options.at) : void 0;
|
|
21064
|
+
let operationId;
|
|
21065
|
+
if (options.operation !== void 0) {
|
|
21066
|
+
operationId = options.operation.trim();
|
|
21067
|
+
if (operationId.length === 0 || operationId.length > MAX_SIMULATE_OPERATION_ID_LEN) {
|
|
21068
|
+
process.stderr.write(
|
|
21069
|
+
`alter: --operation must be a non-empty operation id of at most ${MAX_SIMULATE_OPERATION_ID_LEN} characters
|
|
21070
|
+
`
|
|
21071
|
+
);
|
|
21072
|
+
process.exit(EXIT_USAGE);
|
|
21073
|
+
}
|
|
21074
|
+
}
|
|
21075
|
+
let simulatedParams;
|
|
21076
|
+
if (options.params !== void 0) {
|
|
21077
|
+
let parsed;
|
|
21078
|
+
try {
|
|
21079
|
+
parsed = JSON.parse(options.params);
|
|
21080
|
+
} catch {
|
|
21081
|
+
process.stderr.write("alter: --params must be valid JSON\n");
|
|
21082
|
+
process.exit(EXIT_USAGE);
|
|
21083
|
+
}
|
|
21084
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
21085
|
+
process.stderr.write(
|
|
21086
|
+
`alter: --params must be a JSON object (not an array or scalar), e.g. '{"recipients": ["bob@other.io"]}'
|
|
21087
|
+
`
|
|
21088
|
+
);
|
|
21089
|
+
process.exit(EXIT_USAGE);
|
|
21090
|
+
}
|
|
21091
|
+
simulatedParams = parsed;
|
|
21092
|
+
if (Object.keys(simulatedParams).length > MAX_SIMULATE_PARAMS_ENTRIES) {
|
|
21093
|
+
process.stderr.write(
|
|
21094
|
+
`alter: --params must be a JSON object of at most ${MAX_SIMULATE_PARAMS_ENTRIES} entries
|
|
21095
|
+
`
|
|
21096
|
+
);
|
|
21097
|
+
process.exit(EXIT_USAGE);
|
|
21098
|
+
}
|
|
21099
|
+
if (JSON.stringify(simulatedParams).length > MAX_SIMULATE_PARAMS_LEN) {
|
|
21100
|
+
process.stderr.write(
|
|
21101
|
+
`alter: --params exceeds ${MAX_SIMULATE_PARAMS_LEN} characters when JSON-encoded
|
|
21102
|
+
`
|
|
21103
|
+
);
|
|
21104
|
+
process.exit(EXIT_USAGE);
|
|
21105
|
+
}
|
|
21106
|
+
}
|
|
19774
21107
|
await withClient(async (client) => {
|
|
19775
21108
|
const verdict = await client.policy.simulate(appId, {
|
|
19776
21109
|
grantId: options.grant,
|
|
@@ -19778,7 +21111,9 @@ function buildPolicyCommand() {
|
|
|
19778
21111
|
endpoint: options.endpoint,
|
|
19779
21112
|
agentId: options.agent,
|
|
19780
21113
|
clientIp: options.clientIp?.trim(),
|
|
19781
|
-
at
|
|
21114
|
+
at,
|
|
21115
|
+
operationId,
|
|
21116
|
+
simulatedParams
|
|
19782
21117
|
});
|
|
19783
21118
|
if (format === "json" || format === "jsonl") {
|
|
19784
21119
|
emit2(format, verdict);
|
|
@@ -19796,6 +21131,21 @@ function buildPolicyCommand() {
|
|
|
19796
21131
|
"alter: node liveness NOT simulated \u2014 a revoked/expired ancestor in this delegation chain could still deny the live request\n"
|
|
19797
21132
|
);
|
|
19798
21133
|
}
|
|
21134
|
+
const classificationReason = typeof verdict.classification_reason === "string" ? verdict.classification_reason : null;
|
|
21135
|
+
const classifiedOperation = typeof verdict.classified_operation === "string" ? verdict.classified_operation : null;
|
|
21136
|
+
if (classificationReason !== null && classificationReason !== "not_simulated") {
|
|
21137
|
+
if (classifiedOperation !== null) {
|
|
21138
|
+
process.stderr.write(
|
|
21139
|
+
`alter: classified operation: ${classifiedOperation}
|
|
21140
|
+
`
|
|
21141
|
+
);
|
|
21142
|
+
} else {
|
|
21143
|
+
process.stderr.write(
|
|
21144
|
+
`alter: unclassified request (${classificationReason}) \u2014 content rules targeting this provider DENY unclassified traffic (endpoints outside the policy catalog are refused for ruled traffic)
|
|
21145
|
+
`
|
|
21146
|
+
);
|
|
21147
|
+
}
|
|
21148
|
+
}
|
|
19799
21149
|
const items = extractArrayField(verdict, "rules", "policy.simulate");
|
|
19800
21150
|
const rows = validateRows(
|
|
19801
21151
|
items,
|
|
@@ -19828,6 +21178,51 @@ var PROVIDER_COLUMNS = [
|
|
|
19828
21178
|
{ label: "GRANTS", get: (p) => String(p.grants_count) },
|
|
19829
21179
|
{ label: "STATUS", get: (p) => p.status }
|
|
19830
21180
|
];
|
|
21181
|
+
var PROVIDER_SPEC_KINDS = ["oauth", "managed"];
|
|
21182
|
+
var SPEC_COLUMNS = [
|
|
21183
|
+
{ label: "KIND", get: (s) => s.provider_kind },
|
|
21184
|
+
{ label: "PROVIDER", get: (s) => s.provider_id },
|
|
21185
|
+
{ label: "VER", get: (s) => String(s.version) },
|
|
21186
|
+
{ label: "OPS", get: (s) => String(s.operation_count) },
|
|
21187
|
+
{ label: "PROVENANCE", get: (s) => s.provenance, maxWidth: 16 },
|
|
21188
|
+
{ label: "SPEC", get: (s) => s.spec_version ?? "\u2014", maxWidth: 16 },
|
|
21189
|
+
{ label: "CHANGED", get: (s) => s.changed_at, maxWidth: 24 },
|
|
21190
|
+
{ label: "FETCHED", get: (s) => s.fetched_at, maxWidth: 24 }
|
|
21191
|
+
];
|
|
21192
|
+
var SPEC_OPERATION_COLUMNS = [
|
|
21193
|
+
{ label: "OPERATION", get: (o) => o.operation_id, maxWidth: 40 },
|
|
21194
|
+
{ label: "METHOD", get: (o) => o.method },
|
|
21195
|
+
{ label: "PATH", get: (o) => o.path_template, maxWidth: 48 },
|
|
21196
|
+
{ label: "SUMMARY", get: (o) => o.summary ?? "\u2014", maxWidth: 48 }
|
|
21197
|
+
];
|
|
21198
|
+
function resolveSpecForProvider(items, providerId) {
|
|
21199
|
+
const matches = items.filter((s) => s.provider_id === providerId);
|
|
21200
|
+
if (new Set(matches.map((s) => s.provider_kind)).size > 1) {
|
|
21201
|
+
return { outcome: "ambiguous" };
|
|
21202
|
+
}
|
|
21203
|
+
const spec = matches[0];
|
|
21204
|
+
if (spec === void 0 || spec.provider_kind !== "oauth" && spec.provider_kind !== "managed") {
|
|
21205
|
+
return { outcome: "not_found" };
|
|
21206
|
+
}
|
|
21207
|
+
return { outcome: "resolved", kind: spec.provider_kind, spec };
|
|
21208
|
+
}
|
|
21209
|
+
function unwrapSpecResolutionOrExit(resolution, providerId, kind) {
|
|
21210
|
+
if (resolution.outcome === "ambiguous") {
|
|
21211
|
+
process.stderr.write(
|
|
21212
|
+
`alter: provider "${sanitizeStderrText(providerId)}" has API specs in BOTH the oauth and managed families \u2014 pass --kind oauth or --kind managed to pick one
|
|
21213
|
+
`
|
|
21214
|
+
);
|
|
21215
|
+
process.exit(EXIT_USAGE);
|
|
21216
|
+
}
|
|
21217
|
+
if (resolution.outcome === "not_found") {
|
|
21218
|
+
process.stderr.write(
|
|
21219
|
+
`alter: no ${kind !== void 0 ? `${kind} ` : ""}provider API spec found for "${sanitizeStderrText(providerId)}" \u2014 run \`alter providers spec list\` to see every provider with an active spec
|
|
21220
|
+
`
|
|
21221
|
+
);
|
|
21222
|
+
process.exit(EXIT_NOT_FOUND);
|
|
21223
|
+
}
|
|
21224
|
+
return { kind: resolution.kind, spec: resolution.spec };
|
|
21225
|
+
}
|
|
19831
21226
|
function resolveClientSecret(raw) {
|
|
19832
21227
|
return resolveSecretArg(raw, "--client-secret");
|
|
19833
21228
|
}
|
|
@@ -19879,7 +21274,7 @@ function surfaceProviderResponse(row) {
|
|
|
19879
21274
|
}
|
|
19880
21275
|
function buildProvidersCommand() {
|
|
19881
21276
|
const providers = new Command20("providers").description(
|
|
19882
|
-
"Manage OAuth provider configs"
|
|
21277
|
+
"Manage OAuth provider configs and browse provider API specs"
|
|
19883
21278
|
);
|
|
19884
21279
|
providers.command("list").description("List OAuth provider configs for an app").option(
|
|
19885
21280
|
"--app <app-id>",
|
|
@@ -20200,6 +21595,170 @@ function buildProvidersCommand() {
|
|
|
20200
21595
|
});
|
|
20201
21596
|
}
|
|
20202
21597
|
);
|
|
21598
|
+
const spec = new Command20("spec").description(
|
|
21599
|
+
"Inspect provider API spec freshness, provenance, and versions"
|
|
21600
|
+
);
|
|
21601
|
+
spec.command("list").description(
|
|
21602
|
+
"List every provider's active API spec metadata \u2014 version, provenance, operation count, fetch freshness. The spec store's freshness/provenance dashboard, from the CLI."
|
|
21603
|
+
).option("--kind <kind>", "Filter by provider family: oauth | managed").option(
|
|
21604
|
+
"--output <format>",
|
|
21605
|
+
"Output format: json|jsonl|table (default: table)",
|
|
21606
|
+
"table"
|
|
21607
|
+
).action(async (options) => {
|
|
21608
|
+
const format = coerceOutputFormat(options.output);
|
|
21609
|
+
const kind = validateChoice("--kind", options.kind, PROVIDER_SPEC_KINDS);
|
|
21610
|
+
await withClient(async (client) => {
|
|
21611
|
+
const res = await client.providers.listSpecs(
|
|
21612
|
+
kind !== void 0 ? { kind } : {}
|
|
21613
|
+
);
|
|
21614
|
+
emit2(format, res.items, SPEC_COLUMNS);
|
|
21615
|
+
});
|
|
21616
|
+
});
|
|
21617
|
+
spec.command("status").description(
|
|
21618
|
+
"Show one provider's active spec metadata \u2014 version, provenance, operation count, fetch freshness"
|
|
21619
|
+
).argument("<provider>", "Provider ID (e.g. google, github)").option(
|
|
21620
|
+
"--kind <kind>",
|
|
21621
|
+
"Provider family: oauth | managed. Required only when the provider id exists in both families (e.g. github); otherwise auto-resolved from the spec store."
|
|
21622
|
+
).option(
|
|
21623
|
+
"--output <format>",
|
|
21624
|
+
"Output format: json|jsonl|table (default: json)",
|
|
21625
|
+
"json"
|
|
21626
|
+
).action(
|
|
21627
|
+
async (provider, options) => {
|
|
21628
|
+
const format = coerceOutputFormat(options.output);
|
|
21629
|
+
const kind = validateChoice(
|
|
21630
|
+
"--kind",
|
|
21631
|
+
options.kind,
|
|
21632
|
+
PROVIDER_SPEC_KINDS
|
|
21633
|
+
);
|
|
21634
|
+
const resolution = await withClient(async (client) => {
|
|
21635
|
+
const res = await client.providers.listSpecs(
|
|
21636
|
+
kind !== void 0 ? { kind } : {}
|
|
21637
|
+
);
|
|
21638
|
+
return resolveSpecForProvider(res.items, provider);
|
|
21639
|
+
});
|
|
21640
|
+
const { spec: row } = unwrapSpecResolutionOrExit(
|
|
21641
|
+
resolution,
|
|
21642
|
+
provider,
|
|
21643
|
+
kind
|
|
21644
|
+
);
|
|
21645
|
+
emit2(format, row);
|
|
21646
|
+
}
|
|
21647
|
+
);
|
|
21648
|
+
providers.addCommand(spec);
|
|
21649
|
+
const operations = new Command20("operations").description(
|
|
21650
|
+
"Discover a provider's API operations from its active spec"
|
|
21651
|
+
);
|
|
21652
|
+
operations.command("list").description(
|
|
21653
|
+
"Page one provider's API operations from its active spec (in-band operation discovery)"
|
|
21654
|
+
).argument("<provider>", "Provider ID (e.g. google, github)").option(
|
|
21655
|
+
"--kind <kind>",
|
|
21656
|
+
"Provider family: oauth | managed. Required only when the provider id exists in both families (e.g. github); otherwise auto-resolved from the spec store."
|
|
21657
|
+
).option(
|
|
21658
|
+
"--search <q>",
|
|
21659
|
+
"Server-side filter over operation id, method, path, and summary"
|
|
21660
|
+
).option(
|
|
21661
|
+
"--limit <n>",
|
|
21662
|
+
"Page size (1-500, default 100)",
|
|
21663
|
+
parseBoundedInt("--limit", 1, 500)
|
|
21664
|
+
).option("--offset <n>", "Page offset", parseNonNegativeInt("--offset")).option(
|
|
21665
|
+
"--output <format>",
|
|
21666
|
+
"Output format: json|jsonl|table (default: table)",
|
|
21667
|
+
"table"
|
|
21668
|
+
).action(
|
|
21669
|
+
async (provider, options) => {
|
|
21670
|
+
const format = coerceOutputFormat(options.output);
|
|
21671
|
+
const kindFlag = validateChoice(
|
|
21672
|
+
"--kind",
|
|
21673
|
+
options.kind,
|
|
21674
|
+
PROVIDER_SPEC_KINDS
|
|
21675
|
+
);
|
|
21676
|
+
const outcome = await withClient(async (client) => {
|
|
21677
|
+
let kind = kindFlag;
|
|
21678
|
+
if (kind === void 0) {
|
|
21679
|
+
const res = await client.providers.listSpecs();
|
|
21680
|
+
const resolution = resolveSpecForProvider(res.items, provider);
|
|
21681
|
+
if (resolution.outcome !== "resolved") return resolution;
|
|
21682
|
+
kind = resolution.kind;
|
|
21683
|
+
}
|
|
21684
|
+
const page2 = await client.providers.listSpecOperations(
|
|
21685
|
+
kind,
|
|
21686
|
+
provider,
|
|
21687
|
+
{
|
|
21688
|
+
...options.search !== void 0 ? { search: options.search } : {},
|
|
21689
|
+
...options.limit !== void 0 ? { limit: options.limit } : {},
|
|
21690
|
+
...options.offset !== void 0 ? { offset: options.offset } : {}
|
|
21691
|
+
}
|
|
21692
|
+
);
|
|
21693
|
+
return { outcome: "ok", page: page2 };
|
|
21694
|
+
});
|
|
21695
|
+
if (outcome.outcome !== "ok") {
|
|
21696
|
+
unwrapSpecResolutionOrExit(outcome, provider, kindFlag);
|
|
21697
|
+
return;
|
|
21698
|
+
}
|
|
21699
|
+
const page = outcome.page;
|
|
21700
|
+
const rows = validateRows(
|
|
21701
|
+
page.items,
|
|
21702
|
+
{
|
|
21703
|
+
operation_id: isString,
|
|
21704
|
+
method: isString,
|
|
21705
|
+
path_template: isString,
|
|
21706
|
+
summary: isOptionalString
|
|
21707
|
+
},
|
|
21708
|
+
"providers.list_spec_operations"
|
|
21709
|
+
);
|
|
21710
|
+
emit2(format, rows, SPEC_OPERATION_COLUMNS);
|
|
21711
|
+
if (page.has_more) {
|
|
21712
|
+
process.stderr.write(
|
|
21713
|
+
`alter: showing ${rows.length} of ${page.total} operations \u2014 pass --offset ${page.offset + rows.length} for the next page
|
|
21714
|
+
`
|
|
21715
|
+
);
|
|
21716
|
+
}
|
|
21717
|
+
}
|
|
21718
|
+
);
|
|
21719
|
+
operations.command("get").description(
|
|
21720
|
+
"Show one API operation with its full parameter/request/response schemas"
|
|
21721
|
+
).argument("<provider>", "Provider ID (e.g. google, github)").argument(
|
|
21722
|
+
"<operation-id>",
|
|
21723
|
+
"Operation ID from `alter providers operations list` (may contain slashes, e.g. repos/get)"
|
|
21724
|
+
).option(
|
|
21725
|
+
"--kind <kind>",
|
|
21726
|
+
"Provider family: oauth | managed. Required only when the provider id exists in both families (e.g. github); otherwise auto-resolved from the spec store."
|
|
21727
|
+
).option(
|
|
21728
|
+
"--output <format>",
|
|
21729
|
+
"Output format: json|jsonl|table (default: json)",
|
|
21730
|
+
"json"
|
|
21731
|
+
).action(
|
|
21732
|
+
async (provider, operationId, options) => {
|
|
21733
|
+
const format = coerceOutputFormat(options.output);
|
|
21734
|
+
const kindFlag = validateChoice(
|
|
21735
|
+
"--kind",
|
|
21736
|
+
options.kind,
|
|
21737
|
+
PROVIDER_SPEC_KINDS
|
|
21738
|
+
);
|
|
21739
|
+
const outcome = await withClient(async (client) => {
|
|
21740
|
+
let kind = kindFlag;
|
|
21741
|
+
if (kind === void 0) {
|
|
21742
|
+
const res = await client.providers.listSpecs();
|
|
21743
|
+
const resolution = resolveSpecForProvider(res.items, provider);
|
|
21744
|
+
if (resolution.outcome !== "resolved") return resolution;
|
|
21745
|
+
kind = resolution.kind;
|
|
21746
|
+
}
|
|
21747
|
+
const row = await client.providers.getSpecOperation(
|
|
21748
|
+
kind,
|
|
21749
|
+
provider,
|
|
21750
|
+
operationId
|
|
21751
|
+
);
|
|
21752
|
+
return { outcome: "ok", row };
|
|
21753
|
+
});
|
|
21754
|
+
if (outcome.outcome !== "ok") {
|
|
21755
|
+
unwrapSpecResolutionOrExit(outcome, provider, kindFlag);
|
|
21756
|
+
return;
|
|
21757
|
+
}
|
|
21758
|
+
emit2(format, outcome.row);
|
|
21759
|
+
}
|
|
21760
|
+
);
|
|
21761
|
+
providers.addCommand(operations);
|
|
20203
21762
|
return providers;
|
|
20204
21763
|
}
|
|
20205
21764
|
|