@unbrained/pm-cli 2026.8.11 → 2026.8.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/CHANGELOG.md +52 -21
- package/dist/cli/commander-usage.js +37 -11
- package/dist/cli/error-guidance.d.ts +2 -0
- package/dist/cli/error-guidance.js +80 -13
- package/dist/cli/main.js +34 -19
- package/dist/cli/register-annotations.js +56 -3
- package/dist/cli/register-list-query.js +10 -7
- package/dist/cli/register-operations.js +16 -6
- package/dist/cli-bundle/bundle-manifest.json +157 -157
- package/dist/cli-bundle/chunks/{chunk-2OOU2AFH.js → chunk-3PS7X6GS.js} +2 -2
- package/dist/cli-bundle/chunks/chunk-7KL6KMWF.js +13 -0
- package/dist/cli-bundle/chunks/{chunk-WMEZHCFI.js → chunk-BM5YJ2UW.js} +51 -43
- package/dist/cli-bundle/chunks/chunk-D55A5AEN.js +33 -0
- package/dist/cli-bundle/chunks/chunk-KIKDWECH.js +196 -0
- package/dist/cli-bundle/chunks/{chunk-ZU34C3LH.js → chunk-KOQMGCTA.js} +2 -2
- package/dist/cli-bundle/chunks/chunk-R76342NV.js +2 -0
- package/dist/cli-bundle/chunks/chunk-RBTK5EZV.js +5 -0
- package/dist/cli-bundle/chunks/{chunk-IUNRVDAI.js → chunk-VLUQOIDT.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-A2SJGHSI.js → chunk-WLAHPSOX.js} +2 -2
- package/dist/cli-bundle/chunks/register-list-query-XPCJRUUD.js +10 -0
- package/dist/cli-bundle/chunks/register-mutation-4RJCBKRS.js +20 -0
- package/dist/cli-bundle/chunks/register-operations-2OGISOVR.js +2 -0
- package/dist/cli-bundle/chunks/{register-setup-NOCOOWQY.js → register-setup-LLHAKP2V.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-2DALMVF7.js +5 -0
- package/dist/cli-bundle/focused-chunks/chunk-5OQIMELN.js +155 -0
- package/dist/cli-bundle/focused-chunks/chunk-5TAGTDRE.js +2 -0
- package/dist/cli-bundle/focused-chunks/{chunk-FPSYO5ZG.js → chunk-ECADZMVC.js} +11 -11
- package/dist/cli-bundle/focused-chunks/chunk-EJGXRA6B.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-FFGSZCPK.js +16 -0
- package/dist/cli-bundle/focused-chunks/{chunk-BKPIY34S.js → chunk-GQW4BPUT.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-ISKOBJCM.js +2 -0
- package/dist/cli-bundle/focused-chunks/{chunk-UDAZWT25.js → chunk-JAFRIWVF.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-2DL3MRTD.js → chunk-KHM2CS2C.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-J5EKAIVD.js → chunk-KPSXVBDV.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-23FTGO3W.js → chunk-KTLOKKCQ.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-LGYK3NUF.js +8 -0
- package/dist/cli-bundle/focused-chunks/{chunk-KDH5OX3D.js → chunk-NBLUKITQ.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-55BIB6DV.js → chunk-NYLOUPOB.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-DPO7TEFX.js → chunk-Q4T463SD.js} +28 -28
- package/dist/cli-bundle/focused-chunks/chunk-QVZVY53N.js +2 -0
- package/dist/cli-bundle/focused-chunks/{chunk-TL5M2PAY.js → chunk-RWPAWUOT.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-UTEWXDEQ.js +17 -0
- package/dist/cli-bundle/focused-chunks/{chunk-4H5IA6AV.js → chunk-YIAXMC3T.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-ZMGWAYH7.js +2 -0
- package/dist/cli-bundle/main.js +13 -13
- package/dist/cli-bundle/sdk-authoring.js +1 -1
- package/dist/cli-bundle/sdk-contracts.js +1 -1
- package/dist/cli-bundle/sdk-core.js +40 -40
- package/dist/cli-bundle/sdk-governance.js +1 -1
- package/dist/cli-bundle/sdk-graph.js +1 -1
- package/dist/cli-bundle/sdk-merge.js +1 -1
- package/dist/cli-bundle/sdk-query.js +1 -1
- package/dist/cli-bundle/sdk-runtime.js +1 -1
- package/dist/cli-bundle/sdk-testing.js +1 -1
- package/dist/cli-bundle/sdk.js +2 -2
- package/dist/core/extensions/reserved-host-flags.js +3 -2
- package/dist/core/history/drift-scan.js +13 -8
- package/dist/core/history/history.d.ts +8 -0
- package/dist/core/history/history.js +57 -7
- package/dist/core/history/replay.d.ts +9 -1
- package/dist/core/history/replay.js +48 -13
- package/dist/core/output/output.d.ts +2 -0
- package/dist/core/output/output.js +46 -14
- package/dist/core/shared/command-types.d.ts +2 -0
- package/dist/core/shared/command-types.js +2 -2
- package/dist/core/shared/errors.d.ts +5 -0
- package/dist/core/shared/errors.js +2 -2
- package/dist/core/store/item-metadata-cache.d.ts +1 -1
- package/dist/core/store/item-metadata-cache.js +14 -7
- package/dist/sdk/cli-bootstrap.d.ts +2 -0
- package/dist/sdk/cli-bootstrap.js +6 -2
- package/dist/sdk/cli-contracts/completeness.js +3 -2
- package/dist/sdk/cli-contracts/flag-contracts.js +5 -2
- package/dist/sdk/cli-contracts/registration-helpers.d.ts +20 -3
- package/dist/sdk/cli-contracts/registration-helpers.js +18 -12
- package/dist/sdk/cli-contracts/schemas/assurance-definition.d.ts +8 -6
- package/dist/sdk/cli-contracts/schemas/assurance-definition.js +4 -3
- package/dist/sdk/cli-contracts/tool-option-contracts.js +3 -2
- package/dist/sdk/cli-contracts/tool-parameter-tables.js +22 -2
- package/dist/sdk/cli-contracts/tool-schema.d.ts +2 -2
- package/dist/sdk/cli-contracts/tool-schema.js +7 -4
- package/dist/sdk/cli-program.js +3 -2
- package/dist/sdk/completion.js +16 -7
- package/dist/sdk/context-intent-contracts.js +45 -4
- package/dist/sdk/core.d.ts +1 -0
- package/dist/sdk/core.js +3 -2
- package/dist/sdk/generated/generated-error-code-catalog-part-1.d.ts +8 -0
- package/dist/sdk/generated/generated-error-code-catalog-part-1.js +1585 -0
- package/dist/sdk/generated/generated-error-code-catalog-part-2.d.ts +8 -0
- package/dist/sdk/generated/generated-error-code-catalog-part-2.js +1608 -0
- package/dist/sdk/generated-error-code-catalog.js +6 -3140
- package/dist/sdk/governance/assurance-action.js +9 -7
- package/dist/sdk/governance/assurance-mutation-error.d.ts +7 -0
- package/dist/sdk/governance/assurance-mutation-error.js +35 -0
- package/dist/sdk/governance/assurance-runtime.js +11 -8
- package/dist/sdk/governance/assurance.d.ts +12 -6
- package/dist/sdk/governance/assurance.js +181 -66
- package/dist/sdk/governance/health.d.ts +3 -1
- package/dist/sdk/governance/health.js +21 -4
- package/dist/sdk/governance/validate-item-reader.js +3 -3
- package/dist/sdk/governance/validate.d.ts +2 -0
- package/dist/sdk/governance/validate.js +13 -3
- package/dist/sdk/graph/durable-cache.d.ts +1 -1
- package/dist/sdk/graph/durable-cache.js +98 -29
- package/dist/sdk/graph/governance.d.ts +14 -0
- package/dist/sdk/graph/governance.js +34 -9
- package/dist/sdk/index.d.ts +1 -1
- package/dist/sdk/index.js +3 -3
- package/dist/sdk/lifecycle/claim.js +10 -3
- package/dist/sdk/merge/driver.d.ts +2 -2
- package/dist/sdk/merge/driver.js +3 -3
- package/dist/sdk/merge/receipts.d.ts +6 -4
- package/dist/sdk/merge/receipts.js +7 -5
- package/dist/sdk/merge/three-way.d.ts +2 -2
- package/dist/sdk/merge/three-way.js +3 -3
- package/dist/sdk/output-projection.d.ts +9 -1
- package/dist/sdk/output-projection.js +45 -9
- package/dist/sdk/query/activity.d.ts +49 -2
- package/dist/sdk/query/activity.js +183 -37
- package/dist/sdk/query/get.js +19 -2
- package/dist/sdk/read-output-contracts.d.ts +5 -1
- package/dist/sdk/read-output-contracts.js +17 -2
- package/dist/sdk/read-output-session.d.ts +2 -0
- package/dist/sdk/read-output-session.js +12 -5
- package/dist/sdk/runtime-primitives.d.ts +3 -3
- package/dist/sdk/runtime-primitives.js +5 -5
- package/dist/sdk/runtime-stats-options.js +3 -2
- package/dist/sdk/runtime.js +5 -9
- package/dist/sdk/stats.d.ts +36 -3
- package/dist/sdk/stats.js +55 -15
- package/dist/sdk/test/execution.d.ts +4 -0
- package/dist/sdk/test/execution.js +86 -21
- package/dist/types.d.ts +2 -0
- package/dist/types.js +2 -2
- package/docs/ASSURANCE.md +17 -12
- package/docs/COMMANDS.md +29 -8
- package/docs/CONFIGURATION.md +7 -0
- package/docs/DEPENDENCY_KIND_CONTRACT.md +10 -8
- package/docs/MERGE_SAFETY.md +3 -1
- package/docs/OUTPUT_PROJECTION_CONTRACTS.md +26 -13
- package/docs/README.md +2 -0
- package/docs/READ_OUTPUT_CONTRACTS.md +17 -2
- package/docs/SDK_AGENT_SESSION_CONTEXT.md +12 -1
- package/docs/SDK_CONTEXT_EVIDENCE_CONTRACTS.md +53 -0
- package/docs/SDK_CONTEXT_INTEGRITY.md +28 -1
- package/docs/TRUSTWORTHY_CONTEXT_EVIDENCE.md +106 -0
- package/docs/performance/sdk-entrypoint-import-costs.md +6 -2
- package/marketplace.json +2 -2
- package/package.json +5 -5
- package/packages/pm-beads/package.json +1 -1
- package/packages/pm-calendar/package.json +1 -1
- package/packages/pm-command-kit/package.json +1 -1
- package/packages/pm-digital-twin/package.json +1 -1
- package/packages/pm-governance-audit/package.json +1 -1
- package/packages/pm-guide-shell/package.json +1 -1
- package/packages/pm-kanban/package.json +1 -1
- package/packages/pm-lifecycle-hooks/package.json +1 -1
- package/packages/pm-linked-test-adapters/package.json +1 -1
- package/packages/pm-search-advanced/package.json +1 -1
- package/packages/pm-templates/package.json +1 -1
- package/packages/pm-todos/package.json +1 -1
- package/packages/pm-vcs/package.json +1 -1
- package/plugins/pm-claude/.claude-plugin/plugin.json +1 -1
- package/plugins/pm-codex/.codex-plugin/plugin.json +1 -1
- package/sdk/public-surface.json +203 -52
- package/dist/cli-bundle/chunks/chunk-2RA2YTHJ.js +0 -33
- package/dist/cli-bundle/chunks/chunk-DSKYD4LG.js +0 -13
- package/dist/cli-bundle/chunks/chunk-HC3GM6YY.js +0 -194
- package/dist/cli-bundle/chunks/chunk-W7CPZGSB.js +0 -2
- package/dist/cli-bundle/chunks/chunk-ZXOC6HG5.js +0 -5
- package/dist/cli-bundle/chunks/register-list-query-HAUXVUPY.js +0 -10
- package/dist/cli-bundle/chunks/register-mutation-7Y5U7ABT.js +0 -20
- package/dist/cli-bundle/chunks/register-operations-EQX3DLAZ.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-3AYKXMYX.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-4AHEC74N.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-4SV3ZKG3.js +0 -16
- package/dist/cli-bundle/focused-chunks/chunk-6JW6Z2P5.js +0 -8
- package/dist/cli-bundle/focused-chunks/chunk-D4PIWE6D.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-E3KCL7AY.js +0 -5
- package/dist/cli-bundle/focused-chunks/chunk-ERAAFLPS.js +0 -153
- package/dist/cli-bundle/focused-chunks/chunk-F6E4JZKG.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-OJ3LVVIS.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-VABB4L5Y.js +0 -17
|
@@ -7,12 +7,15 @@
|
|
|
7
7
|
* one deterministic SDK path.
|
|
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]="
|
|
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]="ecc8eb89-b9e9-51fa-8573-2afb8ec801be")}catch(e){}}();
|
|
11
|
+
import { createHash } from "node:crypto";
|
|
11
12
|
import path from "node:path";
|
|
12
13
|
import { EXIT_CODE } from "../../core/shared/constants.js";
|
|
13
14
|
import { PmCliError } from "../../core/shared/errors.js";
|
|
14
|
-
import { appendWorkspaceAuditEvent, mutateWorkspaceJsonWithHistory, readFileIfExists, readHistoryEntries, resolveAuthor, stableStringify, WORKSPACE_HISTORY_ID, getWorkspaceHistoryPath, } from "../runtime-primitives.js";
|
|
15
|
+
import { appendWorkspaceAuditEvent, mutateWorkspaceJsonWithHistory, readFileIfExists, readHistoryEntries, readSettings, resolveAuthor, stableStringify, WORKSPACE_HISTORY_ID, getWorkspaceHistoryPath, } from "../runtime-primitives.js";
|
|
16
|
+
import { resolveCanonicalRelationshipKind } from "../relationships.js";
|
|
15
17
|
import { MAX_ASSURANCE_VERDICT_LIMIT } from "./assurance-limits.js";
|
|
18
|
+
import { AssuranceMutationRefusalError } from "./assurance-mutation-error.js";
|
|
16
19
|
/** Current serialized assurance registry format. */
|
|
17
20
|
export const ASSURANCE_DOCUMENT_VERSION = 1;
|
|
18
21
|
/** Supported lifecycle points that can evaluate a gate. */
|
|
@@ -43,18 +46,18 @@ const EVALUATION_CONCURRENCY = 4;
|
|
|
43
46
|
const DEFAULT_VERDICT_LIMIT = 50;
|
|
44
47
|
function requireStableId(value, field) {
|
|
45
48
|
if (!ID_PATTERN.test(value)) {
|
|
46
|
-
throw new
|
|
49
|
+
throw new AssuranceMutationRefusalError(`${field} must be a stable lowercase id`);
|
|
47
50
|
}
|
|
48
51
|
}
|
|
49
52
|
function requireFiniteNonNegative(value, field) {
|
|
50
53
|
if (!Number.isFinite(value) || value < 0) {
|
|
51
|
-
throw new
|
|
54
|
+
throw new AssuranceMutationRefusalError(`${field} must be a finite non-negative number`);
|
|
52
55
|
}
|
|
53
56
|
}
|
|
54
57
|
function boundFor(assertion) {
|
|
55
58
|
const present = POLARITY_KEYS.filter((key) => assertion[key] !== undefined);
|
|
56
59
|
if (present.length !== 1) {
|
|
57
|
-
throw new
|
|
60
|
+
throw new AssuranceMutationRefusalError(`assertion ${assertion.id} must declare exactly one polarity`);
|
|
58
61
|
}
|
|
59
62
|
const polarity = present[0];
|
|
60
63
|
const raw = assertion[polarity];
|
|
@@ -79,10 +82,12 @@ function compareBound(observed, bound) {
|
|
|
79
82
|
return false;
|
|
80
83
|
}
|
|
81
84
|
const numericBound = bound.value;
|
|
82
|
-
if (bound.polarity === "ceiling" ||
|
|
85
|
+
if (bound.polarity === "ceiling" ||
|
|
86
|
+
bound.polarity === "monotone_nonincreasing") {
|
|
83
87
|
return observed <= numericBound;
|
|
84
88
|
}
|
|
85
|
-
if (bound.polarity === "floor" ||
|
|
89
|
+
if (bound.polarity === "floor" ||
|
|
90
|
+
bound.polarity === "monotone_nondecreasing") {
|
|
86
91
|
return observed >= numericBound;
|
|
87
92
|
}
|
|
88
93
|
return observed === 0;
|
|
@@ -97,12 +102,15 @@ function proveNegativeControl(assertion) {
|
|
|
97
102
|
function boundUpdateIsLoosening(beforeBound, afterBound) {
|
|
98
103
|
if (beforeBound.polarity !== afterBound.polarity)
|
|
99
104
|
return true;
|
|
100
|
-
if (typeof beforeBound.value === "number" &&
|
|
101
|
-
|
|
105
|
+
if (typeof beforeBound.value === "number" &&
|
|
106
|
+
typeof afterBound.value === "number") {
|
|
107
|
+
return beforeBound.polarity === "ceiling" ||
|
|
108
|
+
beforeBound.polarity === "monotone_nonincreasing"
|
|
102
109
|
? afterBound.value > beforeBound.value
|
|
103
|
-
: beforeBound.polarity === "floor" ||
|
|
110
|
+
: beforeBound.polarity === "floor" ||
|
|
111
|
+
beforeBound.polarity === "monotone_nondecreasing"
|
|
104
112
|
? afterBound.value < beforeBound.value
|
|
105
|
-
: afterBound.value !== beforeBound.value
|
|
113
|
+
: afterBound.value !== beforeBound.value;
|
|
106
114
|
}
|
|
107
115
|
if (beforeBound.polarity === "subset_of") {
|
|
108
116
|
const beforeValues = beforeBound.value;
|
|
@@ -125,11 +133,28 @@ export function assuranceAssertionUpdateIsLoosening(before, after) {
|
|
|
125
133
|
warn: 1,
|
|
126
134
|
block: 2,
|
|
127
135
|
};
|
|
128
|
-
const lifetimeLoosened = (before.lifetime ?? "hold") === "hold" &&
|
|
136
|
+
const lifetimeLoosened = (before.lifetime ?? "hold") === "hold" &&
|
|
137
|
+
(after.lifetime ?? "hold") === "retire";
|
|
129
138
|
return (boundUpdateIsLoosening(boundFor(before), boundFor(after)) ||
|
|
130
139
|
enforcementRank[after.enforcement] < enforcementRank[before.enforcement] ||
|
|
131
140
|
lifetimeLoosened);
|
|
132
141
|
}
|
|
142
|
+
/** Validate the field predicate contract of an item-backed measurement source. */
|
|
143
|
+
function validateItemsMeasurementSource(source) {
|
|
144
|
+
if (source.state !== undefined &&
|
|
145
|
+
!(source.state === "present" || source.state === "missing")) {
|
|
146
|
+
throw new AssuranceMutationRefusalError("items source state must be present or missing");
|
|
147
|
+
}
|
|
148
|
+
const hasEquals = Object.hasOwn(source, "equals");
|
|
149
|
+
const hasState = source.state !== undefined;
|
|
150
|
+
const predicateCount = Number(hasEquals) + Number(hasState);
|
|
151
|
+
if (source.field !== undefined && predicateCount !== 1) {
|
|
152
|
+
throw new AssuranceMutationRefusalError("items source with field requires exactly one of equals or state");
|
|
153
|
+
}
|
|
154
|
+
if (source.field === undefined && predicateCount !== 0) {
|
|
155
|
+
throw new AssuranceMutationRefusalError("items source equals/state requires field");
|
|
156
|
+
}
|
|
157
|
+
}
|
|
133
158
|
/** Validate and return one measurement declaration. */
|
|
134
159
|
export function validateMeasurementDefinition(definition) {
|
|
135
160
|
requireStableId(definition.id, "measurement.id");
|
|
@@ -137,14 +162,19 @@ export function validateMeasurementDefinition(definition) {
|
|
|
137
162
|
requireFiniteNonNegative(definition.max_cost, "measurement.max_cost");
|
|
138
163
|
}
|
|
139
164
|
const source = definition.source;
|
|
140
|
-
if (source.kind === "items"
|
|
141
|
-
|
|
165
|
+
if (source.kind === "items") {
|
|
166
|
+
validateItemsMeasurementSource(source);
|
|
142
167
|
}
|
|
143
|
-
if (source.kind === "dependency_kind" &&
|
|
144
|
-
|
|
168
|
+
if (source.kind === "dependency_kind" &&
|
|
169
|
+
(typeof source.dependency_kind !== "string" ||
|
|
170
|
+
source.dependency_kind.trim().length === 0)) {
|
|
171
|
+
throw new AssuranceMutationRefusalError("dependency_kind source requires dependency_kind");
|
|
145
172
|
}
|
|
146
|
-
if ((source.kind === "graph" ||
|
|
147
|
-
|
|
173
|
+
if ((source.kind === "graph" ||
|
|
174
|
+
source.kind === "validate" ||
|
|
175
|
+
source.kind === "health") &&
|
|
176
|
+
(typeof source.field !== "string" || source.field.trim().length === 0)) {
|
|
177
|
+
throw new AssuranceMutationRefusalError(`${source.kind} source requires field`);
|
|
148
178
|
}
|
|
149
179
|
if (source.kind === "provider") {
|
|
150
180
|
requireStableId(source.provider, "provider source provider");
|
|
@@ -156,25 +186,30 @@ export function validateMeasurementDefinition(definition) {
|
|
|
156
186
|
export function validateAssertionDefinition(definition) {
|
|
157
187
|
requireStableId(definition.id, "assertion.id");
|
|
158
188
|
requireStableId(definition.measurement_id, "assertion.measurement_id");
|
|
159
|
-
if (definition.owner_item_id
|
|
160
|
-
|
|
189
|
+
if (typeof definition.owner_item_id !== "string" ||
|
|
190
|
+
definition.owner_item_id.trim().length === 0) {
|
|
191
|
+
throw new AssuranceMutationRefusalError("assertion.owner_item_id is required");
|
|
161
192
|
}
|
|
162
193
|
const bound = boundFor(definition);
|
|
163
194
|
if (typeof bound.value === "number" && !Number.isFinite(bound.value)) {
|
|
164
|
-
throw new
|
|
195
|
+
throw new AssuranceMutationRefusalError(`assertion.${bound.polarity} must be a finite number`);
|
|
165
196
|
}
|
|
166
|
-
if (definition.lifetime === "retire" &&
|
|
167
|
-
|
|
197
|
+
if (definition.lifetime === "retire" &&
|
|
198
|
+
(typeof definition.retire_reason !== "string" ||
|
|
199
|
+
definition.retire_reason.trim().length === 0)) {
|
|
200
|
+
throw new AssuranceMutationRefusalError("retired assertion requires retire_reason");
|
|
168
201
|
}
|
|
169
202
|
if (definition.scope.kind === "filter") {
|
|
170
203
|
requireStableId(definition.scope.measurement_id, "assertion.scope.measurement_id");
|
|
171
204
|
}
|
|
172
205
|
const cases = definition.negative_control?.cases;
|
|
173
|
-
if (!Array.isArray(cases) ||
|
|
174
|
-
|
|
206
|
+
if (!Array.isArray(cases) ||
|
|
207
|
+
!cases.some((entry) => entry.expected === "pass") ||
|
|
208
|
+
!cases.some((entry) => entry.expected === "fail")) {
|
|
209
|
+
throw new AssuranceMutationRefusalError("assertion negative control requires pass and fail cases");
|
|
175
210
|
}
|
|
176
211
|
if (!proveNegativeControl(definition)) {
|
|
177
|
-
throw new
|
|
212
|
+
throw new AssuranceMutationRefusalError(`assertion ${definition.id} negative control does not prove its bound`);
|
|
178
213
|
}
|
|
179
214
|
return definition;
|
|
180
215
|
}
|
|
@@ -182,46 +217,50 @@ export function validateAssertionDefinition(definition) {
|
|
|
182
217
|
export function validateGateDefinition(definition) {
|
|
183
218
|
requireStableId(definition.id, "gate.id");
|
|
184
219
|
if (definition.assertion_ids.length === 0) {
|
|
185
|
-
throw new
|
|
220
|
+
throw new AssuranceMutationRefusalError("gate requires at least one assertion");
|
|
186
221
|
}
|
|
187
222
|
for (const id of definition.assertion_ids)
|
|
188
223
|
requireStableId(id, "gate.assertion_id");
|
|
189
224
|
if (definition.triggers.length === 0) {
|
|
190
|
-
throw new
|
|
225
|
+
throw new AssuranceMutationRefusalError("gate requires at least one trigger");
|
|
191
226
|
}
|
|
192
227
|
return definition;
|
|
193
228
|
}
|
|
194
229
|
/** Validate registry references, uniqueness, and declaration-local contracts. */
|
|
195
230
|
export function validateAssuranceDocument(document) {
|
|
196
231
|
if (document.version !== ASSURANCE_DOCUMENT_VERSION) {
|
|
197
|
-
throw new
|
|
232
|
+
throw new AssuranceMutationRefusalError(`unsupported assurance document version ${String(document.version)}`);
|
|
198
233
|
}
|
|
199
234
|
const ids = new Set();
|
|
200
235
|
for (const definition of document.measurements) {
|
|
201
236
|
validateMeasurementDefinition(definition);
|
|
202
|
-
if (ids.has(`measurement:${definition.id}`))
|
|
203
|
-
throw new
|
|
237
|
+
if (ids.has(`measurement:${definition.id}`)) {
|
|
238
|
+
throw new AssuranceMutationRefusalError(`duplicate measurement ${definition.id}`);
|
|
239
|
+
}
|
|
204
240
|
ids.add(`measurement:${definition.id}`);
|
|
205
241
|
}
|
|
206
242
|
for (const definition of document.assertions) {
|
|
207
243
|
validateAssertionDefinition(definition);
|
|
208
|
-
if (ids.has(`assertion:${definition.id}`))
|
|
209
|
-
throw new
|
|
244
|
+
if (ids.has(`assertion:${definition.id}`)) {
|
|
245
|
+
throw new AssuranceMutationRefusalError(`duplicate assertion ${definition.id}`);
|
|
246
|
+
}
|
|
210
247
|
ids.add(`assertion:${definition.id}`);
|
|
211
248
|
}
|
|
212
249
|
for (const definition of document.gates) {
|
|
213
250
|
validateGateDefinition(definition);
|
|
214
251
|
const missing = definition.assertion_ids.filter((id) => !document.assertions.some((assertionDefinition) => assertionDefinition.id === id));
|
|
215
|
-
if (missing.length > 0)
|
|
216
|
-
throw new
|
|
217
|
-
|
|
218
|
-
|
|
252
|
+
if (missing.length > 0) {
|
|
253
|
+
throw new AssuranceMutationRefusalError(`gate ${definition.id} references missing assertions: ${missing.join(", ")}`);
|
|
254
|
+
}
|
|
255
|
+
if (ids.has(`gate:${definition.id}`)) {
|
|
256
|
+
throw new AssuranceMutationRefusalError(`duplicate gate ${definition.id}`);
|
|
257
|
+
}
|
|
219
258
|
ids.add(`gate:${definition.id}`);
|
|
220
259
|
}
|
|
221
260
|
const measurementIds = new Set(document.measurements.map((entry) => entry.id));
|
|
222
261
|
for (const [measurementId, consumers] of collectMeasurementReferences(document)) {
|
|
223
262
|
if (!measurementIds.has(measurementId)) {
|
|
224
|
-
throw new
|
|
263
|
+
throw new AssuranceMutationRefusalError(`${consumers.join(", ")} references missing measurement ${measurementId}`);
|
|
225
264
|
}
|
|
226
265
|
}
|
|
227
266
|
return document;
|
|
@@ -263,15 +302,31 @@ function collectMeasurementReferences(document) {
|
|
|
263
302
|
}
|
|
264
303
|
/** Create an empty, valid assurance registry. */
|
|
265
304
|
export function createEmptyAssuranceDocument() {
|
|
266
|
-
return {
|
|
305
|
+
return {
|
|
306
|
+
version: ASSURANCE_DOCUMENT_VERSION,
|
|
307
|
+
measurements: [],
|
|
308
|
+
assertions: [],
|
|
309
|
+
gates: [],
|
|
310
|
+
};
|
|
267
311
|
}
|
|
268
312
|
function itemsSourceResult(source, context) {
|
|
269
|
-
const matches = context.items.filter((item) => (source.statuses === undefined ||
|
|
313
|
+
const matches = context.items.filter((item) => (source.statuses === undefined ||
|
|
314
|
+
source.statuses.includes(item.status)) &&
|
|
270
315
|
(source.types === undefined || source.types.includes(item.type)) &&
|
|
271
316
|
(source.tags === undefined ||
|
|
272
317
|
source.tags.every((tag) => item.tags?.includes(tag))) &&
|
|
273
318
|
(source.field === undefined ||
|
|
274
|
-
|
|
319
|
+
(() => {
|
|
320
|
+
const value = item[source.field];
|
|
321
|
+
if (source.state !== undefined) {
|
|
322
|
+
const missing = value === undefined ||
|
|
323
|
+
value === null ||
|
|
324
|
+
value === "" ||
|
|
325
|
+
(Array.isArray(value) && value.length === 0);
|
|
326
|
+
return source.state === "missing" ? missing : !missing;
|
|
327
|
+
}
|
|
328
|
+
return valuesEqual(value, source.equals);
|
|
329
|
+
})()));
|
|
275
330
|
return {
|
|
276
331
|
value: matches.length,
|
|
277
332
|
population_size: context.items.length,
|
|
@@ -281,9 +336,13 @@ function itemsSourceResult(source, context) {
|
|
|
281
336
|
}
|
|
282
337
|
function dependencyKindSourceResult(source, context) {
|
|
283
338
|
const contributors = [];
|
|
339
|
+
const requestedKind = resolveCanonicalRelationshipKind(source.dependency_kind) ??
|
|
340
|
+
source.dependency_kind.trim().toLowerCase();
|
|
284
341
|
for (const item of context.items) {
|
|
285
342
|
for (const dependency of item.dependencies ?? []) {
|
|
286
|
-
|
|
343
|
+
const dependencyKind = resolveCanonicalRelationshipKind(dependency.kind) ??
|
|
344
|
+
dependency.kind.trim().toLowerCase();
|
|
345
|
+
if (dependencyKind === requestedKind) {
|
|
287
346
|
contributors.push(`${item.id}->${dependency.id}`);
|
|
288
347
|
}
|
|
289
348
|
}
|
|
@@ -340,7 +399,13 @@ function sumCosts(costs, durationMs) {
|
|
|
340
399
|
history_entries: total.history_entries + cost.history_entries,
|
|
341
400
|
provider_calls: total.provider_calls + cost.provider_calls,
|
|
342
401
|
duration_ms: durationMs,
|
|
343
|
-
}), {
|
|
402
|
+
}), {
|
|
403
|
+
units: 0,
|
|
404
|
+
items_scanned: 0,
|
|
405
|
+
history_entries: 0,
|
|
406
|
+
provider_calls: 0,
|
|
407
|
+
duration_ms: durationMs,
|
|
408
|
+
});
|
|
344
409
|
}
|
|
345
410
|
function arithmeticValue(operator, values) {
|
|
346
411
|
if (values.length === 0)
|
|
@@ -373,7 +438,12 @@ async function mapWithConcurrency(values, limit, transform) {
|
|
|
373
438
|
}
|
|
374
439
|
async function evaluateExpression(expression, context, definitions, stack, state) {
|
|
375
440
|
if ("literal" in expression)
|
|
376
|
-
return {
|
|
441
|
+
return {
|
|
442
|
+
value: expression.literal,
|
|
443
|
+
costs: [],
|
|
444
|
+
population_size: 0,
|
|
445
|
+
contributors: [],
|
|
446
|
+
};
|
|
377
447
|
if ("measurement" in expression) {
|
|
378
448
|
const definition = definitions.find((entry) => entry.id === expression.measurement);
|
|
379
449
|
if (!definition)
|
|
@@ -383,7 +453,12 @@ async function evaluateExpression(expression, context, definitions, stack, state
|
|
|
383
453
|
throw new TypeError(`derived measurement ${definition.id} is not numeric`);
|
|
384
454
|
const costs = state.charged.has(definition.id) ? [] : [result.cost];
|
|
385
455
|
state.charged.add(definition.id);
|
|
386
|
-
return {
|
|
456
|
+
return {
|
|
457
|
+
value: result.value,
|
|
458
|
+
costs,
|
|
459
|
+
population_size: result.population_size,
|
|
460
|
+
contributors: result.contributors,
|
|
461
|
+
};
|
|
387
462
|
}
|
|
388
463
|
const operands = await mapWithConcurrency(expression.operands, EVALUATION_CONCURRENCY, (operand) => evaluateExpression(operand, context, definitions, stack, state));
|
|
389
464
|
return {
|
|
@@ -401,7 +476,12 @@ async function evaluateMeasurementInternal(definition, context, definitions, sta
|
|
|
401
476
|
let providerCalls = 0;
|
|
402
477
|
if (definition.source.kind === "derived") {
|
|
403
478
|
const derived = await evaluateExpression(definition.source.expression, context, definitions, [...stack, definition.id], state);
|
|
404
|
-
result = {
|
|
479
|
+
result = {
|
|
480
|
+
value: derived.value,
|
|
481
|
+
population_size: derived.population_size,
|
|
482
|
+
cost: 0,
|
|
483
|
+
contributors: derived.contributors,
|
|
484
|
+
};
|
|
405
485
|
nestedCosts = derived.costs;
|
|
406
486
|
}
|
|
407
487
|
else {
|
|
@@ -422,7 +502,11 @@ async function evaluateMeasurementInternal(definition, context, definitions, sta
|
|
|
422
502
|
}
|
|
423
503
|
const ownCost = {
|
|
424
504
|
units: result.cost,
|
|
425
|
-
items_scanned: definition.source.kind === "items" ||
|
|
505
|
+
items_scanned: definition.source.kind === "items" ||
|
|
506
|
+
definition.source.kind === "dependency_kind" ||
|
|
507
|
+
definition.source.kind === "links"
|
|
508
|
+
? context.items.length
|
|
509
|
+
: 0,
|
|
426
510
|
history_entries: definition.source.kind === "history" ? context.history.length : 0,
|
|
427
511
|
provider_calls: providerCalls,
|
|
428
512
|
duration_ms: Date.now() - startedAt,
|
|
@@ -431,7 +515,16 @@ async function evaluateMeasurementInternal(definition, context, definitions, sta
|
|
|
431
515
|
if (definition.max_cost !== undefined && cost.units > definition.max_cost) {
|
|
432
516
|
throw new TypeError(`measurement ${definition.id} exceeded cost ceiling ${definition.max_cost} with ${cost.units}`);
|
|
433
517
|
}
|
|
434
|
-
return {
|
|
518
|
+
return {
|
|
519
|
+
id: definition.id,
|
|
520
|
+
definition_fingerprint: `sha256:${createHash("sha256")
|
|
521
|
+
.update(stableStringify(definition))
|
|
522
|
+
.digest("hex")}`,
|
|
523
|
+
value: result.value,
|
|
524
|
+
population_size: result.population_size,
|
|
525
|
+
cost,
|
|
526
|
+
contributors: result.contributors ?? [],
|
|
527
|
+
};
|
|
435
528
|
}
|
|
436
529
|
function evaluateMeasurementCached(definition, context, definitions, stack, state) {
|
|
437
530
|
if (stack.includes(definition.id)) {
|
|
@@ -454,7 +547,8 @@ export async function evaluateMeasurement(definition, context, definitions = [de
|
|
|
454
547
|
function assertionDistance(observed, bound) {
|
|
455
548
|
if (typeof observed !== "number" || typeof bound.value !== "number")
|
|
456
549
|
return null;
|
|
457
|
-
if (bound.polarity === "ceiling" ||
|
|
550
|
+
if (bound.polarity === "ceiling" ||
|
|
551
|
+
bound.polarity === "monotone_nonincreasing")
|
|
458
552
|
return bound.value - observed;
|
|
459
553
|
if (bound.polarity === "floor" || bound.polarity === "monotone_nondecreasing")
|
|
460
554
|
return observed - bound.value;
|
|
@@ -468,12 +562,17 @@ export function evaluateAssuranceAssertion(definition, measurement, options = {}
|
|
|
468
562
|
return {
|
|
469
563
|
assertion_id: definition.id,
|
|
470
564
|
measurement_id: definition.measurement_id,
|
|
565
|
+
measurement_definition_fingerprint: measurement.definition_fingerprint,
|
|
471
566
|
scope: definition.scope,
|
|
472
567
|
population_size: measurement.population_size,
|
|
473
568
|
observed: measurement.value,
|
|
474
569
|
bound,
|
|
475
570
|
distance: assertionDistance(measurement.value, bound),
|
|
476
|
-
verdict: retired
|
|
571
|
+
verdict: retired
|
|
572
|
+
? "retired"
|
|
573
|
+
: compareBound(measurement.value, bound)
|
|
574
|
+
? "pass"
|
|
575
|
+
: "fail",
|
|
477
576
|
enforcement: definition.enforcement,
|
|
478
577
|
negative_control_proven: proveNegativeControl(definition),
|
|
479
578
|
cost: measurement.cost,
|
|
@@ -571,7 +670,11 @@ function validateForKind(kind, definition) {
|
|
|
571
670
|
/** Read all declarations of one kind with a stable row selector receipt. */
|
|
572
671
|
export async function listAssuranceDeclarations(pmRoot, kind) {
|
|
573
672
|
const items = structuredClone(collectionFor(await readDocument(pmRoot), kind));
|
|
574
|
-
return {
|
|
673
|
+
return {
|
|
674
|
+
items,
|
|
675
|
+
count: items.length,
|
|
676
|
+
row_contract: { row_keys: ["items"], jq_selector: ".items[]" },
|
|
677
|
+
};
|
|
575
678
|
}
|
|
576
679
|
/** Read one named declaration or fail loudly. */
|
|
577
680
|
export async function getAssuranceDeclaration(pmRoot, kind, id) {
|
|
@@ -583,7 +686,8 @@ export async function getAssuranceDeclaration(pmRoot, kind, id) {
|
|
|
583
686
|
/** Create or update one declaration through audited singleton persistence. */
|
|
584
687
|
export async function putAssuranceDeclaration(pmRoot, kind, definition, options = {}) {
|
|
585
688
|
validateForKind(kind, definition);
|
|
586
|
-
const
|
|
689
|
+
const settings = await readSettings(pmRoot);
|
|
690
|
+
const author = resolveAuthor(options.author, settings.author_default);
|
|
587
691
|
const mutation = await mutateWorkspaceJsonWithHistory({
|
|
588
692
|
pmRoot,
|
|
589
693
|
filePath: assurancePath(pmRoot),
|
|
@@ -593,16 +697,20 @@ export async function putAssuranceDeclaration(pmRoot, kind, definition, options
|
|
|
593
697
|
lockTtlSeconds: LOCK_TTL_SECONDS,
|
|
594
698
|
lockWaitMs: LOCK_WAIT_MS,
|
|
595
699
|
mutate: (beforeRaw) => {
|
|
596
|
-
const document = beforeRaw === null
|
|
700
|
+
const document = beforeRaw === null
|
|
701
|
+
? createEmptyAssuranceDocument()
|
|
702
|
+
: parseAssuranceDocument(beforeRaw);
|
|
597
703
|
const collection = collectionFor(document, kind);
|
|
598
704
|
const index = collection.findIndex((entry) => entry.id === definition.id);
|
|
599
705
|
const action = index < 0 ? "created" : "updated";
|
|
600
706
|
if (kind === "assertion" &&
|
|
601
707
|
index >= 0 &&
|
|
602
708
|
assuranceAssertionUpdateIsLoosening(collection[index], definition)) {
|
|
603
|
-
const decision = definition
|
|
604
|
-
|
|
605
|
-
|
|
709
|
+
const decision = definition
|
|
710
|
+
.authorization_decision;
|
|
711
|
+
if (!decision ||
|
|
712
|
+
!options.authorized_decision_ids?.includes(decision)) {
|
|
713
|
+
throw new AssuranceMutationRefusalError(`loosening assertion ${definition.id} requires a verified authorization_decision`);
|
|
606
714
|
}
|
|
607
715
|
}
|
|
608
716
|
if (index < 0)
|
|
@@ -620,7 +728,8 @@ export async function putAssuranceDeclaration(pmRoot, kind, definition, options
|
|
|
620
728
|
}
|
|
621
729
|
/** Remove an unreferenced declaration through audited singleton persistence. */
|
|
622
730
|
export async function removeAssuranceDeclaration(pmRoot, kind, id, options = {}) {
|
|
623
|
-
const
|
|
731
|
+
const settings = await readSettings(pmRoot);
|
|
732
|
+
const author = resolveAuthor(options.author, settings.author_default);
|
|
624
733
|
const mutation = await mutateWorkspaceJsonWithHistory({
|
|
625
734
|
pmRoot,
|
|
626
735
|
filePath: assurancePath(pmRoot),
|
|
@@ -630,20 +739,24 @@ export async function removeAssuranceDeclaration(pmRoot, kind, id, options = {})
|
|
|
630
739
|
lockTtlSeconds: LOCK_TTL_SECONDS,
|
|
631
740
|
lockWaitMs: LOCK_WAIT_MS,
|
|
632
741
|
mutate: (beforeRaw) => {
|
|
633
|
-
const document = beforeRaw === null
|
|
742
|
+
const document = beforeRaw === null
|
|
743
|
+
? createEmptyAssuranceDocument()
|
|
744
|
+
: parseAssuranceDocument(beforeRaw);
|
|
634
745
|
if (kind === "measurement") {
|
|
635
746
|
const consumers = collectMeasurementReferences(document).get(id);
|
|
636
747
|
if (consumers && consumers.length > 0) {
|
|
637
|
-
throw new
|
|
748
|
+
throw new AssuranceMutationRefusalError(`assurance measurement ${id} is referenced by ${consumers.join(", ")}`);
|
|
638
749
|
}
|
|
639
750
|
}
|
|
640
|
-
if (kind === "assertion" &&
|
|
641
|
-
|
|
751
|
+
if (kind === "assertion" &&
|
|
752
|
+
document.gates.some((entry) => entry.assertion_ids.includes(id))) {
|
|
753
|
+
throw new AssuranceMutationRefusalError(`assurance assertion ${id} is referenced by gate`);
|
|
642
754
|
}
|
|
643
755
|
const collection = collectionFor(document, kind);
|
|
644
756
|
const index = collection.findIndex((entry) => entry.id === id);
|
|
645
|
-
if (index < 0)
|
|
646
|
-
throw new
|
|
757
|
+
if (index < 0) {
|
|
758
|
+
throw new AssuranceMutationRefusalError(`assurance ${kind} ${id} not found`);
|
|
759
|
+
}
|
|
647
760
|
collection.splice(index, 1);
|
|
648
761
|
validateAssuranceDocument(document);
|
|
649
762
|
return {
|
|
@@ -658,12 +771,14 @@ export async function removeAssuranceDeclaration(pmRoot, kind, id, options = {})
|
|
|
658
771
|
export async function recordAssuranceVerdict(pmRoot, verdict, options = {}) {
|
|
659
772
|
if (verdict.dry_run)
|
|
660
773
|
throw new TypeError("dry-run assurance verdicts are not persisted");
|
|
774
|
+
const settings = await readSettings(pmRoot);
|
|
661
775
|
await appendWorkspaceAuditEvent({
|
|
662
776
|
pmRoot,
|
|
663
777
|
op: "assurance:gate:verdict",
|
|
664
|
-
author: resolveAuthor(options.author,
|
|
778
|
+
author: resolveAuthor(options.author, settings.author_default),
|
|
665
779
|
context: { assurance_verdict: verdict },
|
|
666
|
-
message: options.message ??
|
|
780
|
+
message: options.message ??
|
|
781
|
+
`Assurance gate ${verdict.gate_id} returned ${verdict.verdict}`,
|
|
667
782
|
lockTtlSeconds: LOCK_TTL_SECONDS,
|
|
668
783
|
lockWaitMs: LOCK_WAIT_MS,
|
|
669
784
|
});
|
|
@@ -686,4 +801,4 @@ export async function listAssuranceVerdicts(pmRoot, options = {}) {
|
|
|
686
801
|
.slice(0, limit);
|
|
687
802
|
}
|
|
688
803
|
//# sourceMappingURL=assurance.js.map
|
|
689
|
-
//# debugId=
|
|
804
|
+
//# debugId=ecc8eb89-b9e9-51fa-8573-2afb8ec801be
|
|
@@ -15,6 +15,8 @@ export interface HealthCheck {
|
|
|
15
15
|
name: "settings" | "directories" | "settings_values" | "telemetry" | "extensions" | "storage" | "locks" | "integrity" | "history_drift" | "vectorization";
|
|
16
16
|
/** Lifecycle state reported for status. */
|
|
17
17
|
status: HealthStatus;
|
|
18
|
+
/** Boolean companion to status for direct agent and assurance predicates. */
|
|
19
|
+
ok: boolean;
|
|
18
20
|
/** Value that configures or reports details for this contract. */
|
|
19
21
|
details: Record<string, unknown>;
|
|
20
22
|
}
|
|
@@ -113,7 +115,7 @@ interface ExtensionHealthTriageSummary {
|
|
|
113
115
|
top_warnings: string[];
|
|
114
116
|
remediation: string[];
|
|
115
117
|
}
|
|
116
|
-
/** Advisory warnings are surfaced for visibility but never flip overall health to not-ok. Telemetry is opt-out, non-critical observability: a queued/unreachable telemetry endpoint or corrupt local telemetry state is not a project-health failure and must not block agents that gate on `pm health` `ok`. History over-compaction-threshold warnings are likewise advisory maintenance hints — a deep stream is healthy, just a candidate for `pm history-compact`. */
|
|
118
|
+
/** Advisory warnings are surfaced for visibility but never flip overall health to not-ok. Telemetry is opt-out, non-critical observability: a queued/unreachable telemetry endpoint or corrupt local telemetry state is not a project-health failure and must not block agents that gate on `pm health` `ok`. History over-compaction-threshold warnings are likewise advisory maintenance hints — a deep stream is healthy, just a candidate for `pm history-compact`. Invalid legacy provenance values remain immutable diagnostic evidence: write-time resolvers reject new invalid values, while health reports the bounded aggregate without requiring truthful history to be rewritten. */
|
|
117
119
|
declare function isAdvisoryHealthWarning(warning: string, requireMergeDrivers?: boolean): boolean;
|
|
118
120
|
declare function warningCode(value: string): string;
|
|
119
121
|
declare function normalizeExtensionNameForMatch(value: string): string;
|