diff --git a/README.md b/README.md index 92003b2..b31d89b 100644 --- a/README.md +++ b/README.md @@ -5,154 +5,32 @@ [![Discussions](https://img.shields.io/github/discussions/local-loop-io/loop-protocol)](https://github.com/local-loop-io/loop-protocol/discussions) [![Specification](https://img.shields.io/badge/spec-v0.1-orange.svg)](SPECIFICATION.md) -> Local Optimization with Overflow Protocol - Infrastructure for planetary-scale circular economy +Local Optimization with Overflow Protocol. A federated circular economy concept with shared schemas and interoperability notes. -## ๐ŸŒ What is LOOP Protocol? +> Early-stage, low-TRL concept. No public pilots or deployments. Lab demo only. -LOOP Protocol proposes a federated circular economy network that preserves local sovereignty. Think of it as "email for materials" โ€” a conceptual framework where cities could run interoperable nodes and exchange materials via shared standards. - -### Core Components - -- **๐Ÿงฌ MaterialDNA (proposed)**: Universal identification for any material or batch -- **๐Ÿ’ฐ LoopCoin (proposed)**: Expiring local currency that incentivizes circulation -- **๐Ÿ“ก LoopSignal (proposed)**: Democratic preferences that shape material flows -- **๐Ÿ’ธ LoopCost (proposed)**: Transparent calculation including all penalties - -## ๐Ÿš€ Quick Start - -### For Cities -```bash -# Prototype-only example (no official images yet) -# docker run -p 8080:8080 localloop/node:latest -``` - -### For Developers - -```javascript -// Proposed SDK interface (no public SDK published yet) -// npm install @local-loop/sdk - -// Example client usage (illustrative only) -const loop = new LoopClient('https://example.loop'); - -// Register material -const material = await loop.registerMaterial({ - category: 'plastic-pet', - quantity: { value: 1000, unit: 'kg' }, - quality: 0.95 -}); -``` - -#### Schema Validation +## What is here +- `SPECIFICATION.md`: core protocol spec. +- `schemas/`: JSON Schema definitions. +- `examples/`: sample payloads for interop. +- `contexts/`: JSON-LD contexts. +- `rfcs/`: RFCs and design notes. +## Quickstart ```bash -npm install +npm ci npm test ``` -## ๐Ÿ“‹ Specification - -Read the full LOOP Protocol Specification v0.1.1 (lab demo baseline) - -### Design Goals - -- **Federated Architecture**: No central authority required -- **Economic Incentives**: Communities keep value from circular behaviors -- **Democratic Control**: Citizens vote on trade preferences -- **Transparent Routing**: Materials find optimal destinations -- **Open Standards**: JSON-LD based, REST APIs - -## ๐Ÿ—๏ธ Implementations - -Implementations are exploratory and may not be publicly available yet. If you -are building one, open an issue or RFC so we can link it here. - -## ๐Ÿงช Test Network - -No public test network is available yet. This is an early, low-TRL concept and the -reference materials here are for specification and lab-only research demonstrations. - -## ๐Ÿค Contributing - -We welcome contributions! See CONTRIBUTING.md for guidelines. - -### How to Contribute - -1. Read the specification -2. Check [open issues](https://github.com/local-loop-io/loop-protocol/issues) -3. Start a discussion in GitHub Discussions -4. Submit RFCs for protocol changes +## Usage notes +- Specs and schemas are draft and subject to change. +- No certified implementations exist at this time. +- Use `examples/` to validate tooling and docs. -## ๐Ÿงญ Governance (Lab Stage) +## Links +- Docs hub: https://local-loop-io.github.io +- Backend API: https://loop-api.urbnia.com -- RFC workflow: `docs/governance/rfc-process.md` -- RFC index: `rfcs/README.md` - -## ๐Ÿ“š Resources - -Public resources are still being assembled. For now, use GitHub Discussions and -the repository documentation. - -- Specification: `SPECIFICATION.md` -- API reference: `openapi.json` -- Security guide: `docs/security-guide.md` -- Federation handshake: `docs/federation-handshake.md` -- Threat model: `docs/compliance/threat-model.md` -- DPIA lite: `docs/compliance/dpia-lite.md` - -## ๐ŸŽฏ Roadmap - -### v0.1.1 (Current) - -- โœ… Minimal interop flow (MaterialDNA โ†’ Offer โ†’ Match โ†’ Transfer) -- โœ… Reference documentation and schemas -- โณ Prototype implementations (lab demo in progress) - -### v0.2 (Planned) - -- Smart contract integration -- Advanced routing algorithms -- Mobile SDKs -- Pilot deployments with research partners (if/when available) - -### v1.0 (Planned) - -- Stable protocol -- Aspirational multi-city adoption -- Ecosystem marketplace -- Carbon credit integration - -## ๐Ÿ“Š Adoption - -There are no public pilots or deployments at this time. We are actively seeking -collaboration partners and research contributors. - -## โš–๏ธ License - -MIT License - see LICENSE for details - -## ๐Ÿ™ Acknowledgments - -- Inspired by [ActivityPub](https://www.w3.org/TR/activitypub/) for federation -- [Carlsson & Nevzorova](https://doi.org/10.1016/j.clet.2025.100911) for MaterialDNA concept -- The circular economy community worldwide - ---- - -_Building the material internet, one city at a time_ ๐Ÿ™๏ธ - -## How to Cite - -If you reference this repository, please cite: -Alphin Tom. "LOOP Protocol Specification and Schemas." LocalLoop, GitHub repository, 2025-2026. https://github.com/local-loop-io/loop-protocol - -```bibtex -@misc{localloop_protocol_2025, - author = {Alphin Tom}, - title = {LOOP Protocol Specification and Schemas}, - year = {2025}, - howpublished = {GitHub repository}, - url = {https://github.com/local-loop-io/loop-protocol}, - note = {Accessed 2025-12-19} -} -``` +## Contributing +- Use `rfcs/` for feedback and proposals. +- See `../AGENTS.md` for org context and domain policy. diff --git a/SPECIFICATION.md b/SPECIFICATION.md index ce77dfe..1007b81 100644 --- a/SPECIFICATION.md +++ b/SPECIFICATION.md @@ -595,6 +595,38 @@ Response: 200 OK } ``` +#### Material Management (Lab Demo Extensions) + +The following endpoint is optional and used by the LocalLoop lab demo only. It is not required for protocol compliance. + +**POST /api/loop/material-status** + +```http +POST /api/loop/material-status +Content-Type: application/ld+json +X-API-Key: {api-key} + +{ + "@context": "https://local-loop-io.github.io/projects/loop-protocol/contexts/loop-v0.1.1.jsonld", + "@type": "MaterialStatusUpdate", + "schema_version": "0.1.1", + "id": "3c9a6a0b-8c1a-4d3f-9c2c-3c1c2f9d5c2a", + "material_id": "DE-MUC-2025-PLASTIC-B847F3", + "status": "reserved", + "updated_at": "2025-06-03T09:15:00Z", + "reason": "Reserved by city exchange", + "notes": "Holding until pickup is confirmed", + "source_node": "lab-hub.loop", + "metadata": { "ticket": "LAB-42" } +} + +Response: 201 Created +{ + "id": "3c9a6a0b-8c1a-4d3f-9c2c-3c1c2f9d5c2a", + "created_at": "2025-06-03T09:15:05Z" +} +``` + #### Node Information **GET /api/v1/node/info** diff --git a/examples/09-material-status.json b/examples/09-material-status.json new file mode 100644 index 0000000..f773943 --- /dev/null +++ b/examples/09-material-status.json @@ -0,0 +1,15 @@ +{ + "@context": "https://local-loop-io.github.io/projects/loop-protocol/contexts/loop-v0.1.1.jsonld", + "@type": "MaterialStatusUpdate", + "schema_version": "0.1.1", + "id": "3c9a6a0b-8c1a-4d3f-9c2c-3c1c2f9d5c2a", + "material_id": "DE-MUC-2025-PLASTIC-B847F3", + "status": "reserved", + "updated_at": "2025-06-03T09:15:00Z", + "reason": "Reserved by city exchange", + "notes": "Holding until pickup is confirmed", + "source_node": "lab-hub.loop", + "metadata": { + "ticket": "LAB-42" + } +} diff --git a/examples/README.md b/examples/README.md index de35e99..7447dcf 100644 --- a/examples/README.md +++ b/examples/README.md @@ -11,6 +11,7 @@ This directory contains JSON examples that validate against the schemas in `sche - `06-offer.json`: Minimal interop offer payload (v0.1.1). - `07-match.json`: Minimal interop match payload (v0.1.1). - `08-transfer.json`: Minimal interop transfer payload (v0.1.1). +- `09-material-status.json`: Material status update payload (v0.1.1 lab demo). ## Validation Run `npm test` from the repository root to validate all examples. diff --git a/openapi.json b/openapi.json new file mode 100644 index 0000000..0f87358 --- /dev/null +++ b/openapi.json @@ -0,0 +1,290 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "LOOP Protocol API", + "version": "0.1.1", + "description": "Reference API surface for LOOP Protocol v0.1.1 (lab demo baseline)." + }, + "servers": [ + { + "url": "https://{node}/api/v1", + "variables": { + "node": { + "default": "example.loop", + "description": "LOOP node host (e.g. munich.loop)" + } + } + } + ], + "tags": [ + { "name": "Material" }, + { "name": "Node" }, + { "name": "Signals" }, + { "name": "Transactions" }, + { "name": "Federation" } + ], + "components": { + "securitySchemes": { + "BearerAuth": { + "type": "http", + "scheme": "bearer" + } + }, + "schemas": { + "MaterialDNA": { + "$ref": "https://local-loop-io.github.io/projects/loop-protocol/schemas/v0.1.1/material-dna.schema.json" + }, + "Offer": { + "$ref": "https://local-loop-io.github.io/projects/loop-protocol/schemas/v0.1.1/offer.schema.json" + }, + "Match": { + "$ref": "https://local-loop-io.github.io/projects/loop-protocol/schemas/v0.1.1/match.schema.json" + }, + "Transfer": { + "$ref": "https://local-loop-io.github.io/projects/loop-protocol/schemas/v0.1.1/transfer.schema.json" + }, + "NodeInfo": { + "$ref": "https://local-loop-io.github.io/projects/loop-protocol/schemas/v1/node-info.schema.json" + }, + "LoopSignalConfig": { + "$ref": "https://local-loop-io.github.io/projects/loop-protocol/schemas/v1/loopsignal.schema.json" + }, + "Transaction": { + "$ref": "https://local-loop-io.github.io/projects/loop-protocol/schemas/v1/transaction.schema.json" + }, + "TransactionStatus": { + "type": "object", + "properties": { + "@context": { "type": "string" }, + "@type": { "type": "string" }, + "transaction_id": { "type": "string" }, + "status": { "type": "string" }, + "updated_at": { "type": "string", "format": "date-time" }, + "settlement_url": { "type": "string" } + } + }, + "MaterialSearchRequest": { + "type": "object", + "properties": { + "category": { "type": "string" }, + "radius_km": { "type": "number" }, + "min_quantity": { "type": "number" }, + "max_loop_cost": { "type": "number" } + }, + "additionalProperties": false + }, + "MaterialSearchResponse": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { "$ref": "#/components/schemas/MaterialDNA" } + }, + "total": { "type": "integer" }, + "next": { "type": "string" } + } + }, + "MaterialAnnouncement": { + "type": "object", + "properties": { + "@context": { "type": "string" }, + "@type": { "type": "string" }, + "material": { "type": "string" }, + "origin": { "type": "string" }, + "available": { "type": "boolean" } + }, + "required": ["@context", "@type", "material", "origin", "available"] + }, + "MaterialOffer": { + "type": "object", + "properties": { + "@context": { "type": "string" }, + "@type": { "type": "string" }, + "material": { "type": "string" }, + "from": { "type": "string" }, + "base_price": { "type": "number" }, + "loop_cost": { "type": "number" }, + "valid_until": { "type": "string", "format": "date-time" } + }, + "required": ["@context", "@type", "material", "from", "base_price", "loop_cost", "valid_until"] + }, + "ErrorResponse": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "code": { "type": "string" }, + "message": { "type": "string" }, + "details": { "type": "object" } + } + } + } + } + } + }, + "security": [ + { "BearerAuth": [] } + ], + "paths": { + "/material": { + "post": { + "tags": ["Material"], + "summary": "Register MaterialDNA", + "requestBody": { + "required": true, + "content": { + "application/ld+json": { + "schema": { "$ref": "#/components/schemas/MaterialDNA" } + } + } + }, + "responses": { + "201": { + "description": "Created", + "content": { + "application/ld+json": { + "schema": { "$ref": "#/components/schemas/MaterialDNA" } + } + } + }, + "400": { "description": "Invalid request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } + } + } + }, + "/material/{id}": { + "get": { + "tags": ["Material"], + "summary": "Get MaterialDNA by ID", + "parameters": [ + { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/ld+json": { + "schema": { "$ref": "#/components/schemas/MaterialDNA" } + } + } + }, + "404": { "description": "Not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } + } + } + }, + "/material/search": { + "post": { + "tags": ["Material"], + "summary": "Search materials", + "requestBody": { + "required": true, + "content": { + "application/ld+json": { + "schema": { "$ref": "#/components/schemas/MaterialSearchRequest" } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/MaterialSearchResponse" } + } + } + } + } + } + }, + "/node/info": { + "get": { + "tags": ["Node"], + "summary": "Get node information", + "responses": { + "200": { + "description": "OK", + "content": { + "application/ld+json": { + "schema": { "$ref": "#/components/schemas/NodeInfo" } + } + } + } + } + } + }, + "/signals": { + "get": { + "tags": ["Signals"], + "summary": "Get LoopSignal configuration", + "responses": { + "200": { + "description": "OK", + "content": { + "application/ld+json": { + "schema": { "$ref": "#/components/schemas/LoopSignalConfig" } + } + } + } + } + } + }, + "/transaction": { + "post": { + "tags": ["Transactions"], + "summary": "Create transaction", + "requestBody": { + "required": true, + "content": { + "application/ld+json": { + "schema": { "$ref": "#/components/schemas/Transaction" } + } + } + }, + "responses": { + "201": { + "description": "Created", + "content": { + "application/ld+json": { + "schema": { "$ref": "#/components/schemas/TransactionStatus" } + } + } + } + } + } + }, + "/federate/announce": { + "post": { + "tags": ["Federation"], + "summary": "Announce material availability", + "requestBody": { + "required": true, + "content": { + "application/ld+json": { + "schema": { "$ref": "#/components/schemas/MaterialAnnouncement" } + } + } + }, + "responses": { + "202": { "description": "Accepted" } + } + } + }, + "/federate/offer": { + "post": { + "tags": ["Federation"], + "summary": "Send offer to a node", + "requestBody": { + "required": true, + "content": { + "application/ld+json": { + "schema": { "$ref": "#/components/schemas/MaterialOffer" } + } + } + }, + "responses": { + "202": { "description": "Accepted" } + } + } + } + } +} diff --git a/package-lock.json b/package-lock.json index 7e67c16..84ff893 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "devDependencies": { "ajv": "^8.17.1", "ajv-formats": "^3.0.1", - "fast-glob": "^3.3.2" + "fast-glob": "^3.3.3" } }, "node_modules/@nodelib/fs.scandir": { diff --git a/package.json b/package.json index d1c77b6..a22907c 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,12 @@ "private": true, "description": "LOOP Protocol specification and schemas", "license": "MIT", + "engines": { + "node": ">=25.1.0" + }, + "volta": { + "node": "25.1.0" + }, "scripts": { "validate:schemas": "node scripts/validate-schemas.js", "test": "npm run validate:schemas" @@ -11,6 +17,6 @@ "devDependencies": { "ajv": "^8.17.1", "ajv-formats": "^3.0.1", - "fast-glob": "^3.3.2" + "fast-glob": "^3.3.3" } } diff --git a/schemas/README.md b/schemas/README.md index 69c3f83..803b089 100644 --- a/schemas/README.md +++ b/schemas/README.md @@ -7,6 +7,7 @@ JSON Schema definitions for LOOP Protocol payloads. - `offer.schema.json` (v0.1.1 minimal interop) - `match.schema.json` (v0.1.1 minimal interop) - `transfer.schema.json` (v0.1.1 minimal interop) +- `material-status.schema.json` (v0.1.1 lab demo) - `handshake.schema.json` (v0.1.1 lab federation) - `loopcoin.schema.json` - `loopsignal.schema.json` diff --git a/schemas/material-status.schema.json b/schemas/material-status.schema.json new file mode 100644 index 0000000..5507f62 --- /dev/null +++ b/schemas/material-status.schema.json @@ -0,0 +1,95 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://local-loop-io.github.io/projects/loop-protocol/schemas/v0.1.1/material-status.schema.json", + "title": "MaterialStatusUpdate", + "description": "Status update for a MaterialDNA record (LOOP Protocol v0.1.1 lab demo)", + "type": "object", + "required": [ + "@context", + "@type", + "schema_version", + "id", + "material_id", + "status", + "updated_at" + ], + "properties": { + "@context": { + "description": "JSON-LD context", + "type": "string", + "const": "https://local-loop-io.github.io/projects/loop-protocol/contexts/loop-v0.1.1.jsonld" + }, + "@type": { + "description": "Object type identifier", + "type": "string", + "const": "MaterialStatusUpdate" + }, + "schema_version": { + "description": "Schema version for the minimal interop flow", + "type": "string", + "const": "0.1.1" + }, + "id": { + "description": "Status update identifier", + "type": "string", + "format": "uuid", + "examples": [ + "3c9a6a0b-8c1a-4d3f-9c2c-3c1c2f9d5c2a" + ] + }, + "material_id": { + "description": "MaterialDNA identifier", + "type": "string", + "pattern": "^[A-Z]{2}-[A-Z]{3}-\\d{4}-[A-Z]+-[A-Z0-9]{6,}$", + "examples": [ + "DE-MUC-2025-PLASTIC-B847F3" + ] + }, + "status": { + "description": "Material availability status", + "type": "string", + "enum": [ + "available", + "reserved", + "withdrawn" + ] + }, + "updated_at": { + "description": "Status update timestamp (ISO 8601)", + "type": "string", + "format": "date-time", + "examples": [ + "2025-06-03T09:15:00Z" + ] + }, + "reason": { + "description": "Optional reason for the status change", + "type": "string", + "maxLength": 120, + "examples": [ + "Reserved by city exchange" + ] + }, + "notes": { + "description": "Optional notes", + "type": "string", + "maxLength": 500, + "examples": [ + "Holding until pickup is confirmed" + ] + }, + "source_node": { + "description": "Node emitting the status update", + "type": "string", + "maxLength": 120, + "examples": [ + "lab-hub.loop" + ] + }, + "metadata": { + "description": "Additional metadata", + "type": "object", + "additionalProperties": true + } + } +} diff --git a/scripts/check-domains.sh b/scripts/check-domains.sh index a06083a..9ac773f 100755 --- a/scripts/check-domains.sh +++ b/scripts/check-domains.sh @@ -18,7 +18,7 @@ if command -v rg >/dev/null 2>&1; then exit 1 fi else - if grep -RIn -E "(${pattern})" --exclude-dir=node_modules --exclude-dir=.git --exclude-dir=dist --exclude=DOMAIN-POLICY.md --exclude=scripts/check-domains.sh . ; then + if grep -RIn -E "(${pattern})" --exclude-dir=node_modules --exclude-dir=.git --exclude-dir=dist --exclude=DOMAIN-POLICY.md --exclude=check-domains.sh . ; then echo "Banned domains found." exit 1 fi