Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,11 @@ Never run tests against production Redis.
release gates; private transcripts and host details remain outside the repo.
- `docs/SECURITY.md` records the current signature profile and additive security
roadmap.
- `docs/FEP-AE0C-COMPATIBILITY.md` records the two retrospective relay
families, Activity-Relay extensions, unresolved behavior questions, and the
characterization fixtures that must remain synchronized with protocol
changes.

- Update `readme.md` and affected deployment documentation whenever behavior,
packaging, configuration, or release procedures change.

Expand Down
55 changes: 40 additions & 15 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,29 +32,54 @@ operator-owned website content, and supported deployment models.
- Container, native-package, interruption, identity, Redis, secure-mode,
interoperability, production-deployment, and overnight-soak validation.

## Immediate post-release verification
## v2.5.0 release verification completed

- Verify the stable GitHub release metadata, release notes, checksums, internal
Debian version, package contents, and required examples.
- Verify `linux/amd64` and `linux/arm64` container manifests plus the `2.5.0`,
`2.5`, `2`, and `latest` tags.
- Deploy the stable `2.5.0-1` package to production and reconfirm actor identity,
dedicated Redis state, API and worker health, private observability, and
public endpoints.
- Retain the RC2 and stable-promotion evidence outside the public repository.
- Stable GitHub release metadata, reviewed release notes, checksums, internal
Debian version, package contents, and required examples passed independent
verification.
- The `2.5.0`, `2.5`, `2`, and `latest` container tags publish matching
`linux/amd64` and `linux/arm64` manifests.
- The stable `2.5.0-1` package is deployed in production with preserved actor
and key identity, dedicated Redis state, healthy API and worker services,
private observability, and healthy public endpoints.
- RC2, stable-promotion, publication, and production-deployment evidence is
retained outside the public repository.

## Activity-Relay 3.0 design

The next active protocol milestone is a modern signing architecture informed by
the retrospective FEP-ae0c relay profiles.

- Land and maintain `docs/FEP-AE0C-COMPATIBILITY.md` and the machine-readable
fixture catalog before changing runtime behavior.
- Convert current Mastodon-style forwarding, LitePub reciprocal-follow and
Announce behavior, open publisher ingestion, NodeBB normalization, actor-path
compatibility, and authorized fetch into executable characterization tests.
- Resolve Public-in-`to` versus Public-only-in-`cc` through explicit
configuration and real interoperability evidence rather than an implicit
routing condition.
- Add RFC 9421 HTTP Message Signatures and RFC 9530 `Content-Digest` support
additively while preserving the established Fediverse `Signature` profile.
- Keep document-level Linked Data proof semantics separate from HTTP message
authentication; do not claim or add proof verification incidentally.
- Prove duplicate and reflection bounds in a mixed two-relay topology before
selecting 3.0 defaults.

## Deferred work after v2.5.0

- Investigate NodeBB follow interoperability separately from Activity-Relay:
reproduce failed attempts to follow `Julian@activitypub.space` from Friendica
and WordPress, use a controlled NodeBB instance, and inspect the corresponding
application logs.
- Define stale-receiver retention and cleanup policy after sufficient delivery
health history exists.
- Add RFC 9421 message-signature and RFC 9530 content-digest support
additively, without removing the established Fediverse `Signature` profile.
- Evaluate replicated or alternative durable-state designs without breaking
current Redis compatibility.
- Define stale-receiver retention and cleanup configuration after sufficient
delivery-health history exists. Begin in report-only dry-run mode; active
membership and queued or claimed work remain non-configurable deletion
barriers.
- Implement the Activity-Relay 3.0 RFC 9421 and RFC 9530 signing milestone
under the FEP-ae0c characterization and interoperability gates above.
- Replicated or alternative durable-state backends are tabled. Revisit only
when production experience, availability requirements, or operator demand
justify the value and compatibility cost.
- Continue maintaining and rebasing the Redis-only Machinery fork for relevant
upstream security and correctness fixes.

Expand Down
101 changes: 101 additions & 0 deletions contrib/ops/test_fep_ae0c_cases.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#!/usr/bin/env python3
# File: contrib/ops/test_fep_ae0c_cases.py

from __future__ import annotations

import json
import unittest
from pathlib import Path
from urllib.parse import urlparse


REPOSITORY_ROOT = Path(__file__).resolve().parents[2]
CASES_PATH = REPOSITORY_ROOT / "testdata" / "fep-ae0c" / "cases.json"

REQUIRED_CASE_FIELDS = {
"id",
"description",
"profile",
"status",
"decision",
"distribution",
"current_v2_5",
"v3_assertion",
}

PAYLOAD_FIELDS = {"activity", "actor", "http_request", "scenario"}


class FEPAE0CCasesTest(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
cls.document = json.loads(CASES_PATH.read_text(encoding="utf-8"))
cls.allowed = cls.document["allowed_values"]
cls.cases = cls.document["cases"]

def test_document_metadata(self) -> None:
self.assertEqual(self.document["schema_version"], 1)
self.assertEqual(
self.document["source"]["fep"],
"https://w3id.org/fep/ae0c",
)
self.assertEqual(
self.document["source"]["activity_relay_baseline"],
"v2.5.0",
)
self.assertGreaterEqual(len(self.cases), 16)

def test_case_ids_are_unique_and_portable(self) -> None:
identifiers = [case["id"] for case in self.cases]
self.assertEqual(len(identifiers), len(set(identifiers)))
for identifier in identifiers:
self.assertRegex(identifier, r"^[a-z0-9]+(?:-[a-z0-9]+)*$")

def test_required_fields_and_enums(self) -> None:
for case in self.cases:
with self.subTest(case=case.get("id")):
self.assertTrue(REQUIRED_CASE_FIELDS.issubset(case))
self.assertIn(case["profile"], self.allowed["profile"])
self.assertIn(case["status"], self.allowed["status"])
self.assertIn(case["decision"], self.allowed["decision"])
self.assertIn(
case["distribution"],
self.allowed["distribution"],
)
self.assertEqual(
len(PAYLOAD_FIELDS.intersection(case)),
1,
"each case must have exactly one payload/scenario field",
)

def test_activity_and_actor_payloads_are_activitystreams_shaped(self) -> None:
for case in self.cases:
payload = case.get("activity") or case.get("actor")
if payload is None:
continue
with self.subTest(case=case["id"]):
self.assertIn("@context", payload)
self.assertIsInstance(payload.get("id"), str)
self.assertIsInstance(payload.get("type"), str)
self.assertTrue(urlparse(payload["id"]).scheme)
if "actor" in payload and isinstance(payload["actor"], str):
self.assertTrue(urlparse(payload["actor"]).scheme)

def test_decisions_include_preservation_extensions_and_open_questions(self) -> None:
decisions = {case["decision"] for case in self.cases}
self.assertTrue(
{"preserve", "document-extension", "investigate", "defer"}
.issubset(decisions)
)

def test_audit_required_cases_are_not_claimed_as_covered(self) -> None:
for case in self.cases:
if case["status"] == "audit-required":
with self.subTest(case=case["id"]):
self.assertEqual(case["decision"], "investigate")


if __name__ == "__main__":
unittest.main()

# EOF: contrib/ops/test_fep_ae0c_cases.py
Loading