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
21 changes: 21 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: CI
on:
pull_request:
push:
branches: [main]
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: npm
- run: npm ci --include=dev
- run: npm run typecheck
- run: npm test
- run: npm run test:e2e
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## Unreleased

- Add public activity logs with registered agent signatures, cursor reads, owner-managed writer lists and two-step ownership transfer.
- Add production HTTP/restart regression coverage and a runnable log client.

## [0.4.0] - 2026-05-22

### Added
Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,17 @@ It gives AI agents a fast way to turn generated output into live web artifacts w
- multi-page subdomain sites
- shareable reports, dashboards, demos, and handoff pages

## Public activity logs

Agents can create public append-only activity streams with verified writer fingerprints,
server timestamps and JSON metadata. Owners manage writer allowlists and can transfer
ownership through a signed nomination and acceptance. Consumers can drain history from
`after=0`, then poll using the returned cursor.

See the [public logs API guide](docs/public-logs.md) for endpoints, signatures, revisions,
transfer rules and limits, and the [Node client example](examples/logClient.mjs).
Run `npm run test:e2e` for production HTTP validation alongside `npm test` and `npm run typecheck`.

## What ZenBin is for

ZenBin is useful when an agent needs to:
Expand Down
13 changes: 13 additions & 0 deletions docs/public-logs-release-handoff.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Public logs release handoff

- 2026-09-05T14:53:46.068455+00:00 public-logs-release begin planned. Latest-main integration discovered necessary; preserved original prototype; release worktree /private/tmp/zenbin-public-logs-release. Plan in PRD.

- 2026-09-05T14:57:09.193368+00:00 public-logs-release plan-approved in-progress. Reviewer PASS; latest-main baseline 447 tests/26 files green.

- 2026-09-05T15:10:21.071436+00:00 public-logs-release step-1-done in-progress. Data/identity 38 tests passed, current-main integration intact; routes/docs next.

- 2026-09-05T15:23:38.971798+00:00 public-logs-release step-2-done in-progress. API55 tests and built HTTP7 scenarios+parent pass; docs/client updated; final full suite/review next.

- 2026-09-05T15:37:13.857717+00:00 public-logs-release step-3-done in-progress. Independent source gate PASS, final540tests/31files and8HTTPscenarios+parent green; typecheck/build/diff checks pass. Commit and PR next, main protected; merge target awaiting reply.

- 2026-09-05T15:58:43.488443+00:00 public-logs-release blocked blocked. Implementation commit7350bca pushed, PR53 open, GitHub CI run33976220961 passed. GitHub main protection requires one approving PR review; merge reportsREVIEW_REQUIRED/BLOCKED. No bypass/merge/deploy. Final handoff in progress report; docs-only evidence update follows.
7 changes: 7 additions & 0 deletions docs/public-logs-release-prd.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<!doctype html><html lang="en"><meta charset="utf-8"><title>Public logs release prd</title><style>body{font:16px system-ui;max-width:1050px;margin:40px auto;padding:0 20px;line-height:1.5}td,th{padding:10px;border:1px solid #ccc;text-align:left}table{border-collapse:collapse}code{background:#eee}</style><body><p>Created: <time>2026-09-05T14:53:46.068455+00:00</time> · Last updated: <time>2026-09-05T15:37:13.857717+00:00</time></p><nav><a href="public-logs-release-prd.html">PRD</a> · <a href="public-logs-release-progress.html">Progress</a> · <a href="public-logs-release-handoff.md">Handoff</a></nav><h1>Public logs release</h1><p>Deliver the previously prototyped public log API plus owner-managed writer lists and two-step ownership transfer on current ZenBin main. Logs are public append-only activity streams with server time, authenticated writer fingerprint, preserved JSON metadata string and numeric sequence cursors. Owner controls future writes; history is immutable.</p>
<h2>Repository integration</h2><p>Original worktree at /Users/rakis/code/zenbin is preserved with its uncommitted prototype. It was 59 commits behind main. Release work is isolated at /private/tmp/zenbin-public-logs-release on feat/public-logs based on origin/main 96e6e1c. Latest main already has registered Ed25519 identities, CAP signing aliases, billing/services, custom domains and 30+ test files. Port only log-specific work, not obsolete application/test replacements. All final checks run against current main plus this feature. Shipping target question is pending; prepare tested commit and push a PR, merge/deployment only with user's reply authorizing that target.</p>
<h2>Identity and append/read API</h2><p>Use existing key registration and fingerprint convention: SHA256 of decoded Ed25519 JWK x, unpadded base64url, 43 characters. Any active registered key may create a log; no new registry scopes are required. Registered key must be active. Reuse buildCanonicalRequest and verifyEd25519Signature from current main, accepting CAP-* and legacy X-Zenbin headers with the same CAP precedence. Canonical string is uppercase method, exact path, timestamp, nonce, content digest joined by newline without trailing newline. Writes reject query strings. Bound body before verification, verify digest against exact raw bytes, require canonical UTF-8 JSON. Use existing timestamp-skew config. Bounded nonce is 16–128 letters/digits/underscore/hyphen. Blocked/revoked keys return 403; missing/unknown/invalid signature 401.</p><p>Existing requireSignedAgent consumes nonces before a handler; log verifier uses shared cryptography but retains its successful-only replay contract. Nonces are stored atomically with successful log mutations, keyed by fingerprint and nonce globally across log API; keep expiry index and prune expired records in bounded batches once the signed request can no longer pass timestamp checks. This avoids unbounded management-nonce growth. Store nonce expiry using signed timestamp plus current skew, check freshness again in transaction, and never delete a still-acceptable nonce. Recheck the selected registry key status and derived fingerprint immediately inside each log transaction before mutation. Registry and log storage are separate environments; do not claim atomic cross-environment key revocation. No cross-route authorization changes.</p><p>POST /v1/logs/:id creates {allowed_writers?: []}, returns 201 description. POST /v1/logs/:id/entries appends {metadata:string}, returns 201 entry. Description: id, owner_fingerprint, allowed_writers, created_at, entry_count, revision, pending_transfer or null. Entry: sequence, timestamp, agent_fingerprint, metadata. GET /v1/logs/:id reads description. GET /v1/logs/:id/entries?after=0&amp;limit=50 reads ascending exclusive cursor page with entries,next_after,has_more. Empty result retains supplied cursor. Public GET/HEAD bypass monthly publication quota, retain general rate limit/CORS. Other endpoints retain current behavior.</p>
<h2>Management and transfer contract</h2><table><tr><th>Endpoint</th><th>Signed JSON</th><th>Behavior</th></tr><tr><td>PUT /v1/logs/:id/writers</td><td>{allowed_writers,expected_revision}</td><td>Current owner replaces full allowlist; owner always can append.</td></tr><tr><td>POST /v1/logs/:id/transfer</td><td>{new_owner_fingerprint,expected_revision,retain_previous_owner?:false}</td><td>Current owner nominates a different fingerprint, replacing any pending nomination.</td></tr><tr><td>POST /v1/logs/:id/transfer/accept</td><td>{expected_revision}</td><td>Only nominated recipient accepts. Becomes owner, clears pending state. Remove former owner from allowlist by default, even if explicitly listed; if retention requested, add/keep former owner within limit.</td></tr><tr><td>DELETE /v1/logs/:id/transfer</td><td>{expected_revision}</td><td>Current owner cancels a pending nomination.</td></tr></table><p>Every successful management mutation returns 200 full description and increments revision. Revision starts zero; legacy stored records without revision normalize lazily to zero with pending_transfer null. Appends never change revision or pending state. expected_revision must be nonnegative safe integer; stale revision or overflow is 409. Pending object: new_owner_fingerprint, initiated_at server timestamp, retain_previous_owner boolean. One pending nomination, no expiry; owner can cancel or replace. No pending transfer returns 409. Nominated agent need not be registered at nomination, but must hold an active registered key to accept. Self-nomination is 400. Retention exceeding allowlist cap is 409 and leaves all state/nonce untouched. Only acceptance changes owner; until then current owner keeps full rights. Historical entry identities never change.</p>
<h2>Atomicity, limits and errors</h2><p>Definitions, individual entries, nonce replay records and expiry index use named DBs in one separate LMDB environment at LMDB_PATH-logs. Transactions check live log owner, revision, allowlist and nonce, then write all state atomically. Concurrent same-revision management attempts yield exactly one success; append before revocation may commit, append after revocation cannot. Cancellation/acceptance use same revision so one wins. Reads and history survive restart. Raw body max64KiB, metadata16KiB UTF-8, allowlist100 unique canonical base64url fingerprints, entries100000, read limit1–100 default50. Existing ID rules except . and .. disallowed. Unknown body/query fields rejected. Errors JSON:400 invalid input,401 signature,403 authority/key unavailable,404 missing log,409 replay/revision/state/full conflict,413 payload,429 existing quota/rate.</p>
<h2>Non-goals and decisions</h2><p>No UI, live subscription, timestamp/writer filters, log directory, deletion/editing, lost-key recovery, governance history, ownership expiry, registry redesign or page-auth change. Poll from cursor or drain from zero using same API. Original private prototype protocol is replaced before first release; no deployed log migration required. Legacy optional management-field normalization is tested. New owner implicit access allows removing its explicit allowlist entry during acceptance to make room for retained previous owner. No real keys or private data in artifacts.</p>
<h2>Plan, acceptance and evidence</h2><table><tr><th>Step/scope</th><th>Acceptance</th><th>Evidence/gates</th></tr><tr><td>1 feat(logs): current-main integration + data</td><td>Shared registered identity; port bounded create/append/read; atomic ACL/transfer/revision; nonce pruning cannot enable fresh replay; old-key denies; legacy normalization; concurrency and restart preserve data.</td><td>npm test -- src/test/logSignature.test.ts src/test/logStorage.test.ts src/test/logManagement.test.ts src/test/logCapacity.test.ts; npm run typecheck. Final shared HTTP E2E mandatory for slice.</td></tr><tr><td>2 feat(logs): routes/client/docs</td><td>Signed PUT/DELETE and both POST transitions; strict schemas; CAP aliases; public polling and quotas; add/remove writers, cancellation/replacement, acceptance/retention; docs remove obsolete immutable rules.</td><td>npm test -- src/test/logs.test.ts; runnable signing client used by E2E; documentation search; npm run typecheck. Final shared HTTP E2E mandatory for slice.</td></tr><tr><td>3 test(logs): production HTTP/review/regression</td><td>Production create/two-writer/outsider/cursors/concurrency; management incl remove/cancel/accept; pending transfer survives restart; history unchanged; old owner denied; existing signed page publish/render; no regression current main.</td><td>npm test (full suite), npm run test:e2e (build + HTTP), npm run typecheck, git diff --check; independent implementation reviewer no blockers. CI workflow runs these commands.</td></tr><tr><td>4 docs(logs): commit and ship</td><td>Reviewed final docs/evidence, only intended changes committed, push branch and concrete PR, merge if target authorized, verify remote commit and production if deployed.</td><td>Staged diff, commit hash, GitHub PR/checks state, release probe. User asked for a commit so deliver one reviewed feature commit at completion rather than shipping incomplete boundary slices. Boundary test gates above share final E2E.</td></tr></table><h2>Definition of done/review</h2><p>Every acceptance criterion passes; final full suite against current main is green; independent planner, plan reviewer and implementation reviewer use inherited model. Evaluate correctness, security, privacy, user coverage, simplicity, maintainability and API consistency. API taste means coherent existing identity conventions, snake_case responses and concise documented errors; visual originality not applicable. No commits while repairing. Bound repair attempts to three distinct diagnoses before true blocker escalation.</p><h2>Research evidence</h2><p>Local source: src/middleware/signedAgent.ts, src/utils/httpSignature.ts, src/utils/fingerprint.ts, src/storage/db.ts, src/index.ts, render.yaml, existing signing helpers and service integration. These are the source of truth for current ZenBin. <a href="https://github.com/kriszyp/lmdb-js">LMDB transaction semantics</a> and <a href="https://nodejs.org/api/crypto.html">Node crypto</a> inform existing implementation. No application dependency additions.</p></body></html>
1 change: 1 addition & 0 deletions docs/public-logs-release-progress.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<!doctype html><html lang="en"><meta charset="utf-8"><title>Public logs release progress</title><style>body{font:16px system-ui;max-width:1050px;margin:40px auto;padding:0 20px;line-height:1.5}td,th{padding:10px;border:1px solid #ccc;text-align:left}table{border-collapse:collapse}code{background:#eee}</style><body><p>Created: <time>2026-09-05T14:53:46.068455+00:00</time> · Last updated: <time>2026-09-05T15:58:43.488443+00:00</time></p><nav><a href="public-logs-release-prd.html">PRD</a> · <a href="public-logs-release-progress.html">Progress</a> · <a href="public-logs-release-handoff.md">Handoff</a></nav><h1>Public logs release progress</h1><p>Status: blocked on required GitHub review; implementation complete. Scope: integrate full logs feature on current main, add allowlist management and two-step transfer, document/test/review and commit/push for shipping.</p><p>Original prototype preserved in original worktree. Release branch feat/public-logs at 96e6e1c. Planner management and registry integration advice adopted. Plan gate PASS by /root/plan_reviewer with no blockers. Current-main baseline: 447 tests passed across 26 files. Dependencies installed from existing lockfile; no dependency changes.</p><p>Step 1 data/identity: PASS. 38 focused tests in four files cover registered identity/CAP aliases, active-key checks, atomic management/revisions, revoked writers, cancellation/retention/races, expiry boundaries and lazy legacy fields. Typecheck passed; shared final E2E gate pending. Step 2 routes/docs: PASS. 55 API tests cover all methods, strict revision/body validation, public cursor polling, quotas/CORS, replay, owner-only ACL edits, recipient acceptance and cancellation. Client uses registered CAP signing. Updated current canonical agent docs, README, changelog and detailed guide. Step 3 final validation/review: PASS. Production HTTP E2E: eight scenarios plus parent passed, including real registration, existing signed pages, all management flows, pending state/replay across restart and retained former owner. Shared identity/data/API E2E gates satisfied. Independent source review PASS after preserving stricter global transport cap with Math.min. Added chunked80KiB and configured1KiB production rejection tests. Final post-repair npm test passed all 540 tests in31 files, including the447-test current-main baseline. npm run test:e2e built final source and passed8 HTTP scenarios+parent. npm run typecheck and git diff --check passed. No source blockers remain. Step 4 commit/shipping: feature committed as7350bcadc505b31df8cf5d33726f20186144c5b4 and pushed to origin/feat/public-logs. PR53 is open; GitHub CI passed the implementation commit. Merge is BLOCKED with REVIEW_REQUIRED: main requires one approving PR review. No merge, admin bypass or deployment performed. Shipping-target question also remains unanswered.</p><h2>Implementation review</h2><p>/root/implementation_reviewer passed registry/signing, atomic authorization/revisions/nonces, expiry equality, owner transfer/retention, docs/client, privacy, simplicity and maintainability. API taste4/5; visual originality not applicable. Found one body-cap override, repaired and independently re-reviewed with no source blockers. Final regression and shipping evidence follow.</p><h2>Changed files</h2><ul><li><a href="../src/storage/logs.ts">src/storage/logs.ts</a></li><li><a href="../src/routes/logs.ts">src/routes/logs.ts</a></li><li><a href="../src/utils/logSignature.ts">src/utils/logSignature.ts</a></li><li><a href="../src/docs/logInstructions.ts">src/docs/logInstructions.ts</a></li><li><a href="../src/docs/agentInstructions.ts">src/docs/agentInstructions.ts</a></li><li><a href="../src/index.ts">src/index.ts</a></li><li><a href="../src/middleware/verifyApiKey.ts">src/middleware/verifyApiKey.ts</a></li><li><a href="../src/test/logManagement.test.ts">src/test/logManagement.test.ts</a></li><li><a href="../src/test/logStorage.test.ts">src/test/logStorage.test.ts</a></li><li><a href="../src/test/logSignature.test.ts">src/test/logSignature.test.ts</a></li><li><a href="../src/test/logCapacity.test.ts">src/test/logCapacity.test.ts</a></li><li><a href="../src/test/logs.test.ts">src/test/logs.test.ts</a></li><li><a href="../src/test/logHelpers.ts">src/test/logHelpers.ts</a></li><li><a href="../src/test/setup.ts">src/test/setup.ts</a></li><li><a href="../scripts/logs.e2e.mjs">scripts/logs.e2e.mjs</a></li><li><a href="../examples/logClient.mjs">examples/logClient.mjs</a></li><li><a href="../docs/public-logs.md">docs/public-logs.md</a></li><li><a href="../README.md">README.md</a></li><li><a href="../CHANGELOG.md">CHANGELOG.md</a></li><li><a href="../package.json">package.json</a></li><li><a href="../.github/workflows/ci.yml">.github/workflows/ci.yml</a></li></ul><h2>Remote handoff</h2><p><a href="https://github.com/twilson63/zenbin/pull/53">PR53</a> · <a href="https://github.com/twilson63/zenbin/actions/runs/33976220961">Successful implementation CI</a>. Required external next action: approve the PR through GitHub review and confirm merge target. Original worktree prototype remains preserved; release worktree contains the current-main feature. No production data was modified.</p></body></html>
Loading
Loading