diff --git a/.github/workflows/dependency-leaf.yml b/.github/workflows/dependency-leaf.yml new file mode 100644 index 0000000..9363391 --- /dev/null +++ b/.github/workflows/dependency-leaf.yml @@ -0,0 +1,32 @@ +name: dependency-leaf + +# The coordination protocol is a dependency leaf (CONFORMANCE.md): it depends +# on nothing but the Go standard library. This check turns a violation into a +# failed build rather than a review catch. + +on: + push: + branches: [main] + pull_request: + +permissions: + contents: read + +jobs: + leaf: + name: go.mod has zero dependencies + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: 'stable' + - name: assert dependency leaf + run: | + deps=$(go list -m all | grep -v "^$(go list -m)$" || true) + if [ -n "$deps" ]; then + echo "Dependency-leaf invariant violated. This module must depend on nothing but the Go standard library. Found:" + echo "$deps" + exit 1 + fi + echo "OK: dependency leaf holds (stdlib only)." diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..36053d8 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,38 @@ +# CLAUDE.md — sohocloud-protocol + +## What this repo is + +The substrate **coordination protocol** of Janus-Facing Architecture — and the **constitution layer**: the one artifact consumers cannot cheaply leave, because forking it incompatibly is schism. Everything here is governed by that fact: keep it minimal, keep it legible, change it deliberately. Read `CONFORMANCE.md` first; `SPEC.md` is normative. + +## Non-negotiable invariants + +Not negotiable by feature request. A change that violates one is not a smaller version of this protocol; it is a different protocol. + +- **Dependency leaf.** Never add a requirement to `go.mod`. Standard library only. CI enforces this; do not weaken the check. +- **Persons never on the wire.** No person-shaped field — name, contact, address, free-text identity — may be added to any message type. The only identity here is workload identity. +- **No economy here.** No escrow, credit, reputation, record, or adjudication types in this module. They belong to the layers above and live in front ends. +- **Fee declarations stay coordinator-signed and legible.** +- **Append-only history.** Never force-push, never rewrite published history, never move or delete a published tag. + +## Change discipline + +1. **SPEC first.** No wire behavior may exist that `SPEC.md` does not state first. A PR changing the wire without changing SPEC in the same PR is nonconformant. +2. **Every normative MUST gets a check.** A new MUST / MUST NOT in SPEC requires a conformance test or golden vector in the same PR. Prose that can fail the build is trustworthy prose. +3. **Wire change = version bump.** New tag, regenerated vectors (`go test ./vectors -run TestVectors -update`), Python reproducer kept passing, coordinated consumer bumps (SoHoLINK, Cloudy). Published tags are immutable. +4. **CONFORMANCE.md rides along.** A change that alters any binding in `CONFORMANCE.md` updates it in the same PR. +5. **Branch → PR → CI green → human review.** The author never merges their own PR. Claude drafts and proposes; a human disposes. + +## Requests to refuse or flag + +If asked to do any of the following, stop, name the tension, and surface it — do not implement quietly: + +- add a dependency "just for now" +- put person data on the wire +- add economy, reputation, record, or adjudication types here +- make the coordinator unpluggable, or any single service the sole unfederatable witness +- change wire behavior without SPEC first +- rewrite history or move a published tag + +## Tension protocol + +If you notice yourself reframing one of these constraints so a feature becomes convenient, implementing a stand-in without labeling it, or routing around an open problem instead of noting it — stop. Name the tension, attach it to the invariant or open problem by name, and propose the minimal conformant move. Surface it; do not absorb it. diff --git a/CONFORMANCE.md b/CONFORMANCE.md new file mode 100644 index 0000000..924a527 --- /dev/null +++ b/CONFORMANCE.md @@ -0,0 +1,48 @@ +# Conformance — Janus-Facing Architecture + +This document is the module's self-description in the architecture's own terms, stated **before** anything product- or deployment-specific, per the architecture's ordering rule. It follows the architecture's legibility discipline: a conformance claim is bound to the mechanism and the check that enforces it, or it is labeled a stand-in. Unbound prose is marketing. + +The architecture is **Janus-Facing Architecture (JFA)** — NTARI's unified architecture document, free documentation under the project's AGPL-3.0 commons. It names roles and mechanisms, never products; this module declares which role it fills and shows its bindings. + +## Role declaration + +This module is the **coordination protocol** of a JFA **substrate**: the dependency-leaf wire language by which participant-owned machines are recognized, advertise capability, and take and report work. The substrate guarantee it serves: **coordination on infrastructure participants can own — no unremovable hosting chokepoint.** + +| This module's term | Architecture role | +|---|---| +| Node | participant-owned infrastructure — the machines members contribute | +| Coordinator | the coordinator: node-side orchestration, a pluggable role | +| Frontend | a front end: the member-facing application | +| FeeDeclaration | the coordinator's authored, legible, contestable fee declaration | +| JobReport | a work claim (witnessed anchoring reserved — see stand-ins) | + +## Invariants and their bindings + +| Invariant (architecture) | Mechanism here | Check | +|---|---|---| +| The coordination protocol is a **dependency leaf** | `go.mod` declares zero requirements; standard library only | CI: `.github/workflows/dependency-leaf.yml` fails the build on any added dependency | +| **Persons never appear on the wire** | The only identity this module knows is workload identity — `identity/` NodeID with its canonical SPIFFE binding; no message carries a name, contact, or person-shaped field | SPEC (normative); the type surface; golden vectors | +| **No unremovable hosting chokepoint** | The coordinator is a pluggable role: a frontend MAY run its own coordinator or match directly against nodes (SPEC §1) | SPEC normative text; two consumers exist against the published tag | +| **Fee legibility** | Fees exist on the wire only as a coordinator-signed `FeeDeclaration` — authored, legible, contestable | `fees/`; golden vectors | +| **Legibility is an output, not a comment** | `SPEC.md` is normative and a build deliverable: a foreign implementer builds from it without reading this source | `testdata/vectors.json` (ground truth) + `testdata/reproduce_spec.py` (independent reproducer) + `vectors/` golden test — drift between code and committed vectors fails the build | +| **Canonical, deterministic encoding** | `canon/` length-prefixed, domain-separated signing encoder | golden vectors regenerated-and-compared byte-for-byte on every test run | + +## Deliberately out of scope + +This module is **not** the member economy, and the absence is an invariant, not a gap: no escrow, no member-issued credit, no reputation covenant, no dialog-sealed record, no dispute adjudication. Those belong to the layers above (record, covenant, economy), live in front ends, and MUST NOT migrate into this leaf. Single-participant-identity is likewise a front-end obligation — this layer knows no persons at all. + +## Stand-ins and open residuals + +Named per the architecture's honesty rule: a worked example that concealed where it falls short would only teach concealment. + +- **`anchor/` is a stub, labeled as such in its package doc.** Witnessed, append-only anchoring of employment claims (job in, result out, meter) is reserved, not built. Until it exists, a coordinator's account of completed work is self-attested — the architecture's open problem 6, coupling to open problem 2. +- **v0 is UNSTABLE.** Wire shapes may change without compatibility guarantees until v1; consumers pin published tags. +- **Sovereign compute buys mechanical, not economic, exit** (open problem 7). This protocol caps how concentrated hosting can become; it cannot fund anyone's exit. Named, not solved. + +## Dependency declaration + +Depends on: the Go standard library. Nothing else — no other NTARI module, no third-party module, nothing above or beside it in the stack. The CI check makes a violation a failed build rather than a review catch. + +## Product-specific notes (last, per the ordering rule) + +SoHoLINK is the reference coordinator; Cloudy is a reference frontend. Both consume this module by published version tag. Neither is privileged by the protocol: any conformant implementation may replace either.