Skip to content

docs(api): discovery, naming, groups, events, rules, flows and DNS (C5) - #40

Merged
Zakkaus merged 5 commits into
daeuniverse:honkfrom
Zakkaus:fix/contract-discovery-groups
Sep 28, 2026
Merged

Zakkaus merged 5 commits into
daeuniverse:honkfrom
Zakkaus:fix/contract-discovery-groups

Conversation

@Zakkaus

@Zakkaus Zakkaus commented Sep 28, 2026 •

Copy link
Copy Markdown
Collaborator

C5 of the API contract revision plan: discovery, naming, groups, events, rules, flows and DNS. Three commits: naming, extensions and events; groups; DNS, rules and flows. Two more address review (see Review fixes). yarn check:contract passes (84 tests, 3 new), and hexo generate passes the link check.

Honk status per finding: matches means honk already behaves as written; HC means honk changes in its HC PR (rows appended to the drift ledger).

F6 Event invalidation (Q5)

  • Change: the event-kind table in events.md gains an Invalidates column. A server sends runtime.updated after any change in its row, either coalesced or on a sampling tick, and every activation is followed by runtime.updated once its runtime-settings reset is visible. No per-resource events.
  • honk: matches. observation.rs committed publishes runtime.updated after settings.activate on every commit (control/reload/transaction.rs:765-772), and the sampler tick (server.rs:56) covers the rest. The conditional F6 item in HC is not needed.
  • dae: publish runtime.updated from the reload completion path and a periodic tick; no new event kinds.

F9 Engine extensions (Q6)

  • Change: new "Engine extensions" section in capabilities.md. Engine-only routes go under /api/v1/x-<engine>/, capability entries under resources["x-<engine>"], discovery links under links["x-<engine>"], and engine-only fields under an x-<engine> member. Capabilities.resources and AdmittedDiscovery.links are now closed apart from ^x- members, and engine.name is a lowercase identifier (^[a-z][a-z0-9]*$). The /api/v1/discovery alias is not allowed. A new test covers the namespaced and the rejected forms.
  • honk: HC. Drop the /api/v1/discovery alias (handlers.rs:25, security.rs:259). Move config export, import, revisions, revision activation and /runtime/mode under /api/v1/x-honk/, move their capability keys and links into x-honk, and move store in GET /config to x-honk.store (config/http.rs:80). Until HC lands, honk's capabilities and discovery fail the closed schemas.
  • Flow-step actions (C4): honk's activate_direct/activate_proxy strings come from control/connection/udp.rs and its tests. Renaming them to an x-honk- form would touch control/, which is not cheap, so I left them as engine-defined values documented in the honk notes.
  • dae: nothing to change unless dae adds its own extras, which then go under x-dae.

F19 Group check URL and policies (Q9)

  • Change: one check_url (no list). dae's addresses after the URL go in config["x-dae"].check_addresses and are dropped when a patch changes check_url. policy.kind gains fixed, and interrupt_connections becomes nullable.
  • honk: matches. It always reports a boolean and never reports fixed.
  • dae: tcp_check_url[0] maps to check_url and tcp_check_url[1:] to x-dae.check_addresses (component/outbound/dialer/connectivity_check.go:426-445). The fixed policy is common/consts/dialer.go:28. dae has no interrupt option, so it reports null.

F20 Group JSON Patch semantics (Q10)

  • Change: new "Patch semantics" section in groups.md. The RFC 6902 target is {policy, config} as GET returns them, and operations apply in order and atomically. remove makes a member absent, so the engine's inheritance and defaults apply (in dae this includes the global group options). null is allowed only where the schema allows it. copy and move are validated like add. A failed test returns 409; replace, remove or test on an absent member, or copy/move from one, returns 400. The result is normalized and revalidated before commit.
  • honk: matches (groups.rs:177-330: values[path].take(), 400 invalid() on an absent member, 409 on a failed test, and the post-loop tolerance check).
  • dae: apply the operations to the projected map, then write the non-null members into the group section and omit the absent ones, so dae's global defaults apply.

F21 API name (Q11)

  • Change: name/api.name is daeuniverse/native. It names the API, not the engine; engine identity is only engine.name. The masked-secret description now names the listener secret generically.
  • honk: HC (types.rs:364,377,413).
  • dae: a constant.

F23 Stale evidence page

  • Change: docs/honk-mapping.md is split. The current honk sections become docs/honk-notes.md, a non-normative page linked from the sidebar and the existing notes links. The pinned-revision evidence moves out of the published docs to design/honk-evidence-780c3f1.md. rule_source and the outbound counters are defined inline, and every normative citation of the evidence page is removed (flows.yaml, flows.md, runtime-status.md, runtime.yaml, configuration.md, index.md).
  • honk: none. dae: none.

F27 max_rules below the loaded count

  • Change: rules.max_rules and dns_rules.max_rules are never below the running list's size. An engine with a fixed bound either refuses the configuration at validation or advertises the larger size. The unreachable 503 temporarily_unavailable path and its examples are removed; 503 snapshot_unavailable stays.
  • honk: HC. routing.rs:27,50,378 and dns/rules.rs:22,26,88 advertise a fixed 4096 and return 503; they should report max(4096, count).
  • dae: advertise the parsed rule count, or a larger bound.

F29 DNS query method (Q14)

  • Change: POST /api/v1/dns/query with a JSON DnsQueryRequest body (domain, type[], upstream, cache_mode). detail stays a query parameter, a non-JSON body returns 415, and the rationale is RFC 9110 (the query sends traffic and writes the cache). The tests that used this operation for query-parameter tooling now use DELETE /dns/cache, and a new assertion checks that the queried name stays out of the request line.
  • honk: HC (handlers.rs:542 route, dns.rs handler).
  • dae: an HTTP handler that decodes the body; no DNS changes.

F32 Group revision scope

  • Change: config_revision is configuration-wide (the same as GET /config revision), so an unrelated accepted change causes 412. groups.config_patch requires config.writable, and without it PATCH returns 404.
  • honk: matches (catalog.rs:669, groups.rs:342, where the capability config_patch equals config.editable()).
  • dae: use the config revision; no per-group revision needed.

F40 One expression rule

  • Change: a "Rule expressions" section in rules.md, linked from DNS rules. expression is the source text as written when the engine has it, and otherwise the engine's rendering, with listener secrets masked.
  • honk: matches. Both lists use routing::located (routing.rs:141-153,255-262, dns/rules.rs:143-169).
  • dae: the rule's source text from the parser, or its String() rendering.

F41 Rule join key

  • Change: FlowSummary.rule_generation_id (required, nullable). Join to GET /rules only when it equals generation_id. A new test ties it to the route step that carries the same rule_id.
  • honk: HC (flows.rs summary; the value is the generation of the traffic-route step).
  • dae: record the generation with the routing decision.

F46 rule_source: userspace

  • Change: the enum gains userspace, the rule a userspace router decided with; each value is defined inline.
  • honk: matches (it never emits userspace).
  • dae: userspace for decisions made in control/dial.go:115-143.

Review fixes

  • Group interrupt: InterruptPatch.value accepts null, which clears the group's own value like the other nullable options. For a supported option, null means the group sets no value; interrupt_connections: null can also mean the engine has no such option. That engine leaves it out of mutable_config, so changing it returns 422 unsupported_value under the existing rule for fields the capabilities do not advertise. dae reports null and rejects a change.
  • Group config is closed like capability resources: known keys plus ^x-[a-z][a-z0-9]*$ objects. x-<engine> members, such as config["x-dae"].check_addresses, are reported by GET and are not patch targets. Tests cover an unnamespaced key and a non-object member.
  • fixed is defined on its own: the group always uses its configured member, even when that member fails its check. dae's fixed(index) maps to it (component/outbound/dialer_group.go:382-390 ignores exclusion).
  • check_interval drops the stale "see its mapping notes" clause.
  • links["x-<engine>"] is in the admitted discovery view only; the public view's links stay closed.
  • runtime.updated also invalidates capabilities, because a recorder mode change moves resources.flows.recording without a new generation.
  • POST /dns/query keeps its 404: it is the capability_not_supported case when resources.dns_query is unavailable, now documented.
  • An engine-only route MUST be under /api/v1/x-<engine>/.
  • Not changed: the links pattern stays unanchored at the end, because it matches a route prefix.
  • Wording from the copy review in dns-query.md, events.md, groups.md, rules.md, dns-rules.md, flows.yaml, runtime.yaml and the honk notes.
  • Drift ledger: new HC rows for honk reporting null for group options it does not set (it materializes tolerance 50 and interrupt_connections false) and for the traffic fallback expression (honk returns the literal fallback; DNS fallback already renders fallback: <outbound>). F40 is otherwise unchanged. The F9 migration also moves the nested capability config.store.

Name the API daeuniverse/native and keep engine identity in engine.name.
Engine-only routes, capability keys, links and fields live under
x-<engine>; the capability resources and discovery links accept no other
unlisted member. Each event kind lists what it invalidates, and every
activation is followed by runtime.updated. The stale honk evidence page
leaves the published docs; rule_source (now with userspace) and the
outbound counters are defined inline, and the current honk notes get
their own page.
The patch target is the group's policy and config as GET returns them;
operations apply in order, remove drops the group's value so the
engine's inheritance applies, null is accepted only where the schema
allows it, copy and move validate like add, and the result is
normalized and revalidated before commit. Keep one check_url and carry
dae's check addresses in x-dae. Add dae's fixed policy and a nullable
interrupt_connections. config_revision is configuration-wide and
config_patch requires writable configuration.
DNS query sends traffic and writes the cache, so it becomes POST with a
JSON body (RFC 9110). max_rules is never below the running list's size,
so rules and DNS rules no longer answer a 503 that cannot succeed. Rule
and DNS rule expressions share one display rule. FlowSummary gains
rule_generation_id for the join with GET /rules.
interrupt_connections accepts null in a patch like the other nullable
group options; an engine without the option leaves it out of
mutable_config, so changing it returns 422 unsupported_value. Group
config is closed except for x-<engine> members, which GET reports and
PATCH does not address. fixed always uses its configured member, even
when that member fails its check. Discovery extension links appear in
the admitted view only, engine-only routes MUST sit under
/api/v1/x-<engine>/, runtime.updated also invalidates capabilities, and
DNS query documents its 404 for an unavailable capability.
Say what the JSON body keeps out of access logs and what it does not,
how clients use the Invalidates column, when expression is source text,
how flow rule generations join to GET /rules, and what the outbound
counters count. The honk notes drop "current" and the repeated
not-part-of-the-contract line.
@Zakkaus
Zakkaus merged commit 9ef1567 into daeuniverse:honk Sep 28, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant