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
6 changes: 3 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ The "resource-bound authority" release: delegations can now name *where* a scope

### Added — specification (implementation follows in the same release)

- **`resource_path` constraint** (SPEC §5.7.2, §5.7.3): binds a delegation to an opaque `resource_id` (exact byte equality, never dereferenced or normalized) and an optional `path_prefix` under segment-boundary matching — deliberately a prefix, not a glob. Absolute logical POSIX-style path model with dot-segments, backslashes, and empty interior segments rejected outright; percent-encoding does not exist in the path model, and post-verification path transformation is forbidden. NFC pre-normalization is the issuer's obligation; the verifier compares bytes exactly (a mixed-form pair fails closed; byte identity, not visual identity, is the boundary). Chain evaluation is conjunctive: a downstream cert can only narrow the upstream bound, and jointly unsatisfiable constraint sets — different resources, or same-resource prefixes that don't nest — must be rejected at issuance (decoders still accept them; verification fails closed).
- **`resource_path` constraint** (SPEC §5.7.2, §5.7.3): binds a delegation to an opaque `resource_id` (exact byte equality, never dereferenced or normalized) and an optional `path_prefix` under segment-boundary matching — deliberately a prefix, not a glob. Absolute logical POSIX-style path model with dot-segments, backslashes, and empty interior segments rejected outright; percent-encoding does not exist in the path model, and post-verification path transformation is forbidden. NFC pre-normalization is the issuer's obligation; the verifier compares bytes exactly (a mixed-form pair fails closed; byte identity, not visual identity, is the boundary). Chain evaluation is conjunctive: effective authority can stay the same or narrow but never widen (a child may carry a broader prefix on the same resource and still verify, gaining nothing because every upstream constraint still applies), and jointly unsatisfiable constraint sets — different resources, or same-resource prefixes that don't nest — must be rejected at issuance (decoders still accept them; verification fails closed).
- **Resource-identifier profiles** (SPEC §5.7.4, `docs/RESOURCE_PROFILES.md`): the shared recipes that make an opaque `resource_id` interoperable. Git profile v1 (repository identity — never a branch, commit, or checkout; renames and transfers fail closed) with known-answer and negative vectors. Profiles for platform-owned resources are authored by the platforms themselves and linked when published.
- **Extension-constraint `params`** (SPEC §5.7.1, §17.7): parameterized extension constraints are now representable in signed certificates under a restricted, cross-language-deterministic value model. Type-only extension constraints serialize exactly as before; existing signed certs remain byte-stable. Closes the wire-format limitation documented in alpha.15.
- **Input bounds** (SPEC §5.1): `MAX_PROOF_BUNDLE_BYTES` (128 KiB, enforced before parsing), `MAX_JSON_NESTING_DEPTH`, and per-cert scope/constraint count and length limits. Violations route to the existing `invalid` status.
Expand Down Expand Up @@ -102,7 +102,7 @@ The "integration readiness" release: everything an integrator needs to adopt the
### Changed — release process: no more direct pushes to main

- The single-step `make release` (which committed the version bump directly to main via a ruleset bypass) is removed. Releases are now two-phase: `make release-prepare VERSION=…` creates a `release/<version>` branch, bumps versions, runs the full cross-SDK gate, and opens a PR; after it merges through the normal path (CI + DCO), `make release-tag VERSION=…` verifies main carries the bump and pushes the coordinated tags. See `docs/RELEASES.md` §4.
- `release.sh` pushes the protocol tag on its own before the `sdk-*` tags: GitHub creates no push event when more than three tags arrive in one push, which had silently prevented the tag-triggered Release workflow from ever firing (§5.3.1).
- `release.sh` pushes the protocol tag on its own before the `sdk-*` tags: GitHub creates no push event when more than three tags arrive in one push, which had silently prevented the tag-triggered Release workflow from ever firing (see `docs/RELEASES.md` §5.3.1).
- `release-prepare` now stamps the `(unreleased)` changelog entry with the release date.

---
Expand Down Expand Up @@ -247,7 +247,7 @@ Total alpha.7 test additions: **134 tests**, all green; **59/59 canonical fixtur

### Spec changes

- **§5.7.2 VerifyOptions** — table extended with `Revocation`, `Policy`, `Audit`, `ConstraintEvaluators`, `PolicyVerdict`, `PolicySecret`, and `AnchorResolver` fields, with precedence rules between the legacy `IsRevoked` closure and the new `Revocation` provider.
- **§5.17 VerifyOptions** — table extended with `Revocation`, `Policy`, `Audit`, `ConstraintEvaluators`, `PolicyVerdict`, `PolicySecret`, and `AnchorResolver` fields, with precedence rules between the legacy `IsRevoked` closure and the new `Revocation` provider.
- **§17 (new section)** — Provider Interfaces, including:
- §17.0 conformance and wire-format invariance
- §17.1–§17.3 the three core providers
Expand Down
6 changes: 3 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ The flow:
• Go tests
• Test vectors regenerate byte-identical
• Release metadata stays in sync
TypeScript / Python / Rust SDK conformance (59 fixtures each)
SDK conformance across Go, TypeScript, Python, Rust, and C (79 fixtures each)
• DCO sign-off
6. Address review feedback. Every inline comment thread must be resolved.
7. If `main` moved while the PR was open: click "Update branch" — strict
Expand Down Expand Up @@ -83,7 +83,7 @@ npm install
npm test
```

All 59 fixtures must pass in every SDK. If a change breaks the TS conformance but passes Go, or vice versa, the implementations have drifted — fix the divergence before the PR is merged.
All 79 fixtures must pass in every SDK (Go, TypeScript, Python, Rust, C). If a change breaks the TS conformance but passes Go, or vice versa, the implementations have drifted — fix the divergence before the PR is merged.

## Canonical serialization

Expand All @@ -96,7 +96,7 @@ The canonicalizer in `crypto.go` (Go) and `sdks/typescript/src/canonical.ts` (TS
## Style

- **Go:** standard `go fmt`, idiomatic Go. No framework-style abstraction layers. Prefer plain structs and functions.
- **TypeScript:** strict mode (already enforced by `tsconfig.json`). No runtime dependencies beyond `@noble/ed25519` and `@noble/hashes`.
- **TypeScript:** strict mode (already enforced by `tsconfig.json`). No runtime dependencies beyond `@noble/ed25519`, `@noble/hashes`, and `@noble/post-quantum` (ML-DSA-65).
- **Documentation:** if a change affects protocol behavior, update `SPEC.md` and `docs/EXPLAINED.md` in the same PR.
- **Commit messages:** conventional commit prefixes preferred (`fix:`, `feat:`, `spec:`, `docs:`). The first line under 72 characters. Body wrapped to 72. A new commit, never `--amend --force-push` to shared branches.

Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Keep your IAM, OAuth, MCP, A2A, and policy engines — Ratify adds the missing p

**Quantum-safe by design.** Every signature is hybrid: Ed25519 + ML-DSA-65 (NIST FIPS 204). Both must verify. Bundles signed today remain unforgeable even when cryptographically-relevant quantum computers exist.

JSON wire format. No blockchain. No tokens. No central issuer. Open spec under CC-BY-4.0.
JSON wire format. No blockchain. No bearer tokens. No central issuer. Open spec under CC-BY-4.0.

**Status:** alpha — fixture bytes may change between pre-releases · reference implementation complete · 79 canonical test vectors · cross-language interop proven (Go + TypeScript + Python + Rust + C/C++) · Patent Pending.

Expand Down Expand Up @@ -347,6 +347,7 @@ Everything above describes a single delegate → present → verify round trip.
| **Transaction receipts** | A canonical `TransactionReceipt` where every party signs the same bytes (terms + sorted party set + transaction ID). Adding, removing, or altering any party invalidates every signature — no partial-valid state. | [§5.14](SPEC.md#514-transactionreceipt), [§6.4.7](SPEC.md#647-transactionreceiptsignable) |
| **Witness append-only log** | Signed `WitnessEntry` hash chain for tamper-evident audit logs. Any party can operate a witness. | [§5.12](SPEC.md#512-witnessentry), [§6.4.6](SPEC.md#646-witnessentrysignable) |
| **Key rotation statements** | `KeyRotationStatement` signed by both the old and new root keys, so auditors and registries can verify identity continuity across rotations. | [§5.15](SPEC.md#515-keyrotationstatement), [§6.4.4](SPEC.md#644-keyrotationsignable) |
| **Resource-bound authority** | The `resource_path` constraint scopes a delegation to one resource (an opaque `resource_id` compared byte-for-byte) and, optionally, to a `path_prefix` within it under segment-boundary matching, so a broad grant like `files:write` can be pinned to `/docs` of a single repository. Down a sub-delegation chain, effective authority can stay the same or narrow but never widen: a child may carry a broader prefix on the same resource and still verify, yet gains no authority because every upstream constraint still applies, so downstream escape is impossible by construction. This is the eighth canonical constraint type, alongside `geo_circle`, `geo_polygon`, `geo_bbox`, `time_window`, `max_speed_mps`, `max_amount`, and `max_rate`. | [§5.7.3](SPEC.md#573-the-resource_path-constraint), [§5.16](SPEC.md#516-verifiercontext) |

The [`docs/AGENT_TO_AGENT.md`](docs/AGENT_TO_AGENT.md) guide shows how these compose for agent-to-agent patterns (mutual authorization, sub-delegation, receipts), and [`docs/TRANSACTION_RECEIPTS.md`](docs/TRANSACTION_RECEIPTS.md) has the receipt envelope design rationale.

Expand Down Expand Up @@ -408,7 +409,7 @@ ratify-protocol/
├── types.go Data structures (DelegationCert, ProofBundle, …)
├── crypto.go Hybrid Ed25519 + ML-DSA-65 primitives + canonical JSON
├── scope.go Canonical 54-scope vocabulary + intersect/expand
├── constraints.go Geo, time, version constraints
├── constraints.go Geo, time, speed, amount, rate, and resource-path constraints
├── verify.go The verifier algorithm
├── streamed_verify.go SessionToken fast path — multi-turn verification (§5.13)
├── receipt_verify.go TransactionReceipt verification (§5.14)
Expand Down
2 changes: 1 addition & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Once v1.0.0 stable is released, the support policy will specify which major/mino

- Vulnerabilities in third-party dependencies that are already known and patched upstream. Report to the dependency, then open a PR here to bump the version.
- Operational issues at any Identities AI-hosted service (Registry, Verify API). Those are reported via `security@identities.ai` with the service name in the subject line — a separate handling path.
- Attacks against out-of-protocol concerns listed in `docs/EXPLAINED.md` §5.2 (endpoint malware, social engineering of delegators, etc.). These are real risks but are not protocol defects.
- Attacks against out-of-protocol concerns listed in `SPEC.md` §15.0 under "Out of scope" (private key theft at endpoints, social engineering of principals, agent-platform compromise, etc.). These are real risks but are not protocol defects.

## Responsible disclosure examples

Expand Down
16 changes: 8 additions & 8 deletions SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ A verifier accepts a `ProofBundle` if and only if every one of the following che
5. **Revocation** — none of the certs appear in the signed revocation list.
6. **Constraint evaluation** — every `Constraint` on every cert in the chain evaluates true against the caller-supplied `VerifierContext`. A constraint whose required context field is absent from the context fails with `constraint_unverifiable`. An unknown constraint `type` fails with `constraint_unknown`.

Any failure causes immediate rejection. The failure type (`expired`, `revoked`, `invalid`, `unauthorized`, `constraint_denied`, `constraint_unverifiable`, `constraint_unknown`) is returned deterministically. A single component-signature failure (e.g. Ed25519 passes but ML-DSA-65 fails, or vice versa) fails the whole signature — fail-closed is the v1 semantics.
Any failure causes immediate rejection. The failure type (`expired`, `revoked`, `scope_denied`, `constraint_denied`, `constraint_unverifiable`, `constraint_unknown`, `invalid_scope`, `delegation_not_authorized`, `invalid`) is returned deterministically. A single component-signature failure (e.g. Ed25519 passes but ML-DSA-65 fails, or vice versa) fails the whole signature — fail-closed is the v1 semantics.

---

Expand Down Expand Up @@ -367,7 +367,7 @@ For failures with their own status, `error_reason` begins with the status name f
}
```

`RevocationPush` is a v1.1 optional signed notification that a revocation-list issuer sends to subscribed verifiers in real time (ROADMAP §2.4). The payload carries a delta — cert IDs added to the revocation list since the previous push — and is hybrid-signed by the issuer so a verifier can trust it without re-fetching the full list.
`RevocationPush` is a v1.1 optional signed notification that a revocation-list issuer sends to subscribed verifiers in real time (ROADMAP, Continuous real-time interactions). The payload carries a delta — cert IDs added to the revocation list since the previous push — and is hybrid-signed by the issuer so a verifier can trust it without re-fetching the full list.

`seq_no` is monotonically increasing per issuer (first push is 1). Receivers MUST detect gaps (missed pushes) and fall back to a full revocation-list fetch when a gap is detected. `entries` is always serialized as an array (`[]` when empty), never as `null`.

Expand All @@ -385,7 +385,7 @@ The push subscription endpoint (WebSocket, SSE, gRPC stream, etc.) is an operato
}
```

`WitnessEntry` is a v1.1 spec-defined element in a hash-chain append-only log (ROADMAP §3.2). Any party may operate a Witness: Identities AI, an enterprise's own audit system, a third-party notary, or a blockchain-anchored system. Multiple witnesses MAY independently log the same events (redundancy).
`WitnessEntry` is a v1.1 spec-defined element in a hash-chain append-only log (ROADMAP, Tamper-evident transaction streams). Any party may operate a Witness: Identities AI, an enterprise's own audit system, a third-party notary, or a blockchain-anchored system. Multiple witnesses MAY independently log the same events (redundancy).

v1.1 defines the shape and the signing semantics only. Operating a scalable witness is an implementation/product concern; the spec does not mandate deployment topology, storage backend, or consistency model. The fixture `witness_entry_valid` proves cross-SDK byte-identicality of the signable and signature.

Expand All @@ -406,7 +406,7 @@ v1.1 defines the shape and the signing semantics only. Operating a scalable witn
}
```

`SessionToken` is a v1.1 backward-compatible signed credential that caches the result of a full chain verification. After a successful `Verify(ProofBundle)`, the verifier MAY issue a SessionToken binding the verified chain to its session. Subsequent turns in the same session present the token plus a fresh `ChallengeSig`; the verifier checks the token's HMAC and challenge signature without re-verifying the delegation chain. This is the session cert cache of ROADMAP §2.3.
`SessionToken` is a v1.1 backward-compatible signed credential that caches the result of a full chain verification. After a successful `Verify(ProofBundle)`, the verifier MAY issue a SessionToken binding the verified chain to its session. Subsequent turns in the same session present the token plus a fresh `ChallengeSig`; the verifier checks the token's HMAC and challenge signature without re-verifying the delegation chain. This is the session cert cache of ROADMAP's Continuous real-time interactions.

`chain_hash` is the 32-byte SHA-256 of the concatenated `DelegationSignBytes` of each cert in the verified chain. Any change to any cert in the chain changes `chain_hash`, invalidating tokens issued against the old chain.

Expand Down Expand Up @@ -466,7 +466,7 @@ ReceiptPartySignature:
"signature": <HybridSignature>
```

`TransactionReceipt` is a v1.1 canonical multi-party transaction envelope (ROADMAP §3.1 / §3.3). Ratify does not interpret `terms_canonical_json` — the application owns the business terms schema. `terms_schema_uri` identifies which schema a specialized verifier dispatches on. Ratify guarantees envelope atomicity and party signatures.
`TransactionReceipt` is a v1.1 canonical multi-party transaction envelope (ROADMAP, Tamper-evident transaction streams). Ratify does not interpret `terms_canonical_json` — the application owns the business terms schema. `terms_schema_uri` identifies which schema a specialized verifier dispatches on. Ratify guarantees envelope atomicity and party signatures.

The signable bytes for every party's signature (§6.4.7) include `version`, `transaction_id`, `created_at`, `terms_schema_uri`, `terms_canonical_json`, and the full sorted `parties` set (sorted lex by `party_id`; each party projected to `{agent_id, agent_pub_key, party_id, role}` — `proof_bundle` is excluded since it is verified independently). Because every party's signature covers the same sorted party set, adding, removing, or altering any party invalidates every existing signature — there is no partial-valid receipt state.

Expand Down Expand Up @@ -893,7 +893,7 @@ Pure ML-DSA-65 would be a single point of cryptographic failure against future a

54 canonical scope strings organized by domain, plus 14 wildcards, plus one extension pattern (`custom:…`) for application-specific scopes outside the canonical vocabulary. Implementations MUST reject scopes that are not canonical, not a wildcard, and not a `custom:` extension — at issuance via `ValidateScopes`, and at verification: the verifier rejects any cert granting invalid vocabulary with `invalid_scope` (§5.9, §10 step 7.a2) before any effective-scope arithmetic.

The vocabulary covers both software agents (meetings, comms, files, transactions, execution, generation) and embodied agents (physical actions, robots, drones, vehicles, infrastructure, generic actuation). Ratify is channel-agnostic by construction (§3.5, §3.6) — the same cert/bundle/verify semantics authorize a software agent in a video meeting and a drone at a delivery address.
The vocabulary covers both software agents (meetings, comms, files, transactions, execution, generation) and embodied agents (physical actions, robots, drones, vehicles, infrastructure, generic actuation). Ratify is channel-agnostic by construction (§3 principle 6) — the same cert/bundle/verify semantics authorize a software agent in a video meeting and a drone at a delivery address.

### 9.1 Canonical scopes

Expand Down Expand Up @@ -1701,7 +1701,7 @@ The verify function:
- Returns `"policy_verdict_denied: ..."` on cached **deny** (MAC valid but `allow=false`).
- Returns any other error if the verdict is unusable (bad MAC, expired, scope mismatch, etc).

**Verifier fast-path semantics (§5.7.2):** when `VerifyOptions.PolicyVerdict` and `VerifyOptions.PolicySecret` are both set, the verifier consults the verdict BEFORE the `Policy` provider:
**Verifier fast-path semantics (§5.17):** when `VerifyOptions.PolicyVerdict` and `VerifyOptions.PolicySecret` are both set, the verifier consults the verdict BEFORE the `Policy` provider:
- Cached allow → live policy is **not called**; return success.
- Cached deny → live policy is **not called**; return `scope_denied`.
- Verdict unusable (expired / wrong MAC / scope mismatch) → fall through to live `Policy` provider. A stale verdict MUST NOT cause a verification failure on its own.
Expand All @@ -1716,7 +1716,7 @@ The `ConstraintEvaluator` interface is the pluggable layer: callers register eva

1. Built-in evaluators handle the universal types (always, by the SDK directly).
2. For any type the built-in evaluators do not recognize, the registry is consulted.
3. If no entry matches, the verifier fails closed with `identity_status="constraint_unknown"` (per §5.16).
3. If no entry matches, the verifier fails closed with `identity_status="constraint_unknown"` (per §5.9).

**Interface:**

Expand Down
Loading