Skip to content
Merged
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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

## Unreleased corrective candidate

- Added opt-in ActionProposal, SettlementReceipt, and SettlementBundle v0.2 schemas that bound
ordered `gte` and `lte` thresholds to finite JavaScript binary64 numbers.
The v0.2 receipt signs its proposal schema version, and verification rejects
bundle, receipt, or audit-proposal version substitution.
Published v0.1 schema bytes and default v0.1 artifact hashes remain
unchanged. Existing v0.1 `eq` and finite numeric ordered clauses remain
compatible; non-numeric ordered thresholds or evidence now fail closed
instead of using JavaScript coercion.
- The loopback sidecar now uses the host clock by default so current-time proposals are admissible, and accepts `--clock demo|system` plus `CONSEQUENCE_RAIL_PORT` / `CONSEQUENCE_RAIL_CLOCK` when flags are omitted.
- ActionProposal and RecourseReservation parsers now reject unsafe, non-integral, and overflow-scale duration values so evidence freshness and remedy-window arithmetic stay exact.
- HTTP sidecar failures now include a `request_id` (and the action id when the route named one). Unexpected exceptions are logged to stderr and returned as `INTERNAL_ERROR` instead of being swallowed as `REQUEST_INVALID`.
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,11 @@ OpenAPI clients can send current timestamps. Pass `--clock demo` to freeze
time at the conformance epoch `2035-01-01T00:00:00.000Z`. When flags are
omitted, `CONSEQUENCE_RAIL_PORT` and `CONSEQUENCE_RAIL_CLOCK` supply the same
defaults. Read [`api/openapi.json`](api/openapi.json) for the request surface.
The sidecar accepts the immutable ActionProposal v0.1 format and the opt-in
v0.2 format for schema-bounded numeric `gte` and `lte` clauses. A v0.2
proposal exports a v0.2 settlement bundle and a signed v0.2 receipt that binds
the proposal schema version even in the proposal-free receipt profile. Default
v0.1 demo artifacts remain unchanged.
The v0.1 sidecar stores bounded state in memory and exposes only the synthetic
connector. It accepts only loopback clients and same-origin loopback Host and
Origin values, caps JSON bodies at 65,536 bytes, restricts content types and
Expand Down
15 changes: 12 additions & 3 deletions api/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,10 @@
"content": {
"application/json": {
"schema": {
"$ref": "../spec/schemas/action-proposal.schema.json"
"oneOf": [
{ "$ref": "../spec/schemas/action-proposal.schema.json" },
{ "$ref": "../spec/schemas/action-proposal-v0.2.schema.json" }
Comment on lines +58 to +60

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Capability discovery omits v0.2 proposals

After v0.2 proposals become accepted, optional_features still advertises only recovery preflight. Discovery-driven clients cannot select the new format.

Prompt for agents
Update the capability discovery contract and implementation to advertise ActionProposal v0.2 support. The response is built in src/http-server.js for /.well-known/consequence-rail, while api/openapi.json defines that response schema. Add deterministic coverage proving discovery exposes the supported proposal formats without changing the meaning of the existing API protocol version.
Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

]
}
}
}
Expand Down Expand Up @@ -376,7 +379,10 @@
"content": {
"application/json": {
"schema": {
"$ref": "../spec/schemas/settlement-bundle.schema.json"
"oneOf": [
{ "$ref": "../spec/schemas/settlement-bundle.schema.json" },
{ "$ref": "../spec/schemas/settlement-bundle-v0.2.schema.json" }
]
}
}
}
Expand All @@ -396,7 +402,10 @@
"content": {
"application/json": {
"schema": {
"$ref": "../spec/schemas/settlement-bundle.schema.json"
"oneOf": [
{ "$ref": "../spec/schemas/settlement-bundle.schema.json" },
{ "$ref": "../spec/schemas/settlement-bundle-v0.2.schema.json" }
]
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions conformance/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ conforming implementation must accept it, produce the same action digest for
any semantically identical object-key ordering, and reject any unknown
top-level field.

`refund-action-v0.2.json` is the ordered-comparison example. It uses both
numeric `gte` and `lte` clauses under the bounded v0.2 proposal schema while
the original v0.1 fixture remains unchanged.

`refund-recovery-contract.json` is the canonical Recovery Preflight contract.
It pins a synthetic duplicate-refund fault, the actual mock connector remedy,
an exact declared-state oracle, the signed reservation and connector
Expand Down
40 changes: 40 additions & 0 deletions conformance/refund-action-v0.2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"schema_version": "consequence-rail/action-proposal/v0.2",
"action_type": "demo.refund.issue/v1",
"subject": {
"type": "service",
"id": "support-agent-demo"
},
"target": {
"connector": "mock-refund-processor",
"resource_type": "order",
"resource_id": "ord_demo_42"
},
"parameters": {
"amount_minor": 12000,
"currency": "USD"
},
"idempotency_key": "refund:ord_demo_42:ordered:1",
"requested_at": "2035-01-01T00:00:00.000Z",
"expires_at": "2035-01-01T00:02:00.000Z",
"assurance_mode": "enforced",
"postcondition": {
"op": "all",
"clauses": [
{
"path": "active_refund_count",
"op": "gte",
"value": 1
},
{
"path": "net_refunded_minor",
"op": "lte",
"value": 12000
}
]
},
"evidence_plan": {
"source": "mock-refund-processor",
"max_age_seconds": 60
}
}
4 changes: 4 additions & 0 deletions scripts/check.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,16 @@ const required = [
"api/openapi.json",
"spec/model.md",
"spec/state-machine.md",
"spec/schemas/action-proposal-v0.2.schema.json",
"spec/schemas/connector-recourse-commitment.schema.json",
"spec/schemas/recovery-contract.schema.json",
"spec/schemas/recovery-drill-attestation.schema.json",
"spec/schemas/recovery-drill-bundle.schema.json",
"spec/schemas/settlement-bundle-v0.2.schema.json",
"spec/schemas/settlement-receipt-v0.2.schema.json",
"docs/threat-model.md",
"conformance/refund-action.json",
"conformance/refund-action-v0.2.json",
"conformance/refund-recovery-contract.json",
];
const textExtensions = new Set([".md", ".json", ".js", ".yaml", ".yml", ".txt"]);
Expand Down
19 changes: 19 additions & 0 deletions spec/model.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,19 @@ external action. It includes:
- data-only postcondition
- declared evidence source and freshness window

Postconditions support strict `eq` and numeric `gte` and `lte` clauses. The
published ActionProposal v0.1 schema remains immutable. ActionProposal v0.2
formally requires each ordered threshold to be a finite JavaScript binary64
number from `-1.7976931348623157e+308` to `1.7976931348623157e+308`, inclusive.
The reference runtime applies that no-coercion rule to both proposal versions:
non-numeric evidence does not satisfy an ordered clause, and a non-numeric
ordered threshold is rejected. Existing v0.1 `eq` proposals retain their
strict-equality behavior. A v0.2 proposal is carried by a v0.2 settlement
bundle so the immutable v0.1 bundle schema continues to reference only the
v0.1 proposal schema. Its v0.2 settlement receipt signs the exact v0.2
proposal schema identifier, so receipt-profile bundles remain version-bound
even though they omit the proposal.

The `action_digest` is the SHA-256 digest, encoded as unpadded base64url
(43 characters), of the proposal's canonical JSON bytes. Artifact fields
named as digests, event hashes, and evidence manifests use that same
Expand Down Expand Up @@ -178,6 +191,7 @@ produce a settlement receipt.

The receipt binds:

- proposal schema version in SettlementReceipt v0.2
- action digest
- reservation digest
- connector commitment digest and final reservation status
Expand All @@ -187,6 +201,11 @@ The receipt binds:
- event-chain head
- close time

Published SettlementReceipt v0.1 bytes remain immutable. A v0.1 bundle MUST
carry a v0.1 receipt. A v0.2 bundle MUST carry a v0.2 receipt whose signed
`proposal_schema_version` is `consequence-rail/action-proposal/v0.2`. Verifiers
reject bundle, receipt, and included audit-proposal version substitutions.

`settlement` is a technical protocol term. It does not mean legal settlement,
financial finality, insurance coverage, or guaranteed recovery.

Expand Down
221 changes: 221 additions & 0 deletions spec/schemas/action-proposal-v0.2.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "urn:consequence-rail:schema:action-proposal:v0.2",
"title": "ActionProposal",
"type": "object",
"additionalProperties": false,
"required": [
"schema_version",
"action_type",
"subject",
"target",
"parameters",
"idempotency_key",
"requested_at",
"expires_at",
"assurance_mode",
"postcondition",
"evidence_plan"
],
"properties": {
"schema_version": {
"const": "consequence-rail/action-proposal/v0.2"
},
"action_type": {
"enum": [
"demo.refund.issue/v1",
"demo.email.send/v1"
]
},
"subject": {
"type": "object",
"additionalProperties": false,
"required": [
"type",
"id"
],
"properties": {
"type": {
"type": "string",
"minLength": 1
},
"id": {
"type": "string",
"minLength": 1
}
}
},
"target": {
"type": "object",
"additionalProperties": false,
"required": [
"connector",
"resource_type",
"resource_id"
],
"properties": {
"connector": {
"type": "string",
"minLength": 1
},
"resource_type": {
"type": "string",
"minLength": 1
},
"resource_id": {
"type": "string",
"minLength": 1
}
}
},
"parameters": {
"type": "object",
"additionalProperties": true
},
"idempotency_key": {
"type": "string",
"minLength": 1
},
"requested_at": {
"type": "string",
"format": "date-time"
},
"expires_at": {
"type": "string",
"format": "date-time"
},
"assurance_mode": {
"enum": [
"enforced",
"cooperative",
"observed"
]
},
"postcondition": {
"$ref": "#/$defs/postcondition"
},
"evidence_plan": {
"type": "object",
"additionalProperties": false,
"required": [
"source",
"max_age_seconds"
],
"properties": {
"source": {
"type": "string",
"minLength": 1
},
"max_age_seconds": {
"type": "integer",
"minimum": 1
}
}
}
},
"allOf": [
{
"if": {
"properties": {
"action_type": { "const": "demo.refund.issue/v1" }
}
},
"then": {
"properties": {
"parameters": {
"type": "object",
"additionalProperties": false,
"required": ["amount_minor", "currency"],
"properties": {
"amount_minor": { "type": "integer", "minimum": 1 },
"currency": { "type": "string", "pattern": "^[A-Z]{3}$" }
}
}
}
}
},
{
"if": {
"properties": {
"action_type": { "const": "demo.email.send/v1" }
}
},
"then": {
"properties": {
"parameters": {
"type": "object",
"additionalProperties": false,
"required": ["recipient_id", "subject"],
"properties": {
"recipient_id": { "type": "string", "minLength": 1 },
"subject": { "type": "string", "minLength": 1 }
}
}
}
}
}
],
"$defs": {
"postcondition": {
"type": "object",
"additionalProperties": false,
"required": [
"op",
"clauses"
],
"properties": {
"op": {
"const": "all"
},
"clauses": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"path",
"op",
"value"
],
"properties": {
"path": {
"type": "string",
"minLength": 1
},
"op": {
"enum": [
"eq",
"gte",
"lte"
]
},
"value": {}
},
"allOf": [
{
"if": {
"properties": {
"op": {
"enum": ["gte", "lte"]
}
},
"required": ["op"]
},
"then": {
"properties": {
"value": {
"type": "number",
"minimum": -1.7976931348623157e+308,
"maximum": 1.7976931348623157e+308
}
}
}
}
]
}
}
}
}
}
}
Loading