@sprigr/apps-app-sdk 0.8.5 → 0.8.7
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/index.d.ts +117 -1
- package/dist/index.js +97 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1424,6 +1424,122 @@ interface OfferUndoArgs {
|
|
|
1424
1424
|
*/
|
|
1425
1425
|
declare function offerUndo(args: OfferUndoArgs): Promise<AppUndoEnvelope | undefined>;
|
|
1426
1426
|
|
|
1427
|
+
/**
|
|
1428
|
+
* Apply confirmation policies to a parsed `sprigr-app.json`, for an app's
|
|
1429
|
+
* `gen:confirmation` script and its drift-guard test.
|
|
1430
|
+
*
|
|
1431
|
+
* Pure: takes the parsed manifest, mutates it in place, returns what it
|
|
1432
|
+
* touched. The script owns the two lines of file I/O around it, so this
|
|
1433
|
+
* module stays free of `node:fs` and safe to bundle into a Worker.
|
|
1434
|
+
*
|
|
1435
|
+
* Besides writing each policy, it retires the two mechanisms a policy
|
|
1436
|
+
* supersedes: the legacy `confirmation_required` flag (a bare
|
|
1437
|
+
* `{ always: true }` with no describe) and any app-declared `confirm` input
|
|
1438
|
+
* (the platform injects its own top-level `confirm` wherever a policy is
|
|
1439
|
+
* present). Both are stripped from EVERY tool, not only the gated ones,
|
|
1440
|
+
* because a stray copy on an ungated tool is how a handler ends up reading
|
|
1441
|
+
* a flag nothing enforces.
|
|
1442
|
+
*/
|
|
1443
|
+
|
|
1444
|
+
interface ManifestDispatchBlock {
|
|
1445
|
+
actionField: string;
|
|
1446
|
+
actions: string[];
|
|
1447
|
+
actionInputs?: Record<string, {
|
|
1448
|
+
accepts: string[];
|
|
1449
|
+
required?: string[];
|
|
1450
|
+
}>;
|
|
1451
|
+
}
|
|
1452
|
+
interface ManifestToolLike {
|
|
1453
|
+
name: string;
|
|
1454
|
+
input_schema?: {
|
|
1455
|
+
properties?: Record<string, unknown>;
|
|
1456
|
+
required?: string[];
|
|
1457
|
+
[k: string]: unknown;
|
|
1458
|
+
};
|
|
1459
|
+
confirmation?: unknown;
|
|
1460
|
+
confirmation_required?: boolean;
|
|
1461
|
+
dispatch?: unknown;
|
|
1462
|
+
[k: string]: unknown;
|
|
1463
|
+
}
|
|
1464
|
+
interface ManifestLike {
|
|
1465
|
+
tools?: ManifestToolLike[];
|
|
1466
|
+
[k: string]: unknown;
|
|
1467
|
+
}
|
|
1468
|
+
interface ApplyPoliciesOptions {
|
|
1469
|
+
/** Tool name -> its policy: a tool-level rule, or `{ actions }` for a dispatcher. */
|
|
1470
|
+
policies: Readonly<Record<string, ConfirmationPolicy>>;
|
|
1471
|
+
/** Tool name -> dispatch block to write alongside (dispatchers only). */
|
|
1472
|
+
dispatch?: Readonly<Record<string, ManifestDispatchBlock>>;
|
|
1473
|
+
/** Delete `confirmation_required` from every tool. Default true. */
|
|
1474
|
+
stripLegacyFlag?: boolean;
|
|
1475
|
+
/** Delete an app-declared `confirm` input (property + required entry) from every tool. Default true. */
|
|
1476
|
+
dropConfirmParam?: boolean;
|
|
1477
|
+
}
|
|
1478
|
+
/**
|
|
1479
|
+
* Write the policies (and dispatch blocks) onto the named tools in place.
|
|
1480
|
+
* Throws when a policy names a tool the manifest does not have, so a
|
|
1481
|
+
* renamed tool cannot silently lose its gate. Returns the tool names touched.
|
|
1482
|
+
*/
|
|
1483
|
+
declare function applyConfirmationPolicies(manifest: ManifestLike, opts: ApplyPoliciesOptions): string[];
|
|
1484
|
+
/**
|
|
1485
|
+
* True when applying the policies to a copy of the manifest changes nothing:
|
|
1486
|
+
* the committed file is a fresh generation. The drift-guard test's one call.
|
|
1487
|
+
*/
|
|
1488
|
+
declare function manifestIsFresh(manifest: ManifestLike, opts: ApplyPoliciesOptions): boolean;
|
|
1489
|
+
/** The manifest, serialised the way the repo commits it (2-space, trailing newline). */
|
|
1490
|
+
declare function serializeManifest(manifest: ManifestLike): string;
|
|
1491
|
+
|
|
1492
|
+
/**
|
|
1493
|
+
* Derive a confirmation policy from an action registry by NAME, for apps
|
|
1494
|
+
* whose registry carries no destructive flag: the verb prefix says what an
|
|
1495
|
+
* action does (`delete_`, `void_`, `cancel_`, `archive_`), so the policy
|
|
1496
|
+
* follows from a small table of verb rules instead of a hand-kept list
|
|
1497
|
+
* that drifts the moment an action is added.
|
|
1498
|
+
*
|
|
1499
|
+
* Each rule names a prefix (or regex), the tier it lands in, and how to
|
|
1500
|
+
* phrase the describe from the rest of the name. The first matching rule
|
|
1501
|
+
* wins, so put the specific ones first.
|
|
1502
|
+
*/
|
|
1503
|
+
|
|
1504
|
+
interface VerbRule {
|
|
1505
|
+
/** A name prefix (`'delete_'`) or a regex tested against the whole name. */
|
|
1506
|
+
match: string | RegExp;
|
|
1507
|
+
/** `irreversible` marks the card "cannot be undone"; `always` is a plain confirmation. */
|
|
1508
|
+
tier: 'irreversible' | 'always';
|
|
1509
|
+
/**
|
|
1510
|
+
* Describe for a matching action. Receives the full name and `rest`: the
|
|
1511
|
+
* name with the matched prefix removed and underscores turned to spaces
|
|
1512
|
+
* (for a regex match, the whole name spaced). No trailing period.
|
|
1513
|
+
*/
|
|
1514
|
+
describe: (name: string, rest: string) => string;
|
|
1515
|
+
}
|
|
1516
|
+
interface DerivePolicyOptions {
|
|
1517
|
+
/** Verb rules, most specific first. */
|
|
1518
|
+
rules: readonly VerbRule[];
|
|
1519
|
+
/** Actions to leave ungated even though a rule matches (returned in `ungated`). */
|
|
1520
|
+
exempt?: readonly string[];
|
|
1521
|
+
/** Explicit rules that win over the verb table (e.g. a hand-written describe). */
|
|
1522
|
+
overrides?: Readonly<Record<string, ConfirmRule>>;
|
|
1523
|
+
}
|
|
1524
|
+
interface DerivedPolicy {
|
|
1525
|
+
policy: ConfirmationPolicy & {
|
|
1526
|
+
actions: Record<string, ConfirmRule>;
|
|
1527
|
+
};
|
|
1528
|
+
/** Registry names a rule matched but `exempt` kept ungated. */
|
|
1529
|
+
ungated: string[];
|
|
1530
|
+
/** Registry names no rule matched (reads and writes the table does not classify). */
|
|
1531
|
+
unmatched: string[];
|
|
1532
|
+
}
|
|
1533
|
+
/** Build a policy over `registry` from the verb table; see the file comment. */
|
|
1534
|
+
declare function deriveConfirmationPolicy(registry: Iterable<string>, opts: DerivePolicyOptions): DerivedPolicy;
|
|
1535
|
+
/**
|
|
1536
|
+
* The verb table most registries need: hard deletes, voids, refunds and
|
|
1537
|
+
* cancellations cannot be undone; archives, removals from a parent and
|
|
1538
|
+
* unassignments are plain confirmations. `subject` names the system for
|
|
1539
|
+
* the describe ("in Xero").
|
|
1540
|
+
*/
|
|
1541
|
+
declare function commonDestructiveVerbs(subject: string): VerbRule[];
|
|
1542
|
+
|
|
1427
1543
|
/**
|
|
1428
1544
|
* @sprigr/apps-app-sdk
|
|
1429
1545
|
*
|
|
@@ -1824,4 +1940,4 @@ interface ExecutionContextLike {
|
|
|
1824
1940
|
}
|
|
1825
1941
|
type HandlerFn<TArgs, TResult> = (args: TArgs, env: Record<string, unknown>, ctx?: ExecutionContextLike) => Promise<TResult>;
|
|
1826
1942
|
|
|
1827
|
-
export { APPROVAL_GRANTED_KEY, type Actor, type AppApprovalEnvelope, type AppFileListItem, type AppFileUrlArgs, type AppFileUrlResult, type AppFilesEnv, type AppUndoEnvelope, type ApprovalGateOptions, type ApprovalSpec, type ArchiveOfferOptions, type ArchiveOfferRefusal, type CaptureJournal, type ConfirmCondition, type ConfirmRule, type ConfirmationPolicy, type D1Like, type D1PreparedStatementLike, DEFAULT_EMIT_TIMEOUT_MS, DEFAULT_MAX_FILE_BYTES, DEFAULT_MONEY_FIELDS, DEFAULT_WRITE_PREFIXES, type DispatcherApprovalGate, type DispatcherApprovalOptions, type EmitMarketplaceEventOptions, type EmitResult, type EmitTransport, type EventArgs, type ExecutionContextLike, type FetchFileResult, type FetchWithRetryOptions, type GetAppFileResult, type HandlerFn, type InboxOwner, type InstallBridge, InvalidUndoEnvelopeError, type JournalRowLike, type MarketplaceEventEmitOpts, type MarketplaceEventSourceIntegration, NotConnectedError, type OAuthBouncerSignals, type OAuthState, type OfferUndoArgs, type PlatformHostEnv, type PolicyCheckInput, type PolicySource, type PutAppFileArgs, type PutAppFileResult, type PutAppFileStreamArgs, type RequireApprovalOptions, type RestoreResult, type RestoreSpec, type ScheduleArgs, type SprigrDataAggregationCell, type SprigrDataAggregationResult, type SprigrDataAggregationSpec, type SprigrDataApi, type SprigrDataIndexOpts, type SprigrDataSearchOpts, type SprigrDataSearchResult, type SprigrFilesApi, type SprigrFilesApiWithEdit, type SprigrFilesCreateInput, type SprigrFilesCreateResult, type SprigrFilesEditInput, type SprigrFilesEditResult, type SprigrFilesJobResult, type ToolArgs, type ToolHandler, type ToolResult, type ToolWrapperOptions, UNIT_SEP, type UndoApplyOptions, type UndoApplyResult, type UndoCaptureSpec, type UndoFidelity, type WebhookArgs, type WfpBridgeEnv, type WfpEmitReply, actorKey, actorTool, appFileUrl, approvalHash, archiveOfferRefusal, base64ToBytes, buildConfirmationPolicy, buildMarketplaceWebhookUrl, bytesToBase64, canEmit, checkConfirmationPolicy, constantTimeEqual, createMarketplaceEmitter, createToolWrappers, decodeState, deleteAppFile, describeMissingBridge, dispatcherApproval, emitMarketplaceEvent, encodeState, fetchFileAsBase64, fetchFileBytes, fetchWithRetry, fullWarning, getAppFile, hmacSha256Hex, installTokenPost, isNotConnectedError, isStagingHost, listAppFiles, offerUndo, overlaySprigr, ownerFromActorKey, parseActor, putAppFile, putAppFileStream, randomHex, recreatedWarning, refuseWithoutForce, requireApproval, resolveInstallBridge, resolveOAuthBouncerBase, resolveOAuthBouncerCallbackUrl, resolvePlatformWebhookBase, runUndoApply, safeCapture, seq, set, tool, undoEnvelope, withSprigrEmitFallback };
|
|
1943
|
+
export { APPROVAL_GRANTED_KEY, type Actor, type AppApprovalEnvelope, type AppFileListItem, type AppFileUrlArgs, type AppFileUrlResult, type AppFilesEnv, type AppUndoEnvelope, type ApplyPoliciesOptions, type ApprovalGateOptions, type ApprovalSpec, type ArchiveOfferOptions, type ArchiveOfferRefusal, type CaptureJournal, type ConfirmCondition, type ConfirmRule, type ConfirmationPolicy, type D1Like, type D1PreparedStatementLike, DEFAULT_EMIT_TIMEOUT_MS, DEFAULT_MAX_FILE_BYTES, DEFAULT_MONEY_FIELDS, DEFAULT_WRITE_PREFIXES, type DerivePolicyOptions, type DerivedPolicy, type DispatcherApprovalGate, type DispatcherApprovalOptions, type EmitMarketplaceEventOptions, type EmitResult, type EmitTransport, type EventArgs, type ExecutionContextLike, type FetchFileResult, type FetchWithRetryOptions, type GetAppFileResult, type HandlerFn, type InboxOwner, type InstallBridge, InvalidUndoEnvelopeError, type JournalRowLike, type ManifestDispatchBlock, type ManifestLike, type ManifestToolLike, type MarketplaceEventEmitOpts, type MarketplaceEventSourceIntegration, NotConnectedError, type OAuthBouncerSignals, type OAuthState, type OfferUndoArgs, type PlatformHostEnv, type PolicyCheckInput, type PolicySource, type PutAppFileArgs, type PutAppFileResult, type PutAppFileStreamArgs, type RequireApprovalOptions, type RestoreResult, type RestoreSpec, type ScheduleArgs, type SprigrDataAggregationCell, type SprigrDataAggregationResult, type SprigrDataAggregationSpec, type SprigrDataApi, type SprigrDataIndexOpts, type SprigrDataSearchOpts, type SprigrDataSearchResult, type SprigrFilesApi, type SprigrFilesApiWithEdit, type SprigrFilesCreateInput, type SprigrFilesCreateResult, type SprigrFilesEditInput, type SprigrFilesEditResult, type SprigrFilesJobResult, type ToolArgs, type ToolHandler, type ToolResult, type ToolWrapperOptions, UNIT_SEP, type UndoApplyOptions, type UndoApplyResult, type UndoCaptureSpec, type UndoFidelity, type VerbRule, type WebhookArgs, type WfpBridgeEnv, type WfpEmitReply, actorKey, actorTool, appFileUrl, applyConfirmationPolicies, approvalHash, archiveOfferRefusal, base64ToBytes, buildConfirmationPolicy, buildMarketplaceWebhookUrl, bytesToBase64, canEmit, checkConfirmationPolicy, commonDestructiveVerbs, constantTimeEqual, createMarketplaceEmitter, createToolWrappers, decodeState, deleteAppFile, deriveConfirmationPolicy, describeMissingBridge, dispatcherApproval, emitMarketplaceEvent, encodeState, fetchFileAsBase64, fetchFileBytes, fetchWithRetry, fullWarning, getAppFile, hmacSha256Hex, installTokenPost, isNotConnectedError, isStagingHost, listAppFiles, manifestIsFresh, offerUndo, overlaySprigr, ownerFromActorKey, parseActor, putAppFile, putAppFileStream, randomHex, recreatedWarning, refuseWithoutForce, requireApproval, resolveInstallBridge, resolveOAuthBouncerBase, resolveOAuthBouncerCallbackUrl, resolvePlatformWebhookBase, runUndoApply, safeCapture, seq, serializeManifest, set, tool, undoEnvelope, withSprigrEmitFallback };
|
package/dist/index.js
CHANGED
|
@@ -940,6 +940,98 @@ function checkConfirmationPolicy(input) {
|
|
|
940
940
|
}
|
|
941
941
|
return out.sort();
|
|
942
942
|
}
|
|
943
|
+
|
|
944
|
+
// src/write-protection/manifest.ts
|
|
945
|
+
function applyConfirmationPolicies(manifest, opts) {
|
|
946
|
+
const tools = manifest.tools ?? [];
|
|
947
|
+
const byName = new Map(tools.map((t) => [t.name, t]));
|
|
948
|
+
const touched = [];
|
|
949
|
+
for (const [name, policy] of Object.entries(opts.policies)) {
|
|
950
|
+
const tool2 = byName.get(name);
|
|
951
|
+
if (!tool2) throw new Error(`applyConfirmationPolicies: no tool named '${name}' in the manifest`);
|
|
952
|
+
tool2.confirmation = policy;
|
|
953
|
+
touched.push(name);
|
|
954
|
+
}
|
|
955
|
+
for (const [name, block] of Object.entries(opts.dispatch ?? {})) {
|
|
956
|
+
const tool2 = byName.get(name);
|
|
957
|
+
if (!tool2) throw new Error(`applyConfirmationPolicies: no tool named '${name}' in the manifest (dispatch)`);
|
|
958
|
+
tool2.dispatch = block;
|
|
959
|
+
if (!touched.includes(name)) touched.push(name);
|
|
960
|
+
}
|
|
961
|
+
const stripLegacy = opts.stripLegacyFlag ?? true;
|
|
962
|
+
const dropConfirm = opts.dropConfirmParam ?? true;
|
|
963
|
+
for (const tool2 of tools) {
|
|
964
|
+
if (stripLegacy) delete tool2.confirmation_required;
|
|
965
|
+
if (dropConfirm) {
|
|
966
|
+
const schema = tool2.input_schema;
|
|
967
|
+
if (schema?.properties && "confirm" in schema.properties) delete schema.properties.confirm;
|
|
968
|
+
if (schema?.required?.includes("confirm")) {
|
|
969
|
+
schema.required = schema.required.filter((k) => k !== "confirm");
|
|
970
|
+
if (schema.required.length === 0) delete schema.required;
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
return touched;
|
|
975
|
+
}
|
|
976
|
+
function manifestIsFresh(manifest, opts) {
|
|
977
|
+
const copy = JSON.parse(JSON.stringify(manifest));
|
|
978
|
+
applyConfirmationPolicies(copy, opts);
|
|
979
|
+
return JSON.stringify(copy) === JSON.stringify(manifest);
|
|
980
|
+
}
|
|
981
|
+
function serializeManifest(manifest) {
|
|
982
|
+
return `${JSON.stringify(manifest, null, 2)}
|
|
983
|
+
`;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
// src/write-protection/derive-policy.ts
|
|
987
|
+
function spaced(s) {
|
|
988
|
+
return s.replace(/_/g, " ");
|
|
989
|
+
}
|
|
990
|
+
function matchRule(name, rule) {
|
|
991
|
+
if (typeof rule.match === "string") {
|
|
992
|
+
return name.startsWith(rule.match) ? spaced(name.slice(rule.match.length)) : null;
|
|
993
|
+
}
|
|
994
|
+
return rule.match.test(name) ? spaced(name) : null;
|
|
995
|
+
}
|
|
996
|
+
function deriveConfirmationPolicy(registry, opts) {
|
|
997
|
+
const exempt = new Set(opts.exempt ?? []);
|
|
998
|
+
const irreversible = {};
|
|
999
|
+
const always = {};
|
|
1000
|
+
const ungated = [];
|
|
1001
|
+
const unmatched = [];
|
|
1002
|
+
const overrides = opts.overrides ?? {};
|
|
1003
|
+
for (const name of registry) {
|
|
1004
|
+
if (overrides[name]) continue;
|
|
1005
|
+
let hit = false;
|
|
1006
|
+
for (const rule of opts.rules) {
|
|
1007
|
+
const rest = matchRule(name, rule);
|
|
1008
|
+
if (rest == null) continue;
|
|
1009
|
+
hit = true;
|
|
1010
|
+
if (exempt.has(name)) ungated.push(name);
|
|
1011
|
+
else (rule.tier === "irreversible" ? irreversible : always)[name] = rule.describe(name, rest);
|
|
1012
|
+
break;
|
|
1013
|
+
}
|
|
1014
|
+
if (!hit) unmatched.push(name);
|
|
1015
|
+
}
|
|
1016
|
+
const policy = buildConfirmationPolicy({ irreversible, always, rules: overrides });
|
|
1017
|
+
return { policy, ungated, unmatched };
|
|
1018
|
+
}
|
|
1019
|
+
function commonDestructiveVerbs(subject) {
|
|
1020
|
+
const where = subject ? ` ${subject}` : "";
|
|
1021
|
+
return [
|
|
1022
|
+
{ match: "delete_", tier: "irreversible", describe: (_n, rest) => `Delete ${rest}${where}` },
|
|
1023
|
+
{ match: "void_", tier: "irreversible", describe: (_n, rest) => `Void ${rest}${where}` },
|
|
1024
|
+
{ match: "refund_", tier: "irreversible", describe: (_n, rest) => `Refund ${rest}${where}` },
|
|
1025
|
+
{ match: "cancel_", tier: "irreversible", describe: (_n, rest) => `Cancel ${rest}${where}` },
|
|
1026
|
+
{ match: "destroy_", tier: "irreversible", describe: (_n, rest) => `Destroy ${rest}${where}` },
|
|
1027
|
+
{ match: "purge_", tier: "irreversible", describe: (_n, rest) => `Purge ${rest}${where}` },
|
|
1028
|
+
{ match: "revoke_", tier: "irreversible", describe: (_n, rest) => `Revoke ${rest}${where}` },
|
|
1029
|
+
{ match: "archive_", tier: "always", describe: (_n, rest) => `Archive ${rest}${where}` },
|
|
1030
|
+
{ match: "remove_", tier: "always", describe: (_n, rest) => `Remove ${rest}${where}` },
|
|
1031
|
+
{ match: "unassign_", tier: "always", describe: (_n, rest) => `Unassign ${rest}${where}` },
|
|
1032
|
+
{ match: "deactivate_", tier: "always", describe: (_n, rest) => `Deactivate ${rest}${where}` }
|
|
1033
|
+
];
|
|
1034
|
+
}
|
|
943
1035
|
export {
|
|
944
1036
|
APPROVAL_GRANTED_KEY,
|
|
945
1037
|
DEFAULT_EMIT_TIMEOUT_MS,
|
|
@@ -952,6 +1044,7 @@ export {
|
|
|
952
1044
|
actorKey,
|
|
953
1045
|
actorTool,
|
|
954
1046
|
appFileUrl,
|
|
1047
|
+
applyConfirmationPolicies,
|
|
955
1048
|
approvalHash,
|
|
956
1049
|
archiveOfferRefusal,
|
|
957
1050
|
base64ToBytes,
|
|
@@ -960,11 +1053,13 @@ export {
|
|
|
960
1053
|
bytesToBase64,
|
|
961
1054
|
canEmit,
|
|
962
1055
|
checkConfirmationPolicy,
|
|
1056
|
+
commonDestructiveVerbs,
|
|
963
1057
|
constantTimeEqual,
|
|
964
1058
|
createMarketplaceEmitter,
|
|
965
1059
|
createToolWrappers,
|
|
966
1060
|
decodeState,
|
|
967
1061
|
deleteAppFile,
|
|
1062
|
+
deriveConfirmationPolicy,
|
|
968
1063
|
describeMissingBridge,
|
|
969
1064
|
dispatcherApproval,
|
|
970
1065
|
emitMarketplaceEvent,
|
|
@@ -979,6 +1074,7 @@ export {
|
|
|
979
1074
|
isNotConnectedError,
|
|
980
1075
|
isStagingHost,
|
|
981
1076
|
listAppFiles,
|
|
1077
|
+
manifestIsFresh,
|
|
982
1078
|
offerUndo,
|
|
983
1079
|
overlaySprigr,
|
|
984
1080
|
ownerFromActorKey,
|
|
@@ -996,6 +1092,7 @@ export {
|
|
|
996
1092
|
runUndoApply,
|
|
997
1093
|
safeCapture,
|
|
998
1094
|
seq,
|
|
1095
|
+
serializeManifest,
|
|
999
1096
|
set,
|
|
1000
1097
|
tool,
|
|
1001
1098
|
undoEnvelope,
|