@unbrained/pm-cli 2026.8.22 → 2026.8.23

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 (168) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/CHANGELOG.md +28 -0
  3. package/dist/cli/error-guidance.d.ts +13 -0
  4. package/dist/cli/error-guidance.js +62 -6
  5. package/dist/cli/main.js +13 -4
  6. package/dist/cli/register-mutation.js +6 -6
  7. package/dist/cli/register-operations.d.ts +73 -0
  8. package/dist/cli/register-operations.js +39 -14
  9. package/dist/cli/register-setup.js +3 -3
  10. package/dist/cli/schema-registration-helpers.d.ts +8 -1
  11. package/dist/cli/schema-registration-helpers.js +14 -5
  12. package/dist/cli-bundle/bundle-manifest.json +155 -155
  13. package/dist/cli-bundle/chunks/chunk-3ENVHX6C.js +36 -0
  14. package/dist/cli-bundle/chunks/{chunk-QI2CT3IX.js → chunk-DW2CGEMU.js} +2 -2
  15. package/dist/cli-bundle/chunks/{chunk-WG4STC36.js → chunk-EG6CLDZT.js} +2 -2
  16. package/dist/cli-bundle/chunks/{chunk-MYYI3ZOA.js → chunk-F3AI2JFL.js} +2 -2
  17. package/dist/cli-bundle/chunks/{chunk-U6GQ4VMV.js → chunk-HWZ6CELB.js} +4 -4
  18. package/dist/cli-bundle/chunks/chunk-HZBMLSZI.js +201 -0
  19. package/dist/cli-bundle/chunks/chunk-JB4KZWBF.js +3 -0
  20. package/dist/cli-bundle/chunks/chunk-KPH73XXJ.js +2 -0
  21. package/dist/cli-bundle/chunks/{chunk-BWX4SK46.js → chunk-KXPMB2YL.js} +58 -58
  22. package/dist/cli-bundle/chunks/{chunk-MZTZQTMS.js → chunk-QDVHNHAQ.js} +2 -2
  23. package/dist/cli-bundle/chunks/chunk-TFI5V5N2.js +13 -0
  24. package/dist/cli-bundle/chunks/{register-list-query-HZKBQNWW.js → register-list-query-UJB5FMV2.js} +2 -2
  25. package/dist/cli-bundle/chunks/register-mutation-NX5XE4VX.js +20 -0
  26. package/dist/cli-bundle/chunks/register-operations-RXIJIYXC.js +2 -0
  27. package/dist/cli-bundle/chunks/{register-setup-QK4OTOXN.js → register-setup-GFUZEKRE.js} +2 -2
  28. package/dist/cli-bundle/focused-chunks/{chunk-2POVTNLY.js → chunk-37Z4OEAP.js} +2 -2
  29. package/dist/cli-bundle/focused-chunks/chunk-3UPTYG44.js +8 -0
  30. package/dist/cli-bundle/focused-chunks/{chunk-E2EWYPY6.js → chunk-5S7E72FY.js} +2 -2
  31. package/dist/cli-bundle/focused-chunks/{chunk-GMYPD4FX.js → chunk-AUFH5H35.js} +2 -2
  32. package/dist/cli-bundle/focused-chunks/chunk-E7X25GC3.js +2 -0
  33. package/dist/cli-bundle/focused-chunks/{chunk-3LKU6PTJ.js → chunk-FQCSTBWE.js} +2 -2
  34. package/dist/cli-bundle/focused-chunks/{chunk-6S2MO3Q3.js → chunk-FZS4H32O.js} +2 -2
  35. package/dist/cli-bundle/focused-chunks/chunk-IFLWKY2R.js +156 -0
  36. package/dist/cli-bundle/focused-chunks/{chunk-PMGJUFNQ.js → chunk-JOWDB2MH.js} +2 -2
  37. package/dist/cli-bundle/focused-chunks/chunk-LYVSW5VM.js +2 -0
  38. package/dist/cli-bundle/focused-chunks/chunk-MRCNF3U6.js +2 -0
  39. package/dist/cli-bundle/focused-chunks/{chunk-A2BC5PQL.js → chunk-OFVUGTLJ.js} +2 -2
  40. package/dist/cli-bundle/focused-chunks/{chunk-2ARQGDDW.js → chunk-OGLQBL46.js} +2 -2
  41. package/dist/cli-bundle/focused-chunks/{chunk-R6SR45XU.js → chunk-OY7QAV4Y.js} +48 -47
  42. package/dist/cli-bundle/focused-chunks/chunk-QGFNI65N.js +26 -0
  43. package/dist/cli-bundle/focused-chunks/{chunk-D6RBZEJJ.js → chunk-R27IDGHD.js} +2 -2
  44. package/dist/cli-bundle/focused-chunks/chunk-UYRFYCDQ.js +5 -0
  45. package/dist/cli-bundle/focused-chunks/{chunk-PH2OXXVZ.js → chunk-ZHVA42R5.js} +9 -9
  46. package/dist/cli-bundle/main.js +13 -13
  47. package/dist/cli-bundle/sdk-authoring.js +1 -1
  48. package/dist/cli-bundle/sdk-contracts.js +2 -2
  49. package/dist/cli-bundle/sdk-core.js +40 -40
  50. package/dist/cli-bundle/sdk-governance.js +1 -1
  51. package/dist/cli-bundle/sdk-graph.js +1 -1
  52. package/dist/cli-bundle/sdk-merge.js +33 -32
  53. package/dist/cli-bundle/sdk-query.js +1 -1
  54. package/dist/cli-bundle/sdk-runtime.js +1 -1
  55. package/dist/cli-bundle/sdk-testing.js +1 -1
  56. package/dist/cli-bundle/sdk.js +5 -5
  57. package/dist/core/config/nested-settings.js +8 -2
  58. package/dist/core/config/positional-value.d.ts +1 -1
  59. package/dist/core/config/positional-value.js +5 -2
  60. package/dist/core/history/history.js +3 -3
  61. package/dist/core/item/item-format.js +66 -12
  62. package/dist/core/reproducibility/context.d.ts +4 -0
  63. package/dist/core/reproducibility/context.js +14 -8
  64. package/dist/core/shared/author.d.ts +55 -1
  65. package/dist/core/shared/author.js +333 -16
  66. package/dist/core/shared/constants.js +3 -2
  67. package/dist/core/store/settings-validator.d.ts +1 -0
  68. package/dist/core/store/settings-validator.js +6 -3
  69. package/dist/core/store/settings.js +5 -2
  70. package/dist/mcp/server.d.ts +1 -1
  71. package/dist/mcp/server.js +30 -9
  72. package/dist/sdk/agent/identity-config.d.ts +63 -0
  73. package/dist/sdk/agent/identity-config.js +107 -0
  74. package/dist/sdk/agent/refusal-corpus-contracts.d.ts +76 -0
  75. package/dist/sdk/agent/refusal-corpus-contracts.js +194 -0
  76. package/dist/sdk/cli-contracts/agent-output-contracts.js +6 -4
  77. package/dist/sdk/cli-contracts/enum-contracts.d.ts +4 -0
  78. package/dist/sdk/cli-contracts/enum-contracts.js +9 -2
  79. package/dist/sdk/cli-contracts/flag-contracts.js +9 -2
  80. package/dist/sdk/cli-contracts/flag-lexicon-contracts.js +4 -4
  81. package/dist/sdk/cli-contracts/registration-helpers.js +5 -2
  82. package/dist/sdk/cli-contracts/tool-parameter-tables.js +22 -2
  83. package/dist/sdk/cli-contracts/tool-schema.d.ts +1 -1
  84. package/dist/sdk/cli-contracts/tool-schema.js +14 -3
  85. package/dist/sdk/cli-contracts.d.ts +3 -3
  86. package/dist/sdk/cli-contracts.js +4 -4
  87. package/dist/sdk/config.d.ts +6 -1
  88. package/dist/sdk/config.js +119 -14
  89. package/dist/sdk/context-intent-contracts.js +4 -2
  90. package/dist/sdk/core-governance.d.ts +1 -1
  91. package/dist/sdk/core-governance.js +3 -3
  92. package/dist/sdk/extension/scaffold.js +11 -5
  93. package/dist/sdk/flag-invocation-contracts.js +4 -2
  94. package/dist/sdk/generated/generated-error-code-catalog-part-1.js +50 -2
  95. package/dist/sdk/generated/generated-error-code-catalog-part-2.js +87 -38
  96. package/dist/sdk/governance/assurance-action.js +30 -10
  97. package/dist/sdk/governance/validate.d.ts +2 -2
  98. package/dist/sdk/governance/validate.js +39 -14
  99. package/dist/sdk/index.d.ts +7 -2
  100. package/dist/sdk/index.js +8 -3
  101. package/dist/sdk/lifecycle/create.js +6 -3
  102. package/dist/sdk/lifecycle/update.js +5 -3
  103. package/dist/sdk/merge/three-way.js +105 -26
  104. package/dist/sdk/read-output-budget.d.ts +21 -0
  105. package/dist/sdk/read-output-budget.js +39 -2
  106. package/dist/sdk/read-output-contracts.d.ts +5 -3
  107. package/dist/sdk/read-output-contracts.js +20 -12
  108. package/dist/sdk/reproducibility/process.d.ts +19 -0
  109. package/dist/sdk/reproducibility/process.js +88 -0
  110. package/dist/sdk/runtime-primitives.d.ts +1 -1
  111. package/dist/sdk/runtime-primitives.js +3 -3
  112. package/dist/sdk/runtime.js +6 -5
  113. package/dist/sdk/test/batch.d.ts +6 -0
  114. package/dist/sdk/test/batch.js +15 -3
  115. package/dist/sdk/test/entry.d.ts +1 -1
  116. package/dist/sdk/test/entry.js +3 -2
  117. package/dist/sdk/test/execution.d.ts +28 -3
  118. package/dist/sdk/test/execution.js +193 -34
  119. package/dist/sdk/test/parsers.d.ts +6 -0
  120. package/dist/sdk/test/parsers.js +21 -2
  121. package/dist/sdk/test/trust.d.ts +33 -0
  122. package/dist/sdk/test/trust.js +207 -0
  123. package/dist/types.d.ts +23 -0
  124. package/dist/types.js +2 -2
  125. package/docs/AGENT_PROVENANCE_ADR.md +41 -5
  126. package/docs/CONFIGURATION.md +4 -0
  127. package/docs/DIAGNOSTIC_OUTPUT_CONTRACTS.md +19 -2
  128. package/docs/MERGE_SAFETY.md +15 -1
  129. package/docs/READ_OUTPUT_CONTRACTS.md +11 -1
  130. package/docs/REPRODUCIBLE_WORKSPACES.md +42 -8
  131. package/docs/SDK.md +9 -0
  132. package/docs/SDK_AGENT_SESSION_CONTEXT.md +6 -4
  133. package/docs/SDK_EVIDENCE_TRACEABILITY.md +1 -1
  134. package/docs/TESTING.md +75 -8
  135. package/docs/agent-task-token-baseline.json +2 -2
  136. package/docs/generated/FLAG_LEXICON_BUDGETS.md +2 -2
  137. package/marketplace.json +2 -2
  138. package/package.json +2 -2
  139. package/packages/pm-beads/package.json +1 -1
  140. package/packages/pm-calendar/package.json +1 -1
  141. package/packages/pm-command-kit/package.json +1 -1
  142. package/packages/pm-digital-twin/package.json +1 -1
  143. package/packages/pm-governance-audit/package.json +1 -1
  144. package/packages/pm-guide-shell/package.json +1 -1
  145. package/packages/pm-kanban/package.json +1 -1
  146. package/packages/pm-lifecycle-hooks/package.json +1 -1
  147. package/packages/pm-linked-test-adapters/package.json +1 -1
  148. package/packages/pm-search-advanced/package.json +1 -1
  149. package/packages/pm-templates/package.json +1 -1
  150. package/packages/pm-todos/package.json +1 -1
  151. package/packages/pm-vcs/package.json +1 -1
  152. package/plugins/pm-claude/.claude-plugin/plugin.json +1 -1
  153. package/plugins/pm-codex/.codex-plugin/plugin.json +1 -1
  154. package/sdk/public-surface.json +427 -17
  155. package/dist/cli-bundle/chunks/chunk-EMWDM5FI.js +0 -13
  156. package/dist/cli-bundle/chunks/chunk-HYG6LRL4.js +0 -200
  157. package/dist/cli-bundle/chunks/chunk-SMWXHMVB.js +0 -35
  158. package/dist/cli-bundle/chunks/chunk-VMMIL3SI.js +0 -2
  159. package/dist/cli-bundle/chunks/chunk-YBPJAOQQ.js +0 -3
  160. package/dist/cli-bundle/chunks/register-mutation-HTKEEGRD.js +0 -20
  161. package/dist/cli-bundle/chunks/register-operations-6EMRWJJY.js +0 -2
  162. package/dist/cli-bundle/focused-chunks/chunk-2AZT5WXZ.js +0 -2
  163. package/dist/cli-bundle/focused-chunks/chunk-5SHSBMX2.js +0 -2
  164. package/dist/cli-bundle/focused-chunks/chunk-C4S4A4JJ.js +0 -5
  165. package/dist/cli-bundle/focused-chunks/chunk-L5LDSRO2.js +0 -155
  166. package/dist/cli-bundle/focused-chunks/chunk-MB37AYN6.js +0 -26
  167. package/dist/cli-bundle/focused-chunks/chunk-MCOR37QK.js +0 -2
  168. package/dist/cli-bundle/focused-chunks/chunk-WZE4LIPW.js +0 -8
@@ -0,0 +1,33 @@
1
+ import type { LinkedTest } from "../../types/index.js";
2
+ /** Classification of one linked-test command against the current clone. */
3
+ export interface LinkedTestTrustDecision {
4
+ /** Stable hash binding the decision to command, context, and provenance. */
5
+ fingerprint: string;
6
+ /** Whether execution may proceed without an untrusted-command override. */
7
+ trusted: boolean;
8
+ /** Machine-readable reason for the decision. */
9
+ reason: "legacy" | "local_mutation" | "local_source_ref" | "acknowledged" | "invalid_provenance" | "foreign_source_ref";
10
+ /** Source ref recorded when the command entered tracker data. */
11
+ source_ref?: string;
12
+ /** Current source ref used for the local decision. */
13
+ current_source_ref?: string;
14
+ }
15
+ /** Resolve the current Git branch without invoking a shell. */
16
+ export declare function resolveLinkedTestSourceRef(cwd?: string): Promise<string | undefined>;
17
+ /** Resolve the source checkout shared by linked-test provenance and execution. */
18
+ export declare function resolveLinkedTestSourceWorkspaceRoot(cwd?: string): string;
19
+ /** Attach immutable author/time/ref provenance to newly stored commands. */
20
+ export declare function attachLinkedTestProvenance(tests: LinkedTest[] | undefined, author: string, createdAt: string, sourceRef: string | undefined): LinkedTest[] | undefined;
21
+ /** Attach provenance while avoiding Git inspection for an empty mutation. */
22
+ export declare function attachLinkedTestMutationProvenance(tests: LinkedTest[] | undefined, author: string, createdAt: string): Promise<LinkedTest[] | undefined>;
23
+ /** Produce the clone-local trust identity for one linked-test entry. */
24
+ export declare function linkedTestTrustFingerprint(test: LinkedTest): string;
25
+ /** Resolve whether one stored command is trusted by this clone. */
26
+ export declare function resolveLinkedTestTrust(pmRoot: string, test: LinkedTest, currentSourceRef?: string): Promise<LinkedTestTrustDecision>;
27
+ /** Resolve a collection with one ledger read for validation and batch runs. */
28
+ export declare function resolveLinkedTestTrustBatch(pmRoot: string, tests: LinkedTest[], currentSourceRef?: string): Promise<LinkedTestTrustDecision[]>;
29
+ /** Persist explicit clone-local acknowledgement for selected linked tests. */
30
+ export declare function acknowledgeLinkedTests(pmRoot: string, tests: LinkedTest[], acknowledgedAt: string): Promise<{
31
+ acknowledged: number;
32
+ fingerprints: string[];
33
+ }>;
@@ -0,0 +1,207 @@
1
+ /**
2
+ * @module sdk/test/trust
3
+ *
4
+ * Provides provenance and clone-local trust decisions for stored linked-test
5
+ * commands. Tracker items remain portable while execution acknowledgements stay
6
+ * outside the merge-unioned project record.
7
+ */
8
+
9
+ !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]="30a61f26-5db8-50ba-af37-1990a4742ce8")}catch(e){}}();
10
+ import { execFile } from "node:child_process";
11
+ import { readFile } from "node:fs/promises";
12
+ import path from "node:path";
13
+ import { promisify } from "node:util";
14
+ import { ensureDir, writeFileAtomic, } from "../../core/fs/fs-utils.js";
15
+ import { sha256Hex, stableStringify } from "../../core/shared/serialization.js";
16
+ import { isTimestampLiteral } from "../../core/shared/time.js";
17
+ import { getRuntimePath } from "../../core/store/paths.js";
18
+ const execFileAsync = promisify(execFile);
19
+ const LINKED_TEST_TRUST_LEDGER_VERSION = 1;
20
+ const LINKED_TEST_TRUST_LEDGER_FILE = "linked-test-trust.json";
21
+ function trustLedgerPath(pmRoot) {
22
+ return path.join(getRuntimePath(pmRoot), LINKED_TEST_TRUST_LEDGER_FILE);
23
+ }
24
+ function emptyTrustLedger() {
25
+ return { version: LINKED_TEST_TRUST_LEDGER_VERSION, acknowledged: {} };
26
+ }
27
+ async function readTrustLedger(pmRoot) {
28
+ try {
29
+ const parsed = JSON.parse(await readFile(trustLedgerPath(pmRoot), "utf8"));
30
+ if (parsed.version !== LINKED_TEST_TRUST_LEDGER_VERSION ||
31
+ typeof parsed.acknowledged !== "object" ||
32
+ parsed.acknowledged === null ||
33
+ Array.isArray(parsed.acknowledged)) {
34
+ return emptyTrustLedger();
35
+ }
36
+ return {
37
+ version: LINKED_TEST_TRUST_LEDGER_VERSION,
38
+ acknowledged: parsed.acknowledged,
39
+ };
40
+ }
41
+ catch {
42
+ // A missing, corrupt, or unreadable ledger grants no acknowledgements.
43
+ // The local/source-ref provenance checks remain independent of this file.
44
+ return emptyTrustLedger();
45
+ }
46
+ }
47
+ /** Resolve the current Git branch without invoking a shell. */
48
+ export async function resolveLinkedTestSourceRef(cwd = process.cwd()) {
49
+ const hostedRef = process.env.GITHUB_HEAD_REF?.trim() || process.env.GITHUB_REF_NAME?.trim();
50
+ if (hostedRef && hostedRef !== "merge") {
51
+ return hostedRef;
52
+ }
53
+ try {
54
+ const { stdout } = await execFileAsync("git", ["symbolic-ref", "--quiet", "--short", "HEAD"], { cwd, encoding: "utf8", windowsHide: true, timeout: 10_000 });
55
+ const ref = stdout.trim();
56
+ /* c8 ignore start -- successful git symbolic-ref output is non-empty by contract */
57
+ if (ref.length === 0)
58
+ return undefined;
59
+ /* c8 ignore stop */
60
+ return ref;
61
+ }
62
+ catch {
63
+ return undefined;
64
+ }
65
+ }
66
+ /** Resolve the source checkout shared by linked-test provenance and execution. */
67
+ export function resolveLinkedTestSourceWorkspaceRoot(cwd = process.cwd()) {
68
+ return process.env.PM_SOURCE_WORKSPACE_ROOT?.trim() || cwd;
69
+ }
70
+ function hasValidLinkedTestProvenance(provenance) {
71
+ if (provenance === null || typeof provenance !== "object")
72
+ return false;
73
+ return (typeof provenance.author === "string" &&
74
+ provenance.author.trim().length > 0 &&
75
+ typeof provenance.created_at === "string" &&
76
+ isTimestampLiteral(provenance.created_at) &&
77
+ (provenance.source_kind === "local_mutation" ||
78
+ provenance.source_kind === "merge_union"));
79
+ }
80
+ /** Build the optional ref portion shared by clone-local trust decisions. */
81
+ function linkedTestTrustRefs(sourceRef, currentSourceRef) {
82
+ return {
83
+ ...(sourceRef ? { source_ref: sourceRef } : {}),
84
+ ...(currentSourceRef ? { current_source_ref: currentSourceRef } : {}),
85
+ };
86
+ }
87
+ /** Attach immutable author/time/ref provenance to newly stored commands. */
88
+ export function attachLinkedTestProvenance(tests, author, createdAt, sourceRef) {
89
+ return tests?.map((test) => test.command && !test.provenance
90
+ ? {
91
+ ...test,
92
+ provenance: {
93
+ author,
94
+ created_at: createdAt,
95
+ source_kind: "local_mutation",
96
+ ...(sourceRef ? { source_ref: sourceRef } : {}),
97
+ },
98
+ }
99
+ : test);
100
+ }
101
+ /** Attach provenance while avoiding Git inspection for an empty mutation. */
102
+ export async function attachLinkedTestMutationProvenance(tests, author, createdAt) {
103
+ if (!tests || tests.length === 0)
104
+ return tests;
105
+ const sourceWorkspaceRoot = resolveLinkedTestSourceWorkspaceRoot();
106
+ return attachLinkedTestProvenance(tests, author, createdAt, await resolveLinkedTestSourceRef(sourceWorkspaceRoot));
107
+ }
108
+ /** Produce the clone-local trust identity for one linked-test entry. */
109
+ export function linkedTestTrustFingerprint(test) {
110
+ return sha256Hex(stableStringify({
111
+ command: test.command ?? null,
112
+ path: test.path ?? null,
113
+ scope: test.scope,
114
+ pm_context_mode: test.pm_context_mode ?? null,
115
+ workspace_context_mode: test.workspace_context_mode ?? null,
116
+ env_set: test.env_set ?? null,
117
+ env_clear: test.env_clear ?? null,
118
+ shared_host_safe: test.shared_host_safe ?? null,
119
+ provenance: test.provenance ?? null,
120
+ provenance_invalid: test.provenance_invalid ?? null,
121
+ }));
122
+ }
123
+ /** Resolve whether one stored command is trusted by this clone. */
124
+ export async function resolveLinkedTestTrust(pmRoot, test, currentSourceRef) {
125
+ const ledger = await readTrustLedger(pmRoot);
126
+ return resolveLinkedTestTrustFromLedger(test, currentSourceRef, ledger);
127
+ }
128
+ function resolveLinkedTestTrustFromLedger(test, currentSourceRef, ledger) {
129
+ const fingerprint = linkedTestTrustFingerprint(test);
130
+ const sourceRef = test.provenance?.source_ref;
131
+ if (test.provenance === undefined &&
132
+ test.provenance_invalid !== true) {
133
+ return { fingerprint, trusted: true, reason: "legacy" };
134
+ }
135
+ const acknowledged = Object.hasOwn(ledger.acknowledged, fingerprint);
136
+ if (test.provenance_invalid === true ||
137
+ !hasValidLinkedTestProvenance(test.provenance)) {
138
+ if (acknowledged) {
139
+ return {
140
+ fingerprint,
141
+ trusted: true,
142
+ reason: "acknowledged",
143
+ ...linkedTestTrustRefs(undefined, currentSourceRef),
144
+ };
145
+ }
146
+ return {
147
+ fingerprint,
148
+ trusted: false,
149
+ reason: "invalid_provenance",
150
+ ...linkedTestTrustRefs(undefined, currentSourceRef),
151
+ };
152
+ }
153
+ if (test.provenance.source_kind === "local_mutation" &&
154
+ sourceRef === undefined) {
155
+ return {
156
+ fingerprint,
157
+ trusted: true,
158
+ reason: "local_mutation",
159
+ ...linkedTestTrustRefs(undefined, currentSourceRef),
160
+ };
161
+ }
162
+ if (test.provenance.source_kind === "local_mutation" &&
163
+ sourceRef !== undefined &&
164
+ sourceRef === currentSourceRef) {
165
+ return {
166
+ fingerprint,
167
+ trusted: true,
168
+ reason: "local_source_ref",
169
+ source_ref: sourceRef,
170
+ current_source_ref: currentSourceRef,
171
+ };
172
+ }
173
+ if (acknowledged) {
174
+ return {
175
+ fingerprint,
176
+ trusted: true,
177
+ reason: "acknowledged",
178
+ ...linkedTestTrustRefs(sourceRef, currentSourceRef),
179
+ };
180
+ }
181
+ return {
182
+ fingerprint,
183
+ trusted: false,
184
+ reason: "foreign_source_ref",
185
+ ...linkedTestTrustRefs(sourceRef, currentSourceRef),
186
+ };
187
+ }
188
+ /** Resolve a collection with one ledger read for validation and batch runs. */
189
+ export async function resolveLinkedTestTrustBatch(pmRoot, tests, currentSourceRef) {
190
+ const ledger = await readTrustLedger(pmRoot);
191
+ return tests.map((test) => resolveLinkedTestTrustFromLedger(test, currentSourceRef, ledger));
192
+ }
193
+ /** Persist explicit clone-local acknowledgement for selected linked tests. */
194
+ export async function acknowledgeLinkedTests(pmRoot, tests, acknowledgedAt) {
195
+ const ledger = await readTrustLedger(pmRoot);
196
+ const fingerprints = tests
197
+ .filter((test) => typeof test.command === "string" && test.command.length > 0)
198
+ .map(linkedTestTrustFingerprint);
199
+ for (const fingerprint of fingerprints) {
200
+ ledger.acknowledged[fingerprint] = { acknowledged_at: acknowledgedAt };
201
+ }
202
+ await ensureDir(path.dirname(trustLedgerPath(pmRoot)));
203
+ await writeFileAtomic(trustLedgerPath(pmRoot), `${JSON.stringify(ledger, null, 2)}\n`);
204
+ return { acknowledged: fingerprints.length, fingerprints };
205
+ }
206
+ //# sourceMappingURL=trust.js.map
207
+ //# debugId=30a61f26-5db8-50ba-af37-1990a4742ce8
package/dist/types.d.ts CHANGED
@@ -166,6 +166,17 @@ export interface LinkedFile {
166
166
  /** Value that configures or reports note for this contract. */
167
167
  note?: string;
168
168
  }
169
+ /** Valid immutable provenance captured when a linked command enters tracker data. */
170
+ export interface LinkedTestProvenance {
171
+ /** Author identity recorded by the originating mutation. */
172
+ author: string;
173
+ /** ISO 8601 time at which the command entered tracker data. */
174
+ created_at: string;
175
+ /** Mutation path that introduced the command. */
176
+ source_kind: "local_mutation" | "merge_union";
177
+ /** Best-effort Git branch or hosted head ref of the originating mutation. */
178
+ source_ref?: string;
179
+ }
169
180
  /** Documents the linked test payload exchanged by command, SDK, and package integrations. */
170
181
  export interface LinkedTest {
171
182
  /** Value that configures or reports command for this contract. */
@@ -178,6 +189,12 @@ export interface LinkedTest {
178
189
  timeout_seconds?: number;
179
190
  /** Strategy used to control pm context behavior. */
180
191
  pm_context_mode?: "schema" | "tracker" | "auto";
192
+ /** Strategy used for source-workspace visibility and command working directory. */
193
+ workspace_context_mode?: "source" | "isolated" | "snapshot";
194
+ /** Immutable provenance captured when the linked command enters tracker data. */
195
+ provenance?: LinkedTestProvenance;
196
+ /** Explicit fail-closed marker retained when stored provenance is malformed. */
197
+ provenance_invalid?: true;
181
198
  /** Value that configures or reports env set for this contract. */
182
199
  env_set?: Record<string, string>;
183
200
  /** Value that configures or reports env clear for this contract. */
@@ -566,6 +583,10 @@ export interface ItemTestRunExecution {
566
583
  requested_pm_context_mode?: "schema" | "tracker" | "auto";
567
584
  /** Effective context mode used by the linked command. */
568
585
  pm_context_mode?: "schema" | "tracker" | "auto";
586
+ /** Effective source-workspace mode used by the linked command. */
587
+ workspace_context_mode?: "source" | "isolated" | "snapshot";
588
+ /** Trust classification applied before execution. */
589
+ trust_reason?: "legacy" | "local_mutation" | "local_source_ref" | "acknowledged" | "invalid_provenance" | "foreign_source_ref";
569
590
  }
570
591
  /** Documents the item test run summary payload exchanged by command, SDK, and package integrations. */
571
592
  export interface ItemTestRunSummary {
@@ -1121,6 +1142,8 @@ export interface PmSettings {
1121
1142
  /** Value that configures or reports testing for this contract. */
1122
1143
  testing: {
1123
1144
  record_results_to_items: boolean;
1145
+ /** Permit explicit one-shot execution of commands not yet trusted by this clone. */
1146
+ allow_untrusted_linked_tests: boolean;
1124
1147
  };
1125
1148
  /** Value that configures or reports telemetry for this contract. */
1126
1149
  telemetry: {
package/dist/types.js CHANGED
@@ -5,7 +5,7 @@
5
5
  */
6
6
  /** Supported values accepted by the builtin item type contract. */
7
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]="13c65470-8f5f-59ca-acb5-50c926c68870")}catch(e){}}();
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]="64223a83-1287-57ac-915d-dbd5e375a6eb")}catch(e){}}();
9
9
  export const BUILTIN_ITEM_TYPE_VALUES = [
10
10
  "Epic",
11
11
  "Feature",
@@ -243,4 +243,4 @@ export const CONTEXT_SECTION_VALUES = [
243
243
  "tests",
244
244
  ];
245
245
  //# sourceMappingURL=types.js.map
246
- //# debugId=13c65470-8f5f-59ca-acb5-50c926c68870
246
+ //# debugId=64223a83-1287-57ac-915d-dbd5e375a6eb
@@ -10,7 +10,10 @@ Implementation lineage:
10
10
  resolution and patch-free historical reads implemented by
11
11
  [pm-ffz0a9](../.agents/pm/issues/pm-ffz0a9.toon),
12
12
  [pm-v8gfi7](../.agents/pm/issues/pm-v8gfi7.toon), and
13
- [pm-3yxwv5](../.agents/pm/issues/pm-3yxwv5.toon).
13
+ [pm-3yxwv5](../.agents/pm/issues/pm-3yxwv5.toon). Cross-harness adapters and
14
+ operator-managed probe/vocabulary controls are tracked by
15
+ [pm-c0lrdm](../.agents/pm/features/pm-c0lrdm.toon) and
16
+ [pm-yds9dt](../.agents/pm/chores/pm-yds9dt.toon).
14
17
 
15
18
  Status: accepted amendment to
16
19
  [pm-qwuber](../.agents/pm/decisions/pm-qwuber.toon). The original stable-author
@@ -51,10 +54,13 @@ Provenance values are descriptive context, never authentication or
51
54
  authorization principals. Values are trimmed, length-bounded, and obtained only
52
55
  from literal descriptor keys, trusted caller data, or a named bounded resolver.
53
56
  Detection does not spawn processes, traverse process trees, evaluate user
54
- regexes, or access the network. The built-in Claude resolver may read only the
55
- tail of the current session's harness-owned JSONL file to recover its recorded
56
- model/version. It caps file bytes, lines, and line length, extracts only those
57
- two allow-listed values, and fails closed. `agent_identity.probes_enabled` or
57
+ regexes, or access the network. The built-in Claude and Codex resolvers read
58
+ only bounded windows of the current session's harness-owned JSONL file. Claude
59
+ uses the recent tail to recover its recorded model/version. Codex uses the
60
+ recent tail plus a bounded initial-head fallback for oversized sessions to
61
+ recover allow-listed `turn_context.model` and `turn_context.effort` values.
62
+ Both cap traversal, file bytes, lines, and line length, ignore all other fields,
63
+ never follow symlinks, and fail closed. `agent_identity.probes_enabled` or
58
64
  `PM_AGENT_PROBES=off` disables every local resolver without disabling ordinary
59
65
  environment, argv, client, or host detection.
60
66
 
@@ -113,6 +119,11 @@ fabricate precision that was never captured.
113
119
  The aggregate and core SDK entrypoints export:
114
120
 
115
121
  - `AGENT_PROVENANCE_DIMENSIONS`;
122
+ - `BUILTIN_AGENT_PROVENANCE_ADAPTERS`,
123
+ `listAgentProvenanceAdapters()`, and
124
+ `registerAgentProvenanceAdapters()`;
125
+ - `normalizeAgentProvenanceAdapterValue()` for stable model-family and effort
126
+ vocabulary projections that retain the bounded raw observation;
116
127
  - `detectAgentIdentity()` and `detectHarnessIdentity()`;
117
128
  - `analyzeAgentProvenanceDescriptorCoverage()` for the descriptor capability
118
129
  matrix and negative controls;
@@ -138,6 +149,31 @@ Legacy author interpretation is workspace-owned data under
138
149
  version and whether a harness was `recorded`, resolved by `vocabulary`, or
139
150
  remains `unresolved`; immutable authors and hashes are never rewritten.
140
151
 
152
+ `pm config get agent-identity-vocabulary`, `config list`, and `config export`
153
+ publish only its `version` and `alias_count`. Typed mutations use the existing
154
+ config transport:
155
+
156
+ ```bash
157
+ pm config set agent-identity-probes-enabled false
158
+ pm config set agent-identity-vocabulary --policy preview-add \
159
+ --value "Legacy Codex=codex" --criterion "Legacy Codex" --criterion "Alice"
160
+ pm config set agent-identity-vocabulary --policy add \
161
+ --value "Legacy Codex=codex"
162
+ pm config set agent-identity-vocabulary --policy remove --value "Legacy Codex"
163
+ ```
164
+
165
+ `preview-*` policies perform no write and report the exact residual unique
166
+ author count without returning author spellings. Real add/remove/clear changes
167
+ bump the vocabulary revision once; identical adds and absent removes are
168
+ idempotent. Aliases for already canonical `harness:<name>` authors fail closed.
169
+
170
+ Every built-in interactive harness has a contract-versioned adapter with an
171
+ implementation version, priority, covered dimensions, source classes,
172
+ normalization revisions, confidence, waivers, and immutable probe bounds.
173
+ Packages may register a new namespace or explicitly replace a built-in only at
174
+ a higher priority. Equal-priority ambiguity and descriptor mismatch fail
175
+ closed, and the disposer restores the prior adapter.
176
+
141
177
  The SDK/CLI matrix classifies every input as shared, positional, transport,
142
178
  presentation, local adapter, scope selector, compatibility alias, or SDK-native.
143
179
  An unknown CLI flag fails closed as `unclassified`. Committed test baselines cap
@@ -29,6 +29,7 @@ pm config project export --json
29
29
  pm config project get item-format --json
30
30
  pm config project set item-format --format toon
31
31
  pm config project set test-result-tracking --policy enabled
32
+ pm config project set untrusted-linked-test-execution --policy disabled
32
33
  ```
33
34
 
34
35
  `config set <key> <value>` also accepts the value as a positional argument; pm routes
@@ -85,6 +86,7 @@ When `settings.json` cannot be loaded, `pm` falls back to built-in defaults and
85
86
  | `history.compact_policy.max_entries` | when the policy is enabled, the entry count above which a stream is flagged by `pm health` and the default `pm history-compact --all-over` threshold (default `500`) |
86
87
  | `history.compact_policy.trigger` | policy intent when enabled: `health_warn` (advisory only) or `auto` (scheduled sweeps expected) |
87
88
  | `testing.record_results_to_items` | persist bounded linked-test summaries |
89
+ | `testing.allow_untrusted_linked_tests` | permit the explicit `pm test --allow-untrusted-linked-tests` one-run override (default `false`; does not trust or execute commands by itself) |
88
90
  | `validation.sprint_release_format` | `warn` or `strict_error` |
89
91
  | `validation.parent_reference` | `warn` or `strict_error` (`strict_error` is the built-in default; `pm create --allow-missing-parent` is the explicit escape hatch) |
90
92
  | `item_types.definitions[]` | custom item types and type options |
@@ -115,6 +117,7 @@ pm config project set schema_unknown_field_policy reject # (schema.unknown_fie
115
117
  pm config project set governance-duplicate-detection-mode strict # (governance.duplicate_detection_mode) off | advisory | strict
116
118
  pm config project set governance-duplicate-detection-threshold 0.8 # (governance.duplicate_detection_threshold) ratio 0..1
117
119
  pm config project set governance-duplicate-detection-limit 3 # (governance.duplicate_detection_limit) integer 0..20
120
+ pm config project set untrusted-linked-test-execution disabled # (testing.allow_untrusted_linked_tests) enabled | disabled
118
121
  ```
119
122
 
120
123
  Duplicate governance uses the shared SDK scorer and bounded metadata query
@@ -227,6 +230,7 @@ pm config project set sprint-release-format-policy --policy warn
227
230
  pm config project set parent-reference-policy --policy strict_error
228
231
  pm config project set history-missing-stream-policy --policy auto_create
229
232
  pm config project set test-result-tracking --policy enabled
233
+ pm config project set untrusted-linked-test-execution --policy disabled
230
234
  ```
231
235
 
232
236
  Use standalone checks when validating a repository:
@@ -58,6 +58,15 @@ estimates, applied stages, and omitted top-level fields. Minimal fallbacks bound
58
58
  the named omission list and disclose any additional count through
59
59
  `omitted_fields_overflow_count`.
60
60
 
61
+ Structured CLI refusals also include a compact `refusal` identity with the
62
+ failing command/flag/operand surface, rejected scalar when observable, complete
63
+ legal domain when one exists, and the process exit code. Recovery-bearing
64
+ errors use the `recovery_bundle` diagnostic class. Collection degradation may
65
+ compact explanatory domains inside `recovery`, but `refusal.legal_domain`
66
+ remains complete and `recovery.suggested_retry_args` is atomic: the projector
67
+ either retains the executable argv or removes the recovery rather than slicing
68
+ it into a dead command.
69
+
61
70
  The deterministic ladder is:
62
71
 
63
72
  1. full diagnostic;
@@ -73,8 +82,12 @@ envelope.
73
82
 
74
83
  ## Executable Assurance
75
84
 
76
- `pnpm quality:recovery-closure` builds the current CLI and replays 22 refusal
77
- contracts in isolated trackers. Ten representative, high-frequency failure
85
+ `pnpm quality:recovery-closure` builds the current CLI and replays 117 refusal
86
+ contracts in isolated trackers: 18 closed-domain rows, 88 required-argument
87
+ omissions derived from core executable positional signatures, seven
88
+ closed-action families, and four tracker-preflight states. Package-owned
89
+ commands enter executable coverage when their package runtime is active rather
90
+ than being misreported as core. Ten representative, high-frequency failure
78
91
  paths are also ratcheted by
79
92
  `scripts/release/diagnostic-output-baseline.json`. The gate requires every row
80
93
  to remain within the SDK-declared JSON ceiling and retain a mechanically
@@ -85,6 +98,10 @@ The baseline is a coverage ratchet, not permission to weaken a ceiling. Its
85
98
  negative control requires a missing baseline probe to fail. The existing
86
99
  refusal-closure negative controls independently prove that incomplete domains,
87
100
  broken retries, and malformed recovery envelopes remain blocking findings.
101
+ The grammar corpus additionally hashes authoritative tracker state around each
102
+ refusal; schema, items, history, settings, and package state must not change.
103
+ Ephemeral runtime lock/cache directories are excluded from that semantic
104
+ snapshot.
88
105
 
89
106
  Run the focused proof with:
90
107
 
@@ -1,6 +1,6 @@
1
1
  # Multi-Branch Tracker Merge Safety
2
2
 
3
- Tracked by [pm-wc1r](../.agents/pm/features/pm-wc1r.toon), with the integrity and concurrency fixes [pm-9q2t](../.agents/pm/issues/pm-9q2t.toon), [pm-cxyv](../.agents/pm/issues/pm-cxyv.toon), [pm-gpo7](../.agents/pm/issues/pm-gpo7.toon), [pm-m3nl](../.agents/pm/issues/pm-m3nl.toon), [pm-wwfd](../.agents/pm/issues/pm-wwfd.toon), and [pm-xdn6](../.agents/pm/issues/pm-xdn6.toon). Fresh-init fence ownership is tracked by [pm-1w3ljt](../.agents/pm/issues/pm-1w3ljt.toon); runtime-cache index governance by [pm-hous](../.agents/pm/issues/pm-hous.toon); local allocation safety by [pm-khdq](../.agents/pm/issues/pm-khdq.toon); fence-coverage completeness and drift detection by [pm-i4fx](../.agents/pm/issues/pm-i4fx.toon); package-defined item coverage by [pm-5rexki](../.agents/pm/issues/pm-5rexki.toon); non-item JSON coverage by [pm-gjicmx](../.agents/pm/issues/pm-gjicmx.toon); portable driver identity by [pm-w91mvg](../.agents/pm/issues/pm-w91mvg.toon); pending receipt validation by [pm-ysqb6n](../.agents/pm/issues/pm-ysqb6n.toon); receipt classification by [pm-jtwsct](../.agents/pm/issues/pm-jtwsct.toon); direction-independent item conflict selection by [pm-dlx7v7](../.agents/pm/issues/pm-dlx7v7.toon); corrected reconciliation guidance by [pm-lwmstb](../.agents/pm/issues/pm-lwmstb.toon); cross-branch id collision safety by [pm-pibw](../.agents/pm/issues/pm-pibw.toon); auditable merge history by [pm-9j2r3b](../.agents/pm/tasks/pm-9j2r3b.toon); durable conflict decisions by [pm-rh98vo](../.agents/pm/issues/pm-rh98vo.toon); continuous conformance by [pm-76dnfg](../.agents/pm/tasks/pm-76dnfg.toon); workspace-wide CI enforcement by [pm-pdr8t1](../.agents/pm/tasks/pm-pdr8t1.toon); post-merge reconciliation by [pm-mfkv92](../.agents/pm/issues/pm-mfkv92.toon); this repository's own adoption by [pm-iwsj](../.agents/pm/chores/pm-iwsj.toon).
3
+ Tracked by [pm-wc1r](../.agents/pm/features/pm-wc1r.toon), with the integrity and concurrency fixes [pm-9q2t](../.agents/pm/issues/pm-9q2t.toon), [pm-cxyv](../.agents/pm/issues/pm-cxyv.toon), [pm-gpo7](../.agents/pm/issues/pm-gpo7.toon), [pm-m3nl](../.agents/pm/issues/pm-m3nl.toon), [pm-wwfd](../.agents/pm/issues/pm-wwfd.toon), and [pm-xdn6](../.agents/pm/issues/pm-xdn6.toon). Fresh-init fence ownership is tracked by [pm-1w3ljt](../.agents/pm/issues/pm-1w3ljt.toon); runtime-cache index governance by [pm-hous](../.agents/pm/issues/pm-hous.toon); local allocation safety by [pm-khdq](../.agents/pm/issues/pm-khdq.toon); fence-coverage completeness and drift detection by [pm-i4fx](../.agents/pm/issues/pm-i4fx.toon); package-defined item coverage by [pm-5rexki](../.agents/pm/issues/pm-5rexki.toon); non-item JSON coverage by [pm-gjicmx](../.agents/pm/issues/pm-gjicmx.toon); portable driver identity by [pm-w91mvg](../.agents/pm/issues/pm-w91mvg.toon); pending receipt validation by [pm-ysqb6n](../.agents/pm/issues/pm-ysqb6n.toon); receipt classification by [pm-jtwsct](../.agents/pm/issues/pm-jtwsct.toon); direction-independent item conflict selection by [pm-dlx7v7](../.agents/pm/issues/pm-dlx7v7.toon); corrected reconciliation guidance by [pm-lwmstb](../.agents/pm/issues/pm-lwmstb.toon); cross-branch id collision safety by [pm-pibw](../.agents/pm/issues/pm-pibw.toon); auditable merge history by [pm-9j2r3b](../.agents/pm/tasks/pm-9j2r3b.toon); durable conflict decisions by [pm-rh98vo](../.agents/pm/issues/pm-rh98vo.toon); continuous conformance by [pm-76dnfg](../.agents/pm/tasks/pm-76dnfg.toon); workspace-wide CI enforcement by [pm-pdr8t1](../.agents/pm/tasks/pm-pdr8t1.toon); post-merge reconciliation by [pm-mfkv92](../.agents/pm/issues/pm-mfkv92.toon); linked-command execution trust by [pm-ed28wi](../.agents/pm/issues/pm-ed28wi.toon); this repository's own adoption by [pm-iwsj](../.agents/pm/chores/pm-iwsj.toon).
4
4
 
5
5
  pm stores project context as reviewable repository files. Concurrent agents can therefore use ordinary branches and worktrees, but tracker artifacts need semantic merge behavior: raw line merging cannot preserve TOON collection counts, JSON object structure, or append-only history hash chains.
6
6
 
@@ -61,6 +61,20 @@ pm merge install --dry-run --json
61
61
  | tracker `**/*.jsonl` except the later `history/*.jsonl` override | `pm-relationship` | Covers default and package-owned custom relationship event paths, unions divergent suffixes by `eventId` (timestamp-ordered, ours-first on ties), and renumbers `sequence` consecutively so the strict-sequence store loader accepts the merged stream. |
62
62
  | root `settings.json` and nested `**/*.json` | `pm-json` | Recursively merges objects per key. Arrays compose when both branches preserve the base and add distinct entries, so independent extension installs and evaluation additions merge without weakening edit/removal conflict detection. |
63
63
 
64
+ The `tests` collection has an additional execution-safety rule. Its semantic
65
+ identity excludes provenance so the same command/context does not duplicate
66
+ when branches record different authorship metadata. A test definition newly
67
+ contributed by the other merge side is persisted with
68
+ `provenance.source_kind=merge_union` while retaining its author, creation time,
69
+ and source ref. `pm test --run` and `pm test-all` refuse that command before
70
+ spawning a process until the receiving clone acknowledges its fingerprint or
71
+ uses the two-part project-policy plus per-run override documented in
72
+ [Testing](TESTING.md#linked-test-command-trust). `pm validate
73
+ --check-command-references` reports outstanding entries. This is the threat
74
+ boundary for append-like data that becomes executable after a merge; ordinary
75
+ notes, files, and other non-executable collections retain normal set-union
76
+ behavior.
77
+
64
78
  When both sides change the same item scalar differently, the driver writes the same stable value regardless of which branch Git labels ours or theirs, but exits nonzero. Item results and receipts expose the caller's `requested_preference`; the per-decision `retained` and `discarded` values or hashes are authoritative because stable value order can retain either side. Readers normalize the legacy receipt key `preferred`, while new receipts no longer emit it. JSON leaf conflicts retain the explicit preferred-side policy. Git keeps either path conflicted so a human or coordinating agent must review the discarded value and explicitly `git add` the resolution. This correction is tracked by [pm-qckpnq](../.agents/pm/issues/pm-qckpnq.toon).
65
79
 
66
80
  The driver result's `guidance` always points unresolved conflicts to `pm merge report`. When a clone-local receipt exists, guidance includes its privacy-safe receipt and item ids for exact correlation; discarded values remain confined to the local receipt and never appear in generic logs or tracker history. Tracked by [pm-fbrz7p](../.agents/pm/issues/pm-fbrz7p.toon).
@@ -1,6 +1,6 @@
1
1
  # Universal Read Output Contracts
2
2
 
3
- Tracker references: [pm-hb7ug8](../.agents/pm/features/pm-hb7ug8.toon), [pm-cxr0jb](../.agents/pm/features/pm-cxr0jb.toon), [pm-hid9g1](../.agents/pm/features/pm-hid9g1.toon), [pm-h8tpeh](../.agents/pm/features/pm-h8tpeh.toon), [pm-5t33or](../.agents/pm/features/pm-5t33or.toon), [pm-srns](../.agents/pm/issues/pm-srns.toon), [pm-sb0tns](../.agents/pm/issues/pm-sb0tns.toon), [pm-gjjurs](../.agents/pm/issues/pm-gjjurs.toon), [pm-eugaqy](../.agents/pm/issues/pm-eugaqy.toon), [pm-jt8aa2](../.agents/pm/issues/pm-jt8aa2.toon), [pm-kyjdne](../.agents/pm/issues/pm-kyjdne.toon), [pm-8nev0o](../.agents/pm/issues/pm-8nev0o.toon), [pm-e5gl05](../.agents/pm/issues/pm-e5gl05.toon), [pm-cha95z](../.agents/pm/tasks/pm-cha95z.toon), and [pm-2qvq7a](../.agents/pm/issues/pm-2qvq7a.toon).
3
+ Tracker references: [pm-hb7ug8](../.agents/pm/features/pm-hb7ug8.toon), [pm-cxr0jb](../.agents/pm/features/pm-cxr0jb.toon), [pm-hid9g1](../.agents/pm/features/pm-hid9g1.toon), [pm-h8tpeh](../.agents/pm/features/pm-h8tpeh.toon), [pm-5t33or](../.agents/pm/features/pm-5t33or.toon), [pm-srns](../.agents/pm/issues/pm-srns.toon), [pm-sb0tns](../.agents/pm/issues/pm-sb0tns.toon), [pm-gjjurs](../.agents/pm/issues/pm-gjjurs.toon), [pm-eugaqy](../.agents/pm/issues/pm-eugaqy.toon), [pm-jt8aa2](../.agents/pm/issues/pm-jt8aa2.toon), [pm-kyjdne](../.agents/pm/issues/pm-kyjdne.toon), [pm-8nev0o](../.agents/pm/issues/pm-8nev0o.toon), [pm-e5gl05](../.agents/pm/issues/pm-e5gl05.toon), [pm-cha95z](../.agents/pm/tasks/pm-cha95z.toon), [pm-2qvq7a](../.agents/pm/issues/pm-2qvq7a.toon), and [pm-xam9bt](../.agents/pm/issues/pm-xam9bt.toon).
4
4
 
5
5
  ## Agent Quick Context
6
6
 
@@ -164,6 +164,16 @@ an unbounded multi-megabyte response;
164
164
  `recovery_budget_multiplier: 1` declares that each next page retains the same
165
165
  useful-result ceiling.
166
166
 
167
+ When compaction affects a nested collection that is not declared resumable,
168
+ the recovery budget is derived from the larger of the binding ceiling plus one
169
+ and the measured pre-compaction result. The SDK adds a 25% envelope margin and
170
+ rounds up to 100 tokens. The CLI, SDK, and MCP bindings therefore receive the
171
+ same finite value, which is strictly larger than the request that already
172
+ truncated. If safe-integer arithmetic cannot represent that value, recovery
173
+ uses `unbounded` explicitly. `resolveReadOutputRecoveryBudget()` exposes the
174
+ versioned pure calculation to package authors; there is no hard-coded smaller
175
+ retry and no recovery cycle.
176
+
167
177
  Assurance declares `budget_retention_policy: verdict_priority`: failing block,
168
178
  warn, and observe rows precede retired and passing rows while preserving order
169
179
  within each class. `assertions_total` remains the pre-projection denominator,
@@ -1,7 +1,8 @@
1
1
  # Reproducible Workspaces and Snapshots
2
2
 
3
- Tracked by [pm-rbcvt2](../.agents/pm/features/pm-rbcvt2.toon) and
4
- [pm-dkrmzv](../.agents/pm/features/pm-dkrmzv.toon).
3
+ Tracked by [pm-rbcvt2](../.agents/pm/features/pm-rbcvt2.toon),
4
+ [pm-dkrmzv](../.agents/pm/features/pm-dkrmzv.toon), and
5
+ [pm-gh1089](../.agents/pm/issues/pm-gh1089.toon).
5
6
 
6
7
  These SDK primitives make a pm workspace reproducible without changing normal
7
8
  interactive behavior. Recipes control time and identifier entropy only inside
@@ -22,9 +23,7 @@ const recipe: WorkspaceRecipe = {
22
23
  seed: "example-fixture-v1",
23
24
  clock: "2026-07-28T10:00:00.000Z",
24
25
  tickMs: 1,
25
- operations: [
26
- { action: "create", input: { title: "Deterministic task" } },
27
- ],
26
+ operations: [{ action: "create", input: { title: "Deterministic task" } }],
28
27
  };
29
28
 
30
29
  const results = await executeWorkspaceRecipe(
@@ -36,9 +35,9 @@ const results = await executeWorkspaceRecipe(
36
35
  The caller owns action dispatch. pm owns the deterministic execution context:
37
36
  `nowIso()` advances from `clock` by `tickMs`, and generated item identifiers
38
37
  derive from `seed`. Async scopes are isolated with `AsyncLocalStorage`, so
39
- concurrent recipes do not share counters. Outside `runWithWorkspaceRecipe` or
40
- `executeWorkspaceRecipe`, the CLI continues using the system clock and
41
- cryptographic randomness.
38
+ concurrent recipes do not share counters. Normal CLI and MCP execution still
39
+ uses the system clock and cryptographic randomness unless the process opts in
40
+ through the environment contract below.
42
41
 
43
42
  `defineWorkspaceRecipe` immediately validates the clock, seed, tick, schema,
44
43
  operation actions, and recursively JSON-compatible inputs. Its TypeScript
@@ -51,6 +50,41 @@ Seeds are reproducibility inputs, not secrets. A replay is byte-identical only
51
50
  when it begins from equivalent authoritative state and invokes the same ordered
52
51
  operations with the same recipe.
53
52
 
53
+ ## CLI and MCP process configuration
54
+
55
+ Shell-based harnesses can install the same SDK-owned deterministic context for
56
+ the `pm` and `pm-mcp` processes without patching JavaScript globals:
57
+
58
+ ```bash
59
+ PM_CLOCK=2026-07-28T10:00:00.000Z \
60
+ PM_CLOCK_TICK_MS=1 \
61
+ PM_SEED=example-fixture-v1 \
62
+ pm create --title "Deterministic task" --type Task --json
63
+ ```
64
+
65
+ `PM_CLOCK` and `PM_SEED` are required together. `PM_CLOCK` must be an ISO-8601
66
+ instant with `Z` or a numeric UTC offset; timezone-less and non-ISO inputs are
67
+ rejected so separate hosts cannot interpret the same configuration differently.
68
+ `PM_CLOCK_TICK_MS` is optional, defaults to `1`, and must be a non-negative integer. Partial or invalid input
69
+ fails before command dispatch with the stable
70
+ `invalid_reproducible_process_environment` code and recovery that names the
71
+ missing or invalid variable. Unset all three variables for normal interactive
72
+ behavior.
73
+
74
+ Every CLI process invocation begins a fresh deterministic scope. A workflow
75
+ that launches several CLI processes must derive a stable, distinct seed for
76
+ each ordered step; reusing one seed intentionally reproduces the same entropy
77
+ sequence and can reproduce an existing generated identifier. A long-lived MCP
78
+ server instead owns one process-lifetime sequence and advances it across its
79
+ serialized JSON-RPC requests. Two equivalent servers therefore produce the
80
+ same files without generating duplicate identifiers inside either server.
81
+
82
+ The public SDK exports `PM_REPRODUCIBLE_PROCESS_ENV`,
83
+ `resolveReproducibleProcessEnvironment`,
84
+ `runWithReproducibleProcessEnvironment`, and
85
+ `createReproducibleProcessRunner` for custom one-shot and long-lived process
86
+ transports.
87
+
54
88
  ## Content-addressed snapshots
55
89
 
56
90
  The SDK exports:
package/docs/SDK.md CHANGED
@@ -1702,6 +1702,15 @@ const telemetry = await runTelemetry(
1702
1702
  global tracker roots. `pm_context_mode`, run-level overrides, automatic tracker
1703
1703
  context, assertion requirements, empty-run detection, and failure categories are
1704
1704
  part of the SDK result contract rather than presentation-layer behavior.
1705
+ Workspace results distinguish the run-level requested mode from the effective
1706
+ per-test mode. `isolated` executes from an empty disposable directory;
1707
+ `snapshot` executes from a filtered disposable workspace. Trust preflight
1708
+ failures report `trust_refusal`, while malformed or incomplete provenance uses
1709
+ the additive `provenance_invalid` storage marker and fail-closed
1710
+ `invalid_provenance` trust reason. Package mutation adapters can call
1711
+ `attachLinkedTestMutationProvenance` so Git inspection occurs only when commands
1712
+ are added; `resolveLinkedTestSourceWorkspaceRoot` keeps their source-ref lookup
1713
+ aligned with CLI execution and `PM_SOURCE_WORKSPACE_ROOT` overrides.
1705
1714
  When item test-result tracking is enabled, `measure` persists typed numeric
1706
1715
  evidence on the producing run. Each entry has a stable name, finite numeric
1707
1716
  value, optional unit and threshold, and the run timestamp. Run history remains
@@ -6,7 +6,8 @@ Tracker references: [pm-9wbiye](../.agents/pm/issues/pm-9wbiye.toon),
6
6
  [pm-3zgh2c](../.agents/pm/features/pm-3zgh2c.toon),
7
7
  [pm-eq9dlw](../.agents/pm/issues/pm-eq9dlw.toon), and
8
8
  [pm-lu6sca](../.agents/pm/features/pm-lu6sca.toon), plus
9
- [pm-5q8wa0](../.agents/pm/issues/pm-5q8wa0.toon).
9
+ [pm-5q8wa0](../.agents/pm/issues/pm-5q8wa0.toon) and
10
+ [pm-c0lrdm](../.agents/pm/features/pm-c0lrdm.toon).
10
11
 
11
12
  Project management is context management. The public SDK therefore carries a
12
13
  session's purpose and episode boundary through the same immutable history that
@@ -128,9 +129,10 @@ public SDK exports for custom hosts.
128
129
  the bounded built-in resolver. It never contains environment values, session
129
130
  paths, prompts, or file contents.
130
131
 
131
- New mutation history records failed resolver outcomes under
132
- `context.agent_provenance_outcomes`; ordinary unavailable dimensions retain
133
- the compact legacy-compatible null projection. A resolver is only counted as
132
+ New mutation history records failed or explicitly unavailable configured
133
+ resolver outcomes under `context.agent_provenance_outcomes`; dimensions with
134
+ no configured resolver retain the compact legacy-compatible null projection.
135
+ A resolver is only counted as
134
136
  attempted when its required input belongs to the detected harness; a foreign
135
137
  host's shared `AI_AGENT` value is not Codex input. Consequently `pm health` can report
136
138
  `provenance_resolver_zero_success:<harness>:<dimension>:<resolver>:<attempts>`