Skip to content

vnext: Mission/Task/Transfer/Assessment contracts + evidence binder (#45) - #46

Merged
Thunderkill016 merged 4 commits into
rebuild/vnextfrom
devin/vnext-contracts-001
Sep 28, 2026
Merged

Thunderkill016 merged 4 commits into
rebuild/vnextfrom
devin/vnext-contracts-001

Conversation

@Thunderkill016

Copy link
Copy Markdown
Owner

Summary

Implements VNEXT-CONTRACT-001 — the contract layer that turns the headless engine into a learning system that cannot lie about itself:

  • contracts.js — MissionContract, TaskContract, validateMission, validateTask, validateMissionContent (policy-injected budgets), effectiveAllowedSupport = capability ∩ task.
  • bind.js — bindAttempt/bindObservation: the sole path from UI reality to EvidenceEvent. Caller cannot author purpose, promptFamily, practicedOrTransfer, identity fields, or eventType outside the purpose's allowed set.
  • projection.js — sticky support per attemptId (answer-reveal provenance survives retry inside the same attempt boundary); evaluator-authority gate (deterministic/human only for INDEPENDENT); support-conditions check uses the bound effective policy.
  • fixtures.js — Mission A "meet a new person" (9 phases) and Mission B "order a drink", both as pure domain tasks.
  • tests/vnext-contracts.test.mjs — 11 groups covering all 15 required invariants; existing vnext.test.mjs stays green (11/11).

Test plan

  • npm run verify green (typecheck 102 files + all node suites + build)
  • fixtures validate clean; injected content budgets enforced
  • §9 regression block + new §10 contract block all pass

Closes #45.

Generated with Devin

)

Teaching item ≠ transfer item ≠ assessment item — the engine now
enforces it structurally instead of trusting callers:

- contracts.js: MissionContract/TaskContract + validators. Missions
  check capability existence, prerequisite closure, task ownership,
  an eliciting path per target, and freshness collisions (a
  practiced family can never masquerade as fresh assessment/transfer).
  Tasks enforce purpose semantics: transfer needs declared changed
  dimensions + fresh_transfer family; assessment needs fresh_assessment
  family, zero allowed support, and no mid-attempt answer reveal.
- bind.js: bindAttempt/bindObservation are the only way learner-facing
  code mints EvidenceEvents. capabilityId/taskId/revision/modality/
  missionId/promptFamily/practicedOrTransfer/purpose/evaluation all
  derive from the contract — caller-supplied copies throw. eventType is
  constrained by purpose (an interaction task cannot emit checkpoint).
- effectiveAllowedSupport = capability ∩ task — a task may narrow,
  never broaden; violations are recorded honestly then demoted.
- projection: sticky support per attemptId (union across canonical
  order — a hint revealed mid-attempt permanently taints that attempt),
  evaluator-authority gate (only deterministic/human can award
  INDEPENDENT; self_report/asr/ai_llm cap at SUPPORTED), and the
  support-conditions check now uses the bound effective policy.
- fixtures.js: two complete headless missions — "meet a new person"
  (diagnostic → input → retrieval → supported → feedback → unaided →
  delayed → transfer → assessment) and "order a drink".
- validateMissionContent enforces declared language + injected budgets;
  no universal thresholds hard-coded.

tests/vnext-contracts.test.mjs pins all 15 required invariants.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>

@Thunderkill016 Thunderkill016 left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ARCHITECT REVIEW — NOT READY TO MERGE

Reviewed exact head 89c75b5423297ec56e24a2dd060a28c59d8020ad.
CI verify:full is green.

The structure is good: separate Mission/Task contracts, binder, fixtures, sticky support, content validation, and evaluator provenance are all the right direction. But #45 exists specifically to make evidence semantics non-forgeable, and there are still several bypasses.

1. Evaluation authority can still be forged by the caller

bindEvaluation() does:

const auth = raw?.evaluation?.authority ?? task.evaluation?.authority

So a task authored as asr or ai_llm can be submitted by UI/runtime as human, and projection will then allow INDEPENDENT credit.

Authority is contract semantics, not observed UI data.

Required:

  • derive authority from task.evaluation.authority;
  • caller may report evaluator/version/raw result, but may not escalate/change authority;
  • if dynamic authorities are ever needed, the Task contract must explicitly declare an allowed set;
  • add regression: ASR task + caller reports human => throw / still ASR, never INDEPENDENT.

Also validate eliciting tasks have a real evaluation contract: supported authority + non-empty contractId. A default deterministic + null contractId must not silently award independent evidence.

2. Assessment is silently conflated with transfer

derivedContext() maps every non-practiced family class to:

practicedOrTransfer: 'transfer'

Therefore fresh_assessment checkpoint events enter projection as transfer context and can populate transferPromptFamilies / earn TRANSFERRED.

The tests currently assert this behavior.

That violates the #45 goal:

Teaching != Transfer != Assessment

Required:

  • represent assessment distinctly (e.g. context kind assessment, or gate transfer milestone strictly on binding.purpose === 'transfer');
  • fresh assessment may remain independent assessment evidence without automatically becoming transfer evidence;
  • add regression: learner with no transfer success + fresh assessment success must NOT become TRANSFERRED.

3. The binder is not actually mandatory

#45 says every attempt must be validated against registered Task + Capability before projection.

But projectLearnerState() still credits unbound makeEvent() attempts when binding === null. Existing vNext tests still rely on that route.

That leaves a complete bypass around:

  • task purpose;
  • prompt family provenance;
  • task revision binding;
  • effective support policy;
  • evaluator contract;
  • transfer/assessment freshness.

Required:

  • bound attempt evidence should be mandatory for state advancement in the vNext contract engine;
  • unbound events may be retained for migration/debug/exposure if needed, but must not award INDEPENDENT/RETAINED/TRANSFERRED;
  • migrate tests to bound fixtures or explicitly test that an unbound success receives no capability credit.

4. Mission validation accepts undeclared extra tasks as evidence paths

validateMission() checks that every mission.taskIds resolves, but later uses the entire supplied tasks array for:

  • evidence-path coverage;
  • freshness checks.

An extra task belonging to the mission but omitted from mission.taskIds can therefore satisfy “target has an eliciting task” or affect freshness validation while not being declared in the Mission.

Required:

  • mission's declared task set must be authoritative;
  • reject mission-owned tasks passed to validation that are not in taskIds, or perform all semantic checks only over the resolved declared task set;
  • add regression: undeclared extra eliciting task cannot satisfy target evidence coverage.

5. Content validation currently lets target capability language bypass mission declaration

validateMissionContent() adds the language of every declared capability — including TARGET capabilities — directly into the known sets.

That means a Mission may omit a target's language from introduced / assumedKnown, yet tasks using that language still pass because the capability itself whitelisted it.

This defeats the content-load contract.

Required:

  • target-capability language requirements must be accounted for by mission declaration (introduced or genuinely assumed/prerequisite-known);
  • do not auto-whitelist target language simply because the capability references it;
  • prerequisite/support capability language may be handled according to an explicit policy;
  • add regression where a target phrase omitted from mission language fails validation.

Related: assumedKnown is currently a flat list added to chunks, vocabulary AND constructions. Make it typed/categorized so one string cannot satisfy every language bucket.

6. Sticky support boundary should be task-scoped, not only attemptId-scoped

supportByAttempt is keyed only by attemptId.

If the same id is accidentally reused across two tasks, support from task A contaminates task B. This is conservative rather than an over-credit bug, but it violates the attempt-boundary model and can make replay state wrong.

Required:

  • key sticky support by at least taskId@taskRevision + attemptId (learner is already scoped by projection);
  • add reuse regression.

Accepted parts

These are good and should stay:

  • task-purpose → event-type constraints;
  • support intersection (Task may narrow, never broaden Capability);
  • sticky answer-reveal idea;
  • transfer changedDimensions validation;
  • exact-family anti-leakage;
  • configurable content budgets;
  • FLUENT remains unreachable;
  • learner isolation + deterministic replay remain intact.

Do not start UI or pilot yet. Fix these contract bypasses, add regressions, run exact-head CI, then re-review.

- evaluation.authority is contract-derived; a caller reporting a
  different authority is a binding error, never an upgrade path
- fresh_assessment binds its own 'assessment' context kind; only
  'transfer' context can feed TRANSFERRED, and assessed families are
  still rehearsed (cannot be re-sold as novel transfer)
- projection only credits contract-bound attempts: binding.purpose must
  exist and agree with eventType, so raw makeEvent() successes cap at
  SUPPORTED
- validateMission scopes evidence-path/freshness checks to taskIds and
  flags tasks that claim the mission without being declared
- validateMissionContent drops the capability-language whitelist:
  mission language must be declared in introduced/assumedKnown, and
  assumedKnown is structured {chunks,vocabulary,constructions}
- attempt-boundary support history keys on taskId::attemptId, so a
  reused attemptId cannot leak support across tasks

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@Thunderkill016

Copy link
Copy Markdown
Owner Author

Round 2 — six blockers closed on d7855e3. CI green on exact head: push 36429327736, pull_request 36429335835 (verify:full pass).

# Blocker Fix Regression pin
1 evaluation.authority caller-forgeable bindEvaluation derives authority from the task contract only; a caller-reported authority that differs is a binding error — identity/version may still be reported, never strength (bind.js:55) §7 + §12a: human on a deterministic/asr task throws; an honestly-tagged asr success still caps at SUPPORTED
2 fresh_assessment mapped to transfer CONTEXT_KINDS gains 'assessment' (evidence.js:28); binder maps family→context via CONTEXT_FOR_FAMILY (bind.js:42); projection feeds TRANSFERRED only on 'transfer' context — assessed families are rehearsed, so they can't be re-sold as novel transfer either (projection.js:176, :219) §9 + §12b: successful checkpoint leaves milestones.transferred === false, transferPromptFamilies excludes the assessment family
3 unbound makeEvent() advanced state boundWell() gate: independent credit requires binding.purpose present and consistent with eventType via EVENT_TYPES_FOR_PURPOSE (projection.js:102) §12c: raw makeEvent success stops at SUPPORTED, milestones.independent === false
4 extra tasks outside taskIds counted validateMission flags missionId-claiming tasks absent from taskIds as integrity violations and scopes evidence-path + freshness-collision checks to declared tasks (contracts.js:251-287) §12d: ghost task flagged; stripped taskIds surfaces "no eliciting task" instead of being satisfied by ghosts
5 capability language auto-whitelisted; flat assumedKnown smeared into all buckets validateMissionContent checks against introduced + structured assumedKnown only; a flat assumedKnown list is itself a problem; ghost tasks still cannot smuggle undeclared language (contracts.js:300) §12e: capability-declared 'Good morning' fails as undeclared; flat assumedKnown rejected; §8 budgets still enforced
6 sticky support keyed by attemptId only boundary key is taskId::attemptId — same id on another task starts a clean boundary (projection.js:189) §12f: shared.id hinted on task A does not contaminate task B; §6 still pins same-task stickiness

Asymmetry note: validateMission treats taskIds as authoritative (ghosts cannot satisfy requirements), while validateMissionContent still checks every task that claims the mission (ghosts cannot violate language policy silently) — conservative in both directions.

vnext.test.mjs 11/11, vnext-contracts.test.mjs 12/12. Core suite events now stamp the same binding provenance the binder produces, so "unbound event advances state" is pinned as impossible at the projection boundary, not just convention.

@Thunderkill016
Thunderkill016 marked this pull request as ready for review September 28, 2026 13:35

@Thunderkill016 Thunderkill016 left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ARCHITECT RE-REVIEW — TWO BLOCKERS REMAIN

Reviewed exact head d7855e346f9290aff6277dfdaec6194127a66850.
Exact-head PR CI is green (Verify FlashDay run 36429335835).

The six round-2 fixes are mostly correct. In particular:

  • task authority can no longer be escalated through bindAttempt;
  • assessment is now distinct from transfer;
  • ghost tasks no longer satisfy Mission evidence-path checks;
  • Mission language declaration is authoritative and typed;
  • sticky support is task-scoped;
  • raw events with no binding no longer earn INDEPENDENT.

However, the central “binder is the only path” invariant is still bypassable, and one evaluator-contract requirement from the previous review is still missing.

1. A forged binding can still bypass bindAttempt

makeEvent() accepts an arbitrary binding object.

projectLearnerState() currently considers an event contract-bound when:

e.binding != null &&
typeof e.binding.purpose === 'string' &&
(EVENT_TYPES_FOR_PURPOSE[e.binding.purpose] ?? []).includes(e.eventType)

That checks only shape/compatibility. It does NOT prove the binding came from the registered Task contract.

So code can construct an event directly with:

  • fake binding.purpose: 'interaction';
  • fake binding.effectiveSupportAllowed;
  • evaluation.authority: 'deterministic';
  • arbitrary prompt/context;

and still satisfy boundWell() without ever calling bindAttempt().

The updated core tests actually demonstrate that binding is manually stampable, which means this remains convention rather than an enforced contract.

#45 explicitly requires:

Every attempt event must be validated against the registered Task + Capability before projection.

Required fix:

  • projection must validate attempt evidence against a Task registry/catalog (or a trusted pre-validation layer whose result cannot be authored via public event fields);
  • re-check at least task id, task revision, capability id, modality, purpose/eventType, promptFamily/context kind, evaluation authority/contract, and effective support policy against the registered Task+Capability;
  • an arbitrary binding payload alone must never be sufficient for credit;
  • add regression: direct makeEvent() with a syntactically valid forged binding still cannot earn INDEPENDENT.

A clean architecture would be something like:

projectLearnerState(learnerId, events, capabilities, tasks, ...)

with contract validation during replay, or a separate validator that produces trusted internal evidence records not constructible through makeEvent.

2. Eliciting tasks still allow an empty evaluator contract

The previous review explicitly required:

validate eliciting tasks have a real evaluation contract: supported authority + non-empty contractId. A default deterministic + null contractId must not silently award independent evidence.

But makeTask() still defaults to:

evaluation: { authority: 'deterministic', contractId: null }

and validateTask() does not reject an eliciting task with contractId:null.

That means a newly-authored retrieval/production/interaction task can still get deterministic authority without specifying WHAT deterministic evaluator actually measured.

Required:

  • for every eliciting purpose, require a supported evaluation.authority;
  • require non-empty evaluation.contractId;
  • exposure-only input/notice tasks may omit an evaluator if desired;
  • add regression that makeTask({ purpose:'interaction', ... }) without a real evaluation contract fails.

Accepted round-2 fixes

The original six blockers themselves are fixed correctly once evidence is assumed to be genuinely task-bound:

  1. authority override through binder — fixed;
  2. assessment != transfer — fixed;
  3. truly unbound event — capped;
  4. ghost tasks — fixed;
  5. content declaration — fixed;
  6. task-scoped sticky support — fixed.

Do not start UI/pilot yet. Close these last two trust-boundary issues, run exact-head CI, then this contract layer should be ready to merge.

@Thunderkill016
Thunderkill016 marked this pull request as draft September 28, 2026 14:20
…ontracts

- projectLearnerState now takes the registered task list and re-derives
  every semantic field from it: taskId+revision, capabilityId, modality,
  purpose↔eventType, promptFamily, context kind, evaluation
  authority+contractId, and the recomputed effective support policy.
  A stamped binding is data, not proof — a raw makeEvent() carrying a
  forged binding can never verify and caps at SUPPORTED.
- Eliciting purposes require a real evaluation.contractId at
  construction (makeTask) AND at verification, so authority:
  deterministic + contractId: null can never mint independent evidence.
- planner passes the task registry through to the projection.
- tests: contract-test registry + core suite registers synthetic
  contracts mirroring binder output; new §13 pins forged-binding and
  missing-contractId cases.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@Thunderkill016

Copy link
Copy Markdown
Owner Author

Round 3 — two trust-boundary blockers closed on 805422d. CI green on exact head: push 36441957546, pull_request 36441963072 (verify:full pass).

7. Forged binding can no longer bypass the binder.
projectLearnerState(learnerId, events, capabilities, tasks, opts) now takes the registered task list as the trust boundary. verifyEventTask (contracts.js:157) re-derives every semantic field from the registry task instead of trusting the stamp:

  • taskId + taskRevision — exact contract version
  • capabilityId, modality — task↔capability coherence
  • purpose ↔ eventType — via EVENT_TYPES_FOR_PURPOSE of the registry task
  • promptFamily, context.missionId, context.practicedOrTransfer — re-derived from CONTEXT_FOR_FAMILY[task.freshness.familyClass]
  • evaluation.authority + evaluation.contractId — identical to the task's declared evaluator contract
  • binding.purpose/familyClass/freshnessRequired + effectiveSupportAllowed recomputed via effectiveAllowedSupport(capability, task) — the stamped list is compared, never used

An unverifiable event is still recorded honestly (EXPOSED/SUPPORTED) but can never mint INDEPENDENT — §13a pins a forged transfer binding on a real task and an authority-inflated interaction_turn: both stop at SUPPORTED, no transfer credit. Note the residual boundary: an event byte-identical to binder output is indistinguishable from it at projection time — the registry prevents claiming semantics the contract does not contain; true non-repudiation would need signing, out of scope for headless v0.

8. Eliciting tasks require a real evaluator contract.
validateTask now requires a valid EVALUATION_AUTHORITIES value AND a non-empty evaluation.contractId for every eliciting purpose — authority: deterministic, contractId: null fails at makeTask (contracts.js:160). verifyEventTask repeats the check at projection time, so even a hand-rolled registry object without a contract id cannot award evidence — §13b pins both.

Call sites updated: planNext accepts tasks in opts and threads it to the projection; contract tests keep a REGISTRY of real fixture + synthetic tasks; the core suite's ev() registers a synthetic contract per claimed semantic signature so every attempt event verifies the same way binder output does.

vnext.test.mjs 11/11, vnext-contracts.test.mjs 13/13, npm run verify green.

@Thunderkill016 Thunderkill016 left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ARCHITECT RE-REVIEW — TWO REGISTRY BLOCKERS REMAIN

Reviewed exact head 805422d0cf06ecadd5113518ae6bd48ba4afc251.
Exact-head CI is green (Verify FlashDay run 36441963072, including npm run verify:full).

The two previous trust-boundary blockers are fixed correctly:

  • event semantics are re-derived against a task registry instead of trusting the stamped binding;
  • eliciting tasks require a non-empty evaluator contractId.

However, the task registry itself still has two replay/trust flaws.

1. Registry lookup is not revision-aware

Projection builds:

const taskById = new Map(tasks.map((t) => [t.id, t]));

and only afterwards checks event.taskRevision === task.revision.

But Task identity in the contract is stable id + revision, and EvidenceEvent persists taskRevision.

Current behavior means:

  • if the registry contains task v1 and v2 with the same id, one silently overwrites the other in the Map;
  • if content is upgraded to v2 and the registry only keeps the current revision, historical v1 evidence stops verifying;
  • adding a new task revision can therefore change/reduce a learner's replayed historical state.

That breaks append-only deterministic replay.

Required:

  • resolve registry tasks by taskId + taskRevision, e.g. ${id}@${revision};
  • allow multiple historical revisions of the same stable task id;
  • reject duplicate entries for the same exact id@revision;
  • add regression: a valid v1 event still verifies after v2 is added to the registry, and v1/v2 events each resolve their exact contract.

Do not “migrate” old evidence to the newest task contract.

2. A hand-rolled invalid registry task can still become trusted

verifyEventTask() checks many individual fields and now checks contractId, but it never requires the registry Task itself to satisfy the full validateTask() contract.

Example: a hand-built task can declare:

  • purpose: 'transfer';
  • freshness: fresh_transfer;
  • valid deterministic evaluator contract;
  • but omit/empty transfer.changedDimensions.

An event matching that object can pass verifyEventTask() and earn transfer even though makeTask()/validateTask() would reject the TaskContract.

Same class of problem applies to invalid assessment/support-policy shapes.

Required:

  • an event may earn state only when its resolved registry task passes validateTask(task) (or an equivalent complete registry validator);
  • preferably validate the registry once at projection/catalog construction rather than partially re-checking ad hoc;
  • add regression: a hand-rolled invalid transfer task with a valid evaluator contract cannot mint INDEPENDENT/TRANSFERRED;
  • likewise pin duplicate id@revision registry entries as invalid.

Accepted on 805422d

  • forged binding no longer overrides registered semantics;
  • authority + evaluator contract are re-derived;
  • assessment != transfer;
  • effective support policy is recomputed;
  • caller cannot inflate evaluator authority;
  • eliciting contractId:null is rejected;
  • task-scoped support provenance remains intact.

CI being green is not enough here because the missing cases are not currently in the suite.

Close these two registry invariants, run exact-head CI, and #46 should then be ready to merge.

- The registry is keyed taskId@taskRevision so a v2 contract cannot
  overwrite v1 — historical evidence keeps verifying under the contract
  that produced it and replay stays stable. Duplicate id@revision
  registrations are rejected outright.
- verifyEventTask now requires the resolved task to pass validateTask()
  first — a hand-rolled invalid contract (e.g. purpose 'transfer' with
  zero changedDimensions) mints nothing, however closely an event's
  fields happen to match it.
- tests: revision coexistence, duplicate rejection, invalid-transfer
  regressions; the core suite's synthetic registry now meets the same
  purpose-specific requirements validateTask enforces.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@Thunderkill016

Copy link
Copy Markdown
Owner Author

Round 4 — two registry-integrity blockers closed on 574c5bd. CI green on exact head: push 36444157617, pull_request 36444164237 (verify:full pass).

9. Registry keyed by taskId@taskRevision. The projection builds taskByRev keyed on id@revision (projection.js:147) — a v2 contract can no longer overwrite or shadow v1, so historical evidence keeps verifying under the contract version that produced it and replay stays stable. Duplicate id@revision registrations throw (duplicate task registration) — an integrity violation, not last-write-wins.

10. Resolved task must itself be a valid contract. verifyEventTask now gates on validateTask(task).length === 0 before any field check (contracts.js:143) — a hand-rolled invalid contract (e.g. purpose: 'transfer' with zero changedDimensions) is never trusted, however closely an event's fields match it.

Regression pins added in §14:

  • v1-bound event still verifies after v2 registers under the same task id (both verify on their own revision — replay stable across contract bumps);
  • projectLearnerState throws on duplicate id@revision;
  • invalid hand-rolled transfer task + perfectly-shaped forged event → SUPPORTED max, independent/transferred both false.

Because verification now runs validateTask on registry entries, the core suite's synthetic registry tasks meet the same purpose-specific requirements (changedDimensions for transfer tasks, capabilitySample/answerRevealDuringAttempt for assessment) — test fixtures comply with the contract, they don't bypass it.

vnext.test.mjs 11/11, vnext-contracts.test.mjs 14/14, npm run verify green.

@Thunderkill016 Thunderkill016 left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ARCHITECT ACCEPTED — FINAL

Reviewed exact head 574c5bd03c802b9bcb73fe9eaf63dd52a21fda6b.

The final registry invariants are now correct:

  • task lookup is revision-aware via taskId@taskRevision, so v1/v2 contracts can coexist and historical evidence is replayed against the exact contract that produced it;
  • duplicate id@revision registrations fail closed instead of last-write-wins;
  • verifyEventTask() refuses any registry task that fails the full validateTask() contract;
  • invalid hand-rolled transfer contracts cannot mint INDEPENDENT or TRANSFERRED.

Previously accepted trust/evidence invariants remain intact: forged bindings cannot override registered semantics, evaluator authority/contract are re-derived, assessment != transfer, content declarations are explicit, support provenance is sticky and task-scoped, and FLUENT remains unreachable in v0.

Exact-head CI is green (run 36444164237, including npm run verify:full).

Accepted for merge into rebuild/vnext.

@Thunderkill016
Thunderkill016 marked this pull request as ready for review September 28, 2026 15:43
@Thunderkill016
Thunderkill016 merged commit 31d79d9 into rebuild/vnext Sep 28, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant