You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
0.0.7 normative scope amendment — 2026-08-13. RFC 0001 Stage 5 reverses the historical
implementation proposal below. Do not ship traceContextContribution() and do not move final
trace injection out of the transport. Prove instead that the transport creates the client span and
remains the sole final author of trace headers, contributor declarations of reserved trace keys are
rejected, retries/reconnects preserve correct span topology, and composed contributions cannot
double-inject or overwrite traceparent/tracestate.
Summary
A contribution seam validated by one consumer encodes that consumer's shape. Auth (#1352) is
credential-shaped; without a structurally different second consumer, RFC-A's claim that the chain is
general is unproven. Trace-context propagation is the right second consumer because it is already
hard-coded inside createHttpClientLink behind a boolean, with a per-call override field and
existing regression coverage — so re-expressing it as a contribution is a migration, not an
addition, and the negative test therefore has teeth: remove the contribution and traceparent must
disappear from the wire. It also removes the framework's last private fast lane through the link, so
NetScript's own default path goes through the public composition path rather than beside it.
Evidence
Corpus: research/external/orpc.md §1.5, §5 (the "header/trace/tenant contribution from a plugin"
row), §7 item 1; research/repo-audit/services-sdk.md §1.3, §2.3 (losing the client span is the
documented cost of the current escape hatch), §3.1.
Source at baseline fac9e339042c (re-verified for this draft):
packages/sdk/src/client/http-client-link.ts:82-101 — the headers callback: Content-Type,
then traceparent/tracestate from options.context.traceHeaders if present, else from getTraceHeaders(). Gated by the propagateTraceContext boolean.
packages/sdk/src/client/service-client.ts:41-49,55-64 — propagateTraceContext = true default,
threaded into the link along with getTraceHeaders.
packages/sdk/src/ports/service-client.ts:149-155 — ServiceClientContext.traceHeaders is the
per-call override, i.e. this concern already exercises the exact "typed per-call context" axis
the envelope claims to generalise.
packages/sdk/src/client/http-client-link.ts:127+ — the custom fetch opens the CLIENT span
with rpc.system=orpc / server.address attributes; this is the behaviour a hand-rolled client
silently loses today.
packages/sdk/src/presets/define-services.ts:106-116 — propagateTraceContext is forwarded from
L3, so the migration must preserve the L3 surface too.
Rejected alternative, with its own citations: the AI/streams header contribution
(getStreamsAuth() → { Authorization: 'Bearer ' + STREAMS_SECRET }, packages/plugin-streams-core/src/application/stream-url-resolver.ts:136-150, consumed at packages/fresh/src/runtime/streams/create-stream-db.ts:111 and packages/fresh/src/runtime/ai/stream-proxy.ts:162).
Current surface
Trace propagation works and is not a defect — it is a hard-code. Two of its three axes
(header authorship, per-call typed override) are exactly the axes RFC-A's envelope exists to
generalise, and the third (server-side context resolution) is the environment boundary. Meanwhile
the only other credential-shaped header seam in the repo, getStreamsAuth(), was built entirely
outside the typed client with raw fetch, because there was nowhere else to put it.
Target contract
traceContextContribution() ships from @netscript/sdk (or @netscript/telemetry, per the
layering decision recorded in the implementing PR) as an SdkClientContribution declaring headerKeys: ['traceparent', 'tracestate'] and per-call context { trace?: { traceparent?: string; tracestate?: string } | null }.
It is composed by default.createServiceClient prepends it when propagateTraceContext is
not false, so the shipped default behaviour and the L3 defineServices surface are unchanged.
The link body no longer authors trace headers. After this issue, createHttpClientLink contains no traceparent literal; ServiceClientContext.traceHeaders
becomes a @deprecated alias forwarding to the contribution's context field for one minor.
It carries no secret and needs no server-only variant for header authorship, which is
precisely what makes it a generality proof rather than a second credential test.
The CLIENT span stays attached to the framework default chain, so composing extra
contributions cannot silently drop it.
Why this consumer, and not the AI/streams headers
Both candidates were evaluated (RFC-A §9). getStreamsAuth() is attractive because it is a real,
shipped, out-of-band header that today lives outside the typed client. It was rejected as the second consumer for three reasons: it is still a credential (a process-global shared secret),
so it re-tests auth's axis rather than a new one; it is entangled with the streams/SSE transport and
with #1329's envelope work, so a failure would not distinguish "the seam is wrong" from "streams is
wrong"; and it is purely additive, so a decorative seam could pass its test. Trace context fails
loudly if the seam is decorative, because its behaviour already exists and must survive the move. The
streams/AI header contribution remains a good third consumer once the streams envelope settles.
Acceptance
Transport remains the sole final author of traceparent/tracestate; no trace SdkClientContribution or public trace contribution factory is introduced.
Existing default/true/false propagateTraceContext behavior and per-call override behavior are
preserved on the wire.
The CLIENT span retains rpc.system=orpc and server.address when unrelated header/auth/cache
contributions compose.
NEGATIVE: duplicate/reserved header attempts fail deterministically; disabling propagation emits
neither trace header; removing tracing from the transport makes the regression red.
Auth and tracing compose in both descriptor orders without lost credentials or trace headers,
while trace ownership never moves to the contribution layer.
Root check, test, and publish dry-run pass.
Boundaries
Do not migrate getStreamsAuth() onto the chain here — it is the deliberately deferred third
consumer, and it depends on the streams envelope work.
Do not change OTEL span names, attributes, or the tracer identity — telemetry semantics are out
of scope; this issue only moves who authors the headers.
Do not duplicate the observability/runtime-truth work in the T4 pack (saga span call-sites,
child liveness, E2E span assertions).
Docs/consumer proof
The SDK reference documents trace propagation as a contribution — the first place a reader sees the
same mechanism used twice, by two unrelated concerns, which is the whole adoption argument. Consumer
proof is a diff: http-client-link.ts loses its trace block, @netscript/sdk's public surface gains
one small value, and the two-contribution composition example in the docs is the one an app author
copies. If the negative gate ("remove it and the header disappears") cannot be written, the seam is
decorative and RFC-A's ratification should be reconsidered rather than the test weakened.
Provenance
Seed run plan-fable5-remediation-roadmap--seed, PR #1347, 2026-08-08. Consumer choice and its
rejected alternative are recorded in rfcs/RFC-A-sdk-client-composition.md §9; sourced from research/external/orpc.md §5/§7 and research/repo-audit/services-sdk.md §1.3/§2.3, with all cited
lines re-verified against worktree baseline fac9e339042c. No GitHub mutation was performed.
Filed from planning seed PR #1347 · source Draft-ID T1-06 · live issue #1353.
Important
0.0.7 normative scope amendment — 2026-08-13. RFC 0001 Stage 5 reverses the historical
implementation proposal below. Do not ship
traceContextContribution()and do not move finaltrace injection out of the transport. Prove instead that the transport creates the client span and
remains the sole final author of trace headers, contributor declarations of reserved trace keys are
rejected, retries/reconnects preserve correct span topology, and composed contributions cannot
double-inject or overwrite
traceparent/tracestate.Summary
A contribution seam validated by one consumer encodes that consumer's shape. Auth (#1352) is
credential-shaped; without a structurally different second consumer, RFC-A's claim that the chain is
general is unproven. Trace-context propagation is the right second consumer because it is already
hard-coded inside
createHttpClientLinkbehind a boolean, with a per-call override field andexisting regression coverage — so re-expressing it as a contribution is a migration, not an
addition, and the negative test therefore has teeth: remove the contribution and
traceparentmustdisappear from the wire. It also removes the framework's last private fast lane through the link, so
NetScript's own default path goes through the public composition path rather than beside it.
Evidence
research/external/orpc.md§1.5, §5 (the "header/trace/tenant contribution from a plugin"row), §7 item 1;
research/repo-audit/services-sdk.md§1.3, §2.3 (losing the client span is thedocumented cost of the current escape hatch), §3.1.
fac9e339042c(re-verified for this draft):packages/sdk/src/client/http-client-link.ts:82-101— theheaderscallback:Content-Type,then
traceparent/tracestatefromoptions.context.traceHeadersif present, else fromgetTraceHeaders(). Gated by thepropagateTraceContextboolean.packages/sdk/src/client/service-client.ts:41-49,55-64—propagateTraceContext = truedefault,threaded into the link along with
getTraceHeaders.packages/sdk/src/ports/service-client.ts:149-155—ServiceClientContext.traceHeadersis theper-call override, i.e. this concern already exercises the exact "typed per-call context" axis
the envelope claims to generalise.
packages/sdk/src/client/http-client-link.ts:127+— the customfetchopens the CLIENT spanwith
rpc.system=orpc/server.addressattributes; this is the behaviour a hand-rolled clientsilently loses today.
packages/sdk/src/presets/define-services.ts:106-116—propagateTraceContextis forwarded fromL3, so the migration must preserve the L3 surface too.
(
getStreamsAuth()→{ Authorization: 'Bearer ' + STREAMS_SECRET },packages/plugin-streams-core/src/application/stream-url-resolver.ts:136-150, consumed atpackages/fresh/src/runtime/streams/create-stream-db.ts:111andpackages/fresh/src/runtime/ai/stream-proxy.ts:162).Current surface
Trace propagation works and is not a defect — it is a hard-code. Two of its three axes
(header authorship, per-call typed override) are exactly the axes RFC-A's envelope exists to
generalise, and the third (server-side context resolution) is the environment boundary. Meanwhile
the only other credential-shaped header seam in the repo,
getStreamsAuth(), was built entirelyoutside the typed client with raw
fetch, because there was nowhere else to put it.Target contract
traceContextContribution()ships from@netscript/sdk(or@netscript/telemetry, per thelayering decision recorded in the implementing PR) as an
SdkClientContributiondeclaringheaderKeys: ['traceparent', 'tracestate']and per-call context{ trace?: { traceparent?: string; tracestate?: string } | null }.createServiceClientprepends it whenpropagateTraceContextisnot
false, so the shipped default behaviour and the L3defineServicessurface are unchanged.createHttpClientLinkcontains notraceparentliteral;ServiceClientContext.traceHeadersbecomes a
@deprecatedalias forwarding to the contribution's context field for one minor.precisely what makes it a generality proof rather than a second credential test.
contributions cannot silently drop it.
Why this consumer, and not the AI/streams headers
Both candidates were evaluated (RFC-A §9).
getStreamsAuth()is attractive because it is a real,shipped, out-of-band header that today lives outside the typed client. It was rejected as the
second consumer for three reasons: it is still a credential (a process-global shared secret),
so it re-tests auth's axis rather than a new one; it is entangled with the streams/SSE transport and
with #1329's envelope work, so a failure would not distinguish "the seam is wrong" from "streams is
wrong"; and it is purely additive, so a decorative seam could pass its test. Trace context fails
loudly if the seam is decorative, because its behaviour already exists and must survive the move. The
streams/AI header contribution remains a good third consumer once the streams envelope settles.
Acceptance
traceparent/tracestate; no traceSdkClientContributionor public trace contribution factory is introduced.values, or bypass the transport's propagation switch.
propagateTraceContextbehavior and per-call override behavior arepreserved on the wire.
rpc.system=orpcandserver.addresswhen unrelated header/auth/cachecontributions compose.
neither trace header; removing tracing from the transport makes the regression red.
while trace ownership never moves to the contribution layer.
Boundaries
getStreamsAuth()onto the chain here — it is the deliberately deferred thirdconsumer, and it depends on the streams envelope work.
fix(streams): documented SSE consumer shape differs from the wireprotocol and does not specify the standard event/OTEL envelope,
0.0.5, p0) — the streamstelemetry envelope is its scope.
of scope; this issue only moves who authors the headers.
child liveness, E2E span assertions).
Docs/consumer proof
The SDK reference documents trace propagation as a contribution — the first place a reader sees the
same mechanism used twice, by two unrelated concerns, which is the whole adoption argument. Consumer
proof is a diff:
http-client-link.tsloses its trace block,@netscript/sdk's public surface gainsone small value, and the two-contribution composition example in the docs is the one an app author
copies. If the negative gate ("remove it and the header disappears") cannot be written, the seam is
decorative and RFC-A's ratification should be reconsidered rather than the test weakened.
Provenance
Seed run
plan-fable5-remediation-roadmap--seed, PR #1347, 2026-08-08. Consumer choice and itsrejected alternative are recorded in
rfcs/RFC-A-sdk-client-composition.md§9; sourced fromresearch/external/orpc.md§5/§7 andresearch/repo-audit/services-sdk.md§1.3/§2.3, with all citedlines re-verified against worktree baseline
fac9e339042c. No GitHub mutation was performed.Filed from planning seed PR #1347 · source Draft-ID T1-06 · live issue #1353.