@cleocode/lafs-protocol 0.1.0 → 0.5.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 CHANGED
File without changes
package/README.md CHANGED
@@ -1,45 +1,151 @@
1
- # lafs-protocol
1
+ # LAFS Protocol
2
2
 
3
- LLM-Agent-First Specification (LAFS) as a standalone protocol repository.
3
+ **LLM-Agent-First Specification** a response envelope contract for AI agent systems.
4
4
 
5
- This repository is language-neutral at the protocol layer and TypeScript-first for reference tooling.
5
+ LAFS defines a standard envelope format for structured responses from LLM-powered agents and tools. It complements transport protocols like [MCP](https://modelcontextprotocol.io/) and [A2A](https://github.com/google/A2A) by standardizing what comes back — not how it gets there.
6
6
 
7
- ## What this repo provides
7
+ **Current version:** 0.5.0 | [Spec](lafs.md) | [Migration Guides](migrations/)
8
8
 
9
- - Canonical protocol spec: `lafs.md`
10
- - Versioned JSON schemas: `schemas/v1/`
11
- - Error registry and transport mappings: `schemas/v1/error-registry.json`
12
- - TypeScript validation/conformance toolkit: `src/`
13
- - Automated conformance tests: `tests/`
9
+ ## What LAFS provides
10
+
11
+ | Layer | Files | Description |
12
+ |-------|-------|-------------|
13
+ | **Spec** | `lafs.md` | Protocol specification with RFC 2119 language |
14
+ | **Schemas** | `schemas/v1/envelope.schema.json` | Envelope schema (Draft-07) with conditional pagination validation |
15
+ | | `schemas/v1/context-ledger.schema.json` | Context ledger for state tracking across request/response cycles |
16
+ | | `schemas/v1/error-registry.json` | 12 registered error codes with HTTP/gRPC/CLI transport mappings |
17
+ | **Tooling** | `src/` | TypeScript validation, conformance runner, CLI diagnostic tool |
18
+ | **Tests** | `tests/` | 31 tests covering envelope, pagination, strict mode, error handling |
19
+ | **Fixtures** | `fixtures/` | 14 JSON fixtures (valid + invalid) for conformance testing |
20
+ | **Docs** | `docs/` | Positioning, vision, conformance tiers, deprecation policy |
14
21
 
15
22
  ## Install
16
23
 
17
24
  ```bash
18
- npm install
25
+ npm install @cleocode/lafs-protocol
26
+ ```
27
+
28
+ ## Usage
29
+
30
+ ```typescript
31
+ import {
32
+ validateEnvelope,
33
+ runEnvelopeConformance,
34
+ isRegisteredErrorCode,
35
+ } from "@cleocode/lafs-protocol";
36
+
37
+ // Validate an envelope against the schema
38
+ const result = validateEnvelope(envelope);
39
+ if (!result.valid) {
40
+ console.error(result.errors);
41
+ }
42
+
43
+ // Run full conformance suite (schema + invariants + error codes + strict mode + pagination)
44
+ const report = runEnvelopeConformance(envelope);
45
+ console.log(report.ok); // true if all checks pass
19
46
  ```
20
47
 
21
- ## Commands
48
+ ## CLI
22
49
 
23
50
  ```bash
24
- npm run typecheck
51
+ # Run conformance checks on a fixture
52
+ npm run conformance -- --envelope fixtures/valid-success-envelope.json
53
+
54
+ # Run tests
25
55
  npm test
26
- npm run conformance -- --envelope fixtures/valid-success-envelope.json --flags fixtures/flags-valid.json
56
+
57
+ # Type check
58
+ npm run typecheck
27
59
  ```
28
60
 
29
- ## Canonical policy
61
+ ## Envelope structure
30
62
 
31
- - JSON default output is REQUIRED.
32
- - Human-readable output is explicit opt-in (`--human`).
33
- - `--json` is optional but recommended explicit alias/override.
34
- - `--human --json` is invalid and MUST fail with `E_FORMAT_CONFLICT`.
63
+ ```json
64
+ {
65
+ "$schema": "https://lafs.dev/schemas/v1/envelope.schema.json",
66
+ "_meta": {
67
+ "specVersion": "0.5.0",
68
+ "schemaVersion": "1.0.0",
69
+ "timestamp": "2026-02-13T00:00:00Z",
70
+ "operation": "example.list",
71
+ "requestId": "req_01",
72
+ "transport": "http",
73
+ "strict": true,
74
+ "mvi": "standard",
75
+ "contextVersion": 1
76
+ },
77
+ "success": true,
78
+ "result": { "items": [{ "id": "1", "title": "Example" }] },
79
+ "page": {
80
+ "mode": "cursor",
81
+ "nextCursor": "eyJpZCI6IjEwIn0=",
82
+ "hasMore": true
83
+ }
84
+ }
85
+ ```
86
+
87
+ ## Key features
88
+
89
+ - **Conditional pagination** — cursor, offset, and none modes with mode-specific required fields
90
+ - **Strict/lenient mode** — `strict: true` rejects unknown properties; `strict: false` allows them
91
+ - **MVI disclosure levels** — `minimal`, `standard`, `full`, `custom` control response verbosity
92
+ - **Field selection** (`_fields`) and **expansion** (`_expand`) request parameters
93
+ - **Context ledger** — tracks state across request/response cycles with monotonic versioning
94
+ - **Error registry** — 12 codes with category, retryability, and transport-specific status mappings
95
+ - **Extension mechanism** — `_extensions` field for vendor metadata (`x-` prefix convention)
96
+ - **Adoption tiers** — Core, Standard, Complete with progressive conformance requirements
97
+
98
+ ## Conformance checks
99
+
100
+ | Check | Description | Tier |
101
+ |-------|-------------|------|
102
+ | `envelope_schema_valid` | Validates against JSON Schema | Core |
103
+ | `envelope_invariants` | success/result/error consistency | Core |
104
+ | `error_code_registered` | Error code exists in registry | Core |
105
+ | `meta_mvi_present` | Valid MVI disclosure level | Standard |
106
+ | `meta_strict_present` | Strict mode declared | Standard |
107
+ | `strict_mode_behavior` | Optional fields omitted (not null) in strict mode | Standard |
108
+ | `strict_mode_enforced` | Additional properties rejected/allowed per mode | Standard |
109
+ | `pagination_mode_consistent` | Page fields match declared mode | Standard |
35
110
 
36
- ## Layout
111
+ ## Project layout
37
112
 
38
- ```text
39
- lafs.md
113
+ ```
114
+ lafs.md # Protocol specification
40
115
  schemas/v1/
116
+ envelope.schema.json # Envelope schema (Draft-07)
117
+ context-ledger.schema.json # Context ledger schema
118
+ error-registry.json # Error code registry
41
119
  src/
42
- tests/
43
- fixtures/
120
+ types.ts # TypeScript types (discriminated unions)
121
+ validateEnvelope.ts # Ajv-based schema validator
122
+ conformance.ts # Conformance runner (8 checks)
123
+ errorRegistry.ts # Error code helpers
124
+ flagSemantics.ts # Format flag resolution
125
+ cli.ts # CLI diagnostic tool
126
+ tests/ # 31 tests (vitest)
127
+ fixtures/ # 14 JSON test fixtures
44
128
  docs/
129
+ POSITIONING.md # MCP/A2A complementary positioning
130
+ VISION.md # Project vision and primary persona
131
+ CONFORMANCE.md # Conformance checks and adoption tiers
132
+ migrations/
133
+ v0.3.0-to-v0.4.0.md # Envelope rationalization migration
134
+ v0.4.0-to-v0.5.0.md # Pagination & MVI schema migration
135
+ CONTRIBUTING.md # Contributor guidelines, RFC process
45
136
  ```
137
+
138
+ ## Version history
139
+
140
+ | Version | Phase | Description |
141
+ |---------|-------|-------------|
142
+ | v0.5.0 | 2B | Conditional pagination, MVI field selection/expansion, context ledger schema |
143
+ | v0.4.0 | 2A | Optional page/error, extensions, strict/lenient mode, warnings |
144
+ | v0.3.0 | 1 | Strategic positioning, vision alignment, adoption tiers |
145
+ | v0.2.0 | 0 | Protocol cleanup, fixtures, governance, security considerations |
146
+ | v0.1.1 | — | Initial npm publish |
147
+ | v0.1.0 | — | Bootstrap |
148
+
149
+ ## License
150
+
151
+ MIT
@@ -3,14 +3,11 @@
3
3
  "$id": "https://lafs.dev/schemas/v1/envelope.schema.json",
4
4
  "title": "LAFS Envelope v1",
5
5
  "type": "object",
6
- "additionalProperties": false,
7
6
  "required": [
8
7
  "$schema",
9
8
  "_meta",
10
9
  "success",
11
- "result",
12
- "error",
13
- "page"
10
+ "result"
14
11
  ],
15
12
  "properties": {
16
13
  "$schema": {
@@ -62,11 +59,28 @@
62
59
  "type": "boolean"
63
60
  },
64
61
  "mvi": {
65
- "type": "boolean"
62
+ "type": "string",
63
+ "enum": ["minimal", "standard", "full", "custom"],
64
+ "description": "Disclosure level: minimal (MVI only), standard (common fields), full (all fields), custom (per _fields parameter)"
66
65
  },
67
66
  "contextVersion": {
68
67
  "type": "integer",
69
68
  "minimum": 0
69
+ },
70
+ "warnings": {
71
+ "type": "array",
72
+ "items": {
73
+ "type": "object",
74
+ "properties": {
75
+ "code": { "type": "string", "description": "Warning identifier (e.g., DEPRECATED_FIELD)" },
76
+ "message": { "type": "string", "description": "Human-readable warning" },
77
+ "deprecated": { "type": "string", "description": "The deprecated feature or field name" },
78
+ "replacement": { "type": "string", "description": "Suggested replacement, if any" },
79
+ "removeBy": { "type": "string", "description": "Version when the deprecated feature will be removed" }
80
+ },
81
+ "required": ["code", "message"]
82
+ },
83
+ "description": "Non-fatal advisory warnings (deprecations, migration hints)"
70
84
  }
71
85
  }
72
86
  },
@@ -127,14 +141,7 @@
127
141
  "page": {
128
142
  "type": ["object", "null"],
129
143
  "additionalProperties": false,
130
- "required": [
131
- "mode",
132
- "limit",
133
- "offset",
134
- "nextCursor",
135
- "hasMore",
136
- "total"
137
- ],
144
+ "required": ["mode"],
138
145
  "properties": {
139
146
  "mode": {
140
147
  "type": "string",
@@ -160,7 +167,61 @@
160
167
  "type": ["integer", "null"],
161
168
  "minimum": 0
162
169
  }
163
- }
170
+ },
171
+ "allOf": [
172
+ {
173
+ "if": {
174
+ "type": "object",
175
+ "properties": { "mode": { "const": "cursor" } },
176
+ "required": ["mode"]
177
+ },
178
+ "then": {
179
+ "required": ["mode", "nextCursor", "hasMore"],
180
+ "properties": {
181
+ "mode": true,
182
+ "nextCursor": true,
183
+ "hasMore": true,
184
+ "limit": true,
185
+ "total": true
186
+ }
187
+ }
188
+ },
189
+ {
190
+ "if": {
191
+ "type": "object",
192
+ "properties": { "mode": { "const": "offset" } },
193
+ "required": ["mode"]
194
+ },
195
+ "then": {
196
+ "required": ["mode", "limit", "offset", "hasMore"],
197
+ "properties": {
198
+ "mode": true,
199
+ "limit": true,
200
+ "offset": true,
201
+ "hasMore": true,
202
+ "total": true
203
+ }
204
+ }
205
+ },
206
+ {
207
+ "if": {
208
+ "type": "object",
209
+ "properties": { "mode": { "const": "none" } },
210
+ "required": ["mode"]
211
+ },
212
+ "then": {
213
+ "required": ["mode"],
214
+ "properties": {
215
+ "mode": true
216
+ }
217
+ }
218
+ }
219
+ ]
220
+ },
221
+ "_extensions": {
222
+ "type": "object",
223
+ "description": "Vendor extensions. Keys SHOULD use x- prefix (e.g., x-myvendor-trace-id).",
224
+ "additionalProperties": true
164
225
  }
165
226
  },
166
227
  "allOf": [
@@ -183,11 +244,39 @@
183
244
  }
184
245
  },
185
246
  "then": {
247
+ "required": ["error"],
186
248
  "properties": {
187
249
  "result": { "type": "null" },
188
250
  "error": { "type": "object" }
189
251
  }
190
252
  }
253
+ },
254
+ {
255
+ "if": {
256
+ "properties": {
257
+ "_meta": {
258
+ "type": "object",
259
+ "properties": {
260
+ "strict": { "const": true }
261
+ }
262
+ }
263
+ }
264
+ },
265
+ "then": {
266
+ "additionalProperties": false,
267
+ "properties": {
268
+ "$schema": true,
269
+ "_meta": true,
270
+ "success": true,
271
+ "result": true,
272
+ "error": true,
273
+ "page": true,
274
+ "_extensions": true
275
+ }
276
+ },
277
+ "else": {
278
+ "additionalProperties": true
279
+ }
191
280
  }
192
281
  ]
193
282
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "$schema": "https://json-schema.org/draft/2020-12/schema",
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
3
  "version": "1.0.0",
4
4
  "codes": [
5
5
  {
@@ -91,6 +91,24 @@
91
91
  "httpStatus": 426,
92
92
  "grpcStatus": "FAILED_PRECONDITION",
93
93
  "cliExit": 10
94
+ },
95
+ {
96
+ "code": "E_DISCLOSURE_UNKNOWN_FIELD",
97
+ "category": "VALIDATION",
98
+ "description": "Unrecognized expansion field in _expand parameter",
99
+ "retryable": false,
100
+ "httpStatus": 400,
101
+ "grpcStatus": "INVALID_ARGUMENT",
102
+ "cliExit": 2
103
+ },
104
+ {
105
+ "code": "E_MVI_BUDGET_EXCEEDED",
106
+ "category": "VALIDATION",
107
+ "description": "Response exceeds declared MVI budget (token, byte, or item limit)",
108
+ "retryable": false,
109
+ "httpStatus": 413,
110
+ "grpcStatus": "RESOURCE_EXHAUSTED",
111
+ "cliExit": 2
94
112
  }
95
113
  ]
96
114
  }
package/dist/src/cli.d.ts CHANGED
@@ -1,2 +1,16 @@
1
1
  #!/usr/bin/env node
2
+ /**
3
+ * LAFS Conformance CLI — diagnostic/human-readable tool.
4
+ *
5
+ * This CLI is a **diagnostic utility** that validates envelopes and flags
6
+ * against the LAFS schema and conformance checks. It is NOT itself a
7
+ * LAFS-conformant envelope producer. Its output is for human consumption
8
+ * and CI pipelines, not for machine-to-machine chaining.
9
+ *
10
+ * Exemption: The CLI is exempt from LAFS envelope conformance requirements.
11
+ * Its output format is not a LAFS envelope and MUST NOT be validated as one.
12
+ *
13
+ * @task T042
14
+ * @epic T034
15
+ */
2
16
  export {};
package/dist/src/cli.js CHANGED
@@ -1,4 +1,18 @@
1
1
  #!/usr/bin/env node
2
+ /**
3
+ * LAFS Conformance CLI — diagnostic/human-readable tool.
4
+ *
5
+ * This CLI is a **diagnostic utility** that validates envelopes and flags
6
+ * against the LAFS schema and conformance checks. It is NOT itself a
7
+ * LAFS-conformant envelope producer. Its output is for human consumption
8
+ * and CI pipelines, not for machine-to-machine chaining.
9
+ *
10
+ * Exemption: The CLI is exempt from LAFS envelope conformance requirements.
11
+ * Its output format is not a LAFS envelope and MUST NOT be validated as one.
12
+ *
13
+ * @task T042
14
+ * @epic T034
15
+ */
2
16
  import { readFile } from "node:fs/promises";
3
17
  import { runEnvelopeConformance, runFlagConformance } from "./conformance.js";
4
18
  function parseArgs(argv) {
File without changes
@@ -12,17 +12,79 @@ export function runEnvelopeConformance(envelope) {
12
12
  return { ok: false, checks };
13
13
  }
14
14
  const typed = envelope;
15
- const invariant = typed.success ? typed.error === null : typed.result === null;
16
- pushCheck(checks, "envelope_invariants", invariant, invariant ? undefined : "success/result/error invariant violated");
15
+ // envelope_invariants: success=true allows error to be null OR omitted;
16
+ // success=false requires error to be a non-null object and result===null.
17
+ const invariant = typed.success
18
+ ? typed.error == null // null or undefined (omitted) both valid for success
19
+ : typed.result === null && typed.error != null;
20
+ pushCheck(checks, "envelope_invariants", invariant, invariant
21
+ ? undefined
22
+ : typed.success
23
+ ? "success=true but error is present and non-null"
24
+ : "success=false requires result===null and error to be a non-null object");
25
+ // error_code_registered: only checked when error is present (error is optional when success=true)
17
26
  if (typed.error) {
18
27
  const registered = isRegisteredErrorCode(typed.error.code);
19
28
  pushCheck(checks, "error_code_registered", registered, registered ? undefined : `unregistered code: ${typed.error.code}`);
20
29
  }
21
30
  else {
22
- pushCheck(checks, "error_code_registered", true);
31
+ pushCheck(checks, "error_code_registered", true, "error field absent or null — skipped (optional when success=true)");
23
32
  }
24
- pushCheck(checks, "meta_mvi_present", typeof typed._meta.mvi === "boolean");
33
+ const validMviLevels = ["minimal", "standard", "full", "custom"];
34
+ pushCheck(checks, "meta_mvi_present", validMviLevels.includes(typed._meta.mvi), validMviLevels.includes(typed._meta.mvi) ? undefined : `invalid mvi level: ${String(typed._meta.mvi)}`);
25
35
  pushCheck(checks, "meta_strict_present", typeof typed._meta.strict === "boolean");
36
+ // strict_mode_behavior: when strict=true, the envelope MUST NOT contain
37
+ // explicit null for optional fields that can be omitted (page, error on success).
38
+ if (typed._meta.strict) {
39
+ const obj = envelope;
40
+ const hasExplicitNullError = typed.success && "error" in obj && obj["error"] === null;
41
+ const hasExplicitNullPage = "page" in obj && obj["page"] === null;
42
+ const strictClean = !hasExplicitNullError && !hasExplicitNullPage;
43
+ pushCheck(checks, "strict_mode_behavior", strictClean, strictClean
44
+ ? undefined
45
+ : "strict mode: optional fields should be omitted rather than set to null");
46
+ }
47
+ // pagination_mode_consistent: when page is present and is an object, verify
48
+ // that the fields present match the declared pagination mode.
49
+ if (typed.page && typeof typed.page === "object") {
50
+ const page = typed.page;
51
+ const mode = page["mode"];
52
+ let consistent = true;
53
+ let detail;
54
+ if (mode === "cursor") {
55
+ if (page["offset"] !== undefined) {
56
+ consistent = false;
57
+ detail = "cursor mode should not include offset field";
58
+ }
59
+ }
60
+ else if (mode === "offset") {
61
+ if (page["nextCursor"] !== undefined) {
62
+ consistent = false;
63
+ detail = "offset mode should not include nextCursor field";
64
+ }
65
+ }
66
+ else if (mode === "none") {
67
+ const extraFields = Object.keys(page).filter((k) => k !== "mode");
68
+ if (extraFields.length > 0) {
69
+ consistent = false;
70
+ detail = `none mode should only have mode field, found: ${extraFields.join(", ")}`;
71
+ }
72
+ }
73
+ pushCheck(checks, "pagination_mode_consistent", consistent, consistent ? undefined : detail);
74
+ }
75
+ // strict_mode_enforced: verify the schema enforces additional-property rules.
76
+ // When strict=true, extra top-level properties must be rejected by validation.
77
+ // When strict=false, extra top-level properties must be allowed.
78
+ {
79
+ const extraPropEnvelope = { ...envelope, _unknown_extra: true };
80
+ const extraResult = validateEnvelope(extraPropEnvelope);
81
+ if (typed._meta.strict) {
82
+ pushCheck(checks, "strict_mode_enforced", !extraResult.valid, extraResult.valid ? "strict=true but additional properties were accepted" : undefined);
83
+ }
84
+ else {
85
+ pushCheck(checks, "strict_mode_enforced", extraResult.valid, !extraResult.valid ? "strict=false but additional properties were rejected" : undefined);
86
+ }
87
+ }
26
88
  return { ok: checks.every((check) => check.pass), checks };
27
89
  }
28
90
  export function runFlagConformance(flags) {
@@ -30,13 +92,24 @@ export function runFlagConformance(flags) {
30
92
  try {
31
93
  const resolved = resolveOutputFormat(flags);
32
94
  pushCheck(checks, "flag_conflict_rejected", !(flags.humanFlag && flags.jsonFlag));
33
- pushCheck(checks, "json_default_when_unspecified", resolved.format === "json" || Boolean(flags.projectDefault || flags.userDefault));
95
+ // Protocol-default check: when nothing is specified (source === "default"),
96
+ // the protocol requires JSON as the default format.
97
+ const isProtocolDefault = resolved.source === "default";
98
+ pushCheck(checks, "json_protocol_default", !isProtocolDefault || resolved.format === "json", isProtocolDefault && resolved.format !== "json"
99
+ ? `protocol default should be json, got ${resolved.format}`
100
+ : undefined);
101
+ // Config-override check: when a project or user default is active,
102
+ // the resolved format must match the config-provided value.
103
+ const hasConfigOverride = resolved.source === "project" || resolved.source === "user";
104
+ const expectedOverride = resolved.source === "project" ? flags.projectDefault : flags.userDefault;
105
+ pushCheck(checks, "config_override_respected", !hasConfigOverride || resolved.format === expectedOverride, hasConfigOverride && resolved.format !== expectedOverride
106
+ ? `config override expected ${String(expectedOverride)}, got ${resolved.format}`
107
+ : undefined);
34
108
  }
35
109
  catch (error) {
36
110
  if (error instanceof LAFSFlagError && error.code === "E_FORMAT_CONFLICT") {
37
111
  pushCheck(checks, "flag_conflict_rejected", true);
38
- pushCheck(checks, "json_default_when_unspecified", true);
39
- return { ok: true, checks };
112
+ return { ok: checks.every((check) => check.pass), checks };
40
113
  }
41
114
  pushCheck(checks, "flag_resolution", false, error instanceof Error ? error.message : String(error));
42
115
  return { ok: false, checks };
File without changes
File without changes
File without changes
File without changes
File without changes
package/dist/src/index.js CHANGED
File without changes
@@ -1,5 +1,12 @@
1
1
  export type LAFSTransport = "cli" | "http" | "grpc" | "sdk";
2
2
  export type LAFSErrorCategory = "VALIDATION" | "AUTH" | "PERMISSION" | "NOT_FOUND" | "CONFLICT" | "RATE_LIMIT" | "TRANSIENT" | "INTERNAL" | "CONTRACT" | "MIGRATION";
3
+ export interface Warning {
4
+ code: string;
5
+ message: string;
6
+ deprecated?: string;
7
+ replacement?: string;
8
+ removeBy?: string;
9
+ }
3
10
  export interface LAFSMeta {
4
11
  specVersion: string;
5
12
  schemaVersion: string;
@@ -8,8 +15,9 @@ export interface LAFSMeta {
8
15
  requestId: string;
9
16
  transport: LAFSTransport;
10
17
  strict: boolean;
11
- mvi: boolean;
18
+ mvi: 'minimal' | 'standard' | 'full' | 'custom';
12
19
  contextVersion: number;
20
+ warnings?: Warning[];
13
21
  }
14
22
  export interface LAFSError {
15
23
  code: string;
@@ -19,21 +27,48 @@ export interface LAFSError {
19
27
  retryAfterMs: number | null;
20
28
  details: Record<string, unknown>;
21
29
  }
22
- export interface LAFSPage {
23
- mode: "offset" | "cursor" | "none";
30
+ export interface LAFSPageCursor {
31
+ mode: "cursor";
32
+ nextCursor: string | null;
33
+ hasMore: boolean;
34
+ limit?: number;
35
+ total?: number | null;
36
+ }
37
+ export interface LAFSPageOffset {
38
+ mode: "offset";
24
39
  limit: number;
25
40
  offset: number;
26
- nextCursor: string | null;
27
41
  hasMore: boolean;
28
- total: number | null;
42
+ total?: number | null;
43
+ }
44
+ export interface LAFSPageNone {
45
+ mode: "none";
46
+ }
47
+ export type LAFSPage = LAFSPageCursor | LAFSPageOffset | LAFSPageNone;
48
+ export interface ContextLedgerEntry {
49
+ entryId: string;
50
+ timestamp: string;
51
+ operation: string;
52
+ contextDelta: Record<string, unknown>;
53
+ requestId?: string;
54
+ }
55
+ export interface ContextLedger {
56
+ ledgerId: string;
57
+ version: number;
58
+ createdAt: string;
59
+ updatedAt: string;
60
+ entries: ContextLedgerEntry[];
61
+ checksum: string;
62
+ maxEntries: number;
29
63
  }
30
64
  export interface LAFSEnvelope {
31
65
  $schema: "https://lafs.dev/schemas/v1/envelope.schema.json";
32
66
  _meta: LAFSMeta;
33
67
  success: boolean;
34
68
  result: Record<string, unknown> | Record<string, unknown>[] | null;
35
- error: LAFSError | null;
36
- page: LAFSPage | null;
69
+ error?: LAFSError | null;
70
+ page?: LAFSPage | null;
71
+ _extensions?: Record<string, unknown>;
37
72
  }
38
73
  export interface FlagInput {
39
74
  requestedFormat?: "json" | "human";
package/dist/src/types.js CHANGED
File without changes
File without changes
File without changes
package/lafs.md CHANGED
@@ -2,19 +2,41 @@
2
2
 
3
3
  ## 1. Scope
4
4
 
5
- LAFS defines a protocol for software systems whose primary consumer is an LLM agent.
5
+ LAFS is a **response envelope contract specification**. It defines the canonical shape of structured responses — success envelopes, error envelopes, pagination metadata, and context preservation — for software systems whose primary consumer is an LLM agent or AI-driven tool.
6
6
 
7
- LAFS is transport-agnostic and language-agnostic. It applies to CLI, SDK, HTTP, gRPC, and orchestrated multi-agent systems.
7
+ LAFS is **not** a protocol, framework, or runtime. It specifies **what** a conformant response looks like, not how that response is transported or generated. Implementations MAY deliver LAFS envelopes over HTTP, gRPC, CLI, SDK interfaces, message queues, or any other transport mechanism. LAFS is transport-agnostic and language-agnostic.
8
+
9
+ LAFS is designed to complement — not compete with — existing agent and tool-integration protocols. The Model Context Protocol (MCP) defines how LLM hosts discover and invoke tools; the Agent-to-Agent protocol (A2A) defines how autonomous agents communicate and delegate tasks. LAFS operates at a different layer: it standardizes the **response contract** that tools and agents SHOULD return, regardless of the protocol used to invoke them. An MCP tool server, an A2A agent, or a plain REST API MAY all return LAFS-conformant envelopes.
10
+
11
+ While LAFS is purpose-built for AI and LLM tool ecosystems — where deterministic, machine-parseable responses are critical — the specification is generally applicable to any API that benefits from structured, predictable response contracts.
8
12
 
9
13
  ---
10
14
 
11
- ## 2. RFC 2119 Keywords
15
+ ## 2. Non-Goals
16
+
17
+ The following capabilities are intentionally outside the scope of LAFS. This section exists to prevent scope creep and to clarify boundaries with complementary protocols.
18
+
19
+ 1. **Streaming responses.** LAFS defines discrete request/response envelopes. Streaming mechanisms such as SSE or WebSocket are transport concerns and MUST NOT be defined by LAFS.
20
+
21
+ 2. **Asynchronous processing.** LAFS envelopes are synchronous response contracts. Async job patterns (polling, webhooks, callback queues) are application-layer concerns and are outside LAFS scope.
22
+
23
+ 3. **Authentication and authorization.** LAFS is transport-agnostic; auth is a transport or middleware concern. LAFS MAY carry auth-related error codes (e.g., `E_AUTH_*`) but MUST NOT define authentication or authorization flows.
24
+
25
+ 4. **Multi-modal content.** LAFS envelopes carry structured JSON data. Binary payloads, media content negotiation, and multi-modal encoding are outside scope.
26
+
27
+ 5. **Transport binding.** LAFS defines the response envelope shape, not how it maps to HTTP status codes, gRPC metadata, or other transport semantics. Transport mapping specifications are a separate concern.
28
+
29
+ 6. **Service discovery.** LAFS does not define how consumers locate or enumerate LAFS-conformant endpoints. Discovery mechanisms SHOULD be provided by the deployment layer or complementary protocols.
30
+
31
+ ---
32
+
33
+ ## 3. RFC 2119 Keywords
12
34
 
13
35
  The keywords MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY are interpreted per RFC 2119.
14
36
 
15
37
  ---
16
38
 
17
- ## 3. Non-Negotiable Protocol Rules
39
+ ## 4. Non-Negotiable Protocol Rules
18
40
 
19
41
  1. Output default MUST be machine-readable JSON.
20
42
  2. Human-readable mode MUST be explicit opt-in.
@@ -25,9 +47,9 @@ The keywords MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY are interpreted per RFC
25
47
 
26
48
  ---
27
49
 
28
- ## 4. Format Semantics
50
+ ## 5. Format Semantics
29
51
 
30
- ### 4.1 Required output semantics
52
+ ### 5.1 Required output semantics
31
53
 
32
54
  - Default format MUST be `json`.
33
55
  - `--human` MUST switch output mode to human-readable.
@@ -35,7 +57,7 @@ The keywords MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY are interpreted per RFC
35
57
  - Providing both `--human` and `--json` MUST fail with `E_FORMAT_CONFLICT`.
36
58
  - Explicit flags MUST override env/config defaults.
37
59
 
38
- ### 4.2 Recommended precedence
60
+ ### 5.2 Recommended precedence
39
61
 
40
62
  1. Explicit CLI/API request value
41
63
  2. Project config
@@ -44,7 +66,7 @@ The keywords MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY are interpreted per RFC
44
66
 
45
67
  ---
46
68
 
47
- ## 5. Canonical Response Envelope
69
+ ## 6. Canonical Response Envelope
48
70
 
49
71
  All responses MUST conform to `schemas/v1/envelope.schema.json`.
50
72
 
@@ -69,16 +91,25 @@ All responses MUST conform to `schemas/v1/envelope.schema.json`.
69
91
  }
70
92
  ```
71
93
 
72
- ### 5.1 Envelope invariants
94
+ ### 6.1 Envelope invariants
73
95
 
74
96
  - Exactly one of `result` or `error` MUST be non-null.
75
- - `success=true` implies `error=null`.
76
- - `success=false` implies `result=null`.
97
+ - `success=true` implies `error=null` or error omitted.
98
+ - `success=false` implies `result=null` and `error` MUST be present.
99
+ - The `page` and `error` fields are optional when their value would be null. In strict mode, producers SHOULD omit these fields rather than set them to null.
77
100
  - Unknown fields SHOULD be rejected when strict mode is enabled.
78
101
 
102
+ ### 6.2 Extensions
103
+
104
+ The envelope supports an optional `_extensions` object for vendor-specific metadata. Because `_extensions` is a declared property in the schema, it is permitted regardless of strict mode.
105
+
106
+ - Keys SHOULD use the `x-` prefix convention (e.g., `x-myvendor-trace-id`).
107
+ - Consumers MUST NOT rely on extension fields for protocol-required behavior.
108
+ - Producers MAY omit `_extensions` entirely; the field is always optional.
109
+
79
110
  ---
80
111
 
81
- ## 6. Error Contract
112
+ ## 7. Error Contract
82
113
 
83
114
  Errors MUST conform to envelope `error` shape and use codes from `schemas/v1/error-registry.json`.
84
115
 
@@ -95,7 +126,30 @@ Errors MUST conform to envelope `error` shape and use codes from `schemas/v1/err
95
126
  }
96
127
  ```
97
128
 
98
- ### 6.1 Required behavior
129
+ ### 7.1 Error code naming convention
130
+
131
+ Error codes MUST match the pattern: `^E_[A-Z0-9]+_[A-Z0-9_]+$`
132
+
133
+ The structure is **E\_\<DOMAIN\>\_\<SPECIFIC\>**, where:
134
+
135
+ - **E\_** — required prefix identifying the value as an error code.
136
+ - **DOMAIN** — a short uppercase token describing the error's semantic area (e.g., `VALIDATION`, `CONTEXT`, `RATE`, `MIGRATION`). The domain is descriptive; it does not need to equal the `category` enum value.
137
+ - **SPECIFIC** — one or more uppercase tokens (separated by `_`) that distinguish the error within its domain (e.g., `SCHEMA`, `MISSING`, `UNSUPPORTED_VERSION`).
138
+
139
+ Examples from the registry:
140
+
141
+ | Code | Domain | Specific | Category |
142
+ |---|---|---|---|
143
+ | `E_VALIDATION_SCHEMA` | `VALIDATION` | `SCHEMA` | VALIDATION |
144
+ | `E_NOT_FOUND_RESOURCE` | `NOT` | `FOUND_RESOURCE` | NOT_FOUND |
145
+ | `E_CONTEXT_MISSING` | `CONTEXT` | `MISSING` | CONTRACT |
146
+ | `E_MIGRATION_UNSUPPORTED_VERSION` | `MIGRATION` | `UNSUPPORTED_VERSION` | MIGRATION |
147
+
148
+ Registered categories (the `category` field in error objects): `VALIDATION`, `AUTH`, `PERMISSION`, `NOT_FOUND`, `CONFLICT`, `RATE_LIMIT`, `TRANSIENT`, `INTERNAL`, `CONTRACT`, `MIGRATION`.
149
+
150
+ Custom error codes MUST match the same regex pattern. Implementations SHOULD choose a domain token that clearly communicates the error's origin.
151
+
152
+ ### 7.2 Required behavior
99
153
 
100
154
  - Error codes MUST be stable within major versions.
101
155
  - Retry semantics MUST be encoded in `retryable` and `retryAfterMs`.
@@ -103,7 +157,7 @@ Errors MUST conform to envelope `error` shape and use codes from `schemas/v1/err
103
157
 
104
158
  ---
105
159
 
106
- ## 7. Context Preservation
160
+ ## 8. Context Preservation
107
161
 
108
162
  Multi-step operations MUST preserve a context ledger with at least:
109
163
 
@@ -124,20 +178,35 @@ Rules:
124
178
 
125
179
  ---
126
180
 
127
- ## 8. MVI and Progressive Disclosure
181
+ ## 9. MVI and Progressive Disclosure
128
182
 
129
- ### 8.1 MVI default
183
+ ### 9.1 MVI default
130
184
 
131
185
  - Default list/batch outputs MUST only contain fields required for next action.
132
186
  - Verbose fields SHOULD be omitted by default.
133
187
  - Systems SHOULD publish operation-level MVI budgets.
134
188
 
135
- ### 8.2 Progressive disclosure
189
+ ### 9.2 Field selection (`_fields`)
136
190
 
137
- - Expanded detail retrieval MUST require explicit request.
138
- - Unknown expansion fields SHOULD fail validation.
191
+ Clients MAY request a subset of response fields via the `_fields` request parameter.
139
192
 
140
- ### 8.3 Pagination
193
+ - `_fields` MUST be an array of strings identifying top-level result field names.
194
+ - When `_fields` is present, the server MUST return only the requested fields plus any MVI-required fields for the declared disclosure level.
195
+ - When `_fields` is absent, the server MUST return fields appropriate for the declared `_meta.mvi` disclosure level.
196
+ - If a requested field does not exist on the resource, the server SHOULD omit it silently (no error). Servers MAY include a warning in `_meta.warnings` for unknown fields.
197
+ - `_fields` MUST NOT affect envelope structural fields (`$schema`, `_meta`, `success`, `error`, `page`, `_extensions`); it applies only to the contents of `result`.
198
+
199
+ ### 9.3 Expansion mechanism (`_expand`)
200
+
201
+ Clients MAY request expanded/nested data via the `_expand` request parameter.
202
+
203
+ - `_expand` MUST be an array of strings identifying relationships or nested resources to include inline.
204
+ - When `_expand` is present, the server MUST resolve and inline the requested expansions within `result`.
205
+ - If a requested expansion field is not recognized, the server MUST return error code `E_DISCLOSURE_UNKNOWN_FIELD` with category `VALIDATION`.
206
+ - Servers SHOULD document available expansion fields per operation.
207
+ - Expansion depth MUST be limited to prevent unbounded recursion. Servers SHOULD enforce a maximum expansion depth and return `E_MVI_BUDGET_EXCEEDED` if exceeded.
208
+
209
+ ### 9.4 Pagination
141
210
 
142
211
  - List operations SHOULD return deterministic `page` metadata.
143
212
  - Pagination mode (offset or cursor) MUST be documented.
@@ -145,7 +214,7 @@ Rules:
145
214
 
146
215
  ---
147
216
 
148
- ## 9. Strictness
217
+ ## 10. Strictness
149
218
 
150
219
  - Agent surfaces SHOULD default `strict=true`.
151
220
  - Strict mode violations SHOULD fail with contract/validation error codes.
@@ -153,7 +222,7 @@ Rules:
153
222
 
154
223
  ---
155
224
 
156
- ## 10. Versioning and Deprecation
225
+ ## 11. Versioning and Deprecation
157
226
 
158
227
  - Protocol versions MUST follow SemVer.
159
228
  - Minor/patch changes MUST be backward compatible.
@@ -164,6 +233,81 @@ See `docs/VERSIONING.md` and `docs/DEPRECATION.md`.
164
233
 
165
234
  ---
166
235
 
167
- ## 11. Conformance
236
+ ## 12. Conformance
168
237
 
169
238
  Conforming implementations MUST pass minimum checks in `docs/CONFORMANCE.md` and schema validation for the canonical envelope.
239
+
240
+ ### 12.1 Adoption Tiers
241
+
242
+ LAFS defines three adoption tiers to enable gradual conformance. Each tier builds on the previous tier's requirements. Implementations MUST declare which tier they target and MUST pass all checks required by that tier.
243
+
244
+ #### 12.1.1 Core Tier
245
+
246
+ The Core tier represents **minimum viable LAFS adoption**. It verifies that responses use the canonical envelope shape and satisfy basic structural invariants.
247
+
248
+ Required conformance checks:
249
+
250
+ | Check | Description |
251
+ |---|---|
252
+ | `envelope_schema_valid` | Response validates against `schemas/v1/envelope.schema.json` |
253
+ | `envelope_invariants` | `success`/`result`/`error` mutual exclusivity holds (Section 6.1) |
254
+
255
+ Use cases: quick adoption, internal APIs, prototyping, evaluating LAFS fit.
256
+
257
+ #### 12.1.2 Standard Tier
258
+
259
+ The Standard tier is **recommended for production** use. It adds semantic checks for error codes, metadata flags, and format defaults on top of all Core tier requirements.
260
+
261
+ Required conformance checks — all Core checks, plus:
262
+
263
+ | Check | Description |
264
+ |---|---|
265
+ | `error_code_registered` | All error codes come from the registered error registry (Section 7) |
266
+ | `meta_mvi_present` | `_meta.mvi` flag is present and valid (Section 9.1) |
267
+ | `meta_strict_present` | `_meta.strict` flag is present and boolean (Section 10) |
268
+ | `json_protocol_default` | JSON is the default output format when no explicit format is requested (Section 5.1) |
269
+
270
+ Use cases: production APIs, public-facing services, third-party integrations.
271
+
272
+ #### 12.1.3 Complete Tier
273
+
274
+ The Complete tier represents **full LAFS compliance**. It adds configuration, flag-handling, and advanced feature checks on top of all Standard tier requirements.
275
+
276
+ Required conformance checks — all Standard checks, plus:
277
+
278
+ | Check | Description |
279
+ |---|---|
280
+ | `config_override_respected` | Project/user config-based format overrides are correctly applied (Section 5.2) |
281
+ | `flag_conflict_rejected` | Conflicting format flags (e.g., `--human --json`) are properly rejected with `E_FORMAT_CONFLICT` (Section 5.1) |
282
+ | `context_validation` | Context preservation invariants hold for multi-step operations (Section 8) |
283
+ | `pagination_validation` | Pagination metadata validates when present (Section 9.3) |
284
+
285
+ Use cases: official LAFS-conformant implementations, reference implementations, certification.
286
+
287
+ > **Note:** `context_validation` and `pagination_validation` are reserved check names. Implementations SHOULD treat these as automatically passing until the corresponding conformance runners are available.
288
+
289
+ ---
290
+
291
+ ## 13. Security Considerations
292
+
293
+ This section addresses security threats relevant to LAFS envelope production and consumption. LAFS is transport-agnostic and does not define its own cryptographic or authentication mechanisms; implementers MUST rely on the underlying transport and application layers for those controls.
294
+
295
+ ### 13.1 Injection attacks
296
+
297
+ LAFS envelopes carry user-provided data in `result`, `error`, and `details` fields. Implementers MUST sanitize all envelope contents before rendering in HTML, constructing shell commands, or executing in eval-like contexts. Error messages MUST NOT contain unsanitized user input. Implementations that embed envelope values in SQL, LDAP, or similar query languages MUST use parameterized interfaces.
298
+
299
+ ### 13.2 Tampering
300
+
301
+ LAFS does not define integrity protection at the envelope level. If envelope integrity is required, implementers SHOULD use transport-level security (e.g., TLS) and MAY implement envelope signing as an extension. Consumers MUST NOT trust envelope contents without verifying the transport channel. Implementations that relay envelopes across trust boundaries SHOULD re-validate against `schemas/v1/envelope.schema.json` at each boundary.
302
+
303
+ ### 13.3 Information disclosure
304
+
305
+ Error details MAY contain sensitive information such as stack traces, internal paths, or database identifiers. Implementations SHOULD distinguish between development and production error detail levels. The `details` field in error objects MUST NOT expose internal system information in production environments. Implementations SHOULD define an explicit allow-list of fields permitted in production error responses.
306
+
307
+ ### 13.4 Replay attacks
308
+
309
+ LAFS includes `requestId` and `timestamp` in `_meta` for correlation (Section 6). Implementers MAY use these fields for replay detection but MUST NOT rely solely on them, as LAFS does not mandate uniqueness or freshness guarantees for these values. Transport-level replay protection (e.g., TLS with appropriate session management) is RECOMMENDED.
310
+
311
+ ### 13.5 Denial of service
312
+
313
+ Large envelope payloads could be used for resource exhaustion. Implementations SHOULD enforce maximum envelope size limits appropriate to their deployment context. Pagination (Section 9.3) SHOULD be used to bound response sizes for list operations. Implementations SHOULD reject envelopes that exceed the configured size limit with a structured error.
package/package.json CHANGED
@@ -1,11 +1,17 @@
1
1
  {
2
2
  "name": "@cleocode/lafs-protocol",
3
- "version": "0.1.0",
3
+ "version": "0.5.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "LLM-Agent-First Specification schemas and conformance tooling",
7
7
  "main": "dist/src/index.js",
8
8
  "types": "dist/src/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/src/index.js",
12
+ "types": "./dist/src/index.d.ts"
13
+ }
14
+ },
9
15
  "files": [
10
16
  "dist",
11
17
  "schemas",
@@ -29,6 +35,7 @@
29
35
  },
30
36
  "scripts": {
31
37
  "build": "rm -rf dist && tsc -p tsconfig.build.json",
38
+ "prepack": "npm run build",
32
39
  "typecheck": "tsc --noEmit",
33
40
  "test": "vitest run",
34
41
  "conformance": "tsx src/cli.ts"
@@ -0,0 +1,70 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://lafs.dev/schemas/v1/context-ledger.schema.json",
4
+ "title": "LAFS Context Ledger",
5
+ "description": "Tracks state across request/response cycles for context preservation",
6
+ "type": "object",
7
+ "required": ["ledgerId", "version", "createdAt", "updatedAt", "entries", "checksum", "maxEntries"],
8
+ "properties": {
9
+ "ledgerId": {
10
+ "type": "string",
11
+ "description": "Unique identifier for this context ledger instance"
12
+ },
13
+ "version": {
14
+ "type": "integer",
15
+ "minimum": 0,
16
+ "description": "Monotonically increasing version number matching _meta.contextVersion"
17
+ },
18
+ "createdAt": {
19
+ "type": "string",
20
+ "format": "date-time",
21
+ "description": "ISO 8601 timestamp when the ledger was created"
22
+ },
23
+ "updatedAt": {
24
+ "type": "string",
25
+ "format": "date-time",
26
+ "description": "ISO 8601 timestamp of the last ledger update"
27
+ },
28
+ "entries": {
29
+ "type": "array",
30
+ "items": {
31
+ "type": "object",
32
+ "required": ["entryId", "timestamp", "operation", "contextDelta"],
33
+ "properties": {
34
+ "entryId": {
35
+ "type": "string",
36
+ "description": "Unique identifier for this ledger entry"
37
+ },
38
+ "timestamp": {
39
+ "type": "string",
40
+ "format": "date-time"
41
+ },
42
+ "operation": {
43
+ "type": "string",
44
+ "description": "The operation that generated this entry"
45
+ },
46
+ "contextDelta": {
47
+ "type": "object",
48
+ "description": "The context changes introduced by this operation",
49
+ "additionalProperties": true
50
+ },
51
+ "requestId": {
52
+ "type": "string",
53
+ "description": "Correlation with the request that produced this entry"
54
+ }
55
+ }
56
+ },
57
+ "description": "Ordered array of context entries (append-only)"
58
+ },
59
+ "checksum": {
60
+ "type": "string",
61
+ "description": "Integrity checksum of the ledger contents"
62
+ },
63
+ "maxEntries": {
64
+ "type": "integer",
65
+ "minimum": 1,
66
+ "description": "Maximum number of entries before truncation/compaction"
67
+ }
68
+ },
69
+ "additionalProperties": false
70
+ }
@@ -3,14 +3,11 @@
3
3
  "$id": "https://lafs.dev/schemas/v1/envelope.schema.json",
4
4
  "title": "LAFS Envelope v1",
5
5
  "type": "object",
6
- "additionalProperties": false,
7
6
  "required": [
8
7
  "$schema",
9
8
  "_meta",
10
9
  "success",
11
- "result",
12
- "error",
13
- "page"
10
+ "result"
14
11
  ],
15
12
  "properties": {
16
13
  "$schema": {
@@ -62,11 +59,28 @@
62
59
  "type": "boolean"
63
60
  },
64
61
  "mvi": {
65
- "type": "boolean"
62
+ "type": "string",
63
+ "enum": ["minimal", "standard", "full", "custom"],
64
+ "description": "Disclosure level: minimal (MVI only), standard (common fields), full (all fields), custom (per _fields parameter)"
66
65
  },
67
66
  "contextVersion": {
68
67
  "type": "integer",
69
68
  "minimum": 0
69
+ },
70
+ "warnings": {
71
+ "type": "array",
72
+ "items": {
73
+ "type": "object",
74
+ "properties": {
75
+ "code": { "type": "string", "description": "Warning identifier (e.g., DEPRECATED_FIELD)" },
76
+ "message": { "type": "string", "description": "Human-readable warning" },
77
+ "deprecated": { "type": "string", "description": "The deprecated feature or field name" },
78
+ "replacement": { "type": "string", "description": "Suggested replacement, if any" },
79
+ "removeBy": { "type": "string", "description": "Version when the deprecated feature will be removed" }
80
+ },
81
+ "required": ["code", "message"]
82
+ },
83
+ "description": "Non-fatal advisory warnings (deprecations, migration hints)"
70
84
  }
71
85
  }
72
86
  },
@@ -127,14 +141,7 @@
127
141
  "page": {
128
142
  "type": ["object", "null"],
129
143
  "additionalProperties": false,
130
- "required": [
131
- "mode",
132
- "limit",
133
- "offset",
134
- "nextCursor",
135
- "hasMore",
136
- "total"
137
- ],
144
+ "required": ["mode"],
138
145
  "properties": {
139
146
  "mode": {
140
147
  "type": "string",
@@ -160,7 +167,61 @@
160
167
  "type": ["integer", "null"],
161
168
  "minimum": 0
162
169
  }
163
- }
170
+ },
171
+ "allOf": [
172
+ {
173
+ "if": {
174
+ "type": "object",
175
+ "properties": { "mode": { "const": "cursor" } },
176
+ "required": ["mode"]
177
+ },
178
+ "then": {
179
+ "required": ["mode", "nextCursor", "hasMore"],
180
+ "properties": {
181
+ "mode": true,
182
+ "nextCursor": true,
183
+ "hasMore": true,
184
+ "limit": true,
185
+ "total": true
186
+ }
187
+ }
188
+ },
189
+ {
190
+ "if": {
191
+ "type": "object",
192
+ "properties": { "mode": { "const": "offset" } },
193
+ "required": ["mode"]
194
+ },
195
+ "then": {
196
+ "required": ["mode", "limit", "offset", "hasMore"],
197
+ "properties": {
198
+ "mode": true,
199
+ "limit": true,
200
+ "offset": true,
201
+ "hasMore": true,
202
+ "total": true
203
+ }
204
+ }
205
+ },
206
+ {
207
+ "if": {
208
+ "type": "object",
209
+ "properties": { "mode": { "const": "none" } },
210
+ "required": ["mode"]
211
+ },
212
+ "then": {
213
+ "required": ["mode"],
214
+ "properties": {
215
+ "mode": true
216
+ }
217
+ }
218
+ }
219
+ ]
220
+ },
221
+ "_extensions": {
222
+ "type": "object",
223
+ "description": "Vendor extensions. Keys SHOULD use x- prefix (e.g., x-myvendor-trace-id).",
224
+ "additionalProperties": true
164
225
  }
165
226
  },
166
227
  "allOf": [
@@ -183,11 +244,39 @@
183
244
  }
184
245
  },
185
246
  "then": {
247
+ "required": ["error"],
186
248
  "properties": {
187
249
  "result": { "type": "null" },
188
250
  "error": { "type": "object" }
189
251
  }
190
252
  }
253
+ },
254
+ {
255
+ "if": {
256
+ "properties": {
257
+ "_meta": {
258
+ "type": "object",
259
+ "properties": {
260
+ "strict": { "const": true }
261
+ }
262
+ }
263
+ }
264
+ },
265
+ "then": {
266
+ "additionalProperties": false,
267
+ "properties": {
268
+ "$schema": true,
269
+ "_meta": true,
270
+ "success": true,
271
+ "result": true,
272
+ "error": true,
273
+ "page": true,
274
+ "_extensions": true
275
+ }
276
+ },
277
+ "else": {
278
+ "additionalProperties": true
279
+ }
191
280
  }
192
281
  ]
193
282
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "$schema": "https://json-schema.org/draft/2020-12/schema",
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
3
  "version": "1.0.0",
4
4
  "codes": [
5
5
  {
@@ -91,6 +91,24 @@
91
91
  "httpStatus": 426,
92
92
  "grpcStatus": "FAILED_PRECONDITION",
93
93
  "cliExit": 10
94
+ },
95
+ {
96
+ "code": "E_DISCLOSURE_UNKNOWN_FIELD",
97
+ "category": "VALIDATION",
98
+ "description": "Unrecognized expansion field in _expand parameter",
99
+ "retryable": false,
100
+ "httpStatus": 400,
101
+ "grpcStatus": "INVALID_ARGUMENT",
102
+ "cliExit": 2
103
+ },
104
+ {
105
+ "code": "E_MVI_BUDGET_EXCEEDED",
106
+ "category": "VALIDATION",
107
+ "description": "Response exceeds declared MVI budget (token, byte, or item limit)",
108
+ "retryable": false,
109
+ "httpStatus": 413,
110
+ "grpcStatus": "RESOURCE_EXHAUSTED",
111
+ "cliExit": 2
94
112
  }
95
113
  ]
96
114
  }