diff --git a/docs/design-history.md b/docs/design-history.md index 52f00543..2b62ed63 100644 --- a/docs/design-history.md +++ b/docs/design-history.md @@ -2933,3 +2933,35 @@ resolver and the offer-key cache took an injected one, so a cache on a test or r clock selected keys at one time and checked the directory's signatures at another. Both now take a clock. Go's fetcher takes it as an optional trailing argument, so its released call still compiles, as the client verbs' call options were added. + +## A publisher proves its domain with the manifest it serves + +The contract carries `RequestDomainVerification` and `ConfirmDomainVerification`, an +ACME-style challenge in which the publisher places a token and the Exchange fetches it. +The publisher guide does not walk through them, because the console publishers onboard +through proves the domain another way: from the `fora.json` the domain serves. One fetch +of that document answers three questions at once. It shows the publisher controls the +domain, it names the account that gets paid, and it lists the catalog contributor the +publisher authorizes. The account travels as a `resource_owner_id` key in the Exchange +entry's `ext`, which the v1 contract does not define: the console writes it and its +Exchange reads it. The Exchange reads the document fresh at registration and again at +every catalog push, there through a short-lived cache, so the proof keeps being renewed +rather than being a token checked once and remembered. + +The catalog key follows from who runs the import. Pages are imported automatically, by +the operator, from the sitemap the console found at registration, so the operator holds +the contributor key and serves its directory from a host of its own; the publisher +generates and uploads no key. Authorization stays with the publisher: the manifest the +domain serves names the operator's contributor host, and removing it refuses the imports +that follow, once the Exchange's cached copy has expired, with nothing on the Exchange +side able to restore it. What counts is the document the domain serves, not the file the +console hands over: an edge package in front of `/.well-known/fora.json` answers that +address itself, from the same values held in its settings, so there the authorization +lives in those settings. The edge package carries no secret either; it fetches the +Exchange's public keys. + +Neither choice removes anything from the contract. The domain-verification RPCs stay in +v1 for deployments that verify that way, and a publisher that pushes its own catalog with +its own key still can, because the push path accepts the domain's own publisher as well +as any contributor its manifest names. What changed is which path the publisher guide +describes: the guide follows the console, not the other way round. diff --git a/scripts/check-doc-conformance.sh b/scripts/check-doc-conformance.sh index 134ae9e8..c8629c70 100755 --- a/scripts/check-doc-conformance.sh +++ b/scripts/check-doc-conformance.sh @@ -300,13 +300,8 @@ fi # appear anywhere — code, docs, config, copyright. The literal is built from two # pieces below so this script does not contain it verbatim, and therefore scans its # own file too (no self-exclusion blind spot). -# -# One host is allowed: the publisher console runs under the company's own domain, -# and the site must link to it. The host is assembled from the same two pieces so -# the self-scan rule holds. Any other use of the name is still an error. old_org='postin''dustria' -console_host="console.fora.${old_org}.com" -org_hits=$(git grep -niF "$old_org" -- . 2>/dev/null | grep -vF "$console_host" || true) +org_hits=$(git grep -niF "$old_org" -- . 2>/dev/null || true) if [ -n "$org_hits" ]; then echo "::error::stale '${old_org}' org reference (the project is github.com/FORA-Protocol):" echo "$org_hits" diff --git a/website/astro.config.mjs b/website/astro.config.mjs index 66367af8..009dc7fc 100644 --- a/website/astro.config.mjs +++ b/website/astro.config.mjs @@ -34,19 +34,23 @@ export default defineConfig({ access: 'public', default: 'https://pub-onboarding.demo.fora-protocol.org/onboarding', }), - // Where the onboarding preview sends a publisher who wants to register: - // the publisher console root, which signs a visitor in or lets them - // create an account first. Astro validates a value set in the build - // environment but never the default, so the default must itself be an - // https URL. + // Where the onboarding page's register button and the publisher guide + // send a publisher: the console root, which signs a visitor in, or lets + // them create an account, before anything else. The default is the + // console's address on fora-protocol.org, which redirects to wherever + // the console runs, so moving the console changes that redirect rather + // than this site. Astro validates a value set in the build environment + // but never the default, so the default must itself be an https URL. FORA_PUBLISHER_CONSOLE_URL: envField.string({ context: 'client', access: 'public', url: true, startsWith: 'https://', - default: 'https://console.fora.postindustria.com/', + default: 'https://console.fora-protocol.org/', }), - // Where a publisher behind an unsupported CDN reaches us. + // Where a publisher reaches us when the preview cannot take them further: + // a CDN the preview does not recognize, or a domain it could not + // generate a discovery document for. FORA_PUBLISHER_CONTACT_URL: envField.string({ context: 'client', access: 'public', diff --git a/website/src/content/docs/architecture/deployment-models.mdx b/website/src/content/docs/architecture/deployment-models.mdx index f04e013c..e61125e8 100644 --- a/website/src/content/docs/architecture/deployment-models.mdx +++ b/website/src/content/docs/architecture/deployment-models.mdx @@ -7,7 +7,7 @@ Three deployment models cover the full provider spectrum: enterprise providers w ## Provider Onboarding: Domain Verification (v1.0) -Before any deployment model, the provider must prove domain ownership via ACME HTTP-01 style verification. This is handled by `fora-cli`: +Before any deployment model, the provider must prove it controls its domain. With the contract's domain-verification RPCs, that is an ACME HTTP-01 style challenge, handled by `fora-cli`: 1. Provider runs `fora-cli verify --domain techcrunch.com --exchange exchange.ssp-alpha.com` 2. CLI calls `RequestDomainVerification` -- Exchange returns a challenge token @@ -17,6 +17,8 @@ Before any deployment model, the provider must prove domain ownership via ACME H The Exchange also checks `fora.json` to verify it is listed as an authorized exchange for this provider -- providing double protection. +An Exchange can instead take the `fora.json` the domain serves as that proof, as the Exchange behind a publisher console does; [Publisher Onboarding](/getting-started/publisher-onboarding/) describes that path. + ### catalog_contributors in fora.json Providers can authorize third parties (verification vendors, enrichment services) to push resource metadata on their behalf. This is declared in `fora.json`: diff --git a/website/src/content/docs/getting-started/for-providers.mdx b/website/src/content/docs/getting-started/for-providers.mdx index 11af450b..a1ed81f4 100644 --- a/website/src/content/docs/getting-started/for-providers.mdx +++ b/website/src/content/docs/getting-started/for-providers.mdx @@ -159,7 +159,7 @@ As a provider, you can sign claims about your own resources — Level 1 self-att 1. You compute a SHA-256 hash of the resource content at publish time (or catalog push time) 2. You sign a `ResourceAttestation` with your Ed25519 key, covering the content hash, quantity estimates, and other claims -3. The attestation is pushed to the Exchange via `CatalogService.PushResources` — the SDK's catalog client in Go, TypeScript or Python signs and sends it; run `ValidateResourceEntry` (or its Python/TypeScript twin) on each entry first, which the SDK also ships (see [Publisher Onboarding](/getting-started/publisher-onboarding/#push-your-catalog-with-the-sdk)) +3. The attestation is pushed to the Exchange via `CatalogService.PushResources` — the SDK's catalog client in Go, TypeScript or Python signs and sends it; run `ValidateResourceEntry` (or its Python/TypeScript twin) on each entry first, which the SDK also ships (see [Pushing with the Publisher SDK](/components/content-ingestion/sources/#pushing-with-the-publisher-sdk)) 4. When an agent fetches your resource, it can recompute the hash and verify the match