Skip to content

Commit b5bc1b1

Browse files
docs(decisions): canonical /decisions verdict values for 9.0.0 (#242)
The /api/v1/decisions read surface canonicalized in platform 9.0.0: the endpoint returns allowed|blocked|redacted|needs_approval|error and the ?decision= filter rejects the old allow|deny|require_approval with HTTP 400. The SDK code is a string passthrough and is unaffected, but the list_decisions / explain_decision docstrings, the example env-var docs, and the test fixtures used the pre-9.0.0 values. Update them to the canonical set and add a pointer to the v8 to v9 migration guide. Docs/examples/fixtures only — no type or logic change. The wire /decide verdict (allow|deny|needs_approval) and the workflow-control gate decision (allow|block|require_approval) are deliberately untouched. Held for the 9.0.0 release. Signed-off-by: Saurabh Jain <saurabh.jain@getaxonflow.com>
1 parent 88b3b7f commit b5bc1b1

4 files changed

Lines changed: 42 additions & 29 deletions

File tree

examples/list-decisions/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
* AXONFLOW_CLIENT_SECRET
1212
*
1313
* Optional filters:
14-
* AXONFLOW_LIST_DECISION allow|deny|require_approval
14+
* AXONFLOW_LIST_DECISION allowed|blocked|redacted|needs_approval|error
15+
* (canonical audit verdicts, platform 9.0.0+;
16+
* pre-9.0.0 allow|deny|require_approval now 400)
1517
* AXONFLOW_LIST_POLICY_ID e.g. sys_sqli_stacked_drop
1618
* AXONFLOW_LIST_LIMIT integer (server-capped per tier)
1719
*

src/client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2768,7 +2768,7 @@ export class AxonFlow {
27682768
* @example
27692769
* ```typescript
27702770
* try {
2771-
* const decisions = await client.listDecisions({ decision: 'deny', limit: 10 });
2771+
* const decisions = await client.listDecisions({ decision: 'blocked', limit: 10 });
27722772
* for (const d of decisions) {
27732773
* console.log(d.decisionId, d.decision, d.timestamp);
27742774
* }

src/types/decisions.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,11 @@ export interface DecisionExplanation {
3636
timestamp: Date;
3737
policyMatches: ExplainPolicy[];
3838
matchedRules?: ExplainRule[];
39-
/** allow | deny | require_approval */
39+
/**
40+
* Canonical audit verdict: allowed | blocked | redacted | needs_approval | error
41+
* (platform 9.0.0+). Pre-9.0.0 this field used allow | deny | require_approval;
42+
* see https://docs.getaxonflow.com/docs/deployment/v8-to-v9-migration/
43+
*/
4044
decision: string;
4145
reason: string;
4246
/** low | medium | high | critical */
@@ -77,7 +81,11 @@ export interface DecisionExplanation {
7781
export interface DecisionSummary {
7882
decisionId: string;
7983
timestamp: Date;
80-
/** allow | deny | require_approval */
84+
/**
85+
* Canonical audit verdict: allowed | blocked | redacted | needs_approval | error
86+
* (platform 9.0.0+). Pre-9.0.0 this field used allow | deny | require_approval;
87+
* see https://docs.getaxonflow.com/docs/deployment/v8-to-v9-migration/
88+
*/
8189
decision: string;
8290
policyId?: string;
8391
toolSignature?: string;
@@ -96,9 +104,12 @@ export interface DecisionSummary {
96104
* Optional filters for {@link AxonFlowClient.listDecisions}.
97105
*
98106
* Every field is optional; `undefined` values are omitted from the URL so
99-
* the platform applies its tier-default page. `decision` must be one of
100-
* `'allow' | 'deny' | 'require_approval'` when set. `limit` is server-
101-
* capped per tier; over-cap requests yield a 429 with the V1 upgrade
107+
* the platform applies its tier-default page. `decision`, when set, must be one
108+
* of the canonical audit verdicts `'allowed' | 'blocked' | 'redacted' |
109+
* 'needs_approval' | 'error'` (platform 9.0.0+); the pre-9.0.0 values
110+
* `'allow' | 'deny' | 'require_approval'` are rejected with HTTP 400 by 9.0.0
111+
* (see https://docs.getaxonflow.com/docs/deployment/v8-to-v9-migration/).
112+
* `limit` is server-capped per tier; over-cap requests yield a 429 with the V1 upgrade
102113
* envelope (surfaced as {@link RateLimitError} carrying `upgrade`).
103114
*/
104115
export interface ListDecisionsOptions {

tests/decisions.test.ts

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ describe('Decision Explainability (ADR-043)', () => {
4646
const raw = {
4747
decision_id: 'dec_wf1_step2',
4848
timestamp: '2026-04-17T12:00:00Z',
49-
decision: 'deny',
49+
decision: 'blocked',
5050
reason: 'SQL injection detected',
5151
risk_level: 'high',
5252
policy_matches: [
@@ -83,7 +83,7 @@ describe('Decision Explainability (ADR-043)', () => {
8383
expect((callArgs[1] as { method: string }).method).toBe('GET');
8484

8585
expect(exp.decisionId).toBe('dec_wf1_step2');
86-
expect(exp.decision).toBe('deny');
86+
expect(exp.decision).toBe('blocked');
8787
expect(exp.policyMatches).toHaveLength(1);
8888
expect(exp.policyMatches[0].policyId).toBe('pol-sqli');
8989
expect(exp.policyMatches[0].allowOverride).toBe(true);
@@ -101,7 +101,7 @@ describe('Decision Explainability (ADR-043)', () => {
101101
mockResponse({
102102
decision_id: 'a/b',
103103
timestamp: '2026-04-17T12:00:00Z',
104-
decision: 'allow',
104+
decision: 'allowed',
105105
reason: '',
106106
policy_matches: [],
107107
override_available: false,
@@ -118,7 +118,7 @@ describe('Decision Explainability (ADR-043)', () => {
118118
mockResponse({
119119
decision_id: 'dec-1',
120120
timestamp: '2026-04-17T12:00:00Z',
121-
decision: 'allow',
121+
decision: 'allowed',
122122
reason: '',
123123
policy_matches: [],
124124
override_available: false,
@@ -135,7 +135,7 @@ describe('Decision Explainability (ADR-043)', () => {
135135
mockResponse({
136136
decision_id: 'dec-ctx',
137137
timestamp: '2026-05-30T12:00:00Z',
138-
decision: 'deny',
138+
decision: 'blocked',
139139
reason: '',
140140
policy_matches: [],
141141
override_available: false,
@@ -162,7 +162,7 @@ describe('Decision Explainability (ADR-043)', () => {
162162
mockResponse({
163163
decision_id: 'dec-1',
164164
timestamp: '2026-04-17T12:00:00Z',
165-
decision: 'allow',
165+
decision: 'allowed',
166166
reason: '',
167167
policy_matches: [],
168168
override_available: false,
@@ -179,7 +179,7 @@ describe('Decision Explainability (ADR-043)', () => {
179179
mockResponse({
180180
decision_id: 'dec-1',
181181
timestamp: '2026-04-17T12:00:00Z',
182-
decision: 'allow',
182+
decision: 'allowed',
183183
reason: '',
184184
policy_matches: [],
185185
override_available: false,
@@ -278,21 +278,21 @@ describe('listDecisions (Session γ #1982)', () => {
278278
{
279279
decision_id: 'dec-1',
280280
timestamp: '2026-05-07T12:00:00Z',
281-
decision: 'deny',
281+
decision: 'blocked',
282282
policy_id: 'pol-sqli',
283283
tool_signature: 'postgres.query',
284284
},
285285
{
286286
decision_id: 'dec-2',
287287
timestamp: '2026-05-07T11:00:00Z',
288-
decision: 'allow',
288+
decision: 'allowed',
289289
policy_id: 'pol-default',
290290
tool_signature: 'github.status',
291291
},
292292
{
293293
decision_id: 'dec-3',
294294
timestamp: '2026-05-07T10:00:00Z',
295-
decision: 'require_approval',
295+
decision: 'needs_approval',
296296
policy_id: 'pol-amount',
297297
tool_signature: 'stripe.charge',
298298
},
@@ -303,10 +303,10 @@ describe('listDecisions (Session γ #1982)', () => {
303303
const got = await client.listDecisions();
304304
expect(got).toHaveLength(3);
305305
expect(got[0].decisionId).toBe('dec-1');
306-
expect(got[0].decision).toBe('deny');
306+
expect(got[0].decision).toBe('blocked');
307307
expect(got[0].policyId).toBe('pol-sqli');
308308
expect(got[0].toolSignature).toBe('postgres.query');
309-
expect(got[2].decision).toBe('require_approval');
309+
expect(got[2].decision).toBe('needs_approval');
310310
});
311311

312312
it('surfaces the truncated request context on the summary (v8.4.0)', async () => {
@@ -316,7 +316,7 @@ describe('listDecisions (Session γ #1982)', () => {
316316
{
317317
decision_id: 'dec-ctx',
318318
timestamp: '2026-05-30T12:00:00Z',
319-
decision: 'deny',
319+
decision: 'blocked',
320320
context: {
321321
x_ai_agent: 'refund-bot',
322322
x_session_id: 'sess-42',
@@ -338,7 +338,7 @@ describe('listDecisions (Session γ #1982)', () => {
338338
mockFetch.mockReturnValueOnce(
339339
ok({
340340
decisions: [
341-
{ decision_id: 'dec-noctx', timestamp: '2026-05-30T12:00:00Z', decision: 'allow' },
341+
{ decision_id: 'dec-noctx', timestamp: '2026-05-30T12:00:00Z', decision: 'allowed' },
342342
],
343343
})
344344
);
@@ -350,7 +350,7 @@ describe('listDecisions (Session γ #1982)', () => {
350350
mockFetch.mockReturnValueOnce(ok({ decisions: [] }));
351351
const opts: ListDecisionsOptions = {
352352
since: new Date('2026-05-07T00:00:00Z'),
353-
decision: 'deny',
353+
decision: 'blocked',
354354
policyId: 'pol-sqli',
355355
toolSignature: 'postgres.query',
356356
limit: 25,
@@ -359,18 +359,18 @@ describe('listDecisions (Session γ #1982)', () => {
359359

360360
const calledUrl = mockFetch.mock.calls[0][0] as string;
361361
expect(calledUrl).toContain('since=2026-05-07T00%3A00%3A00Z');
362-
expect(calledUrl).toContain('decision=deny');
362+
expect(calledUrl).toContain('decision=blocked');
363363
expect(calledUrl).toContain('policy_id=pol-sqli');
364364
expect(calledUrl).toContain('tool_signature=postgres.query');
365365
expect(calledUrl).toContain('limit=25');
366366
});
367367

368368
it('omits unset filters from the URL', async () => {
369369
mockFetch.mockReturnValueOnce(ok({ decisions: [] }));
370-
await client.listDecisions({ decision: 'deny' });
370+
await client.listDecisions({ decision: 'blocked' });
371371

372372
const calledUrl = mockFetch.mock.calls[0][0] as string;
373-
expect(calledUrl).toContain('?decision=deny');
373+
expect(calledUrl).toContain('?decision=blocked');
374374
expect(calledUrl).not.toContain('since=');
375375
expect(calledUrl).not.toContain('policy_id=');
376376
expect(calledUrl).not.toContain('tool_signature=');
@@ -441,7 +441,7 @@ describe('listDecisions (Session γ #1982)', () => {
441441
{
442442
decision_id: 'dec-fwd',
443443
timestamp: '2026-05-07T12:00:00Z',
444-
decision: 'deny',
444+
decision: 'blocked',
445445
policy_id: 'pol-x',
446446
tool_signature: 'tool-x',
447447
policy_version: 7,
@@ -461,7 +461,7 @@ describe('listDecisions (Session γ #1982)', () => {
461461
mockFetch.mockReturnValueOnce(
462462
ok({
463463
decisions: [
464-
{ decision_id: 'dec-min', timestamp: '2026-05-07T12:00:00Z', decision: 'deny' },
464+
{ decision_id: 'dec-min', timestamp: '2026-05-07T12:00:00Z', decision: 'blocked' },
465465
],
466466
})
467467
);
@@ -478,7 +478,7 @@ describe('buildListDecisionsQuery (#1982)', () => {
478478
});
479479

480480
it('omits unset fields and emits stable order', () => {
481-
const qs = buildListDecisionsQuery({ decision: 'deny', limit: 7 });
482-
expect(qs).toBe('decision=deny&limit=7');
481+
const qs = buildListDecisionsQuery({ decision: 'blocked', limit: 7 });
482+
expect(qs).toBe('decision=blocked&limit=7');
483483
});
484484
});

0 commit comments

Comments
 (0)