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

## [Unreleased]

### Added

- Add a versioned source-manifest schema, documentation, and synthetic conforming example for public-source provenance records.

### Security

- Generalize public-boundary policy and scanner rules so documentation does not enumerate non-public system identifiers.
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ python3 -m build

See [docs/reppo-inspector.md](docs/reppo-inspector.md) for the JSON contract, canonical-host policy, input limits, exit codes, endpoint boundary, and failure behavior. Release maintainers should follow [docs/releasing.md](docs/releasing.md).

For portable public-source provenance records, see [docs/provenance-schemas.md](docs/provenance-schemas.md) and `schemas/source-manifest-v1.schema.json`.

For silent compatibility drift detection and bounded weekly project evidence, see [docs/automation.md](docs/automation.md). These helpers are read-only and never perform GitHub mutations.

At the 2026-07-11 compatibility check, the datanet and pod catalogs were live. The documented public stats route returned HTTP 404, so `status` and `snapshot` correctly returned partial result code `2` while preserving catalog data. The upstream pods route also ignored its requested page size; the client applies the requested limit after a capped download.
Expand Down
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ A phase is complete only when its artifacts are exercised and its verification g

## Phase 2 — Provenance and safety (`v0.2.0`)

- [ ] Versioned source-manifest schema
- [x] Versioned source-manifest schema
- [ ] Structured agent-job result schema
- [ ] Cost, timeout, and freshness fields
- [ ] Dry-run and approval-control reference patterns
Expand Down
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Documentation

- [Reppo read-only ecosystem inspector](reppo-inspector.md)
- [Provenance schemas](provenance-schemas.md)
- [Read-only maintenance automation](automation.md)
- [Release process](releasing.md)

Expand Down
33 changes: 33 additions & 0 deletions docs/provenance-schemas.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Provenance schemas

The toolkit's provenance schemas provide portable, public-only records for the sources and evidence behind agentic-commerce artifacts.

## Source manifest v1

`schemas/source-manifest-v1.schema.json` defines a manifest for documenting public sources used by a job, dataset, inspector output, compatibility check, or future reference service.

A conforming manifest records:

- `schemaVersion` — currently `1.0`;
- `manifestId` — a stable, non-secret identifier;
- `generatedAt` — when the manifest was produced;
- `subject` — the public artifact or topic the manifest describes;
- `sources` — one or more public source records;
- optional `notes` — bounded public notes.

Each source record includes:

- a stable `sourceId`;
- title, URL, type, publisher, and access time;
- optional license, version, retrieval time, or SHA-256 digest;
- a `usage` section describing why the source was used, what public facts were derived, and known limitations.

## Boundary rules

Source manifests must not contain credentials, private keys, wallet exports, local runtime state, private communications, unpublished research, or absolute local paths. Use public URLs, intentionally authored descriptions, and synthetic examples.

Manifests are provenance records, not proof that a source is still reachable or authoritative. Consumers should treat them as evidence to review and should re-check upstream sources when freshness matters.

## Example

See `examples/source-manifest/reppo-public-api-manifest-v1.example.json` for a synthetic manifest describing public Reppo inspector inputs.
12 changes: 12 additions & 0 deletions examples/source-manifest/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Source manifest example

This directory contains synthetic examples for `schemas/source-manifest-v1.schema.json`.

A source manifest records the public sources that support an agentic-commerce artifact. It is intended for agent jobs, inspector outputs, compatibility checks, examples, and future reference services that need portable provenance without private runtime context.

The example manifest is intentionally small and public-only:

- no credentials;
- no wallet, account, budget, or transaction data;
- no local filesystem paths;
- no private communications or unpublished research.
52 changes: 52 additions & 0 deletions examples/source-manifest/reppo-public-api-manifest-v1.example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"schemaVersion": "1.0",
"manifestId": "example:reppo-public-api:2026-07-11",
"generatedAt": "2026-07-11T00:00:00Z",
"subject": {
"kind": "protocol-source",
"name": "Reppo public API inspector inputs",
"version": "v0.1.0",
"description": "Synthetic example manifest for public sources used by the read-only Reppo inspector."
},
"sources": [
{
"sourceId": "reppo-docs-api-reference",
"title": "Reppo API reference",
"url": "https://docs.reppo.xyz/",
"sourceType": "documentation",
"publisher": "Reppo",
"accessedAt": "2026-07-11T00:00:00Z",
"usage": {
"purpose": "schema-reference",
"derivedFacts": [
"The inspector should use public Reppo endpoints only.",
"Source URLs and fetch timing belong in the machine-readable output envelope."
],
"limitations": [
"Documentation can drift from live API behavior, so compatibility checks should record partial failures instead of fabricating replacement data."
]
}
},
{
"sourceId": "reppo-public-datanets-endpoint",
"title": "Reppo public datanet catalog endpoint",
"url": "https://api.reppo.xyz/public/subnets",
"sourceType": "api",
"publisher": "Reppo",
"accessedAt": "2026-07-11T00:00:00Z",
"usage": {
"purpose": "input",
"derivedFacts": [
"Datanet catalog responses are preserved under the inspector envelope data field.",
"The endpoint is queried read-only and does not require authentication."
],
"limitations": [
"Availability and response shape can change upstream."
]
}
}
],
"notes": [
"This example is synthetic and contains no credentials, private account data, or local runtime state."
]
}
4 changes: 3 additions & 1 deletion schemas/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@

- `inspector-envelope-v1.schema.json` — stable top-level contract for Reppo inspector output.
- `../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.

Versioned JSON schemas for ecosystem status, provenance, and agent-job results will live here.
Versioned JSON schemas for structured agent-job results and safety patterns will live here.
168 changes: 168 additions & 0 deletions schemas/source-manifest-v1.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://raw.githubusercontent.com/nccrypto/agentic-commerce-toolkit/main/schemas/source-manifest-v1.schema.json",
"title": "Agentic Commerce Source Manifest v1",
"description": "A portable manifest describing public sources used by an agentic-commerce job or dataset.",
"type": "object",
"additionalProperties": false,
"required": [
"schemaVersion",
"manifestId",
"generatedAt",
"subject",
"sources"
],
"properties": {
"schemaVersion": {"const": "1.0"},
"manifestId": {
"type": "string",
"pattern": "^[a-z0-9][a-z0-9._:-]{2,127}$",
"description": "Stable, non-secret identifier for this manifest."
},
"generatedAt": {"type": "string", "format": "date-time"},
"subject": {
"type": "object",
"additionalProperties": false,
"required": ["kind", "name"],
"properties": {
"kind": {
"type": "string",
"pattern": "^[a-z][a-z0-9._-]{1,63}$",
"description": "Public category for the thing being documented, such as protocol, dataset, inspector-output, or agent-job."
},
"name": {
"type": "string",
"minLength": 1,
"maxLength": 160
},
"version": {
"type": "string",
"minLength": 1,
"maxLength": 80
},
"description": {
"type": "string",
"minLength": 1,
"maxLength": 500
}
}
},
"sources": {
"type": "array",
"minItems": 1,
"maxItems": 100,
"items": {"$ref": "#/$defs/source"}
},
"notes": {
"type": "array",
"maxItems": 20,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 500
}
}
},
"$defs": {
"source": {
"type": "object",
"additionalProperties": false,
"required": [
"sourceId",
"title",
"url",
"sourceType",
"publisher",
"accessedAt",
"usage"
],
"properties": {
"sourceId": {
"type": "string",
"pattern": "^[a-z0-9][a-z0-9._:-]{1,127}$"
},
"title": {
"type": "string",
"minLength": 1,
"maxLength": 240
},
"url": {"type": "string", "format": "uri"},
"sourceType": {
"enum": [
"api",
"documentation",
"repository",
"schema",
"example",
"issue",
"release",
"article",
"other"
]
},
"publisher": {
"type": "string",
"minLength": 1,
"maxLength": 160
},
"accessedAt": {"type": "string", "format": "date-time"},
"retrievedAt": {"type": "string", "format": "date-time"},
"license": {
"type": "string",
"minLength": 1,
"maxLength": 120
},
"version": {
"type": "string",
"minLength": 1,
"maxLength": 120
},
"contentDigest": {
"type": "object",
"additionalProperties": false,
"required": ["algorithm", "value"],
"properties": {
"algorithm": {"enum": ["sha256"]},
"value": {"type": "string", "pattern": "^[a-f0-9]{64}$"}
}
},
"usage": {
"type": "object",
"additionalProperties": false,
"required": ["purpose", "derivedFacts"],
"properties": {
"purpose": {
"enum": [
"input",
"validation",
"compatibility-check",
"citation",
"fixture-basis",
"schema-reference",
"other"
]
},
"derivedFacts": {
"type": "array",
"maxItems": 20,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 300
}
},
"limitations": {
"type": "array",
"maxItems": 20,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 300
}
}
}
}
}
}
}
}
47 changes: 41 additions & 6 deletions tests/test_schema_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,54 @@


ROOT = Path(__file__).parents[1]
SCHEMA = ROOT / "schemas" / "inspector-envelope-v1.schema.json"
EXAMPLE = ROOT / "examples" / "reppo-inspector" / "datanets-envelope-v1.example.json"
INSPECTOR_SCHEMA = ROOT / "schemas" / "inspector-envelope-v1.schema.json"
INSPECTOR_EXAMPLE = ROOT / "examples" / "reppo-inspector" / "datanets-envelope-v1.example.json"
SOURCE_MANIFEST_SCHEMA = ROOT / "schemas" / "source-manifest-v1.schema.json"
SOURCE_MANIFEST_EXAMPLE = ROOT / "examples" / "source-manifest" / "reppo-public-api-manifest-v1.example.json"


def load_json(path):
return json.loads(path.read_text(encoding="utf-8"))


def validation_errors(schema_path, example_path):
validator = Draft202012Validator(load_json(schema_path), format_checker=FormatChecker())
example = load_json(example_path)
return sorted(validator.iter_errors(example), key=lambda error: list(error.path))


class SchemaContractTests(unittest.TestCase):
def assert_conforms(self, schema_path, example_path):
errors = validation_errors(schema_path, example_path)
self.assertEqual([], [error.message for error in errors])

def test_reppo_example_conforms_to_inspector_envelope_v1(self):
schema = json.loads(SCHEMA.read_text(encoding="utf-8"))
example = json.loads(EXAMPLE.read_text(encoding="utf-8"))
self.assert_conforms(INSPECTOR_SCHEMA, INSPECTOR_EXAMPLE)

def test_source_manifest_example_conforms_to_source_manifest_v1(self):
self.assert_conforms(SOURCE_MANIFEST_SCHEMA, SOURCE_MANIFEST_EXAMPLE)

def test_source_manifest_requires_public_source_records(self):
schema = load_json(SOURCE_MANIFEST_SCHEMA)
validator = Draft202012Validator(schema, format_checker=FormatChecker())
manifest = load_json(SOURCE_MANIFEST_EXAMPLE)
manifest["sources"] = []

errors = sorted(validator.iter_errors(example), key=lambda error: list(error.path))
errors = sorted(validator.iter_errors(manifest), key=lambda error: list(error.path))

self.assertEqual([], [error.message for error in errors])
self.assertIn("[] should be non-empty", [error.message for error in errors])

def test_source_manifest_rejects_unbounded_extra_fields(self):
schema = load_json(SOURCE_MANIFEST_SCHEMA)
validator = Draft202012Validator(schema, format_checker=FormatChecker())
manifest = load_json(SOURCE_MANIFEST_EXAMPLE)
manifest["privateRuntimeState"] = "not allowed"
manifest["sources"][0]["localPath"] = "runtime-cache"

messages = [error.message for error in validator.iter_errors(manifest)]

self.assertTrue(any("privateRuntimeState" in message for message in messages))
self.assertTrue(any("localPath" in message for message in messages))


if __name__ == "__main__":
Expand Down
Loading