@serovaai/ficta-contract 0.0.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/LICENSE +21 -0
- package/README.md +82 -0
- package/dist/client.d.ts +11 -0
- package/dist/client.js +18 -0
- package/dist/contract.d.ts +139 -0
- package/dist/contract.js +45 -0
- package/dist/errors.d.ts +10 -0
- package/dist/errors.js +74 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/schemas.d.ts +209 -0
- package/dist/schemas.js +179 -0
- package/openapi/ficta-control-plane.openapi.json +439 -0
- package/package.json +63 -0
- package/src/client.ts +29 -0
- package/src/contract.ts +60 -0
- package/src/errors.ts +76 -0
- package/src/index.ts +43 -0
- package/src/schemas.ts +202 -0
package/dist/schemas.js
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const FICTA_CAPABILITIES_PATH = "/__ficta/capabilities";
|
|
3
|
+
export const FICTA_CONTROL_PROTOCOL_VERSION = 1;
|
|
4
|
+
export const FICTA_CONTROL_CAPABILITIES = ["health", "status", "protection-preview"];
|
|
5
|
+
export const FICTA_SCOPE_MAX_LENGTH = 256;
|
|
6
|
+
export const PROTECTION_PREVIEW_TEXT_MAX_BYTES = 2 * 1024 * 1024;
|
|
7
|
+
export const PROTECTION_PREVIEW_VALUES_MAX = 200;
|
|
8
|
+
export const PROTECTION_PREVIEW_VALUE_MAX = 2_000;
|
|
9
|
+
export const PROTECTION_PREVIEW_VALUES_MAX_BYTES = 64 * 1024;
|
|
10
|
+
const utf8Length = (value) => new TextEncoder().encode(value).byteLength;
|
|
11
|
+
export const healthSchema = z
|
|
12
|
+
.object({
|
|
13
|
+
ok: z.literal(true),
|
|
14
|
+
service: z.literal("ficta"),
|
|
15
|
+
})
|
|
16
|
+
.strict();
|
|
17
|
+
export const capabilitiesSchema = z
|
|
18
|
+
.object({
|
|
19
|
+
ok: z.literal(true),
|
|
20
|
+
service: z.literal("ficta"),
|
|
21
|
+
protocolVersion: z
|
|
22
|
+
.literal(FICTA_CONTROL_PROTOCOL_VERSION)
|
|
23
|
+
.describe("Breaking wire-contract version implemented by this control plane."),
|
|
24
|
+
capabilities: z
|
|
25
|
+
.array(z.string().min(1))
|
|
26
|
+
.describe("Supported optional procedures. Clients must ignore capability names they do not recognize."),
|
|
27
|
+
})
|
|
28
|
+
.strict();
|
|
29
|
+
export const registryProtectionStatusSchema = z
|
|
30
|
+
.object({
|
|
31
|
+
required: z.boolean().describe("Whether provider requests are blocked until the registry is ready."),
|
|
32
|
+
status: z.enum(["ready", "empty", "error"]).describe("Current exact-match registry readiness."),
|
|
33
|
+
message: z.string().describe("Values-free operator guidance for the current registry state."),
|
|
34
|
+
})
|
|
35
|
+
.strict();
|
|
36
|
+
export const protectionStatusSchema = z
|
|
37
|
+
.object({
|
|
38
|
+
ok: z.literal(true),
|
|
39
|
+
service: z.literal("ficta"),
|
|
40
|
+
protection: z
|
|
41
|
+
.object({
|
|
42
|
+
enabled: z.boolean().describe("Whether the engine has registered values or detector plugins available."),
|
|
43
|
+
protecting: z.boolean().describe("Whether registered values or an active detector are currently configured."),
|
|
44
|
+
registeredValues: z.number().int().nonnegative().describe("Count of loaded exact-match protected values."),
|
|
45
|
+
policyExcluded: z
|
|
46
|
+
.number()
|
|
47
|
+
.int()
|
|
48
|
+
.nonnegative()
|
|
49
|
+
.describe("Count of discovered registry values excluded by configured policy."),
|
|
50
|
+
})
|
|
51
|
+
.strict(),
|
|
52
|
+
registry: registryProtectionStatusSchema.optional(),
|
|
53
|
+
secretShapes: z
|
|
54
|
+
.object({
|
|
55
|
+
enabled: z.boolean().describe("Whether request-time secret-shape detection is enabled."),
|
|
56
|
+
status: z.enum(["off", "ok"]).describe("Secret-shape detector posture."),
|
|
57
|
+
message: z.string().describe("Values-free explanation of the secret-shape posture."),
|
|
58
|
+
})
|
|
59
|
+
.strict(),
|
|
60
|
+
pii: z
|
|
61
|
+
.object({
|
|
62
|
+
enabled: z.boolean().describe("Whether request-time PII detection is enabled."),
|
|
63
|
+
configuredBackend: z.string().describe("Compatibility string naming the configured PII backend set."),
|
|
64
|
+
configuredBackends: z.array(z.string()).optional().describe("Configured PII backend names."),
|
|
65
|
+
backend: z.string().describe("Active PII backend names as a compatibility string."),
|
|
66
|
+
status: z.enum(["off", "ok", "degraded", "blocking"]).describe("Current PII detector posture."),
|
|
67
|
+
failureMode: z
|
|
68
|
+
.enum(["fail-open", "fail-closed"])
|
|
69
|
+
.describe("Whether a required PII backend outage skips that backend or blocks provider traffic."),
|
|
70
|
+
url: z.string().optional().describe("Values-free health URL for a single configured network backend."),
|
|
71
|
+
detail: z.string().optional().describe("Values-free backend health diagnostic."),
|
|
72
|
+
message: z.string().describe("Values-free explanation of the current PII posture."),
|
|
73
|
+
})
|
|
74
|
+
.strict(),
|
|
75
|
+
activity: z
|
|
76
|
+
.object({
|
|
77
|
+
restoredValues: z
|
|
78
|
+
.number()
|
|
79
|
+
.int()
|
|
80
|
+
.nonnegative()
|
|
81
|
+
.describe("Cumulative protected values restored during this proxy run."),
|
|
82
|
+
withheldFromTools: z
|
|
83
|
+
.number()
|
|
84
|
+
.int()
|
|
85
|
+
.nonnegative()
|
|
86
|
+
.describe("Cumulative protected values withheld from tool-call arguments during this proxy run."),
|
|
87
|
+
})
|
|
88
|
+
.strict()
|
|
89
|
+
.optional(),
|
|
90
|
+
})
|
|
91
|
+
.strict();
|
|
92
|
+
export const protectionHitSchema = z
|
|
93
|
+
.object({
|
|
94
|
+
name: z.string().describe("Values-free detector or registry label for the finding."),
|
|
95
|
+
source: z.string().describe("Values-free source category for the finding."),
|
|
96
|
+
plugin: z.string().optional().describe("Plugin that produced the finding, when available."),
|
|
97
|
+
kind: z.enum(["secret", "pii", "custom"]).optional().describe("Coarse protected-value category."),
|
|
98
|
+
confidence: z
|
|
99
|
+
.enum(["exact", "high", "probabilistic"])
|
|
100
|
+
.optional()
|
|
101
|
+
.describe("Confidence class assigned by the protection source."),
|
|
102
|
+
})
|
|
103
|
+
.strict();
|
|
104
|
+
export const protectionPreviewFindingSchema = protectionHitSchema.extend({
|
|
105
|
+
start: z.number().int().nonnegative().describe("Inclusive UTF-16 offset into the exact preview text."),
|
|
106
|
+
end: z.number().int().nonnegative().describe("Exclusive UTF-16 offset into the exact preview text."),
|
|
107
|
+
surrogate: z.string().describe("Opaque replacement rendered in redactedText."),
|
|
108
|
+
origin: z.enum(["registry", "detected", "user"]).describe("How this protected value entered the preview."),
|
|
109
|
+
});
|
|
110
|
+
const protectedValueSchema = z
|
|
111
|
+
.string()
|
|
112
|
+
.min(1)
|
|
113
|
+
.max(PROTECTION_PREVIEW_VALUE_MAX)
|
|
114
|
+
.transform((value) => value.trim())
|
|
115
|
+
.refine((value) => value.length > 0 && value.length <= PROTECTION_PREVIEW_VALUE_MAX, {
|
|
116
|
+
message: "A protected value is empty or too long.",
|
|
117
|
+
});
|
|
118
|
+
export const protectionPreviewTextSchema = z
|
|
119
|
+
.string()
|
|
120
|
+
.max(PROTECTION_PREVIEW_TEXT_MAX_BYTES)
|
|
121
|
+
.refine((value) => utf8Length(value) <= PROTECTION_PREVIEW_TEXT_MAX_BYTES, {
|
|
122
|
+
message: "Preview text is too large.",
|
|
123
|
+
});
|
|
124
|
+
export const protectionPreviewProtectedValuesSchema = z
|
|
125
|
+
.array(protectedValueSchema)
|
|
126
|
+
.max(PROTECTION_PREVIEW_VALUES_MAX)
|
|
127
|
+
.optional();
|
|
128
|
+
export const protectionPreviewInputSchema = z
|
|
129
|
+
.object({
|
|
130
|
+
text: protectionPreviewTextSchema,
|
|
131
|
+
protectedValues: protectionPreviewProtectedValuesSchema,
|
|
132
|
+
})
|
|
133
|
+
.transform(({ text, protectedValues = [] }, context) => {
|
|
134
|
+
const uniqueValues = [...new Set(protectedValues)];
|
|
135
|
+
const valuesBytes = uniqueValues.reduce((total, value) => total + utf8Length(value), 0);
|
|
136
|
+
if (valuesBytes > PROTECTION_PREVIEW_VALUES_MAX_BYTES) {
|
|
137
|
+
context.addIssue({ code: "custom", message: "Protected values are too large for one chat." });
|
|
138
|
+
return z.NEVER;
|
|
139
|
+
}
|
|
140
|
+
return { text, protectedValues: uniqueValues };
|
|
141
|
+
});
|
|
142
|
+
export const protectionPreviewSchema = z
|
|
143
|
+
.object({
|
|
144
|
+
ok: z.literal(true),
|
|
145
|
+
service: z.literal("ficta"),
|
|
146
|
+
ticket: z.string().describe("Opaque, short-lived, single-use authorization for the reviewed provider send."),
|
|
147
|
+
textSha256: z
|
|
148
|
+
.string()
|
|
149
|
+
.regex(/^[0-9a-f]{64}$/u)
|
|
150
|
+
.describe("Lowercase SHA-256 of the exact preview text bound to the ticket."),
|
|
151
|
+
redactedText: z.string().describe("Preview text with all planned protections applied."),
|
|
152
|
+
findings: z.array(protectionPreviewFindingSchema).describe("Ordered protected occurrences in the preview text."),
|
|
153
|
+
})
|
|
154
|
+
.strict();
|
|
155
|
+
const protectionPreviewErrorBaseSchema = z
|
|
156
|
+
.object({
|
|
157
|
+
ok: z.literal(false),
|
|
158
|
+
service: z.literal("ficta"),
|
|
159
|
+
message: z.string(),
|
|
160
|
+
})
|
|
161
|
+
.strict();
|
|
162
|
+
export const protectionPreviewForbiddenErrorSchema = protectionPreviewErrorBaseSchema.extend({
|
|
163
|
+
status: z.literal("forbidden"),
|
|
164
|
+
});
|
|
165
|
+
export const protectionPreviewInvalidRequestErrorSchema = protectionPreviewErrorBaseSchema.extend({
|
|
166
|
+
status: z.literal("invalid_request"),
|
|
167
|
+
});
|
|
168
|
+
export const protectionPreviewDetectorUnavailableErrorSchema = protectionPreviewErrorBaseSchema.extend({
|
|
169
|
+
status: z.literal("detector_unavailable"),
|
|
170
|
+
});
|
|
171
|
+
export const protectionPreviewInvariantErrorSchema = protectionPreviewErrorBaseSchema.extend({
|
|
172
|
+
status: z.literal("invariant"),
|
|
173
|
+
});
|
|
174
|
+
export const protectionPreviewErrorSchema = z.discriminatedUnion("status", [
|
|
175
|
+
protectionPreviewForbiddenErrorSchema,
|
|
176
|
+
protectionPreviewInvalidRequestErrorSchema,
|
|
177
|
+
protectionPreviewDetectorUnavailableErrorSchema,
|
|
178
|
+
protectionPreviewInvariantErrorSchema,
|
|
179
|
+
]);
|
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
{
|
|
2
|
+
"info": {
|
|
3
|
+
"title": "Ficta control plane",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"description": "Portable HTTP contract for building a frontend for the Ficta protection engine."
|
|
6
|
+
},
|
|
7
|
+
"servers": [{ "url": "http://127.0.0.1:8787", "description": "Default local Ficta proxy" }],
|
|
8
|
+
"tags": [
|
|
9
|
+
{
|
|
10
|
+
"name": "Ficta control plane",
|
|
11
|
+
"description": "Discovery, process health, values-free protection status, and trusted pre-send review."
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"externalDocs": {
|
|
15
|
+
"description": "Frontend integration contract, trust boundary, and reviewed-send lifecycle",
|
|
16
|
+
"url": "https://github.com/SerovaAI/ficta/blob/main/packages/ficta/docs/control-plane.md"
|
|
17
|
+
},
|
|
18
|
+
"openapi": "3.1.1",
|
|
19
|
+
"paths": {
|
|
20
|
+
"/__ficta/capabilities": {
|
|
21
|
+
"get": {
|
|
22
|
+
"operationId": "getFictaCapabilities",
|
|
23
|
+
"summary": "Discover the Ficta control-plane version and supported procedures",
|
|
24
|
+
"tags": ["Ficta control plane"],
|
|
25
|
+
"responses": {
|
|
26
|
+
"200": {
|
|
27
|
+
"description": "OK",
|
|
28
|
+
"content": {
|
|
29
|
+
"application/json": {
|
|
30
|
+
"schema": {
|
|
31
|
+
"type": "object",
|
|
32
|
+
"properties": {
|
|
33
|
+
"ok": { "const": true },
|
|
34
|
+
"service": { "const": "ficta" },
|
|
35
|
+
"protocolVersion": {
|
|
36
|
+
"const": 1,
|
|
37
|
+
"description": "Breaking wire-contract version implemented by this control plane."
|
|
38
|
+
},
|
|
39
|
+
"capabilities": {
|
|
40
|
+
"type": "array",
|
|
41
|
+
"items": { "type": "string", "minLength": 1 },
|
|
42
|
+
"description": "Supported optional procedures. Clients must ignore capability names they do not recognize."
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"required": ["ok", "service", "protocolVersion", "capabilities"],
|
|
46
|
+
"additionalProperties": false
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"/__ficta/health": {
|
|
55
|
+
"get": {
|
|
56
|
+
"operationId": "getFictaHealth",
|
|
57
|
+
"summary": "Check whether the Ficta proxy process is serving requests",
|
|
58
|
+
"tags": ["Ficta control plane"],
|
|
59
|
+
"responses": {
|
|
60
|
+
"200": {
|
|
61
|
+
"description": "OK",
|
|
62
|
+
"content": {
|
|
63
|
+
"application/json": {
|
|
64
|
+
"schema": {
|
|
65
|
+
"type": "object",
|
|
66
|
+
"properties": { "ok": { "const": true }, "service": { "const": "ficta" } },
|
|
67
|
+
"required": ["ok", "service"],
|
|
68
|
+
"additionalProperties": false
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
"head": {
|
|
76
|
+
"operationId": "headFictaHealth",
|
|
77
|
+
"summary": "Check whether the Ficta proxy process is serving requests without a response body",
|
|
78
|
+
"tags": ["Ficta control plane"],
|
|
79
|
+
"responses": { "200": { "description": "OK" } }
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"/__ficta/status": {
|
|
83
|
+
"get": {
|
|
84
|
+
"operationId": "getFictaProtectionStatus",
|
|
85
|
+
"summary": "Read values-free protection readiness and activity metadata",
|
|
86
|
+
"tags": ["Ficta control plane"],
|
|
87
|
+
"responses": {
|
|
88
|
+
"200": {
|
|
89
|
+
"description": "OK",
|
|
90
|
+
"content": {
|
|
91
|
+
"application/json": {
|
|
92
|
+
"schema": {
|
|
93
|
+
"type": "object",
|
|
94
|
+
"properties": {
|
|
95
|
+
"ok": { "const": true },
|
|
96
|
+
"service": { "const": "ficta" },
|
|
97
|
+
"protection": {
|
|
98
|
+
"type": "object",
|
|
99
|
+
"properties": {
|
|
100
|
+
"enabled": {
|
|
101
|
+
"type": "boolean",
|
|
102
|
+
"description": "Whether the engine has registered values or detector plugins available."
|
|
103
|
+
},
|
|
104
|
+
"protecting": {
|
|
105
|
+
"type": "boolean",
|
|
106
|
+
"description": "Whether registered values or an active detector are currently configured."
|
|
107
|
+
},
|
|
108
|
+
"registeredValues": {
|
|
109
|
+
"type": "integer",
|
|
110
|
+
"minimum": 0,
|
|
111
|
+
"maximum": 9007199254740991,
|
|
112
|
+
"description": "Count of loaded exact-match protected values."
|
|
113
|
+
},
|
|
114
|
+
"policyExcluded": {
|
|
115
|
+
"type": "integer",
|
|
116
|
+
"minimum": 0,
|
|
117
|
+
"maximum": 9007199254740991,
|
|
118
|
+
"description": "Count of discovered registry values excluded by configured policy."
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
"required": ["enabled", "protecting", "registeredValues", "policyExcluded"],
|
|
122
|
+
"additionalProperties": false
|
|
123
|
+
},
|
|
124
|
+
"registry": {
|
|
125
|
+
"type": "object",
|
|
126
|
+
"properties": {
|
|
127
|
+
"required": {
|
|
128
|
+
"type": "boolean",
|
|
129
|
+
"description": "Whether provider requests are blocked until the registry is ready."
|
|
130
|
+
},
|
|
131
|
+
"status": {
|
|
132
|
+
"enum": ["ready", "empty", "error"],
|
|
133
|
+
"type": "string",
|
|
134
|
+
"description": "Current exact-match registry readiness."
|
|
135
|
+
},
|
|
136
|
+
"message": {
|
|
137
|
+
"type": "string",
|
|
138
|
+
"description": "Values-free operator guidance for the current registry state."
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
"required": ["required", "status", "message"],
|
|
142
|
+
"additionalProperties": false
|
|
143
|
+
},
|
|
144
|
+
"secretShapes": {
|
|
145
|
+
"type": "object",
|
|
146
|
+
"properties": {
|
|
147
|
+
"enabled": {
|
|
148
|
+
"type": "boolean",
|
|
149
|
+
"description": "Whether request-time secret-shape detection is enabled."
|
|
150
|
+
},
|
|
151
|
+
"status": {
|
|
152
|
+
"enum": ["off", "ok"],
|
|
153
|
+
"type": "string",
|
|
154
|
+
"description": "Secret-shape detector posture."
|
|
155
|
+
},
|
|
156
|
+
"message": {
|
|
157
|
+
"type": "string",
|
|
158
|
+
"description": "Values-free explanation of the secret-shape posture."
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
"required": ["enabled", "status", "message"],
|
|
162
|
+
"additionalProperties": false
|
|
163
|
+
},
|
|
164
|
+
"pii": {
|
|
165
|
+
"type": "object",
|
|
166
|
+
"properties": {
|
|
167
|
+
"enabled": {
|
|
168
|
+
"type": "boolean",
|
|
169
|
+
"description": "Whether request-time PII detection is enabled."
|
|
170
|
+
},
|
|
171
|
+
"configuredBackend": {
|
|
172
|
+
"type": "string",
|
|
173
|
+
"description": "Compatibility string naming the configured PII backend set."
|
|
174
|
+
},
|
|
175
|
+
"configuredBackends": {
|
|
176
|
+
"type": "array",
|
|
177
|
+
"items": { "type": "string" },
|
|
178
|
+
"description": "Configured PII backend names."
|
|
179
|
+
},
|
|
180
|
+
"backend": {
|
|
181
|
+
"type": "string",
|
|
182
|
+
"description": "Active PII backend names as a compatibility string."
|
|
183
|
+
},
|
|
184
|
+
"status": {
|
|
185
|
+
"enum": ["off", "ok", "degraded", "blocking"],
|
|
186
|
+
"type": "string",
|
|
187
|
+
"description": "Current PII detector posture."
|
|
188
|
+
},
|
|
189
|
+
"failureMode": {
|
|
190
|
+
"enum": ["fail-open", "fail-closed"],
|
|
191
|
+
"type": "string",
|
|
192
|
+
"description": "Whether a required PII backend outage skips that backend or blocks provider traffic."
|
|
193
|
+
},
|
|
194
|
+
"url": {
|
|
195
|
+
"type": "string",
|
|
196
|
+
"description": "Values-free health URL for a single configured network backend."
|
|
197
|
+
},
|
|
198
|
+
"detail": { "type": "string", "description": "Values-free backend health diagnostic." },
|
|
199
|
+
"message": {
|
|
200
|
+
"type": "string",
|
|
201
|
+
"description": "Values-free explanation of the current PII posture."
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
"required": ["enabled", "configuredBackend", "backend", "status", "failureMode", "message"],
|
|
205
|
+
"additionalProperties": false
|
|
206
|
+
},
|
|
207
|
+
"activity": {
|
|
208
|
+
"type": "object",
|
|
209
|
+
"properties": {
|
|
210
|
+
"restoredValues": {
|
|
211
|
+
"type": "integer",
|
|
212
|
+
"minimum": 0,
|
|
213
|
+
"maximum": 9007199254740991,
|
|
214
|
+
"description": "Cumulative protected values restored during this proxy run."
|
|
215
|
+
},
|
|
216
|
+
"withheldFromTools": {
|
|
217
|
+
"type": "integer",
|
|
218
|
+
"minimum": 0,
|
|
219
|
+
"maximum": 9007199254740991,
|
|
220
|
+
"description": "Cumulative protected values withheld from tool-call arguments during this proxy run."
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
"required": ["restoredValues", "withheldFromTools"],
|
|
224
|
+
"additionalProperties": false
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
"required": ["ok", "service", "protection", "secretShapes", "pii"],
|
|
228
|
+
"additionalProperties": false
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
"head": {
|
|
236
|
+
"operationId": "headFictaProtectionStatus",
|
|
237
|
+
"summary": "Check whether protection status can be evaluated without a response body",
|
|
238
|
+
"tags": ["Ficta control plane"],
|
|
239
|
+
"responses": { "200": { "description": "OK" } }
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
"/__ficta/protection-preview": {
|
|
243
|
+
"post": {
|
|
244
|
+
"operationId": "createFictaProtectionPreview",
|
|
245
|
+
"summary": "Preview protection and issue a short-lived send ticket",
|
|
246
|
+
"tags": ["Ficta control plane"],
|
|
247
|
+
"requestBody": {
|
|
248
|
+
"required": true,
|
|
249
|
+
"content": {
|
|
250
|
+
"application/json": {
|
|
251
|
+
"schema": {
|
|
252
|
+
"type": "object",
|
|
253
|
+
"properties": {
|
|
254
|
+
"text": {
|
|
255
|
+
"type": "string",
|
|
256
|
+
"maxLength": 2097152,
|
|
257
|
+
"description": "Maximum 2097152 bytes when encoded as UTF-8.",
|
|
258
|
+
"x-ficta-max-utf8-bytes": 2097152
|
|
259
|
+
},
|
|
260
|
+
"protectedValues": {
|
|
261
|
+
"type": "array",
|
|
262
|
+
"maxItems": 200,
|
|
263
|
+
"items": { "type": "string", "minLength": 1, "maxLength": 2000 },
|
|
264
|
+
"description": "Optional protected selections; maximum 65536 combined bytes after UTF-8 encoding, trimming, and deduplication.",
|
|
265
|
+
"x-ficta-max-utf8-bytes": 65536
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
"required": ["text"]
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
},
|
|
273
|
+
"responses": {
|
|
274
|
+
"200": {
|
|
275
|
+
"description": "OK",
|
|
276
|
+
"content": {
|
|
277
|
+
"application/json": {
|
|
278
|
+
"schema": {
|
|
279
|
+
"type": "object",
|
|
280
|
+
"properties": {
|
|
281
|
+
"ok": { "const": true },
|
|
282
|
+
"service": { "const": "ficta" },
|
|
283
|
+
"ticket": {
|
|
284
|
+
"type": "string",
|
|
285
|
+
"description": "Opaque, short-lived, single-use authorization for the reviewed provider send."
|
|
286
|
+
},
|
|
287
|
+
"textSha256": {
|
|
288
|
+
"type": "string",
|
|
289
|
+
"pattern": "^[0-9a-f]{64}$",
|
|
290
|
+
"description": "Lowercase SHA-256 of the exact preview text bound to the ticket."
|
|
291
|
+
},
|
|
292
|
+
"redactedText": {
|
|
293
|
+
"type": "string",
|
|
294
|
+
"description": "Preview text with all planned protections applied."
|
|
295
|
+
},
|
|
296
|
+
"findings": {
|
|
297
|
+
"type": "array",
|
|
298
|
+
"items": {
|
|
299
|
+
"type": "object",
|
|
300
|
+
"properties": {
|
|
301
|
+
"name": {
|
|
302
|
+
"type": "string",
|
|
303
|
+
"description": "Values-free detector or registry label for the finding."
|
|
304
|
+
},
|
|
305
|
+
"source": { "type": "string", "description": "Values-free source category for the finding." },
|
|
306
|
+
"plugin": {
|
|
307
|
+
"type": "string",
|
|
308
|
+
"description": "Plugin that produced the finding, when available."
|
|
309
|
+
},
|
|
310
|
+
"kind": {
|
|
311
|
+
"enum": ["secret", "pii", "custom"],
|
|
312
|
+
"type": "string",
|
|
313
|
+
"description": "Coarse protected-value category."
|
|
314
|
+
},
|
|
315
|
+
"confidence": {
|
|
316
|
+
"enum": ["exact", "high", "probabilistic"],
|
|
317
|
+
"type": "string",
|
|
318
|
+
"description": "Confidence class assigned by the protection source."
|
|
319
|
+
},
|
|
320
|
+
"start": {
|
|
321
|
+
"type": "integer",
|
|
322
|
+
"minimum": 0,
|
|
323
|
+
"maximum": 9007199254740991,
|
|
324
|
+
"description": "Inclusive UTF-16 offset into the exact preview text."
|
|
325
|
+
},
|
|
326
|
+
"end": {
|
|
327
|
+
"type": "integer",
|
|
328
|
+
"minimum": 0,
|
|
329
|
+
"maximum": 9007199254740991,
|
|
330
|
+
"description": "Exclusive UTF-16 offset into the exact preview text."
|
|
331
|
+
},
|
|
332
|
+
"surrogate": {
|
|
333
|
+
"type": "string",
|
|
334
|
+
"description": "Opaque replacement rendered in redactedText."
|
|
335
|
+
},
|
|
336
|
+
"origin": {
|
|
337
|
+
"enum": ["registry", "detected", "user"],
|
|
338
|
+
"type": "string",
|
|
339
|
+
"description": "How this protected value entered the preview."
|
|
340
|
+
}
|
|
341
|
+
},
|
|
342
|
+
"required": ["name", "source", "start", "end", "surrogate", "origin"],
|
|
343
|
+
"additionalProperties": false
|
|
344
|
+
},
|
|
345
|
+
"description": "Ordered protected occurrences in the preview text."
|
|
346
|
+
}
|
|
347
|
+
},
|
|
348
|
+
"required": ["ok", "service", "ticket", "textSha256", "redactedText", "findings"],
|
|
349
|
+
"additionalProperties": false
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
},
|
|
354
|
+
"400": {
|
|
355
|
+
"description": "400",
|
|
356
|
+
"content": {
|
|
357
|
+
"application/json": {
|
|
358
|
+
"schema": {
|
|
359
|
+
"type": "object",
|
|
360
|
+
"properties": {
|
|
361
|
+
"ok": { "const": false },
|
|
362
|
+
"service": { "const": "ficta" },
|
|
363
|
+
"status": { "const": "invalid_request" },
|
|
364
|
+
"message": { "type": "string" }
|
|
365
|
+
},
|
|
366
|
+
"required": ["ok", "service", "status", "message"],
|
|
367
|
+
"additionalProperties": false
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
},
|
|
372
|
+
"403": {
|
|
373
|
+
"description": "403",
|
|
374
|
+
"content": {
|
|
375
|
+
"application/json": {
|
|
376
|
+
"schema": {
|
|
377
|
+
"type": "object",
|
|
378
|
+
"properties": {
|
|
379
|
+
"ok": { "const": false },
|
|
380
|
+
"service": { "const": "ficta" },
|
|
381
|
+
"status": { "const": "forbidden" },
|
|
382
|
+
"message": { "type": "string" }
|
|
383
|
+
},
|
|
384
|
+
"required": ["ok", "service", "status", "message"],
|
|
385
|
+
"additionalProperties": false
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
},
|
|
390
|
+
"422": {
|
|
391
|
+
"description": "422",
|
|
392
|
+
"content": {
|
|
393
|
+
"application/json": {
|
|
394
|
+
"schema": {
|
|
395
|
+
"type": "object",
|
|
396
|
+
"properties": {
|
|
397
|
+
"ok": { "const": false },
|
|
398
|
+
"service": { "const": "ficta" },
|
|
399
|
+
"status": { "const": "invariant" },
|
|
400
|
+
"message": { "type": "string" }
|
|
401
|
+
},
|
|
402
|
+
"required": ["ok", "service", "status", "message"],
|
|
403
|
+
"additionalProperties": false
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
},
|
|
408
|
+
"503": {
|
|
409
|
+
"description": "503",
|
|
410
|
+
"content": {
|
|
411
|
+
"application/json": {
|
|
412
|
+
"schema": {
|
|
413
|
+
"type": "object",
|
|
414
|
+
"properties": {
|
|
415
|
+
"ok": { "const": false },
|
|
416
|
+
"service": { "const": "ficta" },
|
|
417
|
+
"status": { "const": "detector_unavailable" },
|
|
418
|
+
"message": { "type": "string" }
|
|
419
|
+
},
|
|
420
|
+
"required": ["ok", "service", "status", "message"],
|
|
421
|
+
"additionalProperties": false
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
},
|
|
427
|
+
"parameters": [
|
|
428
|
+
{
|
|
429
|
+
"name": "x-ficta-scope",
|
|
430
|
+
"in": "header",
|
|
431
|
+
"required": true,
|
|
432
|
+
"description": "Trusted, server-owned tenant/user/conversation isolation key. Never forwarded upstream.",
|
|
433
|
+
"schema": { "type": "string", "minLength": 1, "maxLength": 256 }
|
|
434
|
+
}
|
|
435
|
+
]
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|