@unbrained/pm-cli 2026.8.25 → 2026.8.26

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 (116) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/CHANGELOG.md +33 -0
  3. package/dist/cli-bundle/bundle-manifest.json +133 -133
  4. package/dist/cli-bundle/chunks/chunk-E2GCFJSU.js +35 -0
  5. package/dist/cli-bundle/chunks/{chunk-QKGMHGEI.js → chunk-ES25LX3D.js} +51 -51
  6. package/dist/cli-bundle/chunks/{chunk-TPQIBSL2.js → chunk-FRDWWB6R.js} +2 -2
  7. package/dist/cli-bundle/chunks/{chunk-65MHLHAA.js → chunk-ICQ3RVIY.js} +2 -2
  8. package/dist/cli-bundle/chunks/chunk-IV64RJVE.js +13 -0
  9. package/dist/cli-bundle/chunks/{chunk-QLUORNIB.js → chunk-KBFP3E4E.js} +2 -2
  10. package/dist/cli-bundle/chunks/{chunk-T2ENPRXF.js → chunk-MVYLQ67M.js} +2 -2
  11. package/dist/cli-bundle/chunks/{chunk-5I5RWIJC.js → chunk-S4U76VZF.js} +2 -2
  12. package/dist/cli-bundle/chunks/{chunk-OWHNAR2B.js → chunk-UKBCRPA2.js} +2 -2
  13. package/dist/cli-bundle/chunks/chunk-WRHJ3MB6.js +8 -0
  14. package/dist/cli-bundle/chunks/{chunk-244MI4GS.js → chunk-ZNRLJ54C.js} +60 -60
  15. package/dist/cli-bundle/chunks/{register-list-query-XVN2ZLI7.js → register-list-query-EMCPMICY.js} +2 -2
  16. package/dist/cli-bundle/chunks/{register-mutation-QCKAEGIJ.js → register-mutation-OJ67ABCB.js} +2 -2
  17. package/dist/cli-bundle/chunks/{register-operations-SDEAXE7E.js → register-operations-H2GLP7LT.js} +2 -2
  18. package/dist/cli-bundle/chunks/{register-setup-LXVBRCJ3.js → register-setup-GLZAHLVI.js} +2 -2
  19. package/dist/cli-bundle/focused-chunks/{chunk-NOOZGIXP.js → chunk-4K2II4TV.js} +2 -2
  20. package/dist/cli-bundle/focused-chunks/{chunk-IBZZZGK3.js → chunk-4XNH2HM7.js} +2 -2
  21. package/dist/cli-bundle/focused-chunks/{chunk-VXWATRFL.js → chunk-57XY346D.js} +2 -2
  22. package/dist/cli-bundle/focused-chunks/{chunk-GQR3WH3F.js → chunk-66VGB23P.js} +2 -2
  23. package/dist/cli-bundle/focused-chunks/{chunk-42S3GGZ7.js → chunk-72T6JGAE.js} +45 -45
  24. package/dist/cli-bundle/focused-chunks/{chunk-RAFKLNZX.js → chunk-FXDLT6FL.js} +2 -2
  25. package/dist/cli-bundle/focused-chunks/{chunk-H5JZEIQV.js → chunk-IBHXMFE7.js} +2 -2
  26. package/dist/cli-bundle/focused-chunks/{chunk-N7W67YIG.js → chunk-LV5N3LK5.js} +2 -2
  27. package/dist/cli-bundle/focused-chunks/{chunk-2UIWOP3O.js → chunk-MMXUPDDJ.js} +2 -2
  28. package/dist/cli-bundle/focused-chunks/{chunk-ONYQCALA.js → chunk-P2E6LDAE.js} +6 -6
  29. package/dist/cli-bundle/focused-chunks/{chunk-XUQPEKRN.js → chunk-TMJDFHVD.js} +2 -2
  30. package/dist/cli-bundle/focused-chunks/{chunk-OVJL6NZE.js → chunk-UYBA57GY.js} +4 -4
  31. package/dist/cli-bundle/main.js +2 -2
  32. package/dist/cli-bundle/sdk-authoring.js +1 -1
  33. package/dist/cli-bundle/sdk-contracts.js +1 -1
  34. package/dist/cli-bundle/sdk-core.js +31 -31
  35. package/dist/cli-bundle/sdk-governance.js +1 -1
  36. package/dist/cli-bundle/sdk-graph.js +1 -1
  37. package/dist/cli-bundle/sdk-merge.js +18 -18
  38. package/dist/cli-bundle/sdk-query.js +1 -1
  39. package/dist/cli-bundle/sdk-runtime.js +1 -1
  40. package/dist/cli-bundle/sdk-testing.js +1 -1
  41. package/dist/cli-bundle/sdk.js +7 -6
  42. package/dist/core/extensions/manifest-schema.d.ts +20 -0
  43. package/dist/core/extensions/manifest-schema.js +28 -10
  44. package/dist/mcp/http-server.d.ts +60 -0
  45. package/dist/mcp/http-server.js +451 -0
  46. package/dist/mcp/legacy-adapter.d.ts +50 -0
  47. package/dist/mcp/legacy-adapter.js +64 -0
  48. package/dist/mcp/server.d.ts +42 -9
  49. package/dist/mcp/server.js +457 -58
  50. package/dist/sdk/agent/refusal-closure-census.d.ts +6 -2
  51. package/dist/sdk/agent/refusal-closure-census.js +16 -8
  52. package/dist/sdk/compose.d.ts +4 -1
  53. package/dist/sdk/compose.js +23 -36
  54. package/dist/sdk/extension/author-manifest.d.ts +22 -0
  55. package/dist/sdk/extension/author-manifest.js +93 -0
  56. package/dist/sdk/extension.js +6 -3
  57. package/dist/sdk/generated/generated-error-code-catalog-part-1.js +20 -5
  58. package/dist/sdk/generated/generated-error-code-catalog-part-2.js +6 -3
  59. package/dist/sdk/governance/health.js +7 -2
  60. package/dist/sdk/index.d.ts +6 -0
  61. package/dist/sdk/index.js +8 -2
  62. package/dist/sdk/mcp/authorization.d.ts +134 -0
  63. package/dist/sdk/mcp/authorization.js +405 -0
  64. package/dist/sdk/mcp/interactions.d.ts +118 -0
  65. package/dist/sdk/mcp/interactions.js +337 -0
  66. package/dist/sdk/mcp/protocol.d.ts +142 -0
  67. package/dist/sdk/mcp/protocol.js +174 -0
  68. package/dist/sdk/mcp/subscriptions.d.ts +65 -0
  69. package/dist/sdk/mcp/subscriptions.js +212 -0
  70. package/dist/sdk/mcp/tasks.d.ts +107 -0
  71. package/dist/sdk/mcp/tasks.js +431 -0
  72. package/dist/sdk/mcp/transport.d.ts +30 -0
  73. package/dist/sdk/mcp/transport.js +261 -0
  74. package/dist/sdk/merge/receipts.d.ts +16 -0
  75. package/dist/sdk/merge/receipts.js +9 -8
  76. package/dist/sdk/runtime-primitives.d.ts +1 -1
  77. package/dist/sdk/runtime-primitives.js +3 -3
  78. package/dist/sdk/test/execution.d.ts +6 -0
  79. package/dist/sdk/test/execution.js +32 -3
  80. package/docs/AGENT_PROVENANCE_ADR.md +6 -4
  81. package/docs/AGENT_RUNTIME_PRIMITIVES.md +6 -5
  82. package/docs/CLAUDE_CODE_PLUGIN.md +12 -5
  83. package/docs/DIAGNOSTIC_OUTPUT_CONTRACTS.md +8 -0
  84. package/docs/EXTENSIONS.md +3 -4
  85. package/docs/MCP_2026_07_28.md +138 -0
  86. package/docs/MCP_2026_07_28_CONFORMANCE.md +30 -0
  87. package/docs/MCP_REMOTE_TRANSPORT_SECURITY.md +180 -0
  88. package/docs/README.md +4 -0
  89. package/docs/RELEASING.md +3 -2
  90. package/docs/SDK.md +22 -1
  91. package/docs/SDK_AGENT_SESSION_CONTEXT.md +18 -13
  92. package/docs/SDK_MCP_INTERACTIONS.md +227 -0
  93. package/docs/TESTING.md +4 -0
  94. package/docs/generated/REFUSAL_CLOSURE_CENSUS.md +9 -8
  95. package/marketplace.json +2 -2
  96. package/package.json +7 -5
  97. package/packages/pm-beads/package.json +1 -1
  98. package/packages/pm-calendar/package.json +1 -1
  99. package/packages/pm-command-kit/package.json +1 -1
  100. package/packages/pm-digital-twin/package.json +1 -1
  101. package/packages/pm-governance-audit/package.json +1 -1
  102. package/packages/pm-guide-shell/package.json +1 -1
  103. package/packages/pm-kanban/package.json +1 -1
  104. package/packages/pm-lifecycle-hooks/package.json +1 -1
  105. package/packages/pm-linked-test-adapters/package.json +1 -1
  106. package/packages/pm-search-advanced/package.json +1 -1
  107. package/packages/pm-templates/package.json +1 -1
  108. package/packages/pm-todos/package.json +1 -1
  109. package/packages/pm-vcs/package.json +1 -1
  110. package/plugins/pm-claude/.claude-plugin/plugin.json +1 -1
  111. package/plugins/pm-codex/.codex-plugin/plugin.json +1 -1
  112. package/scripts/finalize-build.mjs +1 -0
  113. package/sdk/public-surface.json +628 -2
  114. package/dist/cli-bundle/chunks/chunk-2F3LUFMW.js +0 -8
  115. package/dist/cli-bundle/chunks/chunk-6C7GIMIL.js +0 -13
  116. package/dist/cli-bundle/chunks/chunk-YQMYF3YD.js +0 -35
@@ -0,0 +1,261 @@
1
+ /**
2
+ * @module sdk/mcp/transport
3
+ *
4
+ * Implements MCP 2026-07-28 Streamable HTTP header encoding, schema-derived
5
+ * custom header extraction, and body/header parity validation.
6
+ */
7
+
8
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="5ffe7128-0fbb-59af-89ef-74d5d799a00d")}catch(e){}}();
9
+ import { PM_MCP_ERROR_CODES, PM_MCP_PROTOCOL_VERSION, PmMcpProtocolError, isMcpRecord, resolveMcpRequestContext, } from "./protocol.js";
10
+ const BASE64_SENTINEL_PATTERN = /^=\?base64\?([A-Za-z0-9+/]*={0,2})\?=$/u;
11
+ const HEADER_NAME_TOKEN_PATTERN = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/u;
12
+ const PLAIN_HEADER_VALUE_PATTERN = /^[\t\x20-\x7E]*$/u;
13
+ const REQUIRED_NAME_METHODS = new Set([
14
+ "prompts/get",
15
+ "resources/read",
16
+ "tools/call",
17
+ ]);
18
+ function normalizedHeaders(headers) {
19
+ const normalized = new Map();
20
+ for (const [name, value] of Object.entries(headers)) {
21
+ if (value === undefined)
22
+ continue;
23
+ const entry = typeof value === "string" ? value : value.join(", ");
24
+ normalized.set(name.toLowerCase(), entry);
25
+ }
26
+ return normalized;
27
+ }
28
+ function failHeader(message, header) {
29
+ throw new PmMcpProtocolError(message, PM_MCP_ERROR_CODES.headerMismatch, {
30
+ header,
31
+ });
32
+ }
33
+ /** Encode a name or primitive parameter into the protocol's safe header form. */
34
+ export function encodeMcpHttpHeaderValue(value) {
35
+ const serialized = typeof value === "boolean" ? String(value).toLowerCase() : String(value);
36
+ if (serialized.length > 0 &&
37
+ serialized.trim() === serialized &&
38
+ PLAIN_HEADER_VALUE_PATTERN.test(serialized) &&
39
+ !BASE64_SENTINEL_PATTERN.test(serialized)) {
40
+ return serialized;
41
+ }
42
+ return `=?base64?${Buffer.from(serialized, "utf8").toString("base64")}?=`;
43
+ }
44
+ /** Decode and validate a mirrored MCP header value. */
45
+ export function decodeMcpHttpHeaderValue(value, header) {
46
+ const sentinel = BASE64_SENTINEL_PATTERN.exec(value);
47
+ if (sentinel) {
48
+ const encoded = sentinel[1];
49
+ const decoded = Buffer.from(encoded, "base64");
50
+ if (decoded.toString("base64") !== encoded || decoded.length === 0) {
51
+ return failHeader("Malformed MCP Base64 header value", header);
52
+ }
53
+ return decoded.toString("utf8");
54
+ }
55
+ if (value.length === 0 ||
56
+ value.trim() !== value ||
57
+ !PLAIN_HEADER_VALUE_PATTERN.test(value)) {
58
+ return failHeader("Malformed MCP header value", header);
59
+ }
60
+ return value;
61
+ }
62
+ function containsHeaderAnnotation(value) {
63
+ if (Array.isArray(value))
64
+ return value.some(containsHeaderAnnotation);
65
+ if (!isMcpRecord(value))
66
+ return false;
67
+ return (Object.prototype.hasOwnProperty.call(value, "x-mcp-header") ||
68
+ Object.values(value).some(containsHeaderAnnotation));
69
+ }
70
+ /** Validate and normalize one schema-derived custom-header annotation. */
71
+ function parseMcpHeaderAnnotation(propertySchema, propertyPath, names) {
72
+ const annotation = propertySchema["x-mcp-header"];
73
+ if (annotation === undefined)
74
+ return undefined;
75
+ const type = propertySchema.type;
76
+ if (typeof annotation !== "string" ||
77
+ !HEADER_NAME_TOKEN_PATTERN.test(annotation) ||
78
+ !["boolean", "integer", "string"].includes(String(type)) ||
79
+ names.has(annotation.toLowerCase())) {
80
+ throw new PmMcpProtocolError("Invalid MCP x-mcp-header annotation", PM_MCP_ERROR_CODES.invalidParams, { field: propertyPath.join("."), annotation });
81
+ }
82
+ names.add(annotation.toLowerCase());
83
+ return {
84
+ name: annotation,
85
+ path: propertyPath,
86
+ type: type,
87
+ };
88
+ }
89
+ /** Parse valid statically reachable custom-header annotations from a tool schema. */
90
+ export function collectMcpHeaderAnnotations(schema) {
91
+ if (!isMcpRecord(schema)) {
92
+ throw new PmMcpProtocolError("Invalid MCP tool schema", PM_MCP_ERROR_CODES.invalidParams, { field: "inputSchema", expected: "object" });
93
+ }
94
+ const annotations = [];
95
+ const names = new Set();
96
+ const visitProperties = (node, path) => {
97
+ const properties = node.properties;
98
+ if (properties === undefined)
99
+ return;
100
+ if (!isMcpRecord(properties)) {
101
+ throw new PmMcpProtocolError("Invalid MCP tool schema properties", PM_MCP_ERROR_CODES.invalidParams, { field: "inputSchema.properties" });
102
+ }
103
+ for (const [property, propertySchema] of Object.entries(properties)) {
104
+ if (!isMcpRecord(propertySchema))
105
+ continue;
106
+ const propertyPath = [...path, property];
107
+ const annotation = parseMcpHeaderAnnotation(propertySchema, propertyPath, names);
108
+ if (annotation)
109
+ annotations.push(annotation);
110
+ for (const [keyword, nested] of Object.entries(propertySchema)) {
111
+ if (keyword !== "properties" &&
112
+ keyword !== "x-mcp-header" &&
113
+ containsHeaderAnnotation(nested)) {
114
+ throw new PmMcpProtocolError("MCP x-mcp-header is not statically reachable", PM_MCP_ERROR_CODES.invalidParams, { field: propertyPath.join("."), keyword });
115
+ }
116
+ }
117
+ visitProperties(propertySchema, propertyPath);
118
+ }
119
+ };
120
+ for (const [keyword, nested] of Object.entries(schema)) {
121
+ if (keyword !== "properties" && containsHeaderAnnotation(nested)) {
122
+ throw new PmMcpProtocolError("MCP x-mcp-header is not statically reachable", PM_MCP_ERROR_CODES.invalidParams, { field: "inputSchema", keyword });
123
+ }
124
+ }
125
+ visitProperties(schema, []);
126
+ return annotations;
127
+ }
128
+ function valueAtPath(value, path) {
129
+ let current = value;
130
+ for (const part of path) {
131
+ if (!isMcpRecord(current))
132
+ return undefined;
133
+ current = current[part];
134
+ }
135
+ return current;
136
+ }
137
+ function mirroredName(method, params) {
138
+ const value = method === "resources/read" ? params.uri : params.name;
139
+ return typeof value === "string" ? value : undefined;
140
+ }
141
+ function buildMcpCustomRequestHeaders(params, toolSchema) {
142
+ const argumentsValue = isMcpRecord(params.arguments) ? params.arguments : {};
143
+ const headers = {};
144
+ for (const annotation of collectMcpHeaderAnnotations(toolSchema)) {
145
+ const value = valueAtPath(argumentsValue, annotation.path);
146
+ if (value === undefined || value === null)
147
+ continue;
148
+ const valid = (annotation.type === "integer" &&
149
+ typeof value === "number" &&
150
+ Number.isSafeInteger(value)) ||
151
+ (annotation.type === "boolean" && typeof value === "boolean") ||
152
+ (annotation.type === "string" && typeof value === "string");
153
+ if (!valid) {
154
+ throw new PmMcpProtocolError("Invalid MCP custom header argument", PM_MCP_ERROR_CODES.invalidParams, { field: annotation.path.join("."), expected: annotation.type });
155
+ }
156
+ headers[`Mcp-Param-${annotation.name}`] = encodeMcpHttpHeaderValue(value);
157
+ }
158
+ return headers;
159
+ }
160
+ /** Build required Streamable HTTP headers for one modern client request. */
161
+ export function buildMcpHttpRequestHeaders(input) {
162
+ const params = isMcpRecord(input.request.params)
163
+ ? input.request.params
164
+ : undefined;
165
+ const context = resolveMcpRequestContext(params);
166
+ const method = input.request.method;
167
+ if (typeof method !== "string" || method.length === 0) {
168
+ return failHeader("Missing MCP request method", "Mcp-Method");
169
+ }
170
+ const headers = {
171
+ Accept: "application/json, text/event-stream",
172
+ "Content-Type": "application/json",
173
+ "MCP-Protocol-Version": context.protocolVersion,
174
+ "Mcp-Method": method,
175
+ };
176
+ const name = mirroredName(method, params);
177
+ if (REQUIRED_NAME_METHODS.has(method)) {
178
+ if (name === undefined)
179
+ return failHeader("Missing MCP request name", "Mcp-Name");
180
+ headers["Mcp-Name"] = encodeMcpHttpHeaderValue(name);
181
+ }
182
+ if (method === "tools/call" && input.toolSchema !== undefined) {
183
+ Object.assign(headers, buildMcpCustomRequestHeaders(params, input.toolSchema));
184
+ }
185
+ return headers;
186
+ }
187
+ function validateMcpRequiredRequestHeaders(headers, request) {
188
+ const method = request.method;
189
+ if (typeof method !== "string" || method.length === 0) {
190
+ failHeader("Missing MCP request method", "Mcp-Method");
191
+ }
192
+ const params = isMcpRecord(request.params) ? request.params : undefined;
193
+ const expectedProtocol = resolveMcpRequestContext(params).protocolVersion;
194
+ if (headers.get("mcp-protocol-version") !== expectedProtocol) {
195
+ failHeader("MCP protocol header does not match request metadata", "MCP-Protocol-Version");
196
+ }
197
+ if (headers.get("mcp-method") !== method) {
198
+ failHeader("MCP method header does not match request body", "Mcp-Method");
199
+ }
200
+ if (REQUIRED_NAME_METHODS.has(method)) {
201
+ const headerName = headers.get("mcp-name");
202
+ const name = mirroredName(method, params);
203
+ if (headerName === undefined ||
204
+ name === undefined ||
205
+ decodeMcpHttpHeaderValue(headerName, "Mcp-Name") !== name) {
206
+ failHeader("MCP name header does not match request body", "Mcp-Name");
207
+ }
208
+ }
209
+ if (headers.has("last-event-id")) {
210
+ // The preceding match is an mcp-deprecation-negative-control: no resume/redelivery.
211
+ failHeader("MCP SSE resumability is not supported", "Last-Event-ID");
212
+ }
213
+ }
214
+ function customHeaderMatches(annotation, value, decoded) {
215
+ if (annotation.type === "integer") {
216
+ return (typeof value === "number" &&
217
+ Number.isSafeInteger(value) &&
218
+ Number(decoded) === value);
219
+ }
220
+ if (annotation.type === "boolean") {
221
+ return typeof value === "boolean" && decoded === String(value);
222
+ }
223
+ return typeof value === "string" && decoded === value;
224
+ }
225
+ function validateMcpCustomRequestHeaders(input) {
226
+ const argumentsValue = isMcpRecord(input.params.arguments)
227
+ ? input.params.arguments
228
+ : {};
229
+ for (const annotation of collectMcpHeaderAnnotations(input.toolSchema)) {
230
+ const value = valueAtPath(argumentsValue, annotation.path);
231
+ const header = `Mcp-Param-${annotation.name}`;
232
+ const mirrored = input.headers.get(header.toLowerCase());
233
+ if (value === undefined || value === null) {
234
+ if (mirrored !== undefined)
235
+ failHeader("Unexpected MCP parameter header", header);
236
+ continue;
237
+ }
238
+ if (mirrored === undefined)
239
+ failHeader("Missing MCP parameter header", header);
240
+ if (!customHeaderMatches(annotation, value, decodeMcpHttpHeaderValue(mirrored, header))) {
241
+ failHeader("MCP parameter header does not match request body", header);
242
+ }
243
+ }
244
+ }
245
+ /** Validate required Streamable HTTP headers against the JSON-RPC body. */
246
+ export function validateMcpHttpRequestHeaders(input) {
247
+ const headers = normalizedHeaders(input.headers);
248
+ const method = typeof input.request.method === "string" ? input.request.method : "";
249
+ validateMcpRequiredRequestHeaders(headers, input.request);
250
+ if (method !== "tools/call" || input.toolSchema === undefined)
251
+ return;
252
+ validateMcpCustomRequestHeaders({
253
+ headers,
254
+ params: input.request.params,
255
+ toolSchema: input.toolSchema,
256
+ });
257
+ }
258
+ /** Required protocol version exposed for clients constructing headers manually. */
259
+ export const PM_MCP_HTTP_PROTOCOL_VERSION = PM_MCP_PROTOCOL_VERSION;
260
+ //# sourceMappingURL=transport.js.map
261
+ //# debugId=5ffe7128-0fbb-59af-89ef-74d5d799a00d
@@ -77,6 +77,17 @@ export interface MergeReceiptEvidenceScan {
77
77
  /** Number of JSON receipt candidates that could not be validated safely. */
78
78
  invalid_evidence_count: number;
79
79
  }
80
+ declare function prepareReceiptSettlement(params: {
81
+ receiptPath: string;
82
+ receiptId: string;
83
+ evidenceSource: "clone_local" | "durable";
84
+ reconciledAt: string;
85
+ readReceipt?: (receiptPath: string) => Promise<string | null>;
86
+ }): Promise<{
87
+ path: string;
88
+ content: string;
89
+ fingerprint: string;
90
+ } | null>;
80
91
  /** Convert a raw receipt into privacy-safe history context. */
81
92
  export declare function summarizeMergeReceipt(receipt: MergeDecisionReceipt): MergeDecisionReceiptSummary;
82
93
  /** Persist one item-driver outcome in the clone-local Git directory. */
@@ -123,3 +134,8 @@ export declare function runMergeReceiptReport(options: {
123
134
  /** Repository directory to inspect; defaults to the process working directory. */
124
135
  cwd?: string;
125
136
  }): Promise<MergeReceiptReport>;
137
+ /** Test-only seams for deterministic receipt-boundary fault coverage. */
138
+ export declare const _testOnlyMergeReceipts: {
139
+ prepareReceiptSettlement: typeof prepareReceiptSettlement;
140
+ };
141
+ export {};
@@ -7,7 +7,7 @@
7
7
  * value for the coordinator that performed the merge.
8
8
  */
9
9
 
10
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="5add49c0-25be-5159-abfc-6c7ae9272f6b")}catch(e){}}();
10
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="145a6d31-5e14-5b0b-95fb-6f837d11e2f4")}catch(e){}}();
11
11
  import { execFile } from "node:child_process";
12
12
  import { randomUUID } from "node:crypto";
13
13
  import { readdir } from "node:fs/promises";
@@ -222,7 +222,7 @@ async function readBoundedRegularReceiptFile(receiptPath) {
222
222
  async function prepareReceiptSettlement(params) {
223
223
  let raw;
224
224
  try {
225
- raw = await readBoundedRegularReceiptFile(params.receiptPath);
225
+ raw = await (params.readReceipt ?? readBoundedRegularReceiptFile)(params.receiptPath);
226
226
  }
227
227
  catch (error) {
228
228
  if (isFileAbsentError(error))
@@ -467,6 +467,10 @@ export async function markMergeReceiptReconciled(cwd, receipt, options = {}) {
467
467
  }
468
468
  return;
469
469
  }
470
+ const receiptEvidenceSource = receipt.evidence_source ?? "clone_local";
471
+ if (!isMergeDecisionReceipt(receipt, receiptEvidenceSource)) {
472
+ throw new Error("Merge receipt trusted settlement input failed schema validation.");
473
+ }
470
474
  const reconciledAt = nowIso();
471
475
  const localPath = path.join(directory, receiptFileName(receipt.id));
472
476
  const trackerRoot = await resolveTrackerRootFromItemPath(cwd, receipt.item_path);
@@ -498,11 +502,6 @@ export async function markMergeReceiptReconciled(cwd, receipt, options = {}) {
498
502
  }
499
503
  return;
500
504
  }
501
- const receiptEvidenceSource = receipt.evidence_source ?? "clone_local";
502
- const trustedReceiptId = receipt.id;
503
- if (!isMergeDecisionReceipt(receipt, receiptEvidenceSource)) {
504
- throw new Error(`Receipt ${trustedReceiptId} trusted settlement input failed schema validation.`);
505
- }
506
505
  const expectedFingerprint = receiptProvenanceFingerprint(receipt);
507
506
  if (new Set([expectedFingerprint, ...writes.map((write) => write.fingerprint)])
508
507
  .size !== 1) {
@@ -525,5 +524,7 @@ export async function runMergeReceiptReport(options) {
525
524
  generated_at: nowIso(),
526
525
  };
527
526
  }
527
+ /** Test-only seams for deterministic receipt-boundary fault coverage. */
528
+ export const _testOnlyMergeReceipts = { prepareReceiptSettlement };
528
529
  //# sourceMappingURL=receipts.js.map
529
- //# debugId=5add49c0-25be-5159-abfc-6c7ae9272f6b
530
+ //# debugId=145a6d31-5e14-5b0b-95fb-6f837d11e2f4
@@ -13,7 +13,7 @@ export { type ActiveExtensionHookContext, type ExtensionCommandRegistry, type Ex
13
13
  export { applyRegisteredItemFieldDefaultsAndValidation, collectRegisteredItemFieldNames, parseRegisteredItemFieldAssignments, } from "../core/extensions/item-fields.js";
14
14
  export { resolveExtensionRoots } from "../core/extensions/loader.js";
15
15
  export { resolveRegisteredSearchProvider, resolveRegisteredVectorStoreAdapter, } from "../core/extensions/runtime-registrations.js";
16
- export { pathExists, readFileIfExists, removeFileIfExists, writeFileAtomic, } from "../core/fs/fs-utils.js";
16
+ export { isFileMissingError, pathExists, readFileIfExists, removeFileIfExists, writeFileAtomic, } from "../core/fs/fs-utils.js";
17
17
  export { normalizeBulkIdsValue, parseBulkIdsText, resolveCliBulkIdsInput, type BulkIdsFileReader, type BulkIdsInputReaders, type BulkIdsStdinReader, type BulkIdsValue, } from "../core/io/bulk-ids-input.js";
18
18
  export { type HistoryDiffValueEntry, computeHistoryDiff, patchPathToChangedField, } from "../core/history/history-diff.js";
19
19
  export { enforceHistoryStreamPolicyForItem, enforceHistoryStreamPolicyForItems, } from "../core/history/history-stream-policy.js";
@@ -7,7 +7,7 @@
7
7
  * prefer the typed operations exported by the main SDK barrel.
8
8
  */
9
9
 
10
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="be6f7869-f197-5d38-b8d9-faf8f390cd56")}catch(e){}}();
10
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="4be36c51-a9fc-507d-8f13-d764b5668ab9")}catch(e){}}();
11
11
  export { createCheckpointId, loadMutationCheckpoint, restoreCheckpointItems, writeMutationCheckpoint, } from "../core/checkpoint/mutation-checkpoint.js";
12
12
  export { flattenFlagListValue, resolveFlagValueKind, } from "../core/extensions/flag-value-types.js";
13
13
  export { createUnknownSubcommandError, } from "./agent/subcommand-recovery.js";
@@ -15,7 +15,7 @@ export { activateExtensions, clearActiveExtensionHooks, consumeAfterCommandAffec
15
15
  export { applyRegisteredItemFieldDefaultsAndValidation, collectRegisteredItemFieldNames, parseRegisteredItemFieldAssignments, } from "../core/extensions/item-fields.js";
16
16
  export { resolveExtensionRoots } from "../core/extensions/loader.js";
17
17
  export { resolveRegisteredSearchProvider, resolveRegisteredVectorStoreAdapter, } from "../core/extensions/runtime-registrations.js";
18
- export { pathExists, readFileIfExists, removeFileIfExists, writeFileAtomic, } from "../core/fs/fs-utils.js";
18
+ export { isFileMissingError, pathExists, readFileIfExists, removeFileIfExists, writeFileAtomic, } from "../core/fs/fs-utils.js";
19
19
  export { normalizeBulkIdsValue, parseBulkIdsText, resolveCliBulkIdsInput, } from "../core/io/bulk-ids-input.js";
20
20
  export { computeHistoryDiff, patchPathToChangedField, } from "../core/history/history-diff.js";
21
21
  export { enforceHistoryStreamPolicyForItem, enforceHistoryStreamPolicyForItems, } from "../core/history/history-stream-policy.js";
@@ -82,4 +82,4 @@ export { maybeRunFirstUseTelemetryPrompt } from "../core/telemetry/consent.js";
82
82
  export { deriveTelemetryCommandResolution, } from "../core/telemetry/observability.js";
83
83
  export { emitTelemetryErrorEvent, finishTelemetryCommand, startTelemetryCommand, } from "../core/telemetry/runtime.js";
84
84
  //# sourceMappingURL=runtime-primitives.js.map
85
- //# debugId=be6f7869-f197-5d38-b8d9-faf8f390cd56
85
+ //# debugId=4be36c51-a9fc-507d-8f13-d764b5668ab9
@@ -220,6 +220,11 @@ declare function copyIntoSandboxIfPresent(sourcePath: string, targetPath: string
220
220
  }) => Promise<unknown>): Promise<void>;
221
221
  declare function seedLinkedTestSandbox(sandboxPmPath: string, sandboxGlobalPath: string, sourceRoots: LinkedTestSandboxSourceRoots): Promise<void>;
222
222
  declare function seedLinkedTestTrackerData(sourceRoot: string, sandboxRoot: string): Promise<void>;
223
+ /**
224
+ * Create an audited settings snapshot only for source trackers that predate
225
+ * workspace history, while preserving any existing source history verbatim.
226
+ */
227
+ declare function seedMissingLinkedTestSettingsHistory(sourceRoot: string, sandboxRoot: string): Promise<void>;
223
228
  declare function countLinkedTestItemFiles(pmRoot: string): Promise<number>;
224
229
  /** Implements resolve linked test failure exit code for the public runtime surface of this module. */
225
230
  export declare function resolveLinkedTestFailureExitCode(execution: Pick<LinkedTestExecutionResult, "exitCode" | "timedOut" | "maxBufferExceeded">): number;
@@ -321,6 +326,7 @@ export declare const _testOnlyTestCommand: {
321
326
  resolveTestRunOptions: typeof resolveTestRunOptions;
322
327
  resolveTrackedRunId: typeof resolveTrackedRunId;
323
328
  runLinkedTestCommand: typeof runLinkedTestCommand;
329
+ seedMissingLinkedTestSettingsHistory: typeof seedMissingLinkedTestSettingsHistory;
324
330
  seedLinkedTestSandbox: typeof seedLinkedTestSandbox;
325
331
  seedLinkedTestTrackerData: typeof seedLinkedTestTrackerData;
326
332
  segmentInvokesRecursiveTestAll: typeof segmentInvokesRecursiveTestAll;
@@ -4,14 +4,15 @@
4
4
  * Implements the pm test command surface and its agent-facing runtime behavior.
5
5
  */
6
6
 
7
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="7659721c-21f5-55da-8cce-31b290de5bfb")}catch(e){}}();
7
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="99ce92bb-ed45-5327-b148-b5bb26a0bf05")}catch(e){}}();
8
8
  import { assertInitializedTracker } from "../environment/tracker-preflight.js";
9
9
  import { spawn } from "node:child_process";
10
10
  import { cp, mkdir, mkdtemp, open, readdir, rm, symlink, } from "node:fs/promises";
11
11
  import { tmpdir } from "node:os";
12
12
  import path from "node:path";
13
13
  import { getActiveExtensionRegistrations } from "../../core/extensions/index.js";
14
- import { isFileMissingError, pathExists } from "../../core/fs/fs-utils.js";
14
+ import { isFileMissingError, pathExists, readFileIfExists, } from "../../core/fs/fs-utils.js";
15
+ import { getWorkspaceHistoryPath, writeWorkspaceJsonWithHistory, } from "../../core/history/workspace-history.js";
15
16
  import { resolveItemTypeRegistry } from "../../core/item/type-registry.js";
16
17
  import { createStdinTokenResolver, parseCsvKv, parseOptionalNumber, } from "../../core/item/parse.js";
17
18
  import { EXIT_CODE } from "../../core/shared/constants.js";
@@ -1043,6 +1044,31 @@ async function seedLinkedTestTrackerData(sourceRoot, sandboxRoot) {
1043
1044
  }
1044
1045
  }
1045
1046
  }
1047
+ /**
1048
+ * Create an audited settings snapshot only for source trackers that predate
1049
+ * workspace history, while preserving any existing source history verbatim.
1050
+ */
1051
+ async function seedMissingLinkedTestSettingsHistory(sourceRoot, sandboxRoot) {
1052
+ const sourceSettingsRaw = await readFileIfExists(getSettingsPath(sourceRoot));
1053
+ if (sourceSettingsRaw === null ||
1054
+ (await pathExists(getWorkspaceHistoryPath(sourceRoot)))) {
1055
+ return;
1056
+ }
1057
+ const sandboxSettings = await readSettings(sandboxRoot);
1058
+ await rm(getWorkspaceHistoryPath(sandboxRoot), { force: true });
1059
+ await rm(getSettingsPath(sandboxRoot), { force: true });
1060
+ await writeWorkspaceJsonWithHistory({
1061
+ pmRoot: sandboxRoot,
1062
+ filePath: getSettingsPath(sandboxRoot),
1063
+ raw: sourceSettingsRaw,
1064
+ op: "settings:write",
1065
+ author: resolveAuthor(undefined, sandboxSettings.author_default),
1066
+ lockTtlSeconds: sandboxSettings.locks.ttl_seconds,
1067
+ lockWaitMs: sandboxSettings.locks.wait_ms,
1068
+ recordCreation: true,
1069
+ message: "Seed linked-test tracker settings history",
1070
+ });
1071
+ }
1046
1072
  async function countLinkedTestItemFiles(pmRoot) {
1047
1073
  if (!(await pathExists(pmRoot))) {
1048
1074
  return 0;
@@ -1339,6 +1365,8 @@ async function seedLinkedTestSandboxesFromSource(layout, sourceRoots, includeTra
1339
1365
  await seedLinkedTestSandbox(layout.trackerProjectPmPath, layout.trackerGlobalPmPath, sourceRoots);
1340
1366
  await seedLinkedTestTrackerData(sourceRoots.projectPmRoot, layout.trackerProjectPmPath);
1341
1367
  await seedLinkedTestTrackerData(sourceRoots.globalPmRoot, layout.trackerGlobalPmPath);
1368
+ await seedMissingLinkedTestSettingsHistory(sourceRoots.projectPmRoot, layout.trackerProjectPmPath);
1369
+ await seedMissingLinkedTestSettingsHistory(sourceRoots.globalPmRoot, layout.trackerGlobalPmPath);
1342
1370
  }
1343
1371
  function linkedTestsRequireTrackerData(tests, runLevelPmContextMode, options) {
1344
1372
  return tests.some((linkedTest) => {
@@ -2124,6 +2152,7 @@ export const _testOnlyTestCommand = {
2124
2152
  resolveTestRunOptions,
2125
2153
  resolveTrackedRunId,
2126
2154
  runLinkedTestCommand,
2155
+ seedMissingLinkedTestSettingsHistory,
2127
2156
  seedLinkedTestSandbox,
2128
2157
  seedLinkedTestTrackerData,
2129
2158
  segmentInvokesRecursiveTestAll,
@@ -2131,4 +2160,4 @@ export const _testOnlyTestCommand = {
2131
2160
  summarizeRunResultStatuses,
2132
2161
  };
2133
2162
  //# sourceMappingURL=execution.js.map
2134
- //# debugId=7659721c-21f5-55da-8cce-31b290de5bfb
2163
+ //# debugId=99ce92bb-ed45-5327-b148-b5bb26a0bf05
@@ -80,11 +80,13 @@ All new history fields are optional. Readers must accept:
80
80
  - explicit `model: null`, `effort: null`, or `role: null` observations meaning
81
81
  the dimension was declared but unavailable for a detected harness.
82
82
 
83
- MCP clients may supply a bounded `clientInfo.provenance` map during initialize.
83
+ Modern MCP clients may supply a bounded
84
+ `io.modelcontextprotocol/clientInfo.provenance` map in each request's `_meta`.
84
85
  The server retains only valid dimension names and trimmed values, then resolves
85
- that map inside the invocation-scoped identity context for every mutation. A
86
- missing MCP signal is recorded as explicit `null`; it is never confused with a
87
- legacy entry that predates the dimension.
86
+ that map inside the invocation-scoped identity context for that request. The
87
+ bounded `2025-06-18` adapter accepts the same map during legacy `initialize`.
88
+ A missing MCP signal is recorded as explicit `null`; it is never confused with
89
+ a legacy entry that predates the dimension.
88
90
 
89
91
  Unknown provenance dimensions are preserved as data and do not change author
90
92
  resolution. Removing a dimension from a descriptor never rewrites existing
@@ -156,11 +156,12 @@ author literals at read time; every row reports `harness_source` and
156
156
  `vocabulary_version`, and unresolved literals remain visible in the bounded
157
157
  summary. No history entry is rewritten.
158
158
 
159
- MCP captures `clientInfo.name` and `clientInfo.version` during initialize and
160
- scopes all later tool calls to that client signal. Optional host-provided
161
- `model`, `session`, `provenance`, and `episode` fields are supported, but
162
- version is not
163
- misclassified as a model or session. Provenance keys must use the bounded
159
+ MCP 2026-07-28 reads `io.modelcontextprotocol/clientInfo` independently from
160
+ every request's `_meta` and scopes only that request to the client signal. The
161
+ bounded legacy adapter still reads `clientInfo` during `2025-06-18`
162
+ `initialize`. Optional host-provided `model`, `session`, `provenance`, and
163
+ `episode` fields are supported, but version is not misclassified as a model or
164
+ session. Provenance keys must use the bounded
164
165
  lowercase dimension vocabulary; blank, malformed, and oversized data is
165
166
  discarded before mutation context is created. See
166
167
  [Agent Provenance ADR Amendment](AGENT_PROVENANCE_ADR.md) for precedence,
@@ -73,9 +73,10 @@ Add to the project's `.mcp.json`:
73
73
  ```
74
74
 
75
75
  The repo root `.mcp.json` uses this approach and activates automatically when
76
- Claude Code opens this repository. The MCP initialize handshake supplies the
77
- client name/version to pm's bounded identity detector, so normal agent
78
- mutations do not need a hard-wired `PM_AUTHOR`.
76
+ Claude Code opens this repository. Under MCP `2026-07-28`, every request carries
77
+ the protocol version, client capabilities, and client identity in `_meta`, so
78
+ pm's bounded identity detector does not depend on session state and normal
79
+ agent mutations do not need a hard-wired `PM_AUTHOR`.
79
80
 
80
81
  ## MCP Server Launcher
81
82
 
@@ -115,7 +116,10 @@ node scripts/smoke-claude-plugin.mjs
115
116
  pnpm smoke:claude-plugin
116
117
  ```
117
118
 
118
- Verifies: plugin file structure, manifest name consistency, MCP initialize, 31 tools present, full workflow (init → create → claim → update → link files/docs/tests → get → context → search → validate → health), and session-start hook.
119
+ Verifies: plugin file structure, manifest name consistency, stateless MCP
120
+ discovery for `2026-07-28`, 31 tools present, full workflow (init → create →
121
+ claim → update → link files/docs/tests → get → context → search → validate →
122
+ health), and session-start hook.
119
123
 
120
124
  ### MCP server smoke test
121
125
 
@@ -182,7 +186,10 @@ After installing the plugin:
182
186
 
183
187
  The authoritative plugin version is `plugins/pm-claude/.claude-plugin/plugin.json`; this row stays on the `1.x` major line so it does not drift with each plugin release.
184
188
 
185
- The MCP server uses JSON-RPC 2.0 over stdio with protocol version `2025-06-18`.
189
+ The MCP server uses JSON-RPC 2.0 over stdio with canonical protocol version
190
+ `2026-07-28`. A bounded legacy path remains for unversioned older hosts, with
191
+ `2025-06-18` initialize available to enrich client identity; current hosts
192
+ discover the server and send metadata on every request.
186
193
 
187
194
  ## Extension Policy Diagnostics
188
195
 
@@ -103,6 +103,14 @@ refusal; schema, items, history, settings, and package state must not change.
103
103
  Ephemeral runtime lock/cache directories are excluded from that semantic
104
104
  snapshot.
105
105
 
106
+ The complete error-code census currently retains executable evidence for 18
107
+ catalog rows across 17 canonical groups. Two of those rows are author-manifest
108
+ schema findings reached through a real `pm health --full --strict-exit --json`
109
+ process: an unknown top-level key and omitted canonical pm version bounds. The
110
+ remaining rows stay explicit `uncovered` obligations in
111
+ [the generated census](generated/REFUSAL_CLOSURE_CENSUS.md); the ratchet does not
112
+ turn partial catalog closure into an approval claim.
113
+
106
114
  Run the focused proof with:
107
115
 
108
116
  ```bash
@@ -1,6 +1,6 @@
1
1
  # Packages and Extensions
2
2
 
3
- Extension flags declared with `list: true` accumulate repeated long/short alias occurrences and comma-separated values into one array. Dynamic commands preserve flag-like variadic content after `--`, and package handlers can use the public `suppressHostOutput()` protocol when they already emitted streaming, binary, or pre-rendered output. Declarative blueprints are also checked for reserved item-field collisions during SDK lint/preflight and harness activation, so a package cannot pass author-time validation and then fail only when users create or update items. Local archive installation, command ownership, and MCP custom-field diagnostics are tracked by [pm-lw6acw](../.agents/pm/issues/pm-lw6acw.toon), [pm-6z0wzf](../.agents/pm/issues/pm-6z0wzf.toon), and [pm-yfdav2](../.agents/pm/issues/pm-yfdav2.toon). Transactional mutation guards, host-bound command test SDKs, and installed custom-type lifecycle parity are tracked by [pm-hx23u5](../.agents/pm/issues/pm-hx23u5.toon), [pm-wx2lr5](../.agents/pm/issues/pm-wx2lr5.toon), and [pm-scga6k](../.agents/pm/issues/pm-scga6k.toon). Durable migration application, explicit source resolution, and composable preflight ownership are covered in [Extension Lifecycle Contracts](EXTENSION_LIFECYCLE.md).
3
+ Extension flags declared with `list: true` accumulate repeated long/short alias occurrences and comma-separated values into one array. Dynamic commands preserve flag-like variadic content after `--`, and package handlers can use the public `suppressHostOutput()` protocol when they already emitted streaming, binary, or pre-rendered output. Declarative blueprints are also checked for reserved item-field collisions during SDK lint/preflight and harness activation, so a package cannot pass author-time validation and then fail only when users create or update items. Local archive installation, command ownership, MCP custom-field diagnostics, and author-manifest schema diagnostics are tracked by [pm-lw6acw](../.agents/pm/issues/pm-lw6acw.toon), [pm-6z0wzf](../.agents/pm/issues/pm-6z0wzf.toon), [pm-yfdav2](../.agents/pm/issues/pm-yfdav2.toon), and [pm-gh1091](../.agents/pm/issues/pm-gh1091.toon). Transactional mutation guards, host-bound command test SDKs, and installed custom-type lifecycle parity are tracked by [pm-hx23u5](../.agents/pm/issues/pm-hx23u5.toon), [pm-wx2lr5](../.agents/pm/issues/pm-wx2lr5.toon), and [pm-scga6k](../.agents/pm/issues/pm-scga6k.toon). Durable migration application, explicit source resolution, and composable preflight ownership are covered in [Extension Lifecycle Contracts](EXTENSION_LIFECYCLE.md).
4
4
 
5
5
  Packages add optional `pm` workflows without changing the core CLI. A package can ship one or more runtime extensions plus metadata such as docs and examples. Prefer the package-first commands in new docs and automation:
6
6
 
@@ -191,10 +191,9 @@ useful for agents without inflating context or leaking private item content.
191
191
 
192
192
  Runnable manifest examples are the source of truth: [starter extension manifest](examples/starter-extension/manifest.json) and [policy-restricted manifest](examples/policy-restricted-extension/manifest.json).
193
193
 
194
- Use [extension-manifest.schema.json](schemas/extension-manifest.schema.json) as the `$schema` value for inline editor validation. The loader ignores `$schema` and tolerates future manifest fields, but the schema documents the fields pm reads.
195
-
196
- Rules:
194
+ Use [extension-manifest.schema.json](schemas/extension-manifest.schema.json) as the `$schema` value for inline editor validation. The loader ignores `$schema` and tolerates future manifest fields, but the schema documents the fields pm reads. Rules:
197
195
 
196
+ - `manifest.json` is the runtime declaration; package catalog metadata belongs in `package.json#pm`, and a top-level `compatibility` object is ignored. Use the public `inspectExtensionManifestSchema` / `lintExtensionManifestSchema` helpers for schema-only checks. In author workspaces, `pm health --full`, `pm package doctor`, and `pm extension --doctor` expose the same read-only `author_manifest` findings without activating the extension.
198
197
  - `entry` must resolve inside the extension directory.
199
198
  - `manifest_version` is an optional integer identifying the manifest schema generation. Runtime contracts currently support manifest versions `1` and `2`, and first-party runnable examples use `2`. First-party packages declare it; the manifest governance test requires it on every first-party package.
200
199
  - `pm_min_version` is an inclusive minimum pm CLI version. If the running CLI is older, discovery emits `extension_pm_min_version_unmet:<layer>:<name>:required=<version>:current=<version>` and skips the extension before import.