From 0437448b70fbf426d244c744d74a1a15672210e5 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Wed, 12 Aug 2026 20:57:00 -0400 Subject: [PATCH] Add repo memory layer and agent governance Co-Authored-By: Claude Fable 5 --- .agents/MEMORY.md | 33 +++++++++++++ .agents/memory/architecture.md | 46 ++++++++++++++++++ .agents/memory/conventions.md | 6 +++ .agents/memory/decisions.jsonl | 3 ++ .agents/memory/lessons.md | 21 +++++++++ .agents/plans/README.md | 8 ++++ .agents/plans/active/.gitkeep | 0 .agents/plans/archive/.gitkeep | 0 .agents/proposals/README.md | 7 +++ .agents/schemas/memory.schema.json | 29 ++++++++++++ .github/CODEOWNERS | 6 +++ .github/workflows/agent-governance.yml | 65 ++++++++++++++++++++++++++ .gitignore | 2 + AGENTS.md | 7 +++ CONTRIBUTING.md | 9 ++++ SECURITY.md | 20 ++++++++ 16 files changed, 262 insertions(+) create mode 100644 .agents/MEMORY.md create mode 100644 .agents/memory/architecture.md create mode 100644 .agents/memory/conventions.md create mode 100644 .agents/memory/decisions.jsonl create mode 100644 .agents/memory/lessons.md create mode 100644 .agents/plans/README.md create mode 100644 .agents/plans/active/.gitkeep create mode 100644 .agents/plans/archive/.gitkeep create mode 100644 .agents/proposals/README.md create mode 100644 .agents/schemas/memory.schema.json create mode 100644 .github/CODEOWNERS create mode 100644 .github/workflows/agent-governance.yml 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..c63bce1 --- /dev/null +++ b/.agents/memory/architecture.md @@ -0,0 +1,46 @@ +# Architecture + +Durable architectural decisions and constraints for this repository. + +## Key custody in the crypto Web Worker + +Private keys, passwords, and derived secrets are confined to the widget's +dedicated crypto Web Worker: they live in the worker's WASM linear memory or +`ArrayBuffer`s, and the UI thread receives only public keys, signed events, +and ciphertext/decrypted display content. The spec deliberately frames this +as lifetime-and-surface minimization, not an absolute boundary — a Web Worker +compartmentalizes but is not a security boundary against malicious +same-origin code, and the host page is inside the trust boundary. Do not +overstate the containment in code or docs. + +Authoritative source: +- docs/spec.md §11.10, §22.4, §33 +- AGENTS.md "Security" section + +## Vendored widget distribution — no CDN, no npm release + +Consuming sites vendor the built output of `packages/widget/dist/` into +their own origin; there is deliberately no npm package or auto-updating CDN +release. Rationale: a shared live-loaded script would let a single bad or +compromised deploy of this repo break or compromise every consuming site at +once with no per-site review window, and cross-origin workers are rejected +by browsers anyway. Updates therefore reach integrations only through a +reviewed re-vendor step (hash-recorded in `bitlogin.integrity.json`), which +is why integration-visible changes must be recorded in `CHANGELOG.md`. + +Authoritative source: +- README.md "Updating your integration" and "Deployment" sections +- AGENTS.md "Conventions" section; CHANGELOG.md preamble + +## Static, relay-backed system — no account server + +The standing architectural assumption is a static site with encrypted +account material on public Nostr relays: no account server, no database. +Consequences that recur across the spec: no single relay is authoritative, +reads require a quorum, and no purely static system can prove a brand-new +client has found the globally newest event — the interface must not imply +otherwise. + +Authoritative source: +- docs/spec.md §12.3, §16.2 +- AGENTS.md "Purpose" and "Security" sections diff --git a/.agents/memory/conventions.md b/.agents/memory/conventions.md new file mode 100644 index 0000000..08fa746 --- /dev/null +++ b/.agents/memory/conventions.md @@ -0,0 +1,6 @@ +# Conventions + +Validated conventions not obvious from the code; build/test conventions +already live in `AGENTS.md` and `CONTRIBUTING.md`. + +No entries yet. diff --git a/.agents/memory/decisions.jsonl b/.agents/memory/decisions.jsonl new file mode 100644 index 0000000..83e79ca --- /dev/null +++ b/.agents/memory/decisions.jsonl @@ -0,0 +1,3 @@ +{"kind":"decision","subject":"widget/key-custody","summary":"Private keys and derived secrets are confined to the crypto Web Worker (WASM/ArrayBuffer memory, shortest practical lifetime); the UI thread receives only public keys, signed events, and ciphertext — framed as surface minimization, not an absolute boundary, since a worker is not a security boundary against malicious same-origin code.","status":"active","evidence":[{"type":"repository","ref":"docs/spec.md"},{"type":"repository","ref":"AGENTS.md"}],"created":"2026-08-12","supersedes":[]} +{"kind":"decision","subject":"protocol/rollback-detection","summary":"Generation rollback against the device's local high-water mark fails closed by default (RollbackDetectedError) on login and password change, with an explicit acknowledgeRollback override for a merely lagging relay; a brand-new device with no high-water mark retains only the quorum defense.","status":"active","evidence":[{"type":"repository","ref":"docs/spec.md"},{"type":"repository","ref":"SECURITY.md"}],"created":"2026-08-12","supersedes":[]} +{"kind":"decision","subject":"distribution/vendored-widget","summary":"No CDN or npm release: consuming sites vendor the built widget into their own origin so no single compromised deploy can hit every integrator at once and every update passes a per-site review step; integration-visible changes are therefore recorded in CHANGELOG.md.","status":"active","evidence":[{"type":"repository","ref":"README.md"},{"type":"repository","ref":"CHANGELOG.md"}],"created":"2026-08-12","supersedes":[]} diff --git a/.agents/memory/lessons.md b/.agents/memory/lessons.md new file mode 100644 index 0000000..2321985 --- /dev/null +++ b/.agents/memory/lessons.md @@ -0,0 +1,21 @@ +# Lessons + +Hard-won lessons and recurring landmines. + +## Security regressions must fail closed, not warn + +An external integration pass found an account-takeover-adjacent gap: the +password-rotation tombstone (§18.1) is only advisory, so a relay that never +processes it can serve the pre-rotation capsule forever — and the client +accepted that stale capsule with a non-blocking warning while still granting +the session. The fix makes generation-rollback detection fail closed by +default (`RollbackDetectedError` on login and password change), with an +explicit `acknowledgeRollback` override for the narrow non-adversarial +lagging-relay case. Residual weakness, by design limits: a brand-new device +with no local generation high-water mark has only the quorum defense. The +general lesson: on this codebase, a detected security-relevant regression +gets a refusal, not a warning string. + +Authoritative source: +- docs/spec.md "Changes from v0.3" item 1, §16.2 step 6, §29.7 +- SECURITY.md "Status" section 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 12faa64..183b9df 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ dist-types/ .vercel .env* .e2e-*.png +.agents/private/ +.agents/state/ diff --git a/AGENTS.md b/AGENTS.md index 27315bf..e960212 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -62,3 +62,10 @@ The canonical backlog is `docs/TODO.md`. - Keep diffs minimal and focused. - Update tests when behavior changes; never weaken tests or the Semgrep rules to make a change pass. + +## Repo memory + +Curated agent memory lives in `.agents/` (index: `.agents/MEMORY.md`). Read it +before substantive work. Propose additions as files in `.agents/proposals/`; +trusted memory under `.agents/memory/` changes only through reviewed commits. +Code, tests, and configuration always outrank memory. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a3f4863..690f19e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -104,3 +104,12 @@ disclosure process. pass. - Keep the description focused on *why*, not a restatement of the diff. - Small, focused PRs review faster than one that bundles unrelated changes. + +## AI-assisted contributions + +AI-assisted changes are subject to the same testing, security, and review +requirements as any other change; the contributor remains responsible for +understanding what they submit. Do not submit unvalidated generated code, +credentials, or agent-generated trusted memory without evidence and review. +The canonical backlog and planning rules are in `AGENTS.md`; durable agent +plans live under `.agents/plans/`. diff --git a/SECURITY.md b/SECURITY.md index 0a33274..a2d18b9 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -48,3 +48,23 @@ logged in to the account (and so hold a local generation high-water mark) now fail closed on this instead; see "Changes from v0.3" in `docs/spec.md` and §16.2/§29.7 for the full writeup, including the residual case this does not cover (a brand-new device with no local high-water mark). + +## 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.