Discovery truth and cache identity
Issue #391 introduces a
universal connect workflow in protocol-sized slices. The first production
slice is the protocol-neutral truth contract used by endpoint detectors and
metadata adapters. It does not claim that every protocol can already be
passed to one connect() implementation.
Capability truth
PROTOCOL_DEFAULT_CAPABILITIES describes the maximum operations implemented
by an SDK adapter. It is not proof that a particular server or asset enables
those operations. Discovery code should pass endpoint evidence to
resolveDiscoveryCapabilities() and use the returned intersection:
import {
inspectDiscoveredSource,
resolveDiscoveryCapabilities,
} from "@honua/sdk-js/contract";
const resolution = resolveDiscoveryCapabilities(
"ogc-features",
{
kind: "metadata",
capabilities: ["query", "queryObjectIds"],
provenance: [{ source: "GET /conformance" }],
},
{ deny: ["queryObjectIds"] },
);
const inspection = inspectDiscoveredSource(sourceDescriptor, resolution);
// inspection.descriptor.capabilities contains only "query".
// inspection.capabilityDecisions explains every enabled or excluded operation.
Evidence is explicit and may be supplied as multiple capability-scoped records:
metadatameans a server or asset document advertised support.declaredmeans trusted caller/source configuration guarantees support.inferredis recorded but enables nothing unless the caller setsacceptInferred: true.unavailableenables nothing and emits a structured diagnostic. Protocol defaults are never silently substituted.
This lets a connection retain declared known-safe operations, metadata-backed operations, and a failed optional metadata endpoint at the same time. Every capability decision preserves the records and provenance that supported or excluded it; conflicting metadata is resolved conservatively.
The resolver also rejects unknown capability identifiers, reports evidence
that exceeds the adapter implementation, and applies allow/deny policy after
the adapter/evidence intersection. A failed optional metadata endpoint does
not erase explicitly declared known-safe operations; callers must preserve
that provenance instead of relabeling defaults as observed metadata.
Cache identity
createDiscoveryCacheIdentity() builds the common logical key for later
connection caches. It requires an opaque authorization-scope fingerprint so
authenticated metadata cannot accidentally share an anonymous cache entry.
The identity includes opaque SHA-256 endpoint/auth-scope digests, protocol,
source resource dimensions (including WFS/WMS typeName), and
adapter/projection versions. The returned
display endpoint is credential-redacted; the logical key never embeds raw URL
or authorization-scope values.
import { createDiscoveryCacheIdentity } from "@honua/sdk-js/contract";
const identity = await createDiscoveryCacheIdentity({
endpoint: collectionUrl,
protocol: "ogc-features",
authorizationScopeFingerprint: aclFingerprint,
collectionId: "parcels",
adapterVersion: "ogc-features@1",
projectionVersion: "source-inspection@1",
});
URL user information, fragments, OAuth/session/API-key parameters, and cloud
signed-URL credentials are removed. Stable query parameters are sorted and
remain identity-bearing inside the opaque endpoint digest. Ambiguous credential
aliases such as key, api-key, subscription-key, auth, code, and
session are redacted from the display endpoint while their values remain in
the hash input, so key=roads cannot collide with key=buildings. Adapters
classify additional vendor cache busters with transientQueryParameters. The
caller fingerprint is hashed again internally, but it should still be a stable
identity derived from the caller, grants, audience, and ACL version rather than
a token.
Omitting adapterVersion and projectionVersion uses the exported
HONUA_DISCOVERY_ADAPTER_VERSION and
HONUA_DISCOVERY_PROJECTION_VERSION constants. Both dimensions are always in
the cache key, so adapter or normalized-schema upgrades cannot reuse older
entries accidentally.
Public surface
The root and /honua barrels expose the curated workflow: the four primary
evidence/policy/resolution/cache-option types, error, and helper functions.
@honua/sdk-js/contract additionally exports the version constants and the
complete decision, diagnostic, provenance, cache-result, and inspection type
vocabulary for adapter authors. This keeps the beginner surface bounded while
preserving a fully typed protocol integration seam.
Connect facade (OGC Features/Records/Tiles/Maps, STAC API/static, GeoServices, WFS/WMS/WMTS, OData, PMTiles, and GeoParquet slices)
The experimental connect() facade composes this truth contract for raw OGC
API Features and STAC API landing pages or bounded static STAC objects, raw OGC API Records catalog roots, raw
OGC API Tiles and Maps service roots (render-only sources),
WFS 2.0, WMS 1.3.0, and WMTS 1.0.0 endpoints, OData v4 service
roots, direct PMTiles and static-file GeoParquet assets, and canonical GeoServices
FeatureServer / MapServer service or layer URLs plus executable
ImageServer raster catalogs. OGC, STAC, WFS, WMS, WMTS, OData, Records,
Tiles, Maps, GeoParquet, and ordinary HTTPS PMTiles endpoints require an explicit
protocol: "ogc-features",
protocol: "stac", protocol: "wfs", protocol: "wms", protocol: "wmts", protocol: "odata",
protocol: "ogc-records", protocol: "ogc-tiles", protocol: "ogc-maps", or
protocol: "geoparquet" or protocol: "pmtiles" hint. A
pmtiles://https://... asset marker is the sole direct static-asset shape that
auto accepts: the marker is structural evidence and is stripped before
cache/network identity is computed. A .pmtiles filename alone remains
ambiguous and triggers no request. OGC API Processes is deliberately not a
Source-backed protocol; connect() rejects protocol: "ogc-processes" and
directs callers to discoverOgcProcesses(), which returns a
capability/metadata result rather than a Source (see below).
Direct PMTiles discovery admits only exact HTTP 206 byte ranges. The default
ceiling is two requests, 512 KiB per range, 1 MiB transferred, and 4 MiB after
internal metadata/directory decompression; caller overrides may only lower
those values and participate in cache identity. The client timeout remains
active until each admitted range body has been read exactly. Response
interceptors receive only the resulting bounded in-memory response, never an
unvalidated network-stream clone. Multi-range inspection requires a strong
ETag or canonical Last-Modified validator and cannot collectively materialize
the whole archive. Weak ETags are never treated as byte-identity evidence. The
reviewed snapshot retains a bounded canonical encoding of the complete raw
metadata document (up to 1,000,000 UTF-8 bytes, 2,048 raw vector_layers
entries, and an 8,000-node normalized vector-layer structure), so
Source.protocol("pmtiles").describe() preserves custom TileJSON fields
without reopening the archive on live or cache-replay paths. Replay cross-binds
both retained extents, the exact capability/provenance record, and every
derived source projection to that reviewed archive metadata.
Canonical source-backed GeoServices URLs may use
protocol: "auto": classification comes entirely from the URL path and makes
no network request. An ambiguous auto target throws HonuaDiscoveryError with
code ambiguous-protocol before cache hooks, authentication, or network
requests run. Passing a protocol without a reviewed connect adapter throws
unsupported-protocol. Discovery never probes a Honua facade or a second
authenticated protocol endpoint as fallback.
connect() is product-level discovery, not an operation on an individual
Source, so it is intentionally absent from Capability, CAPABILITIES, and
PROTOCOL_DEFAULT_CAPABILITIES. The reviewed connector inventory is exported
internally as CONNECT_SOURCE_PROTOCOLS and projected as connectProtocols in
the support manifest. The generated
connect-discovery-inventory.v1.json
assigns every declared protocol and supported static format to exactly one
source-backed, operation-only, STAC-classified, renderer-native, or explicitly
unsupported boundary. CI checks both projections, requires every connector to
have exactly one positive discovery support claim, and rejects source-backed
entries outside the connector inventory.
import { connect } from "@honua/sdk-js/honua";
const connection = await connect({
endpoint: "https://demo.pygeoapi.io/master",
protocol: "ogc-features",
authorizationScopeFingerprint: "anonymous",
collectionId: "lakes",
});
const inspection = connection.inspection.sources[0];
// Capabilities are the intersection of the SDK adapter and advertised
// conformance evidence, never PROTOCOL_DEFAULT_CAPABILITIES by assumption.
const lakes = connection.source();
GeoServices service discovery reads the service document and then resolves layer/table metadata with a maximum concurrency of four. A layer URL performs only the single selected-layer metadata request:
import { connect } from "@honua/sdk-js/honua";
const parcels = await connect({
endpoint:
"https://sampleserver.example/arcgis/rest/services/Cadastre/Parcels/FeatureServer/0",
protocol: "auto",
authorizationScopeFingerprint: "anonymous",
});
const layer = parcels.source();
// locator.serviceId === "Cadastre/Parcels"; locator.layerId === 0
The client base URL is derived from the portion before /rest/services; the
full service/layer URL remains the redacted discovery identity. A pasted
?f=json, ?f=pjson, or equivalent format query is removed before
classification; all identity-bearing query parameters remain rejected. An
injected HonuaClient must be configured for that derived service root. URL
user info, other query parameters, and fragments remain rejected; credentials
belong in clientOptions.
GeoServices capabilities come from the advertised capabilities string and
reviewed metadata flags (supportsStatistics, attachments, relationships,
standardized queries, and PBF formats). MapServer tile-cache status is retained
from service metadata; a selected layer URL reports it as unavailable rather
than performing a second request. Missing capability
metadata produces discovery-unavailable and enables nothing by assumption.
Discovery only advertises canonical query and stream when layer metadata
explicitly reports supportsPagination: true: canonical query also promises
a safe queryAll(), and repeatedly sending an unsupported or unverified offset
can duplicate the first page indefinitely. Independently safe operations such
as queryObjectIds and queryExtent remain available when advertised.
At a service root, if one optional layer metadata request fails, service-level
evidence retains known-safe operations and the affected source reports
partial-discovery; no adapter default is silently substituted. Layer fields
and the object-id primary key are projected into the common source schema.
Service-shaped GeoServices discovery
discoverGeoServices() from @honua/sdk-js/honua accepts canonical
FeatureServer, MapServer, ImageServer, GeometryServer, and GPServer
URLs. It shares connect()'s strict classifier, including nested service
folders, optional numeric FeatureServer/MapServer layers, selected GP task
names, and removable JSON-format query parameters. Credentials, fragments,
identity-bearing queries, unsafe relative paths, and non-canonical selected
resources fail before authentication or metadata work.
import { discoverGeoServices } from "@honua/sdk-js/honua";
const discovery = await discoverGeoServices({
endpoint:
"https://sampleserver.example/arcgis/rest/services/Analysis/Visibility/GPServer",
});
for (const task of discovery.operations) {
console.log(task.id, task.execution, task.availability, task.href);
}
Every result retains a common service identity, credential-free authentication
evidence, advertised formats, CRS values, numeric limits, provenance, and
structured diagnostics. GeometryServer and GPServer are operation-shaped, so
they always return an empty sources array. ImageServer becomes a Source only
when metadata explicitly advertises catalog/query and pagination support—the
existing raster-catalog adapter can then uphold both query() and the
canonical bounded queryAll() contract. Export-only, secured, or otherwise
unproven imagery remains a service/operation result. Discovery never converts
image/export evidence into feature edits, attachments, relationships, or
streaming capabilities.
Geometry operations come only from advertised operation metadata. GP discovery
reads the advertised task list with concurrency four and classifies task
metadata as synchronous execute, asynchronous submitJob plus inert job URL
templates, or unknown. It never invokes an operation, requests an image,
submits or polls a job, reads results, or starts a stream. Relative operation
links must remain inside the credential-free service root.
HTTP 401/403 and ArcGIS 498/499 error codes preserve the URL-proven service
kind while publishing no inferred operations or capabilities. A failed GP task
metadata request yields an unavailable task beside successful siblings. The
decoded response stream is capped at 1,048,576 bytes before JSON parsing even
when Content-Length is absent or false. Parsed metadata is additionally
bounded to 32 levels, 50,000 values, 10,000 array entries or object keys, 65,536
code units per string, and one million string code units overall. Redirects are
not followed for discovery metadata; the successful response authority and
service path must exactly match the requested endpoint. The nested metadata
option therefore exposes only the honored HTTP cache directive (default or
bypass); TTL and stale fallback are not accepted because this raw service
facade does not own an SDK-local metadata cache. Malformed or oversized
metadata fails closed with a typed discovery error.
At an OGC service root, all advertised collections become Dataset source
descriptors; at a GeoServices root, advertised layers and tables do. A layer
or collection URL/selection returns a single source. connection.source() is
only implicit when exactly one source was selected; otherwise it throws
ambiguous-source and lists the valid IDs. connection.source(id) and
connection.dataset retain the existing reviewed Dataset / Source
execution contract.
WFS discovery performs exactly one GetCapabilities request with
service=WFS, version=2.0.0, and request=GetCapabilities. It returns every
advertised feature type or the exact typeName selection. The common
query/queryAll and stream contract is enabled only when the document is
WFS 2.0, advertises a recognized JSON/GeoJSON representation, and exposes both
GET and POST bindings with validated non-empty DCP URLs for GetFeature; the runtime needs GET for ordinary
queries and switches long FES filters to POST. applyEdits additionally
requires a POST Transaction binding with a validated URL and a root namespace
declaration resolved from the feature-type prefix. Unprefixed types remain
non-editable when capabilities provide no provable feature namespace.
queryObjectIds remains unavailable because
capabilities cannot prove GeoJSON feature.id; queryExtent remains
unavailable because the common filtered-extent drain is not bounded by this
metadata. Those negative decisions are explicit rather than adapter defaults.
Advertised WFS operation URLs must remain on the endpoint origin and carry no
user information, query, or fragment before the connection is accepted.
Relative DCP links are resolved against the capabilities endpoint and retained
as canonical absolute URLs before the runtime can issue GetFeature or
Transaction requests. The
baseline does not issue DescribeFeatureType, so it has no hidden per-type
fan-out and does not invent a field schema. Default CRS, WGS84 bounds, titles,
and feature namespace bindings are parsed from GetCapabilities; the default
CRS and namespace are retained on the WFS locator, while the bbox is not
promoted to the filtered canonical extent capability.
WMS and WMTS discovery each perform one bounded GetCapabilities request.
Standard SERVICE, REQUEST=GetCapabilities, and VERSION parameters are
removed from service identity; protocol: "auto" recognizes an explicit
WMS/WMTS SERVICE query or canonical /MapServer/WMS|WMTS path without
probing. Only WMS 1.3.0 and WMTS 1.0.0 are accepted. Named layers become
sources, while typeName, styleId, and tileMatrixSetId select exact
advertised identifiers.
Inspection retains formats, styles and safe legend URLs, dimensions/defaults,
CRS and WMS 1.3 axis order, operation methods/URLs, extents, and every linked
WMTS matrix definition. Relative operations and templates resolve against the
service endpoint and are executable only when they stay same-origin HTTP(S)
URLs without credentials, fragments, or credential-shaped query parameters.
Malformed optional entries become structured partial-discovery reasons;
malformed required structure and ambiguous selections fail discovery.
Effective raster support is execution-aware. WMS render/tiles requires a
safe GET GetMap binding, supported image format, and exact EPSG:3857 evidence.
WMTS additionally requires one selected style, a GoogleMapsCompatible Web
Mercator matrix set whose identifiers map exactly to MapLibre zooms, and a
safe GetTile KVP or ResourceURL binding. The selected request is pinned on
locator.raster, so the MapLibre projector executes reviewed metadata rather
than guessing a URL.
WMS query is execution-aware in the same way and no longer depends on a Honua
service id. It requires a queryable layer, a safe GET GetFeatureInfo
binding, an advertised info format the canonical Result can carry (GeoJSON and
JSON preferred, GML as the interoperable fallback; unstructured text/plain and
text/html never qualify), and at least one advertised CRS with a provable WMS
1.3 axis order. The reviewed operation is pinned on locator.featureInfo
({ kind, url, format, crs }), so canonical point Source.query() executes the
advertised operation URL under the same same-origin, credential-free policy as
GetMap and emits BBOX in the authority-defined axis order. Any missing piece
keeps query disabled and the surface throwing. Raw WMTS sources remain
query-disabled on the canonical Source surface.
Capabilities XML is byte-, depth-, element-, attribute-, text-, and deadline-bounded without a new parser dependency. A per-client cache is keyed by canonical endpoint, protocol, and authorization-scope digest; it supports ETag/Last-Modified revalidation and bounded stale-if-error while treating cache bypass, caller cancellation, and tighter response limits as hard boundaries. Unsafe advertised URLs prevent raw XML caching.
OData v4 discovery performs exactly one $metadata (CSDL) request against the
service base path carried by the endpoint (for example /odata); the client is
bound to the endpoint origin. Every entity set declared in the container becomes
a source, keyed by its entity-set name. Capabilities are read only from the CSDL:
query and bounded paged stream are enabled for every declared entity set
(OData exposes $top/$skip server-driven paging), queryObjectIds additionally
requires a declared entity key, and applyEdits is enabled unless the
Capabilities.* insert, update, and delete restriction annotations are all
explicitly false. This mirrors the runtime OData adapter's applyEdits gate, so
a discovered descriptor and the live source agree. Declared properties (including
Edm.Geography/Edm.Geometry typing) are projected into the source schema with
the single-key field promoted to primaryKey; the descriptor carries
locator.entitySet so the existing OData source adapter executes against the same
base path. Missing or malformed $metadata fails as invalid-endpoint rather
than inventing an entity set or an adapter-default capability set.
Raw STAC API discovery performs exactly two metadata requests on a valid
service: the root landing page and /collections. Exact STAC API Core 1.0.0
conformance and a credential-free, same-root rel=data collections link must
be established from the landing document before the second request. An
explicit protocol hint never substitutes for protocol identity or authorizes a
guessed operation URL. Discovery never searches items or fans out to
per-collection metadata. Every advertised collection becomes a source, or
collectionId selects exactly one. The descriptor retains
layout: "stac-api", so the existing source adapter executes against the raw
root's /search path rather than the Honua /stac facade. Collection CRS and
spatial/temporal extents are deeply immutable under metadata on the matching
connection.inspection.sources entry; dynamic item properties are not
invented as a field schema.
query, queryObjectIds, and bounded paged stream are enabled together only
when the landing page advertises both STAC API Core 1.0.0 and Item Search
1.0.0, plus a credential-free rel=search link under the connected service
root. Generic STAC catalog conformance does not prove a search API. Missing
Item Search evidence leaves those operations disabled with structured
not-advertised decisions. Cross-origin, credential-bearing, query-bearing,
or path-divergent search/data links are rejected after the landing request and
before any linked endpoint can be contacted. conformsTo is authoritative for
API classification; auto never probes an ambiguous URL to guess STAC API
versus static STAC.
Static STAC discovery accepts an explicit protocol: "stac" endpoint whose
bounded root is a Catalog, Collection, or Item with stac_version and no
conformsTo member. It follows only same-origin, credential-free JSON
child/item links (plus an Item's collection metadata link), resolving
relative links against each admitted document URL. Metadata and asset-probe
requests use Fetch redirect: "error": HTTP redirects and browser-shaped
opaque redirect responses fail closed rather than replaying root credentials.
Traversal defaults
to 32 attempted documents, depth 4, 64 links per document, 256 assets, eight
same-origin HEAD probes, and one MiB per JSON document; caller overrides are
hard-capped and participate in discovery-cache identity. Duplicate canonical
links are deduplicated before requests. Cross-origin asset hrefs are preserved for inspection
but are never probed with root credentials, and asset bodies are never
downloaded during discovery.
Assets are classified from declared/probed media types, STAC roles, extension
URIs, and extension fields—never from filename suffixes. COG, GeoParquet,
PMTiles, tile, and metadata candidates retain structured confidence/evidence,
object and collection identity, CRS, space/time extent, license, attribution,
and validator/content provenance. PMTiles, GeoParquet, and compatible tile
templates project onto existing executable locators; COG remains typed but
non-executable as a protocol-neutral Source. Evidence-classified COG
candidates can instead enter the bounded, caller-decoder-injected
@honua/sdk-js/cog inspection/read boundary. Ambiguous or
unsupported assets remain explicit candidates rather than disappearing.
Direct COG input stays outside connect() even when a URL ends in .tif or
.tiff: a suffix is guidance, not format evidence. The resulting structured
unsupported-protocol error names the stac-classified disposition and directs
the caller to explicit static-STAC classification followed by
@honua/sdk-js/cog; no COG bytes or competing protocol endpoint are probed.
The normalized traversal policy is stored on SourceLocator.stacStatic and is
reapplied by the runtime static-catalog reader. Consequently, a discovered
source cannot later widen its document/depth/link/body bounds or cross the
admitted origin during Source.query(). collectionId selects one traversed
Collection and scopes runtime item filtering; without a selection, the root
object remains the source identity.
Raw OGC API Records discovery performs three metadata requests against the
discovered service root — the landing page, /conformance, and /collections.
The landing page must advertise a collections data link before the follow-up
requests run. Every advertised catalog collection becomes a source keyed by its
collection id. Effective capabilities are the intersection of the OGC Records
adapter surface and the advertised conformance classes: query and
queryObjectIds are enabled only when the service advertises the Records API /
searchable-catalog conformance, and a service that advertises no Records query
conformance leaves those operations disabled with structured not-advertised
decisions rather than adapter defaults. The discovered service-root prefix is
retained on locator.basePath, so the existing OGC Records source adapter
threads it through every wire request — the reviewed descriptor executes the
same catalog search against a third-party root that it would against the Honua
/ogc/records facade (semantic descriptor parity).
Raw OGC API Tiles and Maps discovery follows the identical shape through the
same basePath threading seam (OgcMetadataRequest.basePath,
SourceLocator.basePath): three metadata requests against the discovered
service root (landing, /conformance, and the OGC API Common /collections
list), a required collections data link on the landing page, and one reviewed
render-only source per advertised collection. Their effective capabilities are
the intersection of the render-only adapter surface (render, tiles for
Tiles; render for Maps) and the advertised conformance classes; a service
that advertises no Tiles/Maps conformance leaves those operations disabled with
structured not-advertised decisions. The discovered root is retained on
locator.basePath, so the render-only Tiles/Maps source adapters resolve every
tile / map-image request against the third-party root through the same seam
rather than the /ogc/tiles or /ogc/maps facade.
OGC API Processes is intentionally not a Source-backed Protocol: a process
is an invocable operation, not a queryable dataset. discoverOgcProcesses() is
its raw-discovery counterpart — it threads the discovered service root through
the Processes wire methods (the same basePath seam), performs three bounded
metadata requests (landing, /conformance, /processes), and returns a
capability/metadata result: the advertised process list plus the effective
processes capability intersected from conformance (with a structured
discovery-unavailable diagnostic when Processes core conformance is absent).
It never constructs a connect() Source or dataset. The discovered root is
returned on result.basePath; passing it to client.ogcProcesses({ basePath })
keeps the process-description, execution, and job (status / results / dismiss)
routes on the advertised root rather than the /ogc/processes facade, and a
job created from that handle stays pinned to the same root. Typed execution
against third-party servers remains unclaimed in the capability matrix until
live evidence exists; this is URL resolution, not a support claim.
GeoParquet / static-file discovery has no HTTP metadata document: it reads the
Parquet footer and the GeoParquet geo metadata through an injected
geoparquet.profiler seam (GeoParquetSourceProfiler, satisfied structurally
by GeoparquetRuntime from @honua/sdk-js/geoparquet). The DuckDB engine must
never enter the connect static graph, so the reader is injected rather than
imported. A successful footer read is positive metadata evidence for the
canonical query, queryAggregate, and bounded stream operations the
GeoParquet adapter implements; the detected geometry column, physical encoding,
and any GeoParquet 1.1 bbox-covering column are pinned on locator.geoparquet
so the runtime resolves without a second profiling round-trip, and the file
CRS is retained under metadata. A purely tabular Parquet file discovers the
same three read capabilities (spatial filters simply throw at query time).
Because the GeoParquet Source is resolver-only (DuckDB stays out of the
static graph), an injected resolveSource (for example geoparquetResolver())
lets the reviewed descriptor execute directly through connection.source().
Authentication, retry, timeout, interceptors, and transport fetch overrides
are passed in clientOptions, or callers may inject an existing HonuaClient
whose normalized base URL matches the OGC/STAC/WFS/WMS/WMTS endpoint origin, the OData origin, or
the derived GeoServices
root. Endpoints must be absolute HTTP(S) URLs without user info,
identity-bearing query parameters, or fragments; authentication belongs in
clientOptions. signal cancels metadata work and
settles connect() even when a caller cache hook ignores its supplied signal.
Cache implementations remain responsible for stopping their own late work.
refresh: true skips the caller cache read and forwards conditional-refresh
semantics to the client's metadata cache.
ConnectDiscoveryCache is an optional caller-owned cache hook. Its logical
identity uses createDiscoveryCacheIdentity(), including the opaque
authorization-scope fingerprint plus connect adapter and projection versions.
Stored values are raw, versioned observations; capability policy is reapplied
after every cache read. Cross-version, cross-endpoint, cross-scope, and
cross-collection, cross-WFS-type, cross-WMS/WMTS-layer, style, and matrix-set snapshots are rejected as
invalid-discovery-cache instead
of being trusted. Cache values cross a persistence trust boundary: the SDK
rejects accessors, proxies that throw, cycles, sparse arrays, malformed
evidence/provenance/extents, and non-plain objects as typed
invalid-discovery-cache failures. Accepted values are copied into deeply
owned immutable data before capability resolution, so later caller/cache
mutation cannot change an inspection. The clone is bounded to 32 levels,
10,000 values, 20,000 properties, 10,000 entries per dense array, one million
UTF-16 code units per string, and four million string code units overall.
Owned records use null prototypes, so data keys such as __proto__ cannot
alter object prototypes. A bound violation fails without a network fallback.
Cache hooks must not persist access tokens, API keys, or raw authorization
material.
Static STAC snapshots additionally bind the requested root URL, canonical
same-origin document URLs, object validators/content digests, and classified
asset keys/hrefs/kinds/executable locators into a tree fingerprint. Cache reads
recompute candidate/object identities, canonicalize the redundant root view
from the first validated document, and require diagnostics to reference an
admitted parent document. A forged asset binding, duplicate document URL, policy
drift, or cross-origin tree entry fails as invalid-discovery-cache.
This slice is intentionally not universal-connect completion: GeometryServer
and GPServer are operation-shaped and therefore fail through connect();
callers use discoverGeoServices() instead. Export-only or otherwise unproven
ImageServer metadata follows the same service-shaped path. No protocol falls
through to heuristic detection.
Remaining #391 work
- Static asset classification for GeoParquet via
auto(structural URL recognition) and an explicit ambiguity-recovery contract; today GeoParquet requires an explicitprotocol: "geoparquet"hint plus a metadata reader. - Remaining protocol adapters, normalized schema/queryables, and partial
metadata diagnostics. The instance-scoped
createHonua()owner now consumes this contract without changing its lower-level discovery semantics. - Cross-language semantic descriptor fixtures and scheduled third-party smoke.
Those layers should consume this contract rather than publish capabilities or cache keys through protocol-specific conventions.
Invalid locators, protocols, capability identifiers, mismatched resolutions,
and cache identities throw HonuaDiscoveryError. Its stable .code is also
recognized by the root isHonuaError() guard; these input/metadata failures
are not retryable until the declaration or discovery projection is corrected.