Publishing contract — verification record
This page exists so publishing-proprietary-authority-data.md
can be handed to a customer without an internal appendix attached. Every customer-facing claim
is traced here to file:line in sv0-platform. When the spec changes, change this too — an
uncited spec drifts into fiction within two releases.
Verified 2026-08-01 against sv0-platform origin/main at 173c80d1, and probed against a live
local stack. Two PRs merged on 2026-08-01 moved several claims from [Target] to [Today] and
shifted line numbers in path-materializer.ts and graph-transformer.ts:
- #1928 (
e30da1e8, closes #1926 / #1927) — fail-loud ingest diagnostics + report-only connector identity binding. - #1931 (
173c80d1, closes #1929 / #1930) — typed foreign-entity reference + the on-prem estate published intodemo-w1.
Both hashes re-checked with git rev-parse on 2026-08-01; an earlier revision of this page carried
173d17d1 for #1931, which is not a commit in the repository at all. Every hash on this page must
survive git rev-parse — that is the whole point of the page.
An earlier revision of the spec, and this page, both asserted that a publishing credential gets
403 on GET /api/v1/syncs/:id. That was false, and had been false since 2026-05-19.
Where it came from: the rejection message at
src/api/middleware/bearer-token-middleware.ts:414 still reads "Connector API keys may only
call /api/v1/ingest", although API_KEY_ALLOWED_PATH_PREFIXES at :147 has been
["/api/v1/ingest", "/api/v1/syncs"] since #1189 / #1190. The string was then copied into code
comments and worked around by polling MongoDB directly, in three separate seed scripts —
scripts/seed-demo-onprem.ts:1543-1544 ("Connector API keys are scoped to /api/v1/ingest only,
so GET /api/v1/syncs/:id returns 403"), scripts/seed-demo-jira-aws.ts:1675,1717-1719 and
scripts/seed-demo-w1.ts:1883,1947-1948. One script had it right the whole time —
scripts/ci-mint-connector-key.ts:13 states both prefixes accurately — which is what a false
consensus looks like from the inside: four sources, three agreeing, and the majority wrong.
Nobody probed. The string was trusted, three comments corroborated it, and the claim reached a page marked exportable to customers. Corrected across all three scripts in sv0-platform#1940.
The string is tracked for correction as sv0-platform#1939 (retitled — its original premise, "publisher cannot read its own publish record", was itself the false claim).
The rule this earns: an error string, a code comment and an issue title are all claims, not evidence. A capability assertion in a customer-facing page needs a probe or a read of the enforcing predicate — here, the allow-list constant, not the message printed when it fails.
The traversal constraints the specification is built around
These are the reason the spec is written in the customer's vocabulary rather than ours: each one turns a well-formed payload into a zero-result publish, and none is discoverable from the schema.
| Claim | Citation |
|---|---|
MEMBER_OF is never traversed | Declared src/ingestion/types.ts:36, accepted at the boundary src/api/routes/ingest.ts:55, in the domain enum src/domain/graph/relationship-types.ts:18. Zero occurrences in src/ingestion/path-materializer.ts — repo-wide grep returns only those three plus a comment at src/domain/graph/identity-subtypes.ts:13. The only grant edges read are HAS_ROLE / HAS_PERMISSION_SET (path-materializer.ts:607, :615). |
Permission with no actions_pattern is skipped — and now counted back to the publisher | path-materializer.ts:644-653 (actions.length === 0 → skippedPermissionIds.add(perm._id); continue); set declared :259, returned as permissionsSkippedMissingActions :398, warned once per sync :369-376. De-duplicated by permission _id so the number tracks broken grants, not holders (:83-93). Now persisted — see §4 below. |
Permission with no APPLIES_TO produces no path | path-materializer.ts:656-658 collects APPLIES_TO targets; :660 (resourceIds.length === 0) unless the scope-terminal branch fires: :668-670, gated on enableScopeTerminals (default false at :237 and :578) and SCOPE_TERMINAL_SEVERITIES = {escalation, write} (:427). Flag source: per-tenant scope_terminals_enabled, default false — src/workers/handlers/sync-ingestion.ts:881. |
Nested permission_set composition is not traversed | path-materializer.ts:628-638 — one hop, role → GRANTS → permission; each permission is then read as a leaf (actions_pattern + APPLIES_TO only, :644-658). No recursion into a permission's own GRANTS. |
DELEGATES_TO is never traversed | src/ingestion/types.ts:34, src/api/routes/ingest.ts:53, src/domain/graph/relationship-types.ts:16. No other occurrence in src/. |
| Depth budget | MAX_AUTH_CHAIN_DEPTH = 2 at path-materializer.ts:157. Guarded walks: :893 (AUTHENTICATES_TO, depth + 1) and :956 (SAME_ENTITY / BRIDGES_TO correlations, also depth + 1). Depth-free: FORWARDING_EDGE_TYPES = {CALLS, INVOKES, USES, AUTHENTICATES_AS, AUTHENTICATES_VIA} at :417, walked :851; RUNS_AS at :800-816 (explicitly same depth, :801, :816). |
Cross-system correlation hops consume the budget too, not only AUTHENTICATES_TO
(path-materializer.ts:956). §3.3 of the spec therefore says "boundary crossings", not
"authentication hops". An earlier internal framing said the latter and was wrong.
Note the vocabulary split this creates, and keep it: correlation is the right word for these stitching hops and for the §2 key-matching table. It is the WRONG word for §2.1's cross-payload references, which are declared foreign keys the publisher asserts, not matches we infer.
Section-by-section
§0 / §2 — self-contained payload, exact-string identity. nodeIdToEntityId is built at
src/ingestion/graph-transformer.ts:360-390 from graph.nodes — plus, since #1929, a pre-pass that
seeds only resolved foreign references (:376-381), deliberately ordered before pass 1 so a
declared node always overwrites a colliding seed ("declaration wins", :365-366). buildRelationships
(:262) still drops an unresolved endpoint with a bare continue
— :274 (outbound, if (!targetEntityId) continue;) and :289 (inbound,
if (!sourceEntityId) continue;). What changed (#1926): the drop is no longer silent.
countUndeclaredEndpointEdges (graph-transformer.ts:117-167) walks the raw edge list once and
returns {sourceNodeMissing, targetNodeMissing, total, evidenceEndpointUnconsumed} (interface
:53-75), called from transformGraph at :498-501 and computed from the raw edges rather than
from the per-node relationship build, so the number is independent of how many endpoints of a
dropped edge happened to survive. Identity is
buildStableEntityId(tenantId, sourceSystem, sourceId), SHA-256 over the raw strings with no
normalisation, src/shared/stable-entity-id.ts:21-32.
evidenceEndpointUnconsumed is a distinct class, deliberately: both endpoints are declared, but an
activity-record node is attached by an edge the evidence resolver does not consume — the resolver is
direction-specific (EVIDENCES with the evidence node as source, EXECUTES_ON with it as target),
graph-transformer.ts:149-164. That is the residual silent loss the endpoint counter alone misses,
and it is what §4.1 of the spec calls "activity records attached by a relationship we do not consume".
Known caveat, deliberately kept out of the customer body: the first argument is the mutable tenant
slug, so a tenant rename produces silent dangling — documented at stable-entity-id.ts:11-18.
§1 objects and fields. Node types src/ingestion/types.ts:1-13; statuses :15; required node
fields src/api/routes/ingest.ts:166-177 (nodeId, nodeType, sourceSystem, sourceId,
displayName, status, properties); edge fields :185-198; edge vocabulary ingest.ts:36,
types.ts:17-42. sourceFirstObservedAt → first_observed_at, never synthesised: types.ts:181,
parser graph-transformer.ts:193, applied :472 with an explicit ?? now fallback rather than a
synthesised timestamp. Sensitivity canon — five values, legacy mapping, unknown default —
src/domain/sensitivity.ts:1-40, applied graph-transformer.ts:414 and
path-materializer.ts:744. business_domain default "unknown" path-materializer.ts:743.
Environment label promoted, never defaulted: graph-transformer.ts:417-429,
src/domain/environment.ts:122,211. Impact class max_action_severity consumed by
src/evaluator/rules/action-capability-gained.ts:168 and src/evaluator/path-evaluator.ts:837.
Conditions rendered configured-not-evaluated: path-materializer.ts:436 (buildScopePattern).
§1.7 activity records. ExecutionEvidenceDoc fields src/domain/evidence/types.ts:1-40.
Confidence → strength mapping src/evaluator/evidence-classification.ts:13-25; labels
Confirmed / Correlated / Configured / Possible src/domain/findings/types.ts:29-34; weakest-wins
evidence-classification.ts:27-42. Workload-wide fallback and the ambiguous-attribution flag:
src/ingestion/validators/rules/execution-30d-scoping.ts:8-56.
§1.8 scope and completeness → deletion safety. ScanScope src/ingestion/types.ts:279, and the
explicit note that permissionDenied is observability-only while evidenceCompleteness is the sole
gate: types.ts:289-300, fields :305,317. Exclusion derivation, partial /
unavailable_no_access only, src/ingestion/diff-engine.ts:63-66,102 via
deriveExcludedSourceSystems :95; wired src/workers/handlers/sync-ingestion.ts:525,539-545;
trusted-source filter diff-engine.ts:567-594.
§1.8 correlationKeys and lineage_records — NOT accepted; discarded at the HTTP boundary.
An earlier revision of this page and of the spec called these [Target] — "accepted and stored but
not yet consumed" — and invited the publisher to send them. That was wrong in the direction that
costs a customer real work, so the spec now tells the reader not to send them.
The two halves of the evidence: the fields are declared on the internal payload type —
correlationKeys src/ingestion/types.ts:89-94,154 and lineage_records types.ts:108-134,163 —
but neither appears in the zod object that validates an arriving node,
src/api/routes/ingest.ts:161-184, whose members are exactly nodeId, nodeType, sourceSystem,
sourceId, displayName, status, createdAt, lastModifiedAt, properties,
evidenceConfidence, sourceFirstObservedAt. Zod's default object behaviour is to strip unknown
members, so both fields are gone before the payload reaches anything — they are not persisted, and
they do not survive inside properties either. Probed live 2026-08-01: a node posted with all three
of correlationKeys, lineage_records and sourceFirstObservedAt came back with the control
(sourceFirstObservedAt → first_observed_at) persisted and the other two absent everywhere on the
stored entity. Tracked as
sv0-platform#1941.
Why this belongs on the internal page rather than only in the fix: the schema already carries a
comment recording the same hazard from a previous occurrence. Above sourceFirstObservedAt, at
ingest.ts:179-183: "Without this declaration, zod's default object stripping would silently drop
the field at the HTTP boundary and the platform-side feature would no-op for every connector posting
through /api/v1/ingest." Somebody paid for that lesson once, wrote it down next to the fix, and the
next two optional fields were added to the type without it. A field declared on NormalizedNode is
not a field the boundary accepts — check the zod schema, every time.
Repo-wide grep across src/ for each name returns only the type declarations plus two comments in
src/ingestion/stitching/extractors/index.ts:38,125 — so there is no consumer either. Neither #1928
nor #1931 touched them.
§2 cross-system keys. Registry of 8 rules with collision policies
src/ingestion/stitching/rules/registry.ts:44-237. Email/UPN lowercase + trim, precedence
email → upn → userPrincipalName, no further rewriting extractors/email-lower.ts:1-33; policy
drop_all_ambiguous registry.ts:158. Cloud resource identifier lowercase + trailing-slash strip,
account and region preserved, all_match extractors/arn-canonical.ts:1-42, registry.ts:137.
Endpoint URL normalisation + drop_all_ambiguous extractors/endpoint-uri-normalized.ts:1-49,
registry.ts:182. Client id lowercase, first_match_wins registry.ts:111-113. Resource path id,
all_match registry.ts:232-237. Determinism guarantees src/ingestion/stitching/rule-executor.ts:1-20;
mixed-tenant refusal :65-80.
§2.1 cross-payload references — [Today] as of #1931. The wire type
ForeignEntityRef {sourceSystem, sourceId} src/ingestion/types.ts:220-224; the endpoint shape
(exactly one of nodeId / ref, never both, never two reference endpoints on one edge)
types.ts:228-244 and enforced twice — zod for single-shot src/api/routes/ingest.ts:194-215, and
structurally for every transport at the shared pre-enqueue boundary via assertEdgeEndpointShape
src/ingestion/foreign-reference.ts:252 (mandatory because the chunked route never runs the graph
schema on the assembled payload, foreign-reference.ts:178-185). Per-payload policy
foreignReferencePolicy: "reject" | "report", absent ⇒ reject, types.ts:258,276 and
ingest.ts:231.
Resolution and its diagnostics. One batched probe over the distinct (sourceSystem, sourceId)
pairs — collectForeignReferences foreign-reference.ts:318, existence index
:392, summary summarizeForeignReferences :488-544. Resolution requires a live entity;
soft-deleted targets are counted as unresolved_deleted, split from unresolved_missing
(src/domain/syncs/types.ts:193-203). A reference to an object the same payload also declares is
redundant and resolves without a probe (foreign-reference.ts:500-501, semantics
syncs/types.ts:206-210). Synthetic node ids are injective by construction: foreignRefNodeId
(foreign-reference.ts:77-79) builds the id as the target entity key with a reserved prefix, so any
number of edges naming the same (sourceSystem, sourceId) collapse to one synthetic node and cost
exactly one probed id. (Paraphrased, not quoted — the comment at :73-75 illustrates the point with
a named vendor's group object, which must not travel into a page that can be exported.)
unresolved_sample is capped at 20 (UNRESOLVED_SAMPLE_LIMIT, foreign-reference.ts:478,
applied :527) and sorted rather than first-encountered so the block is byte-reproducible — the sort
is foreign-reference.ts:520-527, on (source_system, source_id, witness edge id), and the
rationale is stated on the field itself at src/domain/syncs/types.ts:219-224 ("SORTED rather than
first-20-encountered because encounter order is not stable across reorderings of graph.edges and
this block must be byte-reproducible"). Do not cite foreign-reference.ts:89-96 for this: that is a
different determinism concern — the choice of the lexicographically smallest edge id as the witness
on each descriptor.
Rejection semantics — the customer-visible part. Rejection happens in the worker, not at the
HTTP boundary: src/workers/handlers/sync-ingestion.ts:303-337 sets the sync failed, writes the
diagnostics under metrics.ingest_rejection (deliberately not ingest_diagnostics, which #1926
pinned to mean "reached completed"), and returns rather than throwing — throwing would feed the
retry ladder, and a retry could flip the verdict if a partner payload landed in between, making the
outcome a function of queue timing. Hence the spec's "202 means accepted for processing, not
processed" in §6 and "acceptance of the upload is not resolution" in §2.1. The error text tells the
publisher the syncId is burned by idempotency and to resubmit under a new one (:325-329).
Disclosure boundary, why the spec says the sample is echoed back unenriched.
src/domain/syncs/types.ts:164-179 states the constraint explicitly: the block is served by
GET /api/v1/syncs/:id, so it is an existence oracle over keys within one tenant, and
unresolved_sample may never carry a key, id, display name or property the submitter did not supply.
The spec's wording ("echoed back exactly as you sent them, never enriched with anything you did not
supply") is that constraint restated for the reader, not a softening of it.
§3.2 / §3.3. Path entry types path-materializer.ts:158 (PATH_MATERIALIZABLE_TYPES =
identity, workload). One-route-per-(anchor, terminal) disclosure path-materializer.ts:585-592.
§4 diagnostics. Metrics block written to the sync record
src/workers/handlers/sync-ingestion.ts:1078-1172: created / updated / affected, events, paths,
chains, authority paths created / updated / removed, entities_with_resource_key /
entities_without_resource_key, evidence_with_resource_key / evidence_without_resource_key,
validation_results, deletion_scope_excluded, and the circuit-breaker block (:1096). Thresholds:
entity deletion 0.50 — DEFAULT_DELETION_THRESHOLD src/ingestion/diff-engine.ts:796, compared
:814-817; authority-path removal 0.50 — AP_REMOVAL_THRESHOLD
src/ingestion/authority-path-materializer.ts:19. Five validators
src/ingestion/validators/index.ts:25-31; dangling-reference check with offender sample
validators/rules/referential-integrity.ts:16-92.
§4.1 the diagnostics block — both former [Target] gaps are SHIPPED. metrics.ingest_diagnostics
is written unconditionally at src/workers/handlers/sync-ingestion.ts:1148-1170, so 0 reads as
"measured, clean" and an absent block means the sync predates #1926 (:1145-1147). Contents, in the
order the spec describes them:
| Spec wording (§4.1) | Field | Citation |
|---|---|---|
| relationships dropped for an undeclared endpoint, split by end | edges_dropped_undeclared_endpoint.{source_node_missing, target_node_missing, total} | sync-ingestion.ts:1149-1154, computed graph-transformer.ts:117-167 |
| activity records attached by a relationship we do not consume | edges_dropped_undeclared_endpoint.evidence_endpoint_unconsumed | graph-transformer.ts:149-164 |
| grants discarded for a missing action list | permissions_skipped_missing_actions | sync-ingestion.ts:1159 ← path-materializer.ts:398 |
| cross-payload reference outcome, incl. the capped unresolved sample | foreign_references | sync-ingestion.ts:1158, shape src/domain/syncs/types.ts:181-231 |
| reach actually computed | paths_computed, paths_cross_boundary_hop, paths_cross_system, max_auth_chain_depth | sync-ingestion.ts:1160-1163, semantics path-materializer.ts:54-78 |
| which publishing identity we bound the payload to | connector_identity_binding | sync-ingestion.ts:1167-1169 — spread-conditional, absent ⇒ not measured, distinct from every measured outcome (:1164-1166) |
The spec's single-system caveat ("the boundary and cross-system counters read zero on a perfectly
good publish") is path-materializer.ts:103-108 verbatim in intent: for a single-source tenant the
healthy shape is depth 0 across one system, so every shape counter reads 0, the same as a collapsed
graph. That caveat is load-bearing and must not be dropped from the customer page.
§4.1 worked example — measured, not asserted. Live local stack (Mongo sv0_e2e on 27018, API
:3100), tenant demo-w1, one run of the seven onprem-* publishes summed over
metrics.ingest_diagnostics (the collection holds two runs of the same seven; select one run by its
shared sync-id suffix, or every counter doubles): declared 36 / distinct 32 / resolved 32 / unresolved_missing 0 / unresolved_deleted 0 / edges_dropped_unresolved 0 / redundant 0, edges_dropped_undeclared_endpoint.total 0,
permissions_skipped_missing_actions 0, paths_computed 38, paths_cross_boundary_hop 27,
paths_cross_system 38, max_auth_chain_depth 1. The spec quotes the declared/resolved figures, the
zeros, paths_cross_system and the depth.
Two precision traps in that triple, both of which the spec now avoids. First, declared counts
edge endpoints carrying a reference while distinct counts (system, id) pairs
(src/domain/syncs/types.ts:182-185), so "36 resolved" would overstate it. Second — and this is the
one an earlier spec revision got wrong — the 32 is a sum of seven per-payload distinct counters,
not an estate-wide distinct count. distinct is scoped to one payload's own reference set
(foreign-reference.ts:318 collects from a single graph), so an object referenced from two
payloads is counted in both and appears twice in the sum. The per-payload breakdown for this run, read
back the same way, makes it plain — declared/distinct per publish: directory 12/9, credential
vault 6/6, deployment registry 7/7 and 3/3, entitlement registry 5/4, authentication realm 3/3; the
record store declared none. Nine plus six plus seven plus three plus four plus three is the 32. The
spec therefore says 36 endpoints declared against 32 lookups, with de-duplication explicitly scoped
per payload. Do not write "32 distinct objects" about a multi-payload total; the number that would
support that phrasing is not one we compute.
§5.3 ownership. Ambiguity rule — all owners are groups or teams —
src/evaluator/rules/ownership-ambiguous.ts:10-45; unknown-ownership rule and its property fallbacks
src/evaluator/rules/ownership-unknown.ts:11-49. Assertion API — create with owner_email,
owner_name, optional team and notes; list; revoke-with-reason:
src/api/routes/ownership-assignments.ts:13-24,43,251,288,304. Asserting principal and timestamp
stamped :58-60. "Governance overrides — they do NOT suppress source-state findings" :39-41.
§6 arrival. Push endpoint src/api/routes/ingest.ts:240; chunked flow
src/api/routes/ingest-chunked.ts:150,211,274 (init → chunk… → commit). Body limits 200 MB on
/api/v1/ingest, 5 MB elsewhere src/api/app.ts:115-116 — but the binding limit is the durable
job cap, MAX_PAYLOAD_BYTES = 14 * 1024 * 1024
(src/storage/mongo/adapters/worker-job-adapter.ts:35, thrown :233). Both transports funnel
through acceptGraph (declared src/ingestion/transport/ingest-service.ts:343), which enqueues the
assembled graph as ONE sync_ingestion job payload — the single enqueueAsync call at :437,
carrying payload.graph — so chunking does NOT raise the ceiling. The
spec says so in §6; an earlier revision implied the 200 MB body limit was the real constraint and was
wrong.
Response codes: 202 accepted / 200 duplicate ingest.ts:321; 409 admission rejection
:294-296; 429 + Retry-After :302-303, constant BACKPRESSURE_RETRY_AFTER_S imported :12.
The previous spec revision said a snapshot over the ceiling "fails after the upload is
accepted". #1926/#1928 added an explicit PayloadTooLargeError branch — ingest.ts:309-311 on
the single-shot route and ingest-chunked.ts:405 on commit — so the breach now surfaces
synchronously as 413 with the actual and maximum byte sizes. Rationale, in
src/api/routes/ingest-errors.ts:8-15: before #1926 neither route caught it, so it fell to the
generic 500 handler and read to a connector as a transient failure worth retrying. It is not
transient. §6 of the spec was updated accordingly.
The publisher CAN read its own publish record. API_KEY_ALLOWED_PATH_PREFIXES = ["/api/v1/ingest", "/api/v1/syncs"] — src/api/middleware/bearer-token-middleware.ts:147, widened
by #1189 / #1190 on 2026-05-19, rationale in the header comment :129-146. The guard that consults
it is :406-415. Tenant scoping is structural, not a filter the caller can influence: the middleware
resolves the tenant from the key record (:369-378) and stamps req.tenantId = tenant.slug in
attachMachineContext (:670, assignment :705); both syncs routes read that value and nothing from
the request body or headers — src/api/routes/syncs.ts:9-15 (list) and :29-32 (single, via
getConnectorSync(tenantId, req.params.id)).
Probed live 2026-08-01 against the local stack (API :3100, tenant demo-w1) with a freshly minted
tenant-bound connector key (scripts/ci-mint-connector-key.ts, whose own header at :12-14 already
documents the /ingest + /syncs scope correctly):
200 GET /api/v1/syncs/onprem-corp-ad-p1-… full record incl. metrics.ingest_diagnostics
200 GET /api/v1/syncs own-tenant list
403 GET /api/v1/entities INSUFFICIENT_SCOPE ← positive control
The positive control is what makes the two 200s mean something: the key is genuinely scope-limited,
and /syncs is genuinely inside the limit. The 403 body carried the stale string verbatim.
This is what backs §4 of the spec, and it is also why the consequential claim that trailed the old 403 warning — that a publisher-visible status endpoint gates operating publishing without us — is deleted rather than reworded. The endpoint exists; that reasoning had no premise. The remaining argument for an assisted first integration is §3.1's, which is independent: the publisher owns membership flattening, bundle flattening and delegation modelling, and none of the three fails loudly.
Scheduler. Configurable tick (default 30 s), 50-scope claim cap, per-scope concurrency cap,
post-failure cooldown, and the explicit no-backfill rule ("connectors are full-state extractors and
replaying is meaningless") src/workers/scheduler.ts:6-46. Read-only invariant scheduler.ts:41-42.
Compliance check on the customer-facing page
Re-checked after the 2026-08-01 revision.
- No vendor, company, product, customer or real identifier appears in the body — every example is
fictional (
CORP.EXAMPLE.INTERNAL,BU-04,grp-payroll-admins,svc-payroll). The §4.1 worked example is described as "a seven-payload on-premises estate published into one of our demonstration tenants"; the underlying publishes are allcorp-*fixtures in a demo tenant, so no real estate, customer or product is identifiable from it, and no internal tenant slug or publish id is quoted. - Vendor-specific correlation rules are still described generically, as key shapes.
- No connector count is stated as a proof point anywhere.
- Continuity is never claimed: the word appears exactly once, in §6, as the explicit disclaimer "we do not claim continuous coverage", followed by the cadence.
- No time estimates, sizing, dates or roadmap timing. The shipped items were promoted to
[Today]with no reference to when they shipped, and the one open item — the disposition of the two §1.8 fields — is stated as tracked, with no projected date. - The anchored-publish mechanism is never called correlation. §2.1 says declared foreign key, not an inference, and explicitly separates it from the cross-system key matching in §2, which is the mechanism that may be called correlation. §3.3's "cross-system correlation matches" refers to that separate stitching mechanism and is correct as written.
- No
[Target]marker survives on the customer page, and every[Today]is proven above. The page no longer promises anything it does not do: the §1.8 fields are stated as not accepted and the reader is told not to send them, and the §1.3 wildcard-scope behaviour is[Today]behind a per-tenant flag that is off by default and is labelled as such. - Admonitions use Docusaurus container syntax (
:::note/:::tip/:::info/:::warning/:::danger), not MkDocs!!!. This site is Docusaurus;!!!blocks do not render as callouts — they print as literal text, and where the body was indented four spaces they degrade into code blocks. Verified against the built HTML, not against the markdown.