Issuer Directory Architecture (served by Authority)
Verified against commit
942a18727con 2026-09-26 (UTC) for the header and sections 1 to 3, 5, 6 and 8. Re-verify:git grep -n "AddFoldedIssuerDirectory\|MapFoldedIssuerDirectory" -- src/Authority/StellaOps.Authority/StellaOps.Authority/Program.csandgrep -n "CREATE TABLE" src/Authority/__Libraries/StellaOps.Authority.Persistence/Migrations/024_authority_issuer_directory_tables.sql. Sections 4 and 7 are carried from the pre-fold revision and were not re-verified on 2026-09-26.
The single operator-key read and Release Orchestrator authentication statements were re-verified at 7e3898fbce (CCP-081, 2026-10-13). Re-run the owner OperatorKeyServiceReadTests, client IssuerDirectoryClientTests and Release Orchestrator IssuerDirectoryServiceIdentityTests through tools/scripts/test-targeted-xunit.ps1; these are isolated source tests, not a live grant.
The standalone issuer-directory service was retired on 2026-09-11 (SPRINT_20260722_016 AUTH-10) after AUTH-9 folded the surface into Authority on 2026-09-08. Its host project is frozen at src/__Obsoleted/Authority/StellaOps.IssuerDirectory.WebService/. Paths, scopes and payloads did not change with the fold; namespaces moved from StellaOps.IssuerDirectory.WebService.* to StellaOps.IssuerDirectory.Api.*. Authority dossier: ../authority/architecture.md.
Source of truth
- Endpoint, contract and fold library:
src/Authority/__Libraries/StellaOps.IssuerDirectory.Api/(Fold/IssuerDirectoryFold.cscomposes it into Authority). - Host:
src/Authority/StellaOps.Authority/StellaOps.Authority/Program.cs(AddFoldedIssuerDirectory,MapFoldedIssuerDirectory). - Domain and application services:
src/Authority/StellaOps.IssuerDirectory/StellaOps.IssuerDirectory.Core/. - Infrastructure:
src/Authority/StellaOps.IssuerDirectory/StellaOps.IssuerDirectory.Infrastructure/. - Persistence library:
src/Authority/__Libraries/StellaOps.IssuerDirectory.Persistence/. - Client library:
src/Authority/__Libraries/StellaOps.IssuerDirectory.Client/(referenced byStellaOps.Policy.Engine,StellaOps.ReleaseOrchestrator.WebApiandStellaOps.DeltaVerdict). - Schema DDL:
src/Authority/__Libraries/StellaOps.Authority.Persistence/Migrations/024_authority_issuer_directory_tables.sql. - Canonical scope catalog:
src/Authority/StellaOps.Authority/StellaOps.Auth.Abstractions/StellaOpsScopes.cs.
1. Purpose
Issuer Directory holds trusted VEX/CSAF publisher metadata so that other services can resolve issuer identity, active signing keys and trust weights. It provides tenant-scoped CRUD with an audit trail, key lifecycle management (add, rotate, revoke), tenant trust-weight overrides, and the operator decision-signing key registry (ADR-025).
2. Runtime topology
- Host:
authority. The fold is gated byIssuerDirectory:Fold:Enabled(IssuerDirectoryFoldOptions, source defaultfalse); when it is false, Authority registers and maps nothing from this component. - Where the switch is on: the Authority family overlay
devops/compose/docker-compose.authority.ymlsetsISSUERDIRECTORY__FOLD__ENABLEDfromAUTHORITY_ISSUER_DIRECTORY_FOLD_ENABLED(defaulttrue). The release bundle (devops/release/bundle/docker-compose.yml) does not set it, so the source default applies there and the/issuer-directoryroutes are not mapped in a bundle-only install. - Authentication, tenancy middleware, health and Router registration are Authority’s. There is no Issuer Directory health endpoint, Kestrel listener or Router microservice of its own.
- Persistence: PostgreSQL schema
issuerinsidestellaops_authority, reached through Authority’s own connection unlessIssuerDirectory:Fold:ConnectionStringoverrides it. There is no generic fallback; an enabled fold with no connection fails startup. - CSAF bootstrap: forced off on the folded host (
SeedCsafPublishers = falsein the fold’s post-configuration). - Audit: every mutation writes an issuer-domain audit row through
IIssuerAuditSink.
Clients --> Authority (token, tenant) --> Issuer Directory endpoints --> PostgreSQL stellaops_authority (schema issuer)
|
+--> IIssuerAuditSink (issuer.audit)
3. Configuration
| Key | Default | Notes |
|---|---|---|
IssuerDirectory:Fold:Enabled | false | Maps the surface into Authority. |
IssuerDirectory:Fold:ConnectionString | empty | Empty means Authority’s own connection. |
IssuerDirectory:Fold:SchemaName | issuer | Schema used by the repositories. |
IssuerDirectory section | IssuerDirectoryApiOptions (API options such as the tenant header); SeedCsafPublishers is forced off by the fold. |
The standalone host’s IssuerDirectory:Authority:*, IssuerDirectory:Persistence:* and IssuerDirectory:Postgres:* keys belonged to the retired host and are not read by the fold.
4. API surface
All endpoints live under the /issuer-directory/issuers route group. Write/delete operations accept an optional audit reason via the X-StellaOps-Reason header (IssuerDirectoryHeaders.AuditReason); trust PUT also accepts a reason in the body.
Tenancy contract (claim-bound)
Sprint SPRINT_20260712_001 (TEN-3) moved tenant resolution off the wire header and onto the authenticated identity. TenantResolver (WebService/Services/TenantResolver.cs) now applies:
| Caller shape | Resolution |
|---|---|
Token carries stellaops:tenant | The claim is the isolation key. A X-StellaOps-TenantId header that disagrees with it is rejected 400 error_code=tenant_conflict — it is never an override. (Global-admin console tenant switching is unaffected: the shared StellaOpsTenantResolver already folds the canonical header into the claim for stellaops:admin principals, so the two agree by construction.) |
No claim + mutation (POST/PUT/DELETE, key rotate/revoke, operator-key enroll/rotate/revoke/approve) | Rejected 400 error_code=tenant_missing. |
No claim + read (GET) | Falls back to the X-StellaOps-TenantId header. |
Authority:Enabled=false (anonymous local harness only — IssuerDirectoryAnonymousModeGuard hard-blocks it outside Development/Testing) | Header remains the tenancy source for every access mode. |
Why reads still honour the header: StellaOps.IssuerDirectory.Client sends the tenant header and no bearer token, and Excititor / Policy Engine / ReleaseOrchestrator resolve issuer keys and trust weights through it over the backend network (Authority:BypassNetworks). Reads are non-destructive; the 2026-07-11 audit finding was that a mutation from that same tokenless bypass shape could rewrite another tenant’s VEX-consensus trust weights. Mutations are therefore claim-bound and a bypass-network caller — which carries no principal, hence no tenant claim — can no longer write into a tenant it merely names.
The failure vocabulary (error_code problem extension: tenant_missing, tenant_conflict, tenant_unknown) mirrors the platform’s StellaOpsTenantEndpointFilter. All tenant failures are 400, matching the platform mapping (not Attestor’s 403 tenant_claim_mismatch).
Authorization policies (IssuerDirectoryPolicies): IssuerDirectory.Reader → read scope, IssuerDirectory.Writer → write scope, IssuerDirectory.Admin → admin scope. Each maps to the configured scope via AddStellaOpsScopePolicy (honors BypassNetworks). When Authority:Enabled=false, all three policies allow any request (anonymous mode).
Issuers
| Method | Route | Policy / Scope | Notes |
|---|---|---|---|
GET | /issuer-directory/issuers | Reader / issuer-directory:read | Query includeGlobal (default true). Returns IssuerResponse[]. |
GET | /issuer-directory/issuers/{id} | Reader / issuer-directory:read | Query includeGlobal (default true); 404 if not found. |
POST | /issuer-directory/issuers | Writer / issuer-directory:write | Body IssuerUpsertRequest; returns 201 Created with Location: /issuer-directory/issuers/{id}. |
PUT | /issuer-directory/issuers/{id} | Writer / issuer-directory:write | Body IssuerUpsertRequest; route id must equal body Id (400 on mismatch). Returns 200. |
DELETE | /issuer-directory/issuers/{id} | Admin / issuer-directory:admin | Returns 204 No Content only when the issuer has no append-only operator decision-signing history; persistence rejects a delete that could cascade into those key rows. |
Issuer keys (nested under {issuerId}/keys)
| Method | Route | Policy / Scope | Notes |
|---|---|---|---|
GET | /issuer-directory/issuers/{issuerId}/keys | Reader / issuer-directory:read | Query includeGlobal. Returns IssuerKeyResponse[]. |
POST | /issuer-directory/issuers/{issuerId}/keys | Writer / issuer-directory:write | Body IssuerKeyCreateRequest; returns 201 Created (Location includes generated key id). Validation failures → 400. |
POST | /issuer-directory/issuers/{issuerId}/keys/{keyId}/rotate | Writer / issuer-directory:write | Body IssuerKeyRotateRequest; retires the active key and creates a replacement atomically. Returns 200. |
DELETE | /issuer-directory/issuers/{issuerId}/keys/{keyId} | Admin / issuer-directory:admin | Revokes the key (status → Revoked). Returns 204. |
Operator decision-signing keys (ADR-025 / AUTH-OPSIGN-002)
The stable enrolled-key contract consumed by the rest of the operator-signed-decisions program (WS3/WS4/WS5/WS6/WS7) — the frozen field shape, the
policy.trusted_keysverification-view delta, and the Authority→Policy pull-by-keyId sync — lives indocs/modules/authority/operator-signing-key-contract.md(AUTH-OPSIGN-006). This section documents the enrolment surface; that doc is the contract.
A separate, account-scoped enrolment surface for operator decision-signing public keys (the operator-decision@v1 predicate). Distinct from the issuer-scoped key endpoints above:
- The key is always bound to the enrolling operator’s
subfrom the verified token — a client-suppliedsubjectIdis honoured only when it matches and is otherwise rejected (400); it is never trusted on its own. - Non-custodial: only the public key (or qualified certificate) is accepted. Private-key material is rejected (
400,IssuerKeyValidator.RejectPrivateKeyMaterial). - Every operation is fresh-auth-gated: a token carrying
authority:signing-keys.enrollorauthority:signing-keys.adminis rejected byStellaOpsScopeAuthorizationHandlerunless itsauth_timeis within the freshness window (403); a missing scope is403, an unauthenticated request401. - Every operation audits the operator
sub,algorithmId,providerHint, and the fresh-auth claims (auth_time/acr/amr) viaIIssuerAuditSink; approval also recordsapprover_sub. - Optional dual-control (
IssuerDirectory:OperatorSigningKeys:RequireDualControl=true): the enrolled key startsPendingand stays inactive until a second approver (distinctsub, holdingauthority:signing-keys.admin) promotes it. Self-approval is rejected (400). - Compliance-profile auto-bind (
IssuerDirectory:OperatorSigningKeys:EnforceComplianceProfile=true, the default): before enrollment, IssuerDirectory calls Platform’s narrow tenant-bound/api/v1/admin/crypto-providers/compliance-profile/operator-signing-enrollmentprojection with the validated operator bearer and tenant. The projection determines the canonicalalgorithmIdand exact activeproviderName; the localICryptoProviderRegistrymust resolve that provider and it must reportSupports(Verification, algorithmId). A client algorithm/provider mismatch is400; an unavailable profile projection or missing active provider is503. The same guard runs before both enrolment and rotation, so rotation cannot replace a compliant key with a caller-selected algorithm/provider. No key row is written or retired on failure. World/FIPS project ES256 browser enrollment; GOST/SM project their regional algorithm and a CLI/smartcard route, never an ES256 downgrade. - The IssuerDirectory host registers
default,fips.ecdsa.soft, andeu.eidas.softverification identities. The FIPS/eIDAS software providers keep their existing explicit environment gates; selecting one without enabling/configuring that provider returns the fail-closed503rather than falling back todefault. Regional verification providers remain operator/plugin configuration. - Structural namespace: enrollment requires an existing issuer. Authority’s folded surface converges one
operator-signingissuer per active tenant at startup and on tenant creation (IOperatorSigningIssuerConvergence); it is a system seed with no keys or trust, created only when missing, so an operator’s later edits survive restarts. The CSAF publisher seed stays off in the fold. - Append-only verification history:
IIssuerKeyRepositoryexposes no delete/remove operation. Revocation and rotation update lifecycle state while retaining the public key. Migration003_operator_signing_key_append_only_guard.sqladds a PostgreSQLBEFORE DELETEtrigger forDecisionSigningrows, so direct deletes and the historical issuerON DELETE CASCADEpath fail closed.PostgresIssuerRepository.DeleteAsyncalso rejects an issuer delete before mutation when that issuer owns operator decision-signing history. - Crash-safe provider-change retirement: the provider-change mutation requires a stable non-empty
operationId. IssuerDirectory canonicalizes the subject and supported-algorithm lists, then commits every affected key retirement, its per-key audit row, and one immutable receipt in a single PostgreSQL transaction. A retry with the same tenant/issuer/canonical selection returns that original receipt without retiring or auditing again, even if the configured grace period or executing service identity changed after the first attempt. Reusing an operation id for a different tenant, issuer, subject set, or algorithm set fails closed with409. The initiating operator is persisted asoriginalActorSubject; the audit also records the current executing subject, while neither request nor receipt carries private key material.
| Method | Route | Policy / Scope | Notes |
|---|---|---|---|
GET | /issuer-directory/operator-signing-keys/{issuerId}/keys | authority:signing-keys.enroll (+ fresh auth) | Lists the current operator’s DecisionSigning keys for the issuer, bound to the token sub. Returns public verification material and lifecycle status. |
GET | /issuer-directory/operator-signing-keys/{issuerId}/keys/subjects/{subjectId} | Reader / issuer-directory:read | Tenant-scoped service/admin lookup for all public DecisionSigning keys belonging to one subject. This is deliberately reader-only: Policy uses it to answer key-presence checks for the authenticated operator without impersonating that operator’s fresh-auth enrolment token. Policy calls it with its own per-tenant policy-engine bearer (issuer-directory:read), and the shared client does not cache the answer, so an enrollment or revocation is visible on the next check. The tenant header remains mandatory and cross-tenant rows are never returned. |
GET | /issuer-directory/operator-signing-keys/{issuerId}/keys/{keyId} | Reader / issuer-directory:read | Service/admin verification read for one DecisionSigning key. Returns 404 when the key is missing or is not a decision-signing key. The shared client reads the owner on every call, so a prior active key does not conceal revocation or an owner refusal. Release Orchestrator supplies its own tenant-bound service bearer; the tenant and issuer IDs returned by the owner may be canonical UUIDs when the request used names. |
POST | /issuer-directory/operator-signing-keys/{issuerId}/keys | authority:signing-keys.enroll (+ fresh auth) | Body OperatorSigningKeyEnrollRequest; binds the key to the token sub with purpose DecisionSigning and, by default, auto-binds the active compliance-profile algorithm/provider. Returns 201 (Active, or Pending if dual-control is on), 400 for a client/profile mismatch, or 503 when the profile/provider cannot be resolved safely. |
POST | /issuer-directory/operator-signing-keys/{issuerId}/keys/{keyId}/rotate | authority:signing-keys.enroll (+ fresh auth) | Body OperatorSigningKeyEnrollRequest; applies the same active-profile algorithm/provider guard as enrolment, then retires the prior key and binds the replacement to the same operator. Returns 200; a rejected replacement leaves the active key unchanged. |
DELETE | /issuer-directory/operator-signing-keys/{issuerId}/keys/{keyId} | authority:signing-keys.enroll (+ fresh auth) | Revokes the key (stays resolvable for historical verification). Returns 204. |
POST | /issuer-directory/operator-signing-keys/{issuerId}/keys/{keyId}/approve | authority:signing-keys.admin (+ fresh auth) | Dual-control: a second approver promotes a Pending key to Active. Returns 200. |
POST | /issuer-directory/operator-signing-keys/{issuerId}/provider-change-supersede | Any of authority:signing-keys.admin, crypto:admin, crypto:profile:admin, or ops.admin | Body OperatorProviderChangeSupersedeRequest contains the stable operationId, initiating originalActorSubject, Authority-resolved exception-granting subjectIds, and active-provider supportedAlgorithmIds. Returns the immutable OperatorProviderChangeSupersedeResponse; exact-request replay returns the original response, conflicting operation-id reuse returns 409. |
Scope gotcha (CLAUDE.md §1.5):
authority:signing-keys.enroll/authority:signing-keys.admin/decision-signingare JWT scope claim values that double as the ASP.NET policy names (the endpoints registerRequireAuthorization(<scope>)with the same literal). The freshness gate is applied byStellaOpsScopeAuthorizationHandlerfor any token presenting these scopes, so applying the policy is sufficient to require a recent step-up.
Issuer trust (nested under {issuerId}/trust)
| Method | Route | Policy / Scope | Notes |
|---|---|---|---|
GET | /issuer-directory/issuers/{issuerId}/trust | Reader / issuer-directory:read | Query includeGlobal. Returns IssuerTrustResponse (tenant + global overrides + effective weight). |
PUT | /issuer-directory/issuers/{issuerId}/trust | Writer / issuer-directory:write | Body IssuerTrustSetRequest (Weight, optional Reason). Weight must be in [-10, 10] (else 400). Returns 200 with the recomputed view. |
DELETE | /issuer-directory/issuers/{issuerId}/trust | Admin / issuer-directory:admin | Removes the tenant override (falls back to global/default weight). Returns 204. |
Request/response contracts
IssuerUpsertRequest:Id,DisplayName,Slug(required),Description?,Contact(IssuerContactRequest:Email,Phone,Website,Timezone),Metadata(IssuerMetadataRequest:CveOrgId,CsafPublisherId,SecurityAdvisoriesUrl,CatalogUrl,Languages[],Attributes{}),Endpoints[](IssuerEndpointRequest:Kind(defaultcsaf),Url,Format?,RequiresAuth),Tags[]. (Slugis required on create/update but is not separately re-validated against the existing record on update.)IssuerResponse:Id,TenantId,DisplayName,Slug,Description?,Contact,Metadata,Endpoints[],Tags[],CreatedAtUtc,CreatedBy,UpdatedAtUtc,UpdatedBy,IsSystemSeed.IssuerKeyCreateRequest/IssuerKeyRotateRequest:Type(parsed case-insensitively toIssuerKeyType),Format,Value,ExpiresAtUtc?. (Format+Valueform theIssuerKeyMaterial.)IssuerKeyResponse:Id,IssuerId,TenantId,Type,Status,MaterialFormat,Fingerprint,CreatedAtUtc,CreatedBy,UpdatedAtUtc,UpdatedBy,ExpiresAtUtc?,RetiredAtUtc?,RevokedAtUtc?,ReplacesKeyId?.OperatorSigningKeyEnrollRequest(ADR-025):Type,Format,Value(PUBLIC key only),AlgorithmId?(aSignatureAlgorithmsconstant),ProviderHint?,SubjectId?(must match the tokensubwhen present),ExpiresAtUtc?.OperatorSigningKeyResponse(ADR-025):KeyId,IssuerId,TenantId,SubjectId,Type,Status,MaterialFormat,PublicKey(the stored publicIssuerKeyRecord.Material.Value),AlgorithmId?,ProviderHint?,Purpose,Fingerprint,PendingApproval,CreatedAtUtc,ExpiresAtUtc?,ValidFromUtc,ValidUntilUtc?,RetiredAtUtc?,RevokedAtUtc?,ReplacesKeyId?. The response never contains private material; enrollment rejects it before a key can be stored.IssuerTrustResponse:TenantOverride?,GlobalOverride?(each aTrustOverrideSummary:Weight,Reason?,UpdatedAtUtc,UpdatedBy,CreatedAtUtc,CreatedBy),EffectiveWeight.
Domain enums
IssuerKeyType:Ed25519PublicKey,X509Certificate,DssePublicKey.IssuerKeyStatus:Active,Retired,Revoked,Pending. (Pendingis the dual-control hold for operator decision-signing keys — ADR-025 / AUTH-OPSIGN-002; not used by classic issuer keys.)IssuerKeyPurpose:Issuer(default, publisher/VEX signing),DecisionSigning(operator decision-signing key — ADR-025).
Note: Authority migration
024keeps the legacy string vocabularies:issuer_keys.key_typeCHECK (ed25519,x509,dsse,kms,hsm,fido2),issuers.statusCHECK (active,revoked,deprecated) andissuer_keys.statusCHECK (active,retired,revoked,pending). These string vocabularies are not the same surface as the C# enums above, which are the authoritative API contract.
5. Authentication
Authentication is Authority’s own resource-server stack; the fold adds only the three named policies and the operator signing-key policies. The retired host’s anonymous mode and IssuerDirectory:Authority:* keys do not exist on the folded host.
6. Persistence schema (schema issuer)
Authority migration 024_authority_issuer_directory_tables.sql creates the schema inside stellaops_authority on Authority’s migration chain; the fold registers no migrator of its own. The SQL files under StellaOps.IssuerDirectory.Persistence/Migrations/ (001 to 004) are the retired host’s history and are not the migration authority. Tables:
issuers—id UUIDPK,tenant_id UUID(global seeds use the@globalGUID),name(slug),display_name,description,endpoints/contact/metadataJSONB,tags TEXT[],status(active|revoked|deprecated),is_system_seed, created/updated audit columns. Unique(tenant_id, name).issuer_keys—id UUIDPK,issuer_id(FK →issuers,ON DELETE CASCADE),tenant_id,key_id,key_type,public_key,fingerprint,not_before/not_after,status(active|retired|revoked|pending),replaces_key_id, lifecycle timestamps,revoke_reason,metadataJSONB. Unique(issuer_id, key_id)and uniquefingerprint.trust_overrides—id UUIDPK,issuer_id(FK),tenant_id,weight NUMERIC(5,2)(CHECK-10..10),rationale,expires_at, audit columns. Unique(issuer_id, tenant_id).audit—id BIGSERIALPK,tenant_id,actor,action,issuer_id,key_id,trust_override_id,reason,detailsJSONB,correlation_id,occurred_at.operator_provider_change_receipts— append-only operation receipt keyed globally byoperation_id, with tenant/issuer identity, canonical subject and algorithm arrays, initiating and executing actor subjects, original reason/grace/time, and the ordered retired-key result as JSONB. Migration024creates the table, a tenant/time index and the append-only triggertrg_operator_provider_change_receipts_append_only. The receipt contains lifecycle/public identifiers only, never private key material.
Indexes cover tenant/status/slug lookups, key issuer/status/tenant, trust tenant, and audit (tenant_id, occurred_at DESC)/issuer. Migration 024 also creates the updated_at triggers on issuers, issuer_keys and trust_overrides, the delete guard trg_operator_decision_signing_key_append_only that preserves operator decision-signing public keys even through the ON DELETE CASCADE from issuers, and the receipt append-only trigger. PostgresOperatorProviderChangeRepository commits each receipt in the same transaction as the corresponding key lifecycle and audit mutations.
The schema isolation from
AuthorityDbContextis a deliberate security feature (blast-radius containment; least privilege). See Authority architecture § 21.3.
7. Observability
OpenTelemetry meter StellaOps.IssuerDirectory (IssuerDirectoryMetrics):
issuer_directory_changes_total— tagstenant,issuer,action. Counts issuer create/update/delete events.issuer_directory_key_operations_total— tagstenant,issuer,operation,key_type. Counts key create/rotate/revoke operations.issuer_directory_key_validation_failures_total— tagstenant,issuer,reason. Counts key validation/verification failures.
The host registers ASP.NET Core + runtime metrics instrumentation and ASP.NET Core + HttpClient tracing.
Audit actions
Issuer-domain audit rows are written with these action values (from the Core services): created, updated, deleted, seeded, key_created, key_enrollment_pending, key_retired, key_rotated, key_revoked, trust_override_set, trust_override_deleted. key_enrollment_pending is written instead of key_created when RequireDualControl is on and the key is held for a second-operator approval (IssuerKeyService.Add.cs:91). Provider-change retirement writes key_retired once per affected key with correlation id operationId; details retain both original_actor_subject and executing_actor_subject. A receipt replay writes no additional audit row.
8. Dependencies and reuse
StellaOps.IssuerDirectory.Core— domain model (IssuerRecord,IssuerKeyRecord,IssuerTrustOverrideRecord,IssuerAuditEntry) and application services (IssuerDirectoryService,IssuerKeyService,IssuerTrustService), key validators and metrics.StellaOps.IssuerDirectory.Infrastructure— CSAF seed loader and Testing-only in-memory repositories.StellaOps.IssuerDirectory.Persistence— PostgreSQL repositories, audit sink andIssuerDirectoryDbContext.StellaOps.IssuerDirectory.Api— endpoints, policies, tenant resolution and the fold composition.
9. Archived and superseded documentation
- Pre-2026-03 dossier:
docs-archive/modules/issuer-directory/architecture.mdandoperations/*. - Standalone-host deployment, backup and Offline Kit procedures, retired 2026-09-11:
docs-archive/modules/issuer-directory/retired-20260926/operations/.
