@com55/node-red-ai-access 0.2.0-beta.6 → 0.2.0-beta.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/CHANGELOG.md +18 -0
- package/README.md +50 -10
- package/ai-access.html +1 -1
- package/dist/plugin.cjs +464 -312
- package/dist/plugin.d.cts +86 -6
- package/docs/api/openapi.yaml +228 -30
- package/docs/errors.md +16 -5
- package/docs/mcp-tools.md +18 -7
- package/package.json +1 -1
package/dist/plugin.d.cts
CHANGED
|
@@ -217,7 +217,29 @@ declare class AuditLog {
|
|
|
217
217
|
* Proposal JSON store under `.ai-access/proposals/`.
|
|
218
218
|
*/
|
|
219
219
|
|
|
220
|
-
type ProposalStatus = 'pending' | 'approved' | 'rejected' | 'failed' | 'withdrawn' | 'expired' | 'superseded';
|
|
220
|
+
type ProposalStatus = 'pending' | 'conflict' | 'approved' | 'rejected' | 'failed' | 'withdrawn' | 'expired' | 'superseded';
|
|
221
|
+
/** One stored rebase conflict (field names only — never values). */
|
|
222
|
+
interface StoredRebaseConflict {
|
|
223
|
+
kind: string;
|
|
224
|
+
nodeId?: string;
|
|
225
|
+
nodeType?: string;
|
|
226
|
+
nodeName?: string;
|
|
227
|
+
fields?: string[];
|
|
228
|
+
message: string;
|
|
229
|
+
choices: ('live' | 'proposed')[];
|
|
230
|
+
}
|
|
231
|
+
/** Result of the last auto-rebase check against the live flows. */
|
|
232
|
+
interface ProposalRebaseInfo {
|
|
233
|
+
/** Live rev the check ran against. */
|
|
234
|
+
rev: string;
|
|
235
|
+
state: 'clean' | 'merged' | 'conflict';
|
|
236
|
+
/** Hash of the tab approve would deploy (absent on conflict). */
|
|
237
|
+
mergedHash?: string;
|
|
238
|
+
conflicts: StoredRebaseConflict[];
|
|
239
|
+
/** Nodes (or the tab) changed live since the base snapshot. */
|
|
240
|
+
liveChangedCount: number;
|
|
241
|
+
checkedAt: string;
|
|
242
|
+
}
|
|
221
243
|
type ProposalType = 'replace_flow';
|
|
222
244
|
interface AccessLinkProposalCreator {
|
|
223
245
|
kind: 'access-link';
|
|
@@ -273,6 +295,14 @@ interface Proposal {
|
|
|
273
295
|
nodeId: string;
|
|
274
296
|
property: string;
|
|
275
297
|
}[];
|
|
298
|
+
/** Persisted while a timed-out runtime deployment has an unknown outcome. */
|
|
299
|
+
deployPending?: {
|
|
300
|
+
mergedHash: string;
|
|
301
|
+
reason: string;
|
|
302
|
+
queueReleased?: boolean;
|
|
303
|
+
};
|
|
304
|
+
/** Last auto-rebase result (pending / conflict proposals). */
|
|
305
|
+
rebase?: ProposalRebaseInfo;
|
|
276
306
|
/**
|
|
277
307
|
* Short human title (≤80 chars). Set at submit (AI-supplied or auto-generated).
|
|
278
308
|
* Optional on legacy proposals; API responses always resolve a title.
|
|
@@ -329,15 +359,38 @@ declare class ProposalStore {
|
|
|
329
359
|
*/
|
|
330
360
|
beginApprove(id: string): Proposal;
|
|
331
361
|
releaseLock(id: string): void;
|
|
362
|
+
recordDeployPending(id: string, pending: Proposal['deployPending']): Proposal;
|
|
332
363
|
markApproved(id: string, decidedBy: ProposalDecisionBy, resultRev: string): Proposal;
|
|
333
364
|
markRejected(id: string, decidedBy: ProposalDecisionBy | undefined, reason: string): Proposal;
|
|
334
365
|
markFailed(id: string, decidedBy: ProposalDecisionBy | undefined, reason: string): Proposal;
|
|
335
366
|
markWithdrawn(id: string, decidedBy: ProposalDecisionBy, reason: string): Proposal;
|
|
336
367
|
/**
|
|
337
368
|
* Auto-close a pending proposal that can no longer be approved
|
|
338
|
-
* (`expired` =
|
|
369
|
+
* (`expired` = pending longer than the proposal TTL).
|
|
339
370
|
*/
|
|
340
371
|
markClosed(id: string, status: 'expired' | 'superseded', reason: string, decidedBy?: ProposalDecisionBy): Proposal;
|
|
372
|
+
/**
|
|
373
|
+
* Store the latest rebase check and flip `pending` ↔ `conflict`.
|
|
374
|
+
* No-op (no write) when nothing changed. Skips decided or in-flight records.
|
|
375
|
+
*/
|
|
376
|
+
recordRebase(id: string, info: ProposalRebaseInfo): Proposal;
|
|
377
|
+
/**
|
|
378
|
+
* Replace an open proposal's base snapshot and/or body (conflict resolution).
|
|
379
|
+
* Status returns to `pending`; the caller re-runs the rebase.
|
|
380
|
+
*/
|
|
381
|
+
updateBody(id: string, update: {
|
|
382
|
+
baseFlow: Partial<FlowTab>;
|
|
383
|
+
flow: Partial<FlowTab>;
|
|
384
|
+
expectedRev?: string;
|
|
385
|
+
title?: string;
|
|
386
|
+
restoredSecrets?: {
|
|
387
|
+
nodeId: string;
|
|
388
|
+
property: string;
|
|
389
|
+
}[];
|
|
390
|
+
warnings?: ProposalWarning[];
|
|
391
|
+
}): Proposal;
|
|
392
|
+
/** True while an approve holds this proposal's lock. */
|
|
393
|
+
isApproving(id: string): boolean;
|
|
341
394
|
}
|
|
342
395
|
|
|
343
396
|
/**
|
|
@@ -357,6 +410,7 @@ interface PersistedAiAccessSettings {
|
|
|
357
410
|
enabled?: boolean;
|
|
358
411
|
allowWithoutAdminAuth?: boolean;
|
|
359
412
|
defaultLinkTtlSeconds?: number;
|
|
413
|
+
proposalTtlDays?: number;
|
|
360
414
|
defaultDeploymentType?: DeploymentType;
|
|
361
415
|
maxActiveLinks?: number;
|
|
362
416
|
publicBaseUrl?: string;
|
|
@@ -378,6 +432,7 @@ interface EffectiveAiAccessSettings {
|
|
|
378
432
|
enabled: boolean;
|
|
379
433
|
allowWithoutAdminAuth: boolean;
|
|
380
434
|
defaultLinkTtlSeconds: number;
|
|
435
|
+
proposalTtlDays: number;
|
|
381
436
|
defaultDeploymentType: DeploymentType;
|
|
382
437
|
maxActiveLinks: number;
|
|
383
438
|
publicBaseUrl: string;
|
|
@@ -463,15 +518,32 @@ type CatalogueFetcher = (url: string, etag?: string) => Promise<{
|
|
|
463
518
|
* Approve / reject proposals through the FlowAdapter with audit logging.
|
|
464
519
|
*/
|
|
465
520
|
|
|
521
|
+
interface ProposalServiceOptions {
|
|
522
|
+
/** Maximum time to await updateFlow (default 60 seconds). Does not cancel the runtime operation. */
|
|
523
|
+
deployTimeoutMs?: number;
|
|
524
|
+
/** Maximum time waiting to start approval (default 60 seconds). */
|
|
525
|
+
approveQueueTimeoutMs?: number;
|
|
526
|
+
/** Release the queue after this much unsettled deployment time (default 5 minutes). */
|
|
527
|
+
deployHardCapMs?: number;
|
|
528
|
+
}
|
|
529
|
+
interface ApproveOptions {
|
|
530
|
+
deploymentType?: DeploymentType;
|
|
531
|
+
/**
|
|
532
|
+
* `mergedHash` the reviewer last saw. Required when the rebase merged live
|
|
533
|
+
* changes into the proposal; when supplied it must match the current merge.
|
|
534
|
+
*/
|
|
535
|
+
mergedHash?: string;
|
|
536
|
+
}
|
|
466
537
|
declare class ProposalService {
|
|
467
538
|
private readonly proposals;
|
|
468
539
|
private readonly audit;
|
|
469
540
|
private readonly adapter;
|
|
470
541
|
private readonly getDeploymentType;
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
542
|
+
private readonly serviceOptions;
|
|
543
|
+
constructor(proposals: ProposalStore, audit: AuditLog, adapter: FlowAdapter, getDeploymentType: () => DeploymentType, serviceOptions?: ProposalServiceOptions);
|
|
544
|
+
approve(id: string, decidedBy: ProposalDecisionBy, options?: ApproveOptions): Promise<Proposal>;
|
|
545
|
+
private approveLocked;
|
|
546
|
+
private withDeployDeadline;
|
|
475
547
|
reject(id: string, decidedBy: ProposalDecisionBy, reason: string): Proposal;
|
|
476
548
|
}
|
|
477
549
|
|
|
@@ -495,10 +567,17 @@ interface AiAccessSettings {
|
|
|
495
567
|
allowWithoutAdminAuth?: boolean;
|
|
496
568
|
/** Default access-link TTL in seconds (default: 14400 = 4h). */
|
|
497
569
|
defaultLinkTtlSeconds?: number;
|
|
570
|
+
proposalTtlDays?: number;
|
|
498
571
|
/** Maximum concurrent active access links (default: 10). */
|
|
499
572
|
maxActiveLinks?: number;
|
|
500
573
|
/** Deployment type used when approving proposals (default: nodes). */
|
|
501
574
|
defaultDeploymentType?: 'nodes' | 'flows' | 'full';
|
|
575
|
+
/** Approval queue waiting deadline in milliseconds (default 60,000). */
|
|
576
|
+
approveQueueTimeoutMs?: number;
|
|
577
|
+
/** Deployment caller deadline in milliseconds (default 60,000). */
|
|
578
|
+
deployTimeoutMs?: number;
|
|
579
|
+
/** Release an unsettled deployment's approval queue after this duration (default 300,000 ms). */
|
|
580
|
+
deployHardCapMs?: number;
|
|
502
581
|
/**
|
|
503
582
|
* Rate-limit window in milliseconds for `/mcp/api` and `/mcp/a`
|
|
504
583
|
* (default: 60000).
|
|
@@ -643,6 +722,7 @@ interface NodeRedPluginAPI {
|
|
|
643
722
|
};
|
|
644
723
|
events?: {
|
|
645
724
|
on(event: string, listener: () => void): void;
|
|
725
|
+
removeListener?(event: string, listener: () => void): void;
|
|
646
726
|
};
|
|
647
727
|
auth: NodeRedAuthAPI;
|
|
648
728
|
plugins: {
|
package/docs/api/openapi.yaml
CHANGED
|
@@ -173,7 +173,7 @@ info:
|
|
|
173
173
|
(`invalid_filter` for list filters, `invalid_arguments` otherwise).
|
|
174
174
|
|
|
175
175
|
On submit and on **get** of a **pending** proposal, responses may include
|
|
176
|
-
`conflictingProposals` (
|
|
176
|
+
`conflictingProposals` (open proposals with overlapping node edits)
|
|
177
177
|
with optional `overlappingNodeIds` / `overlappingFields`. List endpoints
|
|
178
178
|
omit `conflictingProposals`. Submit, get, and list responses include
|
|
179
179
|
`autoClosedCount` when that request auto-closed pending proposals as
|
|
@@ -184,10 +184,10 @@ info:
|
|
|
184
184
|
`N validation errors` and every entry in `errors[]`. Duplicate node ids in a
|
|
185
185
|
full tab or batch return `duplicate_node_id`.
|
|
186
186
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
187
|
+
Open proposals (`pending` or `conflict`) auto-rebase when live revisions change.
|
|
188
|
+
Conflicts block approval until resolved. Proposals expire only after the
|
|
189
|
+
configured `proposalTtlDays` (default 7). Approval rebases other open proposals;
|
|
190
|
+
legacy expired, rejected and superseded proposals stay closed.
|
|
191
191
|
|
|
192
192
|
## Withdraw
|
|
193
193
|
|
|
@@ -963,10 +963,9 @@ paths:
|
|
|
963
963
|
flow tab. Does not mutate Node-RED until an admin with `*` approves.
|
|
964
964
|
|
|
965
965
|
Include `expectedRev` (preferred; `expected_rev` accepted as alias)
|
|
966
|
-
from
|
|
967
|
-
|
|
968
|
-
`version_mismatch
|
|
969
|
-
proposal `rejected` with a resubmit message (no deploy).
|
|
966
|
+
from `GET /flows` or `GET /flows/{id}`. Recent known stale revisions are
|
|
967
|
+
accepted using snapshot history and rebased immediately. Unknown revisions
|
|
968
|
+
return **409** `version_mismatch`. Approval rechecks against the live revision.
|
|
970
969
|
|
|
971
970
|
Optional `title` (≤80 chars) is stored as a human-friendly label; when
|
|
972
971
|
omitted it is auto-generated from the change set. Responses always
|
|
@@ -1055,14 +1054,14 @@ paths:
|
|
|
1055
1054
|
(long text fields listed for expandable UI). Modified nodes include
|
|
1056
1055
|
line-level diffs for text fields such as `func` and `template`.
|
|
1057
1056
|
|
|
1058
|
-
`baseRevStale` is true only for **
|
|
1059
|
-
`expectedRev` does not match the live revision (
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1057
|
+
`baseRevStale` is true only for **open** proposals when
|
|
1058
|
+
`expectedRev` does not match the live revision (informational).
|
|
1059
|
+
Closed proposals never set `baseRevStale`. Open proposals include
|
|
1060
|
+
`rebase` and `liveChanges` (editor changes since the stored base).
|
|
1061
|
+
Without conflicts, `vsCurrent` compares the merged deployment to live.
|
|
1062
|
+
Merges use raw stored proposals and live flows; AI diff sections are redacted.
|
|
1063
1063
|
|
|
1064
|
-
Admins with `read` can diff any proposal.
|
|
1065
|
-
diff proposals they created.
|
|
1064
|
+
Admins with `read` and access-link clients can diff any proposal.
|
|
1066
1065
|
operationId: getProposalDiff
|
|
1067
1066
|
security:
|
|
1068
1067
|
- AccessLinkBearer: []
|
|
@@ -1085,6 +1084,97 @@ paths:
|
|
|
1085
1084
|
'404':
|
|
1086
1085
|
$ref: '#/components/responses/NotFound'
|
|
1087
1086
|
|
|
1087
|
+
/proposals/{id}/resolve-conflict:
|
|
1088
|
+
post:
|
|
1089
|
+
tags: [Proposals]
|
|
1090
|
+
summary: Choose live or proposed for one current conflict
|
|
1091
|
+
operationId: resolveProposalConflict
|
|
1092
|
+
security:
|
|
1093
|
+
- AdminSession: []
|
|
1094
|
+
parameters:
|
|
1095
|
+
- $ref: '#/components/parameters/ProposalId'
|
|
1096
|
+
requestBody:
|
|
1097
|
+
required: true
|
|
1098
|
+
content:
|
|
1099
|
+
application/json:
|
|
1100
|
+
schema:
|
|
1101
|
+
$ref: '#/components/schemas/ConflictChoiceRequest'
|
|
1102
|
+
responses:
|
|
1103
|
+
'200':
|
|
1104
|
+
description: Presented proposal with current rebase metadata
|
|
1105
|
+
content:
|
|
1106
|
+
application/json:
|
|
1107
|
+
schema:
|
|
1108
|
+
$ref: '#/components/schemas/Proposal'
|
|
1109
|
+
'400':
|
|
1110
|
+
$ref: '#/components/responses/BadRequest'
|
|
1111
|
+
'401':
|
|
1112
|
+
$ref: '#/components/responses/Unauthorized'
|
|
1113
|
+
'403':
|
|
1114
|
+
$ref: '#/components/responses/Forbidden'
|
|
1115
|
+
'404':
|
|
1116
|
+
description: Proposal not found
|
|
1117
|
+
'409':
|
|
1118
|
+
description: Proposal closed, conflict missing, or revision unavailable
|
|
1119
|
+
/proposals/{id}/recheck:
|
|
1120
|
+
post:
|
|
1121
|
+
tags: [Proposals]
|
|
1122
|
+
summary: Recheck an open proposal against raw live flows
|
|
1123
|
+
operationId: recheckProposal
|
|
1124
|
+
security:
|
|
1125
|
+
- AdminSession: []
|
|
1126
|
+
parameters:
|
|
1127
|
+
- $ref: '#/components/parameters/ProposalId'
|
|
1128
|
+
responses:
|
|
1129
|
+
'200':
|
|
1130
|
+
description: Presented proposal with current rebase metadata
|
|
1131
|
+
content:
|
|
1132
|
+
application/json:
|
|
1133
|
+
schema:
|
|
1134
|
+
$ref: '#/components/schemas/Proposal'
|
|
1135
|
+
'400':
|
|
1136
|
+
$ref: '#/components/responses/BadRequest'
|
|
1137
|
+
'401':
|
|
1138
|
+
$ref: '#/components/responses/Unauthorized'
|
|
1139
|
+
'403':
|
|
1140
|
+
$ref: '#/components/responses/Forbidden'
|
|
1141
|
+
'404':
|
|
1142
|
+
description: Proposal not found
|
|
1143
|
+
'409':
|
|
1144
|
+
description: Proposal closed, conflict missing, or revision unavailable
|
|
1145
|
+
/proposals/{id}/resolve:
|
|
1146
|
+
post:
|
|
1147
|
+
tags: [Proposals]
|
|
1148
|
+
summary: Resolve your own open proposal with a fresh payload
|
|
1149
|
+
operationId: resolveProposal
|
|
1150
|
+
security:
|
|
1151
|
+
- AccessLinkBearer: []
|
|
1152
|
+
parameters:
|
|
1153
|
+
- $ref: '#/components/parameters/ProposalId'
|
|
1154
|
+
requestBody:
|
|
1155
|
+
required: true
|
|
1156
|
+
content:
|
|
1157
|
+
application/json:
|
|
1158
|
+
schema:
|
|
1159
|
+
$ref: '#/components/schemas/ResolveProposalRequest'
|
|
1160
|
+
responses:
|
|
1161
|
+
'200':
|
|
1162
|
+
description: Presented proposal with current rebase metadata
|
|
1163
|
+
content:
|
|
1164
|
+
application/json:
|
|
1165
|
+
schema:
|
|
1166
|
+
$ref: '#/components/schemas/Proposal'
|
|
1167
|
+
'400':
|
|
1168
|
+
$ref: '#/components/responses/BadRequest'
|
|
1169
|
+
'401':
|
|
1170
|
+
$ref: '#/components/responses/Unauthorized'
|
|
1171
|
+
'403':
|
|
1172
|
+
$ref: '#/components/responses/Forbidden'
|
|
1173
|
+
'404':
|
|
1174
|
+
description: Proposal not found
|
|
1175
|
+
'409':
|
|
1176
|
+
description: Proposal closed, conflict missing, or revision unavailable
|
|
1177
|
+
|
|
1088
1178
|
/proposals/{id}/approve:
|
|
1089
1179
|
post:
|
|
1090
1180
|
tags: [Proposals]
|
|
@@ -1094,9 +1184,12 @@ paths:
|
|
|
1094
1184
|
Optional body `deploymentType`: `nodes` (default from settings),
|
|
1095
1185
|
`flows`, or `full`.
|
|
1096
1186
|
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1187
|
+
Auto-rebases against raw live flows and deploys the merged tab with the live rev.
|
|
1188
|
+
Conflicts return 409 `proposal_conflict` with field-name-only `conflicts`.
|
|
1189
|
+
Merged editor changes require `mergedHash` from the reviewed diff; otherwise
|
|
1190
|
+
409 `rebase_ack_required` includes the current hash. Revision races retry
|
|
1191
|
+
up to three times then return 409 `rebase_race`, leaving the proposal open.
|
|
1192
|
+
Other deploy errors mark it `failed`. Concurrent approval returns 409.
|
|
1100
1193
|
operationId: approveProposal
|
|
1101
1194
|
security:
|
|
1102
1195
|
- AdminSession: []
|
|
@@ -1181,7 +1274,7 @@ paths:
|
|
|
1181
1274
|
tags: [Proposals]
|
|
1182
1275
|
summary: Withdraw own pending proposal
|
|
1183
1276
|
description: |
|
|
1184
|
-
Access-link Bearer only. Withdraws
|
|
1277
|
+
Access-link Bearer only. Withdraws an **open** (pending or conflict) proposal created by
|
|
1185
1278
|
this token. Status becomes `withdrawn` (not `rejected`). Optional
|
|
1186
1279
|
JSON body `{ "reason": "…" }`.
|
|
1187
1280
|
operationId: withdrawProposal
|
|
@@ -1369,8 +1462,8 @@ components:
|
|
|
1369
1462
|
code: access_link_invalid
|
|
1370
1463
|
VersionConflict:
|
|
1371
1464
|
description: |
|
|
1372
|
-
`expectedRev`
|
|
1373
|
-
(`code: version_mismatch`). Re-read flows and resubmit.
|
|
1465
|
+
`expectedRev` is unavailable in recent snapshot history and does not
|
|
1466
|
+
match the live revision (`code: version_mismatch`). Re-read flows and resubmit.
|
|
1374
1467
|
content:
|
|
1375
1468
|
application/json:
|
|
1376
1469
|
schema:
|
|
@@ -1456,6 +1549,14 @@ components:
|
|
|
1456
1549
|
description: |
|
|
1457
1550
|
Machine-readable `code` on JSON error bodies. See docs/errors.md.
|
|
1458
1551
|
|
|
1552
|
+
- `approve_queue_timeout` (504): Approval exceeded its queue-wait deadline (default 60 seconds); no deployment was started for this request
|
|
1553
|
+
- `deploy_timeout` (504): Deployment exceeded its deadline (default 60 seconds); approvals wait for settlement or the hard cap (default 5 minutes)
|
|
1554
|
+
- `proposal_conflict` (409): Editor and proposal changes conflict (conflicts contains field names only)
|
|
1555
|
+
- `rebase_ack_required` (409): The merged result needs review (mergedHash included)
|
|
1556
|
+
- `rebase_race` (409): Flows kept changing during approval
|
|
1557
|
+
- `conflict_not_found` (409): The requested conflict no longer exists
|
|
1558
|
+
- `invalid_choice` (400): Invalid manual conflict choice
|
|
1559
|
+
- `flow_id_mismatch` (400): Resolution tried to change the proposal flow id
|
|
1459
1560
|
- `access_link_missing` (401): No Bearer / path token
|
|
1460
1561
|
- `bearer_scheme_required` (401): Auth used Basic (or non-Bearer)
|
|
1461
1562
|
- `token_expired` (401): Known token past `expiresAt` (body includes `expiredAt` and `accessLinksUrl`)
|
|
@@ -1481,9 +1582,9 @@ components:
|
|
|
1481
1582
|
- `invalid_diff_format` (400): Unknown `format` on proposal diff
|
|
1482
1583
|
- `confirm_required` (400): GET withdraw without `confirm=1`
|
|
1483
1584
|
- `missing_expected_rev` (400): Proposal submitted without `expectedRev` / `rev`
|
|
1484
|
-
- `version_mismatch` (409):
|
|
1585
|
+
- `version_mismatch` (409): Unknown or unavailable `rev` / `expectedRev` snapshot
|
|
1485
1586
|
- `proposal_not_found` (404): Unknown proposal id
|
|
1486
|
-
- `proposal_not_pending` (409): Action needs
|
|
1587
|
+
- `proposal_not_pending` (409): Action needs an open proposal
|
|
1487
1588
|
- `proposal_not_owned` (403): Withdraw of another token’s proposal
|
|
1488
1589
|
- `validation_failed` (400): Several validation errors at once (message `N validation errors`)
|
|
1489
1590
|
- `duplicate_node_id` (400): The same node id appears more than once in a full tab or batch
|
|
@@ -1509,6 +1610,14 @@ components:
|
|
|
1509
1610
|
- `upstream_error` (502): Node-RED runtime/flows error
|
|
1510
1611
|
- `internal_error` (500): Unexpected server error
|
|
1511
1612
|
enum:
|
|
1613
|
+
- approve_queue_timeout
|
|
1614
|
+
- deploy_timeout
|
|
1615
|
+
- proposal_conflict
|
|
1616
|
+
- rebase_ack_required
|
|
1617
|
+
- rebase_race
|
|
1618
|
+
- conflict_not_found
|
|
1619
|
+
- invalid_choice
|
|
1620
|
+
- flow_id_mismatch
|
|
1512
1621
|
- access_link_missing
|
|
1513
1622
|
- bearer_scheme_required
|
|
1514
1623
|
- token_expired
|
|
@@ -1737,6 +1846,11 @@ components:
|
|
|
1737
1846
|
AiAccessSettingsPatch:
|
|
1738
1847
|
type: object
|
|
1739
1848
|
properties:
|
|
1849
|
+
proposalTtlDays:
|
|
1850
|
+
type: integer
|
|
1851
|
+
minimum: 1
|
|
1852
|
+
maximum: 365
|
|
1853
|
+
default: 7
|
|
1740
1854
|
enabled:
|
|
1741
1855
|
type: boolean
|
|
1742
1856
|
allowWithoutAdminAuth:
|
|
@@ -2184,9 +2298,46 @@ components:
|
|
|
2184
2298
|
additionalProperties: true
|
|
2185
2299
|
additionalProperties: true
|
|
2186
2300
|
|
|
2301
|
+
ProposalRebase:
|
|
2302
|
+
type: object
|
|
2303
|
+
description: Merge metadata; conflicts contain field names only, never field values.
|
|
2304
|
+
properties:
|
|
2305
|
+
state:
|
|
2306
|
+
type: string
|
|
2307
|
+
enum: [clean, merged, conflict]
|
|
2308
|
+
rev:
|
|
2309
|
+
type: string
|
|
2310
|
+
mergedHash:
|
|
2311
|
+
type: string
|
|
2312
|
+
conflicts:
|
|
2313
|
+
type: array
|
|
2314
|
+
items:
|
|
2315
|
+
type: object
|
|
2316
|
+
properties:
|
|
2317
|
+
nodeId:
|
|
2318
|
+
type: string
|
|
2319
|
+
kind:
|
|
2320
|
+
type: string
|
|
2321
|
+
fields:
|
|
2322
|
+
type: array
|
|
2323
|
+
items:
|
|
2324
|
+
type: string
|
|
2325
|
+
message:
|
|
2326
|
+
type: string
|
|
2327
|
+
choices:
|
|
2328
|
+
type: array
|
|
2329
|
+
items:
|
|
2330
|
+
type: string
|
|
2331
|
+
enum: [live, proposed]
|
|
2332
|
+
liveChangedCount:
|
|
2333
|
+
type: integer
|
|
2334
|
+
checkedAt:
|
|
2335
|
+
type: string
|
|
2336
|
+
format: date-time
|
|
2337
|
+
|
|
2187
2338
|
ProposalStatus:
|
|
2188
2339
|
type: string
|
|
2189
|
-
enum: [pending, approved, rejected, failed, withdrawn, expired, superseded]
|
|
2340
|
+
enum: [pending, conflict, approved, rejected, failed, withdrawn, expired, superseded]
|
|
2190
2341
|
|
|
2191
2342
|
Proposal:
|
|
2192
2343
|
type: object
|
|
@@ -2204,6 +2355,20 @@ components:
|
|
|
2204
2355
|
- reviewUrl
|
|
2205
2356
|
- reviewPath
|
|
2206
2357
|
properties:
|
|
2358
|
+
deployPending:
|
|
2359
|
+
type: object
|
|
2360
|
+
description: Timed-out deployment has an unknown outcome; approvals wait until settlement or the hard cap. Check live flows before approving again.
|
|
2361
|
+
required: [mergedHash, reason]
|
|
2362
|
+
properties:
|
|
2363
|
+
mergedHash:
|
|
2364
|
+
type: string
|
|
2365
|
+
reason:
|
|
2366
|
+
type: string
|
|
2367
|
+
queueReleased:
|
|
2368
|
+
type: boolean
|
|
2369
|
+
description: True when the deploy hard cap released the queue. Late settlement no longer updates this proposal; approval always re-reads live flows.
|
|
2370
|
+
rebase:
|
|
2371
|
+
$ref: '#/components/schemas/ProposalRebase'
|
|
2207
2372
|
id:
|
|
2208
2373
|
type: string
|
|
2209
2374
|
type:
|
|
@@ -2318,9 +2483,8 @@ components:
|
|
|
2318
2483
|
reason:
|
|
2319
2484
|
type: string
|
|
2320
2485
|
description: |
|
|
2321
|
-
Present when rejected, failed, or
|
|
2322
|
-
|
|
2323
|
-
re-read flows and submit a new proposal.
|
|
2486
|
+
Present when rejected, failed, withdrawn, or expired. Revision changes
|
|
2487
|
+
keep open proposals pending or in conflict; they do not close them.
|
|
2324
2488
|
resultRev:
|
|
2325
2489
|
type: string
|
|
2326
2490
|
|
|
@@ -2424,9 +2588,39 @@ components:
|
|
|
2424
2588
|
find: Hello
|
|
2425
2589
|
replace: Hi
|
|
2426
2590
|
|
|
2591
|
+
ConflictChoiceRequest:
|
|
2592
|
+
type: object
|
|
2593
|
+
required: [nodeId, choice]
|
|
2594
|
+
properties:
|
|
2595
|
+
nodeId:
|
|
2596
|
+
type: string
|
|
2597
|
+
choice:
|
|
2598
|
+
type: string
|
|
2599
|
+
enum: [live, proposed]
|
|
2600
|
+
|
|
2601
|
+
ResolveProposalRequest:
|
|
2602
|
+
type: object
|
|
2603
|
+
required: [expectedRev]
|
|
2604
|
+
properties:
|
|
2605
|
+
expectedRev:
|
|
2606
|
+
type: string
|
|
2607
|
+
title:
|
|
2608
|
+
type: string
|
|
2609
|
+
maxLength: 80
|
|
2610
|
+
payload:
|
|
2611
|
+
$ref: '#/components/schemas/ReplaceFlowPayload'
|
|
2612
|
+
nodes:
|
|
2613
|
+
type: array
|
|
2614
|
+
items:
|
|
2615
|
+
type: object
|
|
2616
|
+
additionalProperties: true
|
|
2617
|
+
|
|
2427
2618
|
ApproveProposalRequest:
|
|
2428
2619
|
type: object
|
|
2429
2620
|
properties:
|
|
2621
|
+
mergedHash:
|
|
2622
|
+
type: string
|
|
2623
|
+
description: Hash of the reviewed merged result; required when editor changes are merged.
|
|
2430
2624
|
deploymentType:
|
|
2431
2625
|
$ref: '#/components/schemas/DeploymentType'
|
|
2432
2626
|
|
|
@@ -2586,7 +2780,7 @@ components:
|
|
|
2586
2780
|
baseRevStale:
|
|
2587
2781
|
type: boolean
|
|
2588
2782
|
description: |
|
|
2589
|
-
|
|
2783
|
+
Informational: true for open proposals when expectedRev ≠ live rev.
|
|
2590
2784
|
Never true for approved/rejected/failed.
|
|
2591
2785
|
compareAgainst:
|
|
2592
2786
|
type: string
|
|
@@ -2594,10 +2788,14 @@ components:
|
|
|
2594
2788
|
baseSnapshotMissing:
|
|
2595
2789
|
type: boolean
|
|
2596
2790
|
description: True for legacy proposals without a stored baseFlow
|
|
2791
|
+
rebase:
|
|
2792
|
+
$ref: '#/components/schemas/ProposalRebase'
|
|
2793
|
+
liveChanges:
|
|
2794
|
+
$ref: '#/components/schemas/ProposalDiffSections'
|
|
2597
2795
|
vsCurrent:
|
|
2598
2796
|
$ref: '#/components/schemas/ProposalDiffSections'
|
|
2599
2797
|
description: |
|
|
2600
|
-
Present
|
|
2798
|
+
Present for open proposals without conflicts — merged result vs live flow.
|
|
2601
2799
|
|
|
2602
2800
|
AuditActor:
|
|
2603
2801
|
oneOf:
|
package/docs/errors.md
CHANGED
|
@@ -13,6 +13,14 @@ GET-link `401` responses include a `WWW-Authenticate: Bearer …` header.
|
|
|
13
13
|
|
|
14
14
|
| Code | HTTP | Meaning | Hint / next step | Channels |
|
|
15
15
|
| --- | --- | --- | --- | --- |
|
|
16
|
+
| `approve_queue_timeout` | 504 | Approval exceeded its queue-wait deadline (default 60 seconds); no deployment was started for this request | Check the live flow and retry approval | admin |
|
|
17
|
+
| `deploy_timeout` | 504 | Deployment exceeded its deadline (default 60 seconds); approvals wait for settlement or the hard cap (default 5 minutes) | Before the cap, late settlement updates the proposal; after the cap, deployPending.queueReleased is true and the outcome stays unknown. Check the live flow before approving again; approval always re-reads live | admin |
|
|
18
|
+
| `proposal_conflict` | 409 | Editor and proposal changes conflict (conflicts contains field names only) | Read the current flow and resolve the proposal before approval | GET, REST, MCP, admin |
|
|
19
|
+
| `rebase_ack_required` | 409 | The merged result needs review (mergedHash included) | Review the diff and approve with its mergedHash | admin |
|
|
20
|
+
| `rebase_race` | 409 | Flows kept changing during approval | Recheck the proposal and retry approval | admin |
|
|
21
|
+
| `conflict_not_found` | 409 | The requested conflict no longer exists | Reload the proposal and its conflicts | admin |
|
|
22
|
+
| `invalid_choice` | 400 | Invalid manual conflict choice | Use an available live or proposed choice | admin |
|
|
23
|
+
| `flow_id_mismatch` | 400 | Resolution tried to change the proposal flow id | Keep the same flowId or submit a new proposal | REST, MCP |
|
|
16
24
|
| `access_link_missing` | 401 | No Bearer / path token | Ask the user for a new access link of the right kind | GET, REST, MCP |
|
|
17
25
|
| `bearer_scheme_required` | 401 | Auth used Basic (or non-Bearer) | Send `Authorization: Bearer <token>` | REST, MCP |
|
|
18
26
|
| `token_expired` | 401 | Known token past `expiresAt` (body includes `expiredAt` and `accessLinksUrl`) | Ask the user to create a new link on the `/ai-access` dashboard → Access tokens (`accessLinksUrl`) | GET, REST, MCP |
|
|
@@ -38,9 +46,9 @@ GET-link `401` responses include a `WWW-Authenticate: Bearer …` header.
|
|
|
38
46
|
| `invalid_diff_format` | 400 | Unknown `format` on proposal diff | Use `format=summary` (default) or `format=full` | GET, REST, MCP |
|
|
39
47
|
| `confirm_required` | 400 | GET withdraw without `confirm=1` | Repeat the request with `confirm=1` | GET |
|
|
40
48
|
| `missing_expected_rev` | 400 | Proposal submitted without `expectedRev` / `rev` | Read the flow and pass its `rev` | GET, REST, MCP |
|
|
41
|
-
| `version_mismatch` | 409 |
|
|
49
|
+
| `version_mismatch` | 409 | Unknown or unavailable `rev` / `expectedRev` snapshot | Re-read flows and resubmit | GET, REST, MCP |
|
|
42
50
|
| `proposal_not_found` | 404 | Unknown proposal id | List proposals and use an existing id | GET, REST, MCP |
|
|
43
|
-
| `proposal_not_pending` | 409 | Action needs
|
|
51
|
+
| `proposal_not_pending` | 409 | Action needs an open proposal | Only pending or conflict proposals can withdraw / decide | GET, REST, MCP |
|
|
44
52
|
| `proposal_not_owned` | 403 | Withdraw of another token’s proposal | Only the creating token can withdraw | GET, REST, MCP |
|
|
45
53
|
| `validation_failed` | 400 | Several validation errors at once (message `N validation errors`) | Fix every entry in errors[] and resubmit. | GET, REST, MCP |
|
|
46
54
|
| `duplicate_node_id` | 400 | The same node id appears more than once in a full tab or batch | Give every node a unique id | GET, REST, MCP |
|
|
@@ -72,6 +80,8 @@ Returned in `warnings[]` on a successful propose/submit; they never block.
|
|
|
72
80
|
|
|
73
81
|
| Code | HTTP | Meaning | Hint / next step | Channels |
|
|
74
82
|
| --- | --- | --- | --- | --- |
|
|
83
|
+
| `duplicate_env_keys` | 200 | Changed env arrays contain duplicate names; pairing by name is ambiguous when entries are reordered | Review the env values before approval | GET, REST, MCP, admin |
|
|
84
|
+
| `secret_may_revert` | 200 | Secret spans could not be aligned during proposed conflict resolution; proposed values were kept | Review the warned field against live flows before approval; an editor secret change may be reverted | GET, REST, MCP, admin |
|
|
75
85
|
| `missing_config_node` | 201 | Warning: node has no (or an unresolved) config node yet | Configure before deploy if needed; see `candidates` | GET, REST, MCP |
|
|
76
86
|
| `unwired_node` | 201 | Warning: node has outputs but no wires | Wire it, or ignore while scaffolding | GET, REST, MCP |
|
|
77
87
|
| `incomplete_wires` | 201 | Warning: fewer wire ports than outputs | Add ports if outputs should connect | GET, REST, MCP |
|
|
@@ -83,9 +93,10 @@ Returned in `warnings[]` on a successful propose/submit; they never block.
|
|
|
83
93
|
| `secret_tab_changed` | 201 | Warning: proposal with restored secrets also changes other nodes in the tab | Review every change before approving | GET, REST, MCP |
|
|
84
94
|
| `secret_reference_added` | 201 | Warning: an added/changed node now reads a secret-named env/global/flow value (env.get, $env, ${…}, typed input) | Reviewer must check the value is not sent anywhere unexpected | GET, REST, MCP |
|
|
85
95
|
|
|
86
|
-
##
|
|
96
|
+
## Proposal statuses
|
|
87
97
|
|
|
88
98
|
| Code | HTTP | Meaning | Hint / next step | Channels |
|
|
89
99
|
| --- | --- | --- | --- | --- |
|
|
90
|
-
| `
|
|
91
|
-
| `
|
|
100
|
+
| `conflict` | 200 | Status: open proposal has conflicting editor changes; approval blocked | Read the current flow and resolve the proposal | GET, REST, MCP |
|
|
101
|
+
| `expired` | 200 | Status: open proposal expired after the configured TTL (default 7 days) | Re-read the flow and submit a new proposal | GET, REST, MCP |
|
|
102
|
+
| `superseded` | 200 | Legacy closed status; new approvals rebase other open proposals | Re-read the flow and submit a new proposal if still needed | GET, REST, MCP |
|