Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Changelog

## 0.1.0 Beta

First self-hosted Evidence and BREG agricultural-holdings pilot.

- Separate OpenFn adaptors use the maintained Registry Stack 0.27.0 client.
- Registration uses explicit idempotency. Corrections follow governed change
requests with separate reviewer approval and manual application.
- Committed events pass through an authenticated bridge, request verified signed
registration evidence, and produce atomic, deduplicated destination updates.
- Pinned upstream Lightning and worker packaging, Compose setup, repeatable
workflow provisioning, and operator recovery commands are included.
- Linux acceptance covers fresh installation, the complete workflow journey,
restart recovery, failed delivery, failed workflow steps and queued work.

### Release scope

This is a source release for supervised synthetic pilots. Setup builds the
containers locally from the committed dependency locks and pinned images.
The worker requires Linux amd64; native adaptor imports also support Linux
arm64. The assertion confirms registration only, without disclosing the holding
name or establishing ownership or programme eligibility.

Relay, legacy Notary, hosted OpenFn, wallet issuance, npm publication and OpenFn
catalogue acceptance are outside this beta. Root dependency overrides apply to
the locked source deployment and do not accompany independently installed npm
packages.

Stop/start preserves data; reset is a separate explicit destructive operation.
See the [operator guide](README.md) for private inputs, retention and recovery.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Registry Stack adaptors for OpenFn

**Beta:** intended for supervised, self-hosted pilots. APIs and deployment
configuration may change before 1.0.

A self-hosted synthetic pilot connecting upstream OpenFn Lightning to Registry
Stack Base Registry Engine (BREG) and signed Evidence. It demonstrates registration,
reviewed correction, and an idempotent downstream update containing a verified
Expand Down
24 changes: 18 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@
"dependencies": {
"@openfn/language-http": "7.3.3"
},
"overrides": {
"@openfn/language-common@3.3.4": {
"csv-parse": "7.0.2"
},
"@openfn/language-common@3.3.5": {
"csv-parse": "7.0.2"
}
},
"engines": {
"node": ">=22.12.0"
}
Expand Down
63 changes: 63 additions & 0 deletions pilot/test/common-compatibility.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { createRequire } from 'node:module';
import { dirname, join } from 'node:path';
import { Readable } from 'node:stream';
import test from 'node:test';
import { pathToFileURL } from 'node:url';
import * as evidence from '../../packages/registry-evidence/src/index.js';
import * as breg from '../../packages/registry-breg/src/index.js';
import * as http from '@openfn/language-http';

const rootRequire = createRequire(import.meta.url);

function dependency(parentRequire, name) {
let directory = dirname(parentRequire.resolve(name));
while (true) {
try {
const manifestPath = join(directory, 'package.json');
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
if (manifest.name === name) return { manifest, require: createRequire(manifestPath) };
} catch (error) {
if (error.code !== 'ENOENT') throw error;
}
const parent = dirname(directory);
if (parent === directory) throw new Error(`Cannot find package manifest for ${name}`);
directory = parent;
}
}

for (const [name, adaptor, parentRequire] of [
['Evidence', evidence, createRequire(new URL('../../packages/registry-evidence/package.json', import.meta.url))],
['BREG', breg, createRequire(new URL('../../packages/registry-breg/package.json', import.meta.url))],
['HTTP', http, dependency(rootRequire, '@openfn/language-http').require],
]) {
test(`${name} common CSV operations remain compatible with the scoped parser override`, async () => {
const common = dependency(parentRequire, '@openfn/language-common');
assert.equal(typeof adaptor.parseCsv, 'function');
const csv = '\uFEFFname,value\n "Synthetic, Farm" , 42 \n\nSecond, 7\n';
const expected = [{ name: 'Synthetic, Farm', value: '42' }, { name: 'Second', value: '7' }];
const result = await adaptor.parseCsv(csv)({ data: {}, configuration: {} });
assert.deepEqual(result.data, expected);

const chunks = [];
const streamed = await adaptor.parseCsv(Readable.from([csv.slice(0, 20), csv.slice(20)]), { chunkSize: 1 }, (state, records) => {
chunks.push(records);
return { ...state, rows: [...state.rows, ...records] };
})({ data: {}, configuration: {}, rows: [] });
assert.deepEqual(streamed.rows, expected);
assert.deepEqual(chunks, expected.map(row => [row]));

// Exercise the actual parser selected for this common package, including its ESM API.
const parserRequire = dependency(common.require, 'csv-parse').require;
const { parse } = await import(pathToFileURL(join(dirname(parserRequire.resolve('csv-parse')), '../../lib/index.js')));
const records = [];
for await (const record of parse('__proto__,__proto__,name\nfirst,second,synthetic\n', { columns: true, group_columns_by_name: true })) records.push(record);
assert.equal(records.length, 1);
assert.equal(Object.getPrototypeOf(records[0]), Object.prototype);
assert.equal(Object.hasOwn(records[0], '__proto__'), true);
assert.deepEqual(records[0].__proto__, ['first', 'second']);
assert.equal(records[0][0], undefined);
assert.equal(records[0].name, 'synthetic');
});
}
Loading