pm-csv 2026.10.22 → 2026.10.24
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/CHANGELOG.md +37 -0
- package/README.md +63 -0
- package/dist/atomic-update.d.ts +3 -2
- package/dist/atomic-update.d.ts.map +1 -1
- package/dist/atomic-update.js +10 -4
- package/dist/atomic-update.js.map +1 -1
- package/dist/edit-refusal.d.ts +21 -0
- package/dist/edit-refusal.d.ts.map +1 -0
- package/dist/edit-refusal.js +21 -0
- package/dist/edit-refusal.js.map +1 -0
- package/dist/index.d.ts +19 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +141 -9
- package/dist/index.js.map +1 -1
- package/dist/lease-guard.d.ts +14 -0
- package/dist/lease-guard.d.ts.map +1 -0
- package/dist/lease-guard.js +25 -0
- package/dist/lease-guard.js.map +1 -0
- package/dist/lineage.d.ts +72 -0
- package/dist/lineage.d.ts.map +1 -0
- package/dist/lineage.js +137 -0
- package/dist/lineage.js.map +1 -0
- package/dist/roundtrip.d.ts +54 -0
- package/dist/roundtrip.d.ts.map +1 -0
- package/dist/roundtrip.js +255 -0
- package/dist/roundtrip.js.map +1 -0
- package/dist/table-schema.d.ts +80 -0
- package/dist/table-schema.d.ts.map +1 -0
- package/dist/table-schema.js +260 -0
- package/dist/table-schema.js.map +1 -0
- package/docs/compatibility-2026.10.20.md +76 -0
- package/docs/row-lineage-validation.md +49 -0
- package/docs/row-lineage.md +87 -0
- package/docs/spreadsheet-roundtrip.md +146 -0
- package/docs/spreadsheet-validation.md +47 -0
- package/docs/table-schema.md +123 -0
- package/manifest.json +1 -1
- package/package.json +10 -6
package/dist/lineage.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { create, locateItem, normalizeItemId, PmCliError, readSettings } from "@unbrained/pm-cli/sdk";
|
|
5
|
+
import { serializeCSV } from "./index.js";
|
|
6
|
+
import { readUpdateDocument } from "./atomic-update.js";
|
|
7
|
+
/** Deliberately bounded import/diagnostic field surface; other columns never leave the input. */
|
|
8
|
+
export const LINEAGE_FIELDS = ["title", "body", "type", "status", "priority", "tags", "assignee", "sprint", "release"];
|
|
9
|
+
/** Stable actionable refusals never contain source values or filesystem paths. */
|
|
10
|
+
export class LineageRefusal extends PmCliError {
|
|
11
|
+
/** Stable machine-readable category. */
|
|
12
|
+
code;
|
|
13
|
+
/** Action required before retry. */
|
|
14
|
+
remedy;
|
|
15
|
+
constructor(code, remedy) {
|
|
16
|
+
super(`${code}: ${remedy}`, 2, { code, required: remedy, nextSteps: [remedy] });
|
|
17
|
+
this.code = code;
|
|
18
|
+
this.remedy = remedy;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/** Versioned SHA-256 over JSON values; artifacts specify this canonicalization. */
|
|
22
|
+
export function lineageDigest(value) {
|
|
23
|
+
return createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
|
24
|
+
}
|
|
25
|
+
/** Refuse an invalid table or disclosure policy before any mutation occurs. */
|
|
26
|
+
function planMapping(table, options) {
|
|
27
|
+
const headers = table[0];
|
|
28
|
+
const fields = options.columns;
|
|
29
|
+
if (!headers?.length || table.length > 1001 || !fields.includes("title") || new Set(fields).size !== fields.length
|
|
30
|
+
|| fields.some(field => !LINEAGE_FIELDS.includes(field)) || new Set(headers).size !== headers.length) {
|
|
31
|
+
throw new LineageRefusal("csv_lineage_invalid", "Provide a header and at most 1000 rows; select distinct supported --columns including title and remove duplicate headers.");
|
|
32
|
+
}
|
|
33
|
+
const fieldMap = options.fieldMap ?? {};
|
|
34
|
+
if (Object.entries(fieldMap).some(([header, field]) => !headers.includes(header) || !fields.includes(field))) {
|
|
35
|
+
throw new LineageRefusal("csv_lineage_invalid", "Map only existing source headers to explicitly allowed columns.");
|
|
36
|
+
}
|
|
37
|
+
const mapping = headers.flatMap((header, index) => {
|
|
38
|
+
const field = Object.hasOwn(fieldMap, header) ? fieldMap[header] : header;
|
|
39
|
+
return fields.includes(field) ? [{ column: index + 1, field: field, header }] : [];
|
|
40
|
+
});
|
|
41
|
+
if (mapping.length !== fields.length || new Set(mapping.map(cell => cell.field)).size !== fields.length) {
|
|
42
|
+
throw new LineageRefusal("csv_lineage_invalid", "Supply exactly one source column for every allowed field; resolve missing or ambiguous mappings.");
|
|
43
|
+
}
|
|
44
|
+
return mapping;
|
|
45
|
+
}
|
|
46
|
+
/** Recover a committed native create by its stable key, including a crash before artifact export. */
|
|
47
|
+
async function existingRow(pmRoot, id, marker) {
|
|
48
|
+
if (!await locateItem(pmRoot, id))
|
|
49
|
+
return undefined;
|
|
50
|
+
const document = await readUpdateDocument(pmRoot, id);
|
|
51
|
+
return document.metadata.tags?.includes(marker) ? "replayed" : "identity_conflict";
|
|
52
|
+
}
|
|
53
|
+
/** Import bounded rows with native SDK validation/history/locking and stable explicit IDs.
|
|
54
|
+
* Rejected artifacts contain only allowed columns plus a stable retry key. Replaying
|
|
55
|
+
* corrected rejects cannot apply successful source rows because those rows are absent.
|
|
56
|
+
*/
|
|
57
|
+
export async function importLineage(pmRoot, table, options) {
|
|
58
|
+
const mapping = planMapping(table, options);
|
|
59
|
+
const sourceDigest = lineageDigest(table);
|
|
60
|
+
const mappingRevision = lineageDigest([1, mapping]);
|
|
61
|
+
const keyColumn = table[0].indexOf("_csv_key");
|
|
62
|
+
const keys = table.slice(1).map((row, index) => keyColumn < 0 ? lineageDigest([sourceDigest, index + 2]) : row[keyColumn]);
|
|
63
|
+
if (keys.some(key => !/^[a-f0-9]{64}$/.test(key)) || new Set(keys).size !== keys.length) {
|
|
64
|
+
throw new LineageRefusal("csv_lineage_invalid", "Keep distinct unchanged _csv_key values in the rejected CSV; use the original artifact if keys were lost.");
|
|
65
|
+
}
|
|
66
|
+
const settings = await readSettings(pmRoot);
|
|
67
|
+
const rows = [];
|
|
68
|
+
const rejected = [["_csv_key", ...mapping.map(cell => cell.field)]];
|
|
69
|
+
for (const [index, row] of table.slice(1).entries()) {
|
|
70
|
+
const key = keys[index];
|
|
71
|
+
const values = Object.fromEntries([...mapping].sort((left, right) => left.field.localeCompare(right.field)).map(cell => [cell.field, (row[cell.column - 1] ?? "").trim()]));
|
|
72
|
+
const marker = `csv-lineage:${lineageDigest([key, values])}`;
|
|
73
|
+
const id = normalizeItemId(`csv-${key}`, settings.id_prefix);
|
|
74
|
+
const record = { row: index + 2, key, outcome: "rejected", cells: [] };
|
|
75
|
+
rows.push(record);
|
|
76
|
+
if (row.length !== table[0].length || !values.title || (values.priority && !/^[0-4]$/.test(values.priority))) {
|
|
77
|
+
record.code = "invalid_row";
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
let outcome = await existingRow(pmRoot, id, marker);
|
|
81
|
+
if (!outcome) {
|
|
82
|
+
try {
|
|
83
|
+
await create({ ...Object.fromEntries(Object.entries(values).filter(([, value]) => value !== "")), id, tags: [values.tags, marker].filter(Boolean).join(",") }, { pmRoot, author: options.author ?? "pm-csv" });
|
|
84
|
+
record.outcome = "imported";
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
// A racing writer may have won the same native create; inspect durable state.
|
|
88
|
+
outcome = await existingRow(pmRoot, id, marker);
|
|
89
|
+
record.code = outcome ? undefined : "create_refused";
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (outcome === "identity_conflict")
|
|
93
|
+
record.code = outcome;
|
|
94
|
+
if (outcome === "replayed")
|
|
95
|
+
record.outcome = outcome;
|
|
96
|
+
if (record.outcome !== "rejected") {
|
|
97
|
+
record.item = id;
|
|
98
|
+
const document = await readUpdateDocument(pmRoot, id);
|
|
99
|
+
record.cells = mapping.map(cell => ({ column: cell.column, field: cell.field,
|
|
100
|
+
sourceDigest: lineageDigest(row[cell.column - 1]),
|
|
101
|
+
valueDigest: lineageDigest(cell.field === "body" ? document.body : document.metadata[cell.field] ?? null),
|
|
102
|
+
}));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (record.outcome === "rejected")
|
|
106
|
+
rejected.push([key, ...mapping.map(cell => row[cell.column - 1] ?? "")]);
|
|
107
|
+
}
|
|
108
|
+
const base = { version: 1, sourceDigest, mappingRevision, mapping, rows, remedies: {
|
|
109
|
+
invalid_row: "Correct the title, priority (0-4), or row width in rejected.csv, then retry that file.",
|
|
110
|
+
create_refused: "Check the tracker schema, required type fields, terminal-status reason, permissions and pm health; correct rejected.csv and retry.",
|
|
111
|
+
identity_conflict: "This key already belongs to different imported values. Restore the unchanged rejected artifact or use pm update for intentional changes.",
|
|
112
|
+
}, rejectedDigest: lineageDigest(rejected) };
|
|
113
|
+
const manifest = { ...base, digest: lineageDigest(base) };
|
|
114
|
+
return { manifest, rejected, receipt: { rows: rows.length,
|
|
115
|
+
imported: rows.filter(row => row.outcome === "imported").length,
|
|
116
|
+
replayed: rows.filter(row => row.outcome === "replayed").length,
|
|
117
|
+
rejected: rejected.length - 1, manifest: manifest.digest,
|
|
118
|
+
} };
|
|
119
|
+
}
|
|
120
|
+
/** Publish only new artifacts; exact-byte checksums complement parsed-content digests. */
|
|
121
|
+
export function writeLineageBundle(output, bundle) {
|
|
122
|
+
const rejectedCsv = serializeCSV(bundle.rejected, ",") + "\n";
|
|
123
|
+
try {
|
|
124
|
+
const manifest = JSON.stringify(bundle.manifest);
|
|
125
|
+
const checksums = {
|
|
126
|
+
"rejected.csv": createHash("sha256").update(rejectedCsv).digest("hex"),
|
|
127
|
+
"manifest.json": createHash("sha256").update(manifest).digest("hex"),
|
|
128
|
+
};
|
|
129
|
+
writeFileSync(join(output, "rejected.csv"), rejectedCsv, { flag: "wx", mode: 0o600 });
|
|
130
|
+
writeFileSync(join(output, "manifest.json"), manifest, { flag: "wx", mode: 0o600 });
|
|
131
|
+
writeFileSync(join(output, "checksums.json"), JSON.stringify(checksums), { flag: "wx", mode: 0o600 });
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
throw new LineageRefusal("csv_bundle_io", "Keep partial artifacts; rerun the unchanged input into a new directory to recover committed rows by stable identity.");
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=lineage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lineage.js","sourceRoot":"","sources":["../lineage.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { CommitWorkspaceTransactionOptions } from "@unbrained/pm-cli/sdk";
|
|
2
|
+
export { EditRefusal } from "./edit-refusal.ts";
|
|
3
|
+
export type { EditConflict, EditRefusalCode } from "./edit-refusal.ts";
|
|
4
|
+
/** Versioned, deliberately scalar spreadsheet patch surface; status is read-only. */
|
|
5
|
+
export declare const EDIT_FIELDS: readonly ["title", "body", "priority", "assignee", "sprint", "release"];
|
|
6
|
+
/** Editable cell names accepted by the session protocol. */
|
|
7
|
+
export type EditField = typeof EDIT_FIELDS[number];
|
|
8
|
+
/** Baseline for one stable item key. Values are exact spreadsheet strings. */
|
|
9
|
+
export interface EditSessionRow {
|
|
10
|
+
id: string;
|
|
11
|
+
revision: string;
|
|
12
|
+
itemDigest: string;
|
|
13
|
+
status: string;
|
|
14
|
+
values: Record<EditField, string>;
|
|
15
|
+
digests: Record<EditField, string>;
|
|
16
|
+
}
|
|
17
|
+
/** Portable immutable baseline; retain this alongside the edited CSV. */
|
|
18
|
+
export interface EditSession {
|
|
19
|
+
version: 1;
|
|
20
|
+
id: string;
|
|
21
|
+
rows: EditSessionRow[];
|
|
22
|
+
digest: string;
|
|
23
|
+
}
|
|
24
|
+
/** Bounded agent receipt for both dry runs and successful application. */
|
|
25
|
+
export interface EditReceipt {
|
|
26
|
+
rows: number;
|
|
27
|
+
changed: number;
|
|
28
|
+
unchanged: number;
|
|
29
|
+
concurrent: number;
|
|
30
|
+
applied: number;
|
|
31
|
+
transaction?: string;
|
|
32
|
+
}
|
|
33
|
+
/** Fixed schema makes altered/missing/duplicate metadata columns fail closed. */
|
|
34
|
+
export declare function editSessionHeader(): string[];
|
|
35
|
+
/** Export explicit stable keys and digests without creating tracker metadata. */
|
|
36
|
+
export declare function exportEditSession(pmRoot: string, ids: readonly string[]): Promise<{
|
|
37
|
+
session: EditSession;
|
|
38
|
+
table: string[][];
|
|
39
|
+
}>;
|
|
40
|
+
/** Validate an untrusted sidecar structurally as well as by its content digest. */
|
|
41
|
+
export declare function parseEditSession(input: unknown): EditSession;
|
|
42
|
+
/**
|
|
43
|
+
* Three-way patch using stable item keys, field-level conflict checks and the existing SDK
|
|
44
|
+
* transaction/CSV compensation. Preflight refusals create no transaction or
|
|
45
|
+
* item/history writes. Field checks run under each native item lease; protection
|
|
46
|
+
* after lease expiry needs host write fencing. Prior rows are compensated on
|
|
47
|
+
* ordinary failure, which explicitly requires reconciliation.
|
|
48
|
+
*/
|
|
49
|
+
export declare function applyEditSession(pmRoot: string, input: unknown, table: string[][], options: {
|
|
50
|
+
author: string;
|
|
51
|
+
dryRun?: boolean;
|
|
52
|
+
onTransition?: CommitWorkspaceTransactionOptions["onTransition"];
|
|
53
|
+
}): Promise<EditReceipt>;
|
|
54
|
+
//# sourceMappingURL=roundtrip.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"roundtrip.d.ts","sourceRoot":"","sources":["../roundtrip.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,iCAAiC,EAA0C,MAAM,uBAAuB,CAAC;AAIvH,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAGvE,qFAAqF;AACrF,eAAO,MAAM,WAAW,YAAI,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,CAAU,CAAC;AACnG,4DAA4D;AAC5D,MAAM,MAAM,SAAS,GAAG,OAAO,WAAW,CAAC,MAAM,CAAC,CAAC;AACnD,8EAA8E;AAC9E,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IAClC,OAAO,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;CACpC;AACD,yEAAyE;AACzE,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,CAAC,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,cAAc,EAAE,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;CAChB;AACD,0EAA0E;AAC1E,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAgDD,iFAAiF;AACjF,wBAAgB,iBAAiB,IAAI,MAAM,EAAE,CAE5C;AAYD,iFAAiF;AACjF,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,WAAW,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,EAAE,CAAA;CAAE,CAAC,CAYpI;AAED,mFAAmF;AACnF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,CAe5D;AA4FD;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,YAAY,CAAC,EAAE,iCAAiC,CAAC,cAAc,CAAC,CAAA;CAAE,GACjK,OAAO,CAAC,WAAW,CAAC,CA6CtB"}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { acquireLock, commitWorkspaceTransaction, getHistoryPath, mutateItem, readHistoryEntries, readSettings, verifyHistoryEntries, } from "@unbrained/pm-cli/sdk";
|
|
3
|
+
import { EditRefusal } from "./edit-refusal.js";
|
|
4
|
+
import { assertEditLease, guardEditMutation } from "./lease-guard.js";
|
|
5
|
+
export { EditRefusal } from "./edit-refusal.js";
|
|
6
|
+
import { captureUpdateCompensation, compensateUpdate, inspectUpdate, readUpdateDocument } from "./atomic-update.js";
|
|
7
|
+
/** Versioned, deliberately scalar spreadsheet patch surface; status is read-only. */
|
|
8
|
+
export const EDIT_FIELDS = ["title", "body", "priority", "assignee", "sprint", "release"];
|
|
9
|
+
/** Hash versioned JSON tuples rather than ambiguous concatenated strings. */
|
|
10
|
+
function digest(value) {
|
|
11
|
+
return createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
|
12
|
+
}
|
|
13
|
+
/** Render supported scalar values exactly as the spreadsheet sees them. */
|
|
14
|
+
function cells(document) {
|
|
15
|
+
return Object.fromEntries(EDIT_FIELDS.map(field => [field, String(field === "body" ? document.body : document.metadata[field] ?? "")]));
|
|
16
|
+
}
|
|
17
|
+
/** Read an item and its verified history while holding its native SDK lock. */
|
|
18
|
+
async function snapshot(pmRoot, id) {
|
|
19
|
+
const settings = await readSettings(pmRoot);
|
|
20
|
+
const release = await acquireLock(pmRoot, id, settings.locks.ttl_seconds, "pm-csv", false, false, settings.locks.wait_ms);
|
|
21
|
+
const startedAt = performance.now();
|
|
22
|
+
let row;
|
|
23
|
+
try {
|
|
24
|
+
const document = await readUpdateDocument(pmRoot, id);
|
|
25
|
+
row = await documentRow(pmRoot, id, document);
|
|
26
|
+
assertEditLease(performance.now() - startedAt, settings.locks.ttl_seconds);
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
if (error instanceof EditRefusal)
|
|
30
|
+
throw error;
|
|
31
|
+
const refusal = new EditRefusal("csv_session_invalid", "Verify that every exported item still exists and is readable; export a fresh session after resolving missing items.", [{ id, fields: ["item"] }]);
|
|
32
|
+
refusal.cause = error;
|
|
33
|
+
throw refusal;
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
await release();
|
|
37
|
+
}
|
|
38
|
+
return row;
|
|
39
|
+
}
|
|
40
|
+
/** Bind the full item and history revision to the exported scalar baseline. */
|
|
41
|
+
async function documentRow(pmRoot, id, document) {
|
|
42
|
+
if (document.metadata.id !== id) {
|
|
43
|
+
throw new EditRefusal("csv_session_invalid", "Use complete canonical item IDs from pm list; abbreviated keys cannot identify an edit session.");
|
|
44
|
+
}
|
|
45
|
+
const history = await readHistoryEntries(getHistoryPath(pmRoot, id), id);
|
|
46
|
+
if (!verifyHistoryEntries(history, document).ok) {
|
|
47
|
+
throw new EditRefusal("csv_session_invalid", "Verify item history with pm health before exporting or applying a session.", [{ id, fields: ["history"] }]);
|
|
48
|
+
}
|
|
49
|
+
const values = cells(document);
|
|
50
|
+
return {
|
|
51
|
+
id, revision: digest(history), itemDigest: digest(document), status: document.metadata.status, values,
|
|
52
|
+
digests: Object.fromEntries(EDIT_FIELDS.map(field => [field, digest([field, values[field]])])),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/** Fixed schema makes altered/missing/duplicate metadata columns fail closed. */
|
|
56
|
+
export function editSessionHeader() {
|
|
57
|
+
return ["_session", "id", "_revision", "_item_digest", "status", ...EDIT_FIELDS.flatMap(field => [field, `_digest_${field}`])];
|
|
58
|
+
}
|
|
59
|
+
/** Escape formula triggers and literal apostrophes without changing baseline values. */
|
|
60
|
+
function toCell(value) {
|
|
61
|
+
return /^[=+\-@\t\r']/.test(value) ? `'${value}` : value;
|
|
62
|
+
}
|
|
63
|
+
/** Remove only a recognized display escape before comparison and persistence. */
|
|
64
|
+
function fromCell(cell) {
|
|
65
|
+
return cell.startsWith("'") && /^[=+\-@\t\r']/.test(cell.slice(1)) ? cell.slice(1) : cell;
|
|
66
|
+
}
|
|
67
|
+
/** Export explicit stable keys and digests without creating tracker metadata. */
|
|
68
|
+
export async function exportEditSession(pmRoot, ids) {
|
|
69
|
+
if (ids.length > 1000 || new Set(ids).size !== ids.length || ids.some(id => !/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(id))) {
|
|
70
|
+
throw new EditRefusal("csv_session_invalid", "Export at most 1000 distinct canonical item IDs; split larger selections.");
|
|
71
|
+
}
|
|
72
|
+
const rows = [];
|
|
73
|
+
for (const id of ids)
|
|
74
|
+
rows.push(await snapshot(pmRoot, id));
|
|
75
|
+
const base = { version: 1, id: randomUUID(), rows };
|
|
76
|
+
const session = { ...base, digest: digest(base) };
|
|
77
|
+
return { session, table: [editSessionHeader(), ...rows.map(row => [
|
|
78
|
+
session.digest, row.id, row.revision, row.itemDigest, row.status,
|
|
79
|
+
...EDIT_FIELDS.flatMap(field => [toCell(row.values[field]), row.digests[field]]),
|
|
80
|
+
])] };
|
|
81
|
+
}
|
|
82
|
+
/** Validate an untrusted sidecar structurally as well as by its content digest. */
|
|
83
|
+
export function parseEditSession(input) {
|
|
84
|
+
try {
|
|
85
|
+
const session = input;
|
|
86
|
+
if (session.version !== 1 || typeof session.id !== "string" || !Array.isArray(session.rows) || session.rows.length > 1000
|
|
87
|
+
|| new Set(session.rows.map(row => row.id)).size !== session.rows.length
|
|
88
|
+
|| session.digest !== digest({ version: session.version, id: session.id, rows: session.rows }))
|
|
89
|
+
throw new Error();
|
|
90
|
+
for (const row of session.rows) {
|
|
91
|
+
if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(row.id) || typeof row.status !== "string"
|
|
92
|
+
|| !/^[a-f0-9]{64}$/.test(row.revision) || !/^[a-f0-9]{64}$/.test(row.itemDigest)
|
|
93
|
+
|| EDIT_FIELDS.some(field => typeof row.values[field] !== "string" || row.digests[field] !== digest([field, row.values[field]])))
|
|
94
|
+
throw new Error();
|
|
95
|
+
}
|
|
96
|
+
return structuredClone(session);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
throw new EditRefusal("csv_session_invalid", "Use the unchanged session JSON from csv checkout; export a fresh session if it was lost or edited.");
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/** Parse all edited rows before reading or writing tracker state. */
|
|
103
|
+
function plans(session, table) {
|
|
104
|
+
if (!Array.isArray(table) || table.some(row => !Array.isArray(row) || row.some(cell => typeof cell !== "string"))) {
|
|
105
|
+
throw new EditRefusal("csv_edit_invalid", "Provide a table of CSV string cells, including the checkout header.");
|
|
106
|
+
}
|
|
107
|
+
const header = table[0];
|
|
108
|
+
if (JSON.stringify(header) !== JSON.stringify(editSessionHeader())) {
|
|
109
|
+
throw new EditRefusal("csv_edit_invalid", "Keep the checkout column names and order, including all digest columns.");
|
|
110
|
+
}
|
|
111
|
+
const byId = new Map(session.rows.map(row => [row.id, row]));
|
|
112
|
+
const seen = new Set();
|
|
113
|
+
return table.slice(1).map(row => {
|
|
114
|
+
const base = byId.get(row[1]);
|
|
115
|
+
if (row.length !== header.length || !base || seen.has(row[1]) || row[0] !== session.digest
|
|
116
|
+
|| row[2] !== base.revision || row[3] !== base.itemDigest
|
|
117
|
+
|| EDIT_FIELDS.some((field, index) => row[6 + index * 2] !== base.digests[field])) {
|
|
118
|
+
throw new EditRefusal("csv_edit_invalid", "Restore item/revision/field digests and unique IDs from the checkout; never paste rows from another session.");
|
|
119
|
+
}
|
|
120
|
+
seen.add(base.id);
|
|
121
|
+
if (row[4] !== base.status) {
|
|
122
|
+
throw new EditRefusal("csv_edit_invalid", "Status is read-only. Use pm close or pm reopen for lifecycle transitions, then export a fresh session.");
|
|
123
|
+
}
|
|
124
|
+
const desired = Object.fromEntries(EDIT_FIELDS.map((field, index) => [field, fromCell(row[5 + index * 2])]));
|
|
125
|
+
// Spreadsheet saves may convert embedded newlines in either direction.
|
|
126
|
+
for (const field of EDIT_FIELDS) {
|
|
127
|
+
if (desired[field].replace(/\r\n/g, "\n") === base.values[field].replace(/\r\n/g, "\n"))
|
|
128
|
+
desired[field] = base.values[field];
|
|
129
|
+
}
|
|
130
|
+
// The host canonical document contract trims trailing body whitespace.
|
|
131
|
+
desired.body = desired.body.trimEnd();
|
|
132
|
+
const fields = EDIT_FIELDS.filter(field => desired[field] !== base.values[field]);
|
|
133
|
+
if (!desired.title.trim() || !/^[0-4]$/.test(desired.priority)
|
|
134
|
+
|| fields.some(field => field !== "body" && desired[field] !== desired[field].trim())) {
|
|
135
|
+
throw new EditRefusal("csv_edit_invalid", "Provide a nonblank title, priority 0..4, and trim scalar cells; body follows host trailing-whitespace normalization.");
|
|
136
|
+
}
|
|
137
|
+
return { base, desired, fields };
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
/** Detect divergence only in edited fields; equal desired values are convergent. */
|
|
141
|
+
function conflicts(plan, current) {
|
|
142
|
+
return plan.fields.filter(field => current.values[field] !== plan.base.values[field] && current.values[field] !== plan.desired[field]);
|
|
143
|
+
}
|
|
144
|
+
/** Check and apply a field patch under the host item lease; write-time fencing remains a host prerequisite. */
|
|
145
|
+
async function applyPlan(pmRoot, plan, marker, author) {
|
|
146
|
+
const settings = await readSettings(pmRoot);
|
|
147
|
+
const result = await mutateItem({ pmRoot, settings, id: plan.base.id, op: "update", author,
|
|
148
|
+
message: `${marker}:apply`, skipNoop: true,
|
|
149
|
+
mutate: guardEditMutation(settings.locks.ttl_seconds, async (document) => {
|
|
150
|
+
const current = await documentRow(pmRoot, plan.base.id, document);
|
|
151
|
+
const divergent = conflicts(plan, current);
|
|
152
|
+
if (divergent.length)
|
|
153
|
+
throw new EditRefusal("csv_edit_conflict", "Re-export and reconcile the edited fields; no conflicting value was overwritten.", [{ id: plan.base.id, fields: divergent }]);
|
|
154
|
+
const changedFields = [];
|
|
155
|
+
for (const field of plan.fields) {
|
|
156
|
+
if (current.values[field] === plan.desired[field])
|
|
157
|
+
continue;
|
|
158
|
+
const value = plan.desired[field];
|
|
159
|
+
if (field === "body")
|
|
160
|
+
document.body = value;
|
|
161
|
+
else if (field === "priority")
|
|
162
|
+
document.metadata.priority = Number(value);
|
|
163
|
+
else if (field === "title")
|
|
164
|
+
document.metadata.title = value;
|
|
165
|
+
else if (value === "")
|
|
166
|
+
delete document.metadata[field];
|
|
167
|
+
else
|
|
168
|
+
document.metadata[field] = value;
|
|
169
|
+
changedFields.push(field);
|
|
170
|
+
}
|
|
171
|
+
return { changedFields };
|
|
172
|
+
}),
|
|
173
|
+
});
|
|
174
|
+
return result.changedFields.length > 0;
|
|
175
|
+
}
|
|
176
|
+
/** Collect conflict keys across cause/aggregate graphs, visiting shared or cyclic errors once. */
|
|
177
|
+
function findConflicts(error) {
|
|
178
|
+
const pending = [error];
|
|
179
|
+
const seen = new Set();
|
|
180
|
+
const found = [];
|
|
181
|
+
for (const current of pending) {
|
|
182
|
+
if (!(current instanceof Error) || seen.has(current))
|
|
183
|
+
continue;
|
|
184
|
+
seen.add(current);
|
|
185
|
+
if (current instanceof EditRefusal)
|
|
186
|
+
found.push(...current.conflicts);
|
|
187
|
+
pending.push(current.cause);
|
|
188
|
+
if (current instanceof AggregateError)
|
|
189
|
+
pending.push(...current.errors);
|
|
190
|
+
}
|
|
191
|
+
return found;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Three-way patch using stable item keys, field-level conflict checks and the existing SDK
|
|
195
|
+
* transaction/CSV compensation. Preflight refusals create no transaction or
|
|
196
|
+
* item/history writes. Field checks run under each native item lease; protection
|
|
197
|
+
* after lease expiry needs host write fencing. Prior rows are compensated on
|
|
198
|
+
* ordinary failure, which explicitly requires reconciliation.
|
|
199
|
+
*/
|
|
200
|
+
export async function applyEditSession(pmRoot, input, table, options) {
|
|
201
|
+
const session = parseEditSession(input);
|
|
202
|
+
const planned = plans(session, table);
|
|
203
|
+
const transaction = `csv-edit-${digest([session.digest, table])}`;
|
|
204
|
+
const edited = planned.filter(plan => plan.fields.length > 0);
|
|
205
|
+
const found = [];
|
|
206
|
+
let concurrent = 0;
|
|
207
|
+
let pending = 0;
|
|
208
|
+
for (const plan of planned) {
|
|
209
|
+
const current = await snapshot(pmRoot, plan.base.id);
|
|
210
|
+
if (current.revision !== plan.base.revision || current.itemDigest !== plan.base.itemDigest)
|
|
211
|
+
concurrent++;
|
|
212
|
+
const divergent = conflicts(plan, current);
|
|
213
|
+
const editIndex = edited.indexOf(plan);
|
|
214
|
+
if (editIndex >= 0 && (await inspectUpdate(pmRoot, plan.base.id, `csv-txrow:${transaction}#${editIndex}`, false)).state === "applied") {
|
|
215
|
+
for (const field of plan.fields)
|
|
216
|
+
if (current.values[field] !== plan.desired[field] && !divergent.includes(field))
|
|
217
|
+
divergent.push(field);
|
|
218
|
+
}
|
|
219
|
+
if (divergent.length)
|
|
220
|
+
found.push({ id: plan.base.id, fields: divergent });
|
|
221
|
+
if (plan.fields.some(field => current.values[field] !== plan.desired[field]))
|
|
222
|
+
pending++;
|
|
223
|
+
}
|
|
224
|
+
if (found.length)
|
|
225
|
+
throw new EditRefusal("csv_edit_conflict", "Export a fresh session and reconcile the reported fields before applying; this preflight wrote zero items.", found);
|
|
226
|
+
const receipt = { rows: planned.length, changed: pending, unchanged: planned.length - pending, concurrent, applied: 0 };
|
|
227
|
+
if (options.dryRun || !pending)
|
|
228
|
+
return receipt;
|
|
229
|
+
// Bind the journal to the exact selected rows and edits, independent of file path.
|
|
230
|
+
let applied = 0;
|
|
231
|
+
const steps = edited.map((plan, index) => {
|
|
232
|
+
const marker = `csv-txrow:${transaction}#${index}`;
|
|
233
|
+
return {
|
|
234
|
+
id: `${index}-${plan.base.id}`,
|
|
235
|
+
inspect: () => inspectUpdate(pmRoot, plan.base.id, marker, false),
|
|
236
|
+
prepareCompensation: () => captureUpdateCompensation(pmRoot, plan.base.id, marker, options.author),
|
|
237
|
+
apply: async () => {
|
|
238
|
+
if (await applyPlan(pmRoot, plan, marker, options.author))
|
|
239
|
+
applied++;
|
|
240
|
+
return plan.base.id;
|
|
241
|
+
},
|
|
242
|
+
compensate: data => compensateUpdate(pmRoot, plan.base.id, marker, options.author, data),
|
|
243
|
+
};
|
|
244
|
+
});
|
|
245
|
+
try {
|
|
246
|
+
await commitWorkspaceTransaction({ pmRoot, transactionId: transaction, author: options.author, steps, onTransition: options.onTransition });
|
|
247
|
+
}
|
|
248
|
+
catch (error) {
|
|
249
|
+
const refusal = new EditRefusal("csv_edit_apply_failed", `Inspect and reconcile transaction ${transaction} before retrying. Item fields were checked before persistence; the host must keep its item lease valid through the write. Reverse compensation was attempted and may have refused a concurrent writer.`, findConflicts(error));
|
|
250
|
+
refusal.cause = error;
|
|
251
|
+
throw refusal;
|
|
252
|
+
}
|
|
253
|
+
return { ...receipt, applied, transaction };
|
|
254
|
+
}
|
|
255
|
+
//# sourceMappingURL=roundtrip.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"roundtrip.js","sourceRoot":"","sources":["../roundtrip.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { PmCliError } from "@unbrained/pm-cli/sdk";
|
|
2
|
+
import type { ExtensionApi } from "@unbrained/pm-cli/sdk/authoring";
|
|
3
|
+
import { IMPORT_COLUMNS } from "./index.ts";
|
|
4
|
+
/** Fields supported by the existing CSV import core; other columns are explicitly ignored. */
|
|
5
|
+
export type TableField = typeof IMPORT_COLUMNS[number];
|
|
6
|
+
/** Stable machine-readable refusal categories. Messages never echo source values or paths. */
|
|
7
|
+
export type TableRefusalCode = "csv_table_invalid" | "csv_schema_invalid" | "csv_schema_changed" | "csv_domain_changed" | "csv_target_changed" | "csv_schema_io" | "csv_table_import_failed";
|
|
8
|
+
/** Expected SDK/CLI refusal with an actionable remedy and semantic usage exit code. */
|
|
9
|
+
export declare class TableSchemaRefusal extends PmCliError {
|
|
10
|
+
/** Stable category for automated refusal handling. */
|
|
11
|
+
readonly code: TableRefusalCode;
|
|
12
|
+
/** Concrete recovery action required before retrying. */
|
|
13
|
+
readonly remedy: string;
|
|
14
|
+
constructor(code: TableRefusalCode, remedy: string);
|
|
15
|
+
}
|
|
16
|
+
/** Lexical domains describe all observed nonempty cells, without storing cell values. */
|
|
17
|
+
export interface TableDomain {
|
|
18
|
+
kind: "empty" | "integer" | "decimal" | "boolean" | "text";
|
|
19
|
+
nullable: boolean;
|
|
20
|
+
}
|
|
21
|
+
/** Version 1 contract. The revision binds dialect, ordered exact headers, domains, mappings and target enums. */
|
|
22
|
+
export interface TableSchema {
|
|
23
|
+
version: 1;
|
|
24
|
+
revision: string;
|
|
25
|
+
dialect: {
|
|
26
|
+
delimiter: string;
|
|
27
|
+
quote: '"';
|
|
28
|
+
bom: boolean;
|
|
29
|
+
eol: "lf" | "crlf" | "none";
|
|
30
|
+
};
|
|
31
|
+
columns: {
|
|
32
|
+
name: string;
|
|
33
|
+
domain: TableDomain;
|
|
34
|
+
field: TableField | null;
|
|
35
|
+
}[];
|
|
36
|
+
target: {
|
|
37
|
+
types: string[];
|
|
38
|
+
statuses: string[];
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/** Proposal controls reuse existing import alias/mapping vocabulary; UTF-8 is required. */
|
|
42
|
+
export interface DescribeTableOptions {
|
|
43
|
+
delimiter?: string;
|
|
44
|
+
fieldMap?: Record<string, string>;
|
|
45
|
+
autoMap?: boolean;
|
|
46
|
+
}
|
|
47
|
+
/** Compact receipt contains counts and an immutable mapping identity, never rows or cell values. */
|
|
48
|
+
export interface TableReceipt {
|
|
49
|
+
version: 1;
|
|
50
|
+
revision: string;
|
|
51
|
+
rows: number;
|
|
52
|
+
columns: number;
|
|
53
|
+
mapped: number;
|
|
54
|
+
ignored: number;
|
|
55
|
+
}
|
|
56
|
+
/** Full reusable artifact is separate from the bounded agent receipt. */
|
|
57
|
+
export interface TableDescription {
|
|
58
|
+
schema: TableSchema;
|
|
59
|
+
receipt: TableReceipt;
|
|
60
|
+
}
|
|
61
|
+
/** Describe and propose a reusable schema without writing tracker state or artifacts. */
|
|
62
|
+
export declare function describeTable(pmRoot: string, text: string, options?: DescribeTableOptions): Promise<TableDescription>;
|
|
63
|
+
/** Validate artifact version, bounds, mapping shape and canonical identity. Unknown versions fail closed. */
|
|
64
|
+
export declare function parseTableSchema(value: unknown): TableSchema;
|
|
65
|
+
/** Enforce a saved mapping against the full new table before any tracker mutation. */
|
|
66
|
+
export declare function previewTable(pmRoot: string, text: string, saved: unknown): Promise<TableReceipt>;
|
|
67
|
+
/** Negotiate a captured UTF-8 table then use the existing import mutation/upsert core.
|
|
68
|
+
* Preview never runs mutation hooks. Import retains legacy partial-write semantics on row failures.
|
|
69
|
+
*/
|
|
70
|
+
export declare function importNegotiatedTable(pmRoot: string, text: string, saved: unknown, options?: {
|
|
71
|
+
dryRun?: boolean;
|
|
72
|
+
keyField?: TableField;
|
|
73
|
+
}): Promise<TableReceipt & {
|
|
74
|
+
imported: number;
|
|
75
|
+
updated: number;
|
|
76
|
+
skipped: number;
|
|
77
|
+
}>;
|
|
78
|
+
/** Register native host-rendered TOON/JSON commands; full artifacts are written only on explicit request. */
|
|
79
|
+
export declare function registerTableSchemaCommands(api: ExtensionApi): void;
|
|
80
|
+
//# sourceMappingURL=table-schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"table-schema.d.ts","sourceRoot":"","sources":["../table-schema.ts"],"names":[],"mappings":"AAEA,OAAO,EAAyB,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAC1E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AACpE,OAAO,EAAE,cAAc,EAE6B,MAAM,YAAY,CAAC;AAEvE,8FAA8F;AAC9F,MAAM,MAAM,UAAU,GAAG,OAAO,cAAc,CAAC,MAAM,CAAC,CAAC;AACvD,8FAA8F;AAC9F,MAAM,MAAM,gBAAgB,GAAG,mBAAmB,GAAG,oBAAoB,GAAG,oBAAoB,GAC9F,oBAAoB,GAAG,oBAAoB,GAAG,eAAe,GAAG,yBAAyB,CAAC;AAC5F,uFAAuF;AACvF,qBAAa,kBAAmB,SAAQ,UAAU;IAChD,sDAAsD;IACtD,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAChC,yDAAyD;IACzD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,YAAY,IAAI,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,EAIjD;CACF;AACD,yFAAyF;AACzF,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,OAAO,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,MAAM,CAAC;IAC3D,QAAQ,EAAE,OAAO,CAAC;CACnB;AACD,iHAAiH;AACjH,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,CAAC,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,GAAG,CAAC;QAAC,GAAG,EAAE,OAAO,CAAC;QAAC,GAAG,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC;IACtF,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,WAAW,CAAC;QAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAA;KAAE,EAAE,CAAC;IAC3E,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;CACjD;AACD,2FAA2F;AAC3F,MAAM,WAAW,oBAAoB;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AACD,oGAAoG;AACpG,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,CAAC,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AACD,yEAAyE;AACzE,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,WAAW,CAAC;IACpB,OAAO,EAAE,YAAY,CAAC;CACvB;AAsFD,yFAAyF;AACzF,wBAAsB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,oBAAyB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAkB/H;AAOD,6GAA6G;AAC7G,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,CAsB5D;AAED,sFAAsF;AACtF,wBAAsB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,YAAY,CAAC,CAkBtG;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,UAAU,CAAA;CAAO,GAAG,OAAO,CAAC,YAAY,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CASnO;AAYD,6GAA6G;AAC7G,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,YAAY,GAAG,IAAI,CA0CnE"}
|