diff --git a/.github/actions/verify-goldseal/action.yml b/.github/actions/verify-korgcert/action.yml similarity index 85% rename from .github/actions/verify-goldseal/action.yml rename to .github/actions/verify-korgcert/action.yml index 9c8d0a6..b23e976 100644 --- a/.github/actions/verify-goldseal/action.yml +++ b/.github/actions/verify-korgcert/action.yml @@ -1,9 +1,9 @@ -name: "Verify korg Gold Seal" +name: "Verify korg Certificate" description: >- - Independently verify goldseal@v1 certificates or korg-ledger@v1 sessions in CI β€” + Independently verify korgcert@v1 certificates or korg-ledger@v1 sessions in CI β€” hash chain, causal DAG, re-derived summary, and the Ed25519 issuer seal. Zero trust in whoever produced the artifact; no network. Renders a rich report into - the job summary and (on pull_request) posts a sticky Gold Seal comment. Fails + the job summary and (on pull_request) posts a sticky Certificate comment. Fails the job on any tampered or unverifiable file. author: "Korg ecosystem maintainers" branding: @@ -19,7 +19,7 @@ inputs: required: false default: "" comment: - description: "Post/update a sticky Gold Seal comment on the PR (pull_request events). 'true'/'false'." + description: "Post/update a sticky Certificate comment on the PR (pull_request events). 'true'/'false'." required: false default: "true" github-token: diff --git a/.github/actions/verify-goldseal/report.mjs b/.github/actions/verify-korgcert/report.mjs similarity index 93% rename from .github/actions/verify-goldseal/report.mjs rename to .github/actions/verify-korgcert/report.mjs index 1187760..33af423 100644 --- a/.github/actions/verify-goldseal/report.mjs +++ b/.github/actions/verify-korgcert/report.mjs @@ -1,4 +1,4 @@ -// Verify korg Gold Seals / ledgers, render a rich Markdown report into the job +// Verify korg Certificates / ledgers, render a rich Markdown report into the job // summary, and (on a pull_request) upsert a single sticky PR comment. Reuses the // repo's conformance-pinned verify.mjs β€” zero extra deps. Exit 0 iff all valid. import { appendFileSync, readFileSync } from "node:fs"; @@ -49,7 +49,7 @@ for (const f of files) { const allValid = results.every((r) => r.verdict && r.verdict.valid); const L = []; -L.push(allValid ? "## πŸ›‘οΈ βœ… Gold Seal verified" : "## πŸ›‘οΈ ❌ Gold Seal verification FAILED"); +L.push(allValid ? "## πŸ›‘οΈ βœ… Certificate verified" : "## πŸ›‘οΈ ❌ Certificate verification FAILED"); L.push(""); L.push("_Independently verified β€” zero trust in the tool that produced it._"); L.push(""); @@ -64,7 +64,7 @@ for (const r of results) { const e = r.env || {}; L.push(`### ${v.valid ? "βœ…" : "❌"} \`${r.f}\` β€” ${v.kind} ${v.valid ? "VALID" : "INVALID"}`); L.push(""); - if (v.kind === "goldseal") { + if (v.kind === "korgcert") { const s = e.summary || {}; // Every field below is seal-derived (untrusted) β†’ esc() before interpolation, // and NOT wrapped in backticks (esc neutralizes them, which would break a span). @@ -143,10 +143,10 @@ if (wantComment && token && repo && pr) { headers: h, body: JSON.stringify({ body }), }); - console.log(`korg: updated sticky Gold Seal comment ${existing.id} on PR #${pr}`); + console.log(`korg: updated sticky Certificate comment ${existing.id} on PR #${pr}`); } else { await fetch(issueComments, { method: "POST", headers: h, body: JSON.stringify({ body }) }); - console.log(`korg: posted Gold Seal comment on PR #${pr}`); + console.log(`korg: posted Certificate comment on PR #${pr}`); } } catch (e) { console.log(`::warning::korg could not upsert the PR comment: ${e.message}`); diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 70a7697..1f8412d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,7 +4,7 @@ on: push: branches: [main, develop, "feat/**"] pull_request: - # Run on every PR regardless of base, so stacked PRs (e.g. goldseal β†’ phase2) + # Run on every PR regardless of base, so stacked PRs (e.g. korgcert β†’ phase2) # get the full cargo + pytest + cross-language conformance gate too. env: @@ -53,7 +53,7 @@ jobs: run: cargo build --workspace --verbose # --workspace tests every member (root korg package + korg-verify/-ledger - # goldseal+fuzz suites + korg-registry journal/rewind, etc.). The root being + # korgcert+fuzz suites + korg-registry journal/rewind, etc.). The root being # both a [workspace] and a [package] means a bare `cargo test` would only run # the root package β€” verified `cargo test --workspace` is green. - name: Run tests (workspace) @@ -122,7 +122,7 @@ jobs: - name: Install test deps # pytest + the optional-extra deps so the FULL suite runs in CI rather # than skipping: hypothesis (property tests) and cryptography (Ed25519 - # signing / goldseal mint+verify / korg-seal). + # signing / korgcert mint+verify / korg-seal). run: python3 -m pip install --quiet pytest hypothesis cryptography - name: Producer + capture + setup + reader test suites diff --git a/.github/workflows/goldseal-demo.yml b/.github/workflows/korgcert-demo.yml similarity index 63% rename from .github/workflows/goldseal-demo.yml rename to .github/workflows/korgcert-demo.yml index 1291fe8..9072395 100644 --- a/.github/workflows/goldseal-demo.yml +++ b/.github/workflows/korgcert-demo.yml @@ -1,19 +1,19 @@ -name: Gold Seal demo β€” mint β†’ verify in CI +name: Certificate demo β€” mint β†’ verify in CI # Dogfoods the whole loop in GitHub Actions: build a session ledger, mint a -# goldseal@v1 with korg-seal, verify it with the independent verifier (pinned to +# korgcert@v1 with korg-seal, verify it with the independent verifier (pinned to # the issuer), and prove a tampered copy is rejected. This is what "verified # agent work in CI" looks like. on: workflow_dispatch: - # On PRs: mint + verify and post a sticky Gold Seal check comment (the demo). + # On PRs: mint + verify and post a sticky Certificate check comment (the demo). pull_request: paths: - "adapters/korg-seal/**" - "adapters/korg-ledger-py/**" - "spec/korg-ledger-v1/**" - - ".github/actions/verify-goldseal/**" - - ".github/workflows/goldseal-demo.yml" + - ".github/actions/verify-korgcert/**" + - ".github/workflows/korgcert-demo.yml" # On main (post-merge): dogfood without commenting. push: branches: [main] @@ -21,12 +21,12 @@ on: - "adapters/korg-seal/**" - "adapters/korg-ledger-py/**" - "spec/korg-ledger-v1/**" - - ".github/actions/verify-goldseal/**" - - ".github/workflows/goldseal-demo.yml" + - ".github/actions/verify-korgcert/**" + - ".github/workflows/korgcert-demo.yml" permissions: contents: read - pull-requests: write # so the Action can post the sticky Gold Seal comment + pull-requests: write # so the Action can post the sticky Certificate comment jobs: mint-and-verify: @@ -48,41 +48,41 @@ jobs: - name: Build a session ledger run: python spec/korg-ledger-v1/tools/make_demo_session.py demo-session.jsonl - - name: Mint a Gold Seal (capture the issuer key) + - name: Mint a Certificate (capture the issuer key) id: mint run: | python -m korg_seal mint demo-session.jsonl \ --claim "CI demo: agent added a /healthz endpoint with a passing test" \ - --key issuer.ed25519 -o demo.goldseal.json + --key issuer.ed25519 -o demo.korgcert.json echo "pub=$(python -m korg_seal key --key issuer.ed25519)" >> "$GITHUB_OUTPUT" - - name: Verify the Gold Seal β€” pinned to the issuer (must PASS) - uses: ./.github/actions/verify-goldseal + - name: Verify the Certificate β€” pinned to the issuer (must PASS) + uses: ./.github/actions/verify-korgcert with: - path: demo.goldseal.json + path: demo.korgcert.json pin-pubkey: ${{ steps.mint.outputs.pub }} - name: A tampered seal must FAIL (negative test) run: | python - <<'PY' import json - e = json.load(open("demo.goldseal.json")) + e = json.load(open("demo.korgcert.json")) e["claim"] = "CI demo: agent fixed an unrelated critical security bug" # move the claim - json.dump(e, open("tampered.goldseal.json", "w")) + json.dump(e, open("tampered.korgcert.json", "w")) PY - if node spec/korg-ledger-v1/js/verify.mjs tampered.goldseal.json; then - echo "::error::a tampered Gold Seal verified β€” this must never happen" + if node spec/korg-ledger-v1/js/verify.mjs tampered.korgcert.json; then + echo "::error::a tampered Certificate verified β€” this must never happen" exit 1 fi - echo "βœ“ tampered Gold Seal correctly rejected" + echo "βœ“ tampered Certificate correctly rejected" - name: Job summary if: always() run: | { - echo "### πŸ›‘οΈ korg Gold Seal β€” verified in CI" + echo "### πŸ›‘οΈ korg Certificate β€” verified in CI" echo "" - echo "Minted a Gold Seal from a session ledger and verified it with the independent" + echo "Minted a Certificate from a session ledger and verified it with the independent" echo "verifier, pinned to issuer \`${{ steps.mint.outputs.pub }}\`. A tampered copy was" echo "correctly rejected. Re-verify in a browser: " } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index a6f2a56..1d30526 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -1,14 +1,14 @@ name: Deploy verifier to GitHub Pages # Publishes the zero-install, client-side verifiers + the korg-ledger@v1 / -# goldseal@v1 specs. The Pages root is spec/korg-ledger-v1/, so: +# korgcert@v1 specs. The Pages root is spec/korg-ledger-v1/, so: # / β†’ landing page (index.html) -# /web/seal.html β†’ the Gold Seal verifier +# /web/seal.html β†’ the Certificate verifier # /web/index.html β†’ the raw-ledger verifier # /js/verify.mjs β†’ the conformance-pinned engine the pages import on: push: - branches: [main, feat/goldseal-certificate] + branches: [main, feat/korgcert-certificate] paths: ["spec/korg-ledger-v1/**", ".github/workflows/pages.yml"] workflow_dispatch: diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 54eca17..b0069b4 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -152,7 +152,7 @@ Korg maintains a tamper-proof audit trail of every significant action using a cr Beyond the in-process provenance chain above, Korg ships a set of independently-verifiable, "flight-recorder" capabilities that let third parties check an agent's work without trusting Korg itself: - **`korg-ledger@v1`**: A frozen, hash-chained, HLC-ordered, tamper-evident event ledger with cross-language conformance (Rust/Python/JS). Each event carries an Ed25519 per-event signature, and the ledger supports git-tip structural anchoring for offline structural verification. -- **Gold Seal (`goldseal@v1`)**: A public, independently-verifiable certificate of agent work, produced by the `korg-seal` CLI (mint/anchor/resolve/verify) and checkable by three conformant verifiers (`korg-verify` in Rust, plus Python and JS) as well as zero-install in-browser verifiers hosted on GitHub Pages. +- **Certificate (`korgcert@v1`)**: A public, independently-verifiable certificate of agent work, produced by the `korg-seal` CLI (mint/anchor/resolve/verify) and checkable by three conformant verifiers (`korg-verify` in Rust, plus Python and JS) as well as zero-install in-browser verifiers hosted on GitHub Pages. - **Honest `korg run-once` pipeline**: Runs a real patch through a real `cargo check` and attests a mutation count that *equals* the real `git diff` (file count + changed paths). When it cannot produce a real result, it reports an honest null rather than fabricating one. - **Provider model**: The default is a hermetic `DeterministicProvider` (fixture-only); `--provider ollama` runs a live local model on arbitrary tasks. diff --git a/CHANGELOG.md b/CHANGELOG.md index d8d7bf8..bcc8208 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,12 +20,12 @@ versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html). A fan-out review across every component, with each finding refuted-by-default by an independent verifier, surfaced **15 confirmed real bugs** (9 false positives filtered out). All fixed, with regression tests + the differential fuzzer extended to lock them: -- **CRITICAL β€” receipt signature forge (empty message).** `verify_tip_sig`/`verifyTipSig` verified an Ed25519 signature over a **0-byte message** when a receipt omitted its `tip`, letting an attacker mint a "validly signed" receipt over arbitrary events with their own key (reproduced empirically). Fixed in Rust + JS: the tip message must be exactly 32 bytes, and `verify_receipt`/`verifyReceipt` now **fail closed** when a signature is present but there's no tip, and reject zero-event receipts. (goldseal@v1 was not affected β€” its seal signs the canonical header, not a bare tip.) +- **CRITICAL β€” receipt signature forge (empty message).** `verify_tip_sig`/`verifyTipSig` verified an Ed25519 signature over a **0-byte message** when a receipt omitted its `tip`, letting an attacker mint a "validly signed" receipt over arbitrary events with their own key (reproduced empirically). Fixed in Rust + JS: the tip message must be exactly 32 bytes, and `verify_receipt`/`verifyReceipt` now **fail closed** when a signature is present but there's no tip, and reject zero-event receipts. (korgcert@v1 was not affected β€” its seal signs the canonical header, not a bare tip.) - **CRITICAL β€” JS canonicalization key sort.** `verify.mjs` sorted object keys by UTF-16 code **unit**, diverging from Python/Rust (code **point**) on astral-plane keys β†’ opposite VALID/INVALID verdicts on the same chain. Fixed with a code-point comparator (also applied to `deriveSummary`'s agent/file sorts). - **Cross-impl number domain.** Unified across Rust/Python/JS: integers must be within Β±(2β΅Β³βˆ’1) (JS `Number` loses precision beyond that) β€” now **rejected by all three**; Python `canonicalize` uses `allow_nan=False` (NaN/Infinity emitted non-standard JSON tokens Rust/JS couldn't parse). Finite floats are documented out of scope (SPEC Β§2) and the JS verifier now degrades gracefully (reports the event unverifiable instead of crashing) to keep capture of float-bearing tool args working. New `korg_ledger::canon_domain_error` + Python `_reject_out_of_domain`. - **Producer/tooling fixes:** the ledger writer now **fails loud on mid-file corruption** (was silently returning a stale tip β†’ forked chain; torn final line still tolerated); `korg-seal anchor` **preserves existing anchors** when re-anchoring (was dropping prior git-tip witnesses); `parse_github_repo` uses an **exact host match** (a suffix test accepted `notgithub.com/…` as the witness repo); `korg-seal mint --allow-unverified` now **surfaces the discarded chain errors** instead of sealing silently. -- **CI hardening:** the `verify-goldseal` PR-comment `report.mjs` now neutralizes Markdown/HTML in every seal-derived field (a crafted `claim`/path could inject into the privileged comment), and only updates a **Bot-authored** sticky comment whose trailing line is the marker (was a bare substring match). -- **Robustness:** the verifiers no longer crash on a `null`/non-object event (Python + JS guards across `verify_chain`/`verify_dag`/`verify_anchors`/`derive_summary`/`verify_structure`/`verify_seal`/`verifyGoldSeal`). +- **CI hardening:** the `verify-korgcert` PR-comment `report.mjs` now neutralizes Markdown/HTML in every seal-derived field (a crafted `claim`/path could inject into the privileged comment), and only updates a **Bot-authored** sticky comment whose trailing line is the marker (was a bare substring match). +- **Robustness:** the verifiers no longer crash on a `null`/non-object event (Python + JS guards across `verify_chain`/`verify_dag`/`verify_anchors`/`derive_summary`/`verify_structure`/`verify_seal`/`verifyKorgCert`). Gate: 41 Rust tests (incl. forge + proptest), 215 Python tests, JS conformance (incl. fuzz + receipt-forge regression), differential fuzz across 27 adversarial + edge-valid cases β€” **0 divergences**. @@ -49,12 +49,12 @@ Gate after second pass: 13 Rust test binaries, **232 Python tests**, JS conforma Gate after third pass: 14 Rust test binaries, **235 Python tests** (incl. NaN/Inf, Stop-flush, concurrency, receipt-domain regressions), JS conformance, differential fuzz 0 divergences. Three adversarial passes, **32 real bugs fixed** β€” the iterative fix-audit caught regressions in both prior passes' fixes. ### Added -- **Adversarial hardening of the verifiers β€” fuzzed + differentially tested across all three impls.** Property-based fuzzing of the goldseal verifiers proves two security invariants: they never crash on hostile input, and never accept junk or any single-character hash/seal-signature flip. The fuzzers **found and fixed two real robustness bugs** β€” the Python and JS verifiers crashed (`AttributeError` / `TypeError`) on a `null`/non-object event; both now degrade gracefully to "invalid" like Rust always did (guards added across `verify_chain`/`verify_dag`/`verify_anchors`/`derive_summary`/`verify_structure`/`verify_seal` and the JS equivalents). Coverage: Python `test_goldseal_properties.py` (Hypothesis, ~1700 examples), Rust `crates/korg-verify/tests/fuzz.rs` (proptest, 5 properties), JS fuzz block in `conformance.mjs`. **Differential fuzzer** `spec/korg-ledger-v1/tools/diff_fuzz.py` runs 24 adversarial mutations of a Gold Seal through the Rust, Python, and JS verifiers and asserts all three return the **same** verdict (a divergence = a conformance bug) β€” **0 divergences**; gated in CI (Build & Test job). 211 Python tests. -- **Time-travel session explorer** ([`spec/korg-ledger-v1/web/explore.html`](spec/korg-ledger-v1/web/explore.html)) β€” a zero-install, client-side replay of any korg-ledger@v1 session or Gold Seal. Drop a ledger/seal and scrub the timeline: the **cumulative state** (agents, tools, files touched) is re-derived live over the slice up to the playhead, so you watch what the agent did evolve step by step, while the hash-chain verifies under every frame. A tampered chain lights up red and auto-jumps the playhead to the exact break; a Gold Seal additionally shows its claim + signer. Play/pause, scrub, click an event for full args/result. Same Web Crypto engine as the verifiers; linked from the landing page (a third card). Verified in a real browser across session / tampered / Gold Seal inputs. -- **Trusted *time* β€” `korg-seal anchor` + `korg-seal resolve` close the provenance triad (what + who + WHEN).** A Gold Seal proves *what* (re-derived summary) and *who* (Ed25519 seal) offline; it could not prove *when*. The new commands do: `korg-seal anchor --repo --commit ` binds a `git-tip` time anchor and re-signs (post-hoc flow: mint β†’ publish/commit β†’ anchor); `korg-seal resolve ` performs the one network step β€” fetches each anchor's public commit (stdlib `urllib`, GitHub API) and confirms it *introduced* the anchored `entry_hash`, yielding a "the chain existed no later than ``" bound. A public commit is immutable once mirrored, so an owner who rewrote the chain would have to force-push the witness (detectable). **Demoed live against the real repo:** the committed fixture's tip `bd8389e3…` is genuinely witnessed by public commit `0e566b0` (committed `2026-06-14T06:01:28Z`); `korg-seal resolve` confirms it over the live GitHub API, and the anchored seal still verifies offline. The resolver is injectable-fetcher-based so it's unit-tested hermetically (witnessed / not-witnessed / commit-404 / repo-URL parsing) + the anchor re-bind roundtrip. GOLDSEAL.md Β§6/Β§8 + positioning updated (time is now an explicit opt-in network step, not a gap). +5 korg-seal tests (14 total). -- **Verify a Gold Seal anywhere β€” hosted, in CI, and from the terminal.** The trust layer is now ubiquitous: (1) **GitHub Pages** publishes the zero-install browser verifiers + specs at (landing page, the Gold Seal verifier, and the raw-ledger verifier; `.github/workflows/pages.yml` deploys `spec/korg-ledger-v1/`). (2) A **`verify-goldseal` GitHub Action** (`.github/actions/verify-goldseal`) verifies seals/ledgers in CI β€” `uses: New1Direction/korg/.github/actions/verify-goldseal@…` with an optional `pin-pubkey`, failing the job on any tampered/unpinned artifact. It renders a **rich report** (a `report.mjs` reusing verify.mjs) into the job summary and, on a `pull_request`, **upserts a sticky PR comment** with the re-derived attestation inline β€” claim Β· who (issuer) Β· what (events/tools/files) Β· when (anchor) Β· integrity β€” so "verified agent work" shows up right on the PR. (3) A **dogfooding demo workflow** (`.github/workflows/goldseal-demo.yml`) mints a Gold Seal from a session ledger with `korg-seal`, verifies it (pinned to the issuer) via the Action, and proves a tampered copy is rejected β€” "verified agent work in CI". (4) The npm verifier (`@korgg/ledger-verify`) bumped to 0.2.0 β€” its `npx` CLI now verifies `goldseal@v1` too. The Gold Seal verifier's badge now links to the hosted page, and `korg-seal mint` prints the live verify URL. -- **`goldseal@v1` β€” the public, independently-verifiable certificate layer (the "Gold Seal").** A portable, signed certificate of an AI-agent session that anyone re-verifies offline with zero trust in the issuer. A strict superset of `korgex-receipt@v1`: it embeds the full event chain + an Ed25519 issuer seal + a human-legible summary that is **re-derived from the events at verify time**, so the "files touched / tools used / steps" a person actually reads *cannot lie* (the legacy tip-signature left the summary unprotected; the seal signs claim + summary + tip together). Three conformant implementations: Python (`korg_ledger.goldseal` stdlib derivation + `korg_ledger.signing.mint_seal`/`verify_seal`), Rust (`korg_verify::verify_goldseal` + the `korg-verify` binary, which renders the attestation), and JS (`verify.mjs` `verifyGoldSeal`/`deriveSummary`). Cross-impl proof: a frozen fixture (`crates/korg-verify/tests/fixtures/goldseal-v1.json`) **minted by Python, verified byte-identically by Rust and JS** (seed `[42;32]`, deterministic re-mint). Adversarially tested in all three: a lying summary, a moved claim, a tampered event, a stripped seal (a downgrade β€” fails), and a wrong pinned issuer key are all rejected. Adds read-time `verify_dag` to the Python `korg_ledger` package (it previously enforced causality only at write time). Spec: [`GOLDSEAL.md`](spec/korg-ledger-v1/GOLDSEAL.md). Headline artifact: a zero-install in-browser Gold Seal verifier ([`spec/korg-ledger-v1/web/seal.html`](spec/korg-ledger-v1/web/seal.html)) β€” drop a seal and watch the summary re-derive (and the gold seal crack red when tampered), entirely client-side via Web Crypto. Positioning: [`docs/goldseal/POSITIONING.md`](docs/goldseal/POSITIONING.md). **Anchors are bound into the seal** (the signed header includes `anchors`, so an anchor cannot be stripped, added, or forged β€” while staying structurally chain-bound), removing the one documented limit; the only remaining external step is the *network* resolution of a git-tip anchor for trusted time. 191 Python tests + 7 Rust goldseal tests + JS conformance (incl. bound-anchor) check. -- **`adapters/korg-seal/` v0.1.0 β€” the producer-side Gold Seal minter (closes the captureβ†’mintβ†’verify loop).** `korg-seal mint --claim "..."` turns a captured korg-ledger@v1 session into a signed `goldseal@v1` certificate; `korg-seal verify` / `korg-seal key` round it out. Manages a local Ed25519 issuer key at `~/.korg/issuer.ed25519` (`0600`, generated on first use) whose public half is the issuer identity a relying party pins. **Refuses to seal a chain that does not verify** (no Gold Seal on a tampered history). Reuses the conformant `korg_ledger.goldseal`/`signing` cores; verification stays the dependency-light Rust/JS/browser path. Proven end-to-end: a freshly-keyed CLI-minted seal verifies VALID under the independent Rust `korg-verify` binary AND `verify.mjs`. 9 tests (key lifecycle + 0600 perms, mint, broken-chain refusal, determinism, full CLI mintβ†’verifyβ†’pin roundtrip). `cryptography`-gated (CI installs only pytest, so they skip there; cross-impl agreement is gated by the verifiers). +- **Adversarial hardening of the verifiers β€” fuzzed + differentially tested across all three impls.** Property-based fuzzing of the korgcert verifiers proves two security invariants: they never crash on hostile input, and never accept junk or any single-character hash/seal-signature flip. The fuzzers **found and fixed two real robustness bugs** β€” the Python and JS verifiers crashed (`AttributeError` / `TypeError`) on a `null`/non-object event; both now degrade gracefully to "invalid" like Rust always did (guards added across `verify_chain`/`verify_dag`/`verify_anchors`/`derive_summary`/`verify_structure`/`verify_seal` and the JS equivalents). Coverage: Python `test_korgcert_properties.py` (Hypothesis, ~1700 examples), Rust `crates/korg-verify/tests/fuzz.rs` (proptest, 5 properties), JS fuzz block in `conformance.mjs`. **Differential fuzzer** `spec/korg-ledger-v1/tools/diff_fuzz.py` runs 24 adversarial mutations of a Certificate through the Rust, Python, and JS verifiers and asserts all three return the **same** verdict (a divergence = a conformance bug) β€” **0 divergences**; gated in CI (Build & Test job). 211 Python tests. +- **Time-travel session explorer** ([`spec/korg-ledger-v1/web/explore.html`](spec/korg-ledger-v1/web/explore.html)) β€” a zero-install, client-side replay of any korg-ledger@v1 session or Certificate. Drop a ledger/seal and scrub the timeline: the **cumulative state** (agents, tools, files touched) is re-derived live over the slice up to the playhead, so you watch what the agent did evolve step by step, while the hash-chain verifies under every frame. A tampered chain lights up red and auto-jumps the playhead to the exact break; a Certificate additionally shows its claim + signer. Play/pause, scrub, click an event for full args/result. Same Web Crypto engine as the verifiers; linked from the landing page (a third card). Verified in a real browser across session / tampered / Certificate inputs. +- **Trusted *time* β€” `korg-seal anchor` + `korg-seal resolve` close the provenance triad (what + who + WHEN).** A Certificate proves *what* (re-derived summary) and *who* (Ed25519 seal) offline; it could not prove *when*. The new commands do: `korg-seal anchor --repo --commit ` binds a `git-tip` time anchor and re-signs (post-hoc flow: mint β†’ publish/commit β†’ anchor); `korg-seal resolve ` performs the one network step β€” fetches each anchor's public commit (stdlib `urllib`, GitHub API) and confirms it *introduced* the anchored `entry_hash`, yielding a "the chain existed no later than ``" bound. A public commit is immutable once mirrored, so an owner who rewrote the chain would have to force-push the witness (detectable). **Demoed live against the real repo:** the committed fixture's tip `bd8389e3…` is genuinely witnessed by public commit `0e566b0` (committed `2026-06-14T06:01:28Z`); `korg-seal resolve` confirms it over the live GitHub API, and the anchored seal still verifies offline. The resolver is injectable-fetcher-based so it's unit-tested hermetically (witnessed / not-witnessed / commit-404 / repo-URL parsing) + the anchor re-bind roundtrip. KORGCERT.md Β§6/Β§8 + positioning updated (time is now an explicit opt-in network step, not a gap). +5 korg-seal tests (14 total). +- **Verify a Certificate anywhere β€” hosted, in CI, and from the terminal.** The trust layer is now ubiquitous: (1) **GitHub Pages** publishes the zero-install browser verifiers + specs at (landing page, the Certificate verifier, and the raw-ledger verifier; `.github/workflows/pages.yml` deploys `spec/korg-ledger-v1/`). (2) A **`verify-korgcert` GitHub Action** (`.github/actions/verify-korgcert`) verifies seals/ledgers in CI β€” `uses: New1Direction/korg/.github/actions/verify-korgcert@…` with an optional `pin-pubkey`, failing the job on any tampered/unpinned artifact. It renders a **rich report** (a `report.mjs` reusing verify.mjs) into the job summary and, on a `pull_request`, **upserts a sticky PR comment** with the re-derived attestation inline β€” claim Β· who (issuer) Β· what (events/tools/files) Β· when (anchor) Β· integrity β€” so "verified agent work" shows up right on the PR. (3) A **dogfooding demo workflow** (`.github/workflows/korgcert-demo.yml`) mints a Certificate from a session ledger with `korg-seal`, verifies it (pinned to the issuer) via the Action, and proves a tampered copy is rejected β€” "verified agent work in CI". (4) The npm verifier (`@korgg/ledger-verify`) bumped to 0.2.0 β€” its `npx` CLI now verifies `korgcert@v1` too. The Certificate verifier's badge now links to the hosted page, and `korg-seal mint` prints the live verify URL. +- **`korgcert@v1` β€” the public, independently-verifiable certificate layer (the "Certificate").** A portable, signed certificate of an AI-agent session that anyone re-verifies offline with zero trust in the issuer. A strict superset of `korgex-receipt@v1`: it embeds the full event chain + an Ed25519 issuer seal + a human-legible summary that is **re-derived from the events at verify time**, so the "files touched / tools used / steps" a person actually reads *cannot lie* (the legacy tip-signature left the summary unprotected; the seal signs claim + summary + tip together). Three conformant implementations: Python (`korg_ledger.korgcert` stdlib derivation + `korg_ledger.signing.mint_seal`/`verify_seal`), Rust (`korg_verify::verify_korgcert` + the `korg-verify` binary, which renders the attestation), and JS (`verify.mjs` `verifyKorgCert`/`deriveSummary`). Cross-impl proof: a frozen fixture (`crates/korg-verify/tests/fixtures/korgcert-v1.json`) **minted by Python, verified byte-identically by Rust and JS** (seed `[42;32]`, deterministic re-mint). Adversarially tested in all three: a lying summary, a moved claim, a tampered event, a stripped seal (a downgrade β€” fails), and a wrong pinned issuer key are all rejected. Adds read-time `verify_dag` to the Python `korg_ledger` package (it previously enforced causality only at write time). Spec: [`KORGCERT.md`](spec/korg-ledger-v1/KORGCERT.md). Headline artifact: a zero-install in-browser Certificate verifier ([`spec/korg-ledger-v1/web/seal.html`](spec/korg-ledger-v1/web/seal.html)) β€” drop a seal and watch the summary re-derive (and the certificate crack red when tampered), entirely client-side via Web Crypto. Positioning: [`docs/korgcert/POSITIONING.md`](docs/korgcert/POSITIONING.md). **Anchors are bound into the seal** (the signed header includes `anchors`, so an anchor cannot be stripped, added, or forged β€” while staying structurally chain-bound), removing the one documented limit; the only remaining external step is the *network* resolution of a git-tip anchor for trusted time. 191 Python tests + 7 Rust korgcert tests + JS conformance (incl. bound-anchor) check. +- **`adapters/korg-seal/` v0.1.0 β€” the producer-side Certificate minter (closes the captureβ†’mintβ†’verify loop).** `korg-seal mint --claim "..."` turns a captured korg-ledger@v1 session into a signed `korgcert@v1` certificate; `korg-seal verify` / `korg-seal key` round it out. Manages a local Ed25519 issuer key at `~/.korg/issuer.ed25519` (`0600`, generated on first use) whose public half is the issuer identity a relying party pins. **Refuses to seal a chain that does not verify** (no Certificate on a tampered history). Reuses the conformant `korg_ledger.korgcert`/`signing` cores; verification stays the dependency-light Rust/JS/browser path. Proven end-to-end: a freshly-keyed CLI-minted seal verifies VALID under the independent Rust `korg-verify` binary AND `verify.mjs`. 9 tests (key lifecycle + 0600 perms, mint, broken-chain refusal, determinism, full CLI mintβ†’verifyβ†’pin roundtrip). `cryptography`-gated (CI installs only pytest, so they skip there; cross-impl agreement is gated by the verifiers). - **`adapters/introspect-mcp/` v0.1.0 β€” generic `--introspect` β†’ MCP bridge.** The leverage move that closes the loop: one MCP server that takes any `--introspect`-aware binary and exposes every `Callable` in its document as a typed MCP tool. After installing once, `korg-introspect-mcp thump`, `korg-introspect-mcp korg`, `korg-introspect-mcp korgex` unlock **30+ tools** across the ecosystem in Claude Code with zero per-binary MCP wiring. Honors `capabilities.side_effects` β€” default policy refuses `fs_write` / `network` / `ledger_write` unless `KORG_INTROSPECT_MCP_ALLOW` opts in. Argv mapping is convention-based (kebab-case long flags, bool flag-on-true, arrays repeat the flag, command_id `.` segments become subcommand path) β€” works for every binary in the ecosystem with no config. Tool names use the introspect `command_id` directly, so the recallβ†’re-execute loop is deterministic (recall returns events with tool_name=`thump.generate`; the bridge serves an MCP tool by the exact same name). 67 tests covering discovery, args mapping, safety gating, invoker output-mode handling, and the full MCP protocol roundtrip with mocked + real binary fixtures. End-to-end smoke-tested against real `thump` and `korg` Rust binaries. - **`adapters/recall-mcp/` β€” Foundry-style `--introspect` + Capabilities, sharing one source of truth with MCP `tools/list`.** New `korg_recall_mcp.introspect` module defines `Callable` and `Capabilities` dataclasses; the same `Callable` instance projects to both an MCP tool descriptor AND an `--introspect` document entry. End-to-end smoke-tested: the input schema served via MCP `tools/list` is byte-identical to the one in `--introspect`. Document includes stable `command_id`, declared `side_effects`/`output_mode`/`long_running`/`stateful`/`reads_stdin` capabilities, and a canonical 8-code `exit_codes` table. Tagged `korg:introspect@v1`. The pattern is now ready to apply to thumper, korg, korgex β€” anywhere we want CLI introspection AND MCP descriptors to stay in sync without two parallel sources. 17 new tests including a contract test that fails CI if the two surfaces drift. - **`adapters/korg-setup/` v0.1.0 β€” one-command install for the Claude Code loop.** Collapses the five-step manual setup (verify binaries β†’ create ledger dir β†’ edit `~/.claude.json` β†’ install launchd plist β†’ load it) into `korg-setup`. Atomic writes with a `.korg-backup` copy of the prior Claude config; idempotent re-runs; `--dry-run` preview; `status` subcommand shows what's installed and running; `uninstall` reverses everything except the ledger itself. macOS launchd is native; Linux gets a manual one-liner (systemd-user native install is a follow-up). 45 tests covering claude-config edits, launchd integration (mocked subprocess), the orchestrator, and the status reporter. diff --git a/README.md b/README.md index 9475ed9..1728f91 100644 --- a/README.md +++ b/README.md @@ -224,9 +224,9 @@ korg-verify > the guarantee, independent of model quality. > **Verify it in your browser β€” sends nothing.** Zero-install, client-side -> verifiers (Web Crypto) for any `korg-ledger@v1` journal or Gold Seal: +> verifiers (Web Crypto) for any `korg-ledger@v1` journal or Certificate: > [verify a session](https://new1direction.github.io/korg/web/index.html) Β· -> [verify a Gold Seal](https://new1direction.github.io/korg/web/seal.html) Β· +> [verify a Certificate](https://new1direction.github.io/korg/web/seal.html) Β· > [time-travel explorer](https://new1direction.github.io/korg/web/explore.html). > They hash-chain, check the causal DAG, validate Ed25519 signatures, and > re-derive the human summary from the events β€” all locally. @@ -282,7 +282,7 @@ Korg treats AI cognition the same way a hypervisor treats compute and Git treats | Speculative branches | 🚧 planned | ❌ | ❌ | ❌ | | Execution checkpoints | 🚧 planned | ❌ | ❌ | ❌ | | Cryptographic audit trail | βœ… | ❌ | ❌ | ❌ | -| Independently-verifiable Gold Seal | βœ… | ❌ | ❌ | ❌ | +| Independently-verifiable Certificate | βœ… | ❌ | ❌ | ❌ | | Honest attestation (real diff, never fabricated) | βœ… | ❌ | ❌ | ❌ | | Micro-healing | βœ… | ❌ | ❌ | ❌ | | Model-agnostic | βœ… | βœ… | βœ… | βœ… | @@ -338,7 +338,7 @@ flowchart TD Chain["korg-ledger@v1
hash-chain: prev_hash to entry_hash
SHA-256 / HMAC + Ed25519"] Projection["ProjectionEngine
pure folds to read models"] Rewind["rewind / rewind_with_seal
truncate to seq + LedgerRewind tip"] - Verify["korg-verify (+ Python / JS)
verify_chain Β· verify_dag Β· sig Β· Gold Seal"] + Verify["korg-verify (+ Python / JS)
verify_chain Β· verify_dag Β· sig Β· Certificate"] Runtime["korg-runtime
multi-persona swarm Β· git-worktree sandbox
arena Β· evaluator Β· run_once"] Agent --> MCP --> Journal @@ -363,7 +363,7 @@ Korg is in active development, built on a **frozen `korg-ledger@v1` spec with cr - [x] Deterministic replay and projection rebuilds - [x] Reversible execution β€” rewind the ledger to any prior sequence point (tamper-evident `LedgerRewind`) - [x] Per-event Ed25519 signatures + structural anchoring (`korg-ledger@v1` Β§8) -- [x] **Gold Seal (`goldseal@v1`)** β€” a public, independently-verifiable certificate of agent work, with zero-install in-browser verifiers +- [x] **Certificate (`korgcert@v1`)** β€” a public, independently-verifiable certificate of agent work, with zero-install in-browser verifiers - [x] **Honest pipeline** (`korg run-once`) β€” real patch β†’ real `cargo check` β†’ an attested mutation count that equals the real `git diff`; never fabricates (reports an honest null instead) - [x] **Live local model** (`--provider ollama`) β€” real per-persona work on arbitrary tasks - [x] **Multi-agent swarm** (Captain, Harper, Benjamin, Lucas, Evaluator) β€” genuine worker subprocesses doing real, measured, attested work with DAG data-flow between personas diff --git a/adapters/korg-ledger-py/src/korg_ledger/goldseal.py b/adapters/korg-ledger-py/src/korg_ledger/korgcert.py similarity index 92% rename from adapters/korg-ledger-py/src/korg_ledger/goldseal.py rename to adapters/korg-ledger-py/src/korg_ledger/korgcert.py index 5944a9e..817e6a6 100644 --- a/adapters/korg-ledger-py/src/korg_ledger/goldseal.py +++ b/adapters/korg-ledger-py/src/korg_ledger/korgcert.py @@ -1,6 +1,6 @@ -"""goldseal@v1 β€” the public, independently-verifiable certificate layer. +"""korgcert@v1 β€” the public, independently-verifiable certificate layer. -A Gold Seal is a single self-contained JSON object that wraps a verified +A Certificate is a single self-contained JSON object that wraps a verified korg-ledger@v1 event chain together with a human-legible *summary* and an Ed25519 *seal* signed by an issuer. Anyone can re-verify it offline, with zero trust in the tool that produced it: @@ -17,19 +17,19 @@ This module is **stdlib-only**: it does derivation + the structural checks (1-3). The Ed25519 seal signature (4) lives in :mod:`korg_ledger.signing` (the optional ``[signing]`` extra), mirroring how per-event signing is kept -off the stdlib core. ``goldseal@v1`` is a strict superset of -``korgex-receipt@v1`` β€” a Gold Seal still verifies as a receipt under an older +off the stdlib core. ``korgcert@v1`` is a strict superset of +``korgex-receipt@v1`` β€” a Certificate still verifies as a receipt under an older receipt-only verifier (chain + DAG + tip), which simply does not see the stronger seal/summary guarantees. Cross-language conformant with the Rust ``korg-verify`` and JS ``verify.mjs`` -goldseal codepaths: derivation and the canonical header are byte-identical. +korgcert codepaths: derivation and the canonical header are byte-identical. """ from __future__ import annotations from ._hash import canonicalize, verify_anchors, verify_chain -SCHEMA = "goldseal@v1" +SCHEMA = "korgcert@v1" SPEC = "korg-ledger@v1" #: Envelope keys that are NOT part of the signed header. ``events`` is excluded @@ -94,7 +94,7 @@ def derive_summary(events: list) -> dict: def seal_header(envelope: dict) -> dict: - """The signed portion of a Gold Seal: the envelope minus ``events`` and + """The signed portion of a Certificate: the envelope minus ``events`` and ``seal`` (so it includes ``anchors`` when present). This is the exact object whose canonicalization is the seal signature @@ -114,7 +114,7 @@ def build_envelope( issued_at: int, anchors: list | None = None, ) -> dict: - """Assemble the *unsigned* Gold Seal envelope (header + events [+ anchors]). + """Assemble the *unsigned* Certificate envelope (header + events [+ anchors]). The caller signs ``seal_header(envelope)`` and attaches the ``seal``. Kept separate from signing so the stdlib core can construct the bound object @@ -142,7 +142,7 @@ def build_envelope( def verify_structure(envelope: dict) -> list: - """Run the hermetic, crypto-free half of Gold Seal verification. + """Run the hermetic, crypto-free half of Certificate verification. Returns a list of human-readable errors; empty iff the chain, DAG, tip, event_count and the **re-derived summary** all check out. The Ed25519 seal diff --git a/adapters/korg-ledger-py/src/korg_ledger/signing.py b/adapters/korg-ledger-py/src/korg_ledger/signing.py index 6f35d16..f26f88b 100644 --- a/adapters/korg-ledger-py/src/korg_ledger/signing.py +++ b/adapters/korg-ledger-py/src/korg_ledger/signing.py @@ -38,8 +38,8 @@ def verify_event_sig(public_bytes: bytes, event: dict, sig_hex: str) -> bool: return False -# ── goldseal@v1 β€” seal-level signing ──────────────────────────────────────── -# The seal signs the canonical *header* (the Gold Seal envelope minus +# ── korgcert@v1 β€” seal-level signing ──────────────────────────────────────── +# The seal signs the canonical *header* (the Certificate envelope minus # events/seal/anchors), binding claim + issuer + tip + event_count + summary # together under one Ed25519 key. Same primitive as per-event signing: # Ed25519 over the canonical preimage, lowercase hex. @@ -56,7 +56,7 @@ def public_key_hex(private_seed: bytes) -> str: def sign_seal(private_seed: bytes, header: dict) -> str: - """Ed25519-sign a Gold Seal header's canonical bytes. Lowercase-hex sig.""" + """Ed25519-sign a Certificate header's canonical bytes. Lowercase-hex sig.""" from ._hash import canonicalize key = Ed25519PrivateKey.from_private_bytes(private_seed) @@ -86,23 +86,23 @@ def mint_seal( private_seed: bytes, anchors: list | None = None, ) -> dict: - """Mint a signed goldseal@v1 envelope from a verified event chain. + """Mint a signed korgcert@v1 envelope from a verified event chain. Builds the bound header (deriving the summary from ``events``), signs its canonical bytes, and attaches the ``seal``. The returned object verifies under :func:`verify_seal` and β€” being a receipt superset β€” under any receipt-only verifier (chain + DAG + tip). """ - from . import goldseal + from . import korgcert - envelope = goldseal.build_envelope( + envelope = korgcert.build_envelope( events=events, claim=claim, issuer_agent=issuer_agent, issued_at=issued_at, anchors=anchors, ) - header = goldseal.seal_header(envelope) + header = korgcert.seal_header(envelope) envelope["seal"] = { "alg": "ed25519", "pubkey": public_key_hex(private_seed), @@ -112,7 +112,7 @@ def mint_seal( def verify_seal(envelope: dict, pin_pubkey: str | None = None) -> list: - """Fully verify a goldseal@v1 envelope: structure (chain + DAG + tip + + """Fully verify a korgcert@v1 envelope: structure (chain + DAG + tip + re-derived summary) plus the Ed25519 seal signature. Returns a list of errors; empty iff valid. @@ -120,18 +120,18 @@ def verify_seal(envelope: dict, pin_pubkey: str | None = None) -> list: already trusts β€” closing the self-referential hole where a bare check only proves the seal matches the *returned* key. """ - from . import goldseal + from . import korgcert if not isinstance(envelope, dict): return ["envelope is not a JSON object"] - errors = goldseal.verify_structure(envelope) + errors = korgcert.verify_structure(envelope) seal = envelope.get("seal") if isinstance(seal, dict): pubkey = seal.get("pubkey") or "" sig = seal.get("sig") or "" - header = goldseal.seal_header(envelope) + header = korgcert.seal_header(envelope) ok = False try: ok = verify_seal_sig(bytes.fromhex(pubkey), header, sig) @@ -144,6 +144,6 @@ def verify_seal(envelope: dict, pin_pubkey: str | None = None) -> list: elif pin_pubkey is not None: errors.append(f"seal is absent but signer {pin_pubkey} was required") else: - errors.append("seal is absent (unsigned Gold Seal)") + errors.append("seal is absent (unsigned Certificate)") return errors diff --git a/adapters/korg-ledger-py/tests/test_goldseal.py b/adapters/korg-ledger-py/tests/test_korgcert.py similarity index 98% rename from adapters/korg-ledger-py/tests/test_goldseal.py rename to adapters/korg-ledger-py/tests/test_korgcert.py index c6cbab2..6947bac 100644 --- a/adapters/korg-ledger-py/tests/test_goldseal.py +++ b/adapters/korg-ledger-py/tests/test_korgcert.py @@ -1,4 +1,4 @@ -"""goldseal@v1 β€” derivation, structural verification, and (crypto) seal tests. +"""korgcert@v1 β€” derivation, structural verification, and (crypto) seal tests. The structural half (derivation + chain/tip/summary checks) is stdlib-only and always runs. The Ed25519 seal half needs `cryptography` and is skipped when the @@ -11,7 +11,7 @@ import pytest from korg_ledger import chain_hash -from korg_ledger.goldseal import ( +from korg_ledger.korgcert import ( SCHEMA, build_envelope, derive_summary, @@ -21,7 +21,7 @@ AGENT = "agent:korgex@0.14.1" REPO = Path(__file__).resolve().parents[3] -FIXTURE = REPO / "crates" / "korg-verify" / "tests" / "fixtures" / "goldseal-v1.json" +FIXTURE = REPO / "crates" / "korg-verify" / "tests" / "fixtures" / "korgcert-v1.json" def _chain(steps): diff --git a/adapters/korg-ledger-py/tests/test_goldseal_properties.py b/adapters/korg-ledger-py/tests/test_korgcert_properties.py similarity index 95% rename from adapters/korg-ledger-py/tests/test_goldseal_properties.py rename to adapters/korg-ledger-py/tests/test_korgcert_properties.py index 2fdaa91..27808b2 100644 --- a/adapters/korg-ledger-py/tests/test_goldseal_properties.py +++ b/adapters/korg-ledger-py/tests/test_korgcert_properties.py @@ -1,4 +1,4 @@ -"""Property-based hardening for the Gold Seal verifiers. +"""Property-based hardening for the Certificate verifiers. Two security invariants, fuzzed: 1. The verifier NEVER crashes on hostile input β€” arbitrary JSON, malformed @@ -20,10 +20,10 @@ from hypothesis import HealthCheck, given, settings # noqa: E402 from hypothesis import strategies as st # noqa: E402 -from korg_ledger.goldseal import derive_summary, verify_structure # noqa: E402 +from korg_ledger.korgcert import derive_summary, verify_structure # noqa: E402 from korg_ledger.signing import verify_seal # noqa: E402 -FIXTURE = Path(__file__).resolve().parents[3] / "crates/korg-verify/tests/fixtures/goldseal-v1.json" +FIXTURE = Path(__file__).resolve().parents[3] / "crates/korg-verify/tests/fixtures/korgcert-v1.json" # arbitrary JSON-shaped values (incl. floats/nan/inf, deep nesting, junk keys) json_values = st.recursive( @@ -52,7 +52,7 @@ def test_verify_structure_never_crashes_and_junk_is_invalid(blob): def test_verify_seal_never_crashes_and_junk_is_invalid(blob): errs = verify_seal(blob) assert isinstance(errs, list) - assert errs != [], "arbitrary input must never verify as a valid Gold Seal" + assert errs != [], "arbitrary input must never verify as a valid Certificate" @given(blob=json_values) diff --git a/adapters/korg-seal/README.md b/adapters/korg-seal/README.md index 3f621a7..39fecac 100644 --- a/adapters/korg-seal/README.md +++ b/adapters/korg-seal/README.md @@ -1,7 +1,7 @@ # korg-seal -Mint and verify **`goldseal@v1`** certificates β€” turn a captured AI-agent session -into a portable, signed **Gold Seal** that anyone can re-verify offline, with zero +Mint and verify **`korgcert@v1`** certificates β€” turn a captured AI-agent session +into a portable, signed **Certificate** that anyone can re-verify offline, with zero trust in the tool that produced it. ``` @@ -13,7 +13,7 @@ trust in the tool that produced it. `korg-seal` is the **producer** side. Verification is deliberately separate and dependency-light: the Rust `korg-verify` binary, the JS `verify.mjs`, and the in-browser page all check a seal independently β€” see -[`spec/korg-ledger-v1/GOLDSEAL.md`](../../spec/korg-ledger-v1/GOLDSEAL.md). +[`spec/korg-ledger-v1/KORGCERT.md`](../../spec/korg-ledger-v1/KORGCERT.md). ## Install @@ -25,16 +25,16 @@ pipx install ./adapters/korg-seal # provides the `korg-seal` command ## Use ```sh -# mint a Gold Seal from a captured session, attaching a human claim +# mint a Certificate from a captured session, attaching a human claim korg-seal mint ~/.korg/sessions/.jsonl \ --claim "Refactored the auth layer to JWTs; tests pass" \ - -o auth-refactor.goldseal.json + -o auth-refactor.korgcert.json # print your issuer public key β€” publish/pin this so others can trust your seals korg-seal key # verify (the Rust korg-verify binary is the canonical, zero-trust verifier) -korg-seal verify auth-refactor.goldseal.json --pin +korg-seal verify auth-refactor.korgcert.json --pin ``` ### Trusted time β€” anchor to a public commit, then resolve @@ -44,11 +44,11 @@ anchor the seal to a public git commit and let anyone resolve it over the networ ```sh # after publishing/committing the seal to a public repo, bind that commit (re-signs) -korg-seal anchor auth-refactor.goldseal.json \ +korg-seal anchor auth-refactor.korgcert.json \ --repo github.com/you/your-repo --commit # anyone resolves it: fetches the commit and confirms it witnesses the tip -korg-seal resolve auth-refactor.goldseal.json +korg-seal resolve auth-refactor.korgcert.json # βœ“ seq 12 Β· 4f9cc6b6… witnessed by github.com/you/your-repo@ Β· committed # β†’ the chain existed by then (published no later than this commit) ``` @@ -61,10 +61,10 @@ force-push the witness, which anyone who fetched it detects. What `mint` does: 1. Loads the ledger (JSONL or JSON array, flat or nested event shape). -2. **Refuses to seal a chain that doesn't verify** β€” you never put a Gold Seal on +2. **Refuses to seal a chain that doesn't verify** β€” you never put a Certificate on a tampered history (override with `--allow-unverified`, not recommended). 3. Derives the human summary *from the events* (so it cannot lie), builds the - `goldseal@v1` envelope, and signs the canonical header with your issuer key. + `korgcert@v1` envelope, and signs the canonical header with your issuer key. ## The issuer key @@ -73,13 +73,13 @@ use). Its public half is your issuer identity β€” a relying party pins it with `--pin`. Keep the seed private; back it up if your seals need to stay attributable to you. Use `--key ` to point at a different key file. -## What a Gold Seal proves (and doesn't) +## What a Certificate proves (and doesn't) A green verdict proves the events are hash-chain-intact, the summary was re-derived from them, and the issuer signed the whole thing (claim + summary + tip + anchors). It does **not** prove *when* it happened (needs an external anchor resolved over the network) or that the issuer key maps to a real-world identity (the relying party -pins that). Full threat model: `GOLDSEAL.md` Β§1. +pins that). Full threat model: `KORGCERT.md` Β§1. ## Tests diff --git a/adapters/korg-seal/pyproject.toml b/adapters/korg-seal/pyproject.toml index 28db982..889cfe0 100644 --- a/adapters/korg-seal/pyproject.toml +++ b/adapters/korg-seal/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta" [project] name = "korg-seal" version = "0.1.0" -description = "Mint and verify goldseal@v1 certificates β€” turn a captured AI-agent session ledger into a portable, signed Gold Seal anyone can re-verify offline." +description = "Mint and verify korgcert@v1 certificates β€” turn a captured AI-agent session ledger into a portable, signed Certificate anyone can re-verify offline." license = { text = "MIT" } requires-python = ">=3.9" authors = [{ name = "Korg ecosystem maintainers" }] diff --git a/adapters/korg-seal/src/korg_seal/__init__.py b/adapters/korg-seal/src/korg_seal/__init__.py index 041aa48..6ab98df 100644 --- a/adapters/korg-seal/src/korg_seal/__init__.py +++ b/adapters/korg-seal/src/korg_seal/__init__.py @@ -1,13 +1,13 @@ -"""korg-seal β€” mint and verify goldseal@v1 certificates. +"""korg-seal β€” mint and verify korgcert@v1 certificates. The producer-side counterpart to the zero-trust verifiers (Rust ``korg-verify``, JS ``verify.mjs``, the in-browser page): it turns a captured korg-ledger@v1 -session into a portable, signed **Gold Seal** that anyone can re-verify offline. +session into a portable, signed **Certificate** that anyone can re-verify offline. - capture (the hook) β†’ ~/.korg/sessions/.jsonl β†’ korg-seal mint β†’ goldseal.json β†’ anyone verifies + capture (the hook) β†’ ~/.korg/sessions/.jsonl β†’ korg-seal mint β†’ korgcert.json β†’ anyone verifies Signing is intrinsic, so this package depends on ``cryptography`` directly and -reuses the conformant ``korg_ledger.goldseal`` / ``korg_ledger.signing`` cores. +reuses the conformant ``korg_ledger.korgcert`` / ``korg_ledger.signing`` cores. """ from .minter import load_ledger, mint diff --git a/adapters/korg-seal/src/korg_seal/cli.py b/adapters/korg-seal/src/korg_seal/cli.py index 7b0b173..46b661b 100644 --- a/adapters/korg-seal/src/korg_seal/cli.py +++ b/adapters/korg-seal/src/korg_seal/cli.py @@ -1,6 +1,6 @@ -"""korg-seal β€” mint and verify goldseal@v1 certificates of AI-agent sessions. +"""korg-seal β€” mint and verify korgcert@v1 certificates of AI-agent sessions. - korg-seal mint --claim "..." # produce a signed Gold Seal + korg-seal mint --claim "..." # produce a signed Certificate korg-seal verify [--pin ] # check one (Python; Rust korg-verify is canonical) korg-seal key # print the issuer public key @@ -55,7 +55,7 @@ def _cmd_mint(args) -> int: rendered = json.dumps(seal, indent=2, ensure_ascii=False) if args.out: Path(args.out).write_text(rendered + "\n", encoding="utf-8") - print(f"βœ“ minted Gold Seal β†’ {args.out}", file=sys.stderr) + print(f"βœ“ minted Certificate β†’ {args.out}", file=sys.stderr) else: print(rendered) print(f" issuer {keys.public_key_hex(seed)}", file=sys.stderr) @@ -88,7 +88,7 @@ def _cmd_verify(args) -> int: seal = env.get("seal", {}) pub = str(seal.get("pubkey", ""))[:16] print( - f"βœ“ goldseal VALID β€” {env.get('event_count')} events, summary re-derived Β· signed by {pub}…" + f"βœ“ korgcert VALID β€” {env.get('event_count')} events, summary re-derived Β· signed by {pub}…" ) print(f" claim: {env.get('claim')}") return 0 @@ -162,11 +162,11 @@ def _cmd_resolve(args) -> int: def main(argv=None) -> int: p = argparse.ArgumentParser( prog="korg-seal", - description="Mint and verify goldseal@v1 certificates of AI-agent sessions.", + description="Mint and verify korgcert@v1 certificates of AI-agent sessions.", ) sub = p.add_subparsers(dest="cmd", required=True) - m = sub.add_parser("mint", help="mint a Gold Seal from a session ledger") + m = sub.add_parser("mint", help="mint a Certificate from a session ledger") m.add_argument("ledger", help="path to a korg-ledger@v1 session (JSONL or JSON array)") m.add_argument("--claim", required=True, help="one-line description the issuer attests to") m.add_argument("-o", "--out", help="write the seal here (default: stdout)") @@ -180,8 +180,8 @@ def main(argv=None) -> int: ) m.set_defaults(func=_cmd_mint) - v = sub.add_parser("verify", help="verify a Gold Seal (Python; Rust korg-verify is canonical)") - v.add_argument("seal", help="path to a goldseal@v1 JSON file") + v = sub.add_parser("verify", help="verify a Certificate (Python; Rust korg-verify is canonical)") + v.add_argument("seal", help="path to a korgcert@v1 JSON file") v.add_argument("--pin", help="require this issuer public key (hex)") v.set_defaults(func=_cmd_verify) @@ -190,7 +190,7 @@ def main(argv=None) -> int: k.set_defaults(func=_cmd_key) a = sub.add_parser("anchor", help="bind a git-tip time anchor to a seal (re-signs)") - a.add_argument("seal", help="path to the goldseal@v1 to anchor") + a.add_argument("seal", help="path to the korgcert@v1 to anchor") a.add_argument("--repo", required=True, help="public repo URL the seal was published to") a.add_argument("--commit", required=True, help="commit SHA that witnesses the tip") a.add_argument("--seq", type=int, help="anchor a specific seq_id (default: the tip)") @@ -199,7 +199,7 @@ def main(argv=None) -> int: a.set_defaults(func=_cmd_anchor) r = sub.add_parser("resolve", help="resolve git-tip anchors over the network β€” proves WHEN") - r.add_argument("seal", help="path to a goldseal@v1 with git-tip anchors") + r.add_argument("seal", help="path to a korgcert@v1 with git-tip anchors") r.set_defaults(func=_cmd_resolve) args = p.parse_args(argv) diff --git a/adapters/korg-seal/src/korg_seal/keys.py b/adapters/korg-seal/src/korg_seal/keys.py index 6a08168..cb0780c 100644 --- a/adapters/korg-seal/src/korg_seal/keys.py +++ b/adapters/korg-seal/src/korg_seal/keys.py @@ -1,6 +1,6 @@ """Issuer Ed25519 key management for korg-seal. -The issuer key is the identity behind a Gold Seal β€” relying parties pin its +The issuer key is the identity behind a Certificate β€” relying parties pin its public half (``korg-seal verify --pin``). The 32-byte raw seed lives at ``~/.korg/issuer.ed25519`` with ``0600`` perms, generated on first use. """ diff --git a/adapters/korg-seal/src/korg_seal/minter.py b/adapters/korg-seal/src/korg_seal/minter.py index 972900c..1449af6 100644 --- a/adapters/korg-seal/src/korg_seal/minter.py +++ b/adapters/korg-seal/src/korg_seal/minter.py @@ -1,4 +1,4 @@ -"""Mint a goldseal@v1 certificate from a captured session ledger.""" +"""Mint a korgcert@v1 certificate from a captured session ledger.""" from __future__ import annotations import json @@ -32,10 +32,10 @@ def mint( anchors: list | None = None, strict: bool = True, ) -> dict: - """Mint a signed Gold Seal from the ledger at ``ledger_path``. + """Mint a signed Certificate from the ledger at ``ledger_path``. Refuses (in ``strict`` mode) to seal a chain that does not verify β€” you - should never put a Gold Seal on a tampered history. ``issuer_agent`` + should never put a Certificate on a tampered history. ``issuer_agent`` defaults to a label derived from the issuer key; ``issued_at`` defaults to the current Unix time. """ @@ -73,7 +73,7 @@ def anchor( seq_id: int | None = None, anchored_at: str | None = None, ) -> dict: - """Re-mint an existing Gold Seal with a git-tip anchor bound into the seal. + """Re-mint an existing Certificate with a git-tip anchor bound into the seal. Anchoring is post-hoc: you mint a seal, publish/commit it to a public repo, then anchor it to that commit. Because the anchor is *bound* (signed), this diff --git a/adapters/korg-seal/src/korg_seal/resolve.py b/adapters/korg-seal/src/korg_seal/resolve.py index 6bdd86e..4c6dd1c 100644 --- a/adapters/korg-seal/src/korg_seal/resolve.py +++ b/adapters/korg-seal/src/korg_seal/resolve.py @@ -110,7 +110,7 @@ def _introduced(patch: str) -> bool: def resolve_seal(env: dict, fetch=None) -> list[AnchorResult]: - """Resolve every git-tip anchor embedded in a Gold Seal.""" + """Resolve every git-tip anchor embedded in a Certificate.""" anchors = env.get("anchors") or [] return [ resolve_anchor(a, fetch=fetch) diff --git a/adapters/korg-seal/tests/test_korg_seal.py b/adapters/korg-seal/tests/test_korg_seal.py index 0d51b55..aeaf72d 100644 --- a/adapters/korg-seal/tests/test_korg_seal.py +++ b/adapters/korg-seal/tests/test_korg_seal.py @@ -88,7 +88,7 @@ def test_mint_produces_a_seal_that_verifies(tmp_path): ledger, _ = _sample(tmp_path) seed = bytes([7]) * 32 seal = mint_mod.mint(ledger_path=ledger, claim="added healthz", seed=seed, issued_at=1) - assert seal["schema"] == "goldseal@v1" + assert seal["schema"] == "korgcert@v1" assert verify_seal(seal) == [] # issuer label is derived from the key when not given assert seal["issuer"]["agent"].startswith("agent:korg-seal#") diff --git a/crates/korg-verify/README.md b/crates/korg-verify/README.md index 6acb10a..b7f7ba1 100644 --- a/crates/korg-verify/README.md +++ b/crates/korg-verify/README.md @@ -21,7 +21,7 @@ Exit code: `0` valid Β· `1` invalid/tampered Β· `2` usage/parse error. It is the third independent implementation of **korg-ledger@v1** β€” Python (`korgex receipt verify`), JavaScript (the self-verifying HTML report), and now Rust β€” all checked against the same frozen conformance vectors. That makes "verify a sealed deliverable without trusting the tool that produced it" provable rather than asserted: a single small binary anyone can run, in CI or by hand. -It also verifies a **goldseal@v1** certificate β€” a public, independently-verifiable Gold Seal of agent work β€” auto-detecting receipts, journals, and seals from the same binary; korg-verify is one of three conformant Gold Seal verifiers (Rust/Python/JS). +It also verifies a **korgcert@v1** certificate β€” a public, independently-verifiable Certificate of agent work β€” auto-detecting receipts, journals, and seals from the same binary; korg-verify is one of three conformant Certificate verifiers (Rust/Python/JS). ## Examples diff --git a/crates/korg-verify/src/lib.rs b/crates/korg-verify/src/lib.rs index ac9553c..d326dae 100644 --- a/crates/korg-verify/src/lib.rs +++ b/crates/korg-verify/src/lib.rs @@ -45,7 +45,7 @@ pub struct Verdict { /// `None` when no `--anchors` sidecar was supplied; `Some(true/false)` for the /// structural anchor check (each anchor's entry_hash matches the chain). pub anchors_ok: Option, - /// `None` for receipts/journals; `Some(true/false)` for a goldseal@v1 β€” whether + /// `None` for receipts/journals; `Some(true/false)` for a korgcert@v1 β€” whether /// the embedded summary byte-matches the summary re-derived from the events. pub summary_ok: Option, pub errors: Vec, @@ -271,8 +271,8 @@ pub fn verify_receipt(receipt: &Value, key: Option<&[u8]>, pin_pubkey: Option<&s } } -/// A Gold Seal's bound, human-legible summary, derived as a *pure function* of the -/// event chain. Byte-identical to the Python (`korg_ledger.goldseal.derive_summary`) +/// A Certificate's bound, human-legible summary, derived as a *pure function* of the +/// event chain. Byte-identical to the Python (`korg_ledger.korgcert.derive_summary`) /// and JS (`deriveSummary`) implementations β€” a verifier re-derives it and rejects /// any mismatch, so the summary cannot lie about what the agent did. fn derive_summary(events: &[Value]) -> Value { @@ -318,7 +318,7 @@ fn derive_summary(events: &[Value]) -> Value { }) } -/// The signed portion of a Gold Seal: the envelope minus `events` and `seal` (so +/// The signed portion of a Certificate: the envelope minus `events` and `seal` (so /// it includes `anchors` when present β€” the seal commits to the anchor set). Its /// canonicalization is the seal-signature preimage (identical at mint and verify). fn seal_header(envelope: &Value) -> Value { @@ -349,14 +349,14 @@ fn verify_seal_sig(pubkey_hex: &str, header: &Value, sig_hex: &str) -> bool { } } -/// Verify a `goldseal@v1` certificate: the embedded chain + DAG, the recorded tip +/// Verify a `korgcert@v1` certificate: the embedded chain + DAG, the recorded tip /// and event_count, the **re-derived summary** (byte-equal to the embedded one), -/// and the issuer's Ed25519 seal over the canonical header. A goldseal is a receipt +/// and the issuer's Ed25519 seal over the canonical header. A korgcert is a receipt /// superset, so it also passes an older receipt-only verifier (chain + DAG + tip) β€” /// minus the summary/seal guarantees. A stripped seal fails the verdict (downgrade). /// /// `pin_pubkey`: require the issuer to equal a key the relying party already trusts. -pub fn verify_goldseal(envelope: &Value, pin_pubkey: Option<&str>) -> Verdict { +pub fn verify_korgcert(envelope: &Value, pin_pubkey: Option<&str>) -> Verdict { let events: Vec = envelope .get("events") .and_then(|e| e.as_array()) @@ -369,9 +369,9 @@ pub fn verify_goldseal(envelope: &Value, pin_pubkey: Option<&str>) -> Verdict { let dag_ok = dag.is_empty(); errors.extend(dag); - let schema_ok = envelope.get("schema").and_then(|s| s.as_str()) == Some("goldseal@v1"); + let schema_ok = envelope.get("schema").and_then(|s| s.as_str()) == Some("korgcert@v1"); if !schema_ok { - errors.push("schema is not goldseal@v1".to_string()); + errors.push("schema is not korgcert@v1".to_string()); } let claimed_tip = envelope.get("tip").and_then(|t| t.as_str()); @@ -434,11 +434,11 @@ pub fn verify_goldseal(envelope: &Value, pin_pubkey: Option<&str>) -> Verdict { } (Some(ok), Some(pubkey.to_string())) } else { - // A goldseal@v1 MUST carry a seal β€” a stripped seal is a downgrade, not a + // A korgcert@v1 MUST carry a seal β€” a stripped seal is a downgrade, not a // merely-unsigned artifact. Fails the verdict in all implementations. errors.push(match pin_pubkey { Some(pin) => format!("seal is absent but signer {pin} was required"), - None => "seal is absent (unsigned Gold Seal)".to_string(), + None => "seal is absent (unsigned Certificate)".to_string(), }); (Some(false), None) }; @@ -464,7 +464,7 @@ pub fn verify_goldseal(envelope: &Value, pin_pubkey: Option<&str>) -> Verdict { && anchors_ok != Some(false); Verdict { valid, - kind: "goldseal", + kind: "korgcert", event_count: events.len(), chain_ok, dag_ok, @@ -478,7 +478,7 @@ pub fn verify_goldseal(envelope: &Value, pin_pubkey: Option<&str>) -> Verdict { } } -/// Auto-detect a goldseal@v1 certificate, a receipt (`{…,"events":[…]}` or +/// Auto-detect a korgcert@v1 certificate, a receipt (`{…,"events":[…]}` or /// `schema: korgex-receipt@*`), or a journal (array or JSONL) and verify accordingly. pub fn verify_text( text: &str, @@ -492,9 +492,9 @@ pub fn verify_text( if let Ok(v) = serde_json::from_str::(text) { if v.get("schema") .and_then(|s| s.as_str()) - .is_some_and(|s| s.starts_with("goldseal")) + .is_some_and(|s| s.starts_with("korgcert")) { - return Ok(verify_goldseal(&v, pin_pubkey)); + return Ok(verify_korgcert(&v, pin_pubkey)); } let is_receipt = v.get("events").is_some() || v.get("schema") diff --git a/crates/korg-verify/src/main.rs b/crates/korg-verify/src/main.rs index 94b4ea6..c6270b3 100644 --- a/crates/korg-verify/src/main.rs +++ b/crates/korg-verify/src/main.rs @@ -10,11 +10,11 @@ USAGE: korg-verify [--key ] [--pubkey ] [--pin-event-pubkey ] [--anchors ] [--json] ARGS: - a Gold Seal (goldseal@v1), a korg receipt (.json), or a journal (.jsonl / JSON array) + a Certificate (korgcert@v1), a korg receipt (.json), or a journal (.jsonl / JSON array) OPTIONS: --key HMAC key (raw bytes) for keyed chains - --pubkey pin the expected signer β€” the receipt-tip signer or the Gold Seal issuer + --pubkey pin the expected signer β€” the receipt-tip signer or the Certificate issuer --pin-event-pubkey require every event's per-event Ed25519 event_sig to verify under this key --anchors verify an anchors.jsonl sidecar (structural: entry_hash ↔ chain) --json machine-readable verdict @@ -140,9 +140,9 @@ fn main() -> ExitCode { }); println!("{out}"); } else { - // For a Gold Seal, surface the human-legible (and re-derived) summary β€” + // For a Certificate, surface the human-legible (and re-derived) summary β€” // that is the artifact's whole purpose. - let seal_view = (verdict.kind == "goldseal") + let seal_view = (verdict.kind == "korgcert") .then(|| serde_json::from_str::(&text).ok()) .flatten(); print_human(&verdict, &file, seal_view.as_ref()); @@ -192,7 +192,7 @@ fn print_human(v: &korg_verify::Verdict, file: &str, seal: Option<&serde_json::V } } -/// Render a verified Gold Seal's human-legible attestation. Every line here was +/// Render a verified Certificate's human-legible attestation. Every line here was /// re-derived from the signed event chain, so it is exactly what happened. fn print_seal_summary(env: &serde_json::Value) { if let Some(claim) = env.get("claim").and_then(|c| c.as_str()) { diff --git a/crates/korg-verify/tests/fixtures/goldseal-v1.json b/crates/korg-verify/tests/fixtures/korgcert-v1.json similarity index 95% rename from crates/korg-verify/tests/fixtures/goldseal-v1.json rename to crates/korg-verify/tests/fixtures/korgcert-v1.json index c58bfd1..63a6ff0 100644 --- a/crates/korg-verify/tests/fixtures/goldseal-v1.json +++ b/crates/korg-verify/tests/fixtures/korgcert-v1.json @@ -1,5 +1,5 @@ { - "schema": "goldseal@v1", + "schema": "korgcert@v1", "spec": "korg-ledger@v1", "claim": "Added a /healthz endpoint to src/app.py with a passing regression test", "issued_at": 1760000000, @@ -122,6 +122,6 @@ "seal": { "alg": "ed25519", "pubkey": "197f6b23e16c8532c6abc838facd5ea789be0c76b2920334039bfa8b3d368d61", - "sig": "a30927527dd88461958288d42d775b06833c488f023f0a054bb7895415f75994b8ac4521c2e3923f86a2f760eda1c58a711e7f3052a3c28cca3a99ac7c9f4307" + "sig": "b01ddf8d10e9819d772ea1892548877e805eba11dfad57afa355a52dc9f78963072e5f2be7412006ba1f79f6ae52b8a9e0030b9c149584e5e1eb9f4ac3e3a800" } } diff --git a/crates/korg-verify/tests/fixtures/goldseal-v1.pubkey b/crates/korg-verify/tests/fixtures/korgcert-v1.pubkey similarity index 100% rename from crates/korg-verify/tests/fixtures/goldseal-v1.pubkey rename to crates/korg-verify/tests/fixtures/korgcert-v1.pubkey diff --git a/crates/korg-verify/tests/fuzz.rs b/crates/korg-verify/tests/fuzz.rs index 8a4f840..ed6bd13 100644 --- a/crates/korg-verify/tests/fuzz.rs +++ b/crates/korg-verify/tests/fuzz.rs @@ -1,8 +1,8 @@ //! Adversarial robustness: the verifier must NEVER panic on hostile input and //! must NEVER accept something it shouldn't. Mirrors the Python Hypothesis suite -//! (`test_goldseal_properties.py`). +//! (`test_korgcert_properties.py`). -use korg_verify::{verify_goldseal, verify_text}; +use korg_verify::{verify_korgcert, verify_text}; use proptest::prelude::*; use serde_json::Value; @@ -25,7 +25,7 @@ fn json_value() -> impl Strategy { fn fixture() -> Value { let path = format!( - "{}/tests/fixtures/goldseal-v1.json", + "{}/tests/fixtures/korgcert-v1.json", env!("CARGO_MANIFEST_DIR") ); serde_json::from_str(&std::fs::read_to_string(path).unwrap()).unwrap() @@ -47,10 +47,10 @@ fn flip(s: &str, i: usize, c: char) -> String { } proptest! { - /// verify_goldseal never panics on arbitrary JSON, and arbitrary input is never valid. + /// verify_korgcert never panics on arbitrary JSON, and arbitrary input is never valid. #[test] - fn verify_goldseal_never_panics_and_junk_is_invalid(v in json_value()) { - let verdict = verify_goldseal(&v, None); + fn verify_korgcert_never_panics_and_junk_is_invalid(v in json_value()) { + let verdict = verify_korgcert(&v, None); prop_assert!(!verdict.valid, "arbitrary JSON must never verify"); } @@ -66,7 +66,7 @@ proptest! { let mut env = fixture(); let tip = env["tip"].as_str().unwrap().to_string(); env["tip"] = Value::String(flip(&tip, i, c.chars().next().unwrap())); - prop_assert!(!verify_goldseal(&env, None).valid); + prop_assert!(!verify_korgcert(&env, None).valid); } /// Flipping any character of the seal signature is rejected. @@ -75,7 +75,7 @@ proptest! { let mut env = fixture(); let sig = env["seal"]["sig"].as_str().unwrap().to_string(); env["seal"]["sig"] = Value::String(flip(&sig, i, c.chars().next().unwrap())); - prop_assert!(!verify_goldseal(&env, None).valid); + prop_assert!(!verify_korgcert(&env, None).valid); } /// Replacing any event with arbitrary JSON is rejected. @@ -84,6 +84,6 @@ proptest! { let mut env = fixture(); let n = env["events"].as_array().unwrap().len(); env["events"][idx % n] = junk; - prop_assert!(!verify_goldseal(&env, None).valid); + prop_assert!(!verify_korgcert(&env, None).valid); } } diff --git a/crates/korg-verify/tests/goldseal.rs b/crates/korg-verify/tests/korgcert.rs similarity index 73% rename from crates/korg-verify/tests/goldseal.rs rename to crates/korg-verify/tests/korgcert.rs index 09a0eac..79d3521 100644 --- a/crates/korg-verify/tests/goldseal.rs +++ b/crates/korg-verify/tests/korgcert.rs @@ -1,14 +1,14 @@ -//! Cross-implementation goldseal@v1 interop + adversarial tamper coverage. +//! Cross-implementation korgcert@v1 interop + adversarial tamper coverage. //! -//! `goldseal-v1.json` is a frozen fixture MINTED BY PYTHON -//! (`spec/korg-ledger-v1/tools/mint_goldseal_fixture.py`, seed `[42; 32]`). This -//! proves a Python-minted Gold Seal verifies β€” unchanged β€” under the independent +//! `korgcert-v1.json` is a frozen fixture MINTED BY PYTHON +//! (`spec/korg-ledger-v1/tools/mint_korgcert_fixture.py`, seed `[42; 32]`). This +//! proves a Python-minted Certificate verifies β€” unchanged β€” under the independent //! Rust verifier (the JS verifier checks the same bytes in //! `spec/korg-ledger-v1/js/conformance.mjs`). It also pins the security //! properties: the summary cannot lie, the claim cannot move, and a stripped //! seal is a downgrade β€” not a merely-unsigned artifact. -use korg_verify::{verify_goldseal, verify_text}; +use korg_verify::{verify_korgcert, verify_text}; use serde_json::Value; fn fixture(name: &str) -> String { @@ -21,19 +21,19 @@ fn fixture(name: &str) -> String { } fn seal() -> Value { - serde_json::from_str(&fixture("goldseal-v1.json")).unwrap() + serde_json::from_str(&fixture("korgcert-v1.json")).unwrap() } fn pubkey() -> String { - fixture("goldseal-v1.pubkey").trim().to_string() + fixture("korgcert-v1.pubkey").trim().to_string() } #[test] -fn python_minted_goldseal_verifies_under_rust() { - let v = verify_text(&fixture("goldseal-v1.json"), None, None).unwrap(); +fn python_minted_korgcert_verifies_under_rust() { + let v = verify_text(&fixture("korgcert-v1.json"), None, None).unwrap(); assert_eq!( - v.kind, "goldseal", - "auto-detect must route to the goldseal path" + v.kind, "korgcert", + "auto-detect must route to the korgcert path" ); assert!( v.valid, @@ -49,12 +49,12 @@ fn python_minted_goldseal_verifies_under_rust() { fn pinned_issuer_key_is_enforced() { let env = seal(); assert!( - verify_goldseal(&env, Some(&pubkey())).valid, + verify_korgcert(&env, Some(&pubkey())).valid, "correct pin must pass" ); let wrong = "00".repeat(32); - let v = verify_goldseal(&env, Some(&wrong)); + let v = verify_korgcert(&env, Some(&wrong)); assert!(!v.valid, "a wrong pinned issuer key must fail"); assert_eq!(v.signature_ok, Some(false)); } @@ -64,7 +64,7 @@ fn a_lying_summary_is_rejected() { // Drop a touched file from the summary β€” the re-derivation must catch it. let mut env = seal(); env["summary"]["files"] = serde_json::json!(["src/app.py"]); - let v = verify_goldseal(&env, None); + let v = verify_korgcert(&env, None); assert!(!v.valid); assert_eq!(v.summary_ok, Some(false)); } @@ -73,7 +73,7 @@ fn a_lying_summary_is_rejected() { fn tampering_an_event_breaks_the_chain() { let mut env = seal(); env["events"][2]["args"]["file_path"] = Value::String("src/evil.py".into()); - let v = verify_goldseal(&env, None); + let v = verify_korgcert(&env, None); assert!(!v.valid); assert!(!v.chain_ok); } @@ -84,7 +84,7 @@ fn moving_the_claim_breaks_the_seal() { // without invalidating the seal (the chain stays intact, so only the seal fails). let mut env = seal(); env["claim"] = Value::String("did something else entirely".into()); - let v = verify_goldseal(&env, None); + let v = verify_korgcert(&env, None); assert!(!v.valid); assert!(v.chain_ok, "the event chain is untouched"); assert_eq!(v.signature_ok, Some(false)); @@ -94,8 +94,8 @@ fn moving_the_claim_breaks_the_seal() { fn a_stripped_seal_is_a_downgrade_not_merely_unsigned() { let mut env = seal(); env.as_object_mut().unwrap().remove("seal"); - let v = verify_goldseal(&env, None); - assert!(!v.valid, "a goldseal@v1 without a seal must not verify"); + let v = verify_korgcert(&env, None); + assert!(!v.valid, "a korgcert@v1 without a seal must not verify"); assert_eq!(v.signature_ok, Some(false)); } @@ -103,9 +103,9 @@ fn a_stripped_seal_is_a_downgrade_not_merely_unsigned() { fn anchors_are_bound_into_the_seal() { // The fixture carries a git-tip anchor. It must verify both structurally // (entry_hash ↔ chain) and cryptographically (the seal signs the anchor set) β€” - // anchors are no longer a detachable sidecar on a Gold Seal. + // anchors are no longer a detachable sidecar on a Certificate. let env = seal(); - let v = verify_goldseal(&env, None); + let v = verify_korgcert(&env, None); assert!(v.valid, "anchored fixture must verify: {:?}", v.errors); assert_eq!( v.anchors_ok, @@ -116,14 +116,14 @@ fn anchors_are_bound_into_the_seal() { // Strip the anchor β†’ the signed header changes β†’ the seal no longer verifies. let mut stripped = env.clone(); stripped.as_object_mut().unwrap().remove("anchors"); - let v = verify_goldseal(&stripped, None); + let v = verify_korgcert(&stripped, None); assert!(!v.valid, "stripping a bound anchor must break the seal"); assert_eq!(v.signature_ok, Some(false)); // Forge the anchor's commit proof β†’ still inside the signed header β†’ fails. let mut forged = env.clone(); forged["anchors"][0]["anchor_proof"]["commit"] = Value::String("f".repeat(40)); - let v = verify_goldseal(&forged, None); + let v = verify_korgcert(&forged, None); assert!(!v.valid, "forging an anchor proof must break the seal"); assert_eq!(v.signature_ok, Some(false)); } diff --git a/docs/goldseal/POSITIONING.md b/docs/korgcert/POSITIONING.md similarity index 89% rename from docs/goldseal/POSITIONING.md rename to docs/korgcert/POSITIONING.md index 35c37f3..0742eb3 100644 --- a/docs/goldseal/POSITIONING.md +++ b/docs/korgcert/POSITIONING.md @@ -1,12 +1,12 @@ -# The Gold Seal β€” the trust layer for AI-agent work +# The Certificate β€” the trust layer for AI-agent work > **One line:** AI agents now do real work β€” write code, move money, file tickets, > touch production. Nobody can independently verify *what an agent actually did*. -> The **Gold Seal** is a portable, cryptographically-signed certificate of an agent +> The **Certificate** is a portable, cryptographically-signed certificate of an agent > session that **anyone can re-verify offline, with zero trust in the issuer**. This document is positioning, not a spec. Every capability it claims is backed by -working, cross-language-conformant code β€” see [`GOLDSEAL.md`](../../spec/korg-ledger-v1/GOLDSEAL.md) +working, cross-language-conformant code β€” see [`KORGCERT.md`](../../spec/korg-ledger-v1/KORGCERT.md) for the normative spec and the "what we do NOT claim" section at the end. --- @@ -34,7 +34,7 @@ We do not start by selling a platform. We start by giving away a primitive that *obviously* useful and *impossible to fake*: ``` - capture β†’ tamper-evident ledger β†’ Gold Seal β†’ re-verify + capture β†’ tamper-evident ledger β†’ Certificate β†’ re-verify (zero-config hook) (hash-chain + causal DAG) (signed certificate) (anybody, offline) ``` @@ -43,10 +43,10 @@ We do not start by selling a platform. We start by giving away a primitive that - **The ledger is tamper-evident by construction.** Hash-chained, HLC-ordered, Ed25519-signable. Edit one byte and verification localizes the break to the exact step. (Shipped: `korg-ledger@v1`, three conformant implementations.) -- **The Gold Seal makes it portable and human.** One JSON object: the events, an +- **The Certificate makes it portable and human.** One JSON object: the events, an issuer signature, and a human-legible summary that is **re-derived from the events** β€” so the "files touched / tools used / steps" a person reads literally cannot lie. - (Shipped: `goldseal@v1`, this work.) + (Shipped: `korgcert@v1`, this work.) - **Re-verification needs nothing of ours.** A small dependency-light Rust binary, a stdlib Python module, or a single browser tab (Web Crypto) all check a seal byte-identically. **Zero trust in the tool that produced it.** That is the whole @@ -91,7 +91,7 @@ checks against, and the network effects accrue to whoever defines and stewards i Honesty is the brand β€” an earlier internal audit flagged and removed overclaimed features, and that discipline is the reason anyone should believe the rest of this. -- A Gold Seal proves a session is a **faithful record**, not that the work is **good**. +- A Certificate proves a session is a **faithful record**, not that the work is **good**. - It proves **authorship by a key**, not **identity** β€” key↔real-world-org binding is pinned by the relying party today; a hosted registry is future work, not a claim. - **Time** is proven by an explicit, opt-in network step, not offline. `korg-seal @@ -101,7 +101,7 @@ features, and that discipline is the reason anyone should believe the rest of th guarantee stronger than "this was in public git by then." - The hosted layers above (transparency log, registry, dashboards, marketplace) are the *business*, and are **not built yet**. What is built and verifiable today is the - primitive: capture β†’ ledger β†’ Gold Seal β†’ independent re-verification. + primitive: capture β†’ ledger β†’ Certificate β†’ independent re-verification. The bet: own the open primitive for verifiable agent work, and the trust network is the company. diff --git a/spec/korg-ledger-v1/GOLDSEAL.md b/spec/korg-ledger-v1/KORGCERT.md similarity index 90% rename from spec/korg-ledger-v1/GOLDSEAL.md rename to spec/korg-ledger-v1/KORGCERT.md index c34ddf5..c43e1a6 100644 --- a/spec/korg-ledger-v1/GOLDSEAL.md +++ b/spec/korg-ledger-v1/KORGCERT.md @@ -1,23 +1,23 @@ -# goldseal@v1 β€” Public, Independently-Verifiable Certificate +# korgcert@v1 β€” Public, Independently-Verifiable Certificate -> A **Gold Seal** is a single, self-contained JSON object that attests an AI-agent +> A **Certificate** is a single, self-contained JSON object that attests an AI-agent > session happened as claimed β€” and lets *anyone* re-verify it offline, with zero > trust in the tool that produced it. It is a strict superset of `korgex-receipt@v1` > built on the [`korg-ledger@v1`](./SPEC.md) chain primitives. -A receipt proves the event chain is intact. A Gold Seal adds the part a **human** +A receipt proves the event chain is intact. A Certificate adds the part a **human** actually reads β€” *what the agent did* β€” and binds it cryptographically so it **cannot lie**. The summary is not asserted; it is a pure function of the events, re-derived by the verifier and rejected on any mismatch. This document is normative. An implementation conforms iff it reproduces the -frozen fixture `crates/korg-verify/tests/fixtures/goldseal-v1.json` (minted by the +frozen fixture `crates/korg-verify/tests/fixtures/korgcert-v1.json` (minted by the Python reference, verified byte-identically by the Rust `korg-verify` and the JS `verify.mjs`). --- -## 1. Threat model β€” what a Gold Seal does and does not prove +## 1. Threat model β€” what a Certificate does and does not prove A green verdict proves, with **no trust in the issuer's tooling**: @@ -43,18 +43,18 @@ It explicitly does **NOT** prove, on its own: asserts; it is signature-protected (cannot be altered by a third party) but is not machine-checkable against the events. Trust it exactly as much as you trust the pinned issuer. The machine-checked facts live in `summary`. -- **Semantic correctness of the work.** A Gold Seal proves the session is a +- **Semantic correctness of the work.** A Certificate proves the session is a faithful record β€” not that the code is good. --- ## 2. Envelope -A Gold Seal is one JSON object: +A Certificate is one JSON object: | Field | Type | Bound by | Meaning | |---------------|----------------|----------|---------| -| `schema` | `"goldseal@v1"`| seal | Format identifier. | +| `schema` | `"korgcert@v1"`| seal | Format identifier. | | `spec` | `"korg-ledger@v1"` | seal | Underlying chain spec. | | `claim` | string | seal | Issuer-asserted one-line description (prose, not machine-checked). | | `issued_at` | integer | seal | Unix seconds the seal was minted (integer β€” floats are out of canonicalization scope, SPEC Β§2). | @@ -120,7 +120,7 @@ captures path-bearing arguments by the fixed key set `{file_path, path}`; this i *defined* rule (exact re-derivation), not a best-effort heuristic. > **Why this matters.** The legacy receipt signature covers only the `tip` hash β€” -> the summary a human reads is unprotected. A Gold Seal closes that gap twice over: +> the summary a human reads is unprotected. A Certificate closes that gap twice over: > the summary is re-derived from events (cannot disagree with them) *and* the seal > signs it (cannot be swapped for the events independently). @@ -131,7 +131,7 @@ captures path-bearing arguments by the fixed key set `{file_path, path}`; this i A conforming verifier, given an envelope and an optional pinned issuer key, runs in order and the verdict is **valid** iff every applicable check passes: -1. `schema == "goldseal@v1"`. +1. `schema == "korgcert@v1"`. 2. `verify_chain(events)` is empty β€” the hash chain is intact (SPEC Β§5). 3. `verify_dag(events)` is empty β€” unique `seq_id`s and strictly-earlier `triggered_by` links. @@ -140,7 +140,7 @@ order and the verdict is **valid** iff every applicable check passes: 6. `canonicalize(derive_summary(events)) == canonicalize(summary)` (Β§4). 7. **Seal present** β€” verify `seal.sig` is a valid Ed25519 signature by `seal.pubkey` over `canonicalize(header)` (Β§3). An **absent seal fails**: a - `goldseal@v1` without a seal is a *downgrade*, not a merely-unsigned artifact. + `korgcert@v1` without a seal is a *downgrade*, not a merely-unsigned artifact. 8. If a key is pinned, `seal.pubkey` MUST equal it. 9. If `anchors` is present and non-empty, each anchor's `entry_hash` MUST match the chain event at its `seq_id` (structural; SPEC Β§8.2). The anchor set is also @@ -149,10 +149,10 @@ order and the verdict is **valid** iff every applicable check passes: ### Graceful degradation -A Gold Seal carries an `events` array, so an **older, receipt-only verifier** still +A Certificate carries an `events` array, so an **older, receipt-only verifier** still checks it β€” chain + DAG + tip β€” and reports it as a valid but *unsigned* receipt (it does not know about `seal`/`summary`). That is honest: an old tool proves integrity; -a goldseal-aware tool additionally proves the summary and authorship. +a korgcert-aware tool additionally proves the summary and authorship. --- @@ -182,12 +182,12 @@ a goldseal-aware tool additionally proves the summary and authorship. ## 7. Conformance -The frozen fixture `crates/korg-verify/tests/fixtures/goldseal-v1.json` is minted by -`spec/korg-ledger-v1/tools/mint_goldseal_fixture.py` (Python, seed `[42; 32]`) and is +The frozen fixture `crates/korg-verify/tests/fixtures/korgcert-v1.json` is minted by +`spec/korg-ledger-v1/tools/mint_korgcert_fixture.py` (Python, seed `[42; 32]`) and is the cross-implementation oracle: - **Python** mints it and `korg_ledger.signing.verify_seal` round-trips it. -- **Rust** `korg-verify` verifies it (`crates/korg-verify/tests/goldseal.rs`) and the +- **Rust** `korg-verify` verifies it (`crates/korg-verify/tests/korgcert.rs`) and the `korg-verify` binary renders its summary. - **JS** `verify.mjs` verifies it and re-derives the identical summary (`spec/korg-ledger-v1/js/conformance.mjs`). diff --git a/spec/korg-ledger-v1/README.md b/spec/korg-ledger-v1/README.md index d8d3a63..38063fa 100644 --- a/spec/korg-ledger-v1/README.md +++ b/spec/korg-ledger-v1/README.md @@ -2,7 +2,7 @@ **Status:** FROZEN Β· **Canonical home:** this directory (`korg/spec/korg-ledger-v1/`) is the normative source; all implementations conform to it. -> **Try it live (zero install, sends nothing):** **[Gold Seal verifier](https://new1direction.github.io/korg/web/seal.html)** Β· **[ledger verifier](https://new1direction.github.io/korg/web/index.html)** Β· **[time-travel explorer](https://new1direction.github.io/korg/web/explore.html)** Β· **[home](https://new1direction.github.io/korg/)** +> **Try it live (zero install, sends nothing):** **[Certificate verifier](https://new1direction.github.io/korg/web/seal.html)** Β· **[ledger verifier](https://new1direction.github.io/korg/web/index.html)** Β· **[time-travel explorer](https://new1direction.github.io/korg/web/explore.html)** Β· **[home](https://new1direction.github.io/korg/)** A korg-ledger is a hash-chained log of agent events. Each event carries `prev_hash` (the previous event's `entry_hash`, or a genesis anchor) and @@ -20,11 +20,11 @@ the key), not merely tamper-evident. | File | Role | |---|---| | [`SPEC.md`](./SPEC.md) | the normative specification (canonicalization, preimage, chaining, HMAC, verify + DAG algorithms) | -| [`GOLDSEAL.md`](./GOLDSEAL.md) | **`goldseal@v1`** β€” the normative spec for the public, independently-verifiable *certificate*: a receipt superset that binds a re-derived (un-spoofable) human summary + an issuer Ed25519 seal. | +| [`KORGCERT.md`](./KORGCERT.md) | **`korgcert@v1`** β€” the normative spec for the public, independently-verifiable *certificate*: a receipt superset that binds a re-derived (un-spoofable) human summary + an issuer Ed25519 seal. | | [`vectors/`](./vectors/) + [`conformance.json`](./conformance.json) | the golden conformance vectors with **frozen tip hashes** β€” the cross-language oracle | | [`conformance.py`](./conformance.py) | a dependency-free Python reference verifier (the executable oracle) | | [`js/`](./js/) | a dependency-free JavaScript verifier (`verify.mjs`) + its conformance harness, for Node and the browser | -| [`web/`](./web/) | **two zero-install in-browser verifiers** (Web Crypto, send nothing): [`index.html`](./web/index.html) checks a raw `ledger.jsonl`; [`seal.html`](./web/seal.html) checks a **Gold Seal** β€” re-deriving the summary live so you can watch it match (or, when tampered, fail). Host on GitHub Pages and anyone verifies a shared session or seal in a tab. | +| [`web/`](./web/) | **two zero-install in-browser verifiers** (Web Crypto, send nothing): [`index.html`](./web/index.html) checks a raw `ledger.jsonl`; [`seal.html`](./web/seal.html) checks a **Certificate** β€” re-deriving the summary live so you can watch it match (or, when tampered, fail). Host on GitHub Pages and anyone verifies a shared session or seal in a tab. | ## Conformance diff --git a/spec/korg-ledger-v1/index.html b/spec/korg-ledger-v1/index.html index 6907c06..5f4e94e 100644 --- a/spec/korg-ledger-v1/index.html +++ b/spec/korg-ledger-v1/index.html @@ -5,7 +5,7 @@ korg β€” verifiable proof of what an AI agent actually did - + @@ -105,7 +105,7 @@
korg ledger - korg-ledger@v1 Β· goldseal@v1 + korg-ledger@v1 Β· korgcert@v1 runs in your browser Β· sends nothing
@@ -115,23 +115,23 @@

Proof of what an AI agent actually did.

AI agents write code, move money, and touch production β€” and nobody can independently verify what one actually did. korg is the open standard that fixes it: a - tamper-evident ledger of agent actions, and a Gold Seal + tamper-evident ledger of agent actions, and a Certificate certificate anyone can re-verify offline, with zero trust in the tool that produced it.

capture β†’ tamper-evident ledger β†’ - Gold Seal β†’ anyone re-verifies, anywhere + Certificate β†’ anyone re-verifies, anywhere
- goldseal@v1 Β· certificate -

Verify a Gold Seal

+ korgcert@v1 Β· certificate +

Verify a Certificate

Drop a signed certificate of an agent session. The page re-derives the human summary from the events (so it can't lie) and checks the issuer's Ed25519 seal. The medallion stamps gold when valid β€” and cracks red at the exact tampered field.

- Open the Gold Seal verifier β†’ + Open the Certificate verifier β†’
korg-ledger@v1 Β· raw session @@ -144,7 +144,7 @@

Verify a ledger

time-travel Β· replay

Replay a session

-

Drop a session or a Gold Seal and scrub the timeline. Watch the cumulative state β€” files +

Drop a session or a Certificate and scrub the timeline. Watch the cumulative state β€” files touched, tools used β€” evolve step by step while the hash-chain verifies under every frame. A break lights up red at the exact step it happened.

Open the time-travel explorer β†’ @@ -156,7 +156,7 @@

Why you can trust a green check

  • You don't trust us. Verification recomputes everything from the bytes you hold β€” no server, no network, nothing phones home.
  • Three independent implementations reproduce one frozen oracle, so a check in one is corroborated by two others written from the same spec.
  • -
  • The summary can't lie: a Gold Seal's human-readable summary is re-derived from the events and rejected on any mismatch.
  • +
  • The summary can't lie: a Certificate's human-readable summary is re-derived from the events and rejected on any mismatch.
Rust korg-verify @@ -166,7 +166,7 @@

Why you can trust a green check