@rolealpha/n8n-nodes-rolealpha 0.5.1 → 0.7.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/README.md CHANGED
@@ -45,6 +45,9 @@ next run follows. You don't edit code and you don't republish anything.
45
45
  - **Teams of agents on one step.** Several roles share a step. Each agent does its own part,
46
46
  waits for what it needs and is woken when it arrives. Agents that hang are escalated to
47
47
  the role that leads their circle.
48
+ - **Workflows that follow the organisation.** When a step, role or agent is created, changed
49
+ or removed in roleALPHA, a model proposes what that means for the n8n workflows. The
50
+ proposal becomes an inactive draft, and only a person filling the deciding role can apply it.
48
51
  - **Limits you can change without republishing.** Retries, wait times, run time and tool
49
52
  calls come from a settings table and are clamped to fixed bounds.
50
53
 
@@ -125,6 +128,64 @@ bound to the account does not run. There is no fallback to the view of the accou
125
128
  Approval gates follow this order: **Get Decider → Check Approver → Approval Seal: Sign**. The
126
129
  workflow that sends runs **Approval Seal: Verify** first and stops if the content changed.
127
130
 
131
+ ## Workflows that follow the organisation
132
+
133
+ Most changes in roleALPHA need no workflow change at all: the runner reads roles, duties and
134
+ deciders on every run. What roleALPHA cannot know is technology: a new step needs a trigger, a
135
+ schedule or a mapping of (collection, status) to its custom ID. The **roleALPHA Workflow
136
+ Builder** closes that gap without letting a model change production on its own.
137
+
138
+ ```
139
+ Scheduler (every 15 min)
140
+ └ Data table: snapshot rows
141
+ └ Workflow Builder: Detect Changes new / changed / removed steps, roles, agents
142
+ ├ store and remove snapshot rows
143
+ └ per change: Workflow Builder: Propose ← chat model
144
+ └ Create Draft inactive, tagged rolealpha-draft
145
+ └ roleALPHA: Get Decider (governing step)
146
+ └ per person: Issue Approval Tokens → e-mail with approve / reject link
147
+
148
+ Approval link (GET) → Verify Approval Token → page with a confirm button
149
+ Confirm button (POST)
150
+ └ Verify Approval Token → roleALPHA: Check Approver (governing step, fail-closed)
151
+ ├ approve → Workflow Builder: Apply Draft only with the hash in the token
152
+ └ reject → Workflow Builder: Discard Draft
153
+ ```
154
+
155
+ **Detect** compares a fingerprint of every watched entity (name, custom ID, attributes and
156
+ edges) with the rows stored last time. The first run only stores the snapshot. An entity that
157
+ cannot be read is neither changed nor removed; removals count only when the complete index was
158
+ read.
159
+
160
+ **Propose** gives the model the change, the entity as roleALPHA shows it now (a step with its
161
+ roles, never persons), the managed workflows and the credentials they use. Its answer is
162
+ checked, and a failed check goes back to the model once more with the problems:
163
+
164
+ - only node types from the allow list (no Code node by default),
165
+ - no e-mail address and no UUID in any parameter: those are read from roleALPHA at run time,
166
+ - only credentials already used by managed workflows; a node that stays keeps its own
167
+ credentials, IDs and webhook IDs,
168
+ - updates and deactivations only of managed workflows, connections only between existing nodes.
169
+
170
+ **Drafts** carry everything Apply needs in a sticky note named `roleALPHA binding`: the action,
171
+ the target, the target's version and the content hash. **Apply** refuses when the draft no
172
+ longer has the approved hash or the target changed after the draft was made. A managed workflow
173
+ is one with that note and the tag `rolealpha-managed`; nothing else is ever touched.
174
+
175
+ **Approval links** carry a token signed with the Approval Seal key: `at1.<claims>.<HMAC>`. It
176
+ names the draft, its content hash, one person and one decision, and it expires (7 days by
177
+ default, at most 30). Nobody can make or change one without the key, so the approval webhook
178
+ can be public and needs no single sign-on. A link works once: Apply and Discard remove the
179
+ draft, and a second use finds none. Whether the person may decide is checked when they
180
+ confirm, not when the mail was sent, so a link of somebody who left the role does nothing.
181
+ Opening a link only shows a confirm page; mail scanners that open links decide nothing.
182
+
183
+ Two ready workflows are in [`examples/`](examples): import
184
+ `workflows-follow-the-organisation.json` and `approve-workflow-drafts.json`, create a data table
185
+ `rolealpha_snapshot` with the text columns `uuid, entity_type, name, custom_id, fingerprint,
186
+ fields, edges, seen_at`, fill in **Settings** and the credentials, and give a step in roleALPHA
187
+ (the governing step, e.g. `automation:workflow-changes`) a deciding role.
188
+
128
189
  ## Nodes
129
190
 
130
191
  ### roleALPHA (`roleAlpha`)
@@ -153,6 +214,19 @@ workflow that sends runs **Approval Seal: Verify** first and stops if the conten
153
214
  | Settings | Resolve | Runner limits from defaults and a settings table (`scope, target, key, value`): default < global < step < agent. Every value is clamped to fixed bounds; unknown keys and unusable values are reported in `settingProblems` and never applied. | – |
154
215
  | Schedule | Is Due | Decides from the step `frequency` (and `termin` / due date) whether a run is due in the current period. | – |
155
216
 
217
+ ### roleALPHA Workflow Builder (`roleAlphaWorkflowBuilder`)
218
+
219
+ | Operation | What it does | Credential |
220
+ |---|---|---|
221
+ | Detect Changes | Compares the watched entity types (default: process, role, agent) with the stored snapshot rows. Returns `changes` (`created`, `changed` with `changedFields`, `edgesAdded`, `edgesRemoved`, `removed`), the rows to `upsert` and `remove`, and `unreadable`. | MCP OAuth2 |
222
+ | List Workflows | The managed workflows with their bound entities and the credentials they use. | n8n API |
223
+ | Propose | Needs a chat model on its **Model** input. Returns `ok`, `action` (`create`, `update`, `deactivate`, `none`), `reason`, `workflow`, `contentHash`, `problems` and `notes`. | MCP OAuth2, n8n API |
224
+ | Create Draft | Stores a proposal with `ok = true` as an inactive draft. Returns `draftId` and `contentHash`. | n8n API |
225
+ | Apply Draft | Applies a draft given the approved hash. Optionally activates a new workflow. | n8n API |
226
+ | Discard Draft | Deletes a draft; refuses any workflow that is not a draft. | n8n API |
227
+ | Issue Approval Tokens | `approveToken` and `rejectToken` for one person, bound to `draftId` and `contentHash`, plus `tokenExpiresAt`. Keeps the input fields. | Approval Seal API |
228
+ | Verify Approval Token | Returns `draftId`, `contentHash`, `approver`, `decision` and `expiresAt` of a valid token; fails on a changed, foreign or expired one. | Approval Seal API |
229
+
156
230
  ### Approval Seal (`approvalSeal`)
157
231
 
158
232
  `Sign` seals exactly the approved content together with the approver. Use it after **Check
@@ -177,7 +251,8 @@ under the agent → *Context & Access*.
177
251
  | **MCP OAuth2** (n8n built-in) | Access to the roleALPHA MCP endpoint for one organisation. Used for agents (default source) and for every traversal. The `agent` view needs admin rights on the tenant. For **Connect As: Agent** it must be a roleALPHA function account (grant type Client Credentials) whose role has `agent:run` and that may run the agent ("May run these agents"). |
178
252
  | **roleALPHA Agent Keys API** | Optional, for the `Agent Keys` source. `baseUrl` of the data node, and `keys`: `{"<agent uuid>": "agt_…"}`, or a list of `{agentUuid, key, label}`. An agent key reads only its own agent, so it cannot traverse. |
179
253
  | **roleALPHA Webhook API** | One signing secret, a JSON list of secrets, or a map from agent UUID to secret. |
180
- | **Approval Seal API** | `key` (sign and verify) and optionally `legacyKey` (verify v2 only). |
254
+ | **n8n API** (n8n built-in) | API key and base URL of the n8n instance the Workflow Builder manages. When the nodes run in that instance, `http://localhost:5678/api/v1` works even if `/api` is closed to the outside. |
255
+ | **Approval Seal API** | `key` (sign and verify seals and approval tokens) and optionally `legacyKey` (verify v2 seals only). |
181
256
 
182
257
  Agent keys and webhook secrets are issued in the roleALPHA interface and shown only once.
183
258
  Store them nowhere except in these credentials.
@@ -216,6 +291,10 @@ record_keys, event, run_id, attempts, progress, at`.
216
291
  - The prompt marks trigger payloads as data. Guardrails take precedence over event content.
217
292
  - Keep `requireActive` and `requireGuardrails` on in production.
218
293
  - Settings are clamped to fixed bounds.
294
+ - The Workflow Builder never activates or changes a workflow by itself. A proposal is an
295
+ inactive draft; Apply needs the hash that was sent to the approver and a target that did not
296
+ change since. The approval link is a signed one-time token for one person and one decision;
297
+ Check Approver decides at use whether that person may still decide.
219
298
  - Duplicate runs are blocked by leases, and wake-ups are claimed before they fire.
220
299
  - No credentials are kept in wait, settings or cache rows. The share cache key holds no
221
300
  record content, and a cached answer is checked again like a fresh one.
@@ -314,6 +393,10 @@ not caught by the graph.
314
393
  | `MCP initialize failed with HTTP 429` | Many runs started at once and roleALPHA turned some away. The client repeats a call turned away with 429 or 503 up to four times (Retry-After, else 1, 2, 4, 8 s). If it still fails, start fewer runs at once. |
315
394
  | `Empty MCP response` | The server answered with no body. Reading calls are repeated; a writing tool is not, so it cannot run twice. |
316
395
  | `Cannot access "arguments" due to security concerns` | n8n blocks the name `arguments` in expressions and in `$fromAI`. Name the tool input `toolArgs` or `args`. |
396
+ | Propose returns `ok: false` | The model's answer failed the check twice; `problems` says why. Often a node type outside the allow list or a hard-coded address. Add what the model needs to **Extra Rules**. |
397
+ | `The draft changed after it was proposed or approved` | Somebody edited the draft, or the link carries another hash. Discard it; the next change proposes again. |
398
+ | `Workflow … changed after the draft was made` | The target was edited after the draft. Discard the draft and let the builder propose again. |
399
+ | Detect fails with `more than Max Entities` | Every watched entity costs two reads per run. Watch fewer types or raise the limit. |
317
400
  | Webhook answers `401` | The `X-RA-Signature` does not match. Check the secret in **roleALPHA Webhook API**. |
318
401
 
319
402
  ## Licence
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Approval tokens: a link that says who may decide what about which draft, signed with the
3
+ * seal key. The link itself is the proof, so it works without a single sign-on proxy.
4
+ *
5
+ * at1.<base64url claims>.<base64url HMAC-SHA256>
6
+ *
7
+ * A token is bound to one draft, its content hash, one person and one decision, and it
8
+ * expires. It can be used once: applying or discarding removes the draft, and a second
9
+ * use finds no draft any more. Whether the person may still decide is checked at use,
10
+ * not when the token is issued.
11
+ */
12
+ export type Decision = 'approve' | 'reject';
13
+ export interface TokenClaims {
14
+ draftId: string;
15
+ contentHash: string;
16
+ approver: string;
17
+ decision: Decision;
18
+ /** Expiry in seconds since the epoch. */
19
+ exp: number;
20
+ }
21
+ export interface TokenCheck {
22
+ valid: boolean;
23
+ reason: string;
24
+ claims: TokenClaims | null;
25
+ }
26
+ /** Only these characters can occur, so a valid token is safe in a URL and in HTML. */
27
+ export declare const TOKEN_PATTERN: RegExp;
28
+ export declare function issueToken(key: string, input: {
29
+ draftId: string;
30
+ contentHash: string;
31
+ approver: string;
32
+ decision: Decision;
33
+ }, opts?: {
34
+ validHours?: number;
35
+ now?: Date;
36
+ }): {
37
+ token: string;
38
+ expiresAt: string;
39
+ };
40
+ export declare function verifyToken(key: string, token: unknown, opts?: {
41
+ now?: Date;
42
+ }): TokenCheck;
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ /**
3
+ * Approval tokens: a link that says who may decide what about which draft, signed with the
4
+ * seal key. The link itself is the proof, so it works without a single sign-on proxy.
5
+ *
6
+ * at1.<base64url claims>.<base64url HMAC-SHA256>
7
+ *
8
+ * A token is bound to one draft, its content hash, one person and one decision, and it
9
+ * expires. It can be used once: applying or discarding removes the draft, and a second
10
+ * use finds no draft any more. Whether the person may still decide is checked at use,
11
+ * not when the token is issued.
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.TOKEN_PATTERN = void 0;
15
+ exports.issueToken = issueToken;
16
+ exports.verifyToken = verifyToken;
17
+ const crypto_1 = require("crypto");
18
+ const seal_1 = require("./seal");
19
+ const graph_1 = require("./graph");
20
+ const PREFIX = 'at1';
21
+ /** Keeps a token signature from ever matching a seal made with the same key. */
22
+ const PURPOSE = 'rolealpha-approval-token';
23
+ /** Only these characters can occur, so a valid token is safe in a URL and in HTML. */
24
+ exports.TOKEN_PATTERN = /^at1\.[A-Za-z0-9_-]{16,2048}\.[A-Za-z0-9_-]{43}$/;
25
+ const MAX_HOURS = 24 * 30;
26
+ function requireKey(key) {
27
+ if (typeof key !== 'string' || key.length < 32)
28
+ throw new Error('The seal key must be at least 32 characters long');
29
+ }
30
+ function mac(key, body) {
31
+ return (0, crypto_1.createHmac)('sha256', key).update(`${PURPOSE}:${body}`, 'utf8').digest('base64url');
32
+ }
33
+ function issueToken(key, input, opts = {}) {
34
+ requireKey(key);
35
+ const draftId = String(input.draftId ?? '').trim();
36
+ const contentHash = String(input.contentHash ?? '').trim();
37
+ const approver = (0, graph_1.normalizeEmail)(input.approver);
38
+ if (!draftId)
39
+ throw new Error('A token needs the draft ID');
40
+ if (!contentHash)
41
+ throw new Error('A token needs the content hash of the draft');
42
+ if (!approver.includes('@'))
43
+ throw new Error('A token needs the approver address');
44
+ if (input.decision !== 'approve' && input.decision !== 'reject')
45
+ throw new Error('The decision is approve or reject');
46
+ const hours = Math.min(Math.max(Number(opts.validHours ?? 168) || 168, 1), MAX_HOURS);
47
+ const exp = Math.floor((opts.now ?? new Date()).getTime() / 1000) + Math.round(hours * 3600);
48
+ const claims = { draftId, contentHash, approver, decision: input.decision, exp };
49
+ const body = Buffer.from((0, seal_1.canonicalJson)(claims), 'utf8').toString('base64url');
50
+ return { token: `${PREFIX}.${body}.${mac(key, body)}`, expiresAt: new Date(exp * 1000).toISOString() };
51
+ }
52
+ function verifyToken(key, token, opts = {}) {
53
+ requireKey(key);
54
+ const value = typeof token === 'string' ? token.trim() : '';
55
+ if (!exports.TOKEN_PATTERN.test(value))
56
+ return { valid: false, reason: 'No valid approval token', claims: null };
57
+ const [, body, sig] = value.split('.');
58
+ const a = Buffer.from(sig, 'utf8');
59
+ const b = Buffer.from(mac(key, body), 'utf8');
60
+ if (a.length !== b.length || !(0, crypto_1.timingSafeEqual)(a, b))
61
+ return { valid: false, reason: 'The token was not issued here or was changed', claims: null };
62
+ let raw;
63
+ try {
64
+ raw = JSON.parse(Buffer.from(body, 'base64url').toString('utf8'));
65
+ }
66
+ catch {
67
+ return { valid: false, reason: 'The token cannot be read', claims: null };
68
+ }
69
+ const claims = {
70
+ draftId: String(raw.draftId ?? ''),
71
+ contentHash: String(raw.contentHash ?? ''),
72
+ approver: String(raw.approver ?? ''),
73
+ decision: raw.decision,
74
+ exp: Number(raw.exp),
75
+ };
76
+ if (!claims.draftId || !claims.contentHash || !claims.approver.includes('@') || !['approve', 'reject'].includes(claims.decision) || !Number.isFinite(claims.exp)) {
77
+ return { valid: false, reason: 'The token is incomplete', claims: null };
78
+ }
79
+ if (claims.exp * 1000 <= (opts.now ?? new Date()).getTime())
80
+ return { valid: false, reason: 'The link has expired', claims };
81
+ return { valid: true, reason: 'Token matches', claims };
82
+ }
83
+ //# sourceMappingURL=approvalToken.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approvalToken.js","sourceRoot":"","sources":["../../lib/approvalToken.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Change detection in the roleALPHA graph. roleALPHA sends no event when a step, role or
3
+ * agent is created or changed, so a scheduler takes a snapshot and compares it with the
4
+ * rows it stored the last time.
5
+ *
6
+ * Fail-closed: an entity that could not be read is neither new nor changed nor removed;
7
+ * its old row stays. Removals are reported only when the complete index was read.
8
+ */
9
+ import type { GraphSource, Relationship } from './graph';
10
+ import type { McpClient } from './mcp';
11
+ /** Entity types of the lean index (list_entities). */
12
+ export declare const ENTITY_TYPES: readonly ["process", "role", "agent", "policy", "organigramm", "project", "okr", "person"];
13
+ export interface IndexEntry {
14
+ uuid: string;
15
+ name: string;
16
+ entityType: string;
17
+ }
18
+ export interface EntitySnapshot {
19
+ uuid: string;
20
+ name: string;
21
+ /** Type of the lean index (process, role, agent, …). */
22
+ entityType: string;
23
+ customId: string | null;
24
+ /** Short hash per attribute, so a change names the attributes that changed. */
25
+ fields: Record<string, string>;
26
+ /** `TYPE>uuid` (outgoing) and `TYPE<uuid` (incoming), sorted. */
27
+ edges: string[];
28
+ fingerprint: string;
29
+ }
30
+ /** A stored snapshot row. `fields` and `edges` are JSON strings so any table can hold them. */
31
+ export interface SnapshotRow {
32
+ uuid: string;
33
+ entity_type: string;
34
+ name: string;
35
+ custom_id: string | null;
36
+ fingerprint: string;
37
+ fields: string;
38
+ edges: string;
39
+ seen_at: string;
40
+ }
41
+ export type ChangeKind = 'created' | 'changed' | 'removed';
42
+ export interface EntityChange {
43
+ change: ChangeKind;
44
+ uuid: string;
45
+ name: string;
46
+ entityType: string;
47
+ customId: string | null;
48
+ previousName: string | null;
49
+ changedFields: string[];
50
+ edgesAdded: string[];
51
+ edgesRemoved: string[];
52
+ }
53
+ export interface DetectResult {
54
+ /** No stored rows yet: the snapshot is stored and nothing is reported. */
55
+ baseline: boolean;
56
+ /** Whether the complete index was read; only then are removals reported. */
57
+ complete: boolean;
58
+ changes: EntityChange[];
59
+ /** Rows to insert or replace (by uuid). */
60
+ upsert: SnapshotRow[];
61
+ /** UUIDs whose rows are deleted. */
62
+ remove: string[];
63
+ /** Entities that could not be read this time; their rows stay as they are. */
64
+ unreadable: Array<{
65
+ uuid: string;
66
+ name: string;
67
+ error: string;
68
+ }>;
69
+ }
70
+ export declare function snapshotOf(entry: IndexEntry, details: {
71
+ name: string;
72
+ customId: string | null;
73
+ data: Record<string, unknown>;
74
+ }, rels: Relationship[]): EntitySnapshot;
75
+ export declare function toRow(s: EntitySnapshot, seenAt: string): SnapshotRow;
76
+ /** Rows as they come back from a table: drops rows without uuid or fingerprint. */
77
+ export declare function normalizeRows(raw: unknown): SnapshotRow[];
78
+ export declare function detectChanges(previousRaw: unknown, current: EntitySnapshot[], opts: {
79
+ complete: boolean;
80
+ types: string[];
81
+ unreadable?: DetectResult['unreadable'];
82
+ now?: Date;
83
+ reportBaseline?: boolean;
84
+ }): DetectResult;
85
+ /** Reads the complete lean index of one type, page by page. */
86
+ export declare function listIndex(client: McpClient, organisation: string, entityType: string, maxPages?: number): Promise<IndexEntry[]>;
87
+ export interface SnapshotRead {
88
+ snapshots: EntitySnapshot[];
89
+ unreadable: DetectResult['unreadable'];
90
+ }
91
+ /** Reads details and relationships of every index entry. A failed read marks the entity unreadable. */
92
+ export declare function readSnapshots(src: GraphSource, entries: IndexEntry[], concurrency?: number): Promise<SnapshotRead>;
@@ -0,0 +1,192 @@
1
+ "use strict";
2
+ /**
3
+ * Change detection in the roleALPHA graph. roleALPHA sends no event when a step, role or
4
+ * agent is created or changed, so a scheduler takes a snapshot and compares it with the
5
+ * rows it stored the last time.
6
+ *
7
+ * Fail-closed: an entity that could not be read is neither new nor changed nor removed;
8
+ * its old row stays. Removals are reported only when the complete index was read.
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.ENTITY_TYPES = void 0;
12
+ exports.snapshotOf = snapshotOf;
13
+ exports.toRow = toRow;
14
+ exports.normalizeRows = normalizeRows;
15
+ exports.detectChanges = detectChanges;
16
+ exports.listIndex = listIndex;
17
+ exports.readSnapshots = readSnapshots;
18
+ const crypto_1 = require("crypto");
19
+ const seal_1 = require("./seal");
20
+ /** Entity types of the lean index (list_entities). */
21
+ exports.ENTITY_TYPES = ['process', 'role', 'agent', 'policy', 'organigramm', 'project', 'okr', 'person'];
22
+ function hash(value, length = 16) {
23
+ return (0, crypto_1.createHash)('sha256').update((0, seal_1.canonicalJson)(value), 'utf8').digest('hex').slice(0, length);
24
+ }
25
+ /** Attributes that change without the entity changing in substance. */
26
+ const VOLATILE = /^(updated_?at|updatedAt|modified_?at|last_?(seen|run|login)(_?at)?|version_?count|views?)$/i;
27
+ function snapshotOf(entry, details, rels) {
28
+ const fields = {};
29
+ for (const [key, value] of Object.entries(details.data ?? {})) {
30
+ if (value === undefined || VOLATILE.test(key))
31
+ continue;
32
+ fields[key] = hash(value, 12);
33
+ }
34
+ const edges = [...new Set(rels.map((r) => `${r.type}${r.direction === 'outgoing' ? '>' : '<'}${r.uuid}`))].sort();
35
+ const name = details.name || entry.name;
36
+ return {
37
+ uuid: entry.uuid,
38
+ name,
39
+ entityType: entry.entityType,
40
+ customId: details.customId,
41
+ fields,
42
+ edges,
43
+ fingerprint: hash({ name, customId: details.customId, fields, edges }),
44
+ };
45
+ }
46
+ function toRow(s, seenAt) {
47
+ return {
48
+ uuid: s.uuid,
49
+ entity_type: s.entityType,
50
+ name: s.name,
51
+ custom_id: s.customId,
52
+ fingerprint: s.fingerprint,
53
+ fields: JSON.stringify(s.fields),
54
+ edges: JSON.stringify(s.edges),
55
+ seen_at: seenAt,
56
+ };
57
+ }
58
+ function parseJson(value, fallback) {
59
+ if (value && typeof value === 'object')
60
+ return value;
61
+ if (typeof value !== 'string' || value.trim() === '')
62
+ return fallback;
63
+ try {
64
+ return JSON.parse(value);
65
+ }
66
+ catch {
67
+ return fallback;
68
+ }
69
+ }
70
+ /** Rows as they come back from a table: drops rows without uuid or fingerprint. */
71
+ function normalizeRows(raw) {
72
+ return (Array.isArray(raw) ? raw : [])
73
+ .filter((r) => !!r && typeof r === 'object')
74
+ .filter((r) => typeof r.uuid === 'string' && r.uuid !== '' && typeof r.fingerprint === 'string')
75
+ .map((r) => ({
76
+ uuid: String(r.uuid).toLowerCase(),
77
+ entity_type: String(r.entity_type ?? ''),
78
+ name: String(r.name ?? ''),
79
+ custom_id: typeof r.custom_id === 'string' && r.custom_id !== '' ? r.custom_id : null,
80
+ fingerprint: String(r.fingerprint),
81
+ fields: typeof r.fields === 'string' ? r.fields : JSON.stringify(r.fields ?? {}),
82
+ edges: typeof r.edges === 'string' ? r.edges : JSON.stringify(r.edges ?? []),
83
+ seen_at: String(r.seen_at ?? ''),
84
+ }));
85
+ }
86
+ function detectChanges(previousRaw, current, opts) {
87
+ const now = (opts.now ?? new Date()).toISOString();
88
+ const types = new Set(opts.types);
89
+ // Rows of other types belong to another detector with another type filter; leave them alone.
90
+ const previous = normalizeRows(previousRaw).filter((r) => types.has(r.entity_type));
91
+ const unreadable = opts.unreadable ?? [];
92
+ const baseline = previous.length === 0 && !opts.reportBaseline;
93
+ const before = new Map(previous.map((r) => [r.uuid, r]));
94
+ const seen = new Set([...current.map((c) => c.uuid), ...unreadable.map((u) => u.uuid.toLowerCase())]);
95
+ const changes = [];
96
+ const upsert = [];
97
+ for (const snap of current) {
98
+ const old = before.get(snap.uuid);
99
+ if (old && old.fingerprint === snap.fingerprint)
100
+ continue;
101
+ upsert.push(toRow(snap, now));
102
+ if (baseline)
103
+ continue;
104
+ if (!old) {
105
+ changes.push({ change: 'created', uuid: snap.uuid, name: snap.name, entityType: snap.entityType, customId: snap.customId, previousName: null, changedFields: [], edgesAdded: snap.edges, edgesRemoved: [] });
106
+ continue;
107
+ }
108
+ const oldFields = parseJson(old.fields, {});
109
+ const oldEdges = new Set(parseJson(old.edges, []));
110
+ const newEdges = new Set(snap.edges);
111
+ const changedFields = [...new Set([...Object.keys(oldFields), ...Object.keys(snap.fields)])].filter((k) => oldFields[k] !== snap.fields[k]).sort();
112
+ if (old.name !== snap.name)
113
+ changedFields.unshift('name');
114
+ if ((old.custom_id ?? null) !== snap.customId)
115
+ changedFields.unshift('customId');
116
+ changes.push({
117
+ change: 'changed',
118
+ uuid: snap.uuid,
119
+ name: snap.name,
120
+ entityType: snap.entityType,
121
+ customId: snap.customId,
122
+ previousName: old.name !== snap.name ? old.name : null,
123
+ changedFields,
124
+ edgesAdded: snap.edges.filter((e) => !oldEdges.has(e)),
125
+ edgesRemoved: [...oldEdges].filter((e) => !newEdges.has(e)).sort(),
126
+ });
127
+ }
128
+ const remove = [];
129
+ if (opts.complete) {
130
+ for (const old of previous) {
131
+ if (seen.has(old.uuid))
132
+ continue;
133
+ remove.push(old.uuid);
134
+ if (!baseline) {
135
+ changes.push({ change: 'removed', uuid: old.uuid, name: old.name, entityType: old.entity_type, customId: old.custom_id, previousName: null, changedFields: [], edgesAdded: [], edgesRemoved: parseJson(old.edges, []) });
136
+ }
137
+ }
138
+ }
139
+ return { baseline, complete: opts.complete, changes, upsert, remove, unreadable };
140
+ }
141
+ // ---------------------------------------------------------------- reading
142
+ function obj(value) {
143
+ return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
144
+ }
145
+ /** Reads the complete lean index of one type, page by page. */
146
+ async function listIndex(client, organisation, entityType, maxPages = 20) {
147
+ const out = [];
148
+ let offset = 0;
149
+ for (let page = 0; page < maxPages; page++) {
150
+ const res = obj(await client.callTool('list_entities', { organisation, entityType, offset }));
151
+ const rows = Array.isArray(res.entities) ? res.entities : [];
152
+ for (const raw of rows.map(obj)) {
153
+ const uuid = typeof raw.uuid === 'string' ? raw.uuid.toLowerCase() : '';
154
+ if (uuid)
155
+ out.push({ uuid, name: String(raw.name ?? ''), entityType });
156
+ }
157
+ if (res.hasMore !== true || rows.length === 0)
158
+ return out;
159
+ offset = typeof res.nextOffset === 'number' ? res.nextOffset : offset + rows.length;
160
+ }
161
+ throw new Error(`The index of "${entityType}" has more than ${maxPages} pages; narrow the entity types`);
162
+ }
163
+ /** Runs `fn` over `items` with at most `limit` calls at a time. */
164
+ async function pool(items, limit, fn) {
165
+ const out = new Array(items.length);
166
+ let next = 0;
167
+ const workers = Array.from({ length: Math.max(1, Math.min(limit, items.length)) }, async () => {
168
+ while (next < items.length) {
169
+ const i = next++;
170
+ out[i] = await fn(items[i]);
171
+ }
172
+ });
173
+ await Promise.all(workers);
174
+ return out;
175
+ }
176
+ /** Reads details and relationships of every index entry. A failed read marks the entity unreadable. */
177
+ async function readSnapshots(src, entries, concurrency = 4) {
178
+ const snapshots = [];
179
+ const unreadable = [];
180
+ await pool(entries, concurrency, async (entry) => {
181
+ try {
182
+ const [details, rels] = await Promise.all([src.details(entry.uuid), src.relationships(entry.uuid)]);
183
+ snapshots.push(snapshotOf(entry, details, rels));
184
+ }
185
+ catch (error) {
186
+ unreadable.push({ uuid: entry.uuid, name: entry.name, error: String(error.message ?? error).slice(0, 300) });
187
+ }
188
+ });
189
+ snapshots.sort((a, b) => a.uuid.localeCompare(b.uuid));
190
+ return { snapshots, unreadable };
191
+ }
192
+ //# sourceMappingURL=changes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"changes.js","sourceRoot":"","sources":["../../lib/changes.ts"],"names":[],"mappings":"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"}