@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 +0 -0
- package/README.md +129 -23
- package/dist/schemas/v1/envelope.schema.json +103 -14
- package/dist/schemas/v1/error-registry.json +19 -1
- package/dist/src/cli.d.ts +14 -0
- package/dist/src/cli.js +14 -0
- package/dist/src/conformance.d.ts +0 -0
- package/dist/src/conformance.js +80 -7
- package/dist/src/errorRegistry.d.ts +0 -0
- package/dist/src/errorRegistry.js +0 -0
- package/dist/src/flagSemantics.d.ts +0 -0
- package/dist/src/flagSemantics.js +0 -0
- package/dist/src/index.d.ts +0 -0
- package/dist/src/index.js +0 -0
- package/dist/src/types.d.ts +42 -7
- package/dist/src/types.js +0 -0
- package/dist/src/validateEnvelope.d.ts +0 -0
- package/dist/src/validateEnvelope.js +0 -0
- package/lafs.md +167 -23
- package/package.json +8 -1
- package/schemas/v1/context-ledger.schema.json +70 -0
- package/schemas/v1/envelope.schema.json +103 -14
- package/schemas/v1/error-registry.json +19 -1
package/LICENSE
CHANGED
|
File without changes
|
package/README.md
CHANGED
|
@@ -1,45 +1,151 @@
|
|
|
1
|
-
#
|
|
1
|
+
# LAFS Protocol
|
|
2
2
|
|
|
3
|
-
LLM-Agent-First Specification
|
|
3
|
+
**LLM-Agent-First Specification** — a response envelope contract for AI agent systems.
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
|
|
7
|
+
**Current version:** 0.5.0 | [Spec](lafs.md) | [Migration Guides](migrations/)
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
##
|
|
48
|
+
## CLI
|
|
22
49
|
|
|
23
50
|
```bash
|
|
24
|
-
|
|
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
|
-
|
|
56
|
+
|
|
57
|
+
# Type check
|
|
58
|
+
npm run typecheck
|
|
27
59
|
```
|
|
28
60
|
|
|
29
|
-
##
|
|
61
|
+
## Envelope structure
|
|
30
62
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
##
|
|
111
|
+
## Project layout
|
|
37
112
|
|
|
38
|
-
```
|
|
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
|
-
|
|
43
|
-
|
|
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": "
|
|
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": "
|
|
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
|
package/dist/src/conformance.js
CHANGED
|
@@ -12,17 +12,79 @@ export function runEnvelopeConformance(envelope) {
|
|
|
12
12
|
return { ok: false, checks };
|
|
13
13
|
}
|
|
14
14
|
const typed = envelope;
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
package/dist/src/index.d.ts
CHANGED
|
File without changes
|
package/dist/src/index.js
CHANGED
|
File without changes
|
package/dist/src/types.d.ts
CHANGED
|
@@ -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:
|
|
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
|
|
23
|
-
mode: "
|
|
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
|
|
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
|
|
36
|
-
page
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
##
|
|
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
|
-
##
|
|
50
|
+
## 5. Format Semantics
|
|
29
51
|
|
|
30
|
-
###
|
|
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
|
-
###
|
|
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
|
-
##
|
|
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
|
-
###
|
|
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
|
-
##
|
|
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
|
-
###
|
|
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
|
-
##
|
|
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
|
-
##
|
|
181
|
+
## 9. MVI and Progressive Disclosure
|
|
128
182
|
|
|
129
|
-
###
|
|
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
|
-
###
|
|
189
|
+
### 9.2 Field selection (`_fields`)
|
|
136
190
|
|
|
137
|
-
|
|
138
|
-
- Unknown expansion fields SHOULD fail validation.
|
|
191
|
+
Clients MAY request a subset of response fields via the `_fields` request parameter.
|
|
139
192
|
|
|
140
|
-
|
|
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
|
-
##
|
|
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
|
-
##
|
|
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
|
-
##
|
|
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.
|
|
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": "
|
|
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": "
|
|
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
|
}
|