Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,9 @@ release-source/
.tmp/
*.log
.DS_Store

.tmp-gh-*.ps1

.tmp-venv*/

TestResults/
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ All notable changes follow Semantic Versioning and Keep a Changelog conventions.
- Conservative automated JSON Schema compatibility classification in pull-request CI.
- Deterministic schema registry packaging and release-triggered GitHub Pages distribution.
- Stable major/minor and immutable patch-version schema URL contracts with SHA-256 manifests.
- `failure-state.schema.json` for typed failure-state payloads in the v1.1 contract line.
- Shared `failureClass` enum in `schemas/v1.1/common.schema.json` for cross-runtime failure classification.

## [1.1.1] - 2026-07-05

Expand Down
10 changes: 10 additions & 0 deletions docs/VERSIONING.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,13 @@ The classifier reports:
- `review_required`: changed semantics that cannot be classified safely by the automated rules.

Both `breaking` and `review_required` exit nonzero. A `review_required` result must be resolved by simplifying the change, extending classifier coverage with tests, or obtaining explicit maintainer review. Automation never treats an unknown semantic change as compatible.

## v1.1 FailureState Addition

The `FailureState` contract added to the v1.1 line is a minor, additive change.

- It introduces a new schema file: `schemas/v1.1/failure-state.schema.json`.
- It adds a shared `failureClass` enum definition to `schemas/v1.1/common.schema.json`.
- It does not remove or rename any existing schema, required property, or accepted enum value in previously published contracts.

Existing v1.1 payloads remain valid, and consumers can adopt `FailureState` incrementally.
27 changes: 27 additions & 0 deletions examples/v1.1/failure-state.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"kind": "FailureState",
"correlationId": "corr-20260707-001",
"promptId": "prompt-typed-failure-001",
"runId": "run-typed-failure-001",
"repo": "Coding-Autopilot-System/gsd-orchestrator",
"actor": { "id": "gsd-orchestrator", "type": "workflow" },
"timestamp": "2026-07-07T09:45:00Z",
"schemaVersion": "1.1.0",
"traceContext": { "traceparent": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01" },
"failureClass": "transient",
"component": "gsd-orchestrator.LoopCoordinator",
"message": "MAF worker process exited with code 1",
"retryable": true,
"exceptionType": "System.InvalidOperationException",
"causeChain": [
"MAF worker failed with exit code 1: connection refused",
"ConnectionRefusedError: [Errno 111] Connection refused"
],
"retryAfterSeconds": 5,
"evidence": [
{
"kind": "log",
"uri": "https://coding-autopilot-system.github.io/cas-contracts/examples/v1.1/failure-state.json"
}
]
}
10 changes: 10 additions & 0 deletions schemas/v1.1/common.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,16 @@
"waiting",
"terminal"
]
},
"failureClass": {
"type": "string",
"enum": [
"transient",
"deterministic",
"policy",
"cancellation",
"unrecoverable"
]
}
}
}
37 changes: 37 additions & 0 deletions schemas/v1.1/failure-state.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://schemas.coding-autopilot.dev/v1.1/failure-state.schema.json",
"title": "FailureState",
"type": "object",
"allOf": [
{ "$ref": "common.schema.json#/$defs/lifecycleMetadata" },
{
"type": "object",
"required": [
"kind",
"failureClass",
"component",
"message",
"retryable"
],
"properties": {
"kind": { "const": "FailureState" },
"failureClass": { "$ref": "common.schema.json#/$defs/failureClass" },
"component": { "type": "string", "minLength": 1, "maxLength": 256 },
"message": { "type": "string", "minLength": 1, "maxLength": 5000 },
"retryable": { "type": "boolean" },
"exceptionType": { "type": "string", "maxLength": 256 },
"causeChain": {
"type": "array",
"items": { "type": "string", "maxLength": 1000 }
},
"retryAfterSeconds": { "type": "number", "minimum": 0 },
"evidence": {
"type": "array",
"items": { "$ref": "common.schema.json#/$defs/evidence" }
}
}
}
],
"unevaluatedProperties": false
}
20 changes: 20 additions & 0 deletions tests/contracts.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,23 @@ test("v1.1 SDLC request rejects missing profile version", () => {
assert.equal(validate(request), false);
assert.match(ajv.errorsText(validate.errors), /profileVersion/);
});

test("v1.1 FailureState example satisfies the authoritative schema", () => {
const examplePath = `${exampleRoot}/v1.1/failure-state.json`;
const validate = ajv.getSchema(schemaIdForExample(examplePath));
const example = examples.find((record) => record.kind === "FailureState" && record.schemaVersion === "1.1.0");

assert.ok(validate, "v1.1 failure-state schema is registered");
assert.ok(example, "v1.1 FailureState example exists");
assert.equal(validate(example), true, ajv.errorsText(validate.errors));
});

test("v1.1 FailureState rejects missing failureClass", () => {
const example = structuredClone(examples.find((record) => record.kind === "FailureState" && record.schemaVersion === "1.1.0"));
const validate = ajv.getSchema(schemaId("v1.1", "failure-state"));
delete example.failureClass;

assert.ok(validate, "v1.1 failure-state schema is registered");
assert.equal(validate(example), false);
assert.match(ajv.errorsText(validate.errors), /failureClass/);
});
2 changes: 1 addition & 1 deletion tests/registry.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -88,5 +88,5 @@ test("registry all-mode publishes all lines without replacing any", async () =>
assert.deepEqual(index.lines, { "v0.1": "0.1.0", "v1.0": "1.0.0", "v1.1": "1.1.0" });
assert.equal((await readJson(path.join(output, "v0.1", "manifest.json"))).schemas.length, 8);
assert.equal((await readJson(path.join(output, "v1.0", "manifest.json"))).schemas.length, 8);
assert.equal((await readJson(path.join(output, "v1.1", "manifest.json"))).schemas.length, 6);
assert.equal((await readJson(path.join(output, "v1.1", "manifest.json"))).schemas.length, 7);
});
Loading