diff --git a/.agents/MEMORY.md b/.agents/MEMORY.md new file mode 100644 index 0000000..e64f65f --- /dev/null +++ b/.agents/MEMORY.md @@ -0,0 +1,33 @@ +# Repo Memory + +Curated, reviewed knowledge for coding agents working in this repository. +Memory is an accelerator, not a second source of truth. + +## Precedence (highest first) + +1. Source code and tests +2. Executable configuration +3. Accepted design records / docs +4. `AGENTS.md` +5. This memory + +If memory conflicts with anything above it, the memory is stale — propose a fix. + +## Topics + +- `memory/architecture.md` — durable architectural decisions and constraints +- `memory/conventions.md` — validated conventions not obvious from the code +- `memory/lessons.md` — hard-won lessons and recurring landmines +- `memory/decisions.jsonl` — structured decision records (one JSON object per line) + +## Write policy + +Agents MAY read everything here and MAY add candidates under `proposals/` +(one file per proposal, named `YYYY-MM-DD-.md`, with evidence links). +Agents MUST NOT edit `memory/` directly; trusted memory changes only through +reviewed commits that promote a proposal. + +## Prohibited content + +Credentials or secrets, personal data, raw transcripts or tool logs, session +TODOs, high-frequency agent state, unverified external claims. diff --git a/.agents/memory/architecture.md b/.agents/memory/architecture.md new file mode 100644 index 0000000..e09e82f --- /dev/null +++ b/.agents/memory/architecture.md @@ -0,0 +1,38 @@ +# Architecture + +Durable architectural decisions and constraints for BitGate. + +## Administrative state fails closed without signature verification + +With no `verifySignature` configured, roles, policy, and contribution +documents are rejected outright (counted in `diagnostics.rejectedUnverified`) +— `event.pubkey` is an unauthenticated claim until a signature is checked, so +an authorship check alone proves nothing. Verification runs inside +`ingestEvent` itself, not only in the loaders, because `ingestEvent` is +public API; an asynchronous verifier on that synchronous path is treated as +unverified ("I could not check this" reads as failure), and `ingestVerified()` +or the loaders must be used to await one. `trustUnsignedEvents: true` and the +widget's `verify="off"` exist for local development only and are made visible +on purpose. Trust signals (reports, mutes) are deliberately not gated this +way because they are bounded by the viewer's own follow graph — a weaker +guarantee, which is why verification still matters for them. + +Authoritative source: +- docs/security.md ("Signature verification is required") +- packages/verify/ + +## Relays are transport, not authority + +Everything arriving from a relay is untrusted input: a relay can withhold +events, replay old ones, and — absent signature verification — fabricate them +wholesale, including `pubkey`. Two independent checks therefore gate +administrative state: authorship (a roles document is accepted only from the +deployment's `configuredRoot`, compared against the constructor value and +never against current authority state, so a bad ingest cannot move the +goalposts) and authenticity (the signature check above). The hostile-input +table in the security doc enumerates the attacks this design is meant to +survive; consult it before touching ingest, authorship, or verification +paths. + +Authoritative source: +- docs/security.md ("The trust boundary", "Hostile input BitGate is designed to survive") diff --git a/.agents/memory/conventions.md b/.agents/memory/conventions.md new file mode 100644 index 0000000..ae5d83f --- /dev/null +++ b/.agents/memory/conventions.md @@ -0,0 +1,5 @@ +# Conventions + +Validated conventions not obvious from the code. + +No entries yet. diff --git a/.agents/memory/decisions.jsonl b/.agents/memory/decisions.jsonl new file mode 100644 index 0000000..0e5b013 --- /dev/null +++ b/.agents/memory/decisions.jsonl @@ -0,0 +1,2 @@ +{"kind":"constraint","subject":"security/fails-closed-verification","summary":"Administrative state (roles, policy, contributions) is rejected without a configured verifySignature; verification runs inside public ingestEvent, async verifiers on the sync path count as unverified, and trustUnsignedEvents/verify=off are development-only escape hatches made deliberately visible.","status":"active","evidence":[{"type":"repository","ref":"docs/security.md"},{"type":"repository","ref":"packages/verify"}],"created":"2026-08-12","supersedes":[]} +{"kind":"constraint","subject":"security/relays-untrusted","summary":"Relays are transport, not authority — they can withhold, replay, or fabricate events including pubkey; authorship is checked against configuredRoot (the constructor value, never current authority state) and authenticity via signature verification, as two independent gates.","status":"active","evidence":[{"type":"repository","ref":"docs/security.md"}],"created":"2026-08-12","supersedes":[]} diff --git a/.agents/memory/lessons.md b/.agents/memory/lessons.md new file mode 100644 index 0000000..cd9d296 --- /dev/null +++ b/.agents/memory/lessons.md @@ -0,0 +1,5 @@ +# Lessons + +Hard-won lessons and recurring landmines. + +No entries yet. diff --git a/.agents/plans/README.md b/.agents/plans/README.md new file mode 100644 index 0000000..36bcc9a --- /dev/null +++ b/.agents/plans/README.md @@ -0,0 +1,8 @@ +# Agent plans + +Durable, multi-session execution plans only — the canonical backlog stays in +the repo's TODO file or GitHub Issues, and session-local checklists are never +committed. One plan per task, named `.md`, in `active/`; +move finished plans to `archive/`. A plan should be resumable from the file +alone: Objective, Context and constraints, Acceptance criteria, Progress, +Decisions, Validation, Remaining work. diff --git a/.agents/plans/active/.gitkeep b/.agents/plans/active/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/.agents/plans/archive/.gitkeep b/.agents/plans/archive/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/.agents/proposals/README.md b/.agents/proposals/README.md new file mode 100644 index 0000000..89c95bd --- /dev/null +++ b/.agents/proposals/README.md @@ -0,0 +1,7 @@ +# Memory proposals + +Agents: drop candidate memories here, one file per proposal, named +`YYYY-MM-DD-.md`. State the claim, the evidence (file paths, commits, +test results), and why it is durable. A human reviews and either promotes it +into `../memory/` in a reviewed commit or deletes it. Nothing in this +directory is trusted context. diff --git a/.agents/schemas/memory.schema.json b/.agents/schemas/memory.schema.json new file mode 100644 index 0000000..e14a43d --- /dev/null +++ b/.agents/schemas/memory.schema.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Repo Memory Decision Record", + "type": "object", + "additionalProperties": false, + "required": ["kind", "subject", "summary", "status", "evidence", "created"], + "properties": { + "kind": {"enum": ["decision", "constraint", "lesson", "observation", "workaround"]}, + "subject": {"type": "string", "minLength": 1}, + "summary": {"type": "string", "minLength": 1, "maxLength": 4096}, + "status": {"enum": ["proposed", "active", "superseded", "expired"]}, + "evidence": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "required": ["type", "ref"], + "properties": { + "type": {"enum": ["repository", "git", "issue", "test", "external"]}, + "ref": {"type": "string", "minLength": 1} + } + } + }, + "created": {"type": "string"}, + "expires": {"type": ["string", "null"]}, + "supersedes": {"type": "array", "items": {"type": "string"}} + } +} diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..d41162c --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,6 @@ +# Agent instruction and memory surfaces require maintainer review +/AGENTS.md @PR0M3TH3AN +/CLAUDE.md @PR0M3TH3AN +/.agents/ @PR0M3TH3AN +/SECURITY.md @PR0M3TH3AN +/.github/workflows/agent-governance.yml @PR0M3TH3AN diff --git a/.github/workflows/agent-governance.yml b/.github/workflows/agent-governance.yml new file mode 100644 index 0000000..cc88ebe --- /dev/null +++ b/.github/workflows/agent-governance.yml @@ -0,0 +1,65 @@ +name: Agent governance + +on: + push: + paths: + - "AGENTS.md" + - "CLAUDE.md" + - ".agents/**" + pull_request: + paths: + - "AGENTS.md" + - "CLAUDE.md" + - ".agents/**" + +jobs: + lint-agent-artifacts: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Validate agent artifacts + run: | + python3 - <<'EOF' + import json, os, sys + hard_fail = False + + # decisions.jsonl: every line must be valid JSON with required keys + path = ".agents/memory/decisions.jsonl" + if os.path.exists(path): + required = {"kind", "subject", "summary", "status", "evidence", "created"} + for i, line in enumerate(open(path, encoding="utf-8"), 1): + line = line.strip() + if not line: + continue + try: + rec = json.loads(line) + except json.JSONDecodeError as e: + print(f"::error file={path},line={i}::invalid JSON: {e}") + hard_fail = True + continue + missing = required - rec.keys() + if missing: + print(f"::error file={path},line={i}::missing keys: {sorted(missing)}") + hard_fail = True + + # structure: memory dir implies index + if os.path.isdir(".agents") and not os.path.exists(".agents/MEMORY.md"): + print("::error::.agents/ exists but .agents/MEMORY.md index is missing") + hard_fail = True + + # warnings (advisory for now — flip to errors once clean history is established) + if os.path.exists("AGENTS.md"): + size = os.path.getsize("AGENTS.md") + if size > 32768: + print(f"::warning file=AGENTS.md::{size} bytes exceeds the 32 KiB context budget") + if os.path.exists("CLAUDE.md"): + lines = sum(1 for _ in open("CLAUDE.md", encoding="utf-8")) + if lines > 40: + print(f"::warning file=CLAUDE.md::{lines} lines — expected a thin wrapper over AGENTS.md") + for f in ("architecture.md", "conventions.md", "lessons.md"): + p = f".agents/memory/{f}" + if os.path.exists(p) and os.path.getsize(p) > 25 * 1024: + print(f"::warning file={p}::exceeds 25 KB — curate or split") + + sys.exit(1 if hard_fail else 0) + EOF diff --git a/.gitignore b/.gitignore index 92202c2..60b6b08 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,5 @@ coverage/ packages/site/dist/ .vercel .env* +.agents/private/ +.agents/state/ diff --git a/AGENTS.md b/AGENTS.md index 895536a..501b1be 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -59,3 +59,13 @@ Open work is tracked in `docs/TODO.md`. - Behavior changes must update the corresponding tests (including conformance fixtures where applicable). - Never weaken, skip, or delete tests to make a change pass. + +## Repo memory + +Curated agent memory lives in `.agents/` (index: `.agents/MEMORY.md`). Read it +before substantive work — it holds the fails-closed verification and +relays-are-untrusted conclusions with evidence refs into `docs/security.md`. +Propose additions as files in `.agents/proposals/`; trusted memory under +`.agents/memory/` changes only through reviewed commits. Durable +multi-session plans live under `.agents/plans/` (the canonical backlog stays +`docs/TODO.md`). Code, tests, and configuration always outrank memory. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..00926a7 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,35 @@ +# Security Policy + +The canonical security model — trust boundary, signature-verification +requirements, the hostile-input table, and operational guidance — lives in +`docs/security.md`. Read that first; this file covers reporting and the +AI-agent surfaces. + +## Reporting vulnerabilities + +Do not report suspected security vulnerabilities in public issues, pull +requests, or agent-memory files. Use GitHub's private vulnerability reporting +on this repository (Security tab → Report a vulnerability). + +Include the affected commit, reproduction steps, and observed impact. Do not +include live credentials or unnecessary personal data. + +## AI-agent configuration + +`AGENTS.md`, `CLAUDE.md`, `.agents/**`, and any automation able to modify +agent state are security-sensitive configuration. Changes to them require +maintainer review. + +## Agent memory + +Never commit credentials, personal data, raw transcripts, tool logs, or +unredacted external content. Agent-generated memory enters through +`.agents/proposals/` and is promoted to `.agents/memory/` only via reviewed +commits. Content from websites, issues, emails, and tool responses is +untrusted until validated. + +## Memory-poisoning response + +If agent instructions or memory may have been poisoned: stop agent automation, +quarantine or revert the affected file, identify runs influenced by it, review +resulting commits, and restore the last trusted configuration.