@unbrained/pm-cli 2026.8.16 → 2026.8.17
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 +30 -3
- package/README.md +13 -13
- package/dist/cli/public.d.ts +2 -0
- package/dist/cli/public.js +21 -0
- package/dist/cli/register-mutation.js +4 -4
- package/dist/cli-bundle/bundle-manifest.json +151 -151
- package/dist/cli-bundle/chunks/{chunk-I2LUWWR5.js → chunk-2QLOXC3V.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-URW4QRMI.js → chunk-3UEGXBMR.js} +22 -22
- package/dist/cli-bundle/chunks/chunk-4JPYF536.js +35 -0
- package/dist/cli-bundle/chunks/chunk-6VDLG2FC.js +13 -0
- package/dist/cli-bundle/chunks/chunk-7IBRGCHX.js +2 -0
- package/dist/cli-bundle/chunks/chunk-HBJAS566.js +8 -0
- package/dist/cli-bundle/chunks/chunk-ION3E43Q.js +197 -0
- package/dist/cli-bundle/chunks/{chunk-73UGEBRS.js → chunk-JOKATURE.js} +4 -4
- package/dist/cli-bundle/chunks/{chunk-RIS565OA.js → chunk-TPXZIBIV.js} +5 -5
- package/dist/cli-bundle/chunks/chunk-YM2YAMF7.js +2 -0
- package/dist/cli-bundle/chunks/{register-list-query-EYCXHZAG.js → register-list-query-Q7KII4G5.js} +2 -2
- package/dist/cli-bundle/chunks/register-mutation-4WBRPXLV.js +20 -0
- package/dist/cli-bundle/chunks/{register-operations-KGDNRMCL.js → register-operations-SSENDH5B.js} +2 -2
- package/dist/cli-bundle/chunks/{register-setup-NVXBOD5I.js → register-setup-DOSFBGKE.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-TZATVC4G.js → chunk-267MUUS6.js} +11 -11
- package/dist/cli-bundle/focused-chunks/chunk-2NNFLLNG.js +2 -0
- package/dist/cli-bundle/focused-chunks/{chunk-HAACPSUW.js → chunk-2QSAKMIH.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-6ROKKT4X.js → chunk-ATEY76SX.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-BUCG4ZI2.js +2 -0
- package/dist/cli-bundle/focused-chunks/{chunk-SSNDYZTM.js → chunk-CYFK5UPB.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-EHXIKD6K.js +2 -0
- package/dist/cli-bundle/focused-chunks/{chunk-J4UFYHOD.js → chunk-KGEXTMPG.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-KP2VQQVQ.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-MHPIL773.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-POEEAD2V.js +4 -0
- package/dist/cli-bundle/focused-chunks/{chunk-T46KLOIB.js → chunk-SALH6B6K.js} +47 -47
- package/dist/cli-bundle/focused-chunks/{chunk-CODV5LUT.js → chunk-SYZENPIT.js} +3 -3
- package/dist/cli-bundle/focused-chunks/{chunk-4JPEBFFC.js → chunk-XKAMP5OU.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-NJ5RLEFD.js → chunk-XPIC53VA.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-C7JUBRFP.js → chunk-ZQPGZZWQ.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-ZT2DJS27.js +155 -0
- package/dist/cli-bundle/main.js +2 -2
- 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 +31 -31
- 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 +32 -32
- 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 +4 -2
- package/dist/core/extensions/loader.js +10 -6
- package/dist/core/extensions/manifest-schema.d.ts +20 -0
- package/dist/core/extensions/manifest-schema.js +52 -0
- package/dist/core/history/replay.js +32 -14
- package/dist/core/schema/fields-file.d.ts +1 -1
- package/dist/core/schema/fields-file.js +2 -2
- package/dist/core/schema/runtime-field-values.js +115 -9
- package/dist/core/schema/runtime-schema.d.ts +3 -1
- package/dist/core/schema/runtime-schema.js +124 -3
- package/dist/core/shared/errors.d.ts +10 -0
- package/dist/core/shared/errors.js +2 -2
- package/dist/core/shared/time.d.ts +2 -0
- package/dist/core/shared/time.js +27 -2
- package/dist/sdk/agent/refusal-reachability.d.ts +46 -6
- package/dist/sdk/agent/refusal-reachability.js +350 -8
- package/dist/sdk/cli-contracts/tool-parameter-tables.js +5 -4
- package/dist/sdk/compose.d.ts +6 -2
- package/dist/sdk/compose.js +33 -3
- package/dist/sdk/context-intent-contracts.d.ts +8 -4
- package/dist/sdk/context-intent-contracts.js +76 -31
- package/dist/sdk/dependency-flag-validation.d.ts +9 -2
- package/dist/sdk/dependency-flag-validation.js +101 -35
- package/dist/sdk/environment/host-environment-errors.js +25 -7
- package/dist/sdk/generated/generated-error-code-catalog-part-1.js +26 -2
- package/dist/sdk/generated/generated-error-code-catalog-part-2.js +14 -2
- package/dist/sdk/governance/assurance-action.d.ts +3 -7
- package/dist/sdk/governance/assurance-action.js +97 -2
- package/dist/sdk/governance/boundary-fixtures.d.ts +86 -0
- package/dist/sdk/governance/boundary-fixtures.js +201 -0
- package/dist/sdk/governance/defect-recurrence-signals.d.ts +10 -0
- package/dist/sdk/governance/defect-recurrence-signals.js +25 -0
- package/dist/sdk/governance/defect-recurrence.d.ts +242 -0
- package/dist/sdk/governance/defect-recurrence.js +523 -0
- package/dist/sdk/governance.d.ts +2 -0
- package/dist/sdk/governance.js +4 -2
- package/dist/sdk/graph/governance.js +3 -2
- package/dist/sdk/guide-topics.js +191 -2
- package/dist/sdk/index.d.ts +3 -0
- package/dist/sdk/index.js +4 -2
- package/dist/sdk/lifecycle/create.js +4 -2
- package/dist/sdk/lifecycle/update-many.js +15 -2
- package/dist/sdk/lifecycle/update.js +5 -2
- package/dist/sdk/linked-artifacts.js +63 -14
- package/dist/sdk/query/get.d.ts +2 -0
- package/dist/sdk/query/get.js +32 -7
- package/dist/sdk/relationship-analytics.js +90 -14
- package/dist/sdk/relationship-kinds/contract.d.ts +4 -0
- package/dist/sdk/relationship-kinds/contract.js +3 -2
- package/dist/sdk/relationships.d.ts +1 -1
- package/dist/sdk/relationships.js +11 -2
- package/dist/sdk/schema.d.ts +1 -0
- package/dist/sdk/schema.js +2 -2
- package/dist/sdk/test/execution.d.ts +6 -2
- package/dist/sdk/test/execution.js +39 -13
- package/dist/types.d.ts +29 -2
- package/dist/types.js +4 -2
- package/docs/CONFIGURATION.md +58 -1
- package/docs/CONTEXT_INTEGRITY_CONTRACTS.md +58 -0
- package/docs/DEFECT_RECURRENCE.md +134 -0
- package/docs/DEPENDENCY_KIND_CONTRACT.md +12 -2
- package/docs/README.md +2 -0
- package/docs/RELATIONSHIP_GRAPH.md +16 -2
- package/docs/SDK.md +38 -5
- package/docs/SDK_RUNTIME_BOUNDARIES.md +9 -1
- package/docs/TESTING.md +18 -1
- package/docs/TRUSTWORTHY_CONTEXT_EVIDENCE.md +1 -1
- package/marketplace.json +2 -2
- package/package.json +6 -4
- 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-claude/skills/pm-audit/SKILL.md +26 -0
- package/plugins/pm-claude/skills/pm-developer/SKILL.md +26 -0
- package/plugins/pm-claude/skills/pm-planner/SKILL.md +26 -0
- package/plugins/pm-claude/skills/pm-release/SKILL.md +26 -0
- package/plugins/pm-claude/skills/pm-workflow/SKILL.md +26 -0
- package/plugins/pm-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/pm-codex/skills/pm-auditor/SKILL.md +26 -0
- package/plugins/pm-codex/skills/pm-native/SKILL.md +26 -0
- package/plugins/pm-codex/skills/pm-release/SKILL.md +26 -0
- package/sdk/public-surface.json +588 -20
- package/dist/cli-bundle/chunks/chunk-47OQEOQB.js +0 -2
- package/dist/cli-bundle/chunks/chunk-HCIYWD6M.js +0 -35
- package/dist/cli-bundle/chunks/chunk-IC5W2T34.js +0 -2
- package/dist/cli-bundle/chunks/chunk-PANOOBIS.js +0 -13
- package/dist/cli-bundle/chunks/chunk-TK6N7HGM.js +0 -8
- package/dist/cli-bundle/chunks/chunk-TOFG2URD.js +0 -197
- package/dist/cli-bundle/chunks/register-mutation-7YVIG54J.js +0 -20
- package/dist/cli-bundle/focused-chunks/chunk-5NMFSX26.js +0 -155
- package/dist/cli-bundle/focused-chunks/chunk-725JSCMP.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-DQ6SMTBF.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-JX5Z344Q.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-L4RDYBIQ.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-QYZERYR5.js +0 -4
- package/dist/cli-bundle/focused-chunks/chunk-WY3WRPVN.js +0 -2
|
@@ -1,7 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module sdk/assurance-action
|
|
3
|
+
*
|
|
4
|
+
* Normalizes the assurance action vocabulary once for CLI, SDK, and MCP hosts.
|
|
5
|
+
*/
|
|
1
6
|
|
|
2
|
-
!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]="57c12241-812d-5db6-9a07-ca256152b130")}catch(e){}}();
|
|
8
|
+
import { createHash } from "node:crypto";
|
|
3
9
|
import { EXIT_CODE } from "../../core/shared/constants.js";
|
|
4
10
|
import { PmCliError } from "../../core/shared/errors.js";
|
|
11
|
+
import { stableStringify } from "../../core/shared/serialization.js";
|
|
5
12
|
import { ASSURANCE_GATE_TRIGGERS, evaluateAssuranceGate, getAssuranceDeclaration, listAssuranceDeclarations, listAssuranceVerdicts, putAssuranceDeclaration, recordAssuranceVerdict, removeAssuranceDeclaration, } from "./assurance.js";
|
|
6
13
|
import { ASSURANCE_PRESET_IDS, acceptAssuranceProposals, applyAssurancePreset, createAssurancePreset, deriveAssuranceProposals, promoteAssuranceAssertion, } from "./assurance-presets.js";
|
|
7
14
|
import { MAX_ASSURANCE_VERDICT_LIMIT } from "./assurance-limits.js";
|
|
@@ -9,6 +16,9 @@ import { normalizeAssuranceEvaluation, normalizeAssuranceMutation, } from "./ass
|
|
|
9
16
|
import { createAssuranceWorkspaceContext, } from "./assurance-runtime.js";
|
|
10
17
|
import { resolvePmRoot } from "../runtime-primitives.js";
|
|
11
18
|
import { parseRuntimeInteger, readRuntimeString } from "../runtime-input.js";
|
|
19
|
+
import { analyzeDefectChangeRisk, buildDefectRecurrenceIndex, parseDefectChangeRiskRequest, } from "./defect-recurrence.js";
|
|
20
|
+
import { defectRecurrenceItemSignals } from "./defect-recurrence-signals.js";
|
|
21
|
+
let riskIndexCache;
|
|
12
22
|
/** Assurance registry and evaluation verbs shared by every transport. */
|
|
13
23
|
export const ASSURANCE_ACTIONS = [
|
|
14
24
|
"list",
|
|
@@ -21,6 +31,7 @@ export const ASSURANCE_ACTIONS = [
|
|
|
21
31
|
"apply",
|
|
22
32
|
"derive",
|
|
23
33
|
"promote",
|
|
34
|
+
"risk",
|
|
24
35
|
];
|
|
25
36
|
/** Assurance declaration kinds shared by every transport. */
|
|
26
37
|
export const ASSURANCE_DECLARATION_KINDS = [
|
|
@@ -205,6 +216,87 @@ async function runGateAction(input, pmRoot, runtime) {
|
|
|
205
216
|
}
|
|
206
217
|
return verdict;
|
|
207
218
|
}
|
|
219
|
+
/** Normalize a request-validation failure into the shared usage-error contract. */
|
|
220
|
+
function throwRiskUsageError(error) {
|
|
221
|
+
if (error instanceof PmCliError)
|
|
222
|
+
throw error;
|
|
223
|
+
throw new PmCliError(error instanceof Error
|
|
224
|
+
? error.message
|
|
225
|
+
: "assurance risk request is invalid", EXIT_CODE.USAGE);
|
|
226
|
+
}
|
|
227
|
+
/** Parse JSON or object risk definitions without wrapping workspace failures. */
|
|
228
|
+
function parseRiskActionRequest(input) {
|
|
229
|
+
if (input.limit !== undefined) {
|
|
230
|
+
throw new PmCliError("assurance risk does not accept --limit; set limit inside the risk definition", EXIT_CODE.USAGE);
|
|
231
|
+
}
|
|
232
|
+
let definition = input.definition;
|
|
233
|
+
if (typeof definition === "string") {
|
|
234
|
+
try {
|
|
235
|
+
definition = JSON.parse(definition);
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
throw new PmCliError("assurance risk definition must be valid JSON", EXIT_CODE.USAGE, { reason: error instanceof Error ? error.message : "invalid_json" });
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
try {
|
|
242
|
+
return parseDefectChangeRiskRequest(definition);
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
return throwRiskUsageError(error);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
/** Derive the exact sparse replacement and deletion set for a cached index. */
|
|
249
|
+
function changedRiskItemIds(cached, current) {
|
|
250
|
+
const changed = new Set();
|
|
251
|
+
for (const [itemId, itemFingerprint] of current) {
|
|
252
|
+
if (cached.item_fingerprints.get(itemId) !== itemFingerprint)
|
|
253
|
+
changed.add(itemId);
|
|
254
|
+
}
|
|
255
|
+
for (const itemId of cached.item_fingerprints.keys()) {
|
|
256
|
+
if (!current.has(itemId))
|
|
257
|
+
changed.add(itemId);
|
|
258
|
+
}
|
|
259
|
+
return changed;
|
|
260
|
+
}
|
|
261
|
+
/** Build and retain a safely invalidated bounded process-local recurrence index. */
|
|
262
|
+
function buildCachedRiskIndex(pmRoot, request, items) {
|
|
263
|
+
const policySerialized = stableStringify(request.policy);
|
|
264
|
+
const cached = riskIndexCache?.pm_root === pmRoot ? riskIndexCache : undefined;
|
|
265
|
+
const itemFingerprints = new Map(items.map((item) => [
|
|
266
|
+
item.id,
|
|
267
|
+
createHash("sha256")
|
|
268
|
+
.update(stableStringify(defectRecurrenceItemSignals(item)))
|
|
269
|
+
.digest("hex"),
|
|
270
|
+
]));
|
|
271
|
+
const reusable = cached?.policy_serialized === policySerialized ? cached : undefined;
|
|
272
|
+
const changed = reusable
|
|
273
|
+
? changedRiskItemIds(reusable, itemFingerprints)
|
|
274
|
+
: new Set();
|
|
275
|
+
const index = buildDefectRecurrenceIndex(request.policy, reusable ? items.filter((item) => changed.has(item.id)) : items, reusable
|
|
276
|
+
? { previous_index: reusable.index, changed_item_ids: [...changed] }
|
|
277
|
+
: {});
|
|
278
|
+
riskIndexCache = {
|
|
279
|
+
pm_root: pmRoot,
|
|
280
|
+
policy_serialized: policySerialized,
|
|
281
|
+
item_fingerprints: itemFingerprints,
|
|
282
|
+
index,
|
|
283
|
+
};
|
|
284
|
+
return index;
|
|
285
|
+
}
|
|
286
|
+
/** Evaluate one serialized change-risk request against authoritative PM metadata. */
|
|
287
|
+
async function runRiskAction(input, pmRoot) {
|
|
288
|
+
const request = parseRiskActionRequest(input);
|
|
289
|
+
const context = await createAssuranceWorkspaceContext(pmRoot, {
|
|
290
|
+
include_history: false,
|
|
291
|
+
resolve_tree: false,
|
|
292
|
+
});
|
|
293
|
+
try {
|
|
294
|
+
return analyzeDefectChangeRisk(buildCachedRiskIndex(pmRoot, request, context.items), request.change, { cursor: request.cursor, limit: request.limit });
|
|
295
|
+
}
|
|
296
|
+
catch (error) {
|
|
297
|
+
return throwRiskUsageError(error);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
208
300
|
/** Execute one assurance request through the public assurance SDK primitives. */
|
|
209
301
|
export async function runAssuranceAction(input, global = {}, runtime = {}) {
|
|
210
302
|
const action = parseAction(input.action);
|
|
@@ -221,6 +313,9 @@ export async function runAssuranceAction(input, global = {}, runtime = {}) {
|
|
|
221
313
|
if (action === "run") {
|
|
222
314
|
return runGateAction(input, pmRoot, runtime);
|
|
223
315
|
}
|
|
316
|
+
if (action === "risk") {
|
|
317
|
+
return runRiskAction(input, pmRoot);
|
|
318
|
+
}
|
|
224
319
|
const kind = parseKind(input.kind);
|
|
225
320
|
if (action === "list")
|
|
226
321
|
return listAssuranceDeclarations(pmRoot, kind);
|
|
@@ -282,4 +377,4 @@ export function runAssuranceDispatch(args, options, global) {
|
|
|
282
377
|
}, global);
|
|
283
378
|
}
|
|
284
379
|
//# sourceMappingURL=assurance-action.js.map
|
|
285
|
-
//# debugId=
|
|
380
|
+
//# debugId=57c12241-812d-5db6-9a07-ca256152b130
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/** Evidence source accepted for a captured boundary sample. */
|
|
2
|
+
export declare const BOUNDARY_FIXTURE_CAPTURE_SOURCES: readonly ["captured_redacted", "captured_verbatim"];
|
|
3
|
+
/** Captured boundary evidence source. */
|
|
4
|
+
export type BoundaryFixtureCaptureSource = (typeof BOUNDARY_FIXTURE_CAPTURE_SOURCES)[number];
|
|
5
|
+
/** Inventory record backed by a committed captured sample. */
|
|
6
|
+
export interface CapturedBoundaryRecord {
|
|
7
|
+
/** Stable external-boundary identifier. */
|
|
8
|
+
id: string;
|
|
9
|
+
/** Production component that emits or obtains the value. */
|
|
10
|
+
producer: string;
|
|
11
|
+
/** Production component that parses or consumes the value. */
|
|
12
|
+
consumer: string;
|
|
13
|
+
/** Concise serialized-format description. */
|
|
14
|
+
format: string;
|
|
15
|
+
/** Relative path to a committed JSON fixture. */
|
|
16
|
+
fixture_path: string;
|
|
17
|
+
}
|
|
18
|
+
/** Inventory record explicitly waived until a bounded expiry. */
|
|
19
|
+
export interface WaivedBoundaryRecord {
|
|
20
|
+
/** Stable external-boundary identifier. */
|
|
21
|
+
id: string;
|
|
22
|
+
/** Production component that emits or obtains the value. */
|
|
23
|
+
producer: string;
|
|
24
|
+
/** Production component that parses or consumes the value. */
|
|
25
|
+
consumer: string;
|
|
26
|
+
/** Concise serialized-format description. */
|
|
27
|
+
format: string;
|
|
28
|
+
/** Concrete reason a safe captured fixture is not available. */
|
|
29
|
+
waiver_reason: string;
|
|
30
|
+
/** Accountable PM item or team. */
|
|
31
|
+
waiver_owner: string;
|
|
32
|
+
/** ISO timestamp after which the waiver fails closed. */
|
|
33
|
+
waiver_expires_at: string;
|
|
34
|
+
}
|
|
35
|
+
/** One externally generated or consumed boundary inventory entry. */
|
|
36
|
+
export type BoundaryInventoryRecord = CapturedBoundaryRecord | WaivedBoundaryRecord;
|
|
37
|
+
/** Versioned project boundary inventory. */
|
|
38
|
+
export interface BoundaryFixtureRegistry {
|
|
39
|
+
/** Serialized registry format. */
|
|
40
|
+
version: 1;
|
|
41
|
+
/** Human-readable statement of what source surfaces were inventoried. */
|
|
42
|
+
inventory_scope: string;
|
|
43
|
+
/** Captured or explicitly waived boundary records. */
|
|
44
|
+
boundaries: BoundaryInventoryRecord[];
|
|
45
|
+
}
|
|
46
|
+
/** Required shape of a committed captured boundary sample. */
|
|
47
|
+
export interface BoundaryFixtureSample {
|
|
48
|
+
/** Serialized fixture format. */
|
|
49
|
+
version: 1;
|
|
50
|
+
/** Inventory boundary id proved by this sample. */
|
|
51
|
+
boundary_id: string;
|
|
52
|
+
/** Captured evidence classification; self-generated is intentionally absent. */
|
|
53
|
+
capture_source: BoundaryFixtureCaptureSource;
|
|
54
|
+
/** Where and how the original value was observed. */
|
|
55
|
+
capture_provenance: string;
|
|
56
|
+
/** Redactions applied before committing the sample. */
|
|
57
|
+
redactions: string[];
|
|
58
|
+
/** Redacted external input. */
|
|
59
|
+
input: unknown;
|
|
60
|
+
/** Redacted observed boundary value. */
|
|
61
|
+
observed: unknown;
|
|
62
|
+
}
|
|
63
|
+
/** Stable boundary-fixture governance finding. */
|
|
64
|
+
export interface BoundaryFixtureFinding {
|
|
65
|
+
/** Boundary id, or registry for registry-wide defects. */
|
|
66
|
+
boundary_id: string;
|
|
67
|
+
/** Machine-readable failure kind. */
|
|
68
|
+
kind: "invalid_registry" | "duplicate_boundary" | "invalid_boundary" | "missing_fixture" | "invalid_fixture" | "fixture_boundary_mismatch" | "unsafe_fixture" | "invalid_waiver" | "expired_waiver";
|
|
69
|
+
/** Actionable explanation. */
|
|
70
|
+
detail: string;
|
|
71
|
+
}
|
|
72
|
+
/** Complete captured-boundary governance report. */
|
|
73
|
+
export interface BoundaryFixtureReport {
|
|
74
|
+
/** Whether every inventoried boundary has safe current evidence. */
|
|
75
|
+
ok: boolean;
|
|
76
|
+
/** Total inventoried boundaries. */
|
|
77
|
+
boundary_count: number;
|
|
78
|
+
/** Boundaries backed by committed captured samples. */
|
|
79
|
+
captured_count: number;
|
|
80
|
+
/** Boundaries carrying live explicit waivers. */
|
|
81
|
+
waived_count: number;
|
|
82
|
+
/** Stable sorted failures. */
|
|
83
|
+
findings: BoundaryFixtureFinding[];
|
|
84
|
+
}
|
|
85
|
+
/** Validate an untrusted boundary registry and its already-loaded JSON samples. */
|
|
86
|
+
export declare function evaluateBoundaryFixtures(registry: BoundaryFixtureRegistry, fixtures: Readonly<Record<string, unknown>>, now?: Date): BoundaryFixtureReport;
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module sdk/boundary-fixtures
|
|
3
|
+
*
|
|
4
|
+
* Validates captured external-boundary samples and explicit time-bounded
|
|
5
|
+
* waivers without allowing production code to manufacture its own fixtures.
|
|
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]="3edb1f4f-d26e-51a8-b62a-caffdd3ac44b")}catch(e){}}();
|
|
9
|
+
import { isRfc3339DateTime } from "../../core/shared/time.js";
|
|
10
|
+
/** Evidence source accepted for a captured boundary sample. */
|
|
11
|
+
export const BOUNDARY_FIXTURE_CAPTURE_SOURCES = [
|
|
12
|
+
"captured_redacted",
|
|
13
|
+
"captured_verbatim",
|
|
14
|
+
];
|
|
15
|
+
const SECRET_OR_PRIVATE_PATTERNS = [
|
|
16
|
+
{ label: "home directory", pattern: /(?:\/home\/|\/Users\/)[^/\s"']+/u },
|
|
17
|
+
{ label: "GitHub token", pattern: /\b(?:gh[oprsu]_[A-Za-z0-9_]{20,})\b/u },
|
|
18
|
+
{ label: "npm token", pattern: /\bnpm_[A-Za-z0-9]{20,}\b/u },
|
|
19
|
+
{
|
|
20
|
+
label: "private key",
|
|
21
|
+
pattern: /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/u,
|
|
22
|
+
},
|
|
23
|
+
];
|
|
24
|
+
function nonEmpty(value) {
|
|
25
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
26
|
+
}
|
|
27
|
+
function validateSample(boundary, sample) {
|
|
28
|
+
if (typeof sample !== "object" || sample === null || Array.isArray(sample)) {
|
|
29
|
+
return [
|
|
30
|
+
{
|
|
31
|
+
boundary_id: boundary.id,
|
|
32
|
+
kind: "invalid_fixture",
|
|
33
|
+
detail: `${boundary.fixture_path} must contain a JSON object.`,
|
|
34
|
+
},
|
|
35
|
+
];
|
|
36
|
+
}
|
|
37
|
+
const fixture = sample;
|
|
38
|
+
const findings = [];
|
|
39
|
+
if (fixture.boundary_id !== boundary.id) {
|
|
40
|
+
findings.push({
|
|
41
|
+
boundary_id: boundary.id,
|
|
42
|
+
kind: "fixture_boundary_mismatch",
|
|
43
|
+
detail: `${boundary.fixture_path} names ${String(fixture.boundary_id)} instead of ${boundary.id}.`,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
if (fixture.version !== 1 ||
|
|
47
|
+
!BOUNDARY_FIXTURE_CAPTURE_SOURCES.includes(fixture.capture_source) ||
|
|
48
|
+
!nonEmpty(fixture.capture_provenance) ||
|
|
49
|
+
!Array.isArray(fixture.redactions) ||
|
|
50
|
+
!fixture.redactions.every(nonEmpty) ||
|
|
51
|
+
!("input" in fixture) ||
|
|
52
|
+
!("observed" in fixture)) {
|
|
53
|
+
findings.push({
|
|
54
|
+
boundary_id: boundary.id,
|
|
55
|
+
kind: "invalid_fixture",
|
|
56
|
+
detail: `${boundary.fixture_path} lacks captured provenance, redaction metadata, input, or observed output.`,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
const serialized = JSON.stringify(sample);
|
|
60
|
+
for (const sensitive of SECRET_OR_PRIVATE_PATTERNS) {
|
|
61
|
+
if (sensitive.pattern.test(serialized)) {
|
|
62
|
+
findings.push({
|
|
63
|
+
boundary_id: boundary.id,
|
|
64
|
+
kind: "unsafe_fixture",
|
|
65
|
+
detail: `${boundary.fixture_path} contains a possible ${sensitive.label}.`,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return findings;
|
|
70
|
+
}
|
|
71
|
+
/** Evaluate the independent validity and expiry rules for one waiver record. */
|
|
72
|
+
function evaluateBoundaryWaiver(boundary, nowMs) {
|
|
73
|
+
const expiresAt = Date.parse(boundary.waiver_expires_at);
|
|
74
|
+
const validWaiver = nonEmpty(boundary.waiver_reason) &&
|
|
75
|
+
nonEmpty(boundary.waiver_owner) &&
|
|
76
|
+
boundary.waiver_expires_at.endsWith("Z") &&
|
|
77
|
+
isRfc3339DateTime(boundary.waiver_expires_at) &&
|
|
78
|
+
Number.isFinite(expiresAt);
|
|
79
|
+
return {
|
|
80
|
+
captured: 0,
|
|
81
|
+
waived: validWaiver && expiresAt >= nowMs ? 1 : 0,
|
|
82
|
+
findings: !validWaiver
|
|
83
|
+
? [
|
|
84
|
+
{
|
|
85
|
+
boundary_id: boundary.id,
|
|
86
|
+
kind: "invalid_waiver",
|
|
87
|
+
detail: `Boundary ${boundary.id} waiver requires reason, owner, and an RFC 3339 UTC expiry.`,
|
|
88
|
+
},
|
|
89
|
+
]
|
|
90
|
+
: expiresAt < nowMs
|
|
91
|
+
? [
|
|
92
|
+
{
|
|
93
|
+
boundary_id: boundary.id,
|
|
94
|
+
kind: "expired_waiver",
|
|
95
|
+
detail: `Boundary ${boundary.id} waiver expired at ${boundary.waiver_expires_at}.`,
|
|
96
|
+
},
|
|
97
|
+
]
|
|
98
|
+
: [],
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function evaluateBoundaryRecord(boundary, fixtures, nowMs) {
|
|
102
|
+
if (!nonEmpty(boundary.id) ||
|
|
103
|
+
!nonEmpty(boundary.producer) ||
|
|
104
|
+
!nonEmpty(boundary.consumer) ||
|
|
105
|
+
!nonEmpty(boundary.format)) {
|
|
106
|
+
const boundaryId = nonEmpty(boundary.id) ? boundary.id : "unknown";
|
|
107
|
+
return {
|
|
108
|
+
captured: 0,
|
|
109
|
+
waived: 0,
|
|
110
|
+
findings: [
|
|
111
|
+
{
|
|
112
|
+
boundary_id: boundaryId,
|
|
113
|
+
kind: "invalid_boundary",
|
|
114
|
+
detail: "Each boundary requires non-empty id, producer, consumer, and format.",
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
if ("fixture_path" in boundary) {
|
|
120
|
+
const sample = fixtures[boundary.fixture_path];
|
|
121
|
+
const findings = nonEmpty(boundary.fixture_path) && boundary.fixture_path in fixtures
|
|
122
|
+
? validateSample(boundary, sample)
|
|
123
|
+
: [
|
|
124
|
+
{
|
|
125
|
+
boundary_id: boundary.id,
|
|
126
|
+
kind: "missing_fixture",
|
|
127
|
+
detail: `Boundary ${boundary.id} fixture ${String(boundary.fixture_path)} is missing.`,
|
|
128
|
+
},
|
|
129
|
+
];
|
|
130
|
+
return {
|
|
131
|
+
captured: findings.length === 0 ? 1 : 0,
|
|
132
|
+
waived: 0,
|
|
133
|
+
findings,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
return evaluateBoundaryWaiver(boundary, nowMs);
|
|
137
|
+
}
|
|
138
|
+
/** Validate an untrusted boundary registry and its already-loaded JSON samples. */
|
|
139
|
+
export function evaluateBoundaryFixtures(registry, fixtures, now = new Date()) {
|
|
140
|
+
const findings = [];
|
|
141
|
+
if (registry.version !== 1 ||
|
|
142
|
+
!nonEmpty(registry.inventory_scope) ||
|
|
143
|
+
!Array.isArray(registry.boundaries)) {
|
|
144
|
+
return {
|
|
145
|
+
ok: false,
|
|
146
|
+
boundary_count: 0,
|
|
147
|
+
captured_count: 0,
|
|
148
|
+
waived_count: 0,
|
|
149
|
+
findings: [
|
|
150
|
+
{
|
|
151
|
+
boundary_id: "registry",
|
|
152
|
+
kind: "invalid_registry",
|
|
153
|
+
detail: "Boundary registry requires version 1, inventory_scope, and boundaries.",
|
|
154
|
+
},
|
|
155
|
+
],
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
const ids = new Set();
|
|
159
|
+
let capturedCount = 0;
|
|
160
|
+
let waivedCount = 0;
|
|
161
|
+
for (const candidate of registry.boundaries) {
|
|
162
|
+
if (typeof candidate !== "object" ||
|
|
163
|
+
candidate === null ||
|
|
164
|
+
Array.isArray(candidate)) {
|
|
165
|
+
findings.push({
|
|
166
|
+
boundary_id: "unknown",
|
|
167
|
+
kind: "invalid_boundary",
|
|
168
|
+
detail: "Each boundary must be a JSON object.",
|
|
169
|
+
});
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
const boundary = candidate;
|
|
173
|
+
const boundaryId = nonEmpty(boundary.id) ? boundary.id : undefined;
|
|
174
|
+
if (boundaryId !== undefined && ids.has(boundaryId)) {
|
|
175
|
+
findings.push({
|
|
176
|
+
boundary_id: boundaryId,
|
|
177
|
+
kind: "duplicate_boundary",
|
|
178
|
+
detail: `Boundary ${boundaryId} is inventoried more than once.`,
|
|
179
|
+
});
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (boundaryId !== undefined)
|
|
183
|
+
ids.add(boundaryId);
|
|
184
|
+
const evaluated = evaluateBoundaryRecord(boundary, fixtures, now.getTime());
|
|
185
|
+
capturedCount += evaluated.captured;
|
|
186
|
+
waivedCount += evaluated.waived;
|
|
187
|
+
findings.push(...evaluated.findings);
|
|
188
|
+
}
|
|
189
|
+
findings.sort((left, right) => left.boundary_id !== right.boundary_id
|
|
190
|
+
? left.boundary_id.localeCompare(right.boundary_id)
|
|
191
|
+
: left.kind.localeCompare(right.kind));
|
|
192
|
+
return {
|
|
193
|
+
ok: findings.length === 0,
|
|
194
|
+
boundary_count: registry.boundaries.length,
|
|
195
|
+
captured_count: capturedCount,
|
|
196
|
+
waived_count: waivedCount,
|
|
197
|
+
findings,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
//# sourceMappingURL=boundary-fixtures.js.map
|
|
201
|
+
//# debugId=3edb1f4f-d26e-51a8-b62a-caffdd3ac44b
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module sdk/defect-recurrence-signals
|
|
3
|
+
*
|
|
4
|
+
* Keeps the recurrence index's PM-item projection shared with transport caches
|
|
5
|
+
* without exposing internal full-record serialization as an invalidation rule.
|
|
6
|
+
*/
|
|
7
|
+
import type { AssuranceItemRecord } from "./assurance.js";
|
|
8
|
+
import type { DefectChangeRiskInput } from "./defect-recurrence.js";
|
|
9
|
+
/** Project one PM item to exactly the signals consumed by recurrence indexing. */
|
|
10
|
+
export declare function defectRecurrenceItemSignals(item: AssuranceItemRecord): DefectChangeRiskInput;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Project one PM item to exactly the signals consumed by recurrence indexing. */
|
|
2
|
+
|
|
3
|
+
!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]="e78c3363-1741-52b9-8bd5-729e3ccada50")}catch(e){}}();
|
|
4
|
+
export function defectRecurrenceItemSignals(item) {
|
|
5
|
+
return {
|
|
6
|
+
files: (Array.isArray(item.files) ? item.files : []).flatMap((entry) => {
|
|
7
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry))
|
|
8
|
+
return [];
|
|
9
|
+
const value = entry.path;
|
|
10
|
+
return typeof value === "string" ? [value] : [];
|
|
11
|
+
}),
|
|
12
|
+
package_names: Array.isArray(item.package_names)
|
|
13
|
+
? item.package_names.filter((value) => typeof value === "string")
|
|
14
|
+
: [],
|
|
15
|
+
item_ids: [item.id],
|
|
16
|
+
tags: Array.isArray(item.tags)
|
|
17
|
+
? item.tags.filter((value) => typeof value === "string")
|
|
18
|
+
: [],
|
|
19
|
+
error_codes: Array.isArray(item.error_codes)
|
|
20
|
+
? item.error_codes.filter((value) => typeof value === "string")
|
|
21
|
+
: [],
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=defect-recurrence-signals.js.map
|
|
25
|
+
//# debugId=e78c3363-1741-52b9-8bd5-729e3ccada50
|