Skip to content

feat(consumerapi): member-facing JSON API — slice 1 (members + techtree + market)#7

Merged
NetworkTheoryAppliedResearchInstitute merged 10 commits into
mainfrom
feat/consumerapi
Jul 13, 2026
Merged

feat(consumerapi): member-facing JSON API — slice 1 (members + techtree + market)#7
NetworkTheoryAppliedResearchInstitute merged 10 commits into
mainfrom
feat/consumerapi

Conversation

@NetworkTheoryAppliedResearchInstitute

Copy link
Copy Markdown
Contributor

The first member-facing ingress — what the React Native app will talk to, and the surface the headless cmd/cloudy root said didn't exist yet. Stacked on feat/market (PR #6).

Trust model (Phase-1 design §7.1)

Member keys never leave the device. Every member artifact (claim, reference, listing) is signed client-side and arrives sealed; the server validates (signature + registered member + invariants), stores, and routes member-local narrative to the erasable record.Locker, keeping only its hash in the commons. The server holds no member private key. Reads are public (commons has no PII); writes are authenticated by the embedded artifact signature, so slice 1 needs no session tokens.

Endpoints (/api/v1)

  • POST /members — self-signed key-possession proof → AccountID + MemberID (open, idempotent).
  • GET /members/{id}/standing — LBTAS distributions (per-category + overall + harm), never a score.
  • POST /claims — client-signed techtree claim; narrative must hash to the signed hashes, then → Locker.
  • GET /claims/{id} — structural facts + legible citation-weight breakdown + narrative from the Locker.
  • POST /references — client-signed cite/contest/reproduce/refute/builds_on edge.
  • GET /market/categories — the node-class hardware allowlist.
  • POST /market/listings — client-signed listing; Spec must be an anchored product_spec claim authored by the maker (specs are contestable claims, not certified facts).
  • GET /market/listings?category= + /market/listings/{id} — public browse, append order (never ranked/promoted); sales_count = provenance depth.

Plus cmd/cloudyd — the serving daemon.

Verification

go build/go vet clean; 4/4 httptest cases pass — full end-to-end (register → anchor product_spec → buyer reproduces it → citation weight updates → list → browse → standing) plus wrong-key-register (401), unregistered-claimant (401), and narrative-hash-mismatch (400) rejections. Live cloudyd smoke test: categories 200, missing claim 404, bad register 400. (Tests run from the project tree — WDAC blocks temp-dir binaries; Cloudy CI from PR #5 is the authoritative gate.)

Deferred slices

Economy spends/settlement (fiat + member-credit rails), disputes, contribution control, storage, session-token auth, and the per-product LBTAS join (needs the exchange-ingress that records sales).

🤖 Generated with Claude Code

…A layers + coord client

Brings the seed skeleton up to the built state: internal/economy (mutual
credit, escrow/credit policy switch), internal/covenant (LBTAS
distributions, harm flagging), internal/record (dialog-sealed witnessed
log), internal/coord (protocol httpjson client), composition tests incl.
the no-cross-layer-import tripwire. Preserves seed LICENSE.

Signed-off-by: Jodson Graves <info@ntari.org>
internal/dispute: staff-adjudicated (generic Charter roster, threshold
signatures), mode-aware resolution (escrow: signed escalation, moves no
money; credit: reputational + voluntary payee-signed refund — forced
clawback impossible by economy invariant), anchored to sealed record
entries, append-only store with a permanent adjudicated-tuple guard so a
resolved exchange can never be re-opened (double-refund prevention).

Signed-off-by: Jodson Graves <info@ntari.org>
Drops the local ../sohocloud-protocol replace so Cloudy builds from a fresh
clone and in CI (the sibling exists only on a dev machine) — mirrors the same
fix already made in SoHoLINK. The protocol is public and tagged v0.1.0.

Signed-off-by: Jodson Graves <info@ntari.org>
B5 — operator credential (internal/opcred):
- keyset.go: 7 Ed25519 keypairs, on-disk custody (0600, O_EXCL no-overwrite),
  load probes + duplicate-key detection, domain-tagged keyset Hash. Private
  keys never leave disk.
- signer.go: TransmissionSigner builds+signs a protocol OperatorTransmission
  with 2 distinct keys (21-pair rotation), 32-byte nonces, strictly-increasing
  Seq — round-trips the protocol's own Verify.
- conformance.go: fresh-oracle conformance responder (wire structs byte-match
  the coordinator's).
- cmd/operator-keygen: one-time keyset bootstrap (refuses to overwrite).

B6 — root-of-trust SEAM (device later; not blocking on hardware):
- root.go: RootSigner interface + canon-domain-tagged
  (cloudy/opcred/delegation/v0) short-lived delegations (root authorizes the
  online keyset; inclusive window, 30-day TTL cap, pinned-key verification);
  StandIn labels a file-backed root as the stand-in it is.
- root_file.go: FileRootSigner (local disk); the device/HSM slot is the named
  follow-up.

Stdlib + sohocloud-protocol only (JFA leaf discipline). Table-driven tests:
signing round-trips vs protocol Verify, keygen overwrite refusal, delegation
verify/expiry/mismatch, domain-separation replay proof.

Signed-off-by: Jodson Graves <info@ntari.org>
… countermeasures

A host storing a member's data can neither read it nor learn much from
watching it move. Implements PHASE1 design 5a/5b entirely above the
sohocloud-protocol thin waist; nothing here touches the wire, and the
wire never carries stored content.

Pipeline (5a): frame+pad to a quantized size class -> pluggable erasure
Coder -> per-shard AES-256-GCM under a random per-object key, position
bound as AAD, content-addressed by SHA-256. Object keys and manifests
live in the member-local Locker; random ObjectIDs (never content-derived
-- convergent encryption would leak plaintext equality).

The four countermeasures (5b):
1. Size quantization (quantize.go, object.go): every sealed shard in a
   class is byte-identical in length; enforced by construction and by
   TestQuantizationUniformity.
2. Placement + fetch decorrelation (placement.go, fetch.go): hard
   distinct-host rule (fails closed on insufficient hosts, incl. the
   duplicate-ID starvation case), soft distinct-owner preference,
   entropy-shuffled placement; randomized fetch order with jittered
   delays over a caller-chosen window.
3. Relay boundary (relay.go): the Relay interface is the only doorway
   for shard bytes; no host-addressed API exists in the package, so the
   compiler enforces that hosts see cloudyd's address, never a member's.
4. Audits as cover traffic (audit.go, cover.go): finite-budget
   challenge-table proof-of-storage (single-use nonced range digests,
   constant-time verify) + memoryless exponential probe cadence where
   latency-tolerant reads ride slots -- TestCoverReadRidesSlot proves the
   host-observable process is identical with or without reads.

Labeled stand-ins, house discipline: StandInSplitter (K data shards,
ZERO parity -- Reed-Solomon is the named follow-up) and the finite
challenge table (homomorphic-tag PoR is the named follow-up). The lease
+ audit wire messages are SCP v0.3 scope per
Development/SCP-completion-roadmap.md.

Stdlib only; entropy injected everywhere for deterministic tests.
21 tests, all green (full repo suite green serialized; parallel test
execution on the dev box still trips WDAC flakiness -- known issue).

Signed-off-by: Jodson Graves <info@ntari.org>
H1 (HIGH) — kill the AES-GCM nonce-reuse hazard. Shard nonces were derived
from the shard index, safe only if one ObjectKey ever encrypts one plaintext.
sealShard now draws a FRESH RANDOM 12-byte nonce per shard and prepends it
(Sealed = nonce || ciphertext||tag); openShard reads it back. Re-sealing an
edited object under the same key (the natural re-upload path) now draws
independent nonces, so (key,nonce) never repeats — the GCM catastrophe
(plaintext-XOR + auth-subkey recovery) can no longer arise even if a caller
reuses a key. Quantization survives: every sealed shard in a class is still a
fixed nonceLen+class+tag bytes. New TestReSealSameKeyUsesFreshNonce proves it.

L3 — anchor entropy to a secure default. New randOr(): every public entry
point (NewObjectKey/ID, SealObject, PlaceShards, FetchPlan,
BuildChallengeTable, NewCoverScheduler) treats a nil io.Reader as
crypto/rand, so a production caller gets CSPRNG randomness by default and only
tests pass a (deterministic) reader. TestNilRandUsesSecureDefault covers it.

L2 — replace a fictional test citation with a real tripwire. relay.go claimed
test/composition asserted the package's no-network property; it did not. New
TestNoNetworkImport walks  and fails if storage transitively
imports any net package, so a future host-dialing edit breaks the build.

L7/L8 — correct overstated doc.go claims: placement guarantees 'no single
HOST regroups', not 'no operator correlates' (a multi-node owner can
co-observe when owners are scarce); the cover cadence is near-memoryless
(clamped exponential), not strictly memoryless.

L9 — document that opcred's 0700/0600 seed-file modes are POSIX-only and do
NOT restrict access on Windows; a Windows deployment must ACL-restrict the
keyset dir itself.

Full repo suite green (go test -p 1 ./...).

Signed-off-by: Jodson Graves <info@ntari.org>
The knowledge substrate behind the Economy & Education layer's empirical-
claims surface, and the shared base the marketplace will ride (a product spec
is a Kind product_spec claim, so buyer exchanges can cite/contest maker
claims — reputation cannot be advertised into existence).

Design, held to Building JFA v2 Part III:
- Claim: a member's single-signed anchored assertion (inputs/method/result),
  closed field set — only structural facts + SHA-256 hashes in the commons,
  narrative stays member-local (no PII), Platform bound in (non-portable).
  A monologue (one seal), unlike record.Entry's dual-sealed dialog.
- Reference: typed single-signed edges (builds_on/cites/contests/reproduces/
  refutes) FROM one claim TO another; a contest is a NEW claim's edge, never
  an erasure.
- Tree: append-only, hash-chained log; verify-on-insert; asserter-owns-From;
  builds_on kept acyclic (contest edges are not cycle-constrained); OpenLog
  replays and re-verifies (tamper/reorder fail closed) — record's discipline.
- CitationWeight: a LEGIBLE, FORKABLE per-kind, distinct-asserter breakdown —
  deliberately NOT a score, and there is no ranking/collapse function. Two
  tripwire tests (reflect + go/ast) forbid any truth-certification or
  single-index symbol; ed25519 Verify is excluded (signature, not truth).

Labeled stand-ins: citation weight is economically INERT until the Sybil
approach (open problem 4) is settled — distinct-key is a weak mitigation, not
resistance; nothing pays or ranks-for-sale on it. The log is not yet
witnessed (shared record follow-up); single-writer StandIn, labeled.

Imports only the protocol canon + stdlib — a leaf like the other JFA layers;
the composition root wires it to covenant (rates the claimant) and record.

Also adds .github/workflows/ci.yml — Cloudy had NO CI (audit gap); this new
code can only truly execute in CI because Windows Application Control blocks
fresh Go test binaries on the dev host (build/vet pass locally; 9/9 techtree
tests verified by running the compiled binary from the project tree).

Signed-off-by: Jodson Graves <info@ntari.org>
#1+#4)

The manufacturer/product-listing surface (and secondary-market resale), built
on internal/techtree per the Market+TechTree plan. A leaf package like the
other JFA modules: imports only the protocol canon + stdlib and holds
cross-layer references as opaque [32]byte values; the composition root wires
it to techtree/covenant/economy.

- Category: closed allowlist aligned to the Substrate node-class taxonomy —
  computer (A), mobile_phone/tablet (B), smart_tv/tv_streaming_stick (C),
  nas_storage (D). Out-of-allowlist listings are rejected (the 'no tomatoes'
  rule); curation is by TYPE, never pay-to-list.
- Listing: maker-signed, closed field set (no free text → no PII; name/copy/
  images stay member-local). Points at a techtree product_spec claim (SpecRef)
  so advertised specs are contestable claims, not certified facts — this is how
  'reputation cannot be advertised into existence' is structural. Platform
  bound in (non-portable).
- AcceptedRails {fiat, member_credit}: the PER-MARKET half of the two-level
  credit gate. Fiat works day one; member_credit is only live once the global
  economy Mode is ModeCredit (Sybil + regulatory gated). One sovereign
  currency, per-market acceptance, never mixed or converted.
- Catalog: append-only, hash-chained, verify-on-insert, dedup, per-platform;
  RecordSale threads completed exchanges (opaque ExchangeRef) so per-product
  LBTAS = the covenant's distribution over SalesOf(listing), joined at the
  root, and a used item's prior sales are its witnessed provenance (secondary
  market, #4). ByCategory orders by APPEND, never rank/promotion. OpenLog
  replays + re-verifies (tamper/reorder fail closed).
- No paid placement / no truth authority: there is deliberately no
  promote/sponsor/boost/rank/certify field, enforced by a reflect + go/ast
  tripwire test.

Per-product LBTAS aggregation and the actual fiat/credit settlement are
composition-root concerns (this leaf provides the product identity + sales
refs; covenant/economy/payment provide distribution + settlement) — the same
opaque-ref discipline dispute uses.

go build/vet clean; 6/6 tests pass (run from the project tree; WDAC blocks
fresh test binaries in temp — CI is the authoritative gate).

Signed-off-by: Jodson Graves <info@ntari.org>
…e, market)

The first member-facing ingress the cmd/cloudy composition root said did not
exist. internal/consumerapi + cmd/cloudyd serve the React Native app's
onboarding, Technology Tree, and hardware Market surfaces (Phase-1 design §3).

Trust model (design §7.1): member keys NEVER leave the device. Every
member-authored artifact — claim, reference, listing — is signed CLIENT-SIDE
and arrives already sealed; the server VALIDATES (signature + a registered
member + invariants), stores, and routes the member-local narrative to the
erasable record.Locker, keeping only its hash in the commons. The server holds
no member private key. Reads are public (the commons carries no PII; JFA 'no
information asymmetry') so GET endpoints need no auth; writes are authenticated
by the embedded artifact signature itself, so slice 1 needs no session tokens.

Endpoints (/api/v1):
- POST /members — self-signed key-possession proof → mints AccountID+MemberID
  (open, idempotent onboarding).
- GET  /members/{id}/standing — LBTAS distributions per category + pooled +
  harm count; never a score.
- POST /claims — ingest a client-signed techtree claim; narrative must hash to
  the signed hashes, then goes to the Locker (commons keeps only the hash).
- GET  /claims/{id} — structural facts + legible citation-weight breakdown +
  narrative from the Locker.
- POST /references — client-signed citation/contest/reproduce edge.
- GET  /market/categories — the node-class hardware allowlist.
- POST /market/listings — client-signed listing; requires the Spec to be an
  anchored product_spec claim authored by the maker (specs are contestable
  claims, not certified facts).
- GET  /market/listings?category= and /market/listings/{id} — public browse,
  append order (never ranked/promoted); per-listing sales_count = provenance
  depth.

Slice 1 uses in-memory stores + ephemeral platform like cmd/cloudy — honest
about not being durable. Deferred slices: economy spends/settlement, disputes,
contribution control, storage, and session-token auth.

Verified: full go build/vet clean; 4/4 httptest cases pass (end-to-end
register→anchor spec→reproduce→citation-weight→list→browse→standing, plus
wrong-key-register, unregistered-claimant, and narrative-hash-mismatch
rejections); live cloudyd smoke test (categories 200, missing claim 404,
bad register 400). Tests run from the project tree (WDAC blocks temp-dir test
binaries); Cloudy CI (added in PR #5) is the authoritative gate.

Signed-off-by: Jodson Graves <info@ntari.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant