feat: Add notification infrastructure with bell UI#1316
Merged
Conversation
Extract generic notification system from board exploration work. Adds notifications table, in-app bell icon with dropdown, Slack DM delivery, and triggers for connection requests/acceptances and badge awards. Also links working group members to their profiles. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix XSS: add proper escapeHtml for all interpolated fields in nav dropdown - Fix duplicate Slack DMs: remove old notifyConnection* functions, notifyUser now handles both in-app and Slack delivery - Add UUID validation on mark-as-read endpoint (400 instead of 500) - Clamp negative offset to 0 on list endpoint - Add rate limiter (120 req/min) on notification endpoints - Replace hardcoded hex colors with design system CSS variables Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Both nav.js and notifications.html used id="notifList", causing getElementById to find the nav's dropdown element instead of the page's container. The page's "Loading..." was never replaced. Renamed page element IDs to pageNotifList, pageMarkAllBtn, and pageLoadMoreBtn to avoid the collision. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wire notification system into participation loop: - Working group join: notify group leaders when someone joins - Meeting scheduled: notify invited attendees when admin creates meeting - Event updated: notify registered users when time/venue/status changes - Event reminder: hourly job sends 24h-before reminders to registrants Also adds NotificationDatabase.exists() for dedup and EventsDatabase.getEventsStartingBetween() for the reminder job. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…collision Main branch added 259_agent_context_auth_type.sql and 260_org_memberships_created_at_index.sql since our branch was created. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The @adcp/client SDK's OrchestratorOptions.auth type only defines 'bearer', but basic auth support was added in the route handler. Cast until the SDK type is updated to include basic auth. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
bokelley
added a commit
that referenced
this pull request
May 1, 2026
The // lgtm[js/request-forgery] suppressions don't actually suppress in GitHub's CodeQL (LGTM was retired); they only shifted the bytes on the flagged lines, which made the PR's CodeQL check re-detect known alert #1316 (already open on main) as "new in this PR". Reverting makes url-security.ts match main byte-for-byte. The pre-existing SSRF false-positive remains tracked as alert #1316 on main and should be dismissed in the security UI as a false positive (validateFetchUrl + SSRF-safe dispatcher with private-IP rejection at connect time). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bokelley
added a commit
that referenced
this pull request
May 2, 2026
…overy (#3690) * spec: brand_url on get_adcp_capabilities for keys-from-agent-URL discovery Proposes a top-level brand_url field on get_adcp_capabilities so verifiers can bootstrap from an agent URL to the operator's brand.json (and from there to signing keys) without out-of-band knowledge of the operator domain. Keeps brand.json operator-attested as the trust root — no jwks_uri added to capabilities. Verifier algorithm pins agent URL eTLD+1 to brand_url eTLD+1 (with authorized_operators[] opt-in for SaaS-platform operators), enforces mandatory identity.key_origins consistency for every declared signing purpose, and joins the existing request_signature_* error-code family. Includes a self-hostable AAO reference resolver (/api/registry/agents/resolve, /api/registry/agents/jwks) framed as a convenience layer not a trust anchor — separate hostname for the JWKS endpoint, no stale-while-revalidate on JWKS, native resolution shown first in caller integration, SDKs default to mode="native". Spec only — no protocol changes ship in this PR. Reviewed by ad-tech protocol, security, ad-tech product, and DX experts; their material feedback is itemized in the spec's "Reviewer feedback addressed" section. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * spec: add RFC 7517 use="sig" to JWK examples and require it normatively Addresses @BaiyuScope3 review on PR #3690 — JWK examples were missing the RFC 7517 `use` field. Added `use: "sig"` to both example JWKs in the resolver responses and made it normative: every signing JWK MUST declare `use: "sig"` for JOSE-library pre-filtering, with the AdCP-specific purpose granularity riding on `adcp_use`. Verifiers MUST enforce both. Also clarified that `key_ops` MUST NOT be used together with `use` per RFC 7517 §4.3 — AdCP standardizes on `use` for cross-library compat. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * spec: round-2 fixes + AAO resolver implementation Spec changes (round 2): - Reverted "MUST NOT use key_ops with use" (security.mdx:780 requires the AdCP four-tuple: use:"sig", key_ops:["verify"], adcp_use, distinct kid). JWK examples now match the canonical shape from security.mdx:840. - Tightened required-when on identity.key_origins to supported_for[]/ required_for[] non-empty (a no-op declaration with empty arrays shouldn't drag in key_origins). - Step 7 consistency check now skips purposes whose JWKS source was a publisher adagents.json signing_keys pin — the pin is an intentional override and may legitimately point at a different host. - Tightened loose SHOULDs to MUSTs (production verifiers MUST use native resolution; verifiers MUST honor upstream Cache-Control). - Dropped /.well-known/adcp-jwks.json shortcut (re-introduced M3-prime self-attestation without saving fetches on the trust-required path). - Expanded SSRF list: pinned PSL snapshot, IPv6 ULA, IPv4-mapped IPv6, AWS/GCP/Azure metadata IPs, bracketed-IPv6 zone-ID handling, DNS-rebinding ordering, agent_url 2KB cap, per-caller < per-host rate. - AAO resolver MUST itself enforce required-when (storyboard alone is insufficient because mode:"aao" callers would lose H2 protection). - Added trace[] array + freshness aggregate to /resolve response. - Added new error code request_signature_key_origin_missing. AAO resolver implementation: - server/src/registry/agent-resolver/ with 10 modules: orchestrator, capabilities-fetcher (via SingleAgentClient.getAdcpCapabilities), brand-json-fetcher, jwks-fetcher (byte-for-byte Cache-Control propagation, no SWR), consistency (eTLD+1 binding, key_origins check, agent-not-in-brand-json), breadcrumb builder, TTL cache + per-host token-bucket rate limiter, SSRF-strict fetch wrapper around safeFetch, typed error class, algorithms allowlist. - Routes /api/registry/agents/resolve and /api/registry/agents/jwks wired in server/src/routes/registry-api.ts with OpenAPI registerPath declarations + static/openapi/registry.yaml entries. - JWKS endpoint preserves RFC 7517 purity in the body; trace surfaced via X-AAO-Trace-URL header pointing at the resolve endpoint. - 63 tests passing: 29 consistency + 10 cache + 13 safe-fetch-strict + 11 integration (in-process express + jose-generated Ed25519 keypair, JOSE round-trip verifying a token signed with the fixture key). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(security): suppress CodeQL js/request-forgery false-positive on safeFetch CodeQL traces user-provided URLs (e.g., the resolver's agent_url query param) through to the fetch() calls inside safeFetch and flags them as SSRF, but safeFetch is the function that validates URLs and pins SSRF-safe dispatch. validateFetchUrl rejects private IPs pre-flight, buildSsrfSafeDispatcher's lookup callback rejects them at TCP connect time, redirects re-run validation per hop. Same suppression pattern as the existing axios.get callers in adagents-manager.ts and brand-manager.ts. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * spec: drop hosted AAO resolver, reframe as @adcp/client SDK + CLI Round-3 pivot driven by CodeQL flagging the hosted resolver's fetch path and a fundamental reconsideration: a centralized fetcher of caller-supplied URLs is the wrong shape for the registry. SSRF amplification on AAO infrastructure, centralized cache as a single poisoning point, and "convenience layer that's not a trust anchor" still drags AAO into JOSE-naive verifiers' trust chains in practice. Per-process SDK resolution is what the spec already required for production verifiers; this round commits to it as the only path. Spec changes: - Replaced §"Hosted resolver (AAO Registry API)" with §"Client SDK + CLI" describing resolveAgent / getAgentJwks / createAgentJwksSet in @adcp/client (TS) and adcp (Python), plus npx @adcp/client resolve <url> CLI for instant-answer UX. - Scrubbed AAO-specific surface (aao_signed, X-AAO-* headers, cache_until, source: live|cached envelope, separate-hostname requirement, mode="aao" SDK option). Trace[]/freshness now ride on the SDK return value. - SSRF list reframed for SDK posture: kept IP blocks (cloud-metadata IPs still matter for SDK callers running in cloud workloads), dropped per-caller-IP rate limit, reframed per-host rate as politeness-not-amplification. - Documented in-browser playground as the right shape for "I have an agent URL, show me its keys" without server-side fetch. Added open question on operator CORS for brand.json/JWKS. - Rollout: dropped PR 4 (hosted resolver). Implementation lands in PR 3 (@adcp/client + Python SDK + CLI). Optional follow-up PR for the browser playground. Implementation removed: - server/src/registry/agent-resolver/ (10 modules) - server/tests/{unit,integration}/agent-resolver*.test.ts (4 files) - Two route handlers in server/src/routes/registry-api.ts - registerPath blocks for both endpoints - Agent Resolution entries in static/openapi/registry.yaml + the tag - .changeset/aao-agent-resolver-impl.md The pure-function modules (consistency, breadcrumb, cache, algorithms, SSRF-strict fetch wrapper) are the right starting point for the @adcp/client port; that work happens in a follow-up PR on the adcp-client repo. CodeQL suppression on safeFetch kept — documents the existing axios.get callers' established pattern and stays correct independent of the resolver routes. Server unit suite: 2930/2930 passing. Typecheck clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(security): revert lgtm comments — modern CodeQL ignores them The // lgtm[js/request-forgery] suppressions don't actually suppress in GitHub's CodeQL (LGTM was retired); they only shifted the bytes on the flagged lines, which made the PR's CodeQL check re-detect known alert #1316 (already open on main) as "new in this PR". Reverting makes url-security.ts match main byte-for-byte. The pre-existing SSRF false-positive remains tracked as alert #1316 on main and should be dismissed in the security UI as a false positive (validateFetchUrl + SSRF-safe dispatcher with private-IP rejection at connect time). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(schema): brand_url on get_adcp_capabilities for keys-from-agent-URL discovery Adds the top-level `brand_url` field on `get_adcp_capabilities` so verifiers can bootstrap from an agent URL to that agent's signing keys without out-of-band knowledge of the operator domain. Closes the discovery gap in the request-signing chain — capabilities → brand_url → brand.json → agents[] → jwks_uri → JWKS. Schema (static/schemas/source/protocol/get-adcp-capabilities-response.json): - New top-level `brand_url` (HTTPS URI). - Schema-optional in 3.x; storyboard-enforced when the agent declares any signing posture (`request_signing.supported_for`/`required_for` non-empty, `webhook_signing.supported === true`, or any field under `identity.key_origins`). Becomes schema-required in 4.0. - Distinct from existing `sponsored_intelligence.brand_url`, which remains a rendering pointer for SI agent visuals. Security (docs/building/implementation/security.mdx): - New §"Discovering an agent's signing keys via brand_url" with the 8-step verifier algorithm: eTLD+1 origin binding (closes shared- tenancy spoofing), `authorized_operators[]` opt-in for SaaS-platform- as-operator, byte-equal agents[].url match, mandatory key_origins consistency check (purpose-generic, with sell-side webhook carve-out for adagents.json signing_keys pin), default jwks_uri at agent origin. - Eight new request_signature_* rejection codes with detail fields: brand_url_missing, capabilities_unreachable, brand_json_unreachable, brand_origin_mismatch, agent_not_in_brand_json, brand_json_ambiguous, key_origin_mismatch, key_origin_missing. - Trust-root distinction documented: brand.json operator-attested, adagents.json publisher-attested, agent never self-attests. - Existing JWKS-discovery prose now points at the new section. Backwards compatibility: strictly additive. Verifiers that ignore brand_url continue to work. The full design (with reviewer history, multi-tenant operator handling, SDK + CLI integration, and rejected alternatives) is in specs/capabilities-brand-url.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(security): address expert-review fixes on brand_url discovery Round-2 expert review on the actual schema diff (commit 4a7aa5d) flagged several correctness/security/clarity items in security.mdx and the schema description. This commit addresses Tier 1 (correctness + security) and Tier 2 (clarity) items inline: Correctness / security: - Heading depth: §"Discovering an agent's signing keys" promoted from ##### to #### so it's a sibling of §Agent key publication / §Agent identity / §Verifier checklist (not orphaned under Agent key publication). - Step 1: capabilities fetch MUST go through SSRF validation and HTTPS enforcement at the verifier level (not just schema-level). - Step 2: 3.x→4.0 transition qualified by `supported_versions` containing any 4.x release — a 4.0 verifier talking to a 3.x-only agent MUST continue to accept absent brand_url. - Step 3: explicit pinned PSL snapshot requirement with publicsuffix.org pointer; ICANN+PRIVATE sections both in scope so platforms like vercel.app are treated as suffixes. Two verifiers on different snapshots are non-conformant. - Step 4: brand.json fetch MUST NOT follow redirects (the single-redirect carve-out for authoritative_location is scoped to that field and MUST NOT be inherited). Connect/total/body-cap budgets stated. Cache TTL MUST be bounded above by JWKS revocation polling. Negative-cache MUST NOT exceed 60s. - Step 6/7: publisher-pin carve-out is per-(agent, purpose, role) tuple, not per-purpose globally. Operator-side use of the same purpose still enforced. Closes the operator-side substitution vector. - SI dual brand_url: explicit MUST rule — verifiers MUST use top-level brand_url for key discovery, MUST NOT use sponsored_intelligence. brand_url as a trust-root pointer. - Verifier checklist step 7: explicit cross-reference to the discovery preamble as a precondition for keyid resolution. Implementers reading the checklist alone won't miss the discovery chain. Clarity: - Schema description: trimmed; constraints moved to security.mdx with an explicit anchor URL (not a bare § reference). Includes the 4.x required-when qualifier and the SI MUST rule. - Step 5: trailing-slash / scheme-mismatch / IDN-punycode common-cause hint on agent_not_in_brand_json (the most common integrator failure). - Detail field HTML-escape note for counterparty-sourced strings (brand_url, matched_entries[]). - Error-code table: added Remediation column. Ops can act on each code without reading prose. Validation: build:schemas, test:schemas (7), test:json-schema (255), test:composed (32) all pass. Reviewed by ad-tech-protocol, security, docs, and dx experts on commit 4a7aa5d. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(schema): rename brand_url → identity.brand_json_url + x-adcp-validation Round-3 expert review settled on three structural improvements: 1. **Move into `identity` block.** brand_url was at the top level of get_adcp_capabilities; it's now `identity.brand_json_url`, co-located with `key_origins`, `per_principal_key_isolation`, and `compromise_notification` — the trust-posture fields it's load-bearing for. The required-when contract reads naturally now: "if any other identity field is set, brand_json_url MUST be set." 2. **Rename `brand_url` → `brand_json_url`.** The naming distinguishes from `sponsored_intelligence.brand_url` semantically: `brand_url` is reserved for "the brand being advertised" contexts (which SI's field is); `brand_json_url` names the file artifact (the operator's brand.json), independent of whether the operator is a single brand, a house, an agency, or a pure operator record. Defuses the name-collision footgun that needed a MUST rule before. 3. **Lift constraints into x-adcp-validation extension.** Required-when rules, verifier constraints, and distinct-from relationships now live in a structured `x-adcp-validation` keyword on the field. The storyboard runner and SDK validators consume the rules programmatically; codegen consumers get a tight 2-sentence JSDoc instead of a 350-word wall of text. JSON Schema validators ignore unknown `x-` keys per draft-07. Schema: - `identity.brand_json_url` added inside the existing `identity` block with the `x-adcp-validation` extension carrying the structured required-when rules and verifier constraints. - Top-level `brand_url` removed. Security.mdx: - §"Discovering an agent's signing keys via brand_json_url" — heading and prose updated for the new field name and path. - New §"Quickstart: implement a brand_json_url-based verifier" mirroring §"Quickstart: opt into request signing" — 8 numbered steps + 30-line TypeScript pseudocode block. Validates against compliance fixture brand.json + JWKS. - New §"Reference implementations" naming @adcp/client (TS), adcp (Python), adcp-go (Go) with their resolveAgent/getAgentJwks/ verify_request_signature signatures and per-SDK CLI commands. - Error-code names updated: request_signature_brand_json_url_missing (was brand_url_missing); detail field brand_url_etld1 → brand_json_url_etld1. New docs/reference/schema-extensions.mdx: - Canonical reference for AdCP's x-prefixed schema annotations. - Documents the existing x-status convention plus the new x-adcp-validation keyword (sub-keys, conformance rules, current usage). - Wired into docs.json sidebar in two places. Spec doc: - Go SDK signatures added to §SDK API (resolver.ResolveAgent, verify.RequestSignature, options shape). - CLI section names all three SDK CLIs (npx @adcp/client / python -m adcp / adcp-go) with identical output format. Validation: build:schemas, test:schemas (7), test:json-schema (255), test:composed (32) all pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(capabilities): wire training agent to emit identity.brand_json_url + round-4 expert fixes Training agent at test-agent.adcontextprotocol.org now declares identity.brand_json_url pointing at https://adcontextprotocol.org/.well-known/brand.json, and the AdCP-domain brand.json lists the training agent's URL byte-equally in agents[] with a jwks_uri at the same eTLD+1. End-to-end discovery chain (capabilities → brand_json_url → brand.json → agents[] → jwks_uri) runs green via scripts/e2e-resolve-training-agent.ts in both --inproc and HTTP modes. Round-4 expert fixes to docs/building/implementation/security.mdx: - Step 1 is a protocol-level call (MCP tools/call / A2A skill), not raw GET - Origin comparisons canonicalize ASCII-lowercase + IDNA-2008 A-label - Budgets/timeouts/redirect bans on every fetch (capabilities, brand.json, JWKS) - Strict-JSON parse on brand.json + new request_signature_brand_json_malformed code - PSL pinning called out per language with named libraries - Step 8 cross-ref off-by-one fixed (verifier checklist step 8+) - Reference implementations paragraph lists result-shape fields per SDK - Python/Go CLIs install adcp binary (console_scripts / distinct from module path) - Error table remediation specifics on retry/cache discipline + concrete URL hint docs/reference/schema-extensions.mdx: required_when is an object wrapping any_of/all_of (mirroring JSON Schema's anyOf/allOf precedent), not a bare array. TODO link replaced with #3827. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: forward-compatibility note — 3.0 implementers can adopt brand_json_url today Add an explicit guidance callout for 3.0-pinned implementers. AdCP doesn't backport new schema fields to patch releases, but the wire shape of identity.brand_json_url is forward-compatible: a 3.0 seller can populate the field today and a 3.x verifier picks it up automatically; a 3.0 verifier can read it opportunistically and run the 8-step chain when present. No version bump, no coordination — the chain is just HTTPS fetches and JSON parsing. Lands in two places: - security.mdx §"Discovering an agent's signing keys" — Info callout right after the error-code table, where verifier authors will see it. - Changeset for the next 3.x minor — generates the release-notes entry with the same guidance for adopters reading the changelog. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(schema): identity.additionalProperties → true for forward-compat The storyboard runner CI failed with "/identity: must NOT have additional properties" when the training agent emitted brand_json_url. The identity block had additionalProperties: false, which rejects new fields added in minor releases — including brand_json_url itself when validated by a runner using a pinned 3.0.x schema. Flipping to true matches the convention on peer capability blocks (measurement, conversion_tracking, audience_targeting, content_standards all have additionalProperties: true). The empty-object semantic from the description still holds; receivers MUST treat unknown sub-fields as advisory and not infer capability from them. Tradeoff: typos in identity sub-field names will silently pass schema validation. Acceptable because the storyboard runner enforces required sub-fields per posture (key_origins.{purpose} when signing supported, etc.) and unknown fields don't trigger capability claims. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(training-agent): defer identity.brand_json_url emit to post-3.1 SDK publish The storyboard runner uses @adcp/sdk's pinned ADCP_VERSION (currently '3.0.1') to fetch the response schema from adcontextprotocol.org/schemas/3.0.1/. That published schema doesn't include identity.brand_json_url and has identity.additionalProperties: false, so the runner rejects responses with the new field. Loosening additionalProperties on the source schema doesn't help here because the runner reads the published 3.0.1 artifact, not the source tree. The right fix is a coordinated SDK version bump after 3.1 ships. For now, drop the training-agent emit. Protocol changes (schema field, security.mdx algorithm, error taxonomy, schema-extensions doc, spec archive, changeset) all ship as designed. The brand.json + e2e fixture wiring stays in place — `scripts/e2e-resolve-training-agent.ts --inproc` exercises the full chain against an in-process Express stack. Production re-wiring is a follow-up tracked alongside the SDK version bump. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(e2e): drop dead etldMatch reassignment, prefer const Address github-code-quality bot finding on scripts/e2e-resolve-training-agent.ts:291. The reassignment `etldMatch = true` after a successful authorized_operators[] delegation check was a useless write — the variable is only read earlier in the block (trace note + branch condition). Removed the write and tightened `let` → `const` since the value is now single-assignment. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
reference_id/reference_typepattern/community/notificationsnotifyUser()service (removes duplicate DM functions)Context
Extracted from the community board exploration branch. The board concept needs more product thinking (how groups relate to Slack channels, whether it's a curated library or parallel forum). This PR ships the generic notification infrastructure that's valuable regardless of how the board evolves.
Test plan
259_notifications.sqlruns clean on deploy/community/notifications/community/people/:slug🤖 Generated with Claude Code