← All docs

freeq Policy & Authority Framework — Demo Walkthrough

This demonstrates freeq's cryptographic channel governance: policy-gated channels with auditable membership, built on standard IRC.

Prerequisites

  • freeq server binary (cargo build --release --bin freeq-server)
  • A Bluesky account (for authenticated demo)
  • Any IRC client (for guest demo)
  • curl and python3 (for API inspection)

Quick Start

# Terminal 1: Start server
cargo run --release --bin freeq-server -- \
  --listen-addr 127.0.0.1:16799 \
  --web-addr 127.0.0.1:8080

# Terminal 2: Run interactive demo
./scripts/demo.sh

The demo script walks you through each scene with pauses. Or follow the manual steps below.


Manual Walkthrough

1. Connect a Guest (Standard IRC Client)

Any IRC client works. Using netcat to prove the point:

$ nc 127.0.0.1 16799
NICK guest42
USER guest 0 * :Guest
JOIN #general
PRIVMSG #general :Hello from a standard IRC client!

This works. Always will. Policy framework doesn't break existing IRC.

2. Connect an Authenticated User

Open the web client at http://127.0.0.1:8080:

  1. Click "Sign in with Bluesky"
  2. Complete OAuth flow
  3. You're now authenticated with your AT Protocol DID

Or use the TUI client with SASL.

3. Create a Channel and Set a Policy

As the authenticated user (who'll be channel op):

/join #freeq-dev

You're the founder — you have ops. Now set a policy:

/POLICY #freeq-dev SET By joining this channel you agree to the freeq Code of Conduct: be respectful, no spam, no harassment. Violations result in removal.

Response:

Policy set for #freeq-dev (version 1, rules_hash=a1b2c3d4e5f6, policy_id=d4e5f6789abc)

You're auto-attested (the op who created the policy is always accepted).

4. Guest Tries to Join — Rejected

The guest user tries:

JOIN #freeq-dev

Response:

477 guest42 #freeq-dev :This channel requires authentication — sign in to join

Clean rejection. The guest can still use #general and any other open channel.

5. Second Authenticated User Accepts Policy

A second user signs in with Bluesky, then:

/POLICY #freeq-dev INFO

Response:

Policy for #freeq-dev:
  Version: 1
  Policy ID: d4e5f6789abc...
  Effective: 2026-02-18T15:55:32+00:00
  Validity: JoinTime
  Requirement: ACCEPT(a1b2c3d4e5f6...)

Accept the policy:

/POLICY #freeq-dev ACCEPT

Response:

Policy accepted for #freeq-dev — role: member. You may now JOIN.

Now join:

/join #freeq-dev

Success. They're in with a verified badge.

6. Inspect the Audit Trail

Current policy:

curl -s http://127.0.0.1:8080/api/v1/policy/%23freeq-dev | python3 -m json.tool
{
    "policy": {
        "channel_id": "#freeq-dev",
        "policy_id": "d4e5f6...",
        "version": 1,
        "effective_at": "2026-02-18T15:55:32.123456+00:00",
        "authority_set_hash": "abc123...",
        "requirements": {
            "type": "ACCEPT",
            "hash": "a1b2c3..."
        },
        "validity_model": "join_time",
        "receipt_embedding": "require"
    },
    "authority_set": {
        "channel_id": "#freeq-dev",
        "signers": [{"did": "did:web:127.0.0.1", ...}],
        "policy_threshold": 1
    }
}

Transparency log (privacy-preserving — no DIDs):

curl -s http://127.0.0.1:8080/api/v1/policy/%23freeq-dev/transparency | python3 -m json.tool
[
    {
        "entry_version": 1,
        "channel_id": "#freeq-dev",
        "policy_id": "d4e5f6...",
        "attestation_hash": "789abc...",
        "issued_at": "2026-02-18T15:56:01.000000+00:00",
        "issuer_authority_id": "did:web:127.0.0.1"
    }
]

Check specific membership:

curl -s http://127.0.0.1:8080/api/v1/policy/%23freeq-dev/membership/did:plc:abc123 | python3 -m json.tool

Policy version history:

curl -s http://127.0.0.1:8080/api/v1/policy/%23freeq-dev/history | python3 -m json.tool

7. Update the Policy

/POLICY #freeq-dev SET Updated Code of Conduct v2: be respectful, no spam, no harassment, no AI-generated content without disclosure.

This creates version 2, chained to version 1. Existing members keep their attestations (JoinTime model). New joiners must accept v2.

8. Remove the Policy

/POLICY #freeq-dev CLEAR

Channel returns to open join. All attestations and logs are removed.


Use Cases

Open Source Project Governance

POLICY #myproject SET <Code of Conduct text>
  • Contributors accept CoC on join
  • Audit trail proves CoC was in place when incident occurred
  • Policy updates create version chain — full history preserved

Role-Based Access via GitHub Org Membership

This is a full working example. A project channel where accepting the Code of Conduct gets you in, but GitHub org members automatically get ops.

Step 1: Create the channel and set base policy

/join #myproject
/POLICY #myproject SET By contributing to this project you agree to our Code of Conduct: be respectful, inclusive, and constructive.

Step 2: Add role escalation — org members get ops

First, get the rules hash from POLICY INFO:

/POLICY #myproject INFO
→ Requirement: ACCEPT(a1b2c3d4e5f6...)

Then set the "op" role requirement (replace the hash):

/POLICY #myproject SET-ROLE op {"type":"ALL","requirements":[{"type":"ACCEPT","hash":"a1b2c3d4e5f6...full-hash..."},{"type":"PRESENT","credential_type":"github_membership","issuer":"github"}]}

Step 3: A contributor verifies their GitHub identity

With GitHub OAuth configured (GITHUB_CLIENT_ID + GITHUB_CLIENT_SECRET):

/POLICY #myproject VERIFY github myorg
→ Open this URL to verify your GitHub identity: http://127.0.0.1:8080/auth/github?did=...&org=myorg

Click the link → GitHub OAuth → authenticate → server confirms org membership → credential stored. The server knows your real GitHub username from the OAuth token — no self-attestation possible.

Without GitHub OAuth (public API fallback — weaker):

/POLICY #myproject VERIFY github myorg octocat
→ ⚠ Note: public API check cannot prove you own this GitHub account
→ ✓ octocat is a public member of myorg. Credential stored (⚠ not OAuth-verified).

Step 4: The contributor accepts the policy

/POLICY #myproject ACCEPT
→ Policy accepted for #myproject — role: op. You may now JOIN.

They got op because they have both the CoC acceptance AND the GitHub credential.

Step 5: Join — auto +o

/join #myproject
→ (joined with +o automatically)

A user without GitHub membership who accepts the same policy gets member role — no ops, but they can chat.

API alternative (for web/mobile):

# Verify GitHub membership
curl -X POST http://localhost:8080/api/v1/verify/github \
  -H 'Content-Type: application/json' \
  -d '{"did":"did:plc:abc123","github_username":"octocat","org":"myorg"}'

# Check stored credentials
curl http://localhost:8080/api/v1/credentials/did:plc:abc123

# Submit join with evidence
curl -X POST http://localhost:8080/api/v1/policy/%23myproject/join \
  -H 'Content-Type: application/json' \
  -d '{"subject_did":"did:plc:abc123","accepted_hashes":["a1b2c3..."],"credentials":[{"credential_type":"github_membership","issuer":"github"}]}'
  • Regular users who accept CoC → member role
  • GitHub org members who accept CoC → op role → automatic +o on join
  • No manual op management needed

Invite-Only Communities

POLICY #private SET PRESENT(invitation, issuer=did:plc:channelowner)
  • Only users with an invitation credential from the owner can join
  • Invitation issuance is a separate step (via API)

Token-Gated Channels

POLICY #holders SET PROVE(nft_ownership)
  • Users must prove ownership of a specific token
  • Proof verification is pluggable

Company Channels

POLICY #acme-eng SET ALL(ACCEPT(employee_handbook), PRESENT(email_verified, issuer=acme.com))
  • Must accept employee handbook AND have verified @acme.com email
  • Policy changes cascade to S2S peers

Architecture Notes

What happens at each layer

Action IRC Layer Policy Layer Crypto Layer
/POLICY SET NOTICE reply PolicyDocument created, versioned SHA-256 hash chain
/POLICY ACCEPT NOTICE reply Evidence evaluated, JoinReceipt + MembershipAttestation created HMAC-SHA256 signed attestation
JOIN #channel Standard JOIN flow Attestation checked (or skipped if no policy) Signature verified
Policy update NOTICE reply New version chained to previous Hash chain integrity
S2S sync — PolicySync message to peers Attestation signatures portable

Requirement DSL

ACCEPT { hash }              — User accepted rules document
PRESENT { type, issuer }     — User has credential of type from issuer
PROVE { proof_type }         — User can prove capability
ALL { requirements[] }       — All must be satisfied
ANY { requirements[] }       — At least one must be satisfied
NOT { requirement }          — Must NOT be satisfied

Max depth: 8. Max nodes: 64. Deterministic, fail-closed.

Validity Models

  • JoinTime — Evaluate once at join. Attestation never expires.
  • Continuous — Attestation expires (default: 1 hour). Background task revalidates.

Privacy

  • Transparency log entries contain attestation hashes, not DIDs
  • Membership checks require knowing the specific DID to query
  • Log proves that attestations were issued, not to whom

HTTP API Reference

Method Endpoint Description
GET /api/v1/policy/{channel} Current policy + authority set
GET /api/v1/policy/{channel}/history Full policy version chain
POST /api/v1/policy/{channel}/join Submit evidence, receive attestation
GET /api/v1/policy/{channel}/membership/{did} Check membership status
GET /api/v1/policy/{channel}/transparency Audit log entries
GET /api/v1/authority/{hash} Authority set by hash

POST /api/v1/policy/{channel}/join

{
    "subject_did": "did:plc:abc123",
    "accepted_hashes": ["a1b2c3..."],
    "credentials": [
        {"credential_type": "github_membership", "issuer": "github"}
    ],
    "proofs": ["nft_ownership_proof"]
}

Response (success):

{
    "status": "confirmed",
    "join_id": "deadbeef...",
    "attestation": {
        "attestation_id": "...",
        "channel_id": "#freeq-dev",
        "subject_did": "did:plc:abc123",
        "role": "member",
        "issued_at": "2026-02-18T15:56:01Z",
        "signature": "hmac-sha256:..."
    }
}

Response (rejected):

{
    "status": "failed",
    "error": "Requirement not satisfied: ACCEPT(a1b2c3...)"
}
Supported by
Powered by