@everfur/cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +114 -0
- package/contract/contract-public.json +1335 -0
- package/dist/chunk-67H43MI6.js +1822 -0
- package/dist/chunk-KQZ6ZXL7.js +3994 -0
- package/dist/everfur-mcp.d.ts +2 -0
- package/dist/everfur-mcp.js +369 -0
- package/dist/everfur.d.ts +2 -0
- package/dist/everfur.js +27 -0
- package/dist/index.d.ts +2511 -0
- package/dist/index.js +22 -0
- package/package.json +85 -0
- package/schema/contract-public.v1.schema.json +36 -0
- package/schema/diagnosis.v1.schema.json +166 -0
- package/schema/error-explanation.v1.schema.json +65 -0
- package/schema/plan.v1.schema.json +244 -0
- package/schema/sandbox-create.v1.schema.json +74 -0
- package/schema/verification.v1.schema.json +54 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
export { COMMANDS, ExitCode, REASON_CODES, buildDiagnosisDocument, buildExplanationDocument, buildSandboxDocument, buildVerificationDocument, editDistance, failureOutcome, formatOutcome, isKnownReasonCode, matchCommand, nextActionFor2 as nextActionForErrorCode, nextActionFor as nextActionForReason, nextActionsFor, parseCapabilities, parseDiagnostics, parseIdentityMode, parseSessionMintLanguage, parseSuppliedFacts, projectSandboxCreate, renderDiagnosis, renderExplanation, renderPlan, renderSandboxCreate, renderVerification, runCli, suggestCodes } from './chunk-67H43MI6.js';
|
|
3
|
+
export { ADMIN_TOKEN, API_PREFIX, BASE_URL_ENV_VAR, CLI_VERSION, CREDENTIAL_SPECS, DEFAULT_TIMEOUT_MS, EMPTY_REGISTRY, ERRORS_DOCUMENT_KEY, IDEMPOTENT_METHODS, MISSING_FACT_NAMES, PARTNER_KEY, PARTNER_SECRET_KEY, PLAN_DOCUMENT_KIND, PLAN_SCHEMA_VERSION, REDACTED, SCHEMAS, SESSION_TOKEN, TransportError, apiUrl, buildDecisionSpace, clampTimeout, createApiClient, createRecordingClock, describeFailure, detectProject, digestOfProjection, digestsEqual, entryFromRow, ephemeralCredential, errorCodeOf, failureDetails, fetchTransport, isCorrelatableRequestId, isIdempotent, loadDecisionSpace, loadErrorRegistry, missingFactsFor, nodeFileSystem, planIntegration, resolveContractPath, resolveCredential, resolveEndpoint, runVerification, systemClock, validateContractFile, validateContractPublicEnvelope, validateDiagnosisDocument, validateErrorExplanationDocument, validatePlanDocument, validateSandboxCreateDocument, validateVerificationDocument, verifyDigest, walkSingle, walkTree, withPrefix } from './chunk-KQZ6ZXL7.js';
|
|
4
|
+
|
|
5
|
+
// src/fs/memory-fs.ts
|
|
6
|
+
function createMemoryFileSystem(files) {
|
|
7
|
+
const table = new Map(Object.entries(files));
|
|
8
|
+
const directories = /* @__PURE__ */ new Set();
|
|
9
|
+
for (const path of table.keys()) {
|
|
10
|
+
const segments = path.split("/");
|
|
11
|
+
for (let index = 1; index < segments.length; index += 1) {
|
|
12
|
+
directories.add(segments.slice(0, index).join("/"));
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return Object.freeze({
|
|
16
|
+
readFile: (path) => table.get(path) ?? null,
|
|
17
|
+
exists: (path) => table.has(path) || directories.has(path),
|
|
18
|
+
isDirectory: (path) => directories.has(path) && !table.has(path)
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export { createMemoryFileSystem };
|
package/package.json
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@everfur/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Everfur CLI: plan, verify, and diagnose an Everfur pet-health integration from the terminal.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"bin": {
|
|
9
|
+
"everfur": "./dist/everfur.js",
|
|
10
|
+
"everfur-mcp": "./dist/everfur-mcp.js"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"!dist/**/*.map",
|
|
15
|
+
"schema",
|
|
16
|
+
"contract"
|
|
17
|
+
],
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"import": {
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"default": "./dist/index.js"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"./schema/plan.v1.schema.json": "./schema/plan.v1.schema.json",
|
|
29
|
+
"./schema/contract-public.v1.schema.json": "./schema/contract-public.v1.schema.json",
|
|
30
|
+
"./schema/diagnosis.v1.schema.json": "./schema/diagnosis.v1.schema.json",
|
|
31
|
+
"./schema/sandbox-create.v1.schema.json": "./schema/sandbox-create.v1.schema.json",
|
|
32
|
+
"./schema/verification.v1.schema.json": "./schema/verification.v1.schema.json",
|
|
33
|
+
"./schema/error-explanation.v1.schema.json": "./schema/error-explanation.v1.schema.json"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "tsup && node scripts/strip-sourcemap-pragma.mjs",
|
|
37
|
+
"clean": "rm -rf dist coverage",
|
|
38
|
+
"typecheck": "tsc --noEmit",
|
|
39
|
+
"lint": "eslint .",
|
|
40
|
+
"pretest": "npm run build",
|
|
41
|
+
"test": "vitest run",
|
|
42
|
+
"pretest:coverage": "npm run build",
|
|
43
|
+
"test:coverage": "vitest run --coverage",
|
|
44
|
+
"prepare": "npm run build",
|
|
45
|
+
"notices": "node scripts/build-third-party-notices.mjs",
|
|
46
|
+
"notices:check": "node scripts/build-third-party-notices.mjs --check"
|
|
47
|
+
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
50
|
+
"ajv": "8.20.0",
|
|
51
|
+
"ajv-formats": "3.0.1",
|
|
52
|
+
"yaml": "2.9.0",
|
|
53
|
+
"zod": "^3.25.76"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@eslint/js": "^9.9.0",
|
|
57
|
+
"@types/node": "^20.14.0",
|
|
58
|
+
"@vitest/coverage-v8": "^4.0.0",
|
|
59
|
+
"eslint": "^9.9.0",
|
|
60
|
+
"tsup": "^8.2.0",
|
|
61
|
+
"typescript": "^5.5.0",
|
|
62
|
+
"typescript-eslint": "^8.0.0",
|
|
63
|
+
"vitest": "^4.0.0"
|
|
64
|
+
},
|
|
65
|
+
"engines": {
|
|
66
|
+
"node": ">=18"
|
|
67
|
+
},
|
|
68
|
+
"repository": {
|
|
69
|
+
"type": "git",
|
|
70
|
+
"url": "git+https://github.com/Everfur-Health/everfur-cli.git"
|
|
71
|
+
},
|
|
72
|
+
"homepage": "https://github.com/Everfur-Health/everfur-cli#readme",
|
|
73
|
+
"bugs": {
|
|
74
|
+
"url": "https://github.com/Everfur-Health/everfur-cli/issues"
|
|
75
|
+
},
|
|
76
|
+
"keywords": [
|
|
77
|
+
"everfur",
|
|
78
|
+
"pet-health",
|
|
79
|
+
"cli",
|
|
80
|
+
"mcp",
|
|
81
|
+
"sdk",
|
|
82
|
+
"veterinary"
|
|
83
|
+
],
|
|
84
|
+
"author": "Everfur Health, Inc."
|
|
85
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://everfur.com/contracts/contract-public/v1",
|
|
4
|
+
"title": "Everfur contract-public projection envelope",
|
|
5
|
+
"description": "The ENVELOPE around a contract-public projection. This CLI owns the envelope and the digest rule; it deliberately does NOT own the shape of `projection`, which is produced by the contract lane. Validating the payload here would fork the definition and guarantee the two drift. `projection` is therefore required to be an object and is otherwise opaque, and the digest is what proves it arrived intact. When the contract lane publishes its own payload schema, add it as a $ref under projection and bump to v2 only if that tightening rejects a document v1 accepted.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["schemaVersion", "kind", "generatedAt", "digest", "projection"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"schemaVersion": { "const": 1 },
|
|
10
|
+
"kind": { "const": "everfur.contract-public" },
|
|
11
|
+
"generatedAt": {
|
|
12
|
+
"type": "string",
|
|
13
|
+
"format": "date-time",
|
|
14
|
+
"description": "RFC 3339 timestamp. Outside the digest input, so re-stamping a projection does not invalidate it."
|
|
15
|
+
},
|
|
16
|
+
"digest": {
|
|
17
|
+
"type": "object",
|
|
18
|
+
"required": ["algorithm", "value"],
|
|
19
|
+
"properties": {
|
|
20
|
+
"algorithm": {
|
|
21
|
+
"const": "sha256",
|
|
22
|
+
"description": "Only sha256 is defined for v1. A new algorithm is an added enum member, which v1 permits."
|
|
23
|
+
},
|
|
24
|
+
"value": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"pattern": "^[0-9a-f]{64}$",
|
|
27
|
+
"description": "Lowercase hex sha256 over the RFC 8785 style canonical JSON of `projection` alone."
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"projection": {
|
|
32
|
+
"type": "object",
|
|
33
|
+
"description": "Opaque to this CLI. Owned by the contract lane."
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://everfur.com/contracts/cli-diagnosis/v1",
|
|
4
|
+
"title": "Everfur CLI diagnosis document",
|
|
5
|
+
"description": "The committed output contract of `everfur diagnose --json`. Mirrors src/commands/diagnose/document.ts; tests/contract validates real emitted output against this file on every run so the two cannot drift. Compatibility rule for schemaVersion 1: fields and enum members may be ADDED; nothing is removed, renamed, retyped, or given a new meaning. reasonCode is deliberately NOT an enum: the platform's DiagnosticsReasonCode grows when a new decision point is added, and a CLI that rejected an unrecognised cause would fail exactly when something new broke. additionalProperties is left open for the same reason.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": [
|
|
8
|
+
"schemaVersion",
|
|
9
|
+
"kind",
|
|
10
|
+
"generatedBy",
|
|
11
|
+
"endpoint",
|
|
12
|
+
"requestId",
|
|
13
|
+
"evaluatedAt",
|
|
14
|
+
"client",
|
|
15
|
+
"auth",
|
|
16
|
+
"scope",
|
|
17
|
+
"summary",
|
|
18
|
+
"capabilities"
|
|
19
|
+
],
|
|
20
|
+
"properties": {
|
|
21
|
+
"schemaVersion": { "const": 1 },
|
|
22
|
+
"kind": { "const": "everfur.diagnosis" },
|
|
23
|
+
"generatedBy": { "$ref": "#/$defs/GeneratedBy" },
|
|
24
|
+
"endpoint": {
|
|
25
|
+
"type": "object",
|
|
26
|
+
"required": ["baseUrl", "path"],
|
|
27
|
+
"properties": {
|
|
28
|
+
"baseUrl": { "type": "string", "minLength": 1 },
|
|
29
|
+
"path": { "type": "string", "minLength": 1 }
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"requestId": {
|
|
33
|
+
"description": "The X-Request-ID of the diagnosing call. Null only when the response carried none.",
|
|
34
|
+
"oneOf": [{ "type": "null" }, { "type": "string", "minLength": 1 }]
|
|
35
|
+
},
|
|
36
|
+
"evaluatedAt": { "type": "string" },
|
|
37
|
+
"client": {
|
|
38
|
+
"type": "object",
|
|
39
|
+
"required": ["id", "slug"],
|
|
40
|
+
"properties": {
|
|
41
|
+
"id": { "type": "string" },
|
|
42
|
+
"slug": { "type": "string" }
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"auth": {
|
|
46
|
+
"type": "object",
|
|
47
|
+
"required": [
|
|
48
|
+
"mode",
|
|
49
|
+
"anonymous",
|
|
50
|
+
"userRefPrefix",
|
|
51
|
+
"userRole",
|
|
52
|
+
"roleSource",
|
|
53
|
+
"sessionId",
|
|
54
|
+
"partnerKeyScopeResolved"
|
|
55
|
+
],
|
|
56
|
+
"properties": {
|
|
57
|
+
"mode": { "type": "string" },
|
|
58
|
+
"anonymous": { "type": "boolean" },
|
|
59
|
+
"userRefPrefix": {
|
|
60
|
+
"description": "At most the first 8 characters of the resolved user ref. Never a credential.",
|
|
61
|
+
"type": "string",
|
|
62
|
+
"maxLength": 8
|
|
63
|
+
},
|
|
64
|
+
"userRole": { "type": "string" },
|
|
65
|
+
"roleSource": { "type": "string" },
|
|
66
|
+
"sessionId": { "type": "string" },
|
|
67
|
+
"partnerKeyScopeResolved": { "type": "boolean" }
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
"scope": {
|
|
71
|
+
"type": "object",
|
|
72
|
+
"required": ["provisioned", "resolverStatus", "revision", "region", "levels"],
|
|
73
|
+
"properties": {
|
|
74
|
+
"provisioned": { "type": "boolean" },
|
|
75
|
+
"resolverStatus": { "type": "string" },
|
|
76
|
+
"revision": { "type": "integer", "minimum": 0 },
|
|
77
|
+
"region": { "type": "string" },
|
|
78
|
+
"levels": {
|
|
79
|
+
"type": "array",
|
|
80
|
+
"items": {
|
|
81
|
+
"type": "object",
|
|
82
|
+
"required": ["level", "kind", "present"],
|
|
83
|
+
"properties": {
|
|
84
|
+
"level": { "type": "integer", "minimum": 1, "maximum": 5 },
|
|
85
|
+
"kind": { "type": "string" },
|
|
86
|
+
"present": { "type": "boolean" }
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
"summary": {
|
|
93
|
+
"type": "object",
|
|
94
|
+
"required": ["total", "available", "unavailable", "byReasonCode"],
|
|
95
|
+
"properties": {
|
|
96
|
+
"total": { "type": "integer", "minimum": 0 },
|
|
97
|
+
"available": { "type": "integer", "minimum": 0 },
|
|
98
|
+
"unavailable": { "type": "integer", "minimum": 0 },
|
|
99
|
+
"byReasonCode": {
|
|
100
|
+
"description": "Cause code to how many capabilities carry it. The field that turns eight symptoms into one ticket.",
|
|
101
|
+
"type": "object",
|
|
102
|
+
"additionalProperties": { "type": "integer", "minimum": 1 }
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
"capabilities": {
|
|
107
|
+
"type": "array",
|
|
108
|
+
"items": { "$ref": "#/$defs/CapabilityDiagnosis" }
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
"$defs": {
|
|
112
|
+
"GeneratedBy": {
|
|
113
|
+
"type": "object",
|
|
114
|
+
"required": ["name", "version"],
|
|
115
|
+
"properties": {
|
|
116
|
+
"name": { "type": "string", "minLength": 1 },
|
|
117
|
+
"version": { "type": "string", "minLength": 1 }
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
"CapabilityDiagnosis": {
|
|
121
|
+
"type": "object",
|
|
122
|
+
"required": [
|
|
123
|
+
"key",
|
|
124
|
+
"available",
|
|
125
|
+
"reasonCode",
|
|
126
|
+
"reason",
|
|
127
|
+
"entitlementReasonCode",
|
|
128
|
+
"decidedAtLevel",
|
|
129
|
+
"decidedAtKind",
|
|
130
|
+
"surface",
|
|
131
|
+
"nextAction"
|
|
132
|
+
],
|
|
133
|
+
"properties": {
|
|
134
|
+
"key": { "type": "string", "minLength": 1 },
|
|
135
|
+
"available": { "type": "boolean" },
|
|
136
|
+
"reasonCode": {
|
|
137
|
+
"description": "The EFFECTIVE cause: the fence that blocks this key, so it names what has to be fixed first.",
|
|
138
|
+
"type": "string",
|
|
139
|
+
"minLength": 1
|
|
140
|
+
},
|
|
141
|
+
"reason": {
|
|
142
|
+
"description": "The server's own static copy for the code. Never operator or upstream text.",
|
|
143
|
+
"type": "string"
|
|
144
|
+
},
|
|
145
|
+
"entitlementReasonCode": {
|
|
146
|
+
"description": "What the entitlement lattice alone decided, ignoring runtime gates. Differs from reasonCode only when a gate masks a lattice outcome.",
|
|
147
|
+
"type": "string",
|
|
148
|
+
"minLength": 1
|
|
149
|
+
},
|
|
150
|
+
"decidedAtLevel": {
|
|
151
|
+
"oneOf": [{ "type": "null" }, { "type": "integer", "minimum": 1, "maximum": 5 }]
|
|
152
|
+
},
|
|
153
|
+
"decidedAtKind": { "oneOf": [{ "type": "null" }, { "type": "string" }] },
|
|
154
|
+
"surface": {
|
|
155
|
+
"description": "The runtime-gated surface name, disclosed only to a caller the lattice grants. Null otherwise.",
|
|
156
|
+
"oneOf": [{ "type": "null" }, { "type": "string" }]
|
|
157
|
+
},
|
|
158
|
+
"nextAction": {
|
|
159
|
+
"description": "CLI-authored. What the partner should do about this cause, which the server cannot know.",
|
|
160
|
+
"type": "string",
|
|
161
|
+
"minLength": 1
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://everfur.com/contracts/cli-error-explanation/v1",
|
|
4
|
+
"title": "Everfur CLI error explanation document",
|
|
5
|
+
"description": "The committed output contract of `everfur explain-error <code> --json`. Every field except `nextAction` is read from the contract-public projection named in `source`, and `source.digest` is the sha256 that projection was verified against, so a reader can prove which registry an explanation came from. `nextAction` is CLI-authored and derived from retryable plus httpStatus, which is why a code added to the registry tomorrow already gets a correct action today.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": [
|
|
8
|
+
"schemaVersion",
|
|
9
|
+
"kind",
|
|
10
|
+
"generatedBy",
|
|
11
|
+
"code",
|
|
12
|
+
"source",
|
|
13
|
+
"envelope",
|
|
14
|
+
"httpStatus",
|
|
15
|
+
"namingFamily",
|
|
16
|
+
"retryable",
|
|
17
|
+
"confirmedOnWire",
|
|
18
|
+
"surfaces",
|
|
19
|
+
"cause",
|
|
20
|
+
"display",
|
|
21
|
+
"docUrl",
|
|
22
|
+
"nextAction"
|
|
23
|
+
],
|
|
24
|
+
"properties": {
|
|
25
|
+
"schemaVersion": { "const": 1 },
|
|
26
|
+
"kind": { "const": "everfur.error-explanation" },
|
|
27
|
+
"generatedBy": {
|
|
28
|
+
"type": "object",
|
|
29
|
+
"required": ["name", "version"],
|
|
30
|
+
"properties": {
|
|
31
|
+
"name": { "type": "string", "minLength": 1 },
|
|
32
|
+
"version": { "type": "string", "minLength": 1 }
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"code": {
|
|
36
|
+
"description": "Exactly the string the wire carries. Not normalised: three naming families are live at once and a client branches on the literal.",
|
|
37
|
+
"type": "string",
|
|
38
|
+
"minLength": 1
|
|
39
|
+
},
|
|
40
|
+
"source": {
|
|
41
|
+
"type": "object",
|
|
42
|
+
"required": ["path", "digest"],
|
|
43
|
+
"properties": {
|
|
44
|
+
"path": { "type": "string", "minLength": 1 },
|
|
45
|
+
"digest": { "type": "string", "pattern": "^[0-9a-f]{64}$" }
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"envelope": { "type": "string", "minLength": 1 },
|
|
49
|
+
"httpStatus": { "type": "integer", "minimum": 0 },
|
|
50
|
+
"namingFamily": { "type": "string", "minLength": 1 },
|
|
51
|
+
"retryable": { "type": "boolean" },
|
|
52
|
+
"confirmedOnWire": {
|
|
53
|
+
"description": "False means the code exists in source but no live route was traced emitting it. Documentation, not a contract to branch on.",
|
|
54
|
+
"type": "boolean"
|
|
55
|
+
},
|
|
56
|
+
"surfaces": { "type": "array", "items": { "type": "string" } },
|
|
57
|
+
"cause": { "type": "string" },
|
|
58
|
+
"display": {
|
|
59
|
+
"description": "Safe copy a client may show a person as-is, authored by the contract lane.",
|
|
60
|
+
"type": "string"
|
|
61
|
+
},
|
|
62
|
+
"docUrl": { "type": "string" },
|
|
63
|
+
"nextAction": { "type": "string", "minLength": 1 }
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://everfur.com/contracts/cli-plan/v1",
|
|
4
|
+
"title": "Everfur CLI plan document",
|
|
5
|
+
"description": "The committed output contract of `everfur plan --json`. Mirrors the TypeScript type in src/commands/plan/types.ts; tests/contract validates real emitted output against this file on every run so the two cannot drift. Compatibility rule for schemaVersion 1: fields and enum members may be ADDED; nothing is removed, renamed, retyped, or given a new meaning. Any of those is schemaVersion 2 and a new schema file. `additionalProperties` is left open on objects for exactly that reason: a consumer pinned to v1 must not break when v1.x gains a field. When the planner decision trees landed, this file gained the `blocked`, `principles` and `decisionSpace` fields, two `missingFacts[].fact` members and one `status` member, all additive; and `Capability` and `IdentityMode` stopped being enums, because the set of capabilities is published by the platform contract rather than by this CLI, and an enum here would reject a capability the platform had already shipped. That relaxation accepts every document v1 accepted and rejects nothing it used to, which is the test that keeps it inside v1.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": [
|
|
8
|
+
"schemaVersion",
|
|
9
|
+
"kind",
|
|
10
|
+
"generatedBy",
|
|
11
|
+
"status",
|
|
12
|
+
"detected",
|
|
13
|
+
"supplied",
|
|
14
|
+
"missingFacts",
|
|
15
|
+
"steps",
|
|
16
|
+
"blocked",
|
|
17
|
+
"principles",
|
|
18
|
+
"warnings",
|
|
19
|
+
"decisionSpace"
|
|
20
|
+
],
|
|
21
|
+
"properties": {
|
|
22
|
+
"schemaVersion": { "const": 1 },
|
|
23
|
+
"kind": { "const": "everfur.plan" },
|
|
24
|
+
"generatedBy": {
|
|
25
|
+
"type": "object",
|
|
26
|
+
"required": ["name", "version"],
|
|
27
|
+
"properties": {
|
|
28
|
+
"name": { "type": "string", "minLength": 1 },
|
|
29
|
+
"version": { "type": "string", "minLength": 1 }
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"status": { "enum": ["ready", "underspecified", "blocked"] },
|
|
33
|
+
"detected": { "$ref": "#/$defs/Detection" },
|
|
34
|
+
"supplied": {
|
|
35
|
+
"type": "object",
|
|
36
|
+
"required": ["capabilities", "identityMode"],
|
|
37
|
+
"properties": {
|
|
38
|
+
"capabilities": {
|
|
39
|
+
"description": "null means the flag was absent. An empty array is never emitted.",
|
|
40
|
+
"oneOf": [
|
|
41
|
+
{ "type": "null" },
|
|
42
|
+
{ "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/Capability" }, "uniqueItems": true }
|
|
43
|
+
]
|
|
44
|
+
},
|
|
45
|
+
"identityMode": {
|
|
46
|
+
"oneOf": [{ "type": "null" }, { "$ref": "#/$defs/IdentityMode" }]
|
|
47
|
+
},
|
|
48
|
+
"sessionMintLanguage": {
|
|
49
|
+
"description": "The backend language that mints sessions. Non-null only when the identity mode requires a server-side mint.",
|
|
50
|
+
"oneOf": [{ "type": "null" }, { "$ref": "#/$defs/OptionId" }]
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"missingFacts": {
|
|
55
|
+
"type": "array",
|
|
56
|
+
"items": { "$ref": "#/$defs/MissingFact" }
|
|
57
|
+
},
|
|
58
|
+
"steps": {
|
|
59
|
+
"type": "array",
|
|
60
|
+
"items": { "$ref": "#/$defs/PlanStep" }
|
|
61
|
+
},
|
|
62
|
+
"blocked": {
|
|
63
|
+
"description": "Capabilities the caller asked for that the contract refuses, with the reason and where to ask. Present in every status; non-empty means no step below covers those capabilities.",
|
|
64
|
+
"type": "array",
|
|
65
|
+
"items": { "$ref": "#/$defs/BlockedCapability" }
|
|
66
|
+
},
|
|
67
|
+
"principles": {
|
|
68
|
+
"description": "Guidance that holds regardless of what was answered, carried in the document so it survives the loss of the context that produced the plan.",
|
|
69
|
+
"type": "array",
|
|
70
|
+
"items": { "$ref": "#/$defs/PlanPrinciple" }
|
|
71
|
+
},
|
|
72
|
+
"warnings": {
|
|
73
|
+
"type": "array",
|
|
74
|
+
"items": { "$ref": "#/$defs/PlanWarning" }
|
|
75
|
+
},
|
|
76
|
+
"decisionSpace": {
|
|
77
|
+
"description": "Which contract projection produced this plan. Null only on the refusal that says none was found.",
|
|
78
|
+
"oneOf": [{ "type": "null" }, { "$ref": "#/$defs/DecisionSpaceProvenance" }]
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
"allOf": [
|
|
82
|
+
{
|
|
83
|
+
"description": "status and missingFacts are two views of one fact and can never disagree.",
|
|
84
|
+
"if": { "properties": { "status": { "const": "underspecified" } }, "required": ["status"] },
|
|
85
|
+
"then": {
|
|
86
|
+
"properties": {
|
|
87
|
+
"missingFacts": { "minItems": 1 },
|
|
88
|
+
"steps": { "maxItems": 0 }
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"description": "A ready plan has no missing facts and at least one step.",
|
|
94
|
+
"if": { "properties": { "status": { "const": "ready" } }, "required": ["status"] },
|
|
95
|
+
"then": {
|
|
96
|
+
"properties": {
|
|
97
|
+
"missingFacts": { "maxItems": 0 },
|
|
98
|
+
"steps": { "minItems": 1 }
|
|
99
|
+
},
|
|
100
|
+
"required": ["supplied", "decisionSpace"],
|
|
101
|
+
"$comment": "A ready plan always carries the supplied facts and the decision space they were judged against."
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"description": "A blocked plan produced no steps and says which capabilities are blocked. An install plan for a surface that answers 404 is not a smaller plan, it is a wrong one.",
|
|
106
|
+
"if": { "properties": { "status": { "const": "blocked" } }, "required": ["status"] },
|
|
107
|
+
"then": {
|
|
108
|
+
"properties": {
|
|
109
|
+
"missingFacts": { "maxItems": 0 },
|
|
110
|
+
"steps": { "maxItems": 0 },
|
|
111
|
+
"blocked": { "minItems": 1 }
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
],
|
|
116
|
+
"$defs": {
|
|
117
|
+
"OptionId": {
|
|
118
|
+
"description": "One option id of one decision-tree question. Kebab-case, published by the contract.",
|
|
119
|
+
"type": "string",
|
|
120
|
+
"pattern": "^[a-z0-9]+(-[a-z0-9]+)*$"
|
|
121
|
+
},
|
|
122
|
+
"Capability": {
|
|
123
|
+
"description": "A capability id: one option of the capabilities tree. NOT enumerated here; the set is the platform contract's, and this CLI reads it from the projection.",
|
|
124
|
+
"$ref": "#/$defs/OptionId"
|
|
125
|
+
},
|
|
126
|
+
"IdentityMode": {
|
|
127
|
+
"description": "An identity mode id: one option of the identity-mode tree.",
|
|
128
|
+
"$ref": "#/$defs/OptionId"
|
|
129
|
+
},
|
|
130
|
+
"ProjectType": {
|
|
131
|
+
"enum": ["expo-managed", "bare-react-native", "flutter", "web", "unknown"]
|
|
132
|
+
},
|
|
133
|
+
"PackageManager": {
|
|
134
|
+
"enum": ["npm", "pnpm", "yarn", "bun", "not-applicable", "undetermined"]
|
|
135
|
+
},
|
|
136
|
+
"Detection": {
|
|
137
|
+
"type": "object",
|
|
138
|
+
"required": [
|
|
139
|
+
"root",
|
|
140
|
+
"projectType",
|
|
141
|
+
"projectTypeEvidence",
|
|
142
|
+
"packageManager",
|
|
143
|
+
"packageManagerEvidence",
|
|
144
|
+
"dependencies"
|
|
145
|
+
],
|
|
146
|
+
"properties": {
|
|
147
|
+
"root": { "type": "string", "minLength": 1 },
|
|
148
|
+
"projectType": { "$ref": "#/$defs/ProjectType" },
|
|
149
|
+
"projectTypeEvidence": { "type": "array", "items": { "type": "string" } },
|
|
150
|
+
"packageManager": { "$ref": "#/$defs/PackageManager" },
|
|
151
|
+
"packageManagerEvidence": { "type": "array", "items": { "type": "string" } },
|
|
152
|
+
"dependencies": { "type": "array", "items": { "$ref": "#/$defs/Dependency" } }
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
"Dependency": {
|
|
156
|
+
"type": "object",
|
|
157
|
+
"required": ["name", "declared", "installed", "source", "scope"],
|
|
158
|
+
"properties": {
|
|
159
|
+
"name": { "type": "string", "minLength": 1 },
|
|
160
|
+
"declared": { "oneOf": [{ "type": "null" }, { "type": "string" }] },
|
|
161
|
+
"installed": { "oneOf": [{ "type": "null" }, { "type": "string" }] },
|
|
162
|
+
"source": { "enum": ["package.json", "pubspec.yaml"] },
|
|
163
|
+
"scope": { "enum": ["dependencies", "devDependencies", "peerDependencies", "sdk"] }
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
"BlockedCapability": {
|
|
167
|
+
"type": "object",
|
|
168
|
+
"required": ["capability", "reason", "unblockOwner", "requestPath", "capabilityKeys"],
|
|
169
|
+
"properties": {
|
|
170
|
+
"capability": { "$ref": "#/$defs/Capability" },
|
|
171
|
+
"reason": { "type": "string", "minLength": 1 },
|
|
172
|
+
"unblockOwner": { "type": "string", "minLength": 1 },
|
|
173
|
+
"requestPath": {
|
|
174
|
+
"description": "Site-relative path on the partner documentation site. Resolved against the generated site when the contract is compiled, so it cannot point at a page that does not exist.",
|
|
175
|
+
"type": "string",
|
|
176
|
+
"pattern": "^/"
|
|
177
|
+
},
|
|
178
|
+
"capabilityKeys": {
|
|
179
|
+
"description": "The capability pack keys this block derives from.",
|
|
180
|
+
"type": "array",
|
|
181
|
+
"items": { "type": "string", "minLength": 1 }
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
"PlanPrinciple": {
|
|
186
|
+
"type": "object",
|
|
187
|
+
"required": ["treeId", "id", "title", "statement", "docPath"],
|
|
188
|
+
"properties": {
|
|
189
|
+
"treeId": { "$ref": "#/$defs/OptionId" },
|
|
190
|
+
"id": { "$ref": "#/$defs/OptionId" },
|
|
191
|
+
"title": { "type": "string", "minLength": 1 },
|
|
192
|
+
"statement": { "type": "string", "minLength": 1 },
|
|
193
|
+
"docPath": { "oneOf": [{ "type": "null" }, { "type": "string", "pattern": "^/" }] }
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
"DecisionSpaceProvenance": {
|
|
197
|
+
"type": "object",
|
|
198
|
+
"required": ["source", "digest", "trees"],
|
|
199
|
+
"properties": {
|
|
200
|
+
"source": { "type": "string", "minLength": 1 },
|
|
201
|
+
"digest": { "type": "string", "pattern": "^[0-9a-f]{64}$" },
|
|
202
|
+
"trees": { "type": "array", "items": { "$ref": "#/$defs/OptionId" } }
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
"MissingFact": {
|
|
206
|
+
"type": "object",
|
|
207
|
+
"required": ["fact", "reason", "supplyWith"],
|
|
208
|
+
"properties": {
|
|
209
|
+
"fact": {
|
|
210
|
+
"enum": [
|
|
211
|
+
"decisionTrees",
|
|
212
|
+
"projectType",
|
|
213
|
+
"packageManager",
|
|
214
|
+
"capabilities",
|
|
215
|
+
"identityMode",
|
|
216
|
+
"sessionMintLanguage"
|
|
217
|
+
]
|
|
218
|
+
},
|
|
219
|
+
"reason": { "type": "string", "minLength": 1 },
|
|
220
|
+
"supplyWith": { "type": "string", "minLength": 1 }
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
"PlanStep": {
|
|
224
|
+
"type": "object",
|
|
225
|
+
"required": ["id", "phase", "title", "detail", "commands", "files"],
|
|
226
|
+
"properties": {
|
|
227
|
+
"id": { "type": "string", "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$" },
|
|
228
|
+
"phase": { "enum": ["install", "native", "configure", "code", "verify"] },
|
|
229
|
+
"title": { "type": "string", "minLength": 1 },
|
|
230
|
+
"detail": { "type": "string", "minLength": 1 },
|
|
231
|
+
"commands": { "type": "array", "items": { "type": "string", "minLength": 1 } },
|
|
232
|
+
"files": { "type": "array", "items": { "type": "string", "minLength": 1 } }
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
"PlanWarning": {
|
|
236
|
+
"type": "object",
|
|
237
|
+
"required": ["code", "message"],
|
|
238
|
+
"properties": {
|
|
239
|
+
"code": { "type": "string", "pattern": "^[a-z0-9]+(_[a-z0-9]+)*$" },
|
|
240
|
+
"message": { "type": "string", "minLength": 1 }
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|