@unbrained/pm-cli 2026.9.5 → 2026.9.7
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 +39 -3
- package/dist/cli/main.js +10 -8
- package/dist/cli/register-operations.js +13 -8
- package/dist/cli-bundle/bundle-manifest.json +150 -158
- package/dist/cli-bundle/chunks/chunk-AZDFJGH6.js +9 -0
- package/dist/cli-bundle/chunks/{chunk-WWAEGW4V.js → chunk-BAX2BYUW.js} +66 -66
- package/dist/cli-bundle/chunks/{chunk-IBYREGSP.js → chunk-CKOL5U7J.js} +51 -51
- package/dist/cli-bundle/chunks/{chunk-ZRAQS3UA.js → chunk-JT6XF6GE.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-HUAQITVW.js → chunk-JX4DH4WC.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-QPZHQY2O.js → chunk-NPN5I67J.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-66VPKFP2.js → chunk-OF76PSDQ.js} +2 -2
- package/dist/cli-bundle/chunks/chunk-QUWFABDM.js +232 -0
- package/dist/cli-bundle/chunks/{chunk-3HBDTJRK.js → chunk-RWWBOWJ5.js} +2 -2
- package/dist/cli-bundle/chunks/chunk-WGAOYSYH.js +40 -0
- package/dist/cli-bundle/chunks/{chunk-SF6SM6U5.js → chunk-WI2AYPA7.js} +2 -2
- package/dist/cli-bundle/chunks/{register-list-query-AYKGODWT.js → register-list-query-5JL52D33.js} +2 -2
- package/dist/cli-bundle/chunks/{register-mutation-YZYG53SQ.js → register-mutation-ZPG7KMVP.js} +2 -2
- package/dist/cli-bundle/chunks/register-operations-I5W3KMGB.js +2 -0
- package/dist/cli-bundle/chunks/{register-setup-UJCRVGBG.js → register-setup-H4SVAVX7.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-QL66QNWG.js → chunk-3BZEOXLT.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-AL5ON6K6.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-CHC2IDXA.js +6 -0
- package/dist/cli-bundle/focused-chunks/chunk-CK6QFAPZ.js +20 -0
- package/dist/cli-bundle/focused-chunks/{chunk-WOCIPMW7.js → chunk-EZR3GHFE.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-H2WJWK7L.js +56 -0
- package/dist/cli-bundle/focused-chunks/{chunk-3ASU6WGI.js → chunk-LIJIA4TY.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-H5FOGAVD.js → chunk-M6PBRFSH.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-B2TTQGFG.js → chunk-NTPY5GSO.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-P5O7MP5Y.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-PDMW2E36.js +177 -0
- package/dist/cli-bundle/focused-chunks/{chunk-YEF5PQQ2.js → chunk-PEFABF7E.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-KWWCBWFO.js → chunk-QTVCLMSZ.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-4OSHLNUE.js → chunk-QTZRBUYA.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-SGQST3PQ.js +12 -0
- package/dist/cli-bundle/focused-chunks/chunk-UKHASMMP.js +10 -0
- package/dist/cli-bundle/focused-chunks/chunk-ZPEKQHOC.js +2 -0
- package/dist/cli-bundle/main.js +20 -20
- package/dist/cli-bundle/sdk-authoring.js +1 -1
- package/dist/cli-bundle/sdk-contracts.js +2 -2
- package/dist/cli-bundle/sdk-core.js +37 -37
- 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 +42 -42
- 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 +9 -9
- package/dist/core/extensions/module-graph-snapshot.js +5 -4
- package/dist/core/fs/host-environment-errors.d.ts +27 -0
- package/dist/core/fs/host-environment-errors.js +142 -0
- package/dist/core/output/mutation-projection.js +12 -5
- package/dist/core/store/item-metadata-query-index.js +13 -10
- package/dist/core/store/item-metadata-read-work.d.ts +19 -0
- package/dist/core/store/item-metadata-read-work.js +44 -0
- package/dist/core/store/item-store.js +6 -2
- package/dist/core/validate/fix-planning.d.ts +1 -1
- package/dist/core/validate/fix-planning.js +2 -2
- package/dist/core/validate/missing-link-owners.d.ts +1 -1
- package/dist/core/validate/missing-link-owners.js +5 -3
- package/dist/core/validate/stale-file-classification.d.ts +6 -4
- package/dist/core/validate/stale-file-classification.js +20 -5
- package/dist/mcp/server.js +12 -5
- package/dist/sdk/cli-contracts/flag-contracts.d.ts +2 -0
- package/dist/sdk/cli-contracts/flag-contracts.js +7 -5
- package/dist/sdk/cli-contracts/flag-lexicon-contracts.js +4 -3
- package/dist/sdk/cli-contracts/grammar-contracts.js +11 -2
- package/dist/sdk/cli-contracts/runtime-contracts.js +5 -2
- package/dist/sdk/cli-contracts/schemas/assurance-definition.d.ts +254 -5
- package/dist/sdk/cli-contracts/schemas/assurance-definition.js +52 -2
- package/dist/sdk/cli-contracts/tool-parameter-tables.js +18 -6
- package/dist/sdk/cli-contracts/tool-schema.d.ts +2 -2
- package/dist/sdk/cli-contracts/tool-schema.js +17 -5
- package/dist/sdk/context/usage-ledger.d.ts +41 -0
- package/dist/sdk/context/usage-ledger.js +186 -0
- package/dist/sdk/context-usage.d.ts +18 -9
- package/dist/sdk/context-usage.js +37 -112
- package/dist/sdk/dependency-provenance.d.ts +7 -0
- package/dist/sdk/dependency-provenance.js +14 -4
- package/dist/sdk/duplicates.d.ts +2 -0
- package/dist/sdk/duplicates.js +3 -2
- package/dist/sdk/environment/host-environment-errors.d.ts +5 -27
- package/dist/sdk/environment/host-environment-errors.js +4 -137
- package/dist/sdk/extension/install-runtime.js +9 -6
- package/dist/sdk/extension/install-snapshot.js +7 -6
- package/dist/sdk/generated/generated-error-code-catalog-part-1.js +8 -8
- package/dist/sdk/governance/assurance-action.d.ts +3 -2
- package/dist/sdk/governance/assurance-action.js +23 -11
- package/dist/sdk/governance/assurance-runtime.d.ts +2 -0
- package/dist/sdk/governance/assurance-runtime.js +6 -3
- package/dist/sdk/governance/defect-recurrence-coverage.d.ts +93 -0
- package/dist/sdk/governance/defect-recurrence-coverage.js +189 -0
- package/dist/sdk/governance/defect-recurrence-graph.d.ts +33 -0
- package/dist/sdk/governance/defect-recurrence-graph.js +121 -0
- package/dist/sdk/governance/defect-recurrence.d.ts +8 -0
- package/dist/sdk/governance/defect-recurrence.js +118 -17
- package/dist/sdk/governance/health.d.ts +17 -2
- package/dist/sdk/governance/health.js +31 -10
- package/dist/sdk/governance/linked-file-report.d.ts +41 -0
- package/dist/sdk/governance/linked-file-report.js +72 -0
- package/dist/sdk/governance/validate.d.ts +5 -2
- package/dist/sdk/governance/validate.js +21 -37
- package/dist/sdk/governance.d.ts +2 -0
- package/dist/sdk/governance.js +4 -2
- package/dist/sdk/history-repair.d.ts +7 -0
- package/dist/sdk/history-repair.js +48 -13
- package/dist/sdk/lifecycle/close.d.ts +8 -0
- package/dist/sdk/lifecycle/close.js +16 -8
- package/dist/sdk/lifecycle/create.js +9 -12
- package/dist/sdk/lifecycle/update.js +12 -12
- package/dist/sdk/lifecycle-policy.d.ts +3 -0
- package/dist/sdk/lifecycle-policy.js +2 -2
- package/dist/sdk/merge/abandoned-receipts.d.ts +15 -0
- package/dist/sdk/merge/abandoned-receipts.js +47 -0
- package/dist/sdk/merge/driver.js +4 -2
- package/dist/sdk/merge/index.d.ts +1 -0
- package/dist/sdk/merge/index.js +2 -2
- package/dist/sdk/merge/receipt-operation.d.ts +15 -0
- package/dist/sdk/merge/receipt-operation.js +97 -0
- package/dist/sdk/merge/receipt-schema.d.ts +20 -0
- package/dist/sdk/merge/receipt-schema.js +398 -0
- package/dist/sdk/merge/receipts.d.ts +16 -7
- package/dist/sdk/merge/receipts.js +54 -340
- package/dist/sdk/merge/reconcile.d.ts +2 -0
- package/dist/sdk/merge/reconcile.js +11 -4
- package/dist/sdk/public-surface.json +1 -1
- package/dist/sdk/query/duplicate-candidates.d.ts +7 -0
- package/dist/sdk/query/duplicate-candidates.js +71 -0
- package/dist/sdk/query/get.js +7 -7
- package/dist/sdk/query.d.ts +2 -0
- package/dist/sdk/query.js +4 -2
- package/dist/sdk/read-output-session.js +5 -3
- package/dist/sdk/runtime.js +4 -2
- package/dist/sdk/similarity.d.ts +24 -3
- package/dist/sdk/similarity.js +50 -55
- package/docs/AGENT_GUIDE.md +23 -2
- package/docs/BOUNDED_CONTEXT_READS.md +185 -0
- package/docs/CLOSE_EVIDENCE_RECOVERY.md +35 -0
- package/docs/COMMANDS.md +30 -8
- package/docs/CONTEXT_RELEVANCE.md +16 -4
- package/docs/DEFECT_RECURRENCE.md +80 -6
- package/docs/EXTENSIONS.md +18 -19
- package/docs/MERGE_SAFETY.md +50 -0
- package/docs/OUTPUT_PROJECTION_CONTRACTS.md +66 -0
- package/docs/PR_REVIEW_LOOP.md +18 -3
- package/docs/README.md +18 -1
- package/docs/RELEASING.md +7 -2
- package/docs/SDK_LIFECYCLE.md +27 -0
- package/docs/SDK_RUNTIME_BOUNDARIES.md +11 -0
- package/docs/generated/FLAG_LEXICON_BUDGETS.md +1 -1
- package/marketplace.json +2 -2
- package/package.json +6 -3
- 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/README.md +10 -1
- package/packages/pm-governance-audit/extensions/governance-audit/dedupe-audit.ts +50 -65
- package/packages/pm-governance-audit/extensions/governance-audit/index.ts +2 -2
- package/packages/pm-governance-audit/extensions/governance-audit/sdk.ts +1 -0
- 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/dist/cli-bundle/chunks/chunk-LUI4E32I.js +0 -231
- package/dist/cli-bundle/chunks/chunk-SVK5Y64D.js +0 -40
- package/dist/cli-bundle/chunks/chunk-V5GBD5KV.js +0 -9
- package/dist/cli-bundle/chunks/register-operations-KFSCC7QV.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-24IG6ONG.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-3ITLIBKS.js +0 -56
- package/dist/cli-bundle/focused-chunks/chunk-3NQD5YDG.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-65N6JHAN.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-GUBM3RGY.js +0 -20
- package/dist/cli-bundle/focused-chunks/chunk-JX3RCVCX.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-MV752SUC.js +0 -12
- package/dist/cli-bundle/focused-chunks/chunk-PKIPMCFS.js +0 -6
- package/dist/cli-bundle/focused-chunks/chunk-RJMR2JIF.js +0 -9
- package/dist/cli-bundle/focused-chunks/chunk-WIUPWIZR.js +0 -177
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module sdk/merge/receipt-schema
|
|
3
|
+
* Validates historical and current receipt encodings and enforces durable-value privacy policy.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
!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]="1f6aa507-79da-5d11-a580-72141b725947")}catch(e){}}();
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { stableStringify } from "../../core/shared/serialization.js";
|
|
9
|
+
import { isRfc3339DateTime } from "../../core/shared/time.js";
|
|
10
|
+
import { encodeItemScalarDecisionValue, hashItemScalarDecisionValue, isItemScalarMissingValue, } from "./three-way.js";
|
|
11
|
+
import { isMergeReceiptOperation } from "./receipt-operation.js";
|
|
12
|
+
const RECEIPT_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/u;
|
|
13
|
+
const RECEIPT_ITEM_ID_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/u;
|
|
14
|
+
const RECEIPT_FIELD_MAX = 2_048;
|
|
15
|
+
const RECEIPT_VALUE_MAX_DEPTH = 64;
|
|
16
|
+
const RECEIPT_VALUE_MAX_NODES = 100_000;
|
|
17
|
+
const RECEIPT_KEYS = new Set([
|
|
18
|
+
"version",
|
|
19
|
+
"id",
|
|
20
|
+
"item_path",
|
|
21
|
+
"item_id",
|
|
22
|
+
"requested_preference",
|
|
23
|
+
"requested_preference_applied",
|
|
24
|
+
"preferred",
|
|
25
|
+
"conflict_resolution",
|
|
26
|
+
"fields_from_theirs",
|
|
27
|
+
"union_fields",
|
|
28
|
+
"merged_field_hashes",
|
|
29
|
+
"decisions",
|
|
30
|
+
"state",
|
|
31
|
+
"created_at",
|
|
32
|
+
"reconciled_at",
|
|
33
|
+
"value_availability",
|
|
34
|
+
"value_policy",
|
|
35
|
+
"evidence_source",
|
|
36
|
+
"operation",
|
|
37
|
+
"settlement",
|
|
38
|
+
]);
|
|
39
|
+
/** Return whether a receipt id is safe to expose in cleartext diagnostics. */
|
|
40
|
+
export function isSafeReceiptId(value) {
|
|
41
|
+
return RECEIPT_ID_PATTERN.test(value);
|
|
42
|
+
}
|
|
43
|
+
const RECEIPT_DECISION_KEYS = new Set([
|
|
44
|
+
"field",
|
|
45
|
+
"base",
|
|
46
|
+
"ours",
|
|
47
|
+
"theirs",
|
|
48
|
+
"retained",
|
|
49
|
+
"discarded",
|
|
50
|
+
"retained_side",
|
|
51
|
+
"resolution_basis",
|
|
52
|
+
]);
|
|
53
|
+
/** Versioned allowlist controlling which scalar evidence may enter tracked receipt sidecars. */
|
|
54
|
+
export const DURABLE_VALUE_POLICY = "bounded_non_sensitive_scalars_v1";
|
|
55
|
+
const REVIEWABLE_BUILTIN_STATUSES = new Set([
|
|
56
|
+
"open",
|
|
57
|
+
"in_progress",
|
|
58
|
+
"blocked",
|
|
59
|
+
"closed",
|
|
60
|
+
"canceled",
|
|
61
|
+
]);
|
|
62
|
+
const REVIEWABLE_ORDINAL_FIELDS = new Set(["risk", "confidence", "severity"]);
|
|
63
|
+
const REVIEWABLE_ORDINAL_VALUES = new Set([
|
|
64
|
+
"low",
|
|
65
|
+
"medium",
|
|
66
|
+
"high",
|
|
67
|
+
"critical",
|
|
68
|
+
]);
|
|
69
|
+
const REVIEWABLE_DURABLE_VALUES_BY_FIELD = new Map([
|
|
70
|
+
["priority", new Set([0, 1, 2, 3, 4].map(stableStringify))],
|
|
71
|
+
["status", new Set([...REVIEWABLE_BUILTIN_STATUSES].map(stableStringify))],
|
|
72
|
+
...[...REVIEWABLE_ORDINAL_FIELDS].map((field) => [
|
|
73
|
+
field,
|
|
74
|
+
new Set([...REVIEWABLE_ORDINAL_VALUES].map(stableStringify)),
|
|
75
|
+
]),
|
|
76
|
+
]);
|
|
77
|
+
/** Narrow untrusted JSON to a non-array object before schema inspection. */
|
|
78
|
+
function isRecord(value) {
|
|
79
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
80
|
+
}
|
|
81
|
+
/** Refuse receipt extensions outside the versioned schema key set. */
|
|
82
|
+
function hasOnlyKeys(value, allowed) {
|
|
83
|
+
return Object.keys(value).every((key) => allowed.has(key));
|
|
84
|
+
}
|
|
85
|
+
/** Validate bounded dense string collections used in receipt coordinates. */
|
|
86
|
+
function isStringArray(value) {
|
|
87
|
+
return (Array.isArray(value) &&
|
|
88
|
+
value.length <= RECEIPT_FIELD_MAX &&
|
|
89
|
+
value.every((entry) => typeof entry === "string" && entry.length > 0 && entry.length <= 256));
|
|
90
|
+
}
|
|
91
|
+
/** Bound JSON depth and node count before retaining untrusted decision values. */
|
|
92
|
+
function hasBoundedJsonStructure(value) {
|
|
93
|
+
const pending = [
|
|
94
|
+
{ value, depth: 0 },
|
|
95
|
+
];
|
|
96
|
+
let nodes = 0;
|
|
97
|
+
while (pending.length > 0) {
|
|
98
|
+
const current = pending.pop();
|
|
99
|
+
nodes += 1;
|
|
100
|
+
if (nodes > RECEIPT_VALUE_MAX_NODES ||
|
|
101
|
+
current.depth > RECEIPT_VALUE_MAX_DEPTH) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
if (Array.isArray(current.value)) {
|
|
105
|
+
for (const entry of current.value) {
|
|
106
|
+
pending.push({ value: entry, depth: current.depth + 1 });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
else if (isRecord(current.value)) {
|
|
110
|
+
for (const entry of Object.values(current.value)) {
|
|
111
|
+
pending.push({ value: entry, depth: current.depth + 1 });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
/** Validate one clone-local scalar decision and its optional resolution evidence. */
|
|
118
|
+
function isMergeDecision(value) {
|
|
119
|
+
if (!isRecord(value))
|
|
120
|
+
return false;
|
|
121
|
+
const retainedSides = [undefined, "ours", "theirs"];
|
|
122
|
+
const resolutionBases = [
|
|
123
|
+
undefined,
|
|
124
|
+
"requested_preference",
|
|
125
|
+
"document_updated_at",
|
|
126
|
+
"stable_value_tiebreak",
|
|
127
|
+
];
|
|
128
|
+
return [
|
|
129
|
+
hasOnlyKeys(value, RECEIPT_DECISION_KEYS),
|
|
130
|
+
typeof value.field === "string",
|
|
131
|
+
typeof value.field === "string" && value.field.length > 0,
|
|
132
|
+
typeof value.field === "string" && value.field.length <= 256,
|
|
133
|
+
Object.hasOwn(value, "base"),
|
|
134
|
+
Object.hasOwn(value, "ours"),
|
|
135
|
+
Object.hasOwn(value, "theirs"),
|
|
136
|
+
Object.hasOwn(value, "retained"),
|
|
137
|
+
Object.hasOwn(value, "discarded"),
|
|
138
|
+
hasBoundedJsonStructure(value.base),
|
|
139
|
+
hasBoundedJsonStructure(value.ours),
|
|
140
|
+
hasBoundedJsonStructure(value.theirs),
|
|
141
|
+
hasBoundedJsonStructure(value.retained),
|
|
142
|
+
hasBoundedJsonStructure(value.discarded),
|
|
143
|
+
retainedSides.includes(value.retained_side),
|
|
144
|
+
resolutionBases.includes(value.resolution_basis),
|
|
145
|
+
].every(Boolean);
|
|
146
|
+
}
|
|
147
|
+
/** Validate the complete field-to-SHA-256 proof map used during repair. */
|
|
148
|
+
function isMergedFieldHashes(value) {
|
|
149
|
+
if (!isRecord(value))
|
|
150
|
+
return false;
|
|
151
|
+
const entries = Object.entries(value);
|
|
152
|
+
return (entries.length <= RECEIPT_FIELD_MAX &&
|
|
153
|
+
entries.every(([field, hash]) => field.length > 0 &&
|
|
154
|
+
field.length <= 256 &&
|
|
155
|
+
typeof hash === "string" &&
|
|
156
|
+
/^[a-f0-9]{64}$/u.test(hash)));
|
|
157
|
+
}
|
|
158
|
+
/** Require a normalized tracker item path whose basename matches the receipt item. */
|
|
159
|
+
function isSafeReceiptItemPath(value, itemId) {
|
|
160
|
+
if (typeof value !== "string" ||
|
|
161
|
+
value.length === 0 ||
|
|
162
|
+
value.length > 4_096 ||
|
|
163
|
+
value.includes("\0") ||
|
|
164
|
+
value.includes("\\") ||
|
|
165
|
+
path.posix.isAbsolute(value)) {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
const normalized = path.posix.normalize(value);
|
|
169
|
+
return (normalized !== ".." &&
|
|
170
|
+
!normalized.startsWith("../") &&
|
|
171
|
+
path.posix.basename(normalized, path.posix.extname(normalized)) === itemId);
|
|
172
|
+
}
|
|
173
|
+
/** Validate receipt arrays and optional merged-field proof coverage. */
|
|
174
|
+
function hasValidReceiptCollections(value) {
|
|
175
|
+
return !isStringArray(value.fields_from_theirs) ||
|
|
176
|
+
!isStringArray(value.union_fields) ||
|
|
177
|
+
!Array.isArray(value.decisions) ||
|
|
178
|
+
value.decisions.length > RECEIPT_FIELD_MAX ||
|
|
179
|
+
!value.decisions.every(isMergeDecision)
|
|
180
|
+
? false
|
|
181
|
+
: value.merged_field_hashes === undefined ||
|
|
182
|
+
isMergedFieldHashes(value.merged_field_hashes);
|
|
183
|
+
}
|
|
184
|
+
/** Validate every closed-domain lifecycle and merge-policy field. */
|
|
185
|
+
function hasValidReceiptEnums(value) {
|
|
186
|
+
const preferences = [undefined, "ours", "theirs"];
|
|
187
|
+
const resolutions = [
|
|
188
|
+
undefined,
|
|
189
|
+
"preferred_side",
|
|
190
|
+
"stable_value_order",
|
|
191
|
+
"latest_document_update",
|
|
192
|
+
];
|
|
193
|
+
const availabilities = [
|
|
194
|
+
undefined,
|
|
195
|
+
"clone_local",
|
|
196
|
+
"hash_only",
|
|
197
|
+
"bounded_inline",
|
|
198
|
+
"mixed",
|
|
199
|
+
];
|
|
200
|
+
const sources = [undefined, "clone_local", "durable"];
|
|
201
|
+
return (preferences.includes(value.requested_preference) &&
|
|
202
|
+
preferences.includes(value.preferred) &&
|
|
203
|
+
(value.requested_preference_applied === undefined ||
|
|
204
|
+
typeof value.requested_preference_applied === "boolean") &&
|
|
205
|
+
resolutions.includes(value.conflict_resolution) &&
|
|
206
|
+
(value.state === "pending" || value.state === "reconciled") &&
|
|
207
|
+
availabilities.includes(value.value_availability) &&
|
|
208
|
+
(value.value_policy === undefined ||
|
|
209
|
+
value.value_policy === DURABLE_VALUE_POLICY) &&
|
|
210
|
+
sources.includes(value.evidence_source));
|
|
211
|
+
}
|
|
212
|
+
/** Validate creation and optional settlement timestamps as RFC 3339 values. */
|
|
213
|
+
function hasValidReceiptTimestamps(value) {
|
|
214
|
+
return (typeof value.created_at === "string" &&
|
|
215
|
+
isRfc3339DateTime(value.created_at) &&
|
|
216
|
+
(value.reconciled_at === undefined ||
|
|
217
|
+
(typeof value.reconciled_at === "string" &&
|
|
218
|
+
isRfc3339DateTime(value.reconciled_at))));
|
|
219
|
+
}
|
|
220
|
+
/** Validate one hash-only or policy-bounded durable decision value. */
|
|
221
|
+
function hasDurableDecisionValue(field, value) {
|
|
222
|
+
if (!isRecord(value) || isPrehashedValue(value) === undefined)
|
|
223
|
+
return false;
|
|
224
|
+
const keys = Object.keys(value);
|
|
225
|
+
if (!keys.every((key) => key === "pm_value_hash" || key === "pm_value")) {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
if (!Object.hasOwn(value, "pm_value"))
|
|
229
|
+
return keys.length === 1;
|
|
230
|
+
const preview = value.pm_value;
|
|
231
|
+
const allowed = preview === null ||
|
|
232
|
+
REVIEWABLE_DURABLE_VALUES_BY_FIELD.get(field)?.has(stableStringify(preview)) === true;
|
|
233
|
+
return (allowed && hashItemScalarDecisionValue(preview) === value.pm_value_hash);
|
|
234
|
+
}
|
|
235
|
+
/** Validate durable decision redaction and the declared availability summary. */
|
|
236
|
+
function hasValidDurableDecisions(receipt) {
|
|
237
|
+
if (!receipt.decisions.every((decision) => decision.base === null &&
|
|
238
|
+
decision.ours === null &&
|
|
239
|
+
decision.theirs === null &&
|
|
240
|
+
hasDurableDecisionValue(decision.field, decision.retained) &&
|
|
241
|
+
hasDurableDecisionValue(decision.field, decision.discarded))) {
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
const inlineValueCount = receipt.decisions.reduce((count, decision) => count +
|
|
245
|
+
(isRecord(decision.retained) &&
|
|
246
|
+
Object.hasOwn(decision.retained, "pm_value")
|
|
247
|
+
? 1
|
|
248
|
+
: 0) +
|
|
249
|
+
(isRecord(decision.discarded) &&
|
|
250
|
+
Object.hasOwn(decision.discarded, "pm_value")
|
|
251
|
+
? 1
|
|
252
|
+
: 0), 0);
|
|
253
|
+
const expectedAvailability = inlineValueCount === 0
|
|
254
|
+
? "hash_only"
|
|
255
|
+
: inlineValueCount === receipt.decisions.length * 2
|
|
256
|
+
? "bounded_inline"
|
|
257
|
+
: "mixed";
|
|
258
|
+
return (receipt.value_availability === expectedAvailability &&
|
|
259
|
+
(inlineValueCount === 0 || receipt.value_policy === DURABLE_VALUE_POLICY));
|
|
260
|
+
}
|
|
261
|
+
/** Require restored-origin settlements to retain the immutable operation they disposition. */
|
|
262
|
+
function hasValidReceiptOperation(value) {
|
|
263
|
+
if (value.operation !== undefined &&
|
|
264
|
+
!isMergeReceiptOperation(value.operation))
|
|
265
|
+
return false;
|
|
266
|
+
return (value.settlement === undefined ||
|
|
267
|
+
(value.settlement === "original_git_state_restored" &&
|
|
268
|
+
value.state === "reconciled" &&
|
|
269
|
+
value.operation !== undefined));
|
|
270
|
+
}
|
|
271
|
+
/** Validate schema identity before inspecting values or lifecycle policy. */
|
|
272
|
+
function receiptIdentityValidationError(value) {
|
|
273
|
+
for (const key of [
|
|
274
|
+
"version",
|
|
275
|
+
"id",
|
|
276
|
+
"item_path",
|
|
277
|
+
"item_id",
|
|
278
|
+
"fields_from_theirs",
|
|
279
|
+
"union_fields",
|
|
280
|
+
"decisions",
|
|
281
|
+
"state",
|
|
282
|
+
"created_at",
|
|
283
|
+
]) {
|
|
284
|
+
if (!Object.hasOwn(value, key))
|
|
285
|
+
return "required_fields";
|
|
286
|
+
}
|
|
287
|
+
if (value.version !== 1 || !hasOnlyKeys(value, RECEIPT_KEYS)) {
|
|
288
|
+
return "required_fields";
|
|
289
|
+
}
|
|
290
|
+
if (typeof value.id !== "string" || !isSafeReceiptId(value.id)) {
|
|
291
|
+
return "receipt_id";
|
|
292
|
+
}
|
|
293
|
+
if (typeof value.item_id !== "string" ||
|
|
294
|
+
!RECEIPT_ITEM_ID_PATTERN.test(value.item_id)) {
|
|
295
|
+
return "item_id";
|
|
296
|
+
}
|
|
297
|
+
if (!isSafeReceiptItemPath(value.item_path, value.item_id)) {
|
|
298
|
+
return "item_path";
|
|
299
|
+
}
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
/** Return the narrowest validation class for untrusted receipt evidence. */
|
|
303
|
+
export function receiptValidationError(value, evidenceSource) {
|
|
304
|
+
if (!isRecord(value))
|
|
305
|
+
return "required_fields";
|
|
306
|
+
const identityError = receiptIdentityValidationError(value);
|
|
307
|
+
if (identityError !== null)
|
|
308
|
+
return identityError;
|
|
309
|
+
if (!hasValidReceiptOperation(value))
|
|
310
|
+
return "operation";
|
|
311
|
+
if (!hasValidReceiptCollections(value))
|
|
312
|
+
return "collections";
|
|
313
|
+
if (!hasValidReceiptEnums(value))
|
|
314
|
+
return "enums";
|
|
315
|
+
if (!hasValidReceiptTimestamps(value))
|
|
316
|
+
return "timestamps";
|
|
317
|
+
if (evidenceSource === "durable" &&
|
|
318
|
+
!hasValidDurableDecisions(value)) {
|
|
319
|
+
return "durable_decisions";
|
|
320
|
+
}
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
/** Restore omitted non-selected slots from identified v1 clone-local writer contracts; never infer a missing retained or discarded value. */
|
|
324
|
+
export function normalizeLegacyReceipt(value) {
|
|
325
|
+
if (!isRecord(value) ||
|
|
326
|
+
value.version !== 1 ||
|
|
327
|
+
value.value_policy !== undefined ||
|
|
328
|
+
!Array.isArray(value.decisions))
|
|
329
|
+
return value;
|
|
330
|
+
const preferredEra = (value.preferred === "ours" || value.preferred === "theirs") &&
|
|
331
|
+
value.requested_preference === undefined;
|
|
332
|
+
const requestedPreferenceEra = (value.requested_preference === "ours" ||
|
|
333
|
+
value.requested_preference === "theirs") &&
|
|
334
|
+
value.value_availability === "clone_local";
|
|
335
|
+
if (!preferredEra && !requestedPreferenceEra)
|
|
336
|
+
return value;
|
|
337
|
+
return {
|
|
338
|
+
...value,
|
|
339
|
+
decisions: value.decisions.map((decision) => {
|
|
340
|
+
if (!isRecord(decision) ||
|
|
341
|
+
!Object.hasOwn(decision, "retained") ||
|
|
342
|
+
!Object.hasOwn(decision, "discarded"))
|
|
343
|
+
return decision;
|
|
344
|
+
return {
|
|
345
|
+
base: encodeItemScalarDecisionValue(undefined),
|
|
346
|
+
ours: encodeItemScalarDecisionValue(undefined),
|
|
347
|
+
theirs: encodeItemScalarDecisionValue(undefined),
|
|
348
|
+
...decision,
|
|
349
|
+
};
|
|
350
|
+
}),
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
/** Identify the rejected collection without exposing its values. */
|
|
354
|
+
export function receiptCollectionValidationPath(value) {
|
|
355
|
+
if (!isRecord(value))
|
|
356
|
+
return undefined;
|
|
357
|
+
if (!isStringArray(value.fields_from_theirs))
|
|
358
|
+
return "fields_from_theirs";
|
|
359
|
+
if (!isStringArray(value.union_fields))
|
|
360
|
+
return "union_fields";
|
|
361
|
+
if (!Array.isArray(value.decisions) ||
|
|
362
|
+
value.decisions.length > RECEIPT_FIELD_MAX)
|
|
363
|
+
return "decisions";
|
|
364
|
+
const index = value.decisions.findIndex((decision) => !isMergeDecision(decision));
|
|
365
|
+
if (index >= 0)
|
|
366
|
+
return `decisions[${index}]`;
|
|
367
|
+
return "merged_field_hashes";
|
|
368
|
+
}
|
|
369
|
+
/** Validate a complete receipt against its clone-local or durable evidence policy. */
|
|
370
|
+
export function isMergeDecisionReceipt(value, evidenceSource) {
|
|
371
|
+
return receiptValidationError(value, evidenceSource) === null;
|
|
372
|
+
}
|
|
373
|
+
/** Extract a structurally valid precomputed SHA-256 decision hash. */
|
|
374
|
+
export function isPrehashedValue(value) {
|
|
375
|
+
if (typeof value !== "object" ||
|
|
376
|
+
value === null ||
|
|
377
|
+
Array.isArray(value) ||
|
|
378
|
+
!("pm_value_hash" in value)) {
|
|
379
|
+
return undefined;
|
|
380
|
+
}
|
|
381
|
+
const hash = value.pm_value_hash;
|
|
382
|
+
return typeof hash === "string" && /^[a-f0-9]{64}$/u.test(hash)
|
|
383
|
+
? hash
|
|
384
|
+
: undefined;
|
|
385
|
+
}
|
|
386
|
+
/** Convert a raw scalar into hashed evidence with an allowlisted preview. */
|
|
387
|
+
export function durableValueEvidence(field, value) {
|
|
388
|
+
const missing = isItemScalarMissingValue(value);
|
|
389
|
+
const pmValueHash = hashItemScalarDecisionValue(missing ? undefined : value);
|
|
390
|
+
const reviewable = !missing &&
|
|
391
|
+
(value === null ||
|
|
392
|
+
REVIEWABLE_DURABLE_VALUES_BY_FIELD.get(field)?.has(stableStringify(value)) === true);
|
|
393
|
+
return reviewable
|
|
394
|
+
? { pm_value_hash: pmValueHash, pm_value: value }
|
|
395
|
+
: { pm_value_hash: pmValueHash };
|
|
396
|
+
}
|
|
397
|
+
//# sourceMappingURL=receipt-schema.js.map
|
|
398
|
+
//# debugId=1f6aa507-79da-5d11-a580-72141b725947
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { Stats } from "node:fs";
|
|
2
2
|
import { type ItemMergeConflictDecision, type ItemScalarConflictResolution, type MergePreferredSide } from "./three-way.js";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
import type { MergeReceiptOperation } from "./receipt-operation.js";
|
|
4
|
+
import { DURABLE_VALUE_POLICY } from "./receipt-schema.js";
|
|
5
|
+
export { isSafeReceiptId } from "./receipt-schema.js";
|
|
6
6
|
/** One clone-local merge receipt with recoverable branch values. */
|
|
7
7
|
export interface MergeDecisionReceipt {
|
|
8
8
|
/** Receipt schema version. */
|
|
@@ -29,7 +29,11 @@ export interface MergeDecisionReceipt {
|
|
|
29
29
|
merged_field_hashes?: Record<string, string>;
|
|
30
30
|
/** Full recoverable scalar decisions, retained only in the clone. */
|
|
31
31
|
decisions: ItemMergeConflictDecision[];
|
|
32
|
-
/**
|
|
32
|
+
/** Immutable original Git coordinates captured while the driver runs during a rebase. */
|
|
33
|
+
operation?: MergeReceiptOperation;
|
|
34
|
+
/** Explicit audited disposition when the original Git state was restored instead of merging. */
|
|
35
|
+
settlement?: "original_git_state_restored";
|
|
36
|
+
/** Whether a reconciliation history event settled this receipt. */
|
|
33
37
|
state: "pending" | "reconciled";
|
|
34
38
|
/** Receipt creation timestamp. */
|
|
35
39
|
created_at: string;
|
|
@@ -121,7 +125,9 @@ export interface MergeReceiptInvalidEvidence {
|
|
|
121
125
|
/** Stable failure class suitable for remediation routing and graph analytics. */
|
|
122
126
|
reason: "directory_unreadable" | "candidate_not_bounded_regular_file" | "candidate_unreadable" | "candidate_invalid_json" | "schema_or_identity_invalid" | "schema_invalid" | "identity_invalid" | "copy_provenance_mismatch";
|
|
123
127
|
/** Exact validation boundary when schema or identity evidence is rejected. */
|
|
124
|
-
validation_error?: "required_fields" | "receipt_id" | "item_id" | "item_path" | "filename" | "collections" | "enums" | "timestamps" | "durable_decisions";
|
|
128
|
+
validation_error?: "required_fields" | "receipt_id" | "item_id" | "item_path" | "filename" | "collections" | "enums" | "timestamps" | "durable_decisions" | "operation";
|
|
129
|
+
/** Exact schema coordinate, excluding rejected values and user-controlled field names. */
|
|
130
|
+
validation_path?: string;
|
|
125
131
|
/** Receipt identity when the bounded filename itself is a valid receipt id. */
|
|
126
132
|
receipt_id?: string;
|
|
127
133
|
/** SHA-256 locator for an unsafe or malformed candidate filename. */
|
|
@@ -133,11 +139,13 @@ declare function prepareReceiptSettlement(params: {
|
|
|
133
139
|
receiptId: string;
|
|
134
140
|
evidenceSource: "clone_local" | "durable";
|
|
135
141
|
reconciledAt: string;
|
|
142
|
+
settlement?: "original_git_state_restored";
|
|
136
143
|
readReceipt?: (receiptPath: string) => Promise<string | null>;
|
|
137
144
|
}): Promise<{
|
|
138
145
|
path: string;
|
|
139
146
|
content: string;
|
|
140
|
-
|
|
147
|
+
fingerprints: string[];
|
|
148
|
+
modern: boolean;
|
|
141
149
|
} | null>;
|
|
142
150
|
/** Convert a raw receipt into privacy-safe history context. */
|
|
143
151
|
export declare function summarizeMergeReceipt(receipt: MergeDecisionReceipt): MergeDecisionReceiptSummary;
|
|
@@ -151,6 +159,7 @@ export declare function writeMergeReceipt(params: {
|
|
|
151
159
|
unionFields: string[];
|
|
152
160
|
mergedFieldHashes?: Record<string, string>;
|
|
153
161
|
decisions: ItemMergeConflictDecision[];
|
|
162
|
+
operation?: MergeReceiptOperation;
|
|
154
163
|
}): Promise<MergeDecisionReceipt | null>;
|
|
155
164
|
/** Distinguish a missing optional receipt store from an unreadable one. */
|
|
156
165
|
declare function receiptDirectoryFailureMeansAbsent(directory: string, error: unknown, inspectAncestor?: (ancestor: string) => Promise<Pick<Stats, "isDirectory">>): Promise<boolean>;
|
|
@@ -179,6 +188,7 @@ export declare function partitionMergeReceipts(receipts: MergeDecisionReceipt[])
|
|
|
179
188
|
/** Mark a receipt as represented by a committed merge history event. */
|
|
180
189
|
export declare function markMergeReceiptReconciled(cwd: string, receipt: MergeDecisionReceipt, options?: {
|
|
181
190
|
requireExisting?: boolean;
|
|
191
|
+
settlement?: "original_git_state_restored";
|
|
182
192
|
}): Promise<void>;
|
|
183
193
|
/** Report pending or historical clone-local merge decisions. */
|
|
184
194
|
export declare function runMergeReceiptReport(options: {
|
|
@@ -199,4 +209,3 @@ export declare const _testOnlyMergeReceipts: {
|
|
|
199
209
|
prepareReceiptSettlement: typeof prepareReceiptSettlement;
|
|
200
210
|
receiptDirectoryFailureMeansAbsent: typeof receiptDirectoryFailureMeansAbsent;
|
|
201
211
|
};
|
|
202
|
-
export {};
|