Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
cbc3b55
feat: DFOS credentials, beacon manifests, revocations, and credential…
bvalosek Apr 14, 2026
1175a71
feat: relay store + ingestion for revocations and public credentials …
bvalosek Apr 14, 2026
8820b47
feat: credential-based content-plane auth and capabilities (Step 6)
bvalosek Apr 14, 2026
3b92d8b
feat: documents endpoint — GET /content/:contentId/documents (Step 7)
bvalosek Apr 14, 2026
8a737c2
feat: reference content stream schema and worked example (Step 8)
bvalosek Apr 14, 2026
7ab4e2f
docs: CREDENTIALS.md, SIWD.md, and spec updates (Step 9)
bvalosek Apr 14, 2026
19e6d88
fix: review fixes — revocation scoping, version fields, spec alignmen…
bvalosek Apr 14, 2026
a52cb8e
fix: update Go packages for new credential/beacon format + prettier
bvalosek Apr 14, 2026
197a880
fix: Go relay/conformance updates + prettier formatting
bvalosek Apr 14, 2026
80f5930
fix: dfos-cli beacon command, site-protocol poster removal, conforman…
bvalosek Apr 14, 2026
386924a
feat: full protocol parity — Go relay, credential revocation, manifes…
bvalosek Apr 14, 2026
f8446ab
style: prettier formatting for dfos-credential.ts
bvalosek Apr 14, 2026
4446030
feat: Go relay auth parity, beacon version restoration, test coverage
bvalosek Apr 14, 2026
e10ff00
fix: review findings — historical key resolution, chain:* wildcard, d…
bvalosek Apr 14, 2026
76df1d3
fix: unexport verifyDelegationChain, handle chain:* in content author…
bvalosek Apr 14, 2026
435e588
fix: spec chain:* wildcard docs, Go dedup, conformance test fixes
bvalosek Apr 14, 2026
8550350
feat: add credentials and SIWD pages to protocol site
bvalosek Apr 15, 2026
b4c9521
test: Go delegation unit tests and conformance tests for credential s…
bvalosek Apr 15, 2026
f12937d
fix: credential verification uses historical key resolution at ingestion
bvalosek Apr 15, 2026
f366852
refactor: clean up credential API, extract shared resolver, fix parit…
bvalosek Apr 15, 2026
2842b73
style: update stale legacy credential type references in comments
bvalosek Apr 15, 2026
c0c8e30
refactor: remove legacy DFOSContentRead/DFOSContentWrite vocabulary
bvalosek Apr 15, 2026
fe3e9b2
chore: sync hardcoded version references to 0.7.1
bvalosek Apr 15, 2026
4fee06e
fix: read relay version from package.json instead of hardcoding
bvalosek Apr 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion deploy/QUICKSTART.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,10 @@ Registry:

```
ghcr.io/metalabel/dfos:latest
ghcr.io/metalabel/dfos:0.7.0
```

Pinned version tags (e.g. `ghcr.io/metalabel/dfos:0.7.1`) are also available.

Browse available tags at
https://github.com/metalabel/dfos/pkgs/container/dfos.

Expand Down
57 changes: 57 additions & 0 deletions examples/reference-content-stream/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Reference Content Stream — Worked Example

This directory contains a worked example of the `reference-content-stream/v1` schema — a toy content model for developing and testing content stream patterns against DFOS content chains.

## Schema

`reference-content-stream/v1` is a discriminated union of five actions:

| Action | Required fields | Description |
| ------------- | -------------------------------- | -------------------------------------- |
| `create-item` | `title` | Create a new item with optional `body` |
| `update-item` | `targetOperationCID`, `title` | Update an existing item |
| `delete-item` | `targetOperationCID` | Delete an existing item |
| `react` | `targetOperationCID`, `reaction` | Add a reaction to an operation |
| `unreact` | `targetOperationCID`, `reaction` | Remove a reaction from an operation |

Every document also carries `createdByDID` — the DID of the content author. This is a content-layer convention, distinct from the operation signer (who may be a delegate or device key).

## Projection Rules

State = fold over operations in chain sequence:

1. **create-item** — add to item set, keyed by operation CID
2. **update-item** — replace item at `targetOperationCID` with new fields
3. **delete-item** — remove item at `targetOperationCID` + remove associated reactions
4. **react** — add `{ reaction, createdByDID }` to target's reaction set
5. **unreact** — remove matching reaction from target's reaction set

## Example Chain

`chain.json` contains 6 operations demonstrating the full lifecycle:

1. `create-item` — alice creates "Hello world"
2. `create-item` — bob creates "Second item"
3. `react` — carol reacts with a thumbs-up to op 1
4. `update-item` — alice edits op 1 to "Hello world (edited)"
5. `react` — alice reacts with fire to op 2
6. `delete-item` — bob deletes op 2

## Projected State

After folding all 6 operations:

- **Items**: `[{ title: "Hello world (edited)", op: 4 }]` — item 2 was deleted
- **Reactions**: `[{ target: op1, reaction: "thumbsup", by: carol }]` — alice's reaction on the deleted item was dropped

See `projected-state.json` for the expected state.

## Purpose

This schema is intentionally simple — it's a development tool, not a production content model. Use it for:

- Testing content chain write-through
- Developing relay document endpoints
- Validating credential-based access control
- Building projection/materialization logic
- Cross-language implementation testing
83 changes: 83 additions & 0 deletions examples/reference-content-stream/chain.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
{
"description": "Six operations demonstrating reference-content-stream/v1 lifecycle",
"schema": "https://schemas.dfos.com/reference-content-stream/v1",
"participants": {
"alice": "did:dfos:alice",
"bob": "did:dfos:bob",
"carol": "did:dfos:carol"
},
"operations": [
{
"sequence": 0,
"operationCID": "op1_create_item_1",
"action": "create-item",
"document": {
"$schema": "https://schemas.dfos.com/reference-content-stream/v1",
"action": "create-item",
"createdByDID": "did:dfos:alice",
"title": "Hello world",
"body": "My first post."
}
},
{
"sequence": 1,
"operationCID": "op2_create_item_2",
"action": "create-item",
"document": {
"$schema": "https://schemas.dfos.com/reference-content-stream/v1",
"action": "create-item",
"createdByDID": "did:dfos:bob",
"title": "Second item",
"body": "Bob's contribution."
}
},
{
"sequence": 2,
"operationCID": "op3_react_thumbsup",
"action": "react",
"document": {
"$schema": "https://schemas.dfos.com/reference-content-stream/v1",
"action": "react",
"createdByDID": "did:dfos:carol",
"targetOperationCID": "op1_create_item_1",
"reaction": "thumbsup"
}
},
{
"sequence": 3,
"operationCID": "op4_update_item_1",
"action": "update-item",
"document": {
"$schema": "https://schemas.dfos.com/reference-content-stream/v1",
"action": "update-item",
"createdByDID": "did:dfos:alice",
"targetOperationCID": "op1_create_item_1",
"title": "Hello world (edited)",
"body": "My first post, now edited."
}
},
{
"sequence": 4,
"operationCID": "op5_react_fire",
"action": "react",
"document": {
"$schema": "https://schemas.dfos.com/reference-content-stream/v1",
"action": "react",
"createdByDID": "did:dfos:alice",
"targetOperationCID": "op2_create_item_2",
"reaction": "fire"
}
},
{
"sequence": 5,
"operationCID": "op6_delete_item_2",
"action": "delete-item",
"document": {
"$schema": "https://schemas.dfos.com/reference-content-stream/v1",
"action": "delete-item",
"createdByDID": "did:dfos:bob",
"targetOperationCID": "op2_create_item_2"
}
}
]
}
36 changes: 36 additions & 0 deletions examples/reference-content-stream/projected-state.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"description": "Expected state after folding all 6 operations from chain.json",
"items": [
{
"operationCID": "op4_update_item_1",
"originalOperationCID": "op1_create_item_1",
"title": "Hello world (edited)",
"body": "My first post, now edited.",
"createdByDID": "did:dfos:alice"
}
],
"deletedItems": [
{
"operationCID": "op2_create_item_2",
"deletedByOperationCID": "op6_delete_item_2",
"title": "Second item",
"createdByDID": "did:dfos:bob"
}
],
"reactions": [
{
"targetOperationCID": "op1_create_item_1",
"reaction": "thumbsup",
"createdByDID": "did:dfos:carol"
}
],
"droppedReactions": [
{
"targetOperationCID": "op2_create_item_2",
"reaction": "fire",
"createdByDID": "did:dfos:alice",
"reason": "target item was deleted"
}
],
"note": "Item 2 was deleted in op6, which also dropped alice's fire reaction from op5. Carol's thumbsup on item 1 survives because item 1 was only edited, not deleted."
}
4 changes: 2 additions & 2 deletions packages/dfos-cli/CLI.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ If the document has no `$schema` field, the CLI warns but proceeds. The relay is

## Credentials

The CLI issues VC-JWT credentials for content access control:
The CLI issues DFOS credentials for content access control:

```bash
# grant read access
Expand All @@ -266,7 +266,7 @@ dfos content grant <contentId> <did> --read --ttl 1h
Credentials are printed to stdout (or as JSON with `--json`). The recipient passes them to relay endpoints via the `X-Credential` header, or to the CLI via `--credential`:

```bash
dfos content download <contentId> --credential <vc-jwt> --relay local
dfos content download <contentId> --credential <jws> --relay local
```

Credential transport is out-of-band — the CLI mints and consumes them, but doesn't transmit them between parties.
Expand Down
41 changes: 18 additions & 23 deletions packages/dfos-cli/internal/cmd/beacon.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package cmd

import (
"fmt"
"strings"

"github.com/metalabel/dfos/packages/dfos-cli/internal/config"
protocol "github.com/metalabel/dfos/packages/dfos-protocol-go"
Expand All @@ -12,7 +11,7 @@ import (
func newBeaconCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "beacon",
Short: "Merkle root announcements",
Short: "Manifest pointer announcements",
GroupID: "beacon",
}
cmd.AddCommand(newBeaconAnnounceCmd())
Expand All @@ -25,10 +24,12 @@ func newBeaconAnnounceCmd() *cobra.Command {
var peerName string

cmd := &cobra.Command{
Use: "announce <contentId...>",
Short: "Build merkle root over content IDs, sign, and optionally submit",
Args: cobra.MinimumNArgs(1),
Use: "announce <manifestContentId>",
Short: "Sign a manifest pointer beacon and optionally submit",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
manifestContentId := args[0]

_, chain, err := requireIdentity()
if err != nil {
return err
Expand All @@ -39,8 +40,6 @@ func newBeaconAnnounceCmd() *cobra.Command {
return err
}

merkleRoot := protocol.BuildMerkleRoot(args)

if len(chain.State.ControllerKeys) == 0 {
return fmt.Errorf("identity has no controller keys")
}
Expand All @@ -51,7 +50,7 @@ func newBeaconAnnounceCmd() *cobra.Command {
return err
}

jwsToken, beaconCID, err := protocol.SignBeacon(chain.DID, merkleRoot, kid, privKey)
jwsToken, beaconCID, err := protocol.SignBeacon(chain.DID, manifestContentId, kid, privKey)
if err != nil {
return err
}
Expand Down Expand Up @@ -86,16 +85,14 @@ func newBeaconAnnounceCmd() *cobra.Command {

if jsonFlag {
outputJSON(map[string]any{
"beaconCID": beaconCID,
"merkleRoot": merkleRoot,
"contentIds": args,
"did": chain.DID,
"beaconCID": beaconCID,
"manifestContentId": manifestContentId,
"did": chain.DID,
})
} else {
fmt.Printf("Beacon announced:\n")
fmt.Printf(" Beacon CID: %s\n", beaconCID)
fmt.Printf(" Merkle root: %s\n", merkleRoot)
fmt.Printf(" Content IDs: %s\n", strings.Join(args, ", "))
fmt.Printf(" Beacon CID: %s\n", beaconCID)
fmt.Printf(" Manifest content ID: %s\n", manifestContentId)
}
return nil
},
Expand Down Expand Up @@ -137,10 +134,10 @@ func newBeaconShowCmd() *cobra.Command {
if name != "" {
label = did + " (" + name + ")"
}
fmt.Printf("DID: %s\n", label)
fmt.Printf("Beacon CID: %s\n", beacon.BeaconCID)
fmt.Printf("Merkle Root: %s\n", beacon.Payload.MerkleRoot)
fmt.Printf("Created: %s\n", beacon.Payload.CreatedAt)
fmt.Printf("DID: %s\n", label)
fmt.Printf("Beacon CID: %s\n", beacon.BeaconCID)
fmt.Printf("Manifest content ID: %s\n", beacon.Payload.ManifestContentId)
fmt.Printf("Created: %s\n", beacon.Payload.CreatedAt)
}
return nil
}
Expand All @@ -158,10 +155,8 @@ func newBeaconShowCmd() *cobra.Command {
if cid, ok := data["beaconCID"].(string); ok {
fmt.Printf("Beacon CID: %s\n", cid)
}
if p, ok := data["payload"].(map[string]any); ok {
if mr, ok := p["merkleRoot"].(string); ok {
fmt.Printf("Merkle Root: %s\n", mr)
}
if mc, ok := data["manifestContentId"].(string); ok {
fmt.Printf("Manifest content ID: %s\n", mc)
}
}
return nil
Expand Down
Loading
Loading