feat(ra): Implement ARD-compliant discovery service and event feed - #46
Conversation
Add spec/api-spec-finder-v1.yaml — the ANS Finder discovery surface, implementing the Agentic Resource Discovery Specification v0.5 Registry REST interface (POST /v1/search, POST /v1/explore). - House style of spec/api-spec-v2.yaml; RFC 7807 Problem errors shaped per spec/api-spec-tl-v2.yaml, carrying the five ARD standard error codes (ARDS Appendix B); 400 INVALID_ARGUMENT for bad arguments. - Each operation documents why POST (structured query body) and that responses are not GET-cacheable. - Query model per ARDS §7.1: text required for search / optional for explore, filter as dot-path keys to arrays (OR within, AND across). - CatalogEntry url-XOR-data via oneOf, with prose scoping the invariant to Active entries (tombstones carry neither and never reach the wire). - TrustManifest/Attestation per ARDS §5.1/§5.2; free-text fields marked untrusted publisher content; §-citations throughout. - federation auto|referrals|none (default auto); pageSize default 10, max 100. Discovery routes are anonymous (security: []), rate-limited. No Makefile/docs-sync wiring — that lands with the server and its conformance test in a later PR. Signed-off-by: kperry <kperry@godaddy.com>
Add internal/finder/feed and internal/finder/project — the consumer-side ingestion contract and the pure event-to-catalog-entry projection. internal/finder/feed: - EventPageResponse/EventItem/AgentEndpoint/AgentFunction mirror the production swagger (swagger_ans.json) field-for-field: JSON tags and required/optional (omitempty) match, Items marshals as [] never null, tokens are the production hyphenated forms (HTTP-API, STREAMABLE-HTTP, JSON-RPC). EventItem.Validate enforces required fields, agentId UUID, createdAt RFC 3339, and binds agentHost to the ansName FQDN via domain.ParseAnsName in one step. internal/finder/project: - FromEvent is the single entry point. The lifecycle split is the safety rule: REVOKED/DEPRECATED mint identity-only tombstones from required fields, never touching label minting or URL policy, so a malformed display field can never block a revocation. feed.Validate failure is a hard error; an unknown eventType is an alertable Skip, not an error, so a growing producer enum cannot wedge ingestion at the cursor. - Two security chokepoints: one text sanitizer strips Cc controls plus bidi/zero-width Cf from every emitted string, and validateEmittedURL (https-or-AllowHTTP, no userinfo/query/fragment, host-bound) is the only way any URL — including the constructed well-known fallback — enters an entry. A present-but-invalid metaDataUrl fails closed (no fallback rescue); an invalid agentUrl is omitted from metadata, not a Skip. URN is a lineage handle (urn:ai:host:agents:label); empty label Skips the Active path but never a tombstone. - Active mapping per ARDS §4.2: A2A/MCP fan out one entry each, HTTP-API is excluded, capabilities/tags are sanitized, deduped, sorted and capped, and entries sort by (identifier, type, url) so duplicate protocols stay deterministic. Standard encoding/json marshaling. Both packages at 100% statement coverage; golden-vector harness with UPDATE_GOLDEN matches internal/tl/event. Conformance against the OSS RA feed route is closed by PR 2's byte-equality and enum-value tests. Signed-off-by: kperry <kperry@godaddy.com>
…alidation
Review-pass fixes for three blockers plus folded-in polish.
B1 — tombstone dropped createdAt. ProjectedEntry now carries CreatedAt
(json:"-"), populated verbatim in both the tombstone and active paths;
the test-local goldenView struct gained the field, goldens regenerated.
The index orders suppression by this timestamp, so a tombstone that
lost it could be applied out of order. The tombstone table test now
asserts createdAt directly (the prior comment falsely claimed a golden
covered it).
B2 — emitted URLs bypassed the text chokepoint. Two parts:
(a) sanitizeText now strips ALL of unicode.Cf (a superset of the prior
enumerated bidi/zero-width list — also covers U+061C, U+2060, and
the U+E0000-E007F TAG block) alongside unicode.Cc;
(b) validateEmittedURL now REJECTS fail-closed (does not strip — a URL
is structural) any raw URL containing a Cc/Cf rune, before
returning it. A bidi-bearing metaDataUrl in event_adversarial_text
now proves URL coverage via the golden (SkipInvalidURL).
B3 — full validation ran before the lifecycle switch, so a
REVOKED/DEPRECATED event missing an Active-only field (e.g. version)
errored and never tombstoned — the exact fail-open the lifecycle split
prevents. Validation is now split: feed.ValidateIdentityKeys (logId,
agentId UUID, ansName parse + FQDN==lower(agentHost), createdAt RFC3339)
runs before the switch; the full feed.Validate (eventType, agentHost,
version presence) runs only on the Active path. A table test proves a
version-less REVOKED/DEPRECATED still tombstones, and an inverse test
keeps the Active path erroring.
Folded-in polish:
- Skip.Detail uses strconv.Quote (raw eventType/protocol with control
chars reach operator logs).
- agentHost lowercased once at the top of projectActive and tombstone
so URN, trustManifest.identity, and the well-known fallback agree;
case-variant events no longer mint byte-different identities.
- validateEmittedURL also rejects u.ForceQuery (bare trailing "?").
- spec: SearchRequest.query uses an allOf overlay adding required:[text]
so a schema validator rejects a text-less search (prose-only before).
- doc comments drop the local checkout path and PR-N references for the
public repo.
Both packages remain at 100% statement coverage; make check green;
goldens regenerated.
Signed-off-by: kperry <kperry@godaddy.com>
Add GET /v1/agents/events on the RA: a public, unauthenticated feed of
agent lifecycle events the ANS Finder ingests. The response is
byte-compatible with the production getAgentEvents contract (consumer
mirror: internal/finder/feed).
Pipeline:
- Migration 006 adds outbox_events.log_id, an index on log_id (cursor
resolution), and a partial (created_at_ms, id) feed index (retention-
seekable reads). The outbox worker persists the TL-assigned logId
atomically with sent_at_ms via MarkSent(ctx, id, logID); the feed
gates on both being non-NULL so an item in the feed is provably
sealed and its receipt is resolvable from logId. An empty logId from
a non-compliant TL is treated as a delivery anomaly (row kept pending),
never written. Open() runs ANALYZE so the planner uses the feed
indexes (SEARCH, not a primary-key SCAN over aged-out rows).
- port.FeedReader/FeedRow/FeedQuery is the read port; the SQLite
FeedStore implements it (JOINs registrations + endpoints, retention
window, outbox-id-ASC ordering, lastLogId cursor resolved to its
lowest matching outbox id, providerId -> empty page).
- service.EventsService projects each row into the wire EventItem and
owns the domain->wire token map (driven by domain.AllProtocols/
AllTransports). providerId is never emitted.
- V1EventsHandler parses limit (1-200, 422 on out-of-range), lastLogId,
providerId.
Security/correctness hardening:
- Auth exemption for the feed is EXACT-match (WithAnonymousExactPath),
not prefix: a subtree exemption let chi backtrack /v1/agents/events/*
onto the authenticated /v1/agents/{agentId}/* routes with auth
skipped. Both static and OIDC providers now match exact paths exactly
and subtree paths on a / boundary (so /docsfoo is not under /docs).
- 500 responses for unexpected (non-domain) errors return a fixed
generic detail; raw fault text no longer leaks to clients. To avoid
swallowing the cause, error responses route through a shared embedded
responder seam (handlers embed it; one injected zerolog.Logger, no
globals) whose writeError logs the real cause of any 5xx before
sanitizing — enforced by construction across all RA handlers, not the
events route alone. The package WriteError is retained as the
domain-error-only entry point for the ownership middleware.
- X-Content-Type-Options: nosniff on all responses.
Conformance tests pin the contract: byte-equality through the consumer
mirror (full AND minimal item), enum-value membership against the
swagger sets via domain.AllProtocols/AllTransports plus the enqueued
eventType tags, feed.EventItem.Validate() over every emitted item, and
an EXPLAIN QUERY PLAN assertion that both feed queries use their
indexes. Auth regression tests assert /v1/agents/events/revoke without
credentials is 401, not a silent bypass; a responder test asserts a
non-domain 500 returns a generic body AND logs the real cause.
events-feed retention added to RA config (default 720h/30d).
Signed-off-by: kperry <kperry@godaddy.com>
…lore
Add the runnable ans-finder binary serving the ARD discovery surface over
the ANS reference implementation.
Pipeline:
- internal/finder/index defines the Catalog port and query vocabulary;
internal/adapter/store/sqlitefinder is its SQLite FTS5 implementation.
Applying an Active event REPLACES the complete row set for its ansName
(grouped by ansName+logId), so an endpoint whose (type,url) changes or
is dropped between versions never lingers ACTIVE. bm25-ranked search
normalized 0-100; type/tags/capabilities/publisher/attestation-type
filters; GROUP BY facets with limit/minCount/otherCount; ansName-keyed
tombstone suppression gated on created_at; replay-safe (a newer-or-equal
tombstone is never overridden by replaying an older Active event); a
revoke that suppresses nothing while the agent is still active is
reported for a WARN. User search text is quoted into FTS5 string
literals so operators can never be injected.
- internal/finder/poller drains the RA events feed from the cursor,
projects each item via project.FromEvent, and applies pages atomically.
A structural feed error aborts the round without advancing the cursor;
an unknown eventType is a logged Skip. A non-2xx (incl. 429) is a
transient retry, never a cursor reset. A no-progress page (same cursor,
more claimed) breaks the drain loop; repeated failure at one cursor
escalates a wedge line; idle rounds log DEBUG, ingesting rounds INFO.
The HTTPS feed client enforces the transport policy (https unless
AllowHTTP, TLS never skipped), refuses redirects, and caps the body.
- internal/finder/handler serves POST /v1/search and /v1/explore per the
frozen spec, RFC 7807 Problem errors, a query-bound opaque pageToken, a
global token-bucket rate limiter (Retry-After + nosniff on responses),
the additive staleSince signal, per-request cost caps (text size/tokens,
filter-value count, facet count + dedup), and control-character
rejection on query text. Filter values accept the spec's bare-scalar or
array form. Readiness (/v1/admin/ready) is gated on the first completed
poll; health is liveness-only.
- cmd/ans-finder wires config.LoadFinder, the index, the poller goroutine,
and the HTTP server (chi, hardened timeouts, graceful shutdown that
drains the poller before closing the store on either exit path); docs at
/docs; the package comment documents health/ready semantics and the
ingestion-wedge recovery runbook.
Wiring: Makefile build-finder + docs-sync; docsui.SpecFinder embed with a
byte-equality drift guard; demo start.sh/stop.sh/run-lifecycle.sh gain an
ans-finder stage that discovers the demo agent (publisher-filtered) end to
end. The frozen finder spec is amended with the additive staleSince
response field and a note that an over-max pageSize is clamped.
internal/finder/{index,poller,handler} and the sqlitefinder adapter are
table-tested against in-memory SQLite and httptest feed servers; a
conformance test validates response field names AND spec-required keys
against the embedded spec. cmd/ans-finder is excluded from the coverage
denominator per repo policy; overall coverage stays above the 90% gate.
Signed-off-by: kperry <kperry@godaddy.com>
…gnote
The C2SP signed-note checkpoint parser was duplicated ~85% between the
offline verifier (cmd/ans-verify) and the TL checkpoint-read path
(internal/tl/service). Consolidate it into a new internal/lognote
package that depends only on internal/crypto and its leaf deps — no
internal/tl/logstore, no storage adapters, no Tessera. This lets
cmd/ans-verify link the verification path without pulling the
log-writer dependency tree.
lognote exposes:
- Signature{Name, Raw, Blob} with KeyHash/KeyHashHex/Body/Classify
- SplitNote(raw) (body, sigs, found) — lenient tokenization; its doc
comment carries the safety invariant that splitting proves nothing
and only VerifyCheckpointNote's unconditional gate (known keyhash
AND valid ECDSA sig over the fixed body) establishes trust
- Checkpoint{Origin, Size, RootHash}
- VerifyCheckpointNote(raw, keysByHash) (*Checkpoint, error)
- VerifyC2SPECDSA (moved verbatim from logstore, DER + legacy P1363)
Migration:
- logstore.VerifyC2SPECDSA deleted outright (no alias); the signer
stays. Its round-trip test now verifies through lognote.
- internal/tl/service deletes splitNoteBody/keyhashFromSumdbSig/
classifySumdbSig and the sigType consts; viewFromRecord maps
lognote.Signature (origin fallback, "0x"+KeyHashHex(), algES256);
the enrich switch gains a default arm; enrichC2SPSignature calls
lognote.VerifyC2SPECDSA.
- cmd/ans-verify deletes verifyCheckpointNote + keyHashHex and the
VerifiedCheckpoint type; verifiedCheckpoint returns
*lognote.Checkpoint and delegates to lognote.VerifyCheckpointNote.
go list -deps ./cmd/ans-verify no longer references tl/logstore or
Tessera client/storage.
internal/lognote ships with table-driven tests at 100% of statements,
including a golden note signed over real bytes with a real ECDSA key
and the adversarial case (known keyhash + garbage sig rejected, loop
continues to a later valid line). Exhaustive cases live in lognote;
thin smoke tests remain at the migrated sites.
Wire-delta disclosures (behavior changes from unifying the two
parsers onto internal/lognote, recorded for reviewers):
- CheckpointView.Signatures: an invalid-base64 signature line is now
omitted from the rendered signatures instead of surfaced with
Valid=false. This state is reachable only via corrupted checkpoint
storage (the TL never writes a malformed line), and dropping it is
fail-closed — a line we cannot decode carries no trustworthy
signer/keyhash to display.
- cmd/ans-verify checkpoint tokenization is unified onto the service
parser's semantics. Consequence: tab-separated signature lines no
longer parse (no writer emits tabs — sumdb-note uses single
spaces), and CRLF / leading-whitespace lines now become signature
candidates (still subject to the same keyhash+signature gate, so
no verification weakening).
Signed-off-by: kperry <kperry@godaddy.com>
Add docs/pr-specs/FINDER-ard-discovery-service.md, the design of record for ans-finder: an ARD-conformant (Agentic Resource Discovery v0.5) discovery service over ANS-registered agents. Covers the feed-only ingestion decision and its accepted trades, the pure EventItem-to-CatalogEntry projection with the tombstone safety rule, the /v1/search and /v1/explore API surface, the trust model and receipt semantics, the text-hygiene and URL-policy security contracts that must precede any wire freeze, the deviations from the ARD and ANS RA contracts, and verbatim ARDS v0.5 and production-swagger field tables in the appendix. Docs-only; new docs/pr-specs/ directory. Signed-off-by: kperry <kperry@godaddy.com>
Adds docs/architecture/ans-finder.md — the as-built companion to the FINDER design spec: system topology, register-to-discover-to-prove sequence, poller round semantics, index entry lifecycle, and the asserted-vs-provable trust split, each as a Mermaid diagram with the operational details (feed gating, projection chokepoints, request-cost caps, readiness semantics, runbook pointers) as built and verified. Trues up the design spec's deviations table with the two rows the implementation added: nextPageToken response naming vs ARD §7.2's example, and the additive optional staleSince freshness field. Signed-off-by: kperry <kperry@godaddy.com>
…spec Signed-off-by: kperry <kperry@godaddy.com>
…ver-card+json and adjust pagination token references Signed-off-by: kperry <kperry@godaddy.com>
Brings in verified identities (#41), the x/mod 0.37.0 bump (#44), and pluggable server-cert issuance via the certificate-order lifecycle (#45). Both lanes now sit side by side: the finder feed/discovery surface and the verified-identity surface. Conflict resolutions: - internal/config RAConfig: keep both Identity (main) and EventsFeed (finder) fields. - internal/port/store.go: keep both FeedReader (finder) and IdentityStore/IdentityLinkStore (main) — independent ports. - cmd/ans-ra/main.go: construct the feed store + events service and the identity stores + identity service together; lifecycle handler is NewLifecycleHandler(regSvc, logger).WithIdentityViews(identitySvc). - spec/api-spec-v2.yaml + docsui mirror: keep both the Events and Verified Identities tags (docsui regenerated via make docs-sync). - scripts/demo/run-lifecycle.sh: keep finder's offline-verify (bin/ans-verify) and ans-finder discovery steps alongside main's identity steps; renumbered to 14-20. - internal/ra/handler/identity_handler_test.go: pass zerolog.Nop() to the logger-param handler constructors finder introduced. - internal/config: hoist the "self" and "noop" adapter-type discriminators to constants. goconst flags them only once both branches' validators (main's CA-server + resolver checks, finder's config package additions) combine in one package. make check passes; internal/ coverage 90.6% (>= 90% gate). Signed-off-by: kperry <kperry@godaddy.com>
…mp comment
spec/api-spec-v2.yaml: the agent-events feed's 422/500 responses
declared application/problem+json but referenced ErrorResponse, whose
shape (status: "ERROR" string enum, message/details, no type/title) does
not match what the handler emits. writeProblem
(internal/ra/handler/errors.go) serializes RFC 7807: {type, title,
status (int), detail, code}. Add a Problem component schema matching that
shape (type/title/status required; detail/code optional) and reference it
from both responses. ErrorResponse is retained for the routes that still
use it. docsui mirror regenerated via make docs-sync.
internal/adapter/store/sqlitefinder/search.go: the rfc3339 comment
claimed "the feed normalizes its timestamps," which is false — the finder
carries feed timestamps verbatim (project.ProjectedEntry) and feed.go
validates createdAt with time.Parse but discards the result. Reword to
state the real dependency: the lexical expires_at compare is sound only
because the RA emits canonical UTC RFC 3339, not because anything
normalizes.
Signed-off-by: kperry <kperry@godaddy.com>
Brings in Adapter Style DNS Discovery Profiles (#25), the 0.1.6 release (#39), and dependency bumps: x/net 0.56.0 (#49), x/crypto 0.53.0 (#51), go-oidc 3.19.0 (#48), sqlite 1.53.0 (#52), plus the actions/checkout and actions/setup-go CI bumps (#50, #53). #25 threaded a discovery ProfileRegistry through NewRegistrationService; that signature change auto-merged into cmd/ans-ra/main.go and the RA handler tests, and build + vet are clean across the finder code. Only go.mod conflicted: kept the higher dependency versions from main (x/net 0.56.0, sqlite 1.53.0) alongside the finder's direct gopkg.in/yaml.v3, then go mod tidy. docsui mirror re-synced from the merged spec; the RFC 7807 Problem schema and the feed's 422/500 references survive intact. make check passes; internal/ coverage 90.8% (>= 90% gate). Signed-off-by: kperry <kperry@godaddy.com>
The ARD spec (ards-project/ard-spec, adr/0009-urn-nid-length-air.md) selects `air` as the URN namespace identifier: a two-character NID (`ai`) is invalid under URN NID length rules (NID must exceed two characters), so the lineage handle is `urn:air:<publisher>:<namespace>:<agent-name>`. Updates the finder identifier construction, tests, OpenAPI specs, golden mock data, and docs from `urn:ai:` to `urn:air:`. Aligns with the ai-catalog work, which already uses `urn:air:`. Signed-off-by: scourtney-godaddy <scourtney@godaddy.com>
Signed-off-by: kperry <kperry@godaddy.com> # Conflicts: # go.mod # internal/config/config.go
There was a problem hiding this comment.
Pull request overview
This PR adds an ARD-compliant discovery component (ans-finder) on top of the ANS RA/TL reference stack by introducing a public RA agent-events feed, persisting TL log cursors for those events, and implementing a Finder that polls/indexes/serves search + explore endpoints with OpenAPI-backed docs.
Changes:
- Add a public, exact-path unauthenticated RA feed (
GET /v1/agents/events) backed by delivered outbox rows with TLlogIdcursors. - Introduce the new
ans-finderservice (poller + projection + SQLite FTS5 index + HTTP handlers), plus config, docs UI spec embedding, and demo scripts. - Refactor RA handlers to embed a responder that logs server-side causes for sanitized 500s, and tighten auth anonymous-path matching semantics.
Reviewed changes
Copilot reviewed 112 out of 112 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| scripts/demo/stop.sh | Stop script updated for ans-finder + longer SIGTERM grace period. |
| scripts/demo/start.sh | Demo start script now composes finder config, starts ans-finder, and checks finder health/ready. |
| scripts/demo/run-lifecycle.sh | Demo lifecycle now includes an ans-finder search step and updates step numbering. |
| scripts/demo/common.sh | Adds FINDER_URL default for demo scripts. |
| Makefile | Adds build-finder and syncs finder OpenAPI spec into docs UI. |
| internal/tl/service/checkpoint_helpers_test.go | Updates/renames tests to service-layer signature-view mapping. |
| internal/tl/logstore/signer_errors_test.go | Removes negative-path tests for VerifyC2SPECDSA (moved to lognote). |
| internal/tl/logstore/c2spsigner.go | Removes VerifyC2SPECDSA from logstore. |
| internal/tl/logstore/c2spsigner_test.go | Switches verification smoke test to internal/lognote verifier. |
| internal/ra/outbox/worker.go | Persists TL logId into outbox rows; rejects empty logId responses by retrying. |
| internal/ra/outbox/worker_test.go | Adds regression tests for persisting logId and keeping empty-logId rows pending. |
| internal/ra/handler/v1renewal.go | Handler now embeds responder + takes injected logger; uses h.writeError. |
| internal/ra/handler/v1registration.go | Handler now embeds responder + takes injected logger; uses h.writeError. |
| internal/ra/handler/v1lifecycle.go | Handler now embeds responder + takes injected logger; uses h.writeError. |
| internal/ra/handler/v1events.go | New public events-feed handler for GET /v1/agents/events. |
| internal/ra/handler/v1certificates.go | Handler now embeds responder + takes injected logger; uses h.writeError. |
| internal/ra/handler/registration.go | Handler now embeds responder + takes injected logger; uses h.writeError. |
| internal/ra/handler/lifecycle.go | Handler now embeds responder + takes injected logger; uses h.writeError. |
| internal/ra/handler/lifecycle_test.go | Updates fixtures to pass a logger into handler constructors. |
| internal/ra/handler/identity_handler_test.go | Updates fixtures to pass a logger into handler constructors. |
| internal/ra/handler/errors.go | Adds responder to log underlying 500 causes while returning sanitized details. |
| internal/port/store.go | Adds feed read-model port types (FeedRow, FeedQuery, FeedReader). |
| internal/finder/project/urn.go | Adds URN minting + labelization for ARD identifiers. |
| internal/finder/project/types.go | Defines Finder projection output types (Entry/ProjectedEntry/Skips). |
| internal/finder/project/sanitize.go | Adds control/bidi sanitization and strict URL validation gate. |
| internal/finder/project/internal_test.go | Adds white-box tests for projection helper branches. |
| internal/finder/project/testdata/event_registered.json | Adds projection input fixture for AGENT_REGISTERED. |
| internal/finder/project/testdata/event_registered.golden.json | Adds golden output for AGENT_REGISTERED projection. |
| internal/finder/project/testdata/event_renewed.json | Adds projection input fixture for AGENT_RENEWED. |
| internal/finder/project/testdata/event_renewed.golden.json | Adds golden output for AGENT_RENEWED projection. |
| internal/finder/project/testdata/event_revoked.json | Adds projection input fixture for AGENT_REVOKED. |
| internal/finder/project/testdata/event_revoked.golden.json | Adds golden output for AGENT_REVOKED projection. |
| internal/finder/project/testdata/event_revoked_minimal.json | Adds minimal revoked-event fixture. |
| internal/finder/project/testdata/event_revoked_minimal.golden.json | Adds golden output for minimal revoked-event fixture. |
| internal/finder/project/testdata/event_deprecated.json | Adds projection input fixture for AGENT_DEPRECATED. |
| internal/finder/project/testdata/event_deprecated.golden.json | Adds golden output for AGENT_DEPRECATED projection. |
| internal/finder/project/testdata/event_no_endpoints.json | Adds projection input fixture with no endpoints. |
| internal/finder/project/testdata/event_no_endpoints.golden.json | Adds golden output for no-endpoints fixture. |
| internal/finder/project/testdata/event_no_displayname.json | Adds projection input fixture with no display name. |
| internal/finder/project/testdata/event_no_displayname.golden.json | Adds golden output for no-displayname fixture. |
| internal/finder/project/testdata/event_adversarial_text.json | Adds adversarial text fixture for sanitization/URL policy. |
| internal/finder/project/testdata/event_adversarial_text.golden.json | Adds golden output for adversarial text fixture. |
| internal/finder/project/testdata/event_nonz_offset.json | Adds non-Z offset timestamp fixture. |
| internal/finder/project/testdata/event_nonz_offset.golden.json | Adds golden output for non-Z offset fixture. |
| internal/finder/poller/httpclient.go | Implements HTTP feed client with strict base URL policy and response caps. |
| internal/finder/poller/httpclient_test.go | Tests feed client URL policy and fetch behavior. |
| internal/finder/index/index.go | Defines Finder index port + supported filter/facet fields. |
| internal/finder/index/index_test.go | Tests supported-field set and constants. |
| internal/finder/handler/ratelimit.go | Adds global token-bucket limiter for anonymous discovery endpoints. |
| internal/finder/handler/ratelimit_test.go | Tests rate limiter behavior. |
| internal/finder/handler/errors.go | Adds RFC7807 Problem writing with nosniff on Finder responses. |
| internal/finder/handler/dto_internal_test.go | Tests pageToken encode/decode and query hashing. |
| internal/finder/handler/handler_error_test.go | Ensures internal errors are sanitized for anonymous callers. |
| internal/finder/handler/conformance_test.go | Enforces handler response keys match embedded OpenAPI spec. |
| internal/adapter/store/sqlitefinder/store_edge_test.go | Adds store edge-case coverage (reopen, unknown lifecycle, unsupported fields). |
| internal/adapter/store/sqlitefinder/sqlite.go | Adds SQLite FTS5-backed Finder store with embedded migrations. |
| internal/adapter/store/sqlitefinder/migrations/001_initial.sql | Defines initial Finder index schema + FTS + cursor table. |
| internal/adapter/store/sqlitefinder/search.go | Implements ranked search + expiry filtering + deterministic ordering. |
| internal/adapter/store/sqlitefinder/explore.go | Implements facet exploration over the matched set. |
| internal/adapter/store/sqlitefinder/filter.go | Builds safe filter SQL clauses with side-table mapping. |
| internal/adapter/store/sqlitefinder/ftsquery.go | Escapes user text into safe FTS5 MATCH expression. |
| internal/adapter/store/sqlitefinder/cursor.go | Persists/reads poll cursor + last successful poll time. |
| internal/adapter/store/sqlite/stores_test.go | Updates outbox tests for MarkSent(logId) behavior. |
| internal/adapter/store/sqlite/sqlite.go | Runs ANALYZE after migrations to seed planner stats for feed queries. |
| internal/adapter/store/sqlite/outbox.go | Updates MarkSent to atomically set sent_at_ms + log_id. |
| internal/adapter/store/sqlite/migrations/006_outbox_log_id.sql | Adds outbox log_id column + indexes for feed/cursor performance. |
| internal/adapter/store/sqlite/feed.go | Adds FeedStore implementing FeedReader over delivered+logged outbox rows. |
| internal/adapter/docsui/docsui.go | Embeds finder OpenAPI spec into docs UI bundle. |
| internal/adapter/docsui/docsui_test.go | Verifies embedded finder spec matches canonical spec file. |
| internal/adapter/auth/static.go | Adds exact-path anonymous exemptions and safer subtree matching semantics. |
| internal/adapter/auth/oidc.go | Mirrors exact/subtree anonymous matching semantics for OIDC auth. |
| internal/domain/protocol.go | Adds AllProtocols / AllTransports enumerators. |
| internal/domain/protocol_test.go | Tests protocol/transport enumerators contain all known constants. |
| internal/config/finder.go | Adds Finder config types + loader + validation. |
| internal/config/finder_test.go | Tests Finder config defaults, env overrides, and validation. |
| internal/config/defaults.go | Adds Finder defaults and RA events-feed default retention. |
| internal/config/config.go | Adds RA events-feed config and shared URL validation helper. |
| internal/config/config_test.go | Tests RA events-feed retention defaulting/clamping. |
| config/finder-local.yaml | Adds local dev config for ans-finder. |
| go.mod | Promotes yaml.v3 to a direct dependency. |
| cmd/ans-verify/walk.go | Moves checkpoint note parsing/verification to internal/lognote. |
| cmd/ans-verify/walk_test.go | Updates checkpoint verification tests to new lognote-based implementation. |
| cmd/ans-ra/main.go | Wires feed store + events service + handler; adds nosniff middleware; passes logger into handlers; adds exact anonymous exemption for feed. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
The EventsFeed.Retention doc comment promised a config-level disable (non-positive value = no lower bound) that RAConfig.Validate actually normalizes back to the 720h default. Reword the comment to state the real behavior: the store's no-floor mode exists only via direct construction, which tests use. Signed-off-by: kperry <kperry@godaddy.com>
4b78243 to
a1487e9
Compare
TestPoller_TickerDrivesSecondRound slept a fixed 120ms and expected a 30ms ticker round to have completed, which flaked under -race on a loaded CI runner. Poll the index for the expected two entries with a 5s deadline instead: the wait stretches under load rather than failing, and the store's single pinned connection serializes the concurrent read with the poller's writes. Signed-off-by: kperry <kperry@godaddy.com>
5060b80 to
d058b3a
Compare
| if c.TLClient.Timeout <= 0 { | ||
| c.TLClient.Timeout = 10 * time.Second | ||
| } | ||
| if c.EventsFeed.Retention <= 0 { |
There was a problem hiding this comment.
The /v1/agents/events returns events up to 30 days ago, is it possible to return events from the very beginning?
There was a problem hiding this comment.
I think we can do this as a fast follow if needed. If users are spinning up the RI though, wouldn't they need to keep it around for 30 days?
runRound started Run in a goroutine, slept a fixed 50ms, and cancelled; runFor did the same over a fixed duration. On a loaded CI runner the cancel landed mid-apply, so positive assertions raced the round (TestPoller_SkipDoesNotAbort and TestPoller_TombstoneNoOpLogsWarn failed with 'context canceled' during the sqlitefinder apply). Export the existing single-round method as RunOnce — Run is unchanged and still wraps it with the interval ticker — and call it synchronously from runRound: no goroutine, no sleep, no wall-clock dependence. The wedge-escalation test now drives exactly the five failing rounds the threshold requires instead of hoping a 15ms ticker fires five times in 300ms, and the unused runFor helper is deleted. Signed-off-by: kperry <kperry@godaddy.com>
…discovery service Reconciles this branch with main's ARD discovery work (#46) at the two points where the features genuinely interact, and addresses the PR #47 Copilot review. Feed visibility for inline-sealed activations - The agent-events feed (GET /v1/agents/events, the Finder's ingest source) is a read model over DELIVERED outbox rows, and the inline activation seal bypasses the outbox worker — so a sealed AGENT_REGISTERED would never surface on the feed and the Finder would never discover the agent. SealAgentEvent now returns the TL ack's logId, and VerifyDNS records a PRE-DELIVERED outbox row (OutboxStore.RecordSealed: payload + sent_at_ms + log_id at insert) inside the activation transaction: feed visibility commits atomically with ACTIVE, the worker never claims the row, and the feed serves the exact bytes the TL verified. Pinned by TestVerifyDNS_SealedActivationIsFeedVisible and end-to-end by the lifecycle demo's Finder discovery step. Finder-parity URN labels - The Finder mints urn:air:{host}:agents:{label} with the label derived from the labelized display name (internal/finder/project/urn.go); the catalog derived it from the leftmost DNS label, so search results and the published ai-catalog.json handed consumers two different lineage identifiers for the same agent. The catalog now applies the Finder's derivation (trim, collapse whitespace runs to hyphens, preserve case, lowercase host) so both surfaces mint ONE handle — also fixing the collision where two distinct same-version agents on one host would have shared a leftmost-label URN. A registration whose display name is missing or sanitizes away to nothing is not catalog-eligible (NO_LABEL), mirroring the Finder's skip. This supersedes the earlier leftmost-label choice; the lifecycle demo now asserts the Finder and the catalog return the identical URN. Copilot review fixes - The 409 AGENT_HOST_TAKEN detail now says a live (ACTIVE or DEPRECATED) registration holds the FQDN — DEPRECATED blocks reuse too, and the old text implied otherwise. - A handler-test comment claimed the exclusivity check is re-checked atomically at activation; aligned with the documented best-effort semantics (the check runs before the inline seal, outside the tx). Signed-off-by: Connor Snitker <csnitker@godaddy.com>
This pull request introduces the new
ans-finderservice, which implements the Agentic Resource Discovery (ARD) service over the ANS reference implementation. It also makes several improvements to the RA service, including the addition of a public agent-events feed and enhanced handler logging. The Makefile is updated to support building and documenting the new finder service. Minor cleanups and dependency updates are also included.New ANS Finder Service:
ans-finderbinary (cmd/ans-finder/main.go), which:Makefile and Build System Updates:
Makefileto support building the newans-finderbinary and to sync its OpenAPI spec into the docs UI. [1] [2] [3] [4]RA Service Improvements:
/v1/agents/eventsfeed to the RA, with exact-path anonymous access and handler wiring for event streaming. [1] [2] [3] [4] [5]X-Content-Type-Options: nosniffon all responses, especially for the public events feed.Code Cleanup and Dependency Updates:
VerifiedCheckpointstruct incmd/ans-verify/walk.go. [1] [2] [3] [4]