Skip to content

[AI assisted] Plan D: Consolidated Approach SVCB + dnsRecordStyle#13

Open
scourtney-godaddy wants to merge 5 commits into
feat/plan-acd-capabilities-hashfrom
feat/plan-d-consolidated-svcb
Open

[AI assisted] Plan D: Consolidated Approach SVCB + dnsRecordStyle#13
scourtney-godaddy wants to merge 5 commits into
feat/plan-acd-capabilities-hashfrom
feat/plan-d-consolidated-svcb

Conversation

@scourtney-godaddy
Copy link
Copy Markdown

@scourtney-godaddy scourtney-godaddy commented May 16, 2026

Adds the Consolidated Approach SVCB record format and a dnsRecordStyle field on the register request, so an operator can choose between the legacy four-record set and the single-record SVCB style described in the DNS-AID consensus discussion.


The V2 register endpoint accepts an optional dnsRecordStyle field
selecting which DNS records the RA emits in dnsRecordsProvisioned
and surfaces in the registration response for the operator to publish.
Three values:

  • consolidated (default, recommended): one SVCB record per protocol
    endpoint at the agent's bare FQDN, carrying connection hints and
    capability locators in a single DNS lookup. Aligns with the
    Consolidated Approach DNSAID consensus discussion at
    github.com/jmozley-infoblox/DNS_for_agents_collaboration.
  • legacy: the original _ans TXT family the RA has emitted since
    v0.1.x, plus an HTTPS RR (RFC 9460 type 65) at the agent FQDN
    carrying ALPN signalling for clients that don't speak ANS protocol
    detection.
  • both: the union of consolidated and legacy, the transition shape
    for AHPs migrating from one to the other.

Empty or missing dnsRecordStyle normalizes to consolidated. An
invalid value surfaces as INVALID_DNS_RECORD_STYLE (HTTP 422).

The SVCB row at the bare FQDN follows RFC 9460 ServiceMode
(SvcPriority 1, TargetName .) so address resolution stays at the
agent's FQDN. SvcParams emitted: alpn, port, wk (well-known
metadata path), card-sha256 (the base64url-encoded
capabilitiesHash from PR #12 in this stack). SvcParams from
DNS-AID, ANS, and other agentic specs coexist in the same record
per RFC 9460 §8 unknown-key ignore semantics.

Migration 007 adds dns_record_style (nullable) to
agent_registrations. Pre-Plan-D rows load with the field empty;
ComputeRequiredDNSRecords treats empty as consolidated.

The DNS verifier (internal/adapter/dns/lookup.go:verifySVCB)
mirrors the existing HTTPS-RR verifier, comparing the resolver's
parsed record against the RA's expected SvcParam set after
normalization. Quoted vs unquoted SvcParamValue forms normalize
to the same equality check; provisional SvcParamKey names that
the resolver-side formatter doesn't yet recognize surface as
non-blocking integrity findings (Required=false).

Tests cover SVCB emission for each style, the cross-record
hash consistency check (SVCB card-sha256 matches the TL's
capabilitiesHash), and the verifier round-trip against the
miekg/dns resolver.

Stacks on #12 (Plans A + C). Merge order: #12 → this.

Test plan

  • make check (gofmt + golangci-lint + 90% coverage gate)
  • SVCB emission tests for consolidated, legacy, both styles
  • Cross-record hash consistency test (SVCB card-sha256 ↔ TL capabilitiesHash)
  • DNS verifier round-trip test against miekg/dns
  • Reviewer confirms migration 007 applies cleanly against the dev DB

🤖 Generated with Claude Code

scourtney-godaddy and others added 3 commits May 16, 2026 10:10
Extends domain.ComputeRequiredDNSRecords to emit one SVCB record per
protocol at the agent bare FQDN, alongside the existing _ans TXT
family. The SVCB row carries:

  alpn=PROTOCOL              from endpoint.Protocol
  port=443                   ServiceMode SvcPriority 1 at the FQDN
  wk=SUFFIX                  A2A: agent-card.json; MCP: mcp.json
  card-sha256=BASE64URL      base64url of reg.CapabilitiesHash when set

card-sha256 and capabilities_hash are the section 4.4.2 cross-check
encodings of the same SHA-256 (DNS uses base64url, TL uses hex). When
the operator did not submit agentCardContent, the SvcParam is absent
and verifiers fall back to TOFU on first Trust Card fetch.

Adds verifySVCB to LookupVerifier mirroring verifyHTTPS. Tests cover
present-matching, absent (zone has different name), and wrong-target
cases (AliasMode where ServiceMode was expected). Provisional SvcParams
(wk, card-sha256) are unit-tested at the domain layer because miekg/dns
rejects them in zone-file form until IANA registration; the verifier-
level test exercises only registered SvcParamKeys (alpn, port).

Required=false: section 4.4.2 marks Consolidated Approach SVCB as MAY,
opt-in during the _ans TXT transition.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds dnsRecordStyle to the V2 RegistrationRequest with three values:
"consolidated" (default, recommended), "legacy" (original _ans TXT
shape), "both" (transition union). Empty -> consolidated. Invalid ->
422 INVALID_DNS_RECORD_STYLE.

The default points new integrations at the lean Consolidated Approach
shape per section 4.4.2 SHOULD: one SVCB record at the bare FQDN per
protocol, plus shared _ans-prefixed records and TLSA. Operators on
existing zone-edit tooling for _ans TXT pick "legacy" explicitly.
Migration operators set "both" for a defined window then flip back to
"consolidated".

V1 lane pins to "legacy" regardless of the request because V1 callers
predate the Consolidated Approach and their tooling expects the
original shape. V1 has no dnsRecordStyle field on the wire.

Migration 007 adds the dns_record_style column on agent_registrations.
Nullable for backwards compatibility with pre-Plan-D rows.

Tests:
- "both" emits 2x _ans TXT + 2x SVCB + shared records (existing test
  updated to set DNSRecordStyleBoth so it exercises the union path).
- New tests cover "consolidated" (no _ans TXT), "legacy" (no SVCB),
  and "both" (union); the SvcParam wk/card-sha256 tests already
  covered the consolidated path implicitly.
- Lint: extracted applyDNSRecordStyle helper to keep RegisterAgent
  under the funlen ceiling.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes a long-standing spec/impl gap: ANS_SPEC.md section A.8.1 lists
the HTTPS RR (RFC 9460 type 65) at the agent FQDN as RA-generated
content the AHP provisions, but ComputeRequiredDNSRecords had never
emitted it. The DNSRecordHTTPS enum value and verifyHTTPS verifier
were already in place; this commit wires the emission.

Generated only for the legacy + both styles, not for consolidated:
the SVCB rows the consolidated form publishes already carry the same
alpn/port/ECH SvcParams the HTTPS RR would, so emitting both would
duplicate content and risk the two records drifting (section A.8.2
explicitly notes this). Operators on the consolidated path who still
want HTTPS-RR-aware clients (typically browsers) to see the metadata
can publish their own HTTPS RR as a side addition.

Required=false: HTTPS RR is blocked by CNAME at the agent FQDN per
RFC 1034 section 3.6.2. AHPs whose apex is fronted via CNAME cannot
publish it at the same name; the RA does not block verify-dns on
its absence.

Tests pin: legacy style includes HTTPS RR + no SVCB; consolidated
style includes SVCB + no HTTPS RR; both style includes both
families.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds support for a new DNS record emission mode (“Consolidated Approach” SVCB) and introduces a dnsRecordStyle selector on the V2 register flow so operators can choose between legacy _ans TXT discovery, consolidated SVCB discovery, or both, with persistence via a new SQLite migration.

Changes:

  • Add dnsRecordStyle to the V2 register request (OpenAPI + handler/service plumbing) and persist it on agent_registrations.
  • Extend required-DNS-record computation to optionally emit SVCB records (and update record-type enums/docs).
  • Add DNS lookup verification for SVCB and propagate DNSSEC AD-bit handling for HTTPS/SVCB.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
spec/api-spec-v2.yaml Adds dnsRecordStyle to the V2 register schema and extends DNS record type enum to include SVCB.
internal/adapter/docsui/openapi/ra.yaml Mirrors the OpenAPI changes used by the docs UI.
internal/ra/handler/registration.go Accepts dnsRecordStyle in the JSON request and forwards it to the service layer.
internal/ra/service/registration.go Applies the requested DNS record style during registration creation.
internal/ra/service/helpers.go Implements style normalization/validation (applyDNSRecordStyle).
internal/ra/service/helpers_test.go Adds unit tests for applyDNSRecordStyle validation and error messaging.
internal/domain/dnsrecords.go Introduces DNSRecordStyle, adds SVCB record type, and emits per-style DNS record sets.
internal/domain/dnsrecords_test.go Adds tests covering style matrix emission, SVCB param composition, and helper functions.
internal/adapter/dns/lookup.go Adds SVCB lookup verification and propagates AD-bit for HTTPS/SVCB.
internal/adapter/dns/dns_test.go Adds SVCB verifier tests and AD-bit propagation tests for HTTPS/SVCB.
internal/ra/service/lifecycle.go Expands DNSSEC-mismatch hard-fail rules to include SVCB/HTTPS.
internal/port/dns.go Updates DNSSECVerified field documentation to include SVCB/HTTPS semantics.
internal/domain/agent.go Stores DNSRecordStyle on the registration aggregate.
internal/adapter/store/sqlite/migrations/007_agent_dns_record_style.sql Adds/persists dns_record_style with CHECK + backfill behavior.
internal/adapter/store/sqlite/agent.go Reads/writes the new dns_record_style column.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +299 to +303
got := formatHTTPSValue(svcb)
if r.Actual == "" {
r.Actual = got
}
if normalizeHTTPS(got) == wantNorm {
Type: DNSRecordSVCB,
Value: value,
Purpose: PurposeDiscovery,
Required: false,
Comment thread internal/ra/handler/registration.go Outdated
Comment on lines +52 to +54
// for this registration. One of "consolidated" (default,
// recommended), "legacy" (original `_ans` TXT shape), "both"
// (transition union). Empty/missing → consolidated. Invalid
Comment thread internal/ra/service/registration.go Outdated
Comment on lines +91 to +92
// in dnsRecordsProvisioned and tells the operator to publish.
// "consolidated" (default), "legacy", or "both". Empty value is
Comment thread internal/domain/agent.go Outdated
Comment on lines +120 to +122
// for this registration: "consolidated" (Consolidated Approach
// SVCB rows, default), "legacy" (the original `_ans` TXT shape),
// or "both" (the transition union). Empty at the domain layer
Comment on lines +15 to +19
-- Nullable to allow rows that pre-date this migration to load. The
-- backfill below sets every such row to LEGACY because every agent
-- registered before this PR shipped received the original `_ans` TXT
-- shape — defaulting them to CONSOLIDATED would silently demand SVCB
-- records they were never told to publish. CHECK matches the
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated 7 comments.

Comment thread spec/api-spec-v2.yaml
Comment on lines +1122 to +1124
Set of DNS record families the RA emits in the 202 register
response's dnsRecords[] and in the AGENT_REGISTERED TL
event's attestations.dnsRecordsProvisioned[]. Not echoed on
Comment on lines +1122 to +1125
Set of DNS record families the RA emits in the 202 register
response's dnsRecords[] and in the AGENT_REGISTERED TL
event's attestations.dnsRecordsProvisioned[]. Not echoed on
GET /v2/ans/agents/{agentId}.
Comment on lines +19 to +23
-- Nullable to allow rows that pre-date this migration to load. The
-- backfill below sets every such row to ["ANS_TXT"] because every
-- agent registered before this PR shipped received the original
-- `_ans` TXT shape — defaulting them to ["ANS_SVCB"] would silently
-- demand SVCB records they were never told to publish. CHECK uses
Comment thread spec/api-spec-v2.yaml
Comment on lines +1116 to +1120
type: array
items:
$ref: '#/components/schemas/DNSRecordStyle'
uniqueItems: true
minItems: 1
Comment on lines +606 to +615
// Required flag. `r.Found` is true only when the actual
// matched after type-specific normalization, so
// `DNSSECVerified && !Found` captures "response was signed,
// but its content disagreed with what we issued" — the exact
// attack we block (an attacker rewrote a record in a signed
// zone). Applies to TLSA (cert binding), SVCB (capability
// locator with card-sha256), and HTTPS (service binding).
if r.DNSSECVerified && !r.Found {
switch r.Record.Type {
case domain.DNSRecordTLSA, domain.DNSRecordSVCB, domain.DNSRecordHTTPS:
Comment thread spec/api-spec-v2.yaml
Comment on lines +1026 to +1030
DNSRecordStyle:
type: string
enum: [ANS_SVCB, ANS_TXT]
description: |
Names one DNS record family the RA can emit for an agent
Type: DNSRecordSVCB,
Value: value,
Purpose: PurposeDiscovery,
Required: false,
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI assisted Pull request created with AI assistance

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants