@fourier-labs/harbour-contracts 0.33.0 → 0.41.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/contract-release.json +23 -23
- package/dist/gen-ts/artifact-contract.d.ts +12 -0
- package/dist/gen-ts/contract-release.d.ts +6 -6
- package/dist/gen-ts/contract-release.js +6 -6
- package/dist/gen-ts/data-source.d.ts +5 -1
- package/dist/gen-ts/deploy-request.d.ts +21 -0
- package/dist/gen-ts/deployment-status-failure.d.ts +6 -1
- package/dist/gen-ts/deployment-status-response.d.ts +5 -0
- package/dist/gen-ts/error-category-registry.d.ts +14 -0
- package/dist/gen-ts/error-category-registry.js +30 -1
- package/dist/gen-ts/transform-report.d.ts +226 -13
- package/dist/gen-ts/transform-report.js +11 -1
- package/gen-ts/artifact-contract.ts +12 -0
- package/gen-ts/contract-release.ts +6 -6
- package/gen-ts/data-source.ts +5 -1
- package/gen-ts/deploy-request.ts +21 -0
- package/gen-ts/deployment-status-failure.ts +6 -1
- package/gen-ts/deployment-status-response.ts +5 -0
- package/gen-ts/error-category-registry.ts +30 -1
- package/gen-ts/transform-report.ts +253 -13
- package/package.json +1 -1
- package/schema/artifact-contract.schema.json +16 -0
- package/schema/data-source.schema.json +7 -1
- package/schema/deploy-request.schema.json +39 -0
- package/schema/deployment-status-failure.schema.json +15 -1
- package/schema/deployment-status-response.schema.json +12 -0
- package/schema/error-category.enum.json +13 -2
- package/schema/transform-report.schema.json +439 -18
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
3
|
"$id": "https://schemas.harbour.dev/deployment-status-failure.schema.json",
|
|
4
4
|
"title": "DeploymentStatusFailure",
|
|
5
|
-
"description": "Frontend-safe failure projection nested in a failed DeploymentStatusResponse.
|
|
5
|
+
"description": "Frontend-safe failure projection nested in a failed DeploymentStatusResponse. Raw provider errors are excluded; a digest-bound diagnosticRef locates the complete redacted terminal diagnostic.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"additionalProperties": false,
|
|
8
8
|
"required": ["category", "retryable", "message", "remediationHint"],
|
|
@@ -23,6 +23,20 @@
|
|
|
23
23
|
"type": "string",
|
|
24
24
|
"minLength": 1,
|
|
25
25
|
"maxLength": 1000
|
|
26
|
+
},
|
|
27
|
+
"diagnosticRef": {
|
|
28
|
+
"$ref": "#/definitions/FailureDiagnosticRef"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"definitions": {
|
|
32
|
+
"FailureDiagnosticRef": {
|
|
33
|
+
"type": "object",
|
|
34
|
+
"additionalProperties": false,
|
|
35
|
+
"required": ["key", "sha256"],
|
|
36
|
+
"properties": {
|
|
37
|
+
"key": { "type": "string", "minLength": 1, "maxLength": 1024, "pattern": "^runs/[A-Za-z0-9._/-]+\\.json$" },
|
|
38
|
+
"sha256": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" }
|
|
39
|
+
}
|
|
26
40
|
}
|
|
27
41
|
}
|
|
28
42
|
}
|
|
@@ -105,9 +105,21 @@
|
|
|
105
105
|
},
|
|
106
106
|
"remediationHint": {
|
|
107
107
|
"type": "string"
|
|
108
|
+
},
|
|
109
|
+
"diagnosticRef": {
|
|
110
|
+
"$ref": "#/definitions/StatusDiagnosticRef"
|
|
108
111
|
}
|
|
109
112
|
}
|
|
110
113
|
},
|
|
114
|
+
"StatusDiagnosticRef": {
|
|
115
|
+
"type": "object",
|
|
116
|
+
"additionalProperties": false,
|
|
117
|
+
"required": ["key", "sha256"],
|
|
118
|
+
"properties": {
|
|
119
|
+
"key": { "type": "string", "minLength": 1, "maxLength": 1024, "pattern": "^runs/[A-Za-z0-9._/-]+\\.json$" },
|
|
120
|
+
"sha256": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" }
|
|
121
|
+
}
|
|
122
|
+
},
|
|
111
123
|
"StatusProgressEvent": {
|
|
112
124
|
"type": "object",
|
|
113
125
|
"additionalProperties": false,
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
{ "id": "intake.unsupported-runtime", "retryable": false, "description": "Detected runtime has no Harbour build recipe." },
|
|
17
17
|
{ "id": "intake.attestation-missing", "retryable": false, "description": "Required auth attestation absent." },
|
|
18
18
|
{ "id": "intake.multi-app-repo", "retryable": false, "description": "Repo contains more than one deployable app." },
|
|
19
|
+
{ "id": "intake.source-invalid", "retryable": false, "description": "The submitted source tree fails its own clean install, build, import, boot, schema-presence, or pristine-lineage checks before Harbour transformation begins." },
|
|
19
20
|
{ "id": "secrets.unresolvable", "retryable": false, "retired": true, "description": "RETIRED 2026-08-08 and no longer emittable: the secret-materializer stack, its reconciler and the secret-reference playbook were all removed, so nothing can reach this state. Kept rather than deleted because historical failure records still carry it and a dropped id would decode to nothing. Do not emit in new code." },
|
|
20
21
|
{ "id": "secrets.reference-unresolvable", "retryable": false, "description": "A governance secret reference supplied on the deploy request could not be resolved to a stored value — the secret does not exist, the read was denied, or the secretref:// URI is malformed. Not retryable: no amount of waiting produces the value, a human must set it (or fix the reference) before the deploy can proceed." },
|
|
21
22
|
{ "id": "secrets.placeholder-unresolved", "retryable": false, "description": "The built workspace still contains a ${HARBOUR_REDACTED_<KEY>} substitution placeholder after transformation, meaning the transform never converted it into a runtime secret lookup and the artifact would ship a literal placeholder where a secret belongs. Not retryable: the workspace is already built, so the same input yields the same placeholder until the transform is corrected." },
|
|
@@ -24,14 +25,24 @@
|
|
|
24
25
|
{ "id": "storage.unsupported-engine", "retryable": false, "family": true, "description": "Storage engine with no Harbour mapping; concrete token names the engine, e.g. storage.unsupported-engine.mongodb." },
|
|
25
26
|
{ "id": "storage.mapping-drift", "retryable": false, "description": "Declared storage binding diverged from detected usage." },
|
|
26
27
|
{ "id": "storage.unavailable", "retryable": true, "description": "The platform's shared application database could not be reached while preparing or provisioning the deploy — e.g. the Aurora cluster dial timing out during a scale-from-zero cold start. Says nothing about the app, so it IS retryable: the same deploy submitted again is expected to succeed once the database is awake. Previously surfaced as uncategorized.deploy (errorType wrapError)." },
|
|
28
|
+
{ "id": "ai.plan-blocked", "retryable": false, "description": "The governance control plane's AI PLAN verdict was BLOCKED: the app makes AI calls that cannot be governed as the tenant is configured today — no provider connected or enabled, no credential, no budget, an SDK or provider the gateway cannot intercept, or a scan that could not cover the whole app. The analyzer's maker message carries the plain-English reason and the governance reason codes. NOT retryable: the same deploy re-earns the same verdict until a person changes the tenant's AI configuration or the app. Previously surfaced as uncategorized.deploy (errorType wrapError)." },
|
|
27
29
|
{ "id": "transform.deadline-exceeded", "retryable": true, "description": "The transform wall deadline was reached before a passing candidate was found. A fresh deployment attempt is safe and may resume from the latest saved transformation checkpoint." },
|
|
28
|
-
{ "id": "transform.
|
|
30
|
+
{ "id": "transform.attempts-exhausted", "retryable": false, "description": "The bounded transformation attempt count was exhausted before a passing candidate was found." },
|
|
31
|
+
{ "id": "transform.non-retryable", "retryable": false, "retired": true, "description": "RETIRED 2026-09-06. Historical agent declines used a prose-derived suffix in this family. New reports must select one of the fixed structured categories below." },
|
|
29
32
|
{ "id": "transform.validation-failed", "retryable": true, "description": "Transform candidate failed validation with iterable feedback; re-enters TRANSFORMING while deadline remains." },
|
|
33
|
+
{ "id": "auth.validation-seed-unsupported", "retryable": false, "description": "The application requires authenticated validation setup that Harbour cannot express with its supported validation seed contract." },
|
|
34
|
+
{ "id": "auth.identity-entity-unresolvable", "retryable": false, "description": "The application identity model cannot deterministically resolve the trusted Harbour subject to one application entity." },
|
|
35
|
+
{ "id": "dependency.python-runtime-unsupported", "retryable": false, "description": "The application requires a Python minor version for which Harbour has no approved resolver and runtime image." },
|
|
36
|
+
{ "id": "dependency.resolver-unavailable", "retryable": true, "description": "The approved dependency resolver image or restricted registry lane was unavailable." },
|
|
37
|
+
{ "id": "dependency.resolution-failed", "retryable": false, "description": "The application dependency manifest could not be resolved by its approved package manager." },
|
|
38
|
+
{ "id": "dependency.lockfile-stale", "retryable": false, "description": "The final dependency manifest and lockfile disagree after dependency resolution." },
|
|
39
|
+
{ "id": "aurora.bundle-invalid", "retryable": false, "description": "The candidate's Harbour-managed Aurora migration bundle is missing, malformed, or inconsistent." },
|
|
40
|
+
{ "id": "capability.unsupported", "retryable": false, "description": "A required application capability has no supported equivalent under the Harbour platform contract." },
|
|
30
41
|
{ "id": "frontend.env-inlined-unfixable", "retryable": false, "description": "Client code inlined build-time env/self-URLs in a way transformation cannot rewrite." },
|
|
31
42
|
{ "id": "build.recipe-failed", "retryable": false, "description": "The Harbour build recipe failed for the detected runtime." },
|
|
32
43
|
{ "id": "build.image-too-large", "retryable": false, "description": "Built image exceeded the size limit." },
|
|
33
44
|
{ "id": "browser.probe-failed", "retryable": true, "description": "The app built and produced its declared outputs, but the browser probe found it does not serve them — typically assets emitted at the root instead of under the app base path. Retryable because it is fed back to the transform agent, which is the component that can fix it; the merged job retries in-loop and the state machine retries the job. Distinct from build.recipe-failed, which means the build itself did not succeed: here it did." },
|
|
34
|
-
{ "id": "browser.probe-unavailable", "retryable": false, "description": "The browser probe could not reach a verdict — Chromium crashed or disconnected, or it wrote no result document. Says nothing about the app, so it is
|
|
45
|
+
{ "id": "browser.probe-unavailable", "retryable": false, "description": "The browser probe could not reach a verdict — Chromium crashed or disconnected, or it wrote no result document. Says nothing about the app, so it is not fed back to the agent. Replaces the uncategorized.browser-probe token the merged job emitted while this entry did not exist." },
|
|
35
46
|
{ "id": "verify.smoke-failed", "retryable": false, "description": "Candidate smoke/health check failed (boot, routes, storage, secret refs, response shape)." },
|
|
36
47
|
{ "id": "verify.characterization-diverged", "retryable": false, "description": "Post-MVP: recorded req/res replay diverged beyond thresholds." },
|
|
37
48
|
{ "id": "verify.migration-from-zero-failed", "retryable": false, "description": "Post-MVP: from-zero migration chain failed on scratch DB." },
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
3
|
"$id": "https://schemas.harbour.dev/transform-report.schema.json",
|
|
4
4
|
"title": "TransformReport",
|
|
5
|
-
"description": "Structured output an agent must return from one Harbour transformation attempt. Handed to the agent as its output schema, then decoded and semantically re-validated by the control plane, which remains the authority. The enum on applied_playbooks.items is the one dynamic part: the adapter injects the names of the playbooks supplied for that invocation before writing the document, so the model is structurally prevented from naming a playbook it was not given. Application-secret handling was removed platform-wide, so this document carries no redactions and no secrets fields.",
|
|
5
|
+
"description": "Structured output an agent must return from one Harbour transformation attempt. Handed to the agent as its output schema, then decoded and semantically re-validated by the control plane, which remains the authority. The enum on applied_playbooks.items is the one dynamic part: the adapter injects the names of the playbooks supplied for that invocation before writing the document, so the model is structurally prevented from naming a playbook it was not given. Application-secret handling was removed platform-wide, so this document carries no redactions and no secrets fields. A deployable report must also carry preservation_plan: the agent's own grounded smoke tests (one to three happy_path user journeys plus a boundary_check per behaviour its playbooks or edits could regress) that the validation cell executes after transformation, and an uncovered list naming every boundary it could not test and why. It also carries validation_fixtures: null when no external fixture is needed, or a bounded generic HTTP/WebSocket fixture declaration for that one run. An unsupported report carries only application.unsupported and no plan or fixtures.",
|
|
6
6
|
"x-go-embed": true,
|
|
7
7
|
"type": "object",
|
|
8
8
|
"additionalProperties": false,
|
|
@@ -56,21 +56,19 @@
|
|
|
56
56
|
"changeType",
|
|
57
57
|
"requiresCodeChanges",
|
|
58
58
|
"requiresSchemaMigration",
|
|
59
|
-
"desiredSchemaPath",
|
|
60
59
|
"migrationPaths",
|
|
61
60
|
"changedFiles",
|
|
62
61
|
"evidence",
|
|
63
62
|
"unsupportedFeatures"
|
|
64
63
|
],
|
|
65
64
|
"properties": {
|
|
66
|
-
"mode": { "type": "string", "enum": ["none", "sql"
|
|
65
|
+
"mode": { "type": "string", "enum": ["none", "sql"] },
|
|
67
66
|
"changeType": {
|
|
68
67
|
"type": "string",
|
|
69
68
|
"enum": ["none", "code_only", "schema_only", "code_and_schema"]
|
|
70
69
|
},
|
|
71
70
|
"requiresCodeChanges": { "type": "boolean" },
|
|
72
71
|
"requiresSchemaMigration": { "type": "boolean" },
|
|
73
|
-
"desiredSchemaPath": { "type": "string", "maxLength": 512 },
|
|
74
72
|
"migrationPaths": {
|
|
75
73
|
"type": "array",
|
|
76
74
|
"maxItems": 128,
|
|
@@ -102,6 +100,203 @@
|
|
|
102
100
|
}
|
|
103
101
|
}
|
|
104
102
|
},
|
|
103
|
+
"preservation_plan": {
|
|
104
|
+
"description": "Agent-authored preservation (smoke) plan for this attempt. Required whenever the report is deployable; forbidden on an unsupported report. Every test must be grounded in files the agent actually read or changed (evidence) and expressed as tenant-relative browser or HTTP steps the validation cell can execute without secrets. Structural bounds live here; per-action required fields, at least one assert* step per test, one to three happy_path tests, unique ids, the {{runId}} substitution and path safety are enforced semantically by the deployment plane.",
|
|
105
|
+
"type": "object",
|
|
106
|
+
"additionalProperties": false,
|
|
107
|
+
"required": ["version", "tests", "uncovered"],
|
|
108
|
+
"properties": {
|
|
109
|
+
"version": { "type": "integer", "const": 1 },
|
|
110
|
+
"tests": {
|
|
111
|
+
"description": "Ordered smoke tests. happy_path tests cover primary user journeys inferred from routes, APIs, forms and focused tests; boundary_check tests pin the smallest observable behaviour each applied playbook or changed file could regress.",
|
|
112
|
+
"type": "array",
|
|
113
|
+
"minItems": 1,
|
|
114
|
+
"maxItems": 8,
|
|
115
|
+
"items": {
|
|
116
|
+
"type": "object",
|
|
117
|
+
"additionalProperties": false,
|
|
118
|
+
"required": ["id", "kind", "name", "boundaries", "evidence", "confidence", "steps"],
|
|
119
|
+
"properties": {
|
|
120
|
+
"id": { "type": "string", "pattern": "^[a-z][a-z0-9-]{0,63}$" },
|
|
121
|
+
"kind": { "type": "string", "enum": ["happy_path", "boundary_check"] },
|
|
122
|
+
"name": { "type": "string", "minLength": 1, "maxLength": 120 },
|
|
123
|
+
"boundaries": {
|
|
124
|
+
"description": "Transformation boundaries this test claims to cover.",
|
|
125
|
+
"type": "array",
|
|
126
|
+
"minItems": 1,
|
|
127
|
+
"maxItems": 8,
|
|
128
|
+
"uniqueItems": true,
|
|
129
|
+
"items": {
|
|
130
|
+
"type": "string",
|
|
131
|
+
"enum": ["application", "routing", "database", "authentication", "runtime_dependency", "files", "provider", "background_work"]
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
"evidence": {
|
|
135
|
+
"description": "Files (and optionally lines) in the transformed workspace that ground this test. Paths must exist after transformation.",
|
|
136
|
+
"type": "array",
|
|
137
|
+
"minItems": 1,
|
|
138
|
+
"maxItems": 8,
|
|
139
|
+
"items": {
|
|
140
|
+
"type": "object",
|
|
141
|
+
"additionalProperties": false,
|
|
142
|
+
"required": ["path", "reason"],
|
|
143
|
+
"properties": {
|
|
144
|
+
"path": { "type": "string", "minLength": 1, "maxLength": 512 },
|
|
145
|
+
"line": { "type": "integer", "minimum": 1 },
|
|
146
|
+
"reason": { "type": "string", "minLength": 1, "maxLength": 512 }
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
"confidence": { "type": "string", "enum": ["high", "medium", "low"] },
|
|
151
|
+
"steps": {
|
|
152
|
+
"description": "Executed in order inside the validation cell's authenticated browser context; the test stops at its first failed step. Which optional fields apply depends on action: navigate/request/assertURL use path; click/fill/assertVisible use target; fill uses value; assertVisible uses text; request uses method, body and name; assertResponse uses request, status, jsonKey and jsonValue.",
|
|
153
|
+
"type": "array",
|
|
154
|
+
"minItems": 1,
|
|
155
|
+
"maxItems": 20,
|
|
156
|
+
"items": {
|
|
157
|
+
"type": "object",
|
|
158
|
+
"additionalProperties": false,
|
|
159
|
+
"required": ["action"],
|
|
160
|
+
"properties": {
|
|
161
|
+
"action": {
|
|
162
|
+
"type": "string",
|
|
163
|
+
"enum": ["navigate", "click", "fill", "reload", "request", "assertVisible", "assertURL", "assertResponse"]
|
|
164
|
+
},
|
|
165
|
+
"path": {
|
|
166
|
+
"description": "Tenant-relative path starting with '/'; never a scheme or host.",
|
|
167
|
+
"type": "string",
|
|
168
|
+
"maxLength": 512
|
|
169
|
+
},
|
|
170
|
+
"target": {
|
|
171
|
+
"type": "object",
|
|
172
|
+
"additionalProperties": false,
|
|
173
|
+
"required": ["by"],
|
|
174
|
+
"properties": {
|
|
175
|
+
"by": { "type": "string", "enum": ["role", "label", "placeholder", "text", "testId", "css"] },
|
|
176
|
+
"role": { "type": "string", "maxLength": 64 },
|
|
177
|
+
"name": { "description": "Accessible name when by is role.", "type": "string", "maxLength": 256 },
|
|
178
|
+
"value": { "description": "Locator value for every by other than role.", "type": "string", "maxLength": 512 }
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
"value": { "type": "string", "maxLength": 1024 },
|
|
182
|
+
"text": { "type": "string", "maxLength": 512 },
|
|
183
|
+
"method": { "type": "string", "enum": ["GET", "POST", "PUT", "PATCH", "DELETE"] },
|
|
184
|
+
"body": { "description": "JSON text sent as the request body.", "type": "string", "maxLength": 16384 },
|
|
185
|
+
"name": { "description": "Names a request's response for a later assertResponse in the same test.", "type": "string", "pattern": "^[a-z][a-z0-9-]{0,63}$" },
|
|
186
|
+
"request": { "description": "The named preceding request an assertResponse inspects.", "type": "string", "pattern": "^[a-z][a-z0-9-]{0,63}$" },
|
|
187
|
+
"status": { "type": "integer", "minimum": 100, "maximum": 599 },
|
|
188
|
+
"jsonKey": { "description": "Top-level key or dotted path into the JSON response; no brackets.", "type": "string", "maxLength": 128 },
|
|
189
|
+
"jsonValue": { "description": "Expected value, compared as a string.", "type": "string", "maxLength": 512 },
|
|
190
|
+
"timeoutMs": { "type": "integer", "minimum": 1000, "maximum": 30000 }
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
"uncovered": {
|
|
198
|
+
"description": "Boundaries the agent deliberately left untested, each with a reviewed reason. An empty array claims full coverage.",
|
|
199
|
+
"type": "array",
|
|
200
|
+
"maxItems": 8,
|
|
201
|
+
"items": {
|
|
202
|
+
"type": "object",
|
|
203
|
+
"additionalProperties": false,
|
|
204
|
+
"required": ["boundary", "reason"],
|
|
205
|
+
"properties": {
|
|
206
|
+
"boundary": {
|
|
207
|
+
"type": "string",
|
|
208
|
+
"enum": ["application", "routing", "database", "authentication", "runtime_dependency", "files", "provider", "background_work"]
|
|
209
|
+
},
|
|
210
|
+
"reason": {
|
|
211
|
+
"type": "string",
|
|
212
|
+
"enum": ["not-observable-in-validation-cell", "no-safe-fixture", "no-grounded-surface"]
|
|
213
|
+
},
|
|
214
|
+
"detail": { "type": "string", "maxLength": 512 }
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
"validation_fixtures": {
|
|
221
|
+
"description": "A bounded, non-secret fixture program for this transformation's throwaway validation cell. Null means the application needs no external fixture. Bindings use application-owned logical names and explicitly inject only generated fixture URLs; fixtures describe deterministic HTTP or WebSocket exchanges without naming an application or provider and without executable code. The deployment plane semantically verifies unique fixture ids, template references, operation coverage, JSON text and safe config updates before forwarding the spec to the data plane.",
|
|
222
|
+
"anyOf": [
|
|
223
|
+
{
|
|
224
|
+
"type": "object",
|
|
225
|
+
"additionalProperties": false,
|
|
226
|
+
"required": ["version", "bindings", "fixtures"],
|
|
227
|
+
"properties": {
|
|
228
|
+
"version": { "type": "integer", "const": 1 },
|
|
229
|
+
"bindings": {
|
|
230
|
+
"description": "Bounded per-run fixture requirements. logical_name is an arbitrary application-owned binding name; deployment-plane semantic validation requires it to be unique within this array.",
|
|
231
|
+
"type": "array",
|
|
232
|
+
"minItems": 1,
|
|
233
|
+
"maxItems": 32,
|
|
234
|
+
"items": { "$ref": "#/definitions/ValidationFixtureBinding" }
|
|
235
|
+
},
|
|
236
|
+
"fixtures": {
|
|
237
|
+
"type": "array",
|
|
238
|
+
"minItems": 1,
|
|
239
|
+
"maxItems": 32,
|
|
240
|
+
"items": {
|
|
241
|
+
"type": "object",
|
|
242
|
+
"additionalProperties": false,
|
|
243
|
+
"required": ["id", "type", "http", "websocket"],
|
|
244
|
+
"properties": {
|
|
245
|
+
"id": {
|
|
246
|
+
"type": "string",
|
|
247
|
+
"pattern": "^[a-z][a-z0-9-]{0,63}$"
|
|
248
|
+
},
|
|
249
|
+
"type": { "type": "string", "enum": ["http", "websocket"] },
|
|
250
|
+
"http": {
|
|
251
|
+
"anyOf": [
|
|
252
|
+
{ "$ref": "#/definitions/ValidationHttpFixture" },
|
|
253
|
+
{ "type": "null" }
|
|
254
|
+
]
|
|
255
|
+
},
|
|
256
|
+
"websocket": {
|
|
257
|
+
"anyOf": [
|
|
258
|
+
{ "$ref": "#/definitions/ValidationWebSocketFixture" },
|
|
259
|
+
{ "type": "null" }
|
|
260
|
+
]
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
"oneOf": [
|
|
264
|
+
{
|
|
265
|
+
"type": "object",
|
|
266
|
+
"additionalProperties": false,
|
|
267
|
+
"required": ["id", "type", "http", "websocket"],
|
|
268
|
+
"properties": {
|
|
269
|
+
"id": {
|
|
270
|
+
"type": "string",
|
|
271
|
+
"pattern": "^[a-z][a-z0-9-]{0,63}$"
|
|
272
|
+
},
|
|
273
|
+
"type": { "const": "http" },
|
|
274
|
+
"http": { "$ref": "#/definitions/ValidationHttpFixture" },
|
|
275
|
+
"websocket": { "type": "null" }
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
"type": "object",
|
|
280
|
+
"additionalProperties": false,
|
|
281
|
+
"required": ["id", "type", "http", "websocket"],
|
|
282
|
+
"properties": {
|
|
283
|
+
"id": {
|
|
284
|
+
"type": "string",
|
|
285
|
+
"pattern": "^[a-z][a-z0-9-]{0,63}$"
|
|
286
|
+
},
|
|
287
|
+
"type": { "const": "websocket" },
|
|
288
|
+
"http": { "type": "null" },
|
|
289
|
+
"websocket": { "$ref": "#/definitions/ValidationWebSocketFixture" }
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
]
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
{ "type": "null" }
|
|
298
|
+
]
|
|
299
|
+
},
|
|
105
300
|
"application": {
|
|
106
301
|
"type": "object",
|
|
107
302
|
"additionalProperties": false,
|
|
@@ -109,11 +304,42 @@
|
|
|
109
304
|
"unsupported": {
|
|
110
305
|
"type": "object",
|
|
111
306
|
"additionalProperties": false,
|
|
112
|
-
"required": ["capability", "constraint", "reason"],
|
|
307
|
+
"required": ["category", "capability", "operation", "constraint", "reason", "evidence"],
|
|
113
308
|
"properties": {
|
|
309
|
+
"category": {
|
|
310
|
+
"type": "string",
|
|
311
|
+
"enum": [
|
|
312
|
+
"auth.validation-seed-unsupported",
|
|
313
|
+
"auth.identity-entity-unresolvable",
|
|
314
|
+
"capability.unsupported"
|
|
315
|
+
],
|
|
316
|
+
"description": "Closed machine category selected independently from the human explanation."
|
|
317
|
+
},
|
|
114
318
|
"capability": { "type": "string", "minLength": 1, "maxLength": 512 },
|
|
319
|
+
"operation": {
|
|
320
|
+
"description": "The exact source operation that cannot be preserved, such as a cross-document transactional write. Name a concrete operation, not an engine or product.",
|
|
321
|
+
"type": "string",
|
|
322
|
+
"minLength": 1,
|
|
323
|
+
"maxLength": 512
|
|
324
|
+
},
|
|
115
325
|
"constraint": { "type": "string", "minLength": 1, "maxLength": 512 },
|
|
116
|
-
"reason": { "type": "string", "minLength": 1, "maxLength": 2048 }
|
|
326
|
+
"reason": { "type": "string", "minLength": 1, "maxLength": 2048 },
|
|
327
|
+
"evidence": {
|
|
328
|
+
"description": "Exact source locations proving the required operation and the unsupported platform capability. Environment failures and unfinished conversion work are not evidence.",
|
|
329
|
+
"type": "array",
|
|
330
|
+
"minItems": 1,
|
|
331
|
+
"maxItems": 32,
|
|
332
|
+
"items": {
|
|
333
|
+
"type": "object",
|
|
334
|
+
"additionalProperties": false,
|
|
335
|
+
"required": ["path", "line", "reason"],
|
|
336
|
+
"properties": {
|
|
337
|
+
"path": { "type": "string", "minLength": 1, "maxLength": 512 },
|
|
338
|
+
"line": { "type": "integer", "minimum": 1 },
|
|
339
|
+
"reason": { "type": "string", "minLength": 1, "maxLength": 1024 }
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
117
343
|
}
|
|
118
344
|
},
|
|
119
345
|
"runtime": {
|
|
@@ -145,23 +371,18 @@
|
|
|
145
371
|
]
|
|
146
372
|
},
|
|
147
373
|
"validationSeed": {
|
|
148
|
-
"description": "Build-only bootstrap for authenticated browser validation. Use null when the application does not require a
|
|
374
|
+
"description": "Build-only PostgreSQL bootstrap for authenticated browser validation. Use null when the application does not require a pre-existing database identity. The SQL file runs only in Harbour's throwaway validation database after migrations and before candidate boot. Harbour supplies the fixed probe subject and lowercased email as transaction-local settings.",
|
|
149
375
|
"anyOf": [
|
|
150
376
|
{
|
|
151
377
|
"type": "object",
|
|
152
378
|
"additionalProperties": false,
|
|
153
|
-
"required": ["
|
|
379
|
+
"required": ["kind", "path"],
|
|
154
380
|
"properties": {
|
|
155
|
-
"
|
|
156
|
-
|
|
157
|
-
"minItems": 1,
|
|
158
|
-
"maxItems": 64,
|
|
159
|
-
"items": { "type": "string", "minLength": 1, "maxLength": 1024 }
|
|
160
|
-
},
|
|
161
|
-
"identityEmailEnvironmentVariable": {
|
|
381
|
+
"kind": { "type": "string", "const": "sql" },
|
|
382
|
+
"path": {
|
|
162
383
|
"type": "string",
|
|
163
|
-
"pattern": "
|
|
164
|
-
"
|
|
384
|
+
"pattern": "^\\.harbour/validation/(?:[^/]+/)*[^/]+\\.sql$",
|
|
385
|
+
"maxLength": 512
|
|
165
386
|
}
|
|
166
387
|
}
|
|
167
388
|
},
|
|
@@ -243,9 +464,209 @@
|
|
|
243
464
|
}
|
|
244
465
|
}
|
|
245
466
|
},
|
|
467
|
+
"definitions": {
|
|
468
|
+
"ValidationFixtureBinding": {
|
|
469
|
+
"type": "object",
|
|
470
|
+
"additionalProperties": false,
|
|
471
|
+
"required": ["logical_name", "operations", "environment", "config"],
|
|
472
|
+
"properties": {
|
|
473
|
+
"logical_name": {
|
|
474
|
+
"type": "string",
|
|
475
|
+
"pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$"
|
|
476
|
+
},
|
|
477
|
+
"fixture_id": {
|
|
478
|
+
"type": "string",
|
|
479
|
+
"pattern": "^[a-z][a-z0-9-]{0,63}$",
|
|
480
|
+
"description": "Optional explicit fixture association for a binding that has no URL mapping, such as a credential-only binding."
|
|
481
|
+
},
|
|
482
|
+
"operations": {
|
|
483
|
+
"type": "array",
|
|
484
|
+
"minItems": 1,
|
|
485
|
+
"maxItems": 5,
|
|
486
|
+
"uniqueItems": true,
|
|
487
|
+
"items": {
|
|
488
|
+
"type": "string",
|
|
489
|
+
"enum": ["http", "websocket", "email", "webhook", "ai"]
|
|
490
|
+
}
|
|
491
|
+
},
|
|
492
|
+
"environment": {
|
|
493
|
+
"description": "Environment variables that receive a generated fixture URL. Values cannot carry arbitrary configuration or secrets.",
|
|
494
|
+
"type": "array",
|
|
495
|
+
"maxItems": 32,
|
|
496
|
+
"items": {
|
|
497
|
+
"type": "object",
|
|
498
|
+
"additionalProperties": false,
|
|
499
|
+
"required": ["name"],
|
|
500
|
+
"properties": {
|
|
501
|
+
"name": {
|
|
502
|
+
"type": "string",
|
|
503
|
+
"pattern": "^[A-Z][A-Z0-9_]{0,127}$"
|
|
504
|
+
},
|
|
505
|
+
"value": {
|
|
506
|
+
"type": "string",
|
|
507
|
+
"pattern": "^\\{\\{fixture\\.[a-z][a-z0-9-]{0,63}\\.url\\}\\}$"
|
|
508
|
+
},
|
|
509
|
+
"test_value": {
|
|
510
|
+
"type": "string",
|
|
511
|
+
"minLength": 13,
|
|
512
|
+
"maxLength": 256,
|
|
513
|
+
"pattern": "^harbour-test-[^:/\\s]{1,243}$",
|
|
514
|
+
"description": "Validation-only synthetic credential. It is never exported as deployed runtime configuration."
|
|
515
|
+
}
|
|
516
|
+
},
|
|
517
|
+
"oneOf": [
|
|
518
|
+
{ "required": ["value"], "not": { "required": ["test_value"] } },
|
|
519
|
+
{ "required": ["test_value"], "not": { "required": ["value"] } }
|
|
520
|
+
]
|
|
521
|
+
}
|
|
522
|
+
},
|
|
523
|
+
"config": {
|
|
524
|
+
"description": "Structured JSON or YAML keys that receive a generated fixture URL in a bounded relative application config file.",
|
|
525
|
+
"type": "array",
|
|
526
|
+
"maxItems": 32,
|
|
527
|
+
"items": {
|
|
528
|
+
"type": "object",
|
|
529
|
+
"additionalProperties": false,
|
|
530
|
+
"required": ["path", "format", "key", "value"],
|
|
531
|
+
"properties": {
|
|
532
|
+
"path": {
|
|
533
|
+
"type": "string",
|
|
534
|
+
"minLength": 1,
|
|
535
|
+
"maxLength": 512,
|
|
536
|
+
"pattern": "^(?:[A-Za-z0-9_-][A-Za-z0-9._-]*/)*[A-Za-z0-9_-][A-Za-z0-9._-]*$"
|
|
537
|
+
},
|
|
538
|
+
"format": { "type": "string", "enum": ["json", "yaml"] },
|
|
539
|
+
"key": {
|
|
540
|
+
"type": "string",
|
|
541
|
+
"minLength": 1,
|
|
542
|
+
"maxLength": 256,
|
|
543
|
+
"pattern": "^[A-Za-z0-9_-]+(?:\\.[A-Za-z0-9_-]+)*$"
|
|
544
|
+
},
|
|
545
|
+
"value": {
|
|
546
|
+
"type": "string",
|
|
547
|
+
"pattern": "^\\{\\{fixture\\.[a-z][a-z0-9-]{0,63}\\.url\\}\\}$"
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
},
|
|
554
|
+
"ValidationFixtureNameValuePairs": {
|
|
555
|
+
"type": "array",
|
|
556
|
+
"maxItems": 32,
|
|
557
|
+
"items": {
|
|
558
|
+
"type": "object",
|
|
559
|
+
"additionalProperties": false,
|
|
560
|
+
"required": ["name", "value"],
|
|
561
|
+
"properties": {
|
|
562
|
+
"name": { "type": "string", "minLength": 1, "maxLength": 128 },
|
|
563
|
+
"value": { "type": "string", "maxLength": 2048 }
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
},
|
|
567
|
+
"ValidationHttpFixture": {
|
|
568
|
+
"type": "object",
|
|
569
|
+
"additionalProperties": false,
|
|
570
|
+
"required": ["base_path", "routes"],
|
|
571
|
+
"properties": {
|
|
572
|
+
"base_path": {
|
|
573
|
+
"type": "string",
|
|
574
|
+
"maxLength": 512,
|
|
575
|
+
"pattern": "^/(?:[A-Za-z0-9._~-]+(?:/[A-Za-z0-9._~-]+)*)?$"
|
|
576
|
+
},
|
|
577
|
+
"routes": {
|
|
578
|
+
"type": "array",
|
|
579
|
+
"minItems": 1,
|
|
580
|
+
"maxItems": 64,
|
|
581
|
+
"items": {
|
|
582
|
+
"type": "object",
|
|
583
|
+
"additionalProperties": false,
|
|
584
|
+
"required": ["operation", "request", "response"],
|
|
585
|
+
"properties": {
|
|
586
|
+
"operation": {
|
|
587
|
+
"type": "string",
|
|
588
|
+
"enum": ["http", "email", "webhook", "ai"]
|
|
589
|
+
},
|
|
590
|
+
"request": {
|
|
591
|
+
"type": "object",
|
|
592
|
+
"additionalProperties": false,
|
|
593
|
+
"required": ["method", "path", "query", "headers", "body"],
|
|
594
|
+
"properties": {
|
|
595
|
+
"method": {
|
|
596
|
+
"type": "string",
|
|
597
|
+
"enum": ["GET", "POST", "PUT", "PATCH", "DELETE"]
|
|
598
|
+
},
|
|
599
|
+
"path": {
|
|
600
|
+
"type": "string",
|
|
601
|
+
"maxLength": 512,
|
|
602
|
+
"pattern": "^/(?:[A-Za-z0-9._~-]+(?:/[A-Za-z0-9._~-]+)*)?$"
|
|
603
|
+
},
|
|
604
|
+
"query": { "$ref": "#/definitions/ValidationFixtureNameValuePairs" },
|
|
605
|
+
"headers": { "$ref": "#/definitions/ValidationFixtureNameValuePairs" },
|
|
606
|
+
"body": { "type": "string", "maxLength": 16384 }
|
|
607
|
+
}
|
|
608
|
+
},
|
|
609
|
+
"response": {
|
|
610
|
+
"type": "object",
|
|
611
|
+
"additionalProperties": false,
|
|
612
|
+
"required": ["status", "headers", "body"],
|
|
613
|
+
"properties": {
|
|
614
|
+
"status": { "type": "integer", "minimum": 100, "maximum": 599 },
|
|
615
|
+
"headers": { "$ref": "#/definitions/ValidationFixtureNameValuePairs" },
|
|
616
|
+
"body": { "type": "string", "maxLength": 16384 }
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
},
|
|
624
|
+
"ValidationWebSocketFixture": {
|
|
625
|
+
"type": "object",
|
|
626
|
+
"additionalProperties": false,
|
|
627
|
+
"required": ["path", "expected_json", "send_messages", "close"],
|
|
628
|
+
"properties": {
|
|
629
|
+
"path": {
|
|
630
|
+
"type": "string",
|
|
631
|
+
"maxLength": 512,
|
|
632
|
+
"pattern": "^/(?:[A-Za-z0-9._~-]+(?:/[A-Za-z0-9._~-]+)*)?$"
|
|
633
|
+
},
|
|
634
|
+
"expected_json": {
|
|
635
|
+
"description": "Exact JSON value the fixture expects as the first client message, or null when no client message is required. JSON parsing is a semantic check.",
|
|
636
|
+
"anyOf": [
|
|
637
|
+
{ "type": "string", "minLength": 1, "maxLength": 16384 },
|
|
638
|
+
{ "type": "null" }
|
|
639
|
+
]
|
|
640
|
+
},
|
|
641
|
+
"send_messages": {
|
|
642
|
+
"type": "array",
|
|
643
|
+
"minItems": 1,
|
|
644
|
+
"maxItems": 64,
|
|
645
|
+
"items": { "type": "string", "maxLength": 16384 }
|
|
646
|
+
},
|
|
647
|
+
"close": {
|
|
648
|
+
"anyOf": [
|
|
649
|
+
{
|
|
650
|
+
"type": "object",
|
|
651
|
+
"additionalProperties": false,
|
|
652
|
+
"required": ["code", "reason"],
|
|
653
|
+
"properties": {
|
|
654
|
+
"code": { "type": "integer", "minimum": 1000, "maximum": 4999 },
|
|
655
|
+
"reason": { "type": "string", "maxLength": 123 }
|
|
656
|
+
}
|
|
657
|
+
},
|
|
658
|
+
{ "type": "null" }
|
|
659
|
+
]
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
},
|
|
664
|
+
"x-unique-array-by": [
|
|
665
|
+
{ "field": "validation_fixtures.bindings", "key": "logical_name" }
|
|
666
|
+
],
|
|
246
667
|
"oneOf": [
|
|
247
668
|
{
|
|
248
|
-
"required": ["applied_playbooks", "static_rebuild_required", "analysis", "database", "application"],
|
|
669
|
+
"required": ["applied_playbooks", "static_rebuild_required", "analysis", "database", "preservation_plan", "validation_fixtures", "application"],
|
|
249
670
|
"properties": {
|
|
250
671
|
"application": {
|
|
251
672
|
"required": ["runtime", "buildKind", "topology", "entrypoint", "build", "basePath", "evidence", "unsupportedFeatures"],
|