← 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