@vostride/agent-qa-mcp 0.2.3 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -47,13 +47,11 @@ __export(index_exports, {
47
47
  AppResourceReadError: () => AppResourceReadError,
48
48
  AppResourceUriError: () => AppResourceUriError,
49
49
  DEGRADED_OUTPUT_CONTRACTS: () => DEGRADED_OUTPUT_CONTRACTS,
50
- JOB_STATUS_TO_MCP_TASK_STATE: () => JOB_STATUS_TO_MCP_TASK_STATE,
51
50
  LIVE_LINK_ABSENT_LINE: () => LIVE_LINK_ABSENT_LINE,
52
51
  LIVE_LINK_NO_RUN_LINE: () => LIVE_LINK_NO_RUN_LINE,
53
52
  MCP_APPS_EXTENSION_ID: () => MCP_APPS_EXTENSION_ID,
54
53
  McpProjectionError: () => McpProjectionError,
55
54
  UNPROJECTED_OPS: () => UNPROJECTED_OPS,
56
- V1_TOOL_MIGRATION_MAP: () => V1_TOOL_MIGRATION_MAP,
57
55
  advertisedContracts: () => advertisedContracts,
58
56
  appResourceUriFor: () => appResourceUriFor,
59
57
  armWithoutDiscriminant: () => armWithoutDiscriminant,
@@ -68,7 +66,6 @@ __export(index_exports, {
68
66
  maskedToolResult: () => maskedToolResult,
69
67
  negotiatedAppsExtension: () => negotiatedAppsExtension,
70
68
  parseAppUri: () => parseAppUri,
71
- projectJobAsTask: () => projectJobAsTask,
72
69
  registerAppResources: () => registerAppResources,
73
70
  renderAppWrapperPage: () => renderAppWrapperPage,
74
71
  stripRedundantObjectStamp: () => stripRedundantObjectStamp,
@@ -208,9 +205,7 @@ ${live && !expired ? '<iframe data-app-live-frame sandbox="allow-scripts" referr
208
205
  var message = document.querySelector('[data-app-message]');
209
206
  var hint = document.querySelector('[data-app-hint]');
210
207
 
211
- // WHICH RUN, as an attribute rather than as prose. Identity is not run STATE (D-11 forbids the
212
- // second, not the first), and it enters through setAttribute from an escaped literal, so a run
213
- // id can never become markup no matter what it spells.
208
+ // The run id enters through setAttribute from an escaped literal, so it never becomes markup.
214
209
  root.setAttribute('data-app-run-id', runId);
215
210
 
216
211
  // The state machine is an ATTRIBUTE plus two textContent writes, exactly as the embed page's is:
@@ -224,25 +219,7 @@ ${live && !expired ? '<iframe data-app-live-frame sandbox="allow-scripts" referr
224
219
 
225
220
  // ---- The minimal ui/initialize handshake (ext-apps spec shape) -------------------------------
226
221
  //
227
- // HAND-ROLLED AND DELIBERATELY ISOLATED IN THIS ONE BLOCK. The ext-apps package
228
- // declares a peer on the v1 monolith SDK, so importing its app-bridge into production would drag
229
- // a second SDK generation in (Pitfall 3) \u2014 and this page has no bundler anyway. RESEARCH
230
- // assumption A5 rated the method-name and field spellings MEDIUM (single spec fetch), and plan
231
- // 02's coverage entry D9 deferred the adjudication to plan 06.
232
- //
233
- // PLAN 06 MEASURED IT, AND IT HAD DRIFTED. Against the installed
234
- // @modelcontextprotocol/ext-apps@1.7.5's own generated schema, McpUiInitializeRequest.params
235
- // requires THREE members: protocolVersion, appInfo and appCapabilities. This block sent only the
236
- // first, so a host validating the request against the published schema would have rejected the
237
- // handshake outright, and the failure would have looked like a host that simply does not support
238
- // Apps. The two missing members are now sent, and ext-apps-constants.test.ts cell (4d) derives
239
- // the required list FROM the shipped schema rather than restating it, so the next spec change
240
- // reds here instead of on a user's machine.
241
- //
242
- // IT STILL ASKS FOR NOTHING. appCapabilities is deliberately empty: this document exposes no
243
- // tools, requests no display mode and declares no experimental feature (the schema requires no
244
- // member of it). No permissions, no re-mint (D-10) \u2014 it announces that this document is ready,
245
- // which is all a wrapper that renders no run state of its own has to say.
222
+ // Hand-rolled: ext-apps' app-bridge peers on the v1 SDK, and this page has no bundler.
246
223
  try {
247
224
  if (window.parent !== window) {
248
225
  window.parent.postMessage({
@@ -259,20 +236,13 @@ ${live && !expired ? '<iframe data-app-live-frame sandbox="allow-scripts" referr
259
236
  } catch (e) { /* a host that refuses the message changes nothing about what this page shows */ }
260
237
 
261
238
  if (mints.length === 0) {
262
- // Either D-07's honest terminal/unknown state or D-10's expired one \u2014 both were decided by the
263
- // RENDERER and are already in the markup. No src was ever assigned, so there is no blank frame
264
- // to mistake for a stalled one, and nothing below this line can move the document off them.
239
+ // The renderer already wrote the terminal or expired state, and no src was ever assigned.
265
240
  return;
266
241
  }
267
242
 
268
- // ---- Reachability, once per ORIGIN (see the header's measured note) ---------------------------
243
+ // ---- Reachability, once per ORIGIN ------------------------------------------------------------
269
244
  //
270
- // Memoised deliberately, and ONLY ON SUCCESS. The picker swaps among panes that share one
271
- // origin, so after the first ANSWERING pane every swap is served from this map and issues NO
272
- // network request at all (D-06/D-10's zero re-mints, zero calls, which the DOM cell counts) \u2014 a
273
- // property that rides entirely on caching the true. The catch arm therefore FORGETS the entry
274
- // before answering false: a refusal is a fact about one moment, not about the origin, and every
275
- // mint on a render shares one origin, so a cached false was total and permanent. See the header.
245
+ // Only success is cached: every pane shares one origin, so a cached refusal would be permanent.
276
246
  var reach = {};
277
247
  function reachable(url) {
278
248
  var origin;
@@ -288,13 +258,7 @@ ${live && !expired ? '<iframe data-app-live-frame sandbox="allow-scripts" referr
288
258
  var attachId = 0;
289
259
 
290
260
  function attach(mint) {
291
- // THE EXPIRED STATE IS TERMINAL, AND THIS IS THE ONE PLACE IT COULD BE LEFT (D-10). The
292
- // picker's change handler \u2014 and nothing else \u2014 can reach this function after the one-shot
293
- // expiry timer has fired; without this line the next statements would set the connecting state
294
- // and assign an aged token over a document that had already told the truth, and no timer would
295
- // ever correct it. The identical predicate inside settle() stays where it is: it closes the
296
- // OTHER door, a probe or a frame load that was already in flight when the timer fired, so
297
- // neither guard makes the other dead.
261
+ // Expired is terminal; a picker change after the one-shot expiry timer must not leave it.
298
262
  if (root.getAttribute('data-app-state') === 'expired') return;
299
263
  var mine = (attachId += 1);
300
264
  var loaded = false;
@@ -313,7 +277,7 @@ ${live && !expired ? '<iframe data-app-live-frame sandbox="allow-scripts" referr
313
277
 
314
278
  frame.addEventListener('error', function () { settle('unreachable-origin'); }, { once: true });
315
279
  frame.addEventListener('load', function () {
316
- // NECESSARY, NOT SUFFICIENT: Chromium fires this for a refused connection too (header note),
280
+ // NECESSARY, NOT SUFFICIENT: Chromium fires this for a refused connection too,
317
281
  // so the probe is what decides \u2014 a frame that loaded on an origin nothing answered for is an
318
282
  // error page, and calling that 'live' is the failure this arm exists to prevent.
319
283
  loaded = true;
@@ -325,14 +289,11 @@ ${live && !expired ? '<iframe data-app-live-frame sandbox="allow-scripts" referr
325
289
  else if (loaded) settle('live');
326
290
  });
327
291
 
328
- // D-11's REQUIRED arm for the origin that never answers at all: a black-holed host fires no
329
- // error and never loads, so silence past this bound is itself the signal, named rather than
330
- // left as a spinner.
292
+ // A black-holed origin fires no error and never loads; silence past this bound is the signal.
331
293
  setTimeout(function () { settle('unreachable-origin'); }, loadTimeoutMs);
332
294
  }
333
295
 
334
- // D-10: the deadline was computed at RENDER time and arrives as a delta, so the host's own clock
335
- // never enters the comparison. When it fires the document says so and stops \u2014 nothing here mints.
296
+ // The deadline arrives as a delta from render time, so the host's own clock never enters it.
336
297
  if (expiresInMs > 0) {
337
298
  setTimeout(function () {
338
299
  attachId += 1;
@@ -348,8 +309,7 @@ ${live && !expired ? '<iframe data-app-live-frame sandbox="allow-scripts" referr
348
309
  }, expiresInMs);
349
310
  }
350
311
 
351
- // D-06/D-10: the picker swaps among tokens ALREADY minted for this render. It never mints, and
352
- // there is no bridge here that could ask anyone to \u2014 a fresh watch call is what re-mints.
312
+ // The picker swaps among tokens minted for this render; only a fresh watch call re-mints.
353
313
  for (var i = 0; i < mints.length; i += 1) {
354
314
  var option = document.createElement('option');
355
315
  option.value = String(i);
@@ -534,74 +494,7 @@ function createAppResourceReadHandler(deps) {
534
494
  uri: `${APP_URI_PREFIX}${runId}`,
535
495
  mimeType: APP_RESOURCE_MIME_TYPE,
536
496
  text: html,
537
- // The host's default is `frame-src 'none'`, so an App that names no frame domain paints
538
- // a BLOCKED inner frame — the blank pane criterion 2 forbids (Pitfall 1). The allowed
539
- // origin is the minted link's OWN origin rather than a separately configured one, so the
540
- // allow can never be broader than the document needs (T-408-05). `frameDomain` is the ONE
541
- // distinct origin `frameOrigins` above resolved over EVERY mint (IN-02) — not `mints[0]`'s,
542
- // which was the same answer only by a fact held in another package.
543
- //
544
- // TWO ABSENCES, NOT ONE (corrected 2026-09-01, 408 gap round; `408-VERIFICATION.md` W-4 /
545
- // `408-REVIEW.md` WR-03). This comment used to read "with no mints there is no inner
546
- // frame, and the block is omitted rather than emitted empty", which describes only the
547
- // FIRST of two conditions that both reach `frameDomain === undefined`. They are different
548
- // questions with different served documents, and collapsing them told a reader about a
549
- // state the code does not produce:
550
- // - `mints.length` asks whether there is anything to render at all. Zero mints means
551
- // `runLive` is `false`, the wrapper emits no `<iframe data-app-live-frame>`, and the
552
- // omitted `csp` block allows nothing because nothing needs allowing. Honest.
553
- // - `frameOrigins[0]` is the SEPARATE question of whether any surviving mint yielded a
554
- // parseable origin to put in `_meta.ui.csp`. With `mints.length > 0` and every
555
- // `originOf(mint.liveUrl)` answering `undefined`, `runLive` is `true` — the document
556
- // DOES emit the iframe and DOES assign `frame.src` — while the `csp` block is omitted,
557
- // so a conformant host falls back to `frame-src 'none'` and the pane is blocked and
558
- // blank. Not honest, and the sentence above never mentioned it.
559
- // WHY NO SHIPPED PORT REACHES IT: `runs.liveurl` would have to answer a `liveUrl` that
560
- // is not a parseable URL, and no shipped port does. The verifier measured this as
561
- // unreachable and classified W-4 as a documentation finding rather than a gap; the
562
- // wrapper's probe/timeout arm would in any case settle `unreachable-origin` with the
563
- // permanent link rather than leave a blank pane under a progress claim, so SC2 survives
564
- // even on the unreachable path.
565
- //
566
- // THE INVARIANT IS UNASSERTED, AND THAT IS A DATED DEFERRAL RATHER THAN A SILENCE
567
- // (2026-09-01, 408 gap round). "Every mint that survives into `runLive` has a nameable
568
- // origin" is asserted nowhere today — not here, not in `renderAppWrapperPage`, not in a
569
- // cell. It is the natural candidate and it is NOT taken in this round, because this round
570
- // is a gap-closure round whose whole reported gap is documentation integrity.
571
- // THE RULE THAT MAKES THAT CONSISTENT, because the neighbouring round looks
572
- // contradictory and a reader is entitled to it: THIS ROUND CHANGES WHAT THE SYSTEM
573
- // SAYS, NEVER WHAT IT DOES. 408-16 task 1 does edit a runtime file — `config.ts` — but
574
- // it only adds words to an error that was already being thrown on exactly the inputs it
575
- // was already thrown on; no parse outcome moves in either direction and no input that
576
- // succeeded now fails. Asserting the invariant here would add a NEW failure mode to a
577
- // path that currently succeeds. Operator-facing error prose is in scope because it is
578
- // prose; a new runtime invariant is out of scope because it is behaviour. That is the
579
- // whole distinction, and it is what makes W-2 closable in this round and W-4's
580
- // invariant half not.
581
- //
582
- // AND THE NEW PROSE CENSUS DOES NOT COVER THIS, which is the question the gap brief asks
583
- // by name. `__tests__/withdrawn-claim-census.test.ts` gates ONE withdrawn security claim
584
- // about the token channel, keyed on a channel cue bound to an exclusivity cue; W-4 is a
585
- // different species — a comment that CONFLATES two conditions rather than one that
586
- // over-claims a control — and widening that census to "any comment that conflates two
587
- // conditions" is not mechanizable, and a vocabulary that tried would red on honest prose,
588
- // which this round's own prohibition forbids.
589
- //
590
- // `connectDomains` NAMES THE SAME ONE ORIGIN, AND IT IS NOT OPTIONAL (408 plan 06 Task 2,
591
- // MEASURED 2026-08-31). ext-apps' shipped schema documents `connectDomains` as mapping to
592
- // `connect-src` with "empty or omitted -> no network connections (secure default)", and
593
- // 408-02 D-H gave this wrapper a no-cors probe of the frame's origin — because Chromium
594
- // fires `load` for a REFUSED connection, so `load` alone would label an error page `live`.
595
- // With only `frameDomains` declared, a conformant host serves the App under
596
- // `connect-src 'none'`, the probe is blocked before it leaves the document, and the
597
- // wrapper settles in `unreachable-origin` for a live view that is perfectly reachable —
598
- // measured in `app-host-harness.test.ts`, with Chromium naming the directive in its own
599
- // console. That is not a blank frame, but it is a lie with a sentence attached, which is
600
- // worse: it tells a person to go somewhere else while the pixels are right there.
601
- //
602
- // It is exactly as narrow as the frame allowance — the same single minted origin, never a
603
- // wildcard and never a second configured value — so the App can reach the one host it
604
- // embeds and nothing else.
497
+ // Hosts default `frame-src` and `connect-src` to none; allow the minted origin in both.
605
498
  ...frameDomain === void 0 ? {} : {
606
499
  _meta: {
607
500
  ui: { csp: { frameDomains: [frameDomain], connectDomains: [frameDomain] } }
@@ -637,11 +530,7 @@ function registerAppResources(server, deps) {
637
530
  });
638
531
  server.registerResource(
639
532
  APP_RESOURCE_NAME,
640
- // `list: undefined` is REQUIRED to be spelled out by the SDK, and here the explicit absence is
641
- // load-bearing rather than merely honest: an unlisted template cannot be reached by
642
- // enumeration, so no model walking `resources/list` can discover an address whose read mints a
643
- // credential (Pitfall 2). Enumerating every watchable run is a listing this surface does not
644
- // offer, and this is the line that says so.
533
+ // Unlisted, so no model walking `resources/list` finds an address whose read mints a token.
645
534
  new import_server.ResourceTemplate(APP_RESOURCE_URI_TEMPLATE, { list: void 0 }),
646
535
  {
647
536
  title: APP_RESOURCE_TITLE,
@@ -671,8 +560,7 @@ function compositionOf(projection) {
671
560
  }
672
561
  var AGENT_QA_OP_CONTRACTS = Object.freeze({
673
562
  "runs.mark": Object.freeze({ input: import_agent_qa_schema2.RunsMarkInputSchema, output: import_agent_qa_schema2.RunsMarkOutcomeSchema }),
674
- // 2026-09-15, 423-21 D-18: the feedback reads keep their ops' own strict contracts. The preview
675
- // takes runs.mark's exact input and answers the read-only plan result.
563
+ // The preview takes `runs.mark`'s exact input and answers the read-only plan result.
676
564
  "runs.marks.history": Object.freeze({
677
565
  input: import_agent_qa_schema2.RunsMarksHistoryInputSchema,
678
566
  output: import_agent_qa_schema2.RunsMarksHistoryResultSchema
@@ -722,7 +610,6 @@ var AGENT_QA_OP_CONTRACTS = Object.freeze({
722
610
  input: import_agent_qa_schema2.ConfigProjectScaffoldInputSchema,
723
611
  output: import_agent_qa_schema2.ConfigChangeOutputSchema
724
612
  }),
725
- // 2026-09-30 (432-12 D-05): the three knowledge changes answer what they applied.
726
613
  "knowledge.accept": Object.freeze({
727
614
  input: import_agent_qa_schema2.KnowledgeAcceptChangeInputSchema,
728
615
  output: import_agent_qa_schema2.KnowledgeChangeOutputSchema
@@ -743,8 +630,7 @@ var AGENT_QA_OP_CONTRACTS = Object.freeze({
743
630
  input: import_agent_qa_schema2.EntitiesGetInputSchema,
744
631
  output: import_agent_qa_schema2.EntityCardSchema
745
632
  }),
746
- // 2026-09-28 (431-03, D-03/D-07): the canonical list contract — `RunsListOutputSchema` as the
747
- // validator, the same object `runs.list` enforces with, plus its explicit output document.
633
+ // `RunsListOutputSchema` is the same validator `runs.list` enforces with.
748
634
  "runs.list": Object.freeze({
749
635
  input: import_agent_qa_schema2.RunsListInputSchema,
750
636
  output: import_agent_qa_schema2.RunsListOutputContract
@@ -753,8 +639,7 @@ var AGENT_QA_OP_CONTRACTS = Object.freeze({
753
639
  input: import_agent_qa_schema2.RunsDigestInputSchema,
754
640
  output: import_agent_qa_schema2.RunDigestSchema
755
641
  }),
756
- // 2026-09-28 (431-04 D-04/D-05/D-07): the eight reads keep their ops' own contracts. The
757
- // evidence arms use the canonical output contracts, whose validators are the runtime schemas.
642
+ // The evidence arms use the canonical output contracts, whose validators are the runtime schemas.
758
643
  "knowledge.bundles.list": Object.freeze({
759
644
  input: import_agent_qa_schema2.KnowledgeBundlesListInputSchema,
760
645
  output: import_agent_qa_schema2.KnowledgeBundlesListOutputSchema
@@ -808,11 +693,9 @@ var AGENT_QA_TOOL_PROJECTIONS = Object.freeze([
808
693
  name: "agent_qa_start_run",
809
694
  ops: Object.freeze(["runs.start"]),
810
695
  kind: "workflow",
811
- // 408 D-05: a run starts and is immediately watchable. The ack carries the runId, so the
812
- // per-call reference is derived from the OUTPUT.
696
+ // The ack carries the run id, so the per-call reference comes from the output.
813
697
  ui: Object.freeze({ resource: "live-run", runIdFrom: "output" })
814
698
  }),
815
- // 2026-09-30 (432-12 D-05): the config change tool writes in the call. The undo arm is gone.
816
699
  Object.freeze({
817
700
  name: "agent_qa_change_config",
818
701
  ops: Object.freeze([
@@ -826,8 +709,7 @@ var AGENT_QA_TOOL_PROJECTIONS = Object.freeze([
826
709
  kind: "workflow",
827
710
  discriminant: import_agent_qa_schema2.TOOL_FOLD_DISCRIMINANT
828
711
  }),
829
- // 2026-09-30 (432-12 D-05/D-09): accept, reject and forget apply in the call. Forget is the
830
- // explicit removal; a wrong record is otherwise corrected by new feedback.
712
+ // Forget is the explicit removal; a wrong record is otherwise corrected by new feedback.
831
713
  Object.freeze({
832
714
  name: "agent_qa_change_knowledge",
833
715
  ops: Object.freeze([
@@ -843,9 +725,7 @@ var AGENT_QA_TOOL_PROJECTIONS = Object.freeze([
843
725
  ops: Object.freeze(["runs.mark"]),
844
726
  kind: "apply"
845
727
  }),
846
- // 2026-09-15, 423-21 D-18: marks history and the mark consequence preview beside the mark
847
- // writer. Both are read projections with no apply envelope, so `agent_qa_mark_run` stays the
848
- // only door that lands a mark.
728
+ // Both are reads, so `agent_qa_mark_run` stays the only door that lands a mark.
849
729
  Object.freeze({
850
730
  name: "agent_qa_get_run_marks",
851
731
  ops: Object.freeze(["runs.marks.history"]),
@@ -861,7 +741,7 @@ var AGENT_QA_TOOL_PROJECTIONS = Object.freeze([
861
741
  ops: Object.freeze(["knowledge.gates.list"]),
862
742
  kind: "read"
863
743
  }),
864
- // 2026-09-28, 431 D-04: the knowledge reads, named one-to-one with their CLI verbs.
744
+ // The knowledge reads, named one-to-one with their CLI verbs.
865
745
  Object.freeze({
866
746
  name: "agent_qa_list_knowledge_bundles",
867
747
  ops: Object.freeze(["knowledge.bundles.list"]),
@@ -901,14 +781,10 @@ var AGENT_QA_TOOL_PROJECTIONS = Object.freeze([
901
781
  name: "agent_qa_get_run_digest",
902
782
  ops: Object.freeze(["runs.digest"]),
903
783
  kind: "read",
904
- // 408 D-05/D-07: the watch/status read, and therefore the LATE-ATTACH door — a run started
905
- // from the CLI, from CI or from the dashboard is watchable through it exactly as an
906
- // MCP-started one is, because a viewer attaches and never owns. Its runId is on the INPUT.
784
+ // The late-attach door: a viewer attaches to any run, so the run id comes from the input.
907
785
  ui: Object.freeze({ resource: "live-run", runIdFrom: "input" })
908
786
  }),
909
- // 2026-09-28, 431 D-05/D-06/D-07: one completed run's whole reads. The overview is the map, run
910
- // knowledge the whole used-and-learned projection unchanged, and evidence one addressed document
911
- // or blob. All three carry the result-size hint.
787
+ // A completed run's whole reads, each carrying the result-size hint.
912
788
  Object.freeze({
913
789
  name: "agent_qa_get_run_overview",
914
790
  ops: Object.freeze(["runs.overview"]),
@@ -942,8 +818,7 @@ var AGENT_QA_COMPOSED_CONTRACTS = Object.freeze({
942
818
  input: import_agent_qa_schema2.KnowledgeChangeFoldInputSchema,
943
819
  output: import_agent_qa_schema2.KnowledgeChangeFoldOutputSchema
944
820
  }),
945
- // 2026-09-28 (431-04 D-07): the evidence fold, composed in the schema package from both arms'
946
- // canonical contracts and routed by full op id.
821
+ // Composed in the schema package from both arms' canonical contracts, routed by full op id.
947
822
  agent_qa_get_run_evidence: Object.freeze({
948
823
  input: import_agent_qa_schema2.RunsEvidenceFoldInputSchema,
949
824
  output: import_agent_qa_schema2.RunsEvidenceFoldOutputContract
@@ -960,163 +835,72 @@ function advertisedContracts(projection) {
960
835
  return only === void 0 ? void 0 : AGENT_QA_OP_CONTRACTS[only];
961
836
  }
962
837
  var DEGRADED_OUTPUT_CONTRACTS = Object.freeze([
963
- // 397 DISPOSITION (2026-08-16 · D-20): keep-degraded. `agent_qa_start_run` advertises no
964
- // `outputSchema` and keeps it that way. The cause is a property of the CONTRACT, not of this
965
- // surface — a branded run id is `z.string().transform(asRunId)`, and the converter refuses a
966
- // transform because there is no true JSON-Schema for "and then this function runs". The two ways
967
- // out are both worse: advertising the input-mode document is the second-recipe lie D-16 forbids,
968
- // and un-branding the id to please a converter would spend a type-safety property of the whole
969
- // codebase on one surface's listing. 397 rules the degradation ADJUDICATED and CLOSED rather than
970
- // pending; re-opening it means changing the contract, which is a golden-diff-visible act. The
971
- // absence itself is not re-pinned here — `projection.ts` probes the conversion both ways and the
972
- // shipped projection suite owns that fact (RESEARCH Pitfall 9); the conformance suite asserts
973
- // only the join.
838
+ // The branded run id is a transform; un-branding it would trade type safety for one listing.
974
839
  Object.freeze({
975
840
  tool: "agent_qa_start_run",
976
841
  opId: "runs.start",
977
842
  schema: "RunStartAcknowledgementSchema",
978
- measured: "Transforms cannot be represented in JSON Schema",
979
- adjudication: "397"
843
+ measured: "Transforms cannot be represented in JSON Schema"
980
844
  })
981
- // 2026-09-28 (431-03, D-03/D-07 · research A1): the `agent_qa_list_runs` row is REMOVED, not
982
- // re-ruled. Its objection stands — no hand-written array schema is advertised. What changed is the
983
- // contract: `runs.list` now declares `RunsListOutputContract`, whose validator is the same
984
- // `RunsListOutputSchema` object and whose output document is derived from the runtime schema's
985
- // own output conversion. It converts cleanly, so the probe in `projection.ts` would refuse the row.
986
845
  ]);
987
- var UNPROJECTED_ADJUDICATIONS = Object.freeze(["397", "398", "405", "408", "428"]);
846
+ var NOT_MID_FLIGHT_READ = "not a mid-flight workflow read: this surface carries only the reads a workflow needs while it runs";
847
+ var NOT_MID_FLIGHT_WRITE = "not a mid-flight workflow write: this surface carries only the writes a workflow needs while it runs";
988
848
  var UNPROJECTED_OPS = Object.freeze([
989
849
  Object.freeze({
990
850
  opId: "agent.ask",
991
- reason: "D-03 (dated 2026-08-15): ask-over-MCP is nested-agent delegation \u2014 trust, token-driven LLM spend, lease interaction and streaming are four separate problems and no MCPS requirement asks for any of them. Deferred as its own seam, not omitted by oversight.",
992
- adjudication: "397"
851
+ reason: "Ask over MCP is nested-agent delegation: a tool call would spend this project's model tokens, take the lease the local host holds and stream through a channel a single `tools/call` round trip has no shape for."
993
852
  }),
994
- // 2026-10-01 (432-17, D-05): `proposals.accept` and `proposals.reject` (397, requiresHumanOrigin)
995
- // are deleted with the inbox and leave the scoping note.
996
- // 2026-10-01 (432-18, D-14/D-15): `grants.revoke` (397, requiresHumanOrigin) and `grants.list`
997
- // (397, not a mid-flight read) are deleted with the grant ledger and leave the scoping note.
998
853
  Object.freeze({
999
854
  opId: "runs.get",
1000
- reason: "not a mid-flight workflow read per D-02 \u2014 the entity door renders a run's card and the digest carries what triage needs; 397 adjudicates",
1001
- adjudication: "397"
855
+ reason: "not a mid-flight workflow read: the entity door renders a run's card and the digest carries what triage needs"
1002
856
  }),
1003
857
  Object.freeze({
1004
858
  opId: "runs.events",
1005
- reason: "not a mid-flight workflow read per D-02, and a stream op besides: a `tools/call` is one round trip and has no shape for a resumable event feed; 397 adjudicates",
1006
- adjudication: "397"
859
+ reason: "not a mid-flight workflow read, and a stream besides: a `tools/call` is one round trip and has no shape for a resumable event feed"
1007
860
  }),
1008
861
  Object.freeze({
1009
862
  opId: "runs.postprocessing",
1010
- reason: "not a mid-flight workflow read per D-02; 397 adjudicates",
1011
- adjudication: "397"
863
+ reason: NOT_MID_FLIGHT_READ
1012
864
  }),
1013
865
  Object.freeze({
1014
866
  opId: "runs.detail",
1015
- reason: "D-03 (dated 2026-08-27, 405-01): not a mid-flight workflow read per D-02 \u2014 this is a COMPLETED run's projected step tree, read by the durable run-detail page, and the entity door already renders a run's card while the digest carries what triage needs. The whole projection of a run is also a large one-shot payload with no shape a `tools/call` round trip wants. Deferred as its own seam, not omitted.",
1016
- adjudication: "405"
867
+ reason: "not a mid-flight workflow read: it is a completed run's whole step tree, read by the run-detail page, and a large one-shot payload with no shape a `tools/call` round trip wants"
1017
868
  }),
1018
869
  Object.freeze({
1019
870
  opId: "runs.liveurl",
1020
- reason: "D-03 (dated 2026-08-25, 403-01): the op MINTS A CREDENTIAL \u2014 a short-lived, view-scoped capability token for a running browser. Whether an agent-origin MCP caller may mint one, and what it may hand it to, is Phase 408's question (the MCP App embed); projecting it here would answer that question by accident. Deferred as its own seam, not omitted. ANSWERED (dated 2026-08-31, 408 D-01): the deferral above is discharged, and the ruling is INTERNAL MINT ONLY. The MCP server invokes this op BY REFERENCE, inside its App-rendering path and nowhere else, and the minted token lands only in the host-rendered HTML of the unlisted `ui://agent-qa/live-run/{runId}` App resource \u2014 never in tool-result text a model reads. Projecting a mint tool stays REJECTED: a live credential in model context is the T-395-R5-10 shape, and masking it would leave a tool that hands back nothing a host could use. The op therefore STAYS unprojected BY RULING rather than by deferral \u2014 answered on the record, never silently reclassified. RE-GROUNDED (dated 2026-09-01, 408 gap round, road `demote`; the full ruling is in `app-resource.ts`'s header). The answer above STANDS and one half of its stated property is corrected rather than deleted. Whether the document this server serves is host-rendered HTML or text a model consumes is a property of the CALLER, not of this server, so the \"never in tool-result text a model reads\" half over-claimed. What this server guarantees is narrower and it is verifiable: it writes the token into exactly one place, the frame src of the document the `ui://` read serves, and that door is reachable by any caller of a composition whose mint is bound (dated 2026-10-01, 432-20/432-21, D-12: the scope gate that once also bounded it is deleted for every caller, and the loopback bind and the Host/Origin guards still bound who can reach it). The op STAYS unprojected on exactly the same footing, and the reason is unchanged and STRONGER rather than weaker: a projected mint tool would put the credential on a channel a model consumes BY DEFAULT, on every call, for every caller, whereas the `ui://` read is an unlisted address a caller must already know and deliberately ask for.",
1021
- adjudication: "408"
871
+ reason: "The op MINTS A CREDENTIAL, a short-lived view token for a running browser: the MCP server mints it only inside its App-rendering path and writes it into the frame src of the unlisted `ui://agent-qa/live-run/{runId}` resource, so a mint tool would put the credential on a channel a model reads by default."
1022
872
  }),
1023
873
  Object.freeze({
1024
874
  opId: "editor.liveurl",
1025
- reason: "D-07 (dated 2026-09-25, 428): the op MINTS A CREDENTIAL \u2014 a view credential for a live EDITOR session, which is private to the dashboard page that owns it; it is reachable only through the dashboard door and never through an MCP tool or the MCP App live-run resource. Projecting it would give an agent a watch carrier for a draft execution, which the owner declined in D-07. Unprojected by ruling, not by deferral.",
1026
- adjudication: "428"
875
+ reason: "The op MINTS A CREDENTIAL, a view credential for a live editor session that is private to the dashboard page owning it, so it is reachable only through the dashboard door and never through an MCP tool or the MCP App live-run resource."
1027
876
  }),
1028
877
  Object.freeze({
1029
878
  opId: "config.get",
1030
- reason: "not a mid-flight workflow read per D-02; 397 adjudicates",
1031
- adjudication: "397"
879
+ reason: NOT_MID_FLIGHT_READ
1032
880
  }),
1033
881
  Object.freeze({
1034
882
  opId: "analysis.get",
1035
- reason: "not a mid-flight workflow read per D-02; 397 adjudicates",
1036
- adjudication: "397"
883
+ reason: NOT_MID_FLIGHT_READ
1037
884
  }),
1038
885
  Object.freeze({
1039
886
  opId: "authoring.funnel.get",
1040
- reason: "not a mid-flight workflow read per D-02; 397 adjudicates",
1041
- adjudication: "397"
887
+ reason: NOT_MID_FLIGHT_READ
1042
888
  }),
1043
889
  Object.freeze({
1044
890
  opId: "quarantine.add",
1045
- reason: "not a mid-flight workflow write per D-02; 397 adjudicates",
1046
- adjudication: "397"
891
+ reason: NOT_MID_FLIGHT_WRITE
1047
892
  }),
1048
893
  Object.freeze({
1049
894
  opId: "quarantine.release",
1050
- reason: "not a mid-flight workflow write per D-02; 397 adjudicates",
1051
- adjudication: "397"
895
+ reason: NOT_MID_FLIGHT_WRITE
1052
896
  }),
1053
897
  Object.freeze({
1054
898
  opId: "quarantine.list",
1055
- reason: "not a mid-flight workflow read per D-02; 397 adjudicates",
1056
- adjudication: "397"
899
+ reason: NOT_MID_FLIGHT_READ
1057
900
  }),
1058
- // 398 D-09 (dated 2026-08-17): the twentieth entry, and the FIRST op ever dispositioned here that
1059
- // 397 did not rule on — because it did not exist then. Its exclusion is argued rather than scoped,
1060
- // which is why it does not take the mid-flight line: `agent.explain` answers "what can you do",
1061
- // and 397 already ruled the v1 discovery tool NOT a gap on this surface, because the protocol
1062
- // answers machine discovery itself. Projecting it anyway would be the third answer that ruling
1063
- // refuses.
1064
901
  Object.freeze({
1065
902
  opId: "agent.explain",
1066
- reason: "D-09 (dated 2026-08-17): the protocol answers machine discovery itself \u2014 `tools/list` plus SEP-2575 `server/discover` \u2014 and 398's `init --agent` manifest answers agent discovery from the committed catalog. This op answers a PERSON asking in prose, which is a third question with a third audience. 397 ruled the v1 discovery TOOL not-a-gap on exactly this reasoning; projecting the op would reinstate it under a new name.",
1067
- adjudication: "398"
1068
- })
1069
- // 2026-09-30 (432-12 D-05): the five pre-432 proposal ops went off the catalogue with their
1070
- // tools. 2026-10-01 (432-17 D-05): they are deleted from the registry and leave the scoping note.
1071
- ]);
1072
- var PROJECTED_RULING_ADDENDA = Object.freeze([
1073
- Object.freeze({
1074
- opId: "knowledge.bundles.list",
1075
- ruledBy: "397",
1076
- ruling: "not a mid-flight workflow read per D-02; 397 adjudicates",
1077
- addendum: "D-01/D-04 (dated 2026-09-28, 431-04): projected as `agent_qa_list_knowledge_bundles`. A coding agent in a fresh workspace reads knowledge only through structured channels, so the committed-knowledge reads are served here rather than left to files; this supersedes the 2026-09-14 decision that projected the six safe reads only, whose no-search half stands (D-09)."
1078
- }),
1079
- Object.freeze({
1080
- opId: "knowledge.records.inspect",
1081
- ruledBy: "397",
1082
- ruling: "not a mid-flight workflow read per D-02; 397 adjudicates",
1083
- addendum: "D-01/D-04 (dated 2026-09-28, 431-04): projected as `agent_qa_inspect_knowledge_records`. A coding agent in a fresh workspace reads knowledge only through structured channels, so the committed-knowledge reads are served here rather than left to files; this supersedes the 2026-09-14 decision that projected the six safe reads only, whose no-search half stands (D-09)."
1084
- }),
1085
- Object.freeze({
1086
- opId: "knowledge.records.diff",
1087
- ruledBy: "397",
1088
- ruling: "not a mid-flight workflow read per D-02; 397 adjudicates",
1089
- addendum: "D-01/D-04 (dated 2026-09-28, 431-04): projected as `agent_qa_diff_knowledge_records`. A coding agent in a fresh workspace reads knowledge only through structured channels, so the committed-knowledge reads are served here rather than left to files; this supersedes the 2026-09-14 decision that projected the six safe reads only, whose no-search half stands (D-09)."
1090
- }),
1091
- Object.freeze({
1092
- opId: "knowledge.record.show",
1093
- ruledBy: "397",
1094
- ruling: "not a mid-flight workflow read per D-02; 397 adjudicates",
1095
- addendum: "D-01/D-04 (dated 2026-09-28, 431-04): projected as `agent_qa_show_knowledge_record`. A coding agent in a fresh workspace reads knowledge only through structured channels, so the committed-knowledge reads are served here rather than left to files; this supersedes the 2026-09-14 decision that projected the six safe reads only, whose no-search half stands (D-09)."
1096
- }),
1097
- Object.freeze({
1098
- opId: "knowledge.brief.why",
1099
- ruledBy: "397",
1100
- ruling: "not a mid-flight workflow read per D-02; 397 adjudicates",
1101
- addendum: "D-01/D-04 (dated 2026-09-28, 431-04): projected as `agent_qa_explain_brief`. A coding agent in a fresh workspace reads knowledge only through structured channels, so the committed-knowledge reads are served here rather than left to files; this supersedes the 2026-09-14 decision that projected the six safe reads only, whose no-search half stands (D-09)."
1102
- }),
1103
- Object.freeze({
1104
- opId: "runs.knowledge",
1105
- ruledBy: "407",
1106
- ruling: "D-05 (dated 2026-08-29, 407-01): not a mid-flight workflow read per D-02 \u2014 this is a COMPLETED run's used-and-learned projection, read by the durable Knowledge tab, and it is unprojected on exactly the footing its sibling `runs.detail` is. It carries every step's three served brief channels, each one's serve-time retrieval trace and the truncated candidate ledger beside it, so it is a large one-shot payload with no shape a `tools/call` round trip wants \u2014 larger than the step tree, not smaller. A planner mid-flight also has no use for what a FINISHED run was briefed with; the entity door already renders a record's card and the digest carries what triage needs. Deferred as its own seam, not omitted.",
1107
- addendum: "D-05 (dated 2026-09-28, 431-04): projected as `agent_qa_get_run_knowledge`, answering the whole `runs.knowledge` projection unchanged; the owner declined a step filter. The size objection above is answered in these terms: `agent_qa_get_run_overview` is the entry point that maps a run without bodies, `agent_qa_explain_brief` and `agent_qa_show_knowledge_record` are the drill-down, and the tool carries a 100000-character result-size hint so a host keeps the one-shot answer whole."
1108
- }),
1109
- Object.freeze({
1110
- opId: "runs.evidence",
1111
- ruledBy: "423",
1112
- ruling: "D-04 (dated 2026-09-15, 423-21): not a mid-flight workflow read per D-02 \u2014 this is one COMPLETED run step's parsed evidence document (console, network, variables, accessibility or ARIA), read by the dashboard evidence tabs, and it is unprojected on exactly the footing its sibling `runs.detail` is. A network document alone may carry hundreds of records, so it is a large one-shot payload with no shape a `tools/call` round trip wants, and a planner mid-flight has no use for a finished step's captured traffic. The raw SHA bytes stay on `runs.artifact` and the two HTTP doors. Deferred as its own seam, not omitted.",
1113
- addendum: "D-07 (dated 2026-09-28, 431-04): projected as the `runs.evidence` arm of `agent_qa_get_run_evidence`. A caller asks for ONE step document by the exact address `agent_qa_get_run_overview` supplies, so the answer is one addressed document rather than a run's captured traffic. Masking is unchanged: the op stays on the cold read-scope allowlist. The tool carries the 100000-character result-size hint."
1114
- }),
1115
- Object.freeze({
1116
- opId: "runs.artifact",
1117
- ruledBy: "405",
1118
- ruling: "D-12/D-13 (dated 2026-08-27, 405-05): not a mid-flight workflow read per D-02 \u2014 this resolves ONE completed run's content-addressed evidence blob, and its result is a base64 body. A `tools/call` round trip is the wrong carriage for bytes: a conversation cannot render a PNG, and a tool that returned one would spend a context window on a payload no reader can read. The two HTTP doors are its carriage, and 408's embed is the surface that renders it. Deferred as its own seam, not omitted. Phase 424 (dated 2026-09-20): the recording fact rides runs.detail; runs.artifact answers a bounded slice when range is supplied; pulling a whole recording through unary base64 is permitted and not recommended; no recording tool is projected because the MX ceiling of 22 is fully allocated.",
1119
- addendum: "D-07 (dated 2026-09-28, 431-04): projected as the `runs.artifact` arm of `agent_qa_get_run_evidence`. The blob is addressed by the sha the overview supplies, with no step-to-sha lookup inside the tool, and the rendering objection is answered by the image result arm 431-05 adds. The Phase 424 note that the MX ceiling of 22 was fully allocated is superseded: the ceiling is 24 (D-01)."
903
+ reason: "The protocol answers machine discovery itself through `tools/list` and SEP-2575 `server/discover`, and `init --agent` answers agent discovery from the committed catalog; this op answers a person asking in prose, which a discovery tool would only restate."
1120
904
  })
1121
905
  ]);
1122
906
 
@@ -1288,248 +1072,6 @@ function decorateRunEvidenceImage(input) {
1288
1072
  };
1289
1073
  }
1290
1074
 
1291
- // src/migration-map.ts
1292
- var V1_TOOL_MIGRATION_MAP = Object.freeze([
1293
- // 1
1294
- Object.freeze({
1295
- v1Name: "agent_qa_discover",
1296
- arm: "retired",
1297
- note: "not-a-gap (397, 2026-08-16): the protocol answers discovery itself, so a hand-written catalogue tool beside it would be a second, drifting answer to one question.",
1298
- route: "`tools/list` (plus SEP-2575 `server/discover`) answers machine discovery; 398's build-time capability catalog, carried by `agent-qa init --agent`, answers the human-facing version."
1299
- }),
1300
- // 2
1301
- Object.freeze({
1302
- v1Name: "agent_qa_get_config",
1303
- arm: "retired",
1304
- note: "A4 adjudication (2026-08-17, phase 398 plan 02): the inventory rules this row `n/a -> projected by 396`, but `config.get` is in UNPROJECTED_OPS after 396 plan 04 \u2014 not a mid-flight workflow read per D-02 \u2014 so there is no live tool to rename to.",
1305
- route: "no MCP tool reads configuration; the workspace posture an agent needs reaches it through `agent-qa init --agent`'s manifest (398 D-16), by env-ref name and never by value."
1306
- }),
1307
- // 3
1308
- Object.freeze({
1309
- v1Name: "agent_qa_schema_reference",
1310
- arm: "retired",
1311
- note: "not-a-gap (397, 2026-08-16): a served COPY of a contract is exactly the drift that advertising the live contract replaced.",
1312
- route: "every tool advertises its op's live `inputSchema`/`outputSchema` in `tools/list` (396 D-16), so a caller reads the contract the gate actually validates with; a fold lists its arms' fields as one object, and its `action` field says which fields each action takes."
1313
- }),
1314
- // 4
1315
- Object.freeze({
1316
- v1Name: "agent_qa_validate_definition",
1317
- arm: "retired",
1318
- note: "not-a-gap (397, 2026-08-16): a standalone linter over arbitrary text is a second validator that can disagree with the one that gates writes.",
1319
- route: "validation reaches a caller as `authoring.draft.verify`'s verdict inside the authoring funnel \u2014 the verify arm of `agent_qa_refine_test`."
1320
- }),
1321
- // 5
1322
- Object.freeze({
1323
- v1Name: "agent_qa_generate_id",
1324
- arm: "retired",
1325
- note: "not-a-gap (397, 2026-08-16): died with the CRUD model. Minting a canonical id on a surface is what 394-11 removed from the CLI, for this same reason.",
1326
- route: "ids are minted host-side where the entity is written; a caller names what it wants and reads the id back off the entity card `agent_qa_get_entity` returns."
1327
- }),
1328
- // 6
1329
- Object.freeze({
1330
- v1Name: "agent_qa_validate_id",
1331
- arm: "retired",
1332
- note: "not-a-gap (397, 2026-08-16): died with the CRUD model. A standalone validator would be a second answer to a question already answered at every parse point.",
1333
- route: "the one URI codec (`packages/schema/src/entity/uri.ts`) validates every `agentqa://` reference as it is parsed, including the one `agent_qa_get_entity` resolves."
1334
- }),
1335
- // 7
1336
- Object.freeze({
1337
- v1Name: "agent_qa_list_tests",
1338
- arm: "pending",
1339
- note: "gap (397, 2026-08-16): nothing in the registry enumerates the test corpus \u2014 `entities.resolve` resolves a reference to an entity, which is a different question.",
1340
- route: "until the owning milestone mints `entities.list`, a known reference resolves through `agent_qa_get_entity`; enumeration has no tool.",
1341
- owner: "M5 (live view & run invocation)"
1342
- }),
1343
- // 8
1344
- Object.freeze({
1345
- v1Name: "agent_qa_read_test",
1346
- arm: "renamed-to",
1347
- v2Tool: "agent_qa_get_entity",
1348
- note: "projected by 396 (inventory row 8, 2026-08-16): `entities.get` is D-02's universal entity read, kind TST, behind the one read door."
1349
- }),
1350
- // 9
1351
- Object.freeze({
1352
- v1Name: "agent_qa_validate_test",
1353
- arm: "retired",
1354
- note: "not-a-gap (397, 2026-08-16): row 4's ruling. `authoring.draft.verify` is the only validator, and it validates a draft in the funnel rather than arbitrary text a caller pasted.",
1355
- route: "the verify arm of `agent_qa_refine_test` returns the same verdict that gates the write."
1356
- }),
1357
- // 10
1358
- Object.freeze({
1359
- v1Name: "agent_qa_create_test",
1360
- arm: "renamed-to",
1361
- v2Tool: "agent_qa_write_test",
1362
- note: "projected by 396 (inventory row 10, 2026-08-16): `authoring.draft.write` is workflow-coarse and verifies a draft rather than performing v1's blind write, so the input shape differs. Since 2026-10-02 (432-40, D-06) it writes the test file in the call unless a keep-gate rejected the draft."
1363
- }),
1364
- // 11
1365
- Object.freeze({
1366
- v1Name: "agent_qa_update_test",
1367
- arm: "renamed-to",
1368
- v2Tool: "agent_qa_refine_test",
1369
- note: "projected by 396 (inventory row 11, 2026-08-16): `authoring.draft.edit` is the edit arm of the refine fold, selected by the fold discriminant."
1370
- }),
1371
- // 12
1372
- Object.freeze({
1373
- v1Name: "agent_qa_delete_test",
1374
- arm: "retired",
1375
- note: "not-a-gap (397, 2026-08-16): a rule, not an absence. MCPS-02 forbids a surface delete verb for irreversible deletes.",
1376
- route: "removing a committed test is a repository edit a person makes and reviews; no surface has a delete door and none is coming."
1377
- }),
1378
- // 13
1379
- Object.freeze({
1380
- v1Name: "agent_qa_list_suites",
1381
- arm: "pending",
1382
- note: `gap (397, 2026-08-16): folded into row 7's verb \u2014 enumerating the corpus answers "which tests exist, and in which files". A suite is a file, so this is not a second registry.`,
1383
- route: "until `entities.list` lands, suite membership is read from the workspace files themselves; the TST records inside them resolve through `agent_qa_get_entity`.",
1384
- owner: "M5 (live view & run invocation)"
1385
- }),
1386
- // 14
1387
- Object.freeze({
1388
- v1Name: "agent_qa_read_suite",
1389
- arm: "retired",
1390
- note: "not-a-gap (397, 2026-08-16): there is no suite RECORD to read. `ENTITY_KINDS` has no suite kind and a suite is a committed file addressed by workspace path.",
1391
- route: "the TST records inside a suite are `agent_qa_get_entity`'s job; the file itself is read from the workspace."
1392
- }),
1393
- // 15
1394
- Object.freeze({
1395
- v1Name: "agent_qa_validate_suite",
1396
- arm: "retired",
1397
- note: "not-a-gap (397, 2026-08-16): row 4's ruling \u2014 one validator, inside the funnel.",
1398
- route: "the verify arm of `agent_qa_refine_test` validates the draft that joins the suite file."
1399
- }),
1400
- // 16
1401
- Object.freeze({
1402
- v1Name: "agent_qa_create_suite",
1403
- arm: "retired",
1404
- note: "not-a-gap (397, 2026-08-16): a suite is a committed file the authoring funnel writes. A blind create is the v1 write the verified authoring funnel replaced (re-spelled 2026-10-02, 432-40, D-06: the funnel writes in the call).",
1405
- route: "`agent_qa_write_test` names the suite file a draft joins and writes it in the call, unless a keep-gate rejected the draft."
1406
- }),
1407
- // 17
1408
- Object.freeze({
1409
- v1Name: "agent_qa_update_suite",
1410
- arm: "retired",
1411
- note: "not-a-gap (397, 2026-08-16): row 16's ruling, through the edit arm instead of the write (re-spelled 2026-10-02, 432-40, D-06: the edit lands in the call).",
1412
- route: "`agent_qa_refine_test` edits the written test; the suite file changes in the call."
1413
- }),
1414
- // 18
1415
- Object.freeze({
1416
- v1Name: "agent_qa_delete_suite",
1417
- arm: "retired",
1418
- note: "not-a-gap (397, 2026-08-16): row 12's MCPS-02 rule, applied to the file rather than the record.",
1419
- route: "removing a suite file is a repository edit a person makes and reviews; no surface door exists."
1420
- }),
1421
- // 19
1422
- Object.freeze({
1423
- v1Name: "agent_qa_list_hooks",
1424
- arm: "pending",
1425
- note: "gap (397, 2026-08-16, corrected the same day): the 396 reason overstated `config.get`, which projects the resolved CONFIG's leaves; hook definitions live in the separate `workspace.hooksFile`, so no registered op reads them today.",
1426
- route: "until the owning milestone re-opens the hook execution model, hooks are read from `workspace.hooksFile` in the workspace; no tool enumerates them.",
1427
- owner: "M7 (skills layer)"
1428
- }),
1429
- // 20
1430
- Object.freeze({
1431
- v1Name: "agent_qa_read_hook",
1432
- arm: "pending",
1433
- note: "gap (397, 2026-08-16): with row 19, ONE hook-read verb and not two. What `config.get` shows is `workspace.hooksFile` \u2014 the path, not the hooks.",
1434
- route: "until that verb lands, a single hook is read from the hooks file itself; no tool projects it.",
1435
- owner: "M7 (skills layer)"
1436
- }),
1437
- // 21
1438
- Object.freeze({
1439
- v1Name: "agent_qa_create_hook",
1440
- arm: "renamed-to",
1441
- v2Tool: "agent_qa_change_config",
1442
- note: "projected by 396 (inventory row 21, 2026-08-16): `config.hook.add` is an arm of the config fold. Since 2026-09-30 (432-12, D-05) the fold is `agent_qa_change_config` and the hook is written in the call, with the files it changed on the answer."
1443
- }),
1444
- // 22
1445
- Object.freeze({
1446
- v1Name: "agent_qa_update_hook",
1447
- arm: "pending",
1448
- note: "gap (397, 2026-08-16): `config.field.set` reaches any schema-addressable CONFIG path and the hooks file is not one, so an existing hook has no edit verb. Minting one now would pin an edit shape to a contract the owning milestone revisits.",
1449
- route: "adding a hook goes through `agent_qa_change_config`; editing an existing one is a hooks-file edit a person makes until the owner rules on the verb.",
1450
- owner: "M7 (skills layer)"
1451
- }),
1452
- // 23
1453
- Object.freeze({
1454
- v1Name: "agent_qa_delete_hook",
1455
- arm: "retired",
1456
- note: "not-a-gap (397, 2026-08-16): row 12's MCPS-02 rule.",
1457
- route: "removing a hook is an edit to the hooks file that a person makes and reviews."
1458
- }),
1459
- // 24
1460
- Object.freeze({
1461
- v1Name: "agent_qa_run_hook",
1462
- arm: "retired",
1463
- note: `not-a-gap (397, 2026-08-16): a hook's whole contract is "runs at this lifecycle point". Invoking one by name from a surface is arbitrary script execution decoupled from the lifecycle that gives it meaning \u2014 the private-power shape M4 abolished.`,
1464
- route: "hooks run at their declared lifecycle points during a run started by `agent_qa_start_run`; nothing invokes one out of band."
1465
- }),
1466
- // 25
1467
- Object.freeze({
1468
- v1Name: "agent_qa_enqueue_test_run",
1469
- arm: "renamed-to",
1470
- v2Tool: "agent_qa_start_run",
1471
- note: "projected by 396 (inventory row 25, 2026-08-16): `runs.start` is workflow-coarse, and the test scope is an argument rather than a tool of its own."
1472
- }),
1473
- // 26
1474
- Object.freeze({
1475
- v1Name: "agent_qa_enqueue_suite_run",
1476
- arm: "renamed-to",
1477
- v2Tool: "agent_qa_start_run",
1478
- note: "projected by 396 (inventory row 26, 2026-08-16): the SAME op as row 25 at suite scope, which is why D-01 allows one tool over both."
1479
- }),
1480
- // 27
1481
- Object.freeze({
1482
- v1Name: "agent_qa_get_run",
1483
- arm: "retired",
1484
- note: "A4 adjudication (2026-08-17, phase 398 plan 02): the inventory rules this row `n/a -> projected by 396`, but `runs.get` is in UNPROJECTED_OPS after 396 plan 04 \u2014 not a mid-flight workflow read per D-02 \u2014 so there is no live tool to rename to.",
1485
- route: "the entity door renders a run's card: `agent_qa_get_entity` on the run reference. `agent_qa_list_runs` enumerates recent runs."
1486
- }),
1487
- // 28
1488
- Object.freeze({
1489
- v1Name: "agent_qa_get_run_steps",
1490
- arm: "retired",
1491
- note: "A4 adjudication (2026-08-17, phase 398 plan 02): ruled `n/a -> projected by 396` against `runs.get`/`runs.events`, both of which are in UNPROJECTED_OPS \u2014 `runs.events` is a stream op besides, and a `tools/call` is one round trip with no shape for a resumable feed.",
1492
- route: "`agent_qa_get_run_digest` carries what triage needs from a run, including its steps' outcome; the run's card comes from `agent_qa_get_entity`."
1493
- }),
1494
- // 29
1495
- Object.freeze({
1496
- v1Name: "agent_qa_get_run_logs",
1497
- arm: "retired",
1498
- note: "A4 adjudication (2026-08-17, phase 398 plan 02): ruled `n/a -> projected by 396` against `runs.events`, which is in UNPROJECTED_OPS as a stream op with no single-round-trip shape.",
1499
- route: "`agent_qa_get_run_digest` carries the triage-relevant remainder of a run; the full event feed is read from the run artifacts on disk, not over this surface."
1500
- }),
1501
- // 30
1502
- Object.freeze({
1503
- v1Name: "agent_qa_get_run_execution_logs",
1504
- arm: "retired",
1505
- note: "A4 adjudication (2026-08-17, phase 398 plan 02): row 29's reading \u2014 `runs.events` is unprojected, so the second logs alias has no live tool either.",
1506
- route: "`agent_qa_get_run_digest` for triage; execution logs live in the run's artifacts on disk."
1507
- }),
1508
- // 31
1509
- Object.freeze({
1510
- v1Name: "agent_qa_get_run_artifact",
1511
- arm: "pending",
1512
- note: "gap (397, 2026-08-16): artifact bytes over a tool result is a disclosure surface nobody has scoped, so the read is routed WITH that scoping rather than minted without it \u2014 an artifact read without it is the gap made worse.",
1513
- route: "until the owning milestone ships artifact resolution, artifacts are read from the run directory on disk; `agent_qa_get_run_digest` names what a run produced.",
1514
- owner: "M5 (artifact resolution route)"
1515
- }),
1516
- // 32
1517
- Object.freeze({
1518
- v1Name: "agent_qa_cancel_run",
1519
- arm: "pending",
1520
- note: "gap (397, 2026-08-16): ruled a REAL gap as `runs.cancel` and routed to its owner rather than minted in 397, because D-17 binds the adjudicator too.",
1521
- route: "until `runs.cancel` lands, a run is stopped by the host that started it; this surface starts runs through `agent_qa_start_run` and cannot stop one.",
1522
- owner: "M5 (live-view control arbiter)"
1523
- }),
1524
- // 33
1525
- Object.freeze({
1526
- v1Name: "agent_qa_classify_failure",
1527
- arm: "retired",
1528
- note: "not-a-gap (397, 2026-08-16): classification on demand over dashboard-fetched data is the private data path M4 abolished, not a capability that went missing.",
1529
- route: "`analysis.get` reads the verdict the `analyze-run` job produced; over this surface the triage answer is `agent_qa_get_run_digest`."
1530
- })
1531
- ]);
1532
-
1533
1075
  // src/projection.ts
1534
1076
  var JSON_SCHEMA_CONVERSION_TARGET = "draft-2020-12";
1535
1077
  function isReadOnly(specs) {
@@ -1609,19 +1151,7 @@ function toRegisterToolConfig(projection, specs, advertised, maskText) {
1609
1151
  inputSchema,
1610
1152
  ...outputSchema === void 0 ? {} : { outputSchema },
1611
1153
  annotations: deriveAnnotations(specs),
1612
- // 408 D-05: the App reference, DERIVED from the projection's own carrier declaration by the
1613
- // same pipeline that derives the annotations above — never a pair of tool names written here.
1614
- // The conditional spread is the house absent-field rule: a non-carrier's config has no `_meta`
1615
- // key at all.
1616
- //
1617
- // NOT MASKED, and it joins the two exemptions the header names for their reason rather than by
1618
- // analogy. `resourceUri` is the address constant this package publishes — a declared literal in
1619
- // this repository's own source, exactly like the `title` above it — and a masker that rewrote
1620
- // it would point every host at an address the resource codec refuses, which is the tool-rename
1621
- // failure with a URI scheme attached. Nothing interpolated into it comes from a caller, from
1622
- // storage or from a resolved value; the run id never appears here, because this is the
1623
- // TEMPLATE and a per-call reference is composed downstream. The result-size hint (431 D-05) is
1624
- // a declared constant too, and rides the same record (`listingMeta`).
1154
+ // Not masked: `resourceUri` is a declared constant, and masking it would break the address.
1625
1155
  ...meta === void 0 ? {} : { _meta: meta }
1626
1156
  };
1627
1157
  }
@@ -1905,67 +1435,6 @@ function createProjectionHandler(projection, invoke, maskText, liveLink) {
1905
1435
  }
1906
1436
  };
1907
1437
  }
1908
- var TOOLS_CALL_METHOD = "tools/call";
1909
- function callToolName(request) {
1910
- if (typeof request !== "object" || request === null) return void 0;
1911
- const params = request.params;
1912
- if (typeof params !== "object" || params === null) return void 0;
1913
- const name = params.name;
1914
- return typeof name === "string" ? name : void 0;
1915
- }
1916
- function v1MigrationEntryFor(toolName) {
1917
- return V1_TOOL_MIGRATION_MAP.find((entry) => entry.v1Name === toolName);
1918
- }
1919
- function v1RedirectFor(toolName, registered) {
1920
- if (registered.has(toolName)) return void 0;
1921
- return v1MigrationEntryFor(toolName);
1922
- }
1923
- function v1InputShapeNote(projection) {
1924
- switch (compositionOf(projection)) {
1925
- case "single":
1926
- return "it takes one input object";
1927
- case "union":
1928
- return `set \`${projection.discriminant}\` to one of ${projection.ops.map((op) => `\`${op}\``).join(", ")} and pass that arm's own fields`;
1929
- case "pipeline":
1930
- return "it takes one entity reference and answers the card that reference resolves to";
1931
- }
1932
- }
1933
- function v1RedirectMessage(entry, visibleTarget) {
1934
- const opening = `\`${entry.v1Name}\` is a retired agent-qa v1 tool name and is not served.`;
1935
- if (entry.arm === "renamed-to") {
1936
- if (visibleTarget === void 0) {
1937
- return `${opening} Call \`tools/list\` for the tools you may call on this server, and \`agent-qa init --agent\` for this workspace's capability manifest, which carries the v1 tool-name migration map.`;
1938
- }
1939
- return `${opening} The capability it named is now \`${visibleTarget.name}\`. ${entry.note} Its input shape is not v1's \u2014 ${v1InputShapeNote(visibleTarget)}; \`tools/list\` advertises the contract the call is validated against.`;
1940
- }
1941
- if (entry.arm === "pending") {
1942
- const owned = entry.owner === void 0 ? "" : ` The capability it named is a known gap owned by ${entry.owner}.`;
1943
- const until = entry.route === void 0 ? "" : ` Until then: ${entry.route}`;
1944
- return `${opening}${owned} ${entry.note}${until}`;
1945
- }
1946
- return `${opening} ${entry.note}${entry.route === void 0 ? "" : ` Instead: ${entry.route}`}`;
1947
- }
1948
- function installV1NameRedirect(server, deps) {
1949
- const dispatch = server.server;
1950
- const sdkHandler = dispatch._requestHandlers?.get(TOOLS_CALL_METHOD);
1951
- if (typeof sdkHandler !== "function") {
1952
- throw new McpProjectionError(
1953
- TOOLS_CALL_METHOD,
1954
- "has no installed request handler to wrap; the SDK no longer registers tool dispatch where this package reads it, so the retired-v1-name redirect cannot be placed"
1955
- );
1956
- }
1957
- dispatch.removeRequestHandler(TOOLS_CALL_METHOD);
1958
- dispatch.setRequestHandler(TOOLS_CALL_METHOD, (request, ctx) => {
1959
- const name = callToolName(request);
1960
- const entry = name === void 0 ? void 0 : v1RedirectFor(name, deps.registered);
1961
- if (entry === void 0) return sdkHandler(request, ctx);
1962
- const target = entry.arm === "renamed-to" && entry.v2Tool !== void 0 && deps.registered.has(entry.v2Tool) ? AGENT_QA_TOOL_PROJECTIONS.find((projection) => projection.name === entry.v2Tool) : void 0;
1963
- throw new import_server3.ProtocolError(
1964
- import_server3.ProtocolErrorCode.InvalidParams,
1965
- deps.maskText(v1RedirectMessage(entry, target))
1966
- );
1967
- });
1968
- }
1969
1438
  function buildAgentQaMcpServer(deps) {
1970
1439
  const server = new import_server3.McpServer(
1971
1440
  { name: "agent-qa", version: AGENT_QA_MCP_SERVER_VERSION },
@@ -1973,15 +1442,10 @@ function buildAgentQaMcpServer(deps) {
1973
1442
  capabilities: {
1974
1443
  tools: {},
1975
1444
  resources: {},
1976
- // 408 D-04: the official MCP Apps extension, ADVERTISED rather than assumed. Extensions are
1977
- // bilateral opt-in — a client declares support in the `io.modelcontextprotocol/
1978
- // clientCapabilities` envelope it sends on every request, and reads the server's set back
1979
- // from discovery — so a server that renders App HTML without naming the extension here is
1980
- // one no host will ever ask for it. The value is an empty record because this extension
1981
- // carries no server-side options; what it says is that this endpoint speaks it at all.
1445
+ // Extensions are bilateral opt-in: a host asks only for an App the server advertises.
1982
1446
  extensions: { [MCP_APPS_EXTENSION_ID]: {} }
1983
1447
  },
1984
- // 433-33 (G-433-9): init's pointer, on the one channel every MCP host receives.
1448
+ // Instructions reach every MCP host, so init's pointer goes here.
1985
1449
  instructions: AGENT_QA_MCP_INSTRUCTIONS
1986
1450
  }
1987
1451
  );
@@ -2031,7 +1495,6 @@ function buildAgentQaMcpServer(deps) {
2031
1495
  createProjectionHandler(projection, invoke, deps.maskText, deps.liveLink)
2032
1496
  );
2033
1497
  }
2034
- installV1NameRedirect(server, { registered, maskText: deps.maskText });
2035
1498
  registerEntityResources(server, {
2036
1499
  registry: deps.registry,
2037
1500
  caller: deps.caller,
@@ -2041,8 +1504,7 @@ function buildAgentQaMcpServer(deps) {
2041
1504
  registry: deps.registry,
2042
1505
  caller: deps.caller,
2043
1506
  maskText: deps.maskText,
2044
- // 408 plan 03 (D-08): the SAME supplier the carrier decoration reads, so the anchor inside the
2045
- // App document and the line beside a tool result name one page for one run.
1507
+ // The same supplier the carrier decoration reads, so both name one page for one run.
2046
1508
  ...deps.liveLink === void 0 ? {} : { liveLink: deps.liveLink }
2047
1509
  });
2048
1510
  return server;
@@ -2125,28 +1587,6 @@ function withoutLegacyResultWrap(document) {
2125
1587
  if (typeof natural !== "object" || natural === null || natural.type === "object") return wrapper;
2126
1588
  return natural;
2127
1589
  }
2128
-
2129
- // src/task-seam.ts
2130
- var JOB_STATUS_TO_MCP_TASK_STATE = {
2131
- // The four checkpoint boundaries. One answer, four times, on purpose.
2132
- queued: "working",
2133
- authored: "working",
2134
- bundling: "working",
2135
- committing: "working",
2136
- // The three terminal states, kept apart.
2137
- done: "completed",
2138
- failed: "failed",
2139
- cancelled: "cancelled"
2140
- };
2141
- function projectJobAsTask(job) {
2142
- return {
2143
- taskId: job.id,
2144
- status: JOB_STATUS_TO_MCP_TASK_STATE[job.status],
2145
- statusMessage: job.summary,
2146
- createdAt: job.createdAt,
2147
- lastUpdatedAt: job.updatedAt
2148
- };
2149
- }
2150
1590
  // Annotate the CommonJS export names for ESM import in node:
2151
1591
  0 && (module.exports = {
2152
1592
  AGENT_QA_COMPOSED_CONTRACTS,
@@ -2176,13 +1616,11 @@ function projectJobAsTask(job) {
2176
1616
  AppResourceReadError,
2177
1617
  AppResourceUriError,
2178
1618
  DEGRADED_OUTPUT_CONTRACTS,
2179
- JOB_STATUS_TO_MCP_TASK_STATE,
2180
1619
  LIVE_LINK_ABSENT_LINE,
2181
1620
  LIVE_LINK_NO_RUN_LINE,
2182
1621
  MCP_APPS_EXTENSION_ID,
2183
1622
  McpProjectionError,
2184
1623
  UNPROJECTED_OPS,
2185
- V1_TOOL_MIGRATION_MAP,
2186
1624
  advertisedContracts,
2187
1625
  appResourceUriFor,
2188
1626
  armWithoutDiscriminant,
@@ -2197,7 +1635,6 @@ function projectJobAsTask(job) {
2197
1635
  maskedToolResult,
2198
1636
  negotiatedAppsExtension,
2199
1637
  parseAppUri,
2200
- projectJobAsTask,
2201
1638
  registerAppResources,
2202
1639
  renderAppWrapperPage,
2203
1640
  stripRedundantObjectStamp,