feat(discovery): add a small immutable Registry Discovery index - #748
feat(discovery): add a small immutable Registry Discovery index#748jeremi wants to merge 9 commits into
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3ee22934df
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1699de0c09
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e852af8644
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
1 similar comment
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2cd82520ef
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d60a4e14e3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
d60a4e1 to
6338f25
Compare
|
@codex review |
|
CI update: every Federation, Rust, native binding, docs, security, contract, tutorial, fuzz, and source-proof check passed on 6338f25. The only failure is |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6338f253e3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
655167c to
99edb59
Compare
|
Rebased onto current main, which now contains the identifier-catalog pin and runtime identity-label contract. The historical v0.21.0 manifest remains unchanged by this branch and |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 99edb598f9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7fd370fc6b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 44e6616b6b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| .map(|mapping| (mapping.mapping_id.as_str(), mapping)) | ||
| .collect::<BTreeMap<_, _>>(); |
There was a problem hiding this comment.
Preserve every authored mapping revision in diffs
When a project retains a superseded mapping beside the active revision for the same mappingId, this map silently overwrites one revision according to filename-derived ordering. If the unchanged superseded file sorts after the active file in both projects, changing the active mapping's alternatives, authority, jurisdiction, or constraints produces no mapping-semantic-change, hiding a governed change from federationctl diff; compare all authored revisions rather than collapsing them by immutable identity.
Useful? React with 👍 / 👎.
| .map(|vocabulary| (vocabulary.id.as_str(), vocabulary)) | ||
| .collect::<BTreeMap<_, _>>(); |
There was a problem hiding this comment.
Preserve every routing-vocabulary revision in diffs
Fresh evidence in the current implementation is that routing vocabularies are now compared, but collecting them by id still overwrites coexisting active and superseded revisions. Because the compiler permits inactive history to reuse a vocabulary identity, an unchanged superseded file that sorts after the active file can mask changes to the active vocabulary's values, lifecycle, title, or purpose entirely; compare the complete revision sets instead of retaining one record per ID.
Useful? React with 👍 / 👎.
| fn review_digest(path: &std::path::Path) -> Result<String, CliError> { | ||
| const MAX_AUTHORED_BYTES: u64 = 2 * 1024 * 1024; | ||
| let metadata = fs::symlink_metadata(path) | ||
| .map_err(|_| CliError::Tooling(registry_federation::tooling::ToolingError::Read))?; | ||
| if !metadata.file_type().is_file() || metadata.len() > MAX_AUTHORED_BYTES { |
There was a problem hiding this comment.
Accept complete registration files in review-digest
When an imported Evidence registration is larger than 2 MiB but within the compiler's 16 MiB bound—for example, one carrying the schema-permitted provider classifications—federationctl check accepts it, but the mandated review-digest step rejects it before parsing. Operators therefore cannot calculate the revision and approval digest needed to package a registration the authoring workflow accepts; use the compiler's authored-file bound here as well.
Useful? React with 👍 / 👎.
| || publication.registry_identifier.is_some() | ||
| || publication.authority_id.is_some() => |
There was a problem hiding this comment.
Reject Relay-only fields in Evidence publications
When an Evidence publication also supplies authoritativeScope, publicSemanticClasses, or publicOperationFamilies, this branch rejects the other Relay-only fields but allows these three through. import-service consequently reports success and writes a mixed-kind registration that the compiler rejects on the next check; enforce the same complete service-kind closure promised by the publication schema before creating project files.
Useful? React with 👍 / 👎.
| match publication.service_kind { | ||
| ServiceKind::Evidence | ||
| if publication.issued_by.is_none() |
There was a problem hiding this comment.
Validate provider-classification bounds during import
For an Evidence publication, this validation never checks providerClassifications, so inputs with more than 32 classification keys, more than 100 values under a key, or malformed classification identifiers are accepted and written to the project. The compiler then rejects the newly imported registration, making import-service claim success for an artifact that violates the publication schema; apply the publication's structural classification bounds before writing either output file.
Useful? React with 👍 / 👎.
| } else { | ||
| (DiffProminence::LabelOnly, "label-only-change") |
There was a problem hiding this comment.
Classify approval and provenance changes as review-required
When a valid service revision changes only review or provenance fields such as approval, registrationRevision, sourceRevision, sourceDigest, sourceOrganization, submissionReference, or importerId, neither comparison branch examines them and the change falls through as label-only-change. In particular, an approval renewal or replacement is presented as cosmetic even though it changes the governance material consumed by production packaging; classify these fields as review-required rather than label-only.
Useful? React with 👍 / 👎.
| pub fn from_json(bytes: &[u8]) -> Result<Self, FederationClientError> { | ||
| if bytes.len() > 1024 * 1024 { | ||
| return Err(FederationClientError::Configuration); |
There was a problem hiding this comment.
Size trust documents for the accepted entry count
When a trust store contains two individually valid near-maximum entries, their combined JSON can exceed 1 MiB because each entry permits 100 jurisdictions and 100 required conformance URIs of up to 4,096 bytes. from_entries accepts as many as 1,000 such entries, but from_json—which is also the construction path used by the Python and Node bindings—rejects this much smaller valid store before parsing; derive the aggregate bound from the accepted entry shape/count or impose the same bound on every constructor.
Useful? React with 👍 / 👎.
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
44e6616 to
d0214b2
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d0214b2edd
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| let mut rendered = canonicalize_json(&value)?; | ||
| rendered.push(b'\n'); | ||
| Ok(rendered) |
There was a problem hiding this comment.
Enforce the description bound while rendering
When a constructed description uses otherwise valid collection and string limits, render_description can return more than MAX_DESCRIPTION_BYTES because only parse_description enforces the 1 MiB aggregate bound. For example, one service may contain 128 jurisdictions, conformance identifiers, and evidence types of up to 4,096 characters each, yielding packaged provider bytes that the same profile parser—and therefore discoveryctl build—immediately rejects; enforce the byte limit before returning rendered output or constrain construction so every rendered description can be parsed.
Useful? React with 👍 / 👎.
| if query.len() > MAXIMUM_QUERY_BYTES { | ||
| return Err(DiscoveryClientError::Query); |
There was a problem hiding this comment.
Size queries for the published filter shape
Fresh evidence in the new Discovery surface is that the OpenAPI permits 100 values of up to 4,096 characters for each non-kind filter, but this 64 KiB aggregate check cannot serialize even one near-maximum 100-value filter (over 400 KiB), let alone combined filters. The maintained client therefore rejects a contracted request locally, and the server applies the same bound; increase the aggregate limit to cover the accepted shape or narrow the generated parameter contract.
AGENTS.md reference: AGENTS.md:L286-L290
Useful? React with 👍 / 👎.
| let loopback = matches!( | ||
| parsed.host_str(), | ||
| Some("localhost") | Some("127.0.0.1") | Some("::1") | ||
| ); |
There was a problem hiding this comment.
Recognize bracketed IPv6 loopback hosts
When a provider publishes the schema-valid endpoint http://[::1]:8080, url::Url::host_str() returns the serialized host [::1], so this comparison against ::1 rejects it. This prevents Evidence or Relay from generating a valid local IPv6 publication despite the committed profile schema explicitly allowing [::1]; the same comparison in registry-discoveryctl/src/project.rs also rejects such origins under --allow-loopback, so both checks should use the parsed Host::Ipv6(...).is_loopback() representation.
Useful? React with 👍 / 👎.
| .route(HEALTH_ROUTE, get(health)) | ||
| .route(READY_ROUTE, get(ready)) | ||
| .route(OPENAPI_ROUTE, get(openapi)) | ||
| .route(SERVICES_ROUTE, get(search_services)) |
There was a problem hiding this comment.
Fresh evidence beyond the earlier Federation finding is that the new Discovery router again uses Axum get(...) routes without explicit HEAD rejection. Axum dispatches HEAD to each GET handler and removes the body, so HEAD requests to the four GET paths return success even though the fixed route inventory and generated OpenAPI expose only GET; the method-not-allowed fallback is never reached, so reject HEAD explicitly or add it to and regenerate the public contract.
AGENTS.md reference: AGENTS.md:L286-L290
Useful? React with 👍 / 👎.
| jurisdictions: &[String], | ||
| ) -> Result<Vec<u8>, ArtifactError> { | ||
| let mut bindings = BTreeSet::new(); | ||
| for resource in ®istry.resources { |
There was a problem hiding this comment.
Include public statistical dataflows in Discovery bindings
When a Relay contains a public statistical dataset, this projection inspects only registry.resources, so the dataset never contributes an Aggregate Data statistical-dataflow capability. The maintained labour-statistics acceptance project has resources: [] and a public dataset, meaning this code takes the empty fallback and publishes a Relay binding with no searchable capability even though its acceptance matrix requires the statistical-dataflow capability; derive bindings from public statistical_datasets as well as resource operations.
AGENTS.md reference: AGENTS.md:L60-L62
Useful? React with 👍 / 👎.
| match tokio::time::timeout(limits.request_timeout, exchange).await { | ||
| Ok(response) => response, | ||
| Err(_) => ProblemCode::Unavailable.response(), |
There was a problem hiding this comment.
Enforce request deadlines inside synchronous queries
When an anonymous search exercises a large index and many valid filter values, Directory::search_services and serde_json::to_vec execute synchronously during a single poll of next.run. Tokio's timeout cannot fire until that future yields, so a CPU-heavy query can run well past requestTimeoutSeconds and occupy a runtime worker despite this middleware promising a 503 deadline; make the query/serialization work cooperatively deadline-aware or run it behind a bounded execution mechanism that the timeout can actually preempt.
Useful? React with 👍 / 👎.
| pub origin_id: String, | ||
| pub origin_url: String, | ||
| pub origin_content_digest: String, | ||
| pub catalog_revision: String, |
There was a problem hiding this comment.
Retain the origin fetch time in saved selections
When an application persists a ServiceSelection, it loses originFetchedAt even though the selected ServiceRecord contains it and the Discovery explanation explicitly says selections retain the origin fields so applications can retain provenance. Because catalogRevision deliberately excludes fetch provenance, a later fetch of identical bytes does not repair this omission; add the timestamp to ServiceSelection and copy it in select_exact, or weaken the public provenance claim.
AGENTS.md reference: docs/site/AGENTS.md:L25-L28
Useful? React with 👍 / 👎.
| pub fn valid_public_url(value: &str) -> bool { | ||
| if value.chars().count() > MAXIMUM_IDENTIFIER_CHARACTERS { | ||
| return false; | ||
| } | ||
| let Ok(url) = Url::parse(value) else { | ||
| return false; |
There was a problem hiding this comment.
Reject unnormalized public URLs before parsing
Fresh evidence in the new Discovery validators is that Url::parse follows WHATWG behavior and trims surrounding C0 controls/spaces and removes embedded ASCII tabs or newlines, while this function retains the original string in the index. Consequently an endpoint or origin URL such as "https://provider.example/catalog\n" can pass index validation and client protocol checks even though the normalized parsed URL differs from the published value; discoveryctl repeats the issue in valid_catalog_url, so reject whitespace and control characters before parsing in both paths.
Useful? React with 👍 / 👎.
| validate_string(&self.title, 1, 4096, "publication title")?; | ||
| validate_string(&self.description, 1, 4096, "publication description")?; |
There was a problem hiding this comment.
Validate publication text with the shared profile rules
When publication.title or publication.description has surrounding whitespace or a non-NUL control character, these calls accept it because validate_string checks only byte length and NUL, but registry_discovery_profile::ServiceDescription later rejects it while rendering the mandatory catalog. Conversely, schema-valid multibyte text can exceed the byte count while remaining below the shared 4,096-character limit and is rejected here; apply the profile's scalar-count, trim, and control-character rules during configuration validation so accepted Evidence publication configuration can always be projected.
AGENTS.md reference: AGENTS.md:L144-L145
Useful? React with 👍 / 👎.
| if status.is_success() { | ||
| if content_type.as_deref() != Some(JSON) { | ||
| return Err(DiscoveryClientError::Protocol); |
There was a problem hiding this comment.
Compare response media types case-insensitively
When a conforming Discovery implementation returns Application/JSON or another case variation of the application/json or application/problem+json media type, this exact string comparison rejects the response as a protocol failure. HTTP media type type/subtype tokens are case-insensitive, and the Discovery server already applies eq_ignore_ascii_case to request content types; normalize or compare response media types the same way so the maintained client interoperates with compliant servers.
Useful? React with 👍 / 👎.
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2a2a77ab71
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if publication.jurisdictions.is_empty() | ||
| || publication | ||
| .jurisdictions | ||
| .iter() | ||
| .any(|value| !valid_global_identifier(value)) | ||
| || publication | ||
| .jurisdictions | ||
| .windows(2) | ||
| .any(|pair| pair[0] >= pair[1]) |
There was a problem hiding this comment.
Enforce the publication jurisdiction limit
When publication.jurisdictions contains 129 sorted, distinct valid URIs, this condition accepts it and relayctl check reports success—the generated authoring schema is also unbounded—but artifact generation passes the collection to ServiceDescription::new, whose shared profile caps identifier collections at 128, so generation and packaging subsequently fail. Enforce the shared bound in the compiler and regenerate the strict authoring schema.
AGENTS.md reference: AGENTS.md:L64-L67
Useful? React with 👍 / 👎.
| .route(EVIDENCE_ROUTE, post(create_evidence)) | ||
| .route(EVIDENCE_BATCH_ROUTE, post(create_evidence_batch)) | ||
| .route(DEFINITIONS_ROUTE, get(discover_evidence)) | ||
| .route(DISCOVERY_ROUTE, get(discovery_description)) |
There was a problem hiding this comment.
Reject HEAD on the new catalog route
When a caller sends HEAD /catalog.jsonld, Axum's get(...) router dispatches it to discovery_description and strips the body, returning success whenever publication is configured even though the generated Evidence OpenAPI and documented route inventory declare only GET; the method-not-allowed fallback is therefore bypassed. Add an explicit HEAD rejection as the Discovery router does, or add HEAD to the generated contract deliberately.
AGENTS.md reference: AGENTS.md:L286-L290
Useful? React with 👍 / 👎.
| "crates/registry-evidencectl/src/authoring.rs", | ||
| "crates/registry-evidencectl/src/build.rs", | ||
| "crates/registry-evidencectl/tests/production_build.rs", |
There was a problem hiding this comment.
Route fixture publication changes through Discovery CI
When a PR changes only crates/registry-evidencectl/src/fixtures.rs, which now supplies the Evidence binary to the editable-project compiler that emits catalog.jsonld, classify selects the Evidence and developer-tools jobs but leaves discovery_contracts false because this path is absent from the explicit provider inputs. A regression in this publication path can therefore skip the cross-product contract and adopter-journey gates that the tuple is intended to enforce; add this implementation path and a focused classifier case.
Useful? React with 👍 / 👎.
| source_of_truth: | ||
| label: Relay V2 contract and runtime types | ||
| url: https://github.com/registrystack/registry-stack/blob/2573f77d0fb59cbdae10c3b554c1b2328eeb32be/crates/registry-relay-v2/src/contract.rs | ||
| url: https://github.com/registrystack/registry-stack/blob/e126c55a98507188e6a5eece818ebf67b3e5b9ed/crates/registry-relay-v2/src/contract.rs |
There was a problem hiding this comment.
Cite only a main-reachable contract source
This public source-of-truth link pins e126c55…, which is reachable only from the review branch and not from pushed main; when the session commits are squashed or otherwise rewritten, the durable docs retain a review-only SHA that may become unavailable or stale. Point the source record at a main-reachable revision or a stable path instead.
AGENTS.md reference: AGENTS.md:L277-L279
Useful? React with 👍 / 👎.
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a1707444a9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const native = require('./index'); | ||
|
|
||
| const MAX_JSON_DEPTH = 128; | ||
| const MAX_JSON_NODES = 100_000; |
There was a problem hiding this comment.
Preserve large search responses through Node selection
When a valid search response contains more than 100,000 JSON nodes—for example, a few hundred Relay records with schema-valid metadata collections—searchServices can return it within the supported 16 MiB response limit, but selectExact immediately passes that same response through requestValue/cloneJson and raises a query error at this cap. This prevents the maintained Node client from completing its search-to-exact-selection flow for a contracted response; size the bridge for the accepted wire shape or avoid reconverting SDK-produced responses.
AGENTS.md reference: AGENTS.md:L77-L79
Useful? React with 👍 / 👎.
| const MAXIMUM_TRUSTED_ROOT_CERTIFICATE_BYTES: usize = 4 * 1024 * 1024; | ||
| const MAXIMUM_JSON_DEPTH: usize = 128; | ||
| const MAXIMUM_JSON_NODES: usize = 100_000; | ||
| const MAXIMUM_JSON_STRING_BYTES: usize = 4 * 1024 * 1024; |
There was a problem hiding this comment.
Preserve large search responses through Python selection
When maximum_response_bytes permits a valid result containing more than 4 MiB of string data—such as records aggregated from several near-maximum provider descriptions—search_services returns it because rust_to_python allows 16 MiB, but select_exact sends the same dictionary through python_to_json and rejects it at this smaller string budget. Align the inbound selection conversion with the supported response bound so every returned search response remains selectable.
AGENTS.md reference: AGENTS.md:L77-L79
Useful? React with 👍 / 👎.
| fail-fast: false | ||
| matrix: | ||
| client: [evidence, relay] | ||
| client: [discovery, evidence, relay] |
There was a problem hiding this comment.
Gate Discovery publication jobs by candidate version
When this workflow is dispatched for a v0.21.1 candidate, verify sets client_registries to true because the registry-package threshold is v0.21.1, but the candidate inventory deliberately contains Discovery packages only from v0.22.0. This unconditional matrix therefore schedules a Discovery job whose validate-dist step fails on missing tarballs; the unconditional Discovery entry in the PyPI matrix fails similarly on missing wheels. Since the workflow accepts historical tags from v0.19 onward, include Discovery only when the candidate version reaches its v0.22.0 minimum.
Useful? React with 👍 / 👎.
Summary
Replace the managed Federation V1 direction with Registry Discovery: Evidence
and Relay publish deterministic public descriptions, a catalog operator runs a
one-shot build from approved URLs, and a read-only service supports exact
search and evidence-type resolution from one immutable index.
Discovery is a curated index. The adopter remains responsible for native
Evidence or Relay trust and invokes the selected provider directly.
Scope
registry-discovery-v1alpha1JSON-LD provider-publication profile.parity corpus.
resolution only.
exact selection, with no native invocation or trust ownership.
smoke tests and exact npm/PyPI promotion from candidate bytes.
limits enforced consistently across the server, client, schemas, and
generated OpenAPI.
adopter tutorial exercised in CI.
Non-goals
ranking, keyword search, pagination, aggregate catalog, or native proxy.
provider routing, registration workflow, or Evidence procedure.
Refactor record
The file-level current-PR decision record is
products/discovery/SALVAGE-LEDGER.md. Deferred butuseful work is recorded in
products/discovery/FUTURE-WORK.md, not left as dormantproduction code.
Architecture decisions
The approved boundary and standards decisions are recorded in
products/discovery/DECISIONS.md. Discovery remains a curatedindex, provider descriptions remain owned by Evidence and Relay, exact
capability bindings remain distinct in RDF and the runtime index, and index
changes remain an explicit build-and-restart operation.
Security notes
parser. No remote context, RDF graph, schema, SHACL, link, or vocabulary is
resolved at runtime.
capability drift, prove private configuration cannot enter the publication,
and preserve exact capability correlation through distinct binding IDs.
authentication, source access, signing, or audit work. Relay tests prove
even a malformed bearer cannot turn that public route into authentication.
The Relay exception is scoped to
discovery-description; a regression testpreserves supplied-bearer authentication for every other public artifact.
resource-bound, provenance, and atomic-write controls.
traffic before the adopter's existing local acceptance.
Security traceability:
products/discovery/contracts/security-invariant-matrix.yaml.Remaining risks
v1alpha1and pre-1.0. Adopters must pin it and coordinateprofile or wire-contract upgrades.
provider, claim, or authorization decision. Consumers must keep their native
Evidence or Relay trust configuration authoritative.
rebuild and restart. There is intentionally no scheduler or hot reload.
Verification