@orboto/mcp 0.192.0 → 0.194.0

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.
Files changed (131) hide show
  1. package/dist/event-bridge.js +1 -47
  2. package/dist/event-bridge.test.js +0 -19
  3. package/dist/http-transport-binding.test.js +1 -4
  4. package/dist/http-transport.js +1 -213
  5. package/dist/http-transport.test.js +0 -56
  6. package/dist/index.js +1 -48
  7. package/dist/input-schema.js +2 -76
  8. package/dist/input-schema.test.js +1 -15
  9. package/dist/instructions-budget.test.js +0 -2
  10. package/dist/manifest-size.test.js +3 -72
  11. package/dist/oauth-bootstrap.js +0 -47
  12. package/dist/oauth-bootstrap.test.js +4 -16
  13. package/dist/orboto-client.js +0 -30
  14. package/dist/orboto-client.test.js +0 -12
  15. package/dist/prompts.js +0 -39
  16. package/dist/protect-text-usage.test.js +1 -9
  17. package/dist/required-rules-wrapper.test.js +0 -5
  18. package/dist/required-rules.js +0 -2
  19. package/dist/resources.js +0 -62
  20. package/dist/resources.test.js +0 -16
  21. package/dist/response-budget.js +2 -150
  22. package/dist/response-budget.test.js +1 -32
  23. package/dist/server.js +4 -180
  24. package/dist/session-nudge.js +0 -9
  25. package/dist/session-nudge.test.js +2 -10
  26. package/dist/session-nudge.transport.test.js +0 -23
  27. package/dist/tool-annotations.test.js +0 -44
  28. package/dist/tool-docs.js +1 -12
  29. package/dist/tool-docs.test.js +2 -8
  30. package/dist/tool-parameters.test.js +0 -19
  31. package/dist/tools/absence-writes.js +1 -17
  32. package/dist/tools/admin-translations.js +0 -11
  33. package/dist/tools/admin-writes.js +0 -28
  34. package/dist/tools/admin-writes.test.js +0 -1
  35. package/dist/tools/agent-coordination.js +2 -24
  36. package/dist/tools/agent-coordination.test.js +0 -2
  37. package/dist/tools/agent-instructions.js +0 -10
  38. package/dist/tools/agent-instructions.test.js +0 -2
  39. package/dist/tools/agent-messages.js +0 -1
  40. package/dist/tools/ai-status.js +0 -14
  41. package/dist/tools/ai-usage.js +0 -10
  42. package/dist/tools/analytics.js +0 -2
  43. package/dist/tools/api-call.js +0 -12
  44. package/dist/tools/api-call.test.js +1 -2
  45. package/dist/tools/api-search.js +1 -6
  46. package/dist/tools/approvals.js +0 -6
  47. package/dist/tools/attach.js +0 -25
  48. package/dist/tools/backup.js +0 -13
  49. package/dist/tools/backup.test.js +1 -2
  50. package/dist/tools/bulk-create.js +1 -25
  51. package/dist/tools/bulk-create.test.js +0 -4
  52. package/dist/tools/bulk-writes.js +69 -61
  53. package/dist/tools/bulk-writes.test.js +34 -4
  54. package/dist/tools/check-similar.js +0 -17
  55. package/dist/tools/checklist-writes.js +82 -27
  56. package/dist/tools/checklist-writes.test.js +69 -1
  57. package/dist/tools/claim.js +1 -71
  58. package/dist/tools/claim.test.js +0 -2
  59. package/dist/tools/critical-path.js +0 -5
  60. package/dist/tools/critical-path.test.js +0 -3
  61. package/dist/tools/cross-project-links.ee.js +1 -24
  62. package/dist/tools/doc-attachments.js +0 -28
  63. package/dist/tools/doc-attachments.test.js +1 -4
  64. package/dist/tools/doc-comments.js +1 -37
  65. package/dist/tools/doc-comments.test.js +1 -2
  66. package/dist/tools/doc-edits.js +1 -31
  67. package/dist/tools/doc-edits.test.js +0 -9
  68. package/dist/tools/doc-export.js +0 -24
  69. package/dist/tools/doc-export.test.js +0 -12
  70. package/dist/tools/doc-revisions.js +0 -23
  71. package/dist/tools/docs-ai.js +0 -34
  72. package/dist/tools/docs-ai.test.js +0 -11
  73. package/dist/tools/docs.js +1 -75
  74. package/dist/tools/docs.test.js +1 -3
  75. package/dist/tools/embedding-status.js +0 -13
  76. package/dist/tools/embedding-status.test.js +0 -1
  77. package/dist/tools/feedback.js +1 -15
  78. package/dist/tools/feedback.test.js +0 -1
  79. package/dist/tools/get-checklists.js +6 -14
  80. package/dist/tools/get-project-primer.js +1 -25
  81. package/dist/tools/get-project.js +0 -17
  82. package/dist/tools/get-ticket.js +1 -61
  83. package/dist/tools/get-timer.js +0 -11
  84. package/dist/tools/git-health-reasons.js +0 -1
  85. package/dist/tools/identity.js +0 -13
  86. package/dist/tools/list-projects.js +0 -20
  87. package/dist/tools/list-projects.test.js +0 -1
  88. package/dist/tools/list-tickets.js +10 -9
  89. package/dist/tools/milestones-crud.test.js +0 -4
  90. package/dist/tools/milestones.js +0 -43
  91. package/dist/tools/my-tickets.js +0 -7
  92. package/dist/tools/phase-b-tools.test.js +14 -21
  93. package/dist/tools/primer-facts.js +1 -49
  94. package/dist/tools/primer-facts.test.js +0 -20
  95. package/dist/tools/project-listings.js +0 -25
  96. package/dist/tools/query.js +1 -12
  97. package/dist/tools/query.test.js +0 -8
  98. package/dist/tools/raci.js +0 -11
  99. package/dist/tools/requirements-spec.js +1 -12
  100. package/dist/tools/response-expand.js +0 -19
  101. package/dist/tools/review-policy.js +0 -24
  102. package/dist/tools/review-policy.test.js +0 -2
  103. package/dist/tools/search.js +1 -13
  104. package/dist/tools/session-start.js +1 -136
  105. package/dist/tools/session-start.test.js +1 -44
  106. package/dist/tools/set-parent.js +1 -19
  107. package/dist/tools/shared.js +0 -41
  108. package/dist/tools/shared.test.js +0 -4
  109. package/dist/tools/similar-projection.js +0 -15
  110. package/dist/tools/ticket-attachments.js +1 -25
  111. package/dist/tools/ticket-schedules.js +0 -9
  112. package/dist/tools/ticket-schedules.test.js +0 -1
  113. package/dist/tools/ticket-writes.js +1 -110
  114. package/dist/tools/ticket-writes.test.js +1 -15
  115. package/dist/tools/time-writes.js +26 -46
  116. package/dist/tools/time-writes.test.js +18 -0
  117. package/dist/tools/update-project.js +1 -27
  118. package/dist/tools/update-project.test.js +1 -1
  119. package/dist/tools/wiki.js +0 -8
  120. package/dist/tools/work-next.js +0 -5
  121. package/dist/tools/work-sessions-start.test.js +0 -2
  122. package/dist/tools/work-sessions.js +0 -48
  123. package/dist/tools/work-sessions.test.js +0 -5
  124. package/dist/toolset.js +2 -81
  125. package/dist/toolset.test.js +0 -20
  126. package/dist/version-guard.test.js +0 -4
  127. package/dist/version.js +0 -12
  128. package/dist/with-metrics-output-schema.test.js +1 -14
  129. package/dist/with-metrics.js +2 -74
  130. package/dist/with-metrics.test.js +7 -29
  131. package/package.json +1 -1
@@ -2,32 +2,12 @@
2
2
  * ORB-885 - `orboto_update_project`.
3
3
  * ORB-830 - `orboto_create_project` + `orboto_archive_project`.
4
4
  *
5
- * Project-CRUD write surface for the MCP. The read half
6
- * (`orboto_list_projects`, `orboto_get_project`) shipped in ORB-244
7
- * Phase B; ORB-885 added `update_project`, ORB-830 closes the gap
8
- * with create + archive so an agent never has to drop to a raw
9
- * `POST /projects` to spin up a new project.
10
- *
11
- * Status mutation is allowed in `update_project` (unlike
12
- * `orboto_update_milestone`, which delegates closing to a dedicated
13
- * tool) because project status is a four-state lifecycle (`draft` →
14
- * `active` → `archived` / `closed`) with no extra semantics - a
15
- * single patch field covers it cleanly. `archive_project` is a
16
- * thin convenience over `update_project({ status: 'archived' })`
17
- * because "archive this project" is a workflow agents reach for
18
- * often enough that an explicit verb beats teaching the model the
19
- * patch shape.
5
+ * @see ORB-244
20
6
  */
21
7
  import { z } from 'zod';
22
8
  import { resolveProjectByKey } from './shared.js';
23
- // Mirrors the Zod schema on `PATCH /projects/:id` (see
24
- // apps/api/src/routes/projects.ts). Kept in sync manually - there's no
25
- // generator from the API's runtime schema into the MCP package yet.
26
9
  const PROJECT_KEY_RE = /^[A-Z0-9]+$/;
27
10
  const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
28
- // ORB-993/ORB-994 - the 18 detector-supported workspace/project locales.
29
- // Mirrors WORKSPACE_LOCALE_CODES in @orboto/shared-schema (kept in sync
30
- // manually, same as the rest of this tool's schema).
31
11
  const LOCALE_CODES = ['en', 'de', 'fr', 'es', 'it', 'nl', 'pt', 'ru', 'pl', 'tr', 'cs', 'da', 'sv', 'no', 'fi', 'ja', 'zh', 'ko'];
32
12
  export const updateProjectToolConfig = {
33
13
  title: 'Update a project\'s metadata',
@@ -70,9 +50,6 @@ export function makeUpdateProjectHandler(client) {
70
50
  };
71
51
  };
72
52
  }
73
- // ---------------------------------------------------------------------------
74
- // orboto_create_project - ORB-830
75
- // ---------------------------------------------------------------------------
76
53
  export const createProjectToolConfig = {
77
54
  title: 'Create a new project',
78
55
  description: 'Create a new project in the workspace. `name` is required; `key` is optional and the API auto-derives one from the name when omitted (uppercase initials). Returns the new project\'s id + key + status (`active` by default). Caller must have `admin:project:create` or be a super-admin. Note: a new project is auto-provisioned with a general doc space (slug `<key>-general`, system-generated, for the AI primer + notes) - don\'t create a separate doc space for its docs, reuse that one (orboto_list_doc_spaces).',
@@ -116,9 +93,6 @@ export function makeCreateProjectHandler(client) {
116
93
  };
117
94
  };
118
95
  }
119
- // ---------------------------------------------------------------------------
120
- // orboto_archive_project - ORB-830
121
- // ---------------------------------------------------------------------------
122
96
  export const archiveProjectToolConfig = {
123
97
  title: 'Archive a project',
124
98
  description: 'Archive a project - moves it out of the active rotation without deleting any data. Convenience wrapper around `orboto_update_project({ status: "archived" })` because "archive this project" is a workflow operators hit often. Archiving is reversible: pass the same project back through `orboto_update_project({ status: "active" })` to restore. Idempotent - re-archiving an already-archived project is a no-op success.',
@@ -152,7 +152,7 @@ describe('orboto_archive_project', () => {
152
152
  { json: { ...PROJ, status: 'archived' } },
153
153
  ]);
154
154
  const res = await makeArchiveProjectHandler(client)({ projectKey: 'ACME' });
155
- expect(calls).toHaveLength(1); // only the resolve, no PATCH
155
+ expect(calls).toHaveLength(1);
156
156
  expect(res.content[0].text).toMatch(/already archived/);
157
157
  expect(res.structuredContent).toMatchObject({ alreadyArchived: true });
158
158
  });
@@ -11,7 +11,6 @@ import { resolveDocId } from './docs.js';
11
11
  function text(t, structured) {
12
12
  return { content: [{ type: 'text', text: t }], ...(structured ? { structuredContent: structured } : {}) };
13
13
  }
14
- // --- orboto_wiki_ingest_url ------------------------------------------------
15
14
  export const wikiIngestUrlToolConfig = {
16
15
  title: 'Ingest a URL into an LLM-Wiki space',
17
16
  description: 'Fetch a public URL, extract its main article as Markdown, and create a SOURCE doc in the space. When the space has the LLM-Wiki enabled, this also enqueues the auto-ingest worker, which curates the source into wiki pages (immediately under auto-apply, or as a pending plan under review-gate). Wraps POST /spaces/:id/docs/ingest-url.',
@@ -31,7 +30,6 @@ export function makeWikiIngestUrlHandler(client) {
31
30
  return text(`Ingested source "${res.title}" (docId: ${res.docId}). If the space is LLM-Wiki-enabled the curation worker is now processing it.`, { docId: res.docId, title: res.title });
32
31
  };
33
32
  }
34
- // --- orboto_wiki_ask -------------------------------------------------------
35
33
  export const wikiAskToolConfig = {
36
34
  title: 'Ask a question over the wiki (RAG with citations)',
37
35
  description: 'Retrieval-augmented Q&A over the docs corpus, optionally scoped to one space. Returns an answer with numbered citations linking back to source docs. Requires both a chat AND an embedding provider (check orboto_ai_status). Wraps POST /ai/ask-docs.',
@@ -54,7 +52,6 @@ export function makeWikiAskHandler(client) {
54
52
  return text(`${res.answer}\n\n${cites ? `Sources:\n${cites}` : ''}`.trim(), { answer: res.answer, citations: res.citations, mode: res.mode });
55
53
  };
56
54
  }
57
- // --- orboto_wiki_lint ------------------------------------------------------
58
55
  export const wikiLintToolConfig = {
59
56
  title: 'Run the LLM-Wiki lint pass on a space',
60
57
  description: 'Scan an LLM-Wiki space for inconsistencies (orphan pages, missing cross-references, stale pages, unprocessed sources, and - when AI is configured - contradictions and undocumented concepts). Returns the open issues, each with a suggested fix. Wraps POST /spaces/:id/llm-wiki/lint.',
@@ -72,7 +69,6 @@ export function makeWikiLintHandler(client) {
72
69
  return text(`Lint complete: ${res.issues.length} open issue(s), ${res.resolved} auto-resolved.\n${lines.join('\n')}`, { issues: res.issues, detected: res.detected, resolved: res.resolved });
73
70
  };
74
71
  }
75
- // --- orboto_wiki_plan_update / apply_plan ----------------------------------
76
72
  export const wikiPlanUpdateToolConfig = {
77
73
  title: 'Plan a wiki edit (dry-run, no writes)',
78
74
  description: 'Turn a natural-language instruction into a concrete set of page operations (create / patch / append) WITHOUT writing anything. Returns a planId valid for 15 minutes plus the proposed ops. Review the ops, then call orboto_wiki_apply_plan to commit. Wraps POST /spaces/:id/docs/plan-update.',
@@ -108,7 +104,6 @@ export function makeWikiApplyPlanHandler(client) {
108
104
  return text(`Applied plan ${input.planId}: ${res.touchedDocs.length} page(s) updated.`, { touchedDocs: res.touchedDocs });
109
105
  };
110
106
  }
111
- // --- orboto_wiki_record (convenience: plan + apply) ------------------------
112
107
  export const wikiRecordToolConfig = {
113
108
  title: 'Record a wiki update in one step (plan + apply)',
114
109
  description: 'Convenience wrapper that plans an edit from your instruction and immediately applies it - use mid-task to capture a fact or update a page without the two-step review loop. Internally calls plan-update then apply-plan. For a reviewable change, use orboto_wiki_plan_update instead.',
@@ -129,7 +124,6 @@ export function makeWikiRecordHandler(client) {
129
124
  return text(`Recorded: ${applied.touchedDocs.length} page(s) updated.`, { planId: plan.planId, touchedDocs: applied.touchedDocs });
130
125
  };
131
126
  }
132
- // --- orboto_wiki_append_section -------------------------------------------
133
127
  export const wikiAppendSectionToolConfig = {
134
128
  title: 'Append a section to a wiki page (idempotent)',
135
129
  description: 'Append a Markdown section to a doc. Idempotent: appending identical content a second time is a no-op, so this is safe to call repeatedly (e.g. across multiple ingests). Wraps POST /docs/:id/append-section.',
@@ -146,7 +140,6 @@ export function makeWikiAppendSectionHandler(client) {
146
140
  return text(res.appended ? 'Section appended.' : 'No change - an identical section is already present.', { appended: res.appended });
147
141
  };
148
142
  }
149
- // --- orboto_wiki_save_answer -----------------------------------------------
150
143
  export const wikiSaveAnswerToolConfig = {
151
144
  title: 'Save a Q&A answer as a wiki page',
152
145
  description: 'Turn an answer (e.g. from orboto_wiki_ask) into a curated wiki page with smart-links to its citations. Idempotent per (space, question): re-saving the same question updates the page instead of duplicating it. Wraps POST /ai/save-answer-to-wiki.',
@@ -166,7 +159,6 @@ export function makeWikiSaveAnswerHandler(client) {
166
159
  return text(res.created ? `Saved as a new wiki page (docId: ${res.docId}).` : `Updated the existing wiki page (docId: ${res.docId}).`, { docId: res.docId, created: res.created });
167
160
  };
168
161
  }
169
- // --- orboto_wiki_flag_stale ------------------------------------------------
170
162
  export const wikiFlagStaleToolConfig = {
171
163
  title: 'Flag (or unflag) a wiki page as possibly outdated',
172
164
  description: 'Set or clear the "may be outdated" flag on a page. The flag surfaces passively as a pill in the UI; it does not change the content. Pass stale=false to clear. Wraps POST /docs/:id/flag-stale.',
@@ -46,7 +46,6 @@ export const workNextToolConfig = {
46
46
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false },
47
47
  };
48
48
  export function makeWorkNextHandler(client) {
49
- // Per-connection rules-hash cache, same pattern as orboto_work_start.
50
49
  let lastKnownRulesHash;
51
50
  return async (args, extra) => {
52
51
  const token = mcpInstanceToken(args.agentSessionToken, extra);
@@ -64,7 +63,6 @@ export function makeWorkNextHandler(client) {
64
63
  });
65
64
  validateNextWorkEnvelope(res, args.peek);
66
65
  if (!res.reserved) {
67
- // ORB-1930 - a peek that found work: name it, reserve nothing.
68
66
  if (args.peek && res.candidate) {
69
67
  const c = res.candidate;
70
68
  return {
@@ -85,8 +83,6 @@ export function makeWorkNextHandler(client) {
85
83
  },
86
84
  };
87
85
  }
88
- // ORB-1774 - a pause is an operator decision, not a backoff situation:
89
- // tell the agent to idle, not to poll for a free slot.
90
86
  if (res.reason === 'autonomy_paused') {
91
87
  return {
92
88
  content: [{
@@ -164,7 +160,6 @@ export function makeWorkNextHandler(client) {
164
160
  }
165
161
  }
166
162
  }
167
- // ORB-1614 - see the comment on the other fmtDeps above.
168
163
  const fmtDeps = (edges) => edges.length === 0 ? '(none)' : edges.map((e) => `- [${e.ticketKey ?? '?'}] ${e.title ?? `External dependency (access restricted)${e.resolved ? ' - resolved' : ' - still open'}`}${e.statusName ? ` - ${e.statusName}` : ''}`).join('\n');
169
164
  lines.push('', '## Dependencies');
170
165
  lines.push('Blocked by:', fmtDeps(r.dependencies.blockedBy), 'Blocks:', fmtDeps(r.dependencies.blocks));
@@ -89,8 +89,6 @@ describe('orboto_work_start', () => {
89
89
  expect(post.method).toBe('POST');
90
90
  expect(post.url).toContain('/work-sessions/start');
91
91
  expect(post.body?.ticketId).toBe('t1');
92
- // The instance token must be sent unprompted, same contract as
93
- // orboto_work_session_start.
94
92
  expect(String(post.body?.agentSessionToken)).toMatch(/^mcp-/);
95
93
  const text = res.content[0].text;
96
94
  expect(text).toContain('Started a implementation work session on ACME-42');
@@ -1,19 +1,5 @@
1
1
  /**
2
2
  * ORB-1609 - work-session tools.
3
- *
4
- * A work session is the transactional record behind "I am working on
5
- * this ticket": it holds the lease, owns the timer, drives presence,
6
- * and carries the finish-time evidence (commit + verification). It is
7
- * the primitive `orboto work start / finish / next` (wave 3) is built
8
- * on, and it is what makes ownership visible ACROSS accounts - a lease
9
- * held by another team's bot is a 409 with the holder attached, not an
10
- * invisible collision discovered at push time.
11
- *
12
- * The three roles that are not `implementation` deliberately do NOT
13
- * reassign the ticket or move its status: a review / preflight /
14
- * integration session attaches to a ticket without pretending to own
15
- * its delivery. That is what makes one-ticket-one-commit workable for
16
- * work that produces no commit of its own.
17
3
  */
18
4
  import { z } from 'zod';
19
5
  import { OrbotoApiError } from '../orboto-client.js';
@@ -44,12 +30,6 @@ function describe(s) {
44
30
  const who = s.userFullName ?? s.userEmail ?? 'unknown';
45
31
  return ` - ${key} [${s.role}] ${who} - lease until ${s.leaseUntil}${s.commitSha ? ` (commit ${s.commitSha.slice(0, 8)})` : ''}`;
46
32
  }
47
- // ---------------------------------------------------------------------------
48
- // orboto_work_start - ORB-1611
49
- // ---------------------------------------------------------------------------
50
- // Mirrors apps/mcp/src/tools/session-start.ts's local shapes - kept
51
- // duplicated rather than imported, same choice that file already made for
52
- // its own bundle types (no shared MCP-side schema layer for these).
53
33
  export const workStartToolConfig = {
54
34
  title: 'Start a work session and load the full context bundle',
55
35
  description: 'ORB-1611 - the one-call ticket pickup. Acquires the (ticket, role) work lease with the exact same guarantees as orboto_work_session_start (exactly ONE active session per ticket+role workspace-wide, a conflict names the holder, resourceClaims apply atomically with the lease), AND in the SAME response returns the rules-hash ack (same semantics as orboto_session_start), the project primer, the ticket enriched with its description/status/priority, its checklists, its dependencies, that project\'s git connection health, and any other live sessions already on the ticket. This replaces the 8-15 separate calls (orboto_session_start, orboto_get_project_primer, orboto_get_ticket, orboto_get_checklists, orboto_list_ticket_dependencies, ...) a normal ticket pickup used to cost. Prefer this over orboto_work_session_start for picking up a ticket; use the plain tool only when you deliberately do not want the bundle (e.g. a mid-task lease renewal where you already have fresh context). A conflict never leaves a partial session behind - same rollback-on-conflict guarantee as orboto_work_session_start.',
@@ -70,12 +50,9 @@ export const workStartToolConfig = {
70
50
  onConflict: z.enum(['reject', 'queue']).optional()
71
51
  .describe('Only matters when `resourceClaims` is set. Default `reject`.'),
72
52
  }).shape,
73
- // takeover=true cancels another holder; hints describe the worst allowed call.
74
53
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true },
75
54
  };
76
55
  export function makeWorkStartHandler(client) {
77
- // Per-connection rules-hash cache, mirrored from session-start.ts's
78
- // makeSessionStartHandler - this closure lives for one MCP connection.
79
56
  let lastKnownRulesHash;
80
57
  return async (args, extra) => {
81
58
  const token = mcpInstanceToken(args.agentSessionToken, extra);
@@ -141,9 +118,6 @@ export function makeWorkStartHandler(client) {
141
118
  }
142
119
  }
143
120
  }
144
- // ORB-1614 - a cross-project blocker/dependent the caller cannot read
145
- // comes back with `title: null` - render a fixed placeholder instead
146
- // of the literal "null".
147
121
  const fmtDeps = (edges) => edges.length === 0 ? '(none)' : edges.map((e) => `- [${e.ticketKey ?? '?'}] ${e.title ?? `External dependency (access restricted)${e.resolved ? ' - resolved' : ' - still open'}`}${e.statusName ? ` - ${e.statusName}` : ''}`).join('\n');
148
122
  lines.push('', '## Dependencies');
149
123
  lines.push('Blocked by:', fmtDeps(res.dependencies.blockedBy), 'Blocks:', fmtDeps(res.dependencies.blocks));
@@ -216,9 +190,6 @@ export function makeWorkStartHandler(client) {
216
190
  }
217
191
  };
218
192
  }
219
- // ---------------------------------------------------------------------------
220
- // orboto_work_session_start
221
- // ---------------------------------------------------------------------------
222
193
  export const workSessionStartToolConfig = {
223
194
  title: 'Start (or renew) a work session on a ticket',
224
195
  description: 'Take the work lease on a ticket in a given role and start its timer. This is the coordination primitive: exactly ONE active session per (ticket, role) exists workspace-wide, so a second agent attempting the same role gets a conflict naming the current holder instead of silently colliding. Re-calling with the same agent instance renews your own lease and is a no-op otherwise - safe to call defensively. Roles other than `implementation` (review / preflight / integration) attach to the ticket WITHOUT reassigning it or moving its status, so a reviewing agent no longer has to fake a claim. The lease expires on its own (default 15 min, renewed automatically by your subsequent calls), so a crashed agent never wedges a ticket. Pass `takeover: true` only when you have decided to displace the current holder - their session is closed and their time booked, and the takeover is visible in history.',
@@ -239,7 +210,6 @@ export const workSessionStartToolConfig = {
239
210
  onConflict: z.enum(['reject', 'queue']).optional()
240
211
  .describe('Only matters when `resourceClaims` is set. Default `reject`: a conflicting write claim fails the WHOLE call with the conflicting holder(s) named - if this call would have created a brand-new session, that session is rolled back rather than left holding the lease without its claims. `queue`: the conflicting claim is accepted as `state: "waiting"` instead of failing; it is promoted automatically once the conflict clears (release, finish, or the next orboto_work_sessions read).'),
241
212
  }).shape,
242
- // The optional takeover can displace someone else's active session.
243
213
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true },
244
214
  };
245
215
  export function makeWorkSessionStartHandler(client) {
@@ -280,8 +250,6 @@ export function makeWorkSessionStartHandler(client) {
280
250
  if (err instanceof OrbotoApiError && err.status === 409) {
281
251
  const claimConflicts = parseClaimConflicts(err);
282
252
  if (claimConflicts && claimConflicts.length > 0) {
283
- // ORB-1610 - a resourceClaims conflict, distinct from the lease
284
- // conflict below: the body carries `claimConflicts`, not `holder`.
285
253
  return {
286
254
  content: [{
287
255
  type: 'text',
@@ -293,10 +261,6 @@ export function makeWorkSessionStartHandler(client) {
293
261
  isError: true,
294
262
  };
295
263
  }
296
- // OrbotoApiError carries the raw body string; the 409 payload is the
297
- // standard i18n error triple plus `holder`, which is the whole point
298
- // of the conflict response - the caller must not need a second call
299
- // to learn who has the ticket.
300
264
  let holder;
301
265
  try {
302
266
  holder = JSON.parse(err.body).holder;
@@ -321,9 +285,6 @@ export function makeWorkSessionStartHandler(client) {
321
285
  }
322
286
  };
323
287
  }
324
- // ---------------------------------------------------------------------------
325
- // orboto_work_session_finish
326
- // ---------------------------------------------------------------------------
327
288
  export const workSessionFinishToolConfig = {
328
289
  title: 'Finish a work session (books time, frees the lease)',
329
290
  description: 'End a work session: its timer is stopped and booked, the (ticket, role) lease is released for the next agent, and the evidence you pass (commit sha + which gates you ran) is recorded on the session. Idempotent - finishing an already-finished session succeeds and still absorbs late evidence, so a retrying harness never has to distinguish "already done" from "failed". This does NOT close the ticket; ticket status is a separate, deliberate decision (use orboto_close_ticket once the acceptance criteria are verified). Prefer orboto_work_finish (ORB-1612) for an `implementation` session that is actually done - it does this AND the ticket transition AND the completion note in one call.',
@@ -414,9 +375,6 @@ export function makeWorkFinishHandler(client) {
414
375
  };
415
376
  };
416
377
  }
417
- // ---------------------------------------------------------------------------
418
- // orboto_work_sessions
419
- // ---------------------------------------------------------------------------
420
378
  export const workSessionsToolConfig = {
421
379
  title: 'List live work sessions (who is working on what)',
422
380
  description: 'The coordination view: every live work lease you can see, or the sessions on one ticket. Call this BEFORE picking up work in a fleet - a ticket with a live `implementation` lease is already being worked, and starting on it anyway is how two agents produce conflicting commits. Unlike agent presence (which reports who is online), this reports who OWNS what, across accounts.',
@@ -451,9 +409,6 @@ export function makeWorkSessionsHandler(client) {
451
409
  return { content: [{ type: 'text', text }], structuredContent: { sessions: rows } };
452
410
  };
453
411
  }
454
- // ---------------------------------------------------------------------------
455
- // orboto_work_session_claims_add
456
- // ---------------------------------------------------------------------------
457
412
  export const workSessionClaimsAddToolConfig = {
458
413
  title: 'Add resource claims to a live work session',
459
414
  description: 'ORB-1610 - declare more resource claims on a session you already hold (from orboto_work_session_start), without touching the ticket lease or timer. Use this when you did not know the files/resources you would touch at start time, or need to widen scope mid-task. Same conflict rule as start: `write` claims conflict with any OVERLAPPING active write claim workspace-wide; `read` claims never conflict. Default `onConflict: "reject"` fails the WHOLE call (nothing is added) and names every conflicting holder; `queue` accepts the conflicting ones as `state: "waiting"`.',
@@ -499,9 +454,6 @@ export function makeWorkSessionClaimsAddHandler(client) {
499
454
  }
500
455
  };
501
456
  }
502
- // ---------------------------------------------------------------------------
503
- // orboto_work_session_claims_release
504
- // ---------------------------------------------------------------------------
505
457
  export const workSessionClaimsReleaseToolConfig = {
506
458
  title: 'Release resource claims from a live work session',
507
459
  description: 'ORB-1610 - drop specific resource claims (or all of them) from a session you hold, WITHOUT finishing the session or touching the ticket lease/timer. Releasing a granted write claim immediately runs a grant pass, so the earliest queued waiter for that resource is promoted as part of this call - useful once you know you are done touching a subtree but are not done with the ticket. Omit `claims` to release everything the session holds.',
@@ -58,8 +58,6 @@ describe('orboto_work_session_start', () => {
58
58
  expect(post.method).toBe('POST');
59
59
  expect(post.url).toContain('/work-sessions');
60
60
  expect(post.body?.ticketId).toBe('t1');
61
- // The instance token must be sent unprompted - without it the lease has
62
- // no owner to renew against.
63
61
  expect(String(post.body?.agentSessionToken)).toMatch(/^mcp-/);
64
62
  const text = res.content[0].text;
65
63
  expect(text).toContain('Started a implementation work session on ACME-42');
@@ -112,7 +110,6 @@ describe('orboto_work_session_start', () => {
112
110
  });
113
111
  const post = calls[calls.length - 1];
114
112
  expect(post.body).toMatchObject({ role: 'review', leaseSeconds: 3600, takeover: true });
115
- // A review session attaches without booking time by default.
116
113
  expect(res.content[0].text).toContain('No timer started for this role.');
117
114
  });
118
115
  });
@@ -350,8 +347,6 @@ describe('ORB-1610 - orboto_work_session_claims_release', () => {
350
347
  it('releases everything when claims is omitted, and still sends a body', async () => {
351
348
  const calls = stub([{ text: JSON.stringify({ ...SESSION, resourceClaims: [] }) }]);
352
349
  const res = await makeWorkSessionClaimsReleaseHandler(client)({ sessionId: 'ws1' });
353
- // A DELETE with a genuinely absent body 400s on the API's schema
354
- // (ORB-1610 finding) - the handler must always send at least `{}`.
355
350
  expect(calls[0].body).toEqual({});
356
351
  expect(res.content[0].text).toContain('Released every claim');
357
352
  });
package/dist/toolset.js CHANGED
@@ -2,100 +2,33 @@
2
2
  * ORB-1520 - manifest triage for the Code-Mode escape hatch (epic
3
3
  * ORB-1517).
4
4
  *
5
- * Eager-loading MCP clients (Codex, Cursor, Claude Desktop) pull EVERY
6
- * tool schema at connect time - with ~190 named tools that is tens of
7
- * thousands of tokens before the user says a word. The default manifest
8
- * is therefore the CURATED set below; the long tail stays fully
9
- * reachable through `orboto_api_search` + `orboto_api_call`, and the
10
- * complete named manifest is one opt-in away - and since ORB-1805 a
11
- * SMALLER one is too, for local models with an 8k/16k window:
12
- *
13
- * stdio: ORBOTO_MCP_TOOLSET=minimal | curated | full
14
- * http: .../mcp?toolset=minimal (or header x-orboto-toolset: full;
15
- * the query param rides on every request, so no session
16
- * persistence is involved)
17
- *
18
- * KEEP-LIST BASIS - measured, not guessed (ORB-1520): 30-day
19
- * `mcp_call_log` distribution on the core instance, 2026-08-10, 15.5k
20
- * calls total. Every tool with a meaningful call volume is in; the two
21
- * additions beyond the data are `orboto_my_tickets` (the natural "what
22
- * am I working on" entry for interactive desktop clients - the exact
23
- * audience this manifest serves; agent-dominated prod traffic
24
- * under-counts it) and the escape-hatch pair itself. The numbers live
25
- * in the ORB-1520 ticket comment.
26
- *
27
- * Tail tools are NOT deleted: their code, tests, chat mirrors and
28
- * drift-guard entries are untouched - only their REGISTRATION is
29
- * skipped in curated mode (the chat-tools parity guard reads server.ts
30
- * SOURCE, so it keeps covering the whole set in both modes).
5
+ * @see ORB-1805
31
6
  */
32
7
  /**
33
8
  * ORB-1805 - the small-context tier.
34
9
  *
35
- * Measured 2026-09-01: the CURATED manifest alone costs ~9.4k tokens of
36
- * tool schemas plus ~1k of instructions, so an 8k local model (LM Studio
37
- * / Ollama defaults) dies before the first user turn:
38
- * `n_keep: 13533 >= n_ctx: 8192`. `minimal` is the daily loop and
39
- * nothing else - orient, find, read, write, claim, close, track time -
40
- * plus the escape-hatch pair, which keeps the ENTIRE REST API reachable
41
- * from 12 tools.
42
- *
43
- * The pair is `claim` + `close_ticket` because that is the loop the
44
- * skill's own workflow mandates (SKILL.md "Claim = assign + status +
45
- * timer; stop the timer the moment you finish"), not
46
- * `work_next`/`work_finish` (those are not even in the curated set).
47
- *
48
- * Deliberately OUT, and why the loop still holds without them:
49
- * - `orboto_whoami` - measured at 357 tokens, 12 % of the whole budget,
50
- * for data `orboto_session_start` already prints ("You are <name>
51
- * (<email>)." + the workspace write language). The mandated first call
52
- * covers it; a second identity tool does not earn a tier this small.
53
- * - `orboto_check_similar` - `orboto_create_ticket` enforces the
54
- * duplicate block server-side (ORB-1471), so the mandated pre-create
55
- * check still happens, just inside the create.
56
- * - `orboto_my_tickets` / `orboto_list_tickets` - `orboto_session_start`
57
- * already returns the caller's in-progress work, and `orboto_search`
58
- * covers "anything about X".
59
- * - `orboto_move_ticket` - claim moves to in_progress and close moves to
60
- * done; any other transition goes through `orboto_api_call`.
61
- * - `orboto_help` / `orboto_response_expand` - both are recovery paths
62
- * for large payloads, which is the opposite of this tier's problem.
63
- *
64
- * MUST stay a subset of CURATED_TOOLS (asserted in toolset.test.ts): a
65
- * name that is not registered in curated cannot be registered here
66
- * either, and the tier ordering minimal < curated < full is what the
67
- * docs promise.
10
+ * @see ORB-1471
68
11
  */
69
12
  export const MINIMAL_TOOLS = new Set([
70
- // Orient (session_start also reports who you are + the write language)
71
13
  'orboto_session_start',
72
- // Find + read
73
14
  'orboto_search',
74
15
  'orboto_get_ticket',
75
- // Write
76
16
  'orboto_create_ticket',
77
17
  'orboto_update_ticket',
78
18
  'orboto_comment',
79
- // The claim -> close loop + its timer
80
19
  'orboto_claim',
81
20
  'orboto_close_ticket',
82
21
  'orboto_timer_start',
83
22
  'orboto_timer_stop',
84
- // Everything else, on demand (ORB-1959 measured orboto_messages at ~3k
85
- // chars - a fifth of this tier's whole window - so the inbox stays in
86
- // curated, where the default connection lives; minimal is the explicit
87
- // small-model opt-in and reaches the inbox through orboto_api_call).
88
23
  'orboto_api_search',
89
24
  'orboto_api_call',
90
25
  ]);
91
26
  /** Order matches the measured 30d call counts (see ticket comment). */
92
27
  export const CURATED_TOOLS = new Set([
93
- // Session + identity + orientation
94
28
  'orboto_session_start', // 662 calls/30d, and the binding-rules bootstrap
95
29
  'orboto_whoami', // 28
96
30
  'orboto_get_project_primer', // 131
97
31
  'orboto_list_projects', // 27, referenced by the server instructions
98
- // Reads
99
32
  'orboto_get_ticket', // 1764
100
33
  'orboto_list_tickets', // 161
101
34
  'orboto_my_tickets', // interactive-client staple (see header)
@@ -103,7 +36,6 @@ export const CURATED_TOOLS = new Set([
103
36
  'orboto_query', // 26 - the OQL surface, prior art of this pattern
104
37
  'orboto_check_similar', // 258 - mandated pre-create dup check
105
38
  'orboto_get_doc', // 86
106
- // Ticket writes (the strict claim->commit->close loop)
107
39
  'orboto_create_ticket', // 2069
108
40
  'orboto_bulk_create_tickets', // ORB-1694 - 48 measured runs of consecutive creates
109
41
  'orboto_bulk_add_ticket_dependencies', // ORB-1694 - longest dependency run: 27 calls
@@ -116,26 +48,15 @@ export const CURATED_TOOLS = new Set([
116
48
  'orboto_add_ticket_dependency', // 314 - the mandated blocker primitive
117
49
  'orboto_check', // 103 - acceptance-criteria checklists
118
50
  'orboto_create_milestone', // 42 - "milestone FIRST" rule for big features
119
- // Time tracking (the timer rules)
120
51
  'orboto_timer_start', // 34
121
52
  'orboto_timer_stop', // 772
122
53
  'orboto_log_time', // 130
123
54
  'orboto_get_timer', // 43
124
- // ORB-1959 - agent coordination on the DEFAULT connection. Without
125
- // these four an agent on curated could neither receive nor acknowledge
126
- // mail (broadcasts included, they land in the inbox since ORB-1959),
127
- // announce itself (heartbeat / presence) nor reach another agent - the
128
- // workspace rules demand exactly that, and every fresh `mcp add` forgot
129
- // the ?toolset=full workaround. Four small schemas; measured cost in
130
- // manifest-size.test.ts.
131
55
  'orboto_messages',
132
56
  'orboto_agent_notify',
133
57
  'orboto_agent_broadcast',
134
58
  'orboto_agent_heartbeat',
135
- // Transport plumbing + the escape hatch itself
136
59
  'orboto_response_expand',
137
- // ORB-1741 - the way back from one-line manifest summaries: full
138
- // guidance on demand. Must ride every manifest the summaries ride.
139
60
  'orboto_report_feedback', // ORB-1910 - an agent that hits a bug reports it without ?toolset=full
140
61
  'orboto_help', // ORB-1697 - the way back from a budget cut
141
62
  'orboto_api_search', // ORB-1518 - discovery half of the escape hatch
@@ -7,8 +7,6 @@ import { buildOrbotoMcpServer } from './server.js';
7
7
  import { CURATED_TOOLS, MINIMAL_TOOLS, resolveToolset, toolInToolset } from './toolset.js';
8
8
  beforeEach(() => {
9
9
  vi.restoreAllMocks();
10
- // buildOrbotoMcpServer fetches /agent-instructions at connect; offline
11
- // in unit tests - the catch keeps the fallback rules, which is fine.
12
10
  vi.spyOn(globalThis, 'fetch').mockRejectedValue(new Error('offline test'));
13
11
  delete process.env.ORBOTO_MCP_TOOLSET;
14
12
  });
@@ -17,9 +15,6 @@ afterEach(() => {
17
15
  delete process.env.ORBOTO_MCP_TOOLSET;
18
16
  });
19
17
  function registeredTools(server) {
20
- // The SDK keeps its registry on `_registeredTools` (verified against
21
- // @modelcontextprotocol/sdk 1.29). If a bump renames it, this helper
22
- // fails loudly rather than passing on an empty object.
23
18
  const tools = server._registeredTools;
24
19
  if (!tools || Object.keys(tools).length === 0) {
25
20
  throw new Error('SDK _registeredTools not found or empty - did the SDK internals change?');
@@ -37,9 +32,6 @@ describe('resolveToolset', () => {
37
32
  expect(resolveToolset('bogus', 'full')).toBe('full');
38
33
  expect(resolveToolset('bogus', 'nonsense')).toBe('curated');
39
34
  });
40
- // ORB-1805 - the small-context tier resolves on both surfaces and,
41
- // like the others, a typo still degrades to curated rather than
42
- // silently handing an 8k model 175 tools.
43
35
  it('minimal resolves from either surface and keeps the safe fallback', () => {
44
36
  expect(resolveToolset('minimal', undefined)).toBe('minimal');
45
37
  expect(resolveToolset(undefined, 'minimal')).toBe('minimal');
@@ -54,7 +46,6 @@ describe('toolInToolset', () => {
54
46
  expect(toolInToolset('orboto_claim', 'curated')).toBe(true);
55
47
  expect(toolInToolset('orboto_api_call', 'curated')).toBe(true);
56
48
  });
57
- // ORB-1959 - coordination is part of the default connection, not an opt-in.
58
49
  it('curated carries the four agent-coordination tools; minimal stays the 12-tool loop', () => {
59
50
  for (const name of ['orboto_messages', 'orboto_agent_notify', 'orboto_agent_broadcast', 'orboto_agent_heartbeat']) {
60
51
  expect(toolInToolset(name, 'curated'), name).toBe(true);
@@ -65,7 +56,6 @@ describe('toolInToolset', () => {
65
56
  expect(toolInToolset('orboto_claim', 'minimal')).toBe(true);
66
57
  expect(toolInToolset('orboto_api_search', 'minimal')).toBe(true);
67
58
  expect(toolInToolset('orboto_api_call', 'minimal')).toBe(true);
68
- // In curated, out of minimal.
69
59
  expect(toolInToolset('orboto_list_projects', 'minimal')).toBe(false);
70
60
  expect(toolInToolset('orboto_help', 'minimal')).toBe(false);
71
61
  expect(toolInToolset('orboto_wiki_ask', 'minimal')).toBe(false);
@@ -73,10 +63,6 @@ describe('toolInToolset', () => {
73
63
  });
74
64
  describe('ORB-1805 - the tiers nest', () => {
75
65
  it('MINIMAL_TOOLS is a strict subset of CURATED_TOOLS', () => {
76
- // A name that is not registered in curated cannot register in
77
- // minimal either - the tier ordering minimal < curated < full is
78
- // what the docs promise, and a typo here would silently ship an
79
- // empty slot.
80
66
  const notInCurated = [...MINIMAL_TOOLS].filter((t) => !CURATED_TOOLS.has(t));
81
67
  expect(notInCurated).toEqual([]);
82
68
  expect(MINIMAL_TOOLS.size).toBeLessThan(CURATED_TOOLS.size);
@@ -115,9 +101,6 @@ describe('buildOrbotoMcpServer toolset gating', () => {
115
101
  it('curated instructions point at the escape hatch; full keeps get_checklists', async () => {
116
102
  const curated = await buildOrbotoMcpServer(baseOpts);
117
103
  const full = await buildOrbotoMcpServer({ ...baseOpts, toolset: 'full' });
118
- // `_instructions` verified against @modelcontextprotocol/sdk 1.29
119
- // (server/index.js). Assert non-empty first so an SDK rename fails
120
- // this test loudly instead of green-skipping.
121
104
  const instructionsOf = (s) => String(s.server._instructions ?? '');
122
105
  const curatedText = instructionsOf(curated);
123
106
  const fullText = instructionsOf(full);
@@ -137,12 +120,9 @@ describe('buildOrbotoMcpServer toolset gating', () => {
137
120
  const minimal = await buildOrbotoMcpServer({ ...baseOpts, toolset: 'minimal' });
138
121
  const text = String(minimal.server._instructions ?? '');
139
122
  expect(text.length).toBeGreaterThan(100);
140
- // The two things a minimal-tier agent cannot work without.
141
123
  expect(text).toContain('orboto_session_start');
142
124
  expect(text).toContain('orboto_api_search');
143
125
  expect(text).toContain('MINIMAL');
144
- // ORB-1805 - the workspace rules block is deliberately absent; it
145
- // arrives via orboto_session_start, on the caller's budget.
146
126
  expect(text).not.toContain('Working rules for this workspace:');
147
127
  });
148
128
  it('minimal drops the display title; curated keeps it', async () => {
@@ -15,7 +15,6 @@ import { createRequire } from 'node:module';
15
15
  import { VERSION } from './version.js';
16
16
  const SRC = dirname(fileURLToPath(import.meta.url));
17
17
  const pkg = createRequire(import.meta.url)('../package.json');
18
- // version.ts legitimately holds the '0.0.0' runtime fallback literal.
19
18
  const ALLOWED = new Set(['version.ts']);
20
19
  function walk(dir, acc = []) {
21
20
  for (const entry of readdirSync(dir)) {
@@ -35,9 +34,6 @@ describe('ORB-1173 - version literal guard', () => {
35
34
  });
36
35
  it('no hardcoded semver string literal in src (use VERSION instead)', () => {
37
36
  const offenders = [];
38
- // A quoted X.Y.Z literal - what a drifting hardcoded version looks
39
- // like. Comments (// v0.110.1) and ticket refs (ORB-1166) are not
40
- // quoted semver, so they don't trip this.
41
37
  const semverLiteral = /['"]\d+\.\d+\.\d+['"]/;
42
38
  for (const file of walk(SRC)) {
43
39
  const text = readFileSync(file, 'utf8');
package/dist/version.js CHANGED
@@ -1,17 +1,5 @@
1
1
  /**
2
2
  * ORB-1166 - single source of truth for the MCP server version.
3
- *
4
- * Read from package.json (which release.mjs bumps) so serverInfo.version
5
- * + the User-Agent header always reflect the real release instead of a
6
- * hardcoded literal that drifts.
7
- *
8
- * WRAPPED IN try/catch on purpose: the Docker runtime image is stripped
9
- * to `dist/` + `node_modules/` and may not ship package.json at the path
10
- * `createRequire` resolves. A bare `require('../package.json')` at module
11
- * load threw there and crash-looped the MCP container (took prod down in
12
- * v0.110.1). The catch guarantees this module can NEVER crash the server
13
- * on boot; the Dockerfile now also copies package.json so the real
14
- * version resolves in-container.
15
3
  */
16
4
  import { createRequire } from 'node:module';
17
5
  function readVersion() {
@@ -2,13 +2,7 @@
2
2
  * ORB-1738 - declared output schemas ADVERTISE the response budget's
3
3
  * `__truncation` marker.
4
4
  *
5
- * Second instance of the ORB-1733 class: the budget injects the marker
6
- * into structuredContent, the SDK advertises output schemas with
7
- * additionalProperties:false, and strict clients validate structured
8
- * content against that - so every over-budget response of a tool with a
9
- * declared outputSchema was rejected wholesale (orboto_list_projects
10
- * failed on EVERY call of this 57-project workspace; reported via the
11
- * agent inbox 2026-08-28).
5
+ * @see ORB-1733
12
6
  */
13
7
  import { describe, it, expect } from 'vitest';
14
8
  import { listProjectsToolConfig } from './tools/list-projects.js';
@@ -40,19 +34,13 @@ describe('ORB-1738 - __truncation is part of the advertised output schema', () =
40
34
  };
41
35
  const { result } = applyResponseBudget('orboto_list_projects', bigResult);
42
36
  const sc = result.structuredContent;
43
- // The budget DID cut and marked it.
44
37
  expect(sc.__truncation).toBeDefined();
45
38
  const shape = advertisedOutputShape(listProjectsToolConfig.outputSchema);
46
- // Before the fix this was false - the marker was undeclared.
47
39
  expect(strictClientAccepts(shape, sc)).toBe(true);
48
- // And the marker itself matches BOTH the exact runtime shape and the
49
- // compact schema actually advertised on the wire (ORB-1805).
50
40
  expect(TruncationBlockSchema.safeParse(sc.__truncation).success).toBe(true);
51
41
  expect(TruncationBlockAdvertisedSchema.safeParse(sc.__truncation).success).toBe(true);
52
42
  });
53
43
  it('ORB-1805 - the advertised marker schema is strictly more permissive than the exact one', () => {
54
- // Anything the exact shape accepts, the advertised one must accept -
55
- // otherwise the diet would start rejecting valid payloads.
56
44
  const block = {
57
45
  handle: 'h1', budgetChars: 4000, originalChars: 9000, omittedChars: 5000,
58
46
  omitted: [{ path: 'items', kind: 'array', omittedItems: 12, keptItems: 3 }],
@@ -60,7 +48,6 @@ describe('ORB-1738 - __truncation is part of the advertised output schema', () =
60
48
  };
61
49
  expect(TruncationBlockSchema.safeParse(block).success).toBe(true);
62
50
  expect(TruncationBlockAdvertisedSchema.safeParse(block).success).toBe(true);
63
- // A future field added to the runtime block must not break clients.
64
51
  expect(TruncationBlockAdvertisedSchema.safeParse({ ...block, futureField: 1 }).success).toBe(true);
65
52
  });
66
53
  it('the budget layer adds exactly ONE key - __truncation - and nothing else (class sweep)', () => {