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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and

- Add a versioned source-manifest schema, documentation, and synthetic conforming example for public-source provenance records.
- Add a bounded agent-job result schema, documentation, and synthetic conforming example linked to source-manifest provenance.
- Add backward-compatible cost, timeout, and freshness metadata to agent-job results.

### Security

Expand Down
4 changes: 4 additions & 0 deletions DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,7 @@ Runtime networking is pinned to the canonical Reppo public API and uses `urllib`
### D8. Defend against upstream pagination and availability drift

The public pod catalog currently returns the full catalog even when a smaller `limit` is requested. The inspector therefore validates `data.subnets` and `data.pods`, downloads only within the hard response cap, and slices returned collections to the caller's requested limit. The documented public stats route currently returns HTTP 404; aggregate commands preserve successful catalog data and mark the result partial rather than inventing replacement metrics.

### D9. Add operational observations to v1 without breaking existing documents

Cost, timeout, and freshness are optional, strictly bounded objects in the agent-job result v1 schema so documents created against the initial v1 contract remain valid. Costs use non-negative decimal strings rather than JSON numbers to avoid floating-point ambiguity. These fields expose public job-level observations only and exclude account, wallet, payment-credential, internal-budget, and provider-secret data.
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ A phase is complete only when its artifacts are exercised and its verification g

- [x] Versioned source-manifest schema
- [x] Structured agent-job result schema
- [ ] Cost, timeout, and freshness fields
- [x] Cost, timeout, and freshness fields
- [ ] Dry-run and approval-control reference patterns

**Gate:** another example can consume the schemas without private project context.
Expand Down
11 changes: 10 additions & 1 deletion docs/provenance-schemas.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,19 @@ See `examples/source-manifest/reppo-public-api-manifest-v1.example.json` for a s
- a bounded request summary made of named public scalar inputs;
- structured result data for successful and partial jobs, or `null` for failed jobs;
- provenance linking the result to a source manifest by `manifestId`, with an optional public manifest URL and source identifiers;
- optional bounded cost, timeout, and freshness metadata;
- bounded error and limitation records.

The status values are `succeeded`, `partial`, and `failed`. Failed jobs must contain at least one error and use `null` for `result`; successful and partial jobs must contain a structured result object. Producers must ensure `completedAt` is not earlier than `startedAt`, because JSON Schema cannot compare the two timestamps.

Request and result containers have explicit item, property, string, and finite nesting limits. These limits reduce the risk of accidental log or blob embedding, but they do not make private content safe: producers must still exclude credentials, wallet or account data, local paths, private runtime identifiers, and unpublished material.

See `examples/agent-job-result/reppo-inspection-result-v1.example.json` for a synthetic partial inspection result linked to the source-manifest example. Cost, timeout, and freshness fields remain a separate Phase 2 roadmap item.
The optional operational fields are backward-compatible additions to v1:

- `cost` records a non-negative decimal-string `amount`, a bounded uppercase currency or settlement-unit code, and whether the amount was measured or estimated. Decimal strings avoid floating-point ambiguity.
- `timeout` records the configured limit, observed elapsed time, and whether the job timed out. Durations are bounded integer milliseconds.
- `freshness` records when freshness was evaluated and whether data was `fresh`, `stale`, or `unknown`. Fresh and stale records also require `dataAsOf` and `maxAgeSeconds`; producers determine the status and must document relevant limitations.

These fields expose public job-level observations only. They must not contain account identifiers, payment credentials, wallet data, internal budgets, billing records, or provider secrets.

See `examples/agent-job-result/reppo-inspection-result-v1.example.json` for a synthetic partial inspection result linked to the source-manifest example.
5 changes: 3 additions & 2 deletions examples/agent-job-result/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@

This directory contains synthetic examples for `schemas/agent-job-result-v1.schema.json`.

The v1 envelope records a stable public job identifier, job type and status, timestamps, a bounded request summary, structured output, source-manifest provenance, errors, and limitations. A failed job uses `null` for `result` and includes at least one error; successful and partial jobs include a structured result object.
The v1 envelope records a stable public job identifier, job type and status, timestamps, a bounded request summary, structured output, source-manifest provenance, optional operational metadata, errors, and limitations. A failed job uses `null` for `result` and includes at least one error; successful and partial jobs include a structured result object.

The example is intentionally public-only and bounded:

- request inputs are named scalar values or bounded scalar arrays;
- result strings, arrays, and objects have per-container and finite nesting limits;
- provenance refers to a public source manifest by stable identifier and optional public URL;
- optional cost, timeout, and freshness records are bounded and contain no account or payment details;
- no credentials, wallets, account identifiers, local paths, private runtime state, or unbounded blobs are included.

Cost, timeout, and freshness fields are intentionally deferred to the next Phase 2 roadmap item.
The example reports a synthetic zero measured cost, a five-second timeout limit, and stale source data. These values demonstrate the contract and are not live operational measurements.
16 changes: 16 additions & 0 deletions examples/agent-job-result/reppo-inspection-result-v1.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,22 @@
"reppo-public-datanets-endpoint"
]
},
"cost": {
"amount": "0",
"currency": "USD",
"basis": "measured"
},
"timeout": {
"limitMs": 5000,
"elapsedMs": 2000,
"timedOut": false
},
"freshness": {
"evaluatedAt": "2026-07-14T12:00:02Z",
"status": "stale",
"dataAsOf": "2026-07-11T00:00:00Z",
"maxAgeSeconds": 86400
},
"errors": [
{
"code": "HTTP_ERROR",
Expand Down
2 changes: 1 addition & 1 deletion schemas/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
- `../examples/reppo-inspector/datanets-envelope-v1.example.json` — synthetic conforming example validated in CI.
- `source-manifest-v1.schema.json` — public-source provenance manifest for agentic-commerce artifacts.
- `../examples/source-manifest/reppo-public-api-manifest-v1.example.json` — synthetic conforming source-manifest example validated in CI.
- `agent-job-result-v1.schema.json` — bounded public result envelope for structured agent jobs.
- `agent-job-result-v1.schema.json` — bounded public result envelope for structured agent jobs, including optional cost, timeout, and freshness metadata.
- `../examples/agent-job-result/reppo-inspection-result-v1.example.json` — synthetic conforming agent-job result validated in CI.

Versioned JSON schemas for additional safety patterns will live here.
71 changes: 70 additions & 1 deletion schemas/agent-job-result-v1.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,10 @@
"minLength": 1,
"maxLength": 500
}
}
},
"cost": {"$ref": "#/$defs/cost"},
"timeout": {"$ref": "#/$defs/timeout"},
"freshness": {"$ref": "#/$defs/freshness"}
},
"allOf": [
{
Expand All @@ -81,6 +84,72 @@
}
],
"$defs": {
"cost": {
"type": "object",
"additionalProperties": false,
"required": ["amount", "currency", "basis"],
"properties": {
"amount": {
"type": "string",
"pattern": "^(0|[1-9][0-9]{0,17})(\\.[0-9]{1,18})?$",
"description": "Non-negative decimal cost encoded as a string to avoid floating-point ambiguity."
},
"currency": {
"type": "string",
"pattern": "^[A-Z][A-Z0-9]{2,11}$",
"description": "Public uppercase currency or settlement-unit code, such as USD or USDC."
},
"basis": {
"enum": ["measured", "estimated"]
}
}
},
"timeout": {
"type": "object",
"additionalProperties": false,
"required": ["limitMs", "elapsedMs", "timedOut"],
"properties": {
"limitMs": {
"type": "integer",
"minimum": 1,
"maximum": 604800000
},
"elapsedMs": {
"type": "integer",
"minimum": 0,
"maximum": 604800000
},
"timedOut": {"type": "boolean"}
}
},
"freshness": {
"type": "object",
"additionalProperties": false,
"required": ["evaluatedAt", "status"],
"properties": {
"evaluatedAt": {"type": "string", "format": "date-time"},
"status": {
"enum": ["fresh", "stale", "unknown"]
},
"dataAsOf": {"type": "string", "format": "date-time"},
"maxAgeSeconds": {
"type": "integer",
"minimum": 0,
"maximum": 315360000
}
},
"allOf": [
{
"if": {
"properties": {"status": {"enum": ["fresh", "stale"]}},
"required": ["status"]
},
"then": {
"required": ["dataAsOf", "maxAgeSeconds"]
}
}
]
},
"request": {
"type": "object",
"additionalProperties": false,
Expand Down
30 changes: 30 additions & 0 deletions tests/test_schema_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,36 @@ def test_source_manifest_example_conforms_to_source_manifest_v1(self):
def test_agent_job_result_example_conforms_to_agent_job_result_v1(self):
self.assert_conforms(AGENT_JOB_RESULT_SCHEMA, AGENT_JOB_RESULT_EXAMPLE)

def test_agent_job_operational_fields_are_backward_compatible_and_optional(self):
schema = load_json(AGENT_JOB_RESULT_SCHEMA)
validator = Draft202012Validator(schema, format_checker=FormatChecker())
job_result = load_json(AGENT_JOB_RESULT_EXAMPLE)
job_result.pop("cost")
job_result.pop("timeout")
job_result.pop("freshness")

self.assertTrue(validator.is_valid(job_result))

def test_agent_job_operational_fields_are_bounded(self):
schema = load_json(AGENT_JOB_RESULT_SCHEMA)
validator = Draft202012Validator(schema, format_checker=FormatChecker())

invalid_cost = load_json(AGENT_JOB_RESULT_EXAMPLE)
invalid_cost["cost"]["amount"] = "-1"
self.assertFalse(validator.is_valid(invalid_cost))

private_cost = load_json(AGENT_JOB_RESULT_EXAMPLE)
private_cost["cost"]["accountId"] = "not allowed"
self.assertFalse(validator.is_valid(private_cost))

invalid_timeout = load_json(AGENT_JOB_RESULT_EXAMPLE)
invalid_timeout["timeout"]["limitMs"] = 0
self.assertFalse(validator.is_valid(invalid_timeout))

incomplete_freshness = load_json(AGENT_JOB_RESULT_EXAMPLE)
incomplete_freshness["freshness"].pop("dataAsOf")
self.assertFalse(validator.is_valid(incomplete_freshness))

def test_source_manifest_requires_public_source_records(self):
schema = load_json(SOURCE_MANIFEST_SCHEMA)
validator = Draft202012Validator(schema, format_checker=FormatChecker())
Expand Down