@ai-matrx/records 0.21.0 → 0.24.0

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 CHANGED
@@ -1,6 +1,79 @@
1
1
  # Changelog — @ai-matrx/records
2
2
 
3
3
 
4
+
5
+ ## 0.24.0 — 2026-09-20
6
+
7
+ **0.23.0 never reached npm.** Its release run died before any package could be
8
+ built: five `node_modules` SYMLINKS, each pointing at an absolute path inside
9
+ one machine's checkout, had been committed to the repository, and on a CI runner
10
+ a dangling symlink makes `pnpm install --frozen-lockfile` answer ENOENT. Every
11
+ package release was blocked, not just this one. The symlinks are untracked and
12
+ `.gitignore` now carries both spellings — `node_modules/` with the slash matches
13
+ a directory only, which is how a symlink of that name walked past it. This
14
+ version is 0.23.0's contents from the repaired tree; a tag is never moved, so
15
+ 0.23.0 is simply absent from npm.
16
+
17
+
18
+ ## 0.23.0
19
+
20
+ **A column a model owns — seven doors, and the four states one cell can be in.**
21
+
22
+ `enrichments`, `enrichDeclare`, `enrichDue`, `enrichCells`, `enrichLand`,
23
+ `enrichPin` and `enrichRuns` (PRODUCTS row 10 / AGT-6). An Enrichment is not a new
24
+ object: it IS the Field, saying `source = agent`, with its instruction, inputs,
25
+ model, confidence floor and freshness in `source_config` and
26
+ `review_interval_days` — so nothing here is keyed by an enrichment id.
27
+
28
+ `EnrichCell` is the type a screen draws a badge from, and it is deliberately able
29
+ to say four different things, because they are four different facts: a value an
30
+ agent wrote (with the model, what it read and how sure it was), a value a person
31
+ took back (`pinned`), an answer the model would not commit to (`absent_reason:
32
+ "conflicting"`, the guess in `alternates[0]`), and a value past its freshness date
33
+ (`stale` — still holding its value; stale marks it, it never erases it).
34
+
35
+ `enrichCells` answers a WHOLE PAGE in one call. Asked per cell it is one round trip
36
+ per badge.
37
+
38
+ The door catalogue is regenerated from the live store: 478 doors.
39
+
40
+ ## 0.22.0 — 2026-09-20
41
+
42
+ **BREAKING: a parse is a parse. `importCsv` / `importXlsx` no longer take the Fields.**
43
+
44
+ Found by looking at the import screen, not by reading it. The plan showed *"every
45
+ row in this column is empty, so there is nothing to go on"* for columns plainly
46
+ full of values, and every unmapped column's sample list was blank.
47
+
48
+ The cause was in this package. The parse took the Fields, matched each heading by
49
+ key or label, keyed the rows by whatever it matched and **dropped every column it
50
+ did not**. That is a mapping decision, made by the one component that must not
51
+ make it: the rows handed to `custom.io_import_rows` carried nothing for the
52
+ unmatched columns, so the store never saw them and could not offer a single one as
53
+ a new column. The whole "never a junk bag" half of SCR-N-7 was unreachable through
54
+ that screen — and it shipped in **0.21.0**, so anyone on that version has it.
55
+
56
+ Fixed in the SHAPE rather than beside it. These functions no longer take `fields`
57
+ at all, so a parse cannot decide a mapping even by accident:
58
+
59
+ ```ts
60
+ // before
61
+ importCsv(fields, text) // -> { columns: [{header, field}], rows, unmapped }
62
+ // after
63
+ importCsv(text) // -> { headers, rows } rows keyed by heading
64
+ ```
65
+
66
+ `ParsedImport` is now `{ headers: string[]; rows: Array<Record<string, string>> }`
67
+ — every heading, every row keyed by heading, nothing dropped — with no `field`, no
68
+ `unmapped`, and **no safe path left standing beside the unsafe one**. Only the door
69
+ decides. `parseCsv`, `exportCsv`, `exportXlsx`, `toAoa` and `cellText` are
70
+ unchanged.
71
+
72
+ **Migrating:** drop the first argument, and send `rows` straight to
73
+ `custom.io_import_rows` — the store matches the headings and offers what it could
74
+ not match. A caller that genuinely wants the old pre-matched shape can build it
75
+ from `headers` in four lines, which is what this package's own red twin does.
76
+
4
77
  ## 0.21.0 — 2026-09-20
5
78
 
6
79
  **The store moved and this package's census caught it — twice.** The release
@@ -68,6 +141,23 @@ cannot unzip a workbook; **everything after the parse is the store's**, so a fil
68
141
  imported from a screen, from an agent and from a script land identically and
69
142
  there is no second importer in this package to drift.
70
143
 
144
+ **BREAKING, and it is the point: `importCsv` / `importXlsx` no longer take the
145
+ Fields.** They took them, matched each heading to a Field by key or label, keyed
146
+ the rows by whatever they matched and **dropped every column they did not** —
147
+ and that cannot be the input to a door whose whole job is to decide the mapping
148
+ and offer what it could not match. Measured by looking at the screen: every
149
+ unmapped column's samples came back empty and its proposal read *"every row in
150
+ this column is empty, so there is nothing to go on"* for columns plainly full of
151
+ values. The store can only offer a column whose values reached it.
152
+
153
+ `ParsedImport` is now `{ headers: string[]; rows: Array<Record<string, string>> }`
154
+ — every heading, every row keyed by heading, nothing dropped — and there is no
155
+ `field`, no `unmapped` and no way to ask for one. The fix is the SHAPE rather
156
+ than a second field beside the first: a parse that is not given the Fields
157
+ cannot decide a mapping even by accident, and no safe path is left standing
158
+ beside an unsafe one. `parseCsv`, `exportCsv`, `exportXlsx`, `toAoa` and
159
+ `cellText` are unchanged.
160
+
71
161
  - **`importPlan({ table_id, columns })`** — what the store makes of a file's
72
162
  columns before anything is written, reading the parity map AND this
73
163
  organization: an address that belongs to a member is a `member` column, a few
@@ -104,7 +194,6 @@ New types in `@ai-matrx/records`: `ImportPlan`, `ImportColumnPlan`,
104
194
  `InboundAddress`, `InboundDeclared`.
105
195
 
106
196
  `store.generated.ts` regenerated against the live store: **428 doors**.
107
-
108
197
  ## 0.18.0
109
198
 
110
199
  **"Who changed this price, and can I put it back?" — the doors for PRODUCTS row 4.**
@@ -2085,6 +2085,22 @@ var DOORS = {
2085
2085
  dashboardRun: "dashboard_run",
2086
2086
  dashboardStuck: "dashboard_stuck",
2087
2087
  dashboardDelete: "dashboard_delete",
2088
+ // PRODUCTS row 10 — "fill in each company's industry and headcount, and keep
2089
+ // it fresh". An Enrichment is not an object of its own: it is a Field saying
2090
+ // `source = agent`, with its instruction, inputs, model, confidence floor and
2091
+ // freshness in `source_config` (AGT-6). `enrichCells` is the one a GRID calls —
2092
+ // the badge data for a whole page in one round trip, because a badge asked per
2093
+ // cell is fifty round trips to draw fifty badges. `enrichPin` is the half of
2094
+ // this product that decides whether anybody ever trusts it: the moment a
2095
+ // person types over an agent-owned cell the store pins it, and the next run
2096
+ // leaves it exactly as they left it.
2097
+ enrichments: "enrichments",
2098
+ enrichDeclare: "enrich_declare",
2099
+ enrichDue: "enrich_due",
2100
+ enrichCells: "enrich_cells",
2101
+ enrichLand: "enrich_land",
2102
+ enrichPin: "enrich_pin",
2103
+ enrichRuns: "enrich_runs",
2088
2104
  // PRODUCTS row 4 — "Who changed this price, and can I put it back?" The
2089
2105
  // history has been in the store since W3-HIST; until 2026-09-20 the only way
2090
2106
  // to ask for it was `io_revisions`, which answers a raw uuid for the author
@@ -3002,6 +3018,67 @@ function createRecordsClient(config) {
3002
3018
  "ruleEval"
3003
3019
  );
3004
3020
  },
3021
+ async enrichments(args) {
3022
+ return callDoor(
3023
+ DOORS.enrichments,
3024
+ { p_organization_id: org, p_table_id: args?.table_id ?? null },
3025
+ "enrichments"
3026
+ );
3027
+ },
3028
+ async enrichDeclare(args) {
3029
+ return callDoor(
3030
+ DOORS.enrichDeclare,
3031
+ { p_organization_id: org, p_field_id: args.field_id, p_spec: args.spec },
3032
+ "enrichDeclare"
3033
+ );
3034
+ },
3035
+ async enrichDue(args) {
3036
+ return callDoor(
3037
+ DOORS.enrichDue,
3038
+ {
3039
+ p_organization_id: org,
3040
+ p_field_id: args.field_id,
3041
+ p_limit: args.limit ?? 50,
3042
+ p_include_fresh: args.include_fresh ?? false
3043
+ },
3044
+ "enrichDue"
3045
+ );
3046
+ },
3047
+ async enrichCells(args) {
3048
+ return callDoor(
3049
+ DOORS.enrichCells,
3050
+ {
3051
+ p_organization_id: org,
3052
+ p_table_id: args.table_id,
3053
+ p_field_keys: args.field_keys ?? null,
3054
+ p_record_ids: args.record_ids ?? null
3055
+ },
3056
+ "enrichCells"
3057
+ );
3058
+ },
3059
+ async enrichPin(args) {
3060
+ return callDoor(
3061
+ DOORS.enrichPin,
3062
+ {
3063
+ p_organization_id: org,
3064
+ p_record_id: args.record_id,
3065
+ p_field_key: args.field_key,
3066
+ p_pinned: args.pinned ?? true
3067
+ },
3068
+ "enrichPin"
3069
+ );
3070
+ },
3071
+ async enrichRuns(args) {
3072
+ return callDoor(
3073
+ DOORS.enrichRuns,
3074
+ {
3075
+ p_organization_id: org,
3076
+ p_field_id: args?.field_id ?? null,
3077
+ p_limit: args?.limit ?? 50
3078
+ },
3079
+ "enrichRuns"
3080
+ );
3081
+ },
3005
3082
  async dashboards(args) {
3006
3083
  return callDoor(
3007
3084
  DOORS.dashboards,
@@ -4300,10 +4377,6 @@ async function exportXlsx(fields, rows, sheetLabel = "Records") {
4300
4377
  XLSX.utils.book_append_sheet(workbook, worksheet, name);
4301
4378
  return XLSX.write(workbook, { type: "array", bookType: "xlsx", compression: true });
4302
4379
  }
4303
- function matchField(header, fields) {
4304
- const wanted = header.trim().toLowerCase();
4305
- return fields.find((f) => f.key.toLowerCase() === wanted) ?? fields.find((f) => (f.label ?? "").toLowerCase() === wanted) ?? null;
4306
- }
4307
4380
  function parseCsv(text) {
4308
4381
  const rows = [];
4309
4382
  let row = [];
@@ -4338,31 +4411,30 @@ function parseCsv(text) {
4338
4411
  }
4339
4412
  return rows;
4340
4413
  }
4341
- function importCsv(fields, text) {
4342
- return fromMatrix(fields, parseCsv(text));
4414
+ function importCsv(text) {
4415
+ return fromMatrix(parseCsv(text));
4343
4416
  }
4344
- async function importXlsx(fields, bytes) {
4417
+ async function importXlsx(bytes) {
4345
4418
  const XLSX = await import("xlsx");
4346
4419
  const workbook = XLSX.read(bytes, { type: "array" });
4347
4420
  const first = workbook.SheetNames[0];
4348
- if (!first) return { columns: [], rows: [], unmapped: [] };
4421
+ if (!first) return { headers: [], rows: [] };
4349
4422
  const sheet = workbook.Sheets[first];
4350
- if (!sheet) return { columns: [], rows: [], unmapped: [] };
4423
+ if (!sheet) return { headers: [], rows: [] };
4351
4424
  const matrix = XLSX.utils.sheet_to_json(sheet, { header: 1, raw: false, defval: "" });
4352
- return fromMatrix(fields, matrix);
4425
+ return fromMatrix(matrix);
4353
4426
  }
4354
- function fromMatrix(fields, matrix) {
4427
+ function fromMatrix(matrix) {
4355
4428
  const [header, ...body] = matrix;
4356
- if (!header) return { columns: [], rows: [], unmapped: [] };
4357
- const columns = header.map((text) => ({ header: text, field: matchField(text, fields) }));
4358
- const unmapped = columns.filter((c) => c.field === null).map((c) => c.header).filter((h) => h.trim().length > 0);
4429
+ if (!header) return { headers: [], rows: [] };
4430
+ const headers = header.map((text) => String(text ?? ""));
4359
4431
  const rows = body.filter((line) => line.some((cell) => String(cell ?? "").trim().length > 0)).map((line) => {
4360
4432
  const record = {};
4361
- columns.forEach((column, index) => {
4362
- if (column.field) record[column.field.key] = String(line[index] ?? "");
4433
+ headers.forEach((heading, index) => {
4434
+ if (heading.trim().length > 0) record[heading] = String(line[index] ?? "");
4363
4435
  });
4364
4436
  return record;
4365
4437
  });
4366
- return { columns, rows, unmapped };
4438
+ return { headers, rows };
4367
4439
  }
4368
4440
  //# sourceMappingURL=index.cjs.map