Companion to spec.md. Each item links to the spec section (
§X) it implements and any risk (R#) it addresses. This file is the single source of truth for status — check items off here as they land. Convention:[ ] B# — title (→ spec §X, R#).Note (v0.4): the auth design in
B2/B3/B4(custom token helper + grant selector + password grant) is superseded by theoAuth2Apirefactor — seeB19–B24.
- B1 — Scaffold via
@n8n/node-cli+Taskfile.yml(→ §1, §5, §9) Scaffold withnpm create @n8n/node(programmatic template): generatespackage.json(correctn8nfield),tsconfig, eslint (eslint-plugin-n8n-nodes-base) + prettier, then8n-nodebuild/dev/lint/release scripts, and a provenancepublish.yml. No gulp — the CLI handles the build/icons. Set name@eccenca/n8n-nodes-corporate-memory, keywordn8n-community-node-package,LICENSE(MIT),README.md,.gitignore/.npmignore; keep the existing.markdownlint.json. Requires Node.js v22+. Add aTaskfile.yml(go-task) wrapping the lifecycle + dependency targets to operate the project, each delegating to the generatedn8n-node/npm scripts:deps(install),build,lint,lint:fix,dev(n8n-node dev, hot reload),test,release,clean, and adefaultthat lists targets. Verifytask buildproducesdist/. - B2 —
CorporateMemoryApicredential (→ §4)grantTypeselector for bothclient_credentialsandpassword(resource-owner) flows. Fields:baseUrl,clientId,clientSecret(password; required for client-credentials, optional for password if the Keycloak client is public),username+password(shown for the password grant),tokenUrl(default Keycloak URL, overridable), optionaldiBaseUrl/dpBaseUrl. No auto-authenticate(token fetched in helper). - B3 —
GenericFunctions(→ §4,R1,R4)getToken()(client_credentials, in-memory cache keyed by client+token-url, refresh ~30s early),cmemApiRequest()(resolve component base, attach Bearer,this.helpers.httpRequest), base-URL normalization (strip trailing slash, avoid double slashes). - B4 — Credential test (→ §4,
R3) Declarative credentialtest(required by n8n verification — a node-leveltestedByis not accepted). Key gotcha: n8n only invokespreAuthenticationwhen the credential declares ahiddenproperty withtypeOptions.expirable: true— so a hiddenexpirablesessionTokenfield is required. With it:preAuthenticationfetches the token (getCmemToken) → stored insessionToken;authenticateinjectsBearer {{$credentials.sessionToken}}; the test GETs{dp}/userinfo(base URL from static$credentialsfields, since the test URL resolves beforepreAuthentication). - B5 — Node skeleton + versioning + router (→ §5, §7)
Corporate Memorynode,version: [1]/defaultVersion: 1, resource→operation dropdowns,execute()dispatch,Continue On Fail+NodeApiErrormapping. - B6 — Workflow → Execute (sync) (→ §3.1, §5, §6,
R2)POST /api/workflow/result/{project}/{workflow}. Project/Workflow dropdowns (loadOptions off/api/workflow/info);payloadTypeNone/JSON/XML/CSV (inputContent-Type);resultFormatJSON/XML/CSV/N-Triples (outputAccept);splitOutput.204→{ executed, hasResult: false }. Verified end-to-end on docker.localhost. - B7 — Workflow → Execute (Async) (→ §3.1, §6)
POST /api/workflow/executeAsync?output:type=…; emits{ activityId, instanceId }. Async result-polling + cancellation (activity API) deferred toB16. - B8 — Unit tests: auth (→ §9)
getTokencache/expiry + error mapping. - B9 — CI + local-dev docs (→ §9,
R7) GitHub Actions from the scaffold: PR → build/lint/test; tagv*→n8n-node releasepublishing to npm with provenance (mandatory for community nodes from 2026-05-01). Document local dev viatask dev(n8n-node dev, hot reload) in the README +Taskfile.yml— nonpm link.
- B10 — SPARQL → Select Query (→ §3.2, §5)
GET /proxy/default/sparql?query=…withAccept: application/sparql-results+json; optional repeatabledefault-graph-uri/named-graph-uri(Options collection). Verified on docker.localhost. - B11 — SPARQL result flattening (→ §5, §6, §9)
{head,results.bindings}→ one item per row;simplifytoggle (string values vs full binding objects); unbound vars omitted; ASK →{ boolean }. Unit-tested from a fixture. - B12 — Query Catalog → List Queries + catalog-graph awareness (→ §3.3, §5)
Queries span multiple catalog graphs, discovered via a labelled
shui:SparqlQuerySPARQL (mirrors CMEM's catalog selector).getQueryCatalogsloadOptions lists those graphs; a Catalog Graph selector (default = All Catalogs) scopes both List Queries and thegetCatalogQueriesqueryIridropdown. List emits one item per saved query (iri,label,description,queryText,queryTypes,catalogGraph). Verified on docker.localhost (4 catalogs). - B13 — Query Catalog → Run Report (parameter handover) (→ §3.3, §5)
GET /api/queries/reports/perform?queryIri=…&substitutions=<json>—substitutionsbuilt from a fixedCollection of name/value pairs (buildSubstitutions()) plus a raw-JSON escape hatch and optionalcontextGraph. Parameter handover verified on docker.localhost. - B14 — CSV → items parser (→ §5, §6, §9,
R5) Quote/newline-aware CSV parse (header row = keys) → one item per row;Parse CSV Into Itemstoggle returns the raw CSV string instead. Unit-tested (quoting / embedded comma / newline / escaped quotes).
The Cloud reviewer requires the credential to extend n8n's built-in
oAuth2Api(clientCredentials) instead of the custom token helper. This supersedes the auth parts ofB2/B3/B4and resolvesB15. The password grant is dropped (R8) — it is not supported byoAuth2Apiand CMEM rejects the generic Basic/header alternative. Spec §4 rewritten. Order: B19 → B20 → B21 → B22 → B23 → B24.Verification rename: lint rule
cred-class-oauth2-namingforces anOAuth2marker on anyoAuth2Api-extending credential, so the credential was renamedCorporateMemoryApi/corporateMemoryApi→CorporateMemoryOAuth2Api/corporateMemoryOAuth2Api(displayName eccenca Corporate Memory OAuth2 API), and the file/n8n.credentialspath with it.Field labels/order (post-review tweak): renamed CMEM Base URL → Base URL; moved the token URL to the bottom by hiding inherited
accessTokenUrland re-exposing it as the optional customtokenUrlfield. n8n appends custom fields after all inherited ones (mergeNodeProperties, verified), so Base URL cannot be the absolute first field — rendered order is Client ID · Client Secret · Send Additional Body Properties · Allowed HTTP Request Domains · Base URL · OAuth Token URL · DI/DP Base URL. See spec §4 "Field order".
-
B19 — Credential extends
oAuth2Api(clientCredentials) (→ §4,R1,R8) Rewrotecredentials/CorporateMemoryOAuth2Api.credentials.ts:extends = ['oAuth2Api']; hidden overridesgrantType=clientCredentials,scope='',authentication='body',authQueryParameters=''(inheritedauthUrlstays hidden via oAuth2Api's own grant-type displayOptions).accessTokenUrl= visible/editable string, default'={{$self["baseUrl"]}}/auth/realms/cmem/protocol/openid-connect/token'— prefilled frombaseUrlbut user-overridable (R4); fallback (hidden ternary +tokenUrlfield) documented in spec §4 if the visible default mis-renders in the UI (validate in B22). Kept custombaseUrl(required),diBaseUrl,dpBaseUrl; inheritedclientId/clientSecretvisible. Removed thegrantTypeoptions selector,username,password,tokenUrl,sessionToken,preAuthentication,authenticate. Kept theeccencalowercase-brand lint exception. -
B20 —
GenericFunctionsuse n8n-managed auth (→ §4,R1) RemovedgetCmemToken,tokenCache,clearCmemTokenCache,resolveTokenUrl,buildTokenRequestBody,TOKEN_EXPIRY_SKEW_MS,ApplicationErrorimport, and theTokenResponse/TokenCacheEntryinterfaces. NarrowedCorporateMemoryCredentialsto{ baseUrl; clientId?; clientSecret?; diBaseUrl?; dpBaseUrl? }andCmemRequesterto{ helpers: { httpRequestWithAuthentication(credentialsType, options) } }.cmemApiRequest→helpers.httpRequestWithAuthentication('corporateMemoryOAuth2Api', options)(no manual Bearer). KeptresolveComponentBaseUrl,normalizeBaseUrl, all SPARQL/CSV/substitution helpers. -
B21 — Node wiring (→ §5) No behaviour change; the
this as unknown as CmemRequestercast inexecute()and the fourloadOptionsnow resolves tohttpRequestWithAuthentication.getCredentialsstill fetched for base-URL resolution;NodeApiErrormapping unchanged. Credential name updated in the node'scredentials[]. Idiomatic-alignment pass (post-review): reviewedn8n-nodes-baseself-hosted analogs —Elasticsearch,Gitlab,Grafana(all store a base/server URL in the credential). Refactored the helpers to the standardthis-based shape:cmemApiRequest/listQueryCatalogGraphs/listCatalogQueriestakethis: IExecuteFunctions | ILoadOptionsFunctions(CmemFunctions), read the credential viathis.getCredentials(…), and are invoked with.call(this, …). Dropped the customCmemRequestertype and allrequester/credentialsthreading from the node. This both fixes the loadOptions failure ("this.getNode is not a function") and matches how every core node wires authenticated requests. See spec §4. Validation findings:- Request wiring matches
gitlabApiRequest/grafanaApiRequestexactly:this-based, read creds internally, derive base URL from a credential field with a trailing-slash strip (server.replace(/\/$/, '')/tolerateTrailingSlash),helpers.…WithAuthentication.call(this, …). (Those two use the legacyrequestWithAuthentication; we use the modernhttpRequestWithAuthentication— preferred for new nodes.) - Credential matches
GitlabOAuth2Api: hiddengrantType, base/serverfield, hiddenaccessTokenUrlderived via={{$self["…"]}}, hiddenscope/authQueryParameters/authentication: 'body'. (We add an optional editabletokenUrloverride + field reorder; GitLab derives directly fromserverwith no override — both valid.) version: 1confirmed idiomatic: plain number used by 286 core nodes (incl. Grafana); arrays are only for multi-version nodes. No conversion needed — keptversion: 1.
- Request wiring matches
-
B22 — Credential test (→ §4,
R3) Kept the declarativetestGET{dp}/userinfo; removed thesessionToken/preAuthenticationworkaround. Pending manual check: verify Test/save in the n8n UI against a real CMEM (task dev), and confirm the visibleaccessTokenUrldefault renders editably (else apply the B19 fallback). -
B23 — Tests (→ §9) Rewrote
test/CorporateMemoryOAuth2Api.test.ts(extendsoAuth2Api; hiddengrantType/authentication; visible derivedaccessTokenUrl; password fields &sessionTokenasserted absent) and the auth parts oftest/GenericFunctions.test.ts/ the node test (deleted the token-cache/getCmemTokensuites; assertcmemApiRequest→httpRequestWithAuthentication('corporateMemoryOAuth2Api', …)with the resolved URL + method). SPARQL/CSV/substitution suites kept green. 31 tests pass;task lint+task buildclean. -
B24 — Docs + reviewer reply (→ §1) Updated the README credentials section (client-credentials only; new credential name;
baseUrl/derivedaccessTokenUrl/clientId/clientSecret, optionaldiBaseUrl/dpBaseUrl; password-grant note).task lint+task test(31) +task buildclean. Reviewer reply drafted below. Left to the release step (not done here):package.jsonversion bump to0.4.0and the auto-generatedCHANGELOG.mdare produced bytask release(n8n-node release/ release-it), the same flow that cut 0.3.0 — don't hand-edit. Then publish (npm, with provenance) and reply to the reviewer.Draft reviewer reply:
Thanks for the review. v0.4.0 refactors the credential to extend n8n's built-in
oAuth2Apiusing the client_credentials grant — n8n now performs the token exchange, caching and refresh; our customgetCmemTokenhelper and in-memory cache are removed, and all requests go throughhttpRequestWithAuthentication. The credential is renamed to eccenca Corporate Memory OAuth2 API to satisfy the OAuth2 naming lint rule.We dropped the resource-owner password grant rather than request a waiver:
oAuth2Apisupports onlyauthorizationCode/clientCredentials/pkce, and CMEM/Keycloak only accepts a Keycloak-issued Bearer JWT (not the HTTP-Basic style of the generic username/passwordauthenticateexamples), so a password grant would have required reintroducing custom token code. The client-credentials service-account flow covers the automation use case. Published as v0.4.0; ready for re-review.
- B15 —
oAuth2ApiclientCredentials variant (→ §4, §7,R1) Done viaB19(v0.4) — adoptedextends: ['oAuth2Api']withclientCredentials, ahead of the original "once n8n#16857 is fixed" plan, because n8n Cloud verification requires the built-in credential. - B16 — Additional CMEM surface (→ §2)
SPARQL CONSTRUCT/ASK/UPDATE; graph-store read/write; vocabulary/SHACL ops; trigger node;
async workflow result-polling + cancellation via the activity API
(
/workspace/activities/*,/api/workflow/executionResult). - B17 — Verified community node submission (→ §1,
R7) Two tracks. Unverified (publish to npm; users self-install by package name) — essentially met today. Verified (listed/installable on n8n Cloud) needs the checklist below. Already satisfied: one third-party service; TypeScript +n8n-nodetooling; no runtime dependencies; no env-var/filesystem access; per-item error handling; MIT; English; README;n8n-node lintclean. Gaps for verification:- Public GitHub repo
eccenca/n8n-nodes-corporate-memory; npmrepository/homepagematch. - GitHub Actions
ci.yml+publish.yml(npm publish with provenance,id-token: write). - Publish
@eccenca/n8n-nodes-corporate-memoryto npm, then passnpx @n8n/scan-community-package @eccenca/n8n-nodes-corporate-memory. - Consistent author/maintainer identity across npm + GitHub.
-
CorporateMemory.node.jsoncodex (categories Data & Storage / Development / Analytics, README doc links, search aliases). Ships indist; lint + scan still pass. - Nice-to-have: an example workflow in the README.
- Submit via the n8n Creator Portal and pass review.
- Public GitHub repo
- B18 — Resolve open questions (→ §8)
Remaining: obtain a demo/staging CMEM for repeatable CI verification. (
R2/R3/R6resolved; manual verification done against docker.localhost.)
- M1 (v1): B1–B9 — credential proven end-to-end + Workflow Execute against a real CMEM.
- M2 (v2): B10–B14 — SPARQL SELECT + template reports usable as n8n items.
- M3 (release): B17 + semver
1.0.0+ (optional) verified listing.