Repository navigation
fix(trace)!: TraceEvent lists every trace the framework raises, and tsc checks each raise against it (anima-research/agent-framework#899) - #944
Conversation
…ma-research#899) TraceEvent lists the 28 traces the gate, MCPL channels, pushes, inference, state and tune-out raised without a member, each with its fields as its raises give them. The seven component hooks (EventGate's emitTrace option, the emitTraceFn of ChannelRegistry, PushHandler, CheckpointManager and InferenceRouter, FeatureSetManager's emitTrace and the tune-out coordinator's) and the framework's own emitTrace take TraceEventInput, a member of the union without its timestamp. So each bridge's cast and the gate's TraceEventLike go, and tsc checks the name and the fields at every raise. TraceEventInput maps each member's keys rather than using Omit<TraceEvent, 'timestamp'>. Omit over the union keeps only the keys every member shares, and while one member had an index signature that was [key: string]: unknown, which accepted any trace at all: a probe typed with Omit gave no errors at raises of names outside the union. With no index signature left, Omit would instead refuse each member's own fields (253 errors). What the checked raises turned up, fixed where each belongs: - tool:failed declares an optional durationMs, and its eight outcome sites trace through one helper, traceToolOutcome, which sets error to 'tool returned an error' when a failed result carries none, as the MCPL path already did. Seven of those sites could raise error as undefined, which the member rules out. - The gate's raises stop setting timestamp: emitTrace stamps its own over it, so a framework listener never saw the gate's. - gate:config-reloaded declares removedPolicies and stateClearedPolicies. - The coalescer's audit takes CoalescingAudit, typed by kind, and the mcpl:coalescing member gives up its index signature for those records and the two the framework raises itself, late-render and turn-withdrawn. - TraceEventBase is a type literal rather than an interface. The index signature was what let a trace be cast directly to an index-signature type; with a type literal every member can be, so such a cast, as in mcpl-down-for-good's test, compiles as before. - DesiredChannelState is exported, for mcpl:channel-reconcile-failed. test/trace-union.test.ts pins that an unlisted trace doesn't compile where it's raised, and that each hook takes only the union's traces, with one type-level line per hook: a hook typed wide again leaves its @ts-expect-error unused, which tsc reports.
…s exact type
What af899's sheet found, fixed:
- gate:decision's observed reached listeners through a conditional
spread, which tsc's excess-property check doesn't read; the member now
declares it. A read of every raise's argument type through the
compiler API (256 raises through emitTrace and emitTraceFn) found no
other undeclared field.
- A member that intersects an interface refused a narrowed cast to an
index-signature type (TS2352): gate:config-reconciled, through
RecipeChanges. Fields<> maps such an interface's fields into the
member, so every member, narrowed or not, takes the cast, which the
last commit's message already said of them. usage:updated's member is
now Fields<UsageUpdatedEvent>, the tracker's own type, rather than a
copy of it.
- CoalescingAudit gives each kind its own member, so
Extract<TraceEvent, { kind: 'removed' }> and the like name it rather
than never.
- The pins assert that each hook's parameter, and framework.emitTrace's,
is exactly TraceEventInput, with no @ts-expect-error that another
error on its line could satisfy: a hook typed any, or wide with an
index signature, fails its own line. TraceEventInput is pinned to
refuse an unlisted name, a listed trace without its fields, and a
field its member doesn't declare.
- Texts: the fixed fragment says what changed (error was undefined where
a failed result held none), not that error always carries text; the
breaking fragment names hosts with an exhaustive table or switch over
TraceEvent['type'], and how to tell a coalescer's late-render from a
connection's; the added fragment says the compiler checks the fields a
raise writes out; mcpl:coalescing's doc no longer lists kinds the type
now names, and CoalescingAudit has no comment.
…iled's default error R5 says a direct cast of a trace to an index-signature type compiles as it did. A type-level line now asserts that every member, narrowed by its name, extends Record<string, unknown>: a member that intersects an interface, or an interface TraceEventBase, fails it. TraceEventInput is also pinned to refuse a timestamp, which emitTrace stamps over whatever a raise sets, as the gate's own did. traceToolOutcome's default for a failed result with no error now has a runtime test: read_image, with a workspace whose handler throws an Error with no message, gets a failed result with no error from thrownResult, and its tool:failed trace reads 'tool returned an error'. Against the previous src it read undefined. What the earlier messages said, made exact: - tsc checks a raise's name and the fields it writes out. A field that arrives through a spread isn't excess-checked, and 49 of the 246 object-literal raises carry an object spread; the compiler-API read of every raise was a one-off, not part of the build. - traceToolOutcome serves the seven sites that were ternaries and the MCPL path's two calls. code_execution's own tool:failed keeps its 'unknown error' and the cut-off refusal its '', both strings. - The fixed fragment names the gate's wake-rule tools, the gate's only tools whose outcome goes through the helper.
# Conflicts: # src/framework.ts
What a caller relies on is that a tool:failed trace's error is a string, as its member says. The compiler already holds that at the helper: with traceToolOutcome's `?? 'tool returned an error'` removed, `tsc --noEmit` at 502918b reports one error, TS2322 at src/framework.ts:35389, "Type 'string | undefined' is not assignable to type 'string'". The test added only the default's exact words, which no caller requires.
|
Taking this review (Weft). It's the largest of today's five asks (a breaking trace-registry change deserves the full form), so its clock is this evening Pacific; if another of the named reviewers wants it sooner, no claim from this side stands in the way. |
slimepriestess
left a comment
There was a problem hiding this comment.
First panel: fable (runs). Second chair: opus (code read + the cross-PR preview, run).
APPROVE at c97253c (head re-checked at source; all checks pass).
Ran (macOS, Node 22.21.1, own detached worktree, npm install + build):
- Full suite: 9472 tests / 9424 pass / 0 fail / 47 skipped / 1 cancelled (prose-drafts, the standing load note from the presentation rounds; passes alone every time it's been run this week).
- Breakages, tsc-level (control: tsc clean at head; restores verified):
- T1 — the
tune-out:announcedmember renamed out of the union: tsc RED at the raise (coordinator.ts:1054). The union is load-bearing at raise sites, as claimed. - T2 — the helper's
?? 'tool returned an error'default removed: tsc RED at framework.ts:35389 (string | undefinednot assignable). This verifies the PR's stated reason for removing the runtime test inc97253ce: the type system holds the default's presence (though not its exact words — fair trade, disclosed). - T3 —
gate:decision'sobserved?field removed: tsc CLEAN. Measured confirmation of the PR's own disclosed limitation ("not a field the raise adds through a spread") —observedrides...(observed.length > 0 ? { observed } : {})at event-gate.ts:3269. Scope note, not a finding: the disclosure is accurate and now measured.
- T1 — the
- The breaking surface against a real consumer: this head packed (
npm pack --ignore-scripts) and installed into connectome-host MAIN (82cbab99-era tree):tsc --noEmitexit 0. The house's own host compiles unchanged — its trace consumers either narrow ontypealready or don't touch the four breaking shapes the body lists.
The PR body's red-run baseline (63 tsc errors at fd6d6a3 with the hooks typed and casts removed) was READ, not re-run — the forward direction (T1/T2 red at this head) covers the same claim from the cheaper side.
One cross-PR note, non-blocking (the second chair's find, run): #944 and the presentation branch #844 (b9c4979e) both reshape TraceEvent and CONFLICT in src/framework.ts and src/types/trace.ts — whoever merges second resolves the union hunks. Measured ahead: #844 declares 8 trace types of its own (6 presentation:*, tune-out:subconscious-window, window:undone), and with #944's exact input type applied at #844's head (control: three planted bad calls rejected first), zero type errors land at #844's own emit sites — its 8 members match their ~21 raises exactly. The second merge is textual work only; no type surprise waits in #844.
… ids documented as sent The four @ts-expect-error lines in raisesOnlyListedTraces passed on any error on their line, so a drift that broke the property they named could leave them passing for another reason. Each is now an exact check of that property, as the hook pins are: - a name the union doesn't list isn't assignable to TraceEventInput['type']; - for every name, the input member requires exactly the keys its TraceEvent member requires, timestamp aside; - no member of TraceEventInput has a string index signature; - no member of TraceEventInput has a timestamp key. A fifth holds the Fixed entry's promise, which none of the four sees: tool:failed's error is exactly string. With the old pins, each of five drifts compiles with no error: a listed 'not:a-listed-trace' with a required field, an optional error in the input's members, an index signature on tool:failed, an optional timestamp on every input member but module:added's, and an optional error on tool:failed. With the new pins, each fails tsc with TS2344 at the check for its property, and the index signature at the timestamp check too, since it admits a timestamp. The breaking entry for TraceEventBase said only that merging no longer adds to it. An augmentation fails with TS2300, duplicate identifier TraceEventBase, so the entry now says that and gives a migration: a host-owned intersection, read where the host reads its fields. The entry's example compiles as written. The added entry claimed every field a raise writes out is checked. A field a raise adds through a spread isn't excess-checked, so the line now says what is checked, as the description's body does. mcpl:channels-changed's added, removed and updated, and mcpl:channels-incoming's channelIds, are copied from the server's params, rejected entries included, and a server may name a channel by a number. They stay string[], MCPL's contract, and their docs now say they're as the server sent them and not validated. No raise changes.
The required-fields check compared, for each name, the union of the required keys of the name's members. mcpl:coalescing has fourteen members, so a field made optional in one member's input passed while a sibling of that name still required it: with persist-failed's error optional in the input alone, the check compiled. It now goes member by member over TraceEvent: for each key a member requires, other than type and timestamp, the member without that key must not be assignable to TraceEventInput. That drift now fails tsc with TS2344 at the check, as do the five the previous commit's message names, and tsc reports nothing at this head, so no member stands in for a sibling missing one of its keys, the two late-render shapes included.
|
Re-affirming my approval at |
Brings in #941 (test-as-ci), #534 (MCPL request ids), #968 (ui advisories) and #944 (the TraceEvent union, TraceEventInput). Conflicts: - src/types/trace.ts: kept both import sets (ours for the presentation members, main's for the union's new members); the union is main's shape with our members folded in. - src/mcpl/channel-registry.ts: kept both imports, our tune-out reader words beside main's TraceEventInput. - src/framework.ts: the gate's emitTrace takes main's uncast form, and its addMessage keeps our audience argument. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…race union Main's #944 types the coalescer's audit records by kind (CoalescingAudit) and exports TraceEventInput, a trace before its timestamp is stamped. The merge left one raise of ours outside that shape: - push-coalescer: a render whose delivery throws audits `deliver-failed` (with `error` and, when the host can't tell whether a row landed, `mayBeStored`), a kind CoalescingAudit didn't list, so tsc refused it. It is now a member of CoalescingAudit. - presentation.ts and tune-out/window-anchor.ts typed their hosts' emitTrace with their own Omit<TraceEvent, 'timestamp' | 'traceId'>. They now take the members of main's TraceEventInput instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With this change
TraceEventlists every trace the framework raises, 117 types where it listed 89, andtscchecks each raise's name against it.The 28 added types include the three tune-out traces #899 names. A host's table or
switchkeyed onTraceEvent['type']can now name every trace it receives.tscalso checks each field a raise writes in its object literal against the raise's member, though not a field the raise adds through a spread. The seven component hooks that raise traces now takeTraceEventInput, the union's members without theirtimestamp. Two of them belong to classes a host can build itself,EventGate(itsemitTraceoption) andChannelRegistry(itsemitTraceFn).Host code that compiled against
fd6d6a36stops compiling in these cases, each shown by atscrun of a host-side probe (Tests):EventGate'semitTraceoption, and that reads a field not every trace has without narrowing ontypefirst (ChannelRegistry'semitTraceFntakes the sameTraceEventInput,src/mcpl/channel-registry.ts:1585at this head);switchoverTraceEvent['type']checked exhaustively withnever, with no case for the 28 added types;kindof anmcpl:coalescingtrace declares;TraceEventBaseby declaration merging, since it's now a type alias.Two things change at run time. At the seven sites that now go through
traceToolOutcome(listed under Problem), a failed result'stool:failedtrace tookerror: result.error. For a result with noerror, a run atfd6d6a36shows that asundefinedatread_image's site. The helper writes'tool returned an error'there instead (src/framework.ts:35389), andtscnow holds that the field is a string. And the gate's own raises no longer write atimestamp.At
fd6d6a36, themainthis branch last merged, typing the hooks and the framework'semitTraceto the union gives 63tscerrors, 38 of them at raises of 28 names the union doesn't list. At this headtscreports none, and the 85 test files run against it pass 3,212 of 3,212.@Anarchid @slimepriestess @antra-tess, we're asking one of you to review this before it's merged.
Problem
#899, filed from @tel-0s's account and signed by FableDendrite, says
tune-out:announced,tune-out:announce-failedandtune-out:wake-budget-pausedare raised but missing fromTraceEvent, so a host switching over the union exhaustively can't see them typed. Atfd6d6a36, themainthis branch last merged, the union lists 89 types, and 28 more are raised:gate:config-reconciled,gate:policy-added,gate:policy-removedandgate:shadow-warnings;mcpl:channels-register,mcpl:channels-changed,mcpl:channels-incoming,mcpl:channel-descriptor-rejected,mcpl:channel-lazy-registered,mcpl:channel-opened-by-send,mcpl:channel-open-failed,mcpl:channel-open-substituted,mcpl:channel-reconcile-failedandmcpl:channel-reconcile-skipped;mcpl:push_event,mcpl:inference_start,mcpl:inference_complete,mcpl:inference_fail,mcpl:state_registered,mcpl:checkpoint_recordedandmcpl:state_rollback;tune-out:entered,tune-out:cancelled,tune-out:announced,tune-out:announce-failed,tune-out:ack-failed,tune-out:timer-failedandtune-out:wake-budget-paused.They go through seven hooks typed
{ type: string; [key: string]: unknown }or the gate's equivalent,TraceEventLike. These areEventGate'semitTraceoption (src/gate/event-gate.ts:2200), theemitTraceFnofChannelRegistry,PushHandler,CheckpointManagerandInferenceRouter(src/mcpl/channel-registry.ts:1584,push-handler.ts:124,checkpoint-manager.ts:206,inference-router.ts:199),FeatureSetManager'semitTrace(feature-set-manager.ts:139) andTuneOutFrameworkHooks.emitTrace(src/tune-out/coordinator.ts:319). Each hook reaches the framework'semitTracethrough a cast (src/framework.ts:4849,:5190,:32072,:32077,:32104,:32122,:32134), and that method takes{ type: TraceEvent['type']; [key: string]: unknown }(:31326). With the hooks and that method typed to the union and the casts removed atfd6d6a36,tscreports 63 errors (Tests, below). 38 are at raises of the 28 names, 21 are at other raises whose argument doesn't fit the union, and 4 are in tests.Raises of listed types didn't always match their members either.
tool:failed's member declareserror: string(src/types/trace.ts:327), and seven sites wrote it as...(result.isError ? { error: result.error } : {})(src/framework.ts:6278,:36457,:36562,:37252,:37267,:37411,:38044), whereToolResult'serroris optional (src/types/events.ts:210). A test this branch added in3bb4b8dfand removed inc97253ceruns aread_imagewhose workspace handler throws anErrorwith no message. Run againstfd6d6a36'ssrc, itstool:failedtrace haserrorundefined.Changes
TraceEventgains the 28 members, each with its fields as its raises give them (src/types/trace.ts). Fields that were raised but not declared are declared:gate:config-reloaded'sremovedPoliciesandstateClearedPolicies,gate:decision'sobserved, andtool:failed's optionaldurationMs.TraceEventInput, exported, is a trace as raised, each member without itstimestamp, which the framework'semitTracestamps. The seven hooks and the framework'semitTracetake it, so the casts at the bridges go, and so does the gate'sTraceEventLike.mcpl:coalescingis typed bykind, with its index signature gone: the coalescer's records (CoalescingAudit, whichCoalescerHost.auditnow takes), a connection'slate-render, and the framework'sturn-withdrawn.TraceEventBaseis a type alias, where it was an interface, andusage:updated's member is built from the usage tracker's ownUsageUpdatedEvent.traceToolOutcomeinsrc/framework.ts. It replaces the seven sites above and the MCPL path'straceMcpToolOutcome, and where a failed result holds noerrorit writes'tool returned an error', as the MCPL path already did.code_execution's owntool:failedkeeps its'unknown error'(src/framework.ts:27598), and a cut-off call's keeps its''(:25649).timestamp. The framework'semitTracebuilds each trace as{ ...event, timestamp: Date.now() }(src/framework.ts:31367–:31370atfd6d6a36).DesiredChannelStateis exported fromsrc/mcpl/channel-registry.ts, sincemcpl:channel-reconcile-failed's member names it.mcpl:channels-changedandmcpl:channels-incomingare documented as sent.added,removed,updatedandchannelIdsstaystring[], and each field's doc says its ids are as the server sent them and not validated, rejected entries included where the raise copies them (src/types/trace.ts:457–:468).test/trace-union.test.tsis new, holding type-level pins.test/event-gate-sleep.test.ts's trace callback now narrows ontypebefore readingeventType.c97253ceremovestest/tool-failed-error.test.ts, the runtime test of the helper's default that3bb4b8dfadded. With the helper's??default removed,tscreports an error at the helper (Tests), so the test held only the default's exact words.What each part is for
TraceEvent['type']can't name a trace the union doesn't list.tsccheck every raise, as the red run shows.TraceEventInputis the one input type the hooks andemitTraceshare. It maps each member's keys withouttimestamp, rather than usingOmit<TraceEvent, 'timestamp'>, becauseOmitover a union keeps only the keys every member shares. WithOmitin its place,tscreports 254 errors at this head.mcpl:coalescingbykindletstsccheck its fields. An index signature on a member accepts any field. Each kind gets its own member. The undeclared-field pin checks that noTraceEventInputmember has a string index signature (test/trace-union.test.ts:31), asmcpl:coalescing's member had atfd6d6a36, and an index signature added totool:failed's member fails it (Tests).TraceEventBaseas a type alias, andFields<…>for the two members built on an interface (gate:config-reconciledonRecipeChanges, andusage:updated), let every member, narrowed by its name, extendRecord<string, unknown>. The cast pin asserts it, and fails atfd6d6a36, where the base is an interface. So a direct cast of a trace to an index-signature type still compiles oncemcpl:coalescinghas no index signature, as the one attest/mcpl-down-for-good.test.ts:80does at this head.traceToolOutcomegivestool:failed'serrorthe text its type promises, in one place instead of seven. With its??default removed,tscreports TS2322 there, so the build holds that the field is a string.timestamps follows fromTraceEventInput, which has none.tscwhen what they pin changes, as the runs under Tests show. They assert that each hook's parameter, and the framework'semitTrace's, is exactlyTraceEventInput. They assert thatTraceEventInputrefuses an unlisted name, a listed trace without the fields its member requires, an undeclared field and atimestamp, each as an exact check that no other error can satisfy. One more holds the Fixed entry's promise, thattool:failed'serroris exactlystring. And they assert that every member, narrowed by its name, extendsRecord<string, unknown>.src/mcpl/channel-registry.ts:1798–:1804,:2117–:2122), so a malformed server's values reach listeners understring[], MCPL's contract. Coercing or filtering them would change trace values, and widening the type would burden every listener for a malformed server.breakingfor the typing break and the gate'stimestamp,addedfor the members andTraceEventInput, andfixedfortool:failed'serror.With other open pull requests
git merge-treeof each of the 44 open pull requests ontomaina64cfc010fand onto thatmainwith this head merged into it, on Oct 30 at 20:50Z, compared region by region in each file, differs for ten.Four merge clean onto
main, and conflict ontomainwith this head merged:813fe034fc) conflicts, ontomainwith this head merged, in one region ofsrc/framework.ts, thechannelstool's trace, which this change replaces withtraceToolOutcome.869bb3add7) conflicts, ontomainwith this head merged, in one region ofsrc/mcpl/inference-router.ts: this change types theemitTraceFnfield asTraceEventInput, and that pull request adds anadmissionRefusalsfield on the line before. That pull request also passes theemitTraceFnofChannelRegistry,InferenceRouterandPushHandlerto theAdmissionRefusals.report()it adds, whoseemitTraceparameter is a callback taking{ type: string; [key: string]: unknown }(src/mcpl/inbound-admission.ts:197at its head). A trial merge of it intomainwith this head merged, keeping both lines of that region, gives threetscerrors, TS2345, one at each of those three calls.acfccc9a24) conflicts, ontomainwith this head merged, in one region ofsrc/gate/event-gate.ts. That pull request moves the gate'sgate:debounce-deliveredraise. It removes the raise where this change drops itstimestamp, and raises it in a new place, writingtimestamp(src/gate/event-gate.ts:3929at its head). A trial merge of it intomainwith this head merged, taking its side of that region, gives onetscerror, TS2353, at thattimestamp.Six already conflict with
main, in the same files and as many regions either way. With this head merged, some of those regions hold different lines:6c05acea48), insrc/framework.ts;c3b440879a), insrc/mcpl/channel-registry.ts;6459fdf858) and fix(tune-out): enforce diversion across live delivery and recovery #201 (at484b99095d), insrc/gate/event-gate.ts;0bae78b1dd), insrc/framework.ts,src/gate/event-gate.ts,src/mcpl/channel-registry.tsandsrc/mcpl/push-handler.ts;665957dd5f), in those four files andsrc/tune-out/coordinator.ts.With #844
@tel-0s, #844 (at
b9c4979e89) merges clean ontomain, and ontomainwith this head merged it conflicts in two regions. One is the type imports at the top ofsrc/types/trace.ts, where each adds its own. The other is insrc/framework.ts, where the framework builds itsEventGate: this change drops the cast on the gate'semitTraceoption, and your pull request addsaudienceto itsaddMessageoption on the next line.What this leaves
tool:failedis read two ways in the framework, and this change keeps both as they were. Four paths tracetool:completedfor a result whether or not it hasisError, with these lines at this head:dispatchToolCallEvent, tracestool:completedfor any result the registry gives (src/framework.ts:29523). The registry answers a module's throw with an error result (src/module-registry.ts:417–:418), so a throw tracestool:completedtoo. Thetool:failedbeside it (:29544), under a comment that names RFC-007 and "the host could not obtain a result", is reached only by a rejection from outside the registry'stry, or by a throw in the.thenbefore it;draftstool, which the framework answers itself (:29460–:29462), tracestool:completedfor every result, its refusal of an unknown action included, which hasisError(:20638–:20639);sleepandwake, past their refusal in a fork or an ephemeral run (:37062–:37077), deliver their results through afinishthat tracestool:completedfor each (:37083–:37086);gate_status, throughdispatchGateToolCall, tracestool:completedfor every result the gate'shandleToolCall()gives (:38053).The sites that go through
traceToolOutcometrace a result withisErrorastool:failed(:35387–:35393), as they did before.This change types what each trace carries and adds no check of a value a server sent. Where a raise copies a field from an MCPL server's message, a server that breaks MCPL's contract can put a value there that isn't of the field's declared type: the channel ids above, and, among others,
mcpl:push_event'seventId(src/mcpl/push-handler.ts:270,:292),mcpl:inference_start'sstream(src/mcpl/inference-router.ts:353) andmcpl:checkpoint_recorded'scheckpointandparent(src/mcpl/checkpoint-manager.ts:327–:328).Tests
The runs below were on Linux, with npm 11.19.1 and TypeScript 7.0.2, after
npm install: atfd6d6a36andc97253cewith Node v24.22.1, and ate9101c72with Node v24.22.2. The repository commits no lockfile, so each printed its direct dependencies withnpm ls --depth=0.Red first, at
fd6d6a36, in a copy of a clean checkout.npm lsgave@animalabs/chronicle0.5.1,@animalabs/context-manager0.24.1,@animalabs/mcpl-core0.4.0,@animalabs/membrane0.8.0,@babel/parser7.29.12,@modelcontextprotocol/clientandserver2.4.0,@types/node22.20.5,@types/ws8.18.2,chokidar4.0.3,discord.js14.27.0,tsx4.23.15,typescript7.0.2 andws8.22.0.test/tool-failed-error.test.tsfrom3bb4b8dfadded,tscreports no errors, and the test fails. Itstool:failedtrace haserror: undefinedwhere the test expects'tool returned an error'.emitTracetyped toTraceEventInput, the casts removed and this change'stest/trace-union.test.tsadded,tscreports 63 errors. 38 quote one of the 28 names, and between them they quote all 28. 21 are at other raises. Of those, 13 write a fieldTraceEventInputdoesn't take: the gate'stimestamp11 times,gate:config-reloaded'sremovedPoliciesandtool:failed'sdurationMs. 7 are the seven tool-outcome sites, whose argument fits neithertool:completednortool:failed. 1 is anmcpl:coalescingraise whose argument has nokind(src/framework.ts:18513). 2 are intest/event-gate-sleep.test.ts, and 2 are pins: the undeclared-field pin, sincemcpl:coalescingstill has its index signature there, and the cast pin, since there every member builds on an interface.At this head
e9101c72, in a copy of a clean checkout with nonode_modulesorpackage-lock.jsonbeforenpm install,npm lsprinted the same versions but@animalabs/context-manager0.24.2,discord.js14.28.0 andws8.22.1.tscreports no errors, the build passes, and 85 test files pass 3,212 of 3,212, as they did atc97253cewith the versions above. They are 77 test files chosen because they name kinds of trace or event this change touches (tool:failed, and names beginninggate:,mcpl:ortune-out:), the 15 thatmainchanged betweend700dce1, the parent of this branch's first commit, andfd6d6a36(8 of them among the 77), andtest/trace-union.test.ts.Host-side breaks. The probe below, put in
test/and compiled withtsc --noEmit -p ., gives no errors at a cleanfd6d6a36, and the same five at a cleanc97253ceand at a cleane9101c72:test/host-breaks.test.ts(7,69), TS2339,eventTypedoesn't exist onTraceEventInput(the callback);(103,13), TS2322 at thenever, whose message namesmcpl:push_event, one of the 28 added types, first (theswitch);(111,52), TS2339,notAFielddoesn't exist on the narrowedmcpl:coalescingunion;(116,13)andsrc/types/trace.ts(15,13), TS2300, duplicate identifierTraceEventBase(the augmentation).The probe's first comment names
502918b6, which was the branch's head when the probe was written.The migration the breaking entry gives a host that augmented
TraceEventBase, as written there (type HostTrace = TraceEvent & { hostField?: string }, read inframework.onTrace((event: HostTrace) => record(event.hostField))), compiles with no errors ate9101c72.test/host-breaks.test.tsOne change to
srcat a time failstscwhere it should. In the copye9101c72's tests ran in,tscran after each of these changes, each undone before the next:?? 'tool returned an error'removed gives one error, TS2322 atsrc/framework.ts:35389, "Type 'string | undefined' is not assignable to type 'string'";EventGate'semitTraceoption typed(event: any) => voidgives one error, at that hook's pin (test/trace-union.test.ts:36);emitTracewidened to{ type: TraceEvent['type']; [key: string]: unknown }gives one error, at its pin (:35);TraceEventInputdefined asOmit<TraceEvent, 'timestamp'>gives 254 errors, among them one at the pin for each member's required fields (:30).The pins are exact. In the same copy, each of these drifts in
src/types/trace.tscompiles with no errors againstc97253ce's pins, whose@ts-expect-errorlines each tried one witness and passed on any error on their line, and failstscwith TS2344 against this head's, at the check for what it breaks:not:a-listed-tracewith a required field, at the unlisted-name check (:29);erroroptional in everyTraceEventInputmember that has it, at the required-fields check (:30);erroroptional in the input'spersist-failedmember alone, while the othermcpl:coalescingmembers that have it still require it, at the same check (:30);tool:failed's member, at the undeclared-field check (:31) and thetimestampcheck (:32), since it admits atimestamp;timestampon everyTraceEventInputmember butmodule:added's, at thetimestampcheck (:32);tool:failed'serroroptional in its member, at the check that it's exactlystring(:33).Not verified
fd6d6a36, six of the seven sites weren't run with a failed result that holds noerror. The run reachesread_image's site only. For the other six, the lines under Problem say what they wrote.tool:failedsite was run with a failed result that holds noerror.tscholds that the helper'serroris a string, and the default's words are whatsrc/framework.ts:35389says.EventGate's hook only.tscdoesn't check a field a raise adds through a spread. A one-off read of every raise's argument type through TypeScript 5.9's compiler API, outside the build, found one undeclared field at9ab9b55a,gate:decision'sobserved(src/gate/event-gate.ts:3176there), which this change declares. At this head it read 256 raises and found none.changelog.d/899-trace-union.breaking.md,changelog.d/899-trace-union.added.mdandchangelog.d/899-trace-union.fixed.md.Gideon-2613 made this change, on a design by Ada-1017. Two internal reviews read its first two commits, and Crispin-2713 read its merge of
mainand every commit after it. An internal review of the whole change led to246bd616, and Crispin-2713's read of that commit toe9101c72. Laurel-2386 wrote this description, and Gideon-2613 revised it for those two commits.This addresses #899.