@unbrained/pm-cli 2026.8.12 → 2026.8.14
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 +162 -111
- package/dist/cli/error-guidance.js +4 -8
- package/dist/cli/main.js +40 -3
- package/dist/cli/register-assurance.js +28 -17
- package/dist/cli-bundle/bundle-manifest.json +141 -173
- package/dist/cli-bundle/chunks/chunk-BBOFSVAK.js +197 -0
- package/dist/cli-bundle/chunks/chunk-BFALV3YP.js +5 -0
- package/dist/cli-bundle/chunks/{chunk-WLAHPSOX.js → chunk-FIW4P64F.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-BM5YJ2UW.js → chunk-G5JUB66K.js} +52 -52
- package/dist/cli-bundle/chunks/chunk-SBRKH6OR.js +2 -0
- package/dist/cli-bundle/chunks/chunk-TMJ7URWS.js +13 -0
- package/dist/cli-bundle/chunks/chunk-TXMEUUUY.js +8 -0
- package/dist/cli-bundle/chunks/{chunk-KOQMGCTA.js → chunk-XAVMOK6B.js} +50 -50
- package/dist/cli-bundle/chunks/chunk-YAMTJL6Y.js +34 -0
- package/dist/cli-bundle/chunks/chunk-YSVSMJ2B.js +2 -0
- package/dist/cli-bundle/chunks/{register-list-query-XPCJRUUD.js → register-list-query-T3EHHNNV.js} +2 -2
- package/dist/cli-bundle/chunks/{register-mutation-4RJCBKRS.js → register-mutation-E27WCLSZ.js} +2 -2
- package/dist/cli-bundle/chunks/register-operations-4R6I7CPY.js +2 -0
- package/dist/cli-bundle/chunks/{register-setup-LLHAKP2V.js → register-setup-3YXZ444E.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-JAFRIWVF.js → chunk-4DQ2YERC.js} +4 -4
- package/dist/cli-bundle/focused-chunks/chunk-F43TT2QQ.js +8 -0
- package/dist/cli-bundle/focused-chunks/chunk-K7RWWOAG.js +4 -0
- package/dist/cli-bundle/focused-chunks/chunk-KFSZ3BP6.js +16 -0
- package/dist/cli-bundle/focused-chunks/{chunk-KHM2CS2C.js → chunk-KSEDUWVZ.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-KYUQGTSB.js +2 -0
- package/dist/cli-bundle/focused-chunks/{chunk-ZMGWAYH7.js → chunk-LZ4MV3RH.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-PGXJQLCT.js +155 -0
- package/dist/cli-bundle/focused-chunks/{chunk-RWPAWUOT.js → chunk-PPFGQDSI.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-Q4YY3XNF.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-QK753U3U.js +46 -0
- package/dist/cli-bundle/focused-chunks/chunk-QUMB2J27.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-SM7MTG27.js +2 -0
- package/dist/cli-bundle/focused-chunks/{chunk-GQW4BPUT.js → chunk-WCVE2NIG.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-XDNAW5OI.js +18 -0
- package/dist/cli-bundle/focused-chunks/{chunk-YIAXMC3T.js → chunk-YOOI3UZX.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-ZM6TZVHC.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 +45 -41
- 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 +109 -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/checkpoint/checkpoint-gc.js +7 -12
- package/dist/core/extensions/activation-summary-markdown.js +5 -4
- package/dist/core/extensions/activation-summary.d.ts +2 -0
- package/dist/core/extensions/activation-summary.js +6 -2
- package/dist/core/extensions/capability-usage.d.ts +1 -1
- package/dist/core/extensions/capability-usage.js +4 -3
- package/dist/core/extensions/contribution-inventory.d.ts +1 -0
- package/dist/core/extensions/contribution-inventory.js +6 -2
- package/dist/core/extensions/extension-registries.js +3 -2
- package/dist/core/extensions/extension-types.d.ts +83 -1
- package/dist/core/extensions/extension-types.js +3 -2
- package/dist/core/extensions/loader.d.ts +2 -8
- package/dist/core/extensions/loader.js +53 -144
- package/dist/core/extensions/registration-values.d.ts +43 -0
- package/dist/core/extensions/registration-values.js +210 -0
- package/dist/core/extensions/runtime-registrations.d.ts +3 -1
- package/dist/core/extensions/runtime-registrations.js +12 -2
- package/dist/core/fs/fs-utils.d.ts +2 -0
- package/dist/core/fs/fs-utils.js +7 -3
- package/dist/core/history/drift-cache.js +4 -3
- package/dist/core/history/drift-scan.d.ts +6 -0
- package/dist/core/history/drift-scan.js +76 -58
- package/dist/core/history/workspace-history.d.ts +59 -0
- package/dist/core/history/workspace-history.js +197 -10
- package/dist/core/lock/lock-gc.js +8 -13
- package/dist/core/lock/lock.js +5 -4
- package/dist/core/output/output.js +12 -4
- package/dist/core/search/vector-stores.js +6 -11
- package/dist/core/store/item-metadata-cache.js +4 -4
- package/dist/core/telemetry/runtime.d.ts +6 -0
- package/dist/core/telemetry/runtime.js +101 -30
- package/dist/sdk/agent/command-recovery.d.ts +2 -0
- package/dist/sdk/agent/command-recovery.js +10 -2
- package/dist/sdk/annotations.js +4 -4
- package/dist/sdk/cli-contracts/completeness.js +9 -3
- package/dist/sdk/cli-contracts/enum-contracts.d.ts +1 -1
- package/dist/sdk/cli-contracts/flag-contracts.js +6 -3
- package/dist/sdk/cli-contracts/runtime-contracts.d.ts +6 -1
- package/dist/sdk/cli-contracts/runtime-contracts.js +155 -60
- package/dist/sdk/cli-contracts/schemas/assurance-definition.d.ts +32 -0
- package/dist/sdk/cli-contracts/schemas/assurance-definition.js +25 -2
- package/dist/sdk/cli-contracts/tool-parameter-tables.js +35 -5
- package/dist/sdk/cli-contracts/tool-schema.js +17 -2
- package/dist/sdk/cli-program.js +3 -3
- package/dist/sdk/comments.js +4 -3
- package/dist/sdk/compile-cache.js +4 -3
- package/dist/sdk/compose.d.ts +3 -1
- package/dist/sdk/compose.js +16 -2
- package/dist/sdk/context-relevance.js +12 -4
- package/dist/sdk/context-signal-store.js +4 -3
- package/dist/sdk/extension/install-runtime.js +4 -4
- package/dist/sdk/extension/managed-state.js +4 -3
- package/dist/sdk/extension-contracts.d.ts +1 -1
- package/dist/sdk/flag-invocation-contracts.js +5 -6
- package/dist/sdk/generated/generated-error-code-catalog-part-1.js +14 -2
- package/dist/sdk/governance/assurance-action.d.ts +27 -3
- package/dist/sdk/governance/assurance-action.js +112 -28
- package/dist/sdk/governance/assurance-mutation-error.d.ts +7 -0
- package/dist/sdk/governance/assurance-mutation-error.js +25 -2
- package/dist/sdk/governance/assurance-presets.d.ts +54 -0
- package/dist/sdk/governance/assurance-presets.js +136 -0
- package/dist/sdk/governance/assurance-runtime.d.ts +4 -0
- package/dist/sdk/governance/assurance-runtime.js +109 -8
- package/dist/sdk/governance/assurance.d.ts +39 -0
- package/dist/sdk/governance/assurance.js +145 -6
- package/dist/sdk/governance/gc.js +5 -11
- package/dist/sdk/governance/health.js +18 -6
- package/dist/sdk/governance/validate-history-drift.d.ts +7 -0
- package/dist/sdk/governance/validate-history-drift.js +68 -0
- package/dist/sdk/governance/validate.js +6 -48
- package/dist/sdk/graph/assembly.d.ts +23 -0
- package/dist/sdk/graph/assembly.js +54 -2
- package/dist/sdk/graph/durable-cache.js +91 -3
- package/dist/sdk/graph/governance-contracts.d.ts +12 -0
- package/dist/sdk/graph/governance-contracts.js +29 -0
- package/dist/sdk/graph/governance.d.ts +51 -3
- package/dist/sdk/graph/governance.js +216 -35
- package/dist/sdk/graph/index.d.ts +2 -2
- package/dist/sdk/graph/index.js +4 -4
- package/dist/sdk/graph/mutation-advisory.js +15 -7
- package/dist/sdk/graph/remediation.js +12 -2
- package/dist/sdk/guide.js +4 -4
- package/dist/sdk/history-read.d.ts +8 -0
- package/dist/sdk/history-read.js +2 -2
- package/dist/sdk/index.d.ts +3 -2
- package/dist/sdk/index.js +5 -4
- package/dist/sdk/linked-artifacts.d.ts +2 -0
- package/dist/sdk/linked-artifacts.js +29 -4
- package/dist/sdk/merge/install.js +5 -4
- package/dist/sdk/query/activity.js +4 -3
- package/dist/sdk/query/history.js +20 -3
- package/dist/sdk/read-output-budget.d.ts +2 -0
- package/dist/sdk/read-output-budget.js +12 -5
- package/dist/sdk/read-output-contracts.d.ts +61 -5
- package/dist/sdk/read-output-contracts.js +210 -28
- package/dist/sdk/read-output-rows.d.ts +11 -0
- package/dist/sdk/read-output-rows.js +47 -2
- package/dist/sdk/relationship-history.js +5 -5
- package/dist/sdk/runtime-primitives.d.ts +1 -1
- package/dist/sdk/runtime-primitives.js +3 -3
- package/dist/sdk/runtime.js +5 -3
- package/dist/sdk/schema-migration.js +4 -4
- package/dist/sdk/test/execution.js +4 -4
- package/dist/sdk/workspace-memory.js +4 -4
- package/dist/sdk/workspace-snapshot.js +8 -14
- package/dist/sdk/workspace-transaction-gc.js +4 -9
- package/dist/sdk/workspace-transaction.js +4 -4
- package/dist/sdk/workspace.js +4 -3
- package/docs/ASSURANCE.md +163 -3
- package/docs/COMMANDS.md +31 -2
- package/docs/CONFIGURATION.md +2 -0
- package/docs/EXTENSIONS.md +11 -12
- package/docs/READ_OUTPUT_CONTRACTS.md +44 -8
- package/docs/RELATIONSHIP_GRAPH.md +34 -4
- package/docs/SDK.md +29 -8
- package/docs/SDK_CONTEXT_INTEGRITY.md +32 -1
- package/docs/TESTING.md +47 -16
- package/docs/performance/sdk-entrypoint-import-costs.md +20 -13
- package/marketplace.json +2 -2
- package/package.json +6 -6
- package/packages/pm-beads/extensions/beads/index.ts +1 -0
- package/packages/pm-beads/package.json +1 -1
- package/packages/pm-calendar/extensions/calendar/index.ts +1 -0
- package/packages/pm-calendar/package.json +1 -1
- package/packages/pm-command-kit/package.json +1 -1
- package/packages/pm-digital-twin/extensions/digital-twin/index.ts +7 -0
- package/packages/pm-digital-twin/package.json +1 -1
- package/packages/pm-governance-audit/extensions/governance-audit/index.ts +4 -0
- package/packages/pm-governance-audit/package.json +1 -1
- package/packages/pm-guide-shell/extensions/guide-shell/index.ts +5 -0
- 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/extensions/linked-test-adapters/index.ts +6 -0
- package/packages/pm-linked-test-adapters/package.json +1 -1
- package/packages/pm-search-advanced/extensions/search-advanced/index.ts +2 -0
- package/packages/pm-search-advanced/package.json +1 -1
- package/packages/pm-templates/extensions/templates/index.ts +4 -0
- package/packages/pm-templates/package.json +1 -1
- package/packages/pm-todos/extensions/todos/index.ts +2 -0
- package/packages/pm-todos/package.json +1 -1
- package/packages/pm-vcs/extensions/vcs/index.ts +7 -0
- 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/scripts/bundle-cli.mjs +17 -4
- package/sdk/public-surface.json +390 -60
- package/dist/cli-bundle/chunks/chunk-3PS7X6GS.js +0 -8
- package/dist/cli-bundle/chunks/chunk-7KL6KMWF.js +0 -13
- package/dist/cli-bundle/chunks/chunk-D55A5AEN.js +0 -33
- package/dist/cli-bundle/chunks/chunk-KIKDWECH.js +0 -196
- package/dist/cli-bundle/chunks/chunk-R76342NV.js +0 -2
- package/dist/cli-bundle/chunks/chunk-RBTK5EZV.js +0 -5
- package/dist/cli-bundle/chunks/chunk-VLUQOIDT.js +0 -2
- package/dist/cli-bundle/chunks/register-operations-2OGISOVR.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-2DALMVF7.js +0 -5
- package/dist/cli-bundle/focused-chunks/chunk-5OQIMELN.js +0 -155
- package/dist/cli-bundle/focused-chunks/chunk-5TAGTDRE.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-ECADZMVC.js +0 -18
- package/dist/cli-bundle/focused-chunks/chunk-EJGXRA6B.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-FFGSZCPK.js +0 -16
- package/dist/cli-bundle/focused-chunks/chunk-ISKOBJCM.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-KPSXVBDV.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-KTLOKKCQ.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-LGYK3NUF.js +0 -8
- package/dist/cli-bundle/focused-chunks/chunk-NBLUKITQ.js +0 -4
- package/dist/cli-bundle/focused-chunks/chunk-NYLOUPOB.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-Q4T463SD.js +0 -31
- package/dist/cli-bundle/focused-chunks/chunk-QVZVY53N.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-UTEWXDEQ.js +0 -17
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module sdk/governance/assurance-presets
|
|
3
|
+
*
|
|
4
|
+
* Supplies readable assurance starter bundles and observation-derived proposals
|
|
5
|
+
* without introducing privileged evaluation semantics.
|
|
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]="05c29627-6763-5aee-8579-71e563a98780")}catch(e){}}();
|
|
9
|
+
import { evaluateMeasurement, putAssuranceDeclaration, putAssuranceDeclarationBundle, } from "./assurance.js";
|
|
10
|
+
/** Built-in project shapes that can acquire a quality contract in one command. */
|
|
11
|
+
export const ASSURANCE_PRESET_IDS = [
|
|
12
|
+
"software-delivery",
|
|
13
|
+
"research",
|
|
14
|
+
"agent-evaluation",
|
|
15
|
+
"operations",
|
|
16
|
+
];
|
|
17
|
+
function assertion(id, measurementId, ownerItemId, ceiling, enforcement) {
|
|
18
|
+
return {
|
|
19
|
+
id,
|
|
20
|
+
measurement_id: measurementId,
|
|
21
|
+
owner_item_id: ownerItemId,
|
|
22
|
+
scope: { kind: "active" },
|
|
23
|
+
ceiling,
|
|
24
|
+
lifetime: "hold",
|
|
25
|
+
enforcement,
|
|
26
|
+
negative_control: {
|
|
27
|
+
cases: [
|
|
28
|
+
{ observed: ceiling, expected: "pass" },
|
|
29
|
+
{ observed: ceiling + 1, expected: "fail" },
|
|
30
|
+
],
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function evidencePreset(id, title, description, ownerItemId, links) {
|
|
35
|
+
const prefix = `preset-${id}`;
|
|
36
|
+
const measurements = links.map((link) => ({
|
|
37
|
+
id: `${prefix}-missing-${link}`,
|
|
38
|
+
description: `Active items missing ${link} evidence.`,
|
|
39
|
+
source: { kind: "links", link, state: "missing" },
|
|
40
|
+
}));
|
|
41
|
+
const assertions = measurements.map((measurement) => assertion(`${measurement.id}-zero`, measurement.id, ownerItemId, 0, "warn"));
|
|
42
|
+
return {
|
|
43
|
+
id,
|
|
44
|
+
title,
|
|
45
|
+
description,
|
|
46
|
+
declarations: {
|
|
47
|
+
measurements,
|
|
48
|
+
assertions,
|
|
49
|
+
gates: [
|
|
50
|
+
{
|
|
51
|
+
id: `${prefix}-readiness`,
|
|
52
|
+
description,
|
|
53
|
+
assertion_ids: assertions.map((entry) => entry.id),
|
|
54
|
+
triggers: ["ci", "scheduled"],
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/** Materialize one built-in preset as ordinary declarations owned by a pm item. */
|
|
61
|
+
export function createAssurancePreset(id, ownerItemId) {
|
|
62
|
+
if (id === "software-delivery") {
|
|
63
|
+
return evidencePreset(id, "Software delivery", "Warn when active delivery work lacks executable tests or documentation.", ownerItemId, ["tests", "docs"]);
|
|
64
|
+
}
|
|
65
|
+
if (id === "research") {
|
|
66
|
+
return evidencePreset(id, "Research", "Warn when active research work lacks reproducibility files or documentation.", ownerItemId, ["files", "docs"]);
|
|
67
|
+
}
|
|
68
|
+
if (id === "agent-evaluation") {
|
|
69
|
+
return evidencePreset(id, "Agent evaluation", "Warn when active evaluation work lacks executable scenarios or recorded artifacts.", ownerItemId, ["tests", "files"]);
|
|
70
|
+
}
|
|
71
|
+
return evidencePreset(id, "Operations", "Warn when active operational work lacks runbook documentation or repair evidence.", ownerItemId, ["docs", "files"]);
|
|
72
|
+
}
|
|
73
|
+
/** Apply one preset atomically; a second identical application is idempotent. */
|
|
74
|
+
export function applyAssurancePreset(pmRoot, id, ownerItemId, options = {}) {
|
|
75
|
+
return putAssuranceDeclarationBundle(pmRoot, createAssurancePreset(id, ownerItemId).declarations, options);
|
|
76
|
+
}
|
|
77
|
+
/** Propose active-scope evidence ceilings from the record without persisting them. */
|
|
78
|
+
export async function deriveAssuranceProposals(context, ownerItemId) {
|
|
79
|
+
const terminalStatuses = new Set(context.terminal_statuses ?? ["closed", "canceled"]);
|
|
80
|
+
const activeContext = {
|
|
81
|
+
...context,
|
|
82
|
+
items: context.items.filter((item) => !terminalStatuses.has(item.status)),
|
|
83
|
+
};
|
|
84
|
+
const proposals = await Promise.all(["files", "tests", "docs"].map(async (link) => {
|
|
85
|
+
const measurement = {
|
|
86
|
+
id: `derived-active-missing-${link}`,
|
|
87
|
+
description: `Observed active items missing ${link} evidence.`,
|
|
88
|
+
source: { kind: "links", link, state: "missing" },
|
|
89
|
+
};
|
|
90
|
+
const measured = await evaluateMeasurement(measurement, activeContext);
|
|
91
|
+
const observed = measured.value;
|
|
92
|
+
const derivedAssertion = assertion(`${measurement.id}-ceiling`, measurement.id, ownerItemId, observed, "observe");
|
|
93
|
+
const id = `active-missing-${link}`;
|
|
94
|
+
return {
|
|
95
|
+
id,
|
|
96
|
+
population_size: activeContext.items.length,
|
|
97
|
+
scope: { kind: "active" },
|
|
98
|
+
observed,
|
|
99
|
+
declarations: {
|
|
100
|
+
measurements: [measurement],
|
|
101
|
+
assertions: [derivedAssertion],
|
|
102
|
+
gates: [
|
|
103
|
+
{
|
|
104
|
+
id: `derived-${id}`,
|
|
105
|
+
description: `Observe the accepted ${link} evidence baseline.`,
|
|
106
|
+
assertion_ids: [derivedAssertion.id],
|
|
107
|
+
triggers: ["ci", "scheduled"],
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}));
|
|
113
|
+
return proposals;
|
|
114
|
+
}
|
|
115
|
+
/** Accept all derived proposals in one audited registry transaction. */
|
|
116
|
+
export function acceptAssuranceProposals(pmRoot, proposals, options = {}) {
|
|
117
|
+
return putAssuranceDeclarationBundle(pmRoot, {
|
|
118
|
+
measurements: proposals.flatMap((proposal) => proposal.declarations.measurements),
|
|
119
|
+
assertions: proposals.flatMap((proposal) => proposal.declarations.assertions),
|
|
120
|
+
gates: proposals.flatMap((proposal) => proposal.declarations.gates),
|
|
121
|
+
}, options);
|
|
122
|
+
}
|
|
123
|
+
/** Promote one assertion by exactly one enforcement level and audit the transition. */
|
|
124
|
+
export async function promoteAssuranceAssertion(pmRoot, definition, enforcement, options = {}) {
|
|
125
|
+
const expected = definition.enforcement === "observe" ? "warn" : "block";
|
|
126
|
+
if (definition.enforcement === "block" || enforcement !== expected) {
|
|
127
|
+
throw new TypeError(`assurance assertion ${definition.id} promotion must be ${definition.enforcement} -> ${expected}`);
|
|
128
|
+
}
|
|
129
|
+
return putAssuranceDeclaration(pmRoot, "assertion", { ...definition, enforcement }, {
|
|
130
|
+
...options,
|
|
131
|
+
message: options.message ??
|
|
132
|
+
`Promote assurance assertion ${definition.id} from ${definition.enforcement} to ${enforcement}`,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=assurance-presets.js.map
|
|
136
|
+
//# debugId=05c29627-6763-5aee-8579-71e563a98780
|
|
@@ -11,6 +11,10 @@ export interface CreateAssuranceWorkspaceContextOptions {
|
|
|
11
11
|
resolve_tree?: boolean;
|
|
12
12
|
/** Provider resolvers keyed by stable provider id. */
|
|
13
13
|
providers?: Readonly<Record<string, AssuranceProviderResolver>>;
|
|
14
|
+
/** Capabilities for host-provided resolvers that are not extension registrations. */
|
|
15
|
+
provider_capabilities?: AssuranceEvaluationContext["provider_capabilities"];
|
|
16
|
+
/** Gate trigger supplied to extension provider resolvers. */
|
|
17
|
+
trigger?: string;
|
|
14
18
|
}
|
|
15
19
|
/**
|
|
16
20
|
* Build an assurance context from authoritative workspace data and public SDK
|
|
@@ -5,15 +5,87 @@
|
|
|
5
5
|
* and verdict semantics in the host-neutral assurance module.
|
|
6
6
|
*/
|
|
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]="
|
|
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]="7c99dfd8-2fa4-56c4-8191-229701eab251")}catch(e){}}();
|
|
9
9
|
import { execFile } from "node:child_process";
|
|
10
10
|
import { promisify } from "node:util";
|
|
11
11
|
import { runGraph } from "../graph/run.js";
|
|
12
12
|
import { runHealth } from "./health.js";
|
|
13
13
|
import { runValidate } from "./validate.js";
|
|
14
|
+
import { getActiveExtensionRegistrations, resolvePortableWorkspaceContext, } from "../../core/extensions/index.js";
|
|
15
|
+
import { resolveRegisteredAssuranceMeasurementProvider } from "../../core/extensions/runtime-registrations.js";
|
|
16
|
+
import { stableStringify } from "../../core/shared/serialization.js";
|
|
14
17
|
import { getHistoryPath, listAllItemMetadata, readHistoryEntries, readSettings, resolveItemTypeRegistry, resolveRuntimeStatusRegistry, } from "../runtime-primitives.js";
|
|
15
18
|
const execFileAsync = promisify(execFile);
|
|
16
19
|
const HISTORY_READ_CONCURRENCY = 16;
|
|
20
|
+
const DEFAULT_PROVIDER_TIMEOUT_MS = 30_000;
|
|
21
|
+
function assertProviderResult(provider, key, result) {
|
|
22
|
+
const keyDefinition = provider.keys[key];
|
|
23
|
+
const valueMatches = keyDefinition.value_type === "number"
|
|
24
|
+
? typeof result.value === "number" && Number.isFinite(result.value)
|
|
25
|
+
: Array.isArray(result.value) &&
|
|
26
|
+
result.value.every((entry) => typeof entry === "string");
|
|
27
|
+
if (!valueMatches) {
|
|
28
|
+
throw new TypeError(`assurance provider ${provider.id} key ${key} returned the wrong value type`);
|
|
29
|
+
}
|
|
30
|
+
if (!Number.isInteger(result.population_size) ||
|
|
31
|
+
result.population_size < 0 ||
|
|
32
|
+
!Number.isFinite(result.cost) ||
|
|
33
|
+
result.cost < 0) {
|
|
34
|
+
throw new TypeError(`assurance provider ${provider.id} returned an invalid population or cost`);
|
|
35
|
+
}
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
function validateProviderParameters(provider, source) {
|
|
39
|
+
const keyDefinition = provider.keys[source.key];
|
|
40
|
+
if (!keyDefinition) {
|
|
41
|
+
throw new TypeError(`assurance provider ${provider.id} does not declare key ${source.key}`);
|
|
42
|
+
}
|
|
43
|
+
const parameters = source.parameters ?? {};
|
|
44
|
+
const schema = keyDefinition.parameters ?? {};
|
|
45
|
+
for (const required of Object.entries(schema)
|
|
46
|
+
.filter(([, definition]) => definition.required === true)
|
|
47
|
+
.map(([name]) => name)) {
|
|
48
|
+
if (!Object.hasOwn(parameters, required)) {
|
|
49
|
+
throw new TypeError(`assurance provider ${provider.id} key ${source.key} requires parameter ${required}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
for (const [name, value] of Object.entries(parameters)) {
|
|
53
|
+
const definition = schema[name];
|
|
54
|
+
if (!definition) {
|
|
55
|
+
throw new TypeError(`assurance provider ${provider.id} key ${source.key} does not declare parameter ${name}`);
|
|
56
|
+
}
|
|
57
|
+
if (value === null || typeof value !== definition.type) {
|
|
58
|
+
throw new TypeError(`assurance provider ${provider.id} parameter ${name} must be ${definition.type}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return { ...parameters };
|
|
62
|
+
}
|
|
63
|
+
async function runRegisteredProvider(provider, source, pmRoot, trigger) {
|
|
64
|
+
const parameters = validateProviderParameters(provider, source);
|
|
65
|
+
const providerPromise = Promise.resolve().then(() => provider.resolve({
|
|
66
|
+
provider: provider.id,
|
|
67
|
+
key: source.key,
|
|
68
|
+
parameters,
|
|
69
|
+
trigger,
|
|
70
|
+
pm_root: pmRoot,
|
|
71
|
+
...resolvePortableWorkspaceContext(pmRoot),
|
|
72
|
+
}));
|
|
73
|
+
providerPromise.catch(() => { });
|
|
74
|
+
let timeoutHandle;
|
|
75
|
+
try {
|
|
76
|
+
const result = await Promise.race([
|
|
77
|
+
providerPromise,
|
|
78
|
+
new Promise((_, reject) => {
|
|
79
|
+
timeoutHandle = setTimeout(() => reject(new TypeError(`assurance provider ${provider.id} timed out`)), provider.timeout_ms ?? DEFAULT_PROVIDER_TIMEOUT_MS);
|
|
80
|
+
timeoutHandle.unref?.();
|
|
81
|
+
}),
|
|
82
|
+
]);
|
|
83
|
+
return assertProviderResult(provider, source.key, result);
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
clearTimeout(timeoutHandle);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
17
89
|
function valueAtPath(input, field) {
|
|
18
90
|
let current = input;
|
|
19
91
|
for (const segment of field.split(".")) {
|
|
@@ -96,6 +168,17 @@ export async function createAssuranceWorkspaceContext(pmRoot, options = {}) {
|
|
|
96
168
|
type: item.type,
|
|
97
169
|
}));
|
|
98
170
|
const global = { path: pmRoot };
|
|
171
|
+
const activeRegistrations = getActiveExtensionRegistrations();
|
|
172
|
+
const registeredProviderCapabilities = Object.fromEntries((activeRegistrations?.assurance_providers ?? []).map((registration) => [
|
|
173
|
+
registration.definition.id,
|
|
174
|
+
{
|
|
175
|
+
cost_class: registration.runtime_definition.cost_class,
|
|
176
|
+
network: registration.runtime_definition.network,
|
|
177
|
+
},
|
|
178
|
+
]));
|
|
179
|
+
const graphRuns = new Map();
|
|
180
|
+
let validateRun;
|
|
181
|
+
let healthRun;
|
|
99
182
|
return {
|
|
100
183
|
tree_id: options.resolve_tree === false
|
|
101
184
|
? (options.tree_id ?? "working-copy")
|
|
@@ -105,9 +188,22 @@ export async function createAssuranceWorkspaceContext(pmRoot, options = {}) {
|
|
|
105
188
|
? []
|
|
106
189
|
: await readWorkspaceHistory(pmRoot, items),
|
|
107
190
|
terminal_statuses: [...statusRegistry.terminal_statuses],
|
|
191
|
+
provider_capabilities: {
|
|
192
|
+
...registeredProviderCapabilities,
|
|
193
|
+
...options.provider_capabilities,
|
|
194
|
+
},
|
|
108
195
|
external: async (source) => {
|
|
109
196
|
if (source.kind === "graph") {
|
|
110
|
-
const
|
|
197
|
+
const key = stableStringify({
|
|
198
|
+
operation: source.operation,
|
|
199
|
+
parameters: source.parameters ?? {},
|
|
200
|
+
});
|
|
201
|
+
let pending = graphRuns.get(key);
|
|
202
|
+
if (pending === undefined) {
|
|
203
|
+
pending = runGraph(source.operation, undefined, undefined, { ...source.parameters, full: true }, global);
|
|
204
|
+
graphRuns.set(key, pending);
|
|
205
|
+
}
|
|
206
|
+
const result = await pending;
|
|
111
207
|
return {
|
|
112
208
|
value: valueAtPath(result, source.field),
|
|
113
209
|
population_size: items.length,
|
|
@@ -115,20 +211,25 @@ export async function createAssuranceWorkspaceContext(pmRoot, options = {}) {
|
|
|
115
211
|
};
|
|
116
212
|
}
|
|
117
213
|
if (source.kind === "validate") {
|
|
118
|
-
|
|
214
|
+
validateRun ??= runValidate({ counts: true }, global);
|
|
215
|
+
const result = await validateRun;
|
|
119
216
|
return checkValue(result, source.check, source.field);
|
|
120
217
|
}
|
|
121
218
|
if (source.kind === "health") {
|
|
122
|
-
|
|
219
|
+
healthRun ??= runHealth(global, { checkOnly: true, full: true });
|
|
220
|
+
const result = await healthRun;
|
|
123
221
|
return checkValue(result, source.check, source.field);
|
|
124
222
|
}
|
|
125
|
-
const
|
|
126
|
-
if (
|
|
223
|
+
const hostProvider = options.providers?.[source.provider];
|
|
224
|
+
if (hostProvider)
|
|
225
|
+
return hostProvider(source);
|
|
226
|
+
const registeredProvider = resolveRegisteredAssuranceMeasurementProvider(activeRegistrations, source.provider);
|
|
227
|
+
if (!registeredProvider) {
|
|
127
228
|
throw new TypeError(`assurance provider ${source.provider} is not registered`);
|
|
128
229
|
}
|
|
129
|
-
return
|
|
230
|
+
return runRegisteredProvider(registeredProvider.runtime_definition, source, pmRoot, options.trigger ?? "sdk");
|
|
130
231
|
},
|
|
131
232
|
};
|
|
132
233
|
}
|
|
133
234
|
//# sourceMappingURL=assurance-runtime.js.map
|
|
134
|
-
//# debugId=
|
|
235
|
+
//# debugId=7c99dfd8-2fa4-56c4-8191-229701eab251
|
|
@@ -6,6 +6,8 @@ export declare const ASSURANCE_GATE_TRIGGERS: readonly ["pre-commit", "pre-push"
|
|
|
6
6
|
export type AssuranceGateTrigger = (typeof ASSURANCE_GATE_TRIGGERS)[number];
|
|
7
7
|
/** Assertion enforcement strength. */
|
|
8
8
|
export type AssuranceEnforcement = "block" | "warn" | "observe";
|
|
9
|
+
/** Provider execution classes ordered from cheapest to most expensive. */
|
|
10
|
+
export type AssuranceProviderCostClass = "low" | "medium" | "high";
|
|
9
11
|
/** Assertion lifetime after its owning work item becomes terminal. */
|
|
10
12
|
export type AssuranceLifetime = "hold" | "retire";
|
|
11
13
|
/** Numeric or labelled-set value produced by a measurement. */
|
|
@@ -246,6 +248,18 @@ export interface AssuranceGateDefinition {
|
|
|
246
248
|
triggers: AssuranceGateTrigger[];
|
|
247
249
|
/** Optional description. */
|
|
248
250
|
description?: string;
|
|
251
|
+
/** Explicit provider allow-list and trigger-specific execution policy. */
|
|
252
|
+
provider_policy?: {
|
|
253
|
+
/** Provider ids this gate may invoke. An empty set refuses every provider. */
|
|
254
|
+
allowed_providers: string[];
|
|
255
|
+
/** Trigger rules; an omitted rule refuses provider execution for that trigger. */
|
|
256
|
+
triggers: Partial<Record<AssuranceGateTrigger, {
|
|
257
|
+
/** Highest provider cost class allowed at this trigger. */
|
|
258
|
+
max_cost_class: AssuranceProviderCostClass;
|
|
259
|
+
/** Whether network-declaring providers may run. */
|
|
260
|
+
allow_network: boolean;
|
|
261
|
+
}>>;
|
|
262
|
+
};
|
|
249
263
|
}
|
|
250
264
|
/** Complete persisted assurance registry. */
|
|
251
265
|
export interface AssuranceDocument {
|
|
@@ -258,6 +272,24 @@ export interface AssuranceDocument {
|
|
|
258
272
|
/** Gate declarations. */
|
|
259
273
|
gates: AssuranceGateDefinition[];
|
|
260
274
|
}
|
|
275
|
+
/** Atomic set of ordinary declarations used by presets and accepted proposals. */
|
|
276
|
+
export interface AssuranceDeclarationBundle {
|
|
277
|
+
/** Measurement declarations to create without overwriting divergent ids. */
|
|
278
|
+
measurements: AssuranceMeasurementDefinition[];
|
|
279
|
+
/** Assertion declarations to create without overwriting divergent ids. */
|
|
280
|
+
assertions: AssuranceAssertionDefinition[];
|
|
281
|
+
/** Gate declarations to create without overwriting divergent ids. */
|
|
282
|
+
gates: AssuranceGateDefinition[];
|
|
283
|
+
}
|
|
284
|
+
/** Audited receipt for one atomic assurance bundle application. */
|
|
285
|
+
export interface AssuranceBundleMutationReceipt {
|
|
286
|
+
/** Whether persistence changed. */
|
|
287
|
+
changed: boolean;
|
|
288
|
+
/** Stable ids created by the bundle. */
|
|
289
|
+
created_ids: string[];
|
|
290
|
+
/** Stable ids already present with byte-equivalent declarations. */
|
|
291
|
+
unchanged_ids: string[];
|
|
292
|
+
}
|
|
261
293
|
/** Host adapter result for graph, validate, health, or provider sources. */
|
|
262
294
|
export interface AssuranceExternalMeasurementResult {
|
|
263
295
|
/** Observed value. */
|
|
@@ -279,6 +311,11 @@ export interface AssuranceEvaluationContext {
|
|
|
279
311
|
history: AssuranceHistoryRecord[];
|
|
280
312
|
/** Status ids that make an assertion owner terminal. */
|
|
281
313
|
terminal_statuses?: string[];
|
|
314
|
+
/** Host-declared capabilities for provider-policy enforcement. */
|
|
315
|
+
provider_capabilities?: Readonly<Record<string, {
|
|
316
|
+
cost_class: AssuranceProviderCostClass;
|
|
317
|
+
network: boolean;
|
|
318
|
+
}>>;
|
|
282
319
|
/** External source adapter. Implementations must enforce an appropriate timeout. */
|
|
283
320
|
external: (source: AssuranceGraphSource | AssuranceValidateSource | AssuranceHealthSource | AssuranceProviderSource) => Promise<AssuranceExternalMeasurementResult>;
|
|
284
321
|
}
|
|
@@ -427,6 +464,8 @@ export declare function listAssuranceDeclarations(pmRoot: string, kind: Assuranc
|
|
|
427
464
|
export declare function getAssuranceDeclaration(pmRoot: string, kind: AssuranceDeclarationKind, id: string): Promise<AssuranceDeclaration>;
|
|
428
465
|
/** Create or update one declaration through audited singleton persistence. */
|
|
429
466
|
export declare function putAssuranceDeclaration(pmRoot: string, kind: AssuranceDeclarationKind, definition: AssuranceDeclaration, options?: AssuranceMutationOptions): Promise<AssuranceMutationReceipt>;
|
|
467
|
+
/** Apply a declaration bundle atomically, idempotently, and without replacing divergent ids. */
|
|
468
|
+
export declare function putAssuranceDeclarationBundle(pmRoot: string, bundle: AssuranceDeclarationBundle, options?: AssuranceMutationOptions): Promise<AssuranceBundleMutationReceipt>;
|
|
430
469
|
/** Remove an unreferenced declaration through audited singleton persistence. */
|
|
431
470
|
export declare function removeAssuranceDeclaration(pmRoot: string, kind: AssuranceDeclarationKind, id: string, options?: AssuranceMutationOptions): Promise<AssuranceMutationReceipt>;
|
|
432
471
|
/** Append one immutable gate verdict without changing registry state. */
|
|
@@ -7,7 +7,7 @@
|
|
|
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]="07889857-2176-5659-96f7-8f5591f4c692")}catch(e){}}();
|
|
11
11
|
import { createHash } from "node:crypto";
|
|
12
12
|
import path from "node:path";
|
|
13
13
|
import { EXIT_CODE } from "../../core/shared/constants.js";
|
|
@@ -15,7 +15,7 @@ import { PmCliError } from "../../core/shared/errors.js";
|
|
|
15
15
|
import { appendWorkspaceAuditEvent, mutateWorkspaceJsonWithHistory, readFileIfExists, readHistoryEntries, readSettings, resolveAuthor, stableStringify, WORKSPACE_HISTORY_ID, getWorkspaceHistoryPath, } from "../runtime-primitives.js";
|
|
16
16
|
import { resolveCanonicalRelationshipKind } from "../relationships.js";
|
|
17
17
|
import { MAX_ASSURANCE_VERDICT_LIMIT } from "./assurance-limits.js";
|
|
18
|
-
import { AssuranceMutationRefusalError } from "./assurance-mutation-error.js";
|
|
18
|
+
import { AssuranceEvaluationRefusalError, AssuranceMutationRefusalError, } from "./assurance-mutation-error.js";
|
|
19
19
|
/** Current serialized assurance registry format. */
|
|
20
20
|
export const ASSURANCE_DOCUMENT_VERSION = 1;
|
|
21
21
|
/** Supported lifecycle points that can evaluate a gate. */
|
|
@@ -213,6 +213,30 @@ export function validateAssertionDefinition(definition) {
|
|
|
213
213
|
}
|
|
214
214
|
return definition;
|
|
215
215
|
}
|
|
216
|
+
function validateGateProviderPolicy(policy) {
|
|
217
|
+
if (!Array.isArray(policy.allowed_providers)) {
|
|
218
|
+
throw new AssuranceMutationRefusalError("gate provider_policy.allowed_providers must be an array");
|
|
219
|
+
}
|
|
220
|
+
for (const provider of policy.allowed_providers) {
|
|
221
|
+
requireStableId(provider, "gate.provider_policy.allowed_provider");
|
|
222
|
+
}
|
|
223
|
+
if (typeof policy.triggers !== "object" ||
|
|
224
|
+
policy.triggers === null ||
|
|
225
|
+
Array.isArray(policy.triggers)) {
|
|
226
|
+
throw new AssuranceMutationRefusalError("gate provider_policy.triggers must be an object");
|
|
227
|
+
}
|
|
228
|
+
for (const [trigger, rule] of Object.entries(policy.triggers)) {
|
|
229
|
+
if (!ASSURANCE_GATE_TRIGGERS.includes(trigger)) {
|
|
230
|
+
throw new AssuranceMutationRefusalError(`gate provider policy has unknown trigger ${trigger}`);
|
|
231
|
+
}
|
|
232
|
+
if (typeof rule !== "object" ||
|
|
233
|
+
rule === null ||
|
|
234
|
+
!["low", "medium", "high"].includes(rule.max_cost_class) ||
|
|
235
|
+
typeof rule.allow_network !== "boolean") {
|
|
236
|
+
throw new AssuranceMutationRefusalError(`gate provider policy trigger ${trigger} is invalid`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
216
240
|
/** Validate and return one gate declaration. */
|
|
217
241
|
export function validateGateDefinition(definition) {
|
|
218
242
|
requireStableId(definition.id, "gate.id");
|
|
@@ -224,6 +248,9 @@ export function validateGateDefinition(definition) {
|
|
|
224
248
|
if (definition.triggers.length === 0) {
|
|
225
249
|
throw new AssuranceMutationRefusalError("gate requires at least one trigger");
|
|
226
250
|
}
|
|
251
|
+
if (definition.provider_policy) {
|
|
252
|
+
validateGateProviderPolicy(definition.provider_policy);
|
|
253
|
+
}
|
|
227
254
|
return definition;
|
|
228
255
|
}
|
|
229
256
|
/** Validate registry references, uniqueness, and declaration-local contracts. */
|
|
@@ -300,6 +327,33 @@ function collectMeasurementReferences(document) {
|
|
|
300
327
|
}
|
|
301
328
|
return references;
|
|
302
329
|
}
|
|
330
|
+
function collectGateProviderIds(gate, document) {
|
|
331
|
+
const pending = gate.assertion_ids.flatMap((assertionId) => {
|
|
332
|
+
const assertion = document.assertions.find((entry) => entry.id === assertionId);
|
|
333
|
+
return [
|
|
334
|
+
assertion.measurement_id,
|
|
335
|
+
...(assertion.scope.kind === "filter"
|
|
336
|
+
? [assertion.scope.measurement_id]
|
|
337
|
+
: []),
|
|
338
|
+
];
|
|
339
|
+
});
|
|
340
|
+
const visited = new Set();
|
|
341
|
+
const providers = new Set();
|
|
342
|
+
while (pending.length > 0) {
|
|
343
|
+
const measurementId = pending.pop();
|
|
344
|
+
if (visited.has(measurementId))
|
|
345
|
+
continue;
|
|
346
|
+
visited.add(measurementId);
|
|
347
|
+
const measurement = document.measurements.find((entry) => entry.id === measurementId);
|
|
348
|
+
if (measurement.source.kind === "provider") {
|
|
349
|
+
providers.add(measurement.source.provider);
|
|
350
|
+
}
|
|
351
|
+
else if (measurement.source.kind === "derived") {
|
|
352
|
+
collectExpressionMeasurementReferences(measurement.source.expression, pending);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return providers;
|
|
356
|
+
}
|
|
303
357
|
/** Create an empty, valid assurance registry. */
|
|
304
358
|
export function createEmptyAssuranceDocument() {
|
|
305
359
|
return {
|
|
@@ -513,7 +567,7 @@ async function evaluateMeasurementInternal(definition, context, definitions, sta
|
|
|
513
567
|
};
|
|
514
568
|
const cost = sumCosts([...nestedCosts, ownCost], Date.now() - startedAt);
|
|
515
569
|
if (definition.max_cost !== undefined && cost.units > definition.max_cost) {
|
|
516
|
-
throw new
|
|
570
|
+
throw new AssuranceEvaluationRefusalError(`measurement ${definition.id} exceeded cost ceiling ${definition.max_cost} with ${cost.units}`);
|
|
517
571
|
}
|
|
518
572
|
return {
|
|
519
573
|
id: definition.id,
|
|
@@ -579,15 +633,47 @@ export function evaluateAssuranceAssertion(definition, measurement, options = {}
|
|
|
579
633
|
contributors: measurement.contributors,
|
|
580
634
|
};
|
|
581
635
|
}
|
|
636
|
+
function enforceGateProviderPolicy(gate, document, context, trigger) {
|
|
637
|
+
const providerIds = collectGateProviderIds(gate, document);
|
|
638
|
+
if (providerIds.size === 0)
|
|
639
|
+
return;
|
|
640
|
+
const policy = gate.provider_policy;
|
|
641
|
+
const triggerPolicy = policy?.triggers[trigger];
|
|
642
|
+
if (!policy || !triggerPolicy) {
|
|
643
|
+
throw new AssuranceEvaluationRefusalError(`assurance gate ${gate.id} refuses provider execution at trigger ${trigger} without an explicit provider policy`);
|
|
644
|
+
}
|
|
645
|
+
const allowedProviders = new Set(policy.allowed_providers);
|
|
646
|
+
const costRank = {
|
|
647
|
+
low: 0,
|
|
648
|
+
medium: 1,
|
|
649
|
+
high: 2,
|
|
650
|
+
};
|
|
651
|
+
for (const providerId of providerIds) {
|
|
652
|
+
if (!allowedProviders.has(providerId)) {
|
|
653
|
+
throw new AssuranceEvaluationRefusalError(`assurance gate ${gate.id} refuses provider ${providerId}`);
|
|
654
|
+
}
|
|
655
|
+
const capability = context.provider_capabilities?.[providerId];
|
|
656
|
+
if (!capability) {
|
|
657
|
+
throw new AssuranceEvaluationRefusalError(`assurance provider ${providerId} has no declared capabilities`);
|
|
658
|
+
}
|
|
659
|
+
if (costRank[capability.cost_class] > costRank[triggerPolicy.max_cost_class]) {
|
|
660
|
+
throw new AssuranceEvaluationRefusalError(`assurance gate ${gate.id} refuses ${capability.cost_class}-cost provider ${providerId} at trigger ${trigger}`);
|
|
661
|
+
}
|
|
662
|
+
if (capability.network && !triggerPolicy.allow_network) {
|
|
663
|
+
throw new AssuranceEvaluationRefusalError(`assurance gate ${gate.id} refuses network provider ${providerId} at trigger ${trigger}`);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
}
|
|
582
667
|
/** Evaluate a named gate into one presentation-independent verdict document. */
|
|
583
668
|
export async function evaluateAssuranceGate(gateId, document, context, options) {
|
|
584
669
|
validateAssuranceDocument(document);
|
|
585
670
|
const gateDefinition = document.gates.find((entry) => entry.id === gateId);
|
|
586
671
|
if (!gateDefinition)
|
|
587
|
-
throw new
|
|
672
|
+
throw new AssuranceEvaluationRefusalError(`assurance gate ${gateId} not found`);
|
|
588
673
|
if (!gateDefinition.triggers.includes(options.trigger)) {
|
|
589
|
-
throw new
|
|
674
|
+
throw new AssuranceEvaluationRefusalError(`assurance gate ${gateId} does not declare trigger ${options.trigger}`);
|
|
590
675
|
}
|
|
676
|
+
enforceGateProviderPolicy(gateDefinition, document, context, options.trigger);
|
|
591
677
|
const assertions = await mapWithConcurrency(gateDefinition.assertion_ids, EVALUATION_CONCURRENCY, async (id) => {
|
|
592
678
|
const assertionDefinition = document.assertions.find((entry) => entry.id === id);
|
|
593
679
|
const measurementDefinition = document.measurements.find((entry) => entry.id === assertionDefinition.measurement_id);
|
|
@@ -726,6 +812,59 @@ export async function putAssuranceDeclaration(pmRoot, kind, definition, options
|
|
|
726
812
|
});
|
|
727
813
|
return { ...mutation.result, changed: mutation.changed };
|
|
728
814
|
}
|
|
815
|
+
/** Apply a declaration bundle atomically, idempotently, and without replacing divergent ids. */
|
|
816
|
+
export async function putAssuranceDeclarationBundle(pmRoot, bundle, options = {}) {
|
|
817
|
+
const candidate = createEmptyAssuranceDocument();
|
|
818
|
+
candidate.measurements = structuredClone(bundle.measurements);
|
|
819
|
+
candidate.assertions = structuredClone(bundle.assertions);
|
|
820
|
+
candidate.gates = structuredClone(bundle.gates);
|
|
821
|
+
validateAssuranceDocument(candidate);
|
|
822
|
+
const settings = await readSettings(pmRoot);
|
|
823
|
+
const author = resolveAuthor(options.author, settings.author_default);
|
|
824
|
+
const mutation = await mutateWorkspaceJsonWithHistory({
|
|
825
|
+
pmRoot,
|
|
826
|
+
filePath: assurancePath(pmRoot),
|
|
827
|
+
op: "assurance:bundle:apply",
|
|
828
|
+
author,
|
|
829
|
+
message: options.message,
|
|
830
|
+
lockTtlSeconds: LOCK_TTL_SECONDS,
|
|
831
|
+
lockWaitMs: LOCK_WAIT_MS,
|
|
832
|
+
mutate: (beforeRaw) => {
|
|
833
|
+
const document = beforeRaw === null
|
|
834
|
+
? createEmptyAssuranceDocument()
|
|
835
|
+
: parseAssuranceDocument(beforeRaw);
|
|
836
|
+
const createdIds = [];
|
|
837
|
+
const unchangedIds = [];
|
|
838
|
+
const apply = (target, additions, kind) => {
|
|
839
|
+
for (const definition of additions) {
|
|
840
|
+
const existing = target.find((entry) => entry.id === definition.id);
|
|
841
|
+
if (!existing) {
|
|
842
|
+
target.push(structuredClone(definition));
|
|
843
|
+
createdIds.push(`${kind}:${definition.id}`);
|
|
844
|
+
continue;
|
|
845
|
+
}
|
|
846
|
+
if (stableStringify(existing) !== stableStringify(definition)) {
|
|
847
|
+
throw new AssuranceMutationRefusalError(`assurance bundle refuses to replace divergent ${kind} ${definition.id}`);
|
|
848
|
+
}
|
|
849
|
+
unchangedIds.push(`${kind}:${definition.id}`);
|
|
850
|
+
}
|
|
851
|
+
};
|
|
852
|
+
apply(document.measurements, candidate.measurements, "measurement");
|
|
853
|
+
apply(document.assertions, candidate.assertions, "assertion");
|
|
854
|
+
apply(document.gates, candidate.gates, "gate");
|
|
855
|
+
validateAssuranceDocument(document);
|
|
856
|
+
return {
|
|
857
|
+
raw: `${JSON.stringify(document, null, 2)}\n`,
|
|
858
|
+
result: {
|
|
859
|
+
changed: createdIds.length > 0,
|
|
860
|
+
created_ids: createdIds,
|
|
861
|
+
unchanged_ids: unchangedIds,
|
|
862
|
+
},
|
|
863
|
+
};
|
|
864
|
+
},
|
|
865
|
+
});
|
|
866
|
+
return { ...mutation.result, changed: mutation.changed };
|
|
867
|
+
}
|
|
729
868
|
/** Remove an unreferenced declaration through audited singleton persistence. */
|
|
730
869
|
export async function removeAssuranceDeclaration(pmRoot, kind, id, options = {}) {
|
|
731
870
|
const settings = await readSettings(pmRoot);
|
|
@@ -801,4 +940,4 @@ export async function listAssuranceVerdicts(pmRoot, options = {}) {
|
|
|
801
940
|
.slice(0, limit);
|
|
802
941
|
}
|
|
803
942
|
//# sourceMappingURL=assurance.js.map
|
|
804
|
-
//# debugId=
|
|
943
|
+
//# debugId=07889857-2176-5659-96f7-8f5591f4c692
|
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
* Implements the pm gc 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]="
|
|
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]="e982158e-4a8d-57a9-bd0b-24bd73aef342")}catch(e){}}();
|
|
8
8
|
import fs from "node:fs/promises";
|
|
9
9
|
import path from "node:path";
|
|
10
10
|
import { runCheckpointGc } from "../../core/checkpoint/checkpoint-gc.js";
|
|
11
11
|
import { runWorkspaceTransactionGc } from "../workspace-transaction-gc.js";
|
|
12
12
|
import { runActiveOnIndexHooks, runActiveOnReadHooks, runActiveOnWriteHooks, } from "../../core/extensions/index.js";
|
|
13
|
-
import { pathExists } from "../../core/fs/fs-utils.js";
|
|
13
|
+
import { isFileMissingError, pathExists } from "../../core/fs/fs-utils.js";
|
|
14
14
|
import { runLockGc } from "../../core/lock/lock-gc.js";
|
|
15
15
|
import { EXIT_CODE } from "../../core/shared/constants.js";
|
|
16
16
|
import { PmCliError } from "../../core/shared/errors.js";
|
|
@@ -73,12 +73,6 @@ const GC_TARGETS = [
|
|
|
73
73
|
kind: "file",
|
|
74
74
|
},
|
|
75
75
|
];
|
|
76
|
-
function isErrno(error, code) {
|
|
77
|
-
return (typeof error === "object" &&
|
|
78
|
-
error !== null &&
|
|
79
|
-
"code" in error &&
|
|
80
|
-
error.code === code);
|
|
81
|
-
}
|
|
82
76
|
async function removeCacheFile(pmRoot, target, dryRun) {
|
|
83
77
|
const relativePath = target.relativePath;
|
|
84
78
|
const absolutePath = path.join(pmRoot, relativePath);
|
|
@@ -123,7 +117,7 @@ async function removeCacheFile(pmRoot, target, dryRun) {
|
|
|
123
117
|
};
|
|
124
118
|
}
|
|
125
119
|
catch (error) {
|
|
126
|
-
if (
|
|
120
|
+
if (isFileMissingError(error)) {
|
|
127
121
|
return {
|
|
128
122
|
removed: false,
|
|
129
123
|
warnings,
|
|
@@ -163,7 +157,7 @@ async function sweepWorkspaceSnapshotTemps(pmRoot, dryRun) {
|
|
|
163
157
|
scope: "project",
|
|
164
158
|
})));
|
|
165
159
|
const stats = await fs.stat(absolutePath).catch((error) => {
|
|
166
|
-
if (
|
|
160
|
+
if (isFileMissingError(error)) {
|
|
167
161
|
return undefined;
|
|
168
162
|
}
|
|
169
163
|
throw error;
|
|
@@ -373,4 +367,4 @@ export async function runGc(global, options = {}) {
|
|
|
373
367
|
};
|
|
374
368
|
}
|
|
375
369
|
//# sourceMappingURL=gc.js.map
|
|
376
|
-
//# debugId=
|
|
370
|
+
//# debugId=e982158e-4a8d-57a9-bd0b-24bd73aef342
|