Skip to content

fix(roles): support colons in smart-action names in the roles CSV - #813

Open
Gawtier wants to merge 1 commit into
mainfrom
fix/roles-csv-colon-in-action-name
Open

fix(roles): support colons in smart-action names in the roles CSV#813
Gawtier wants to merge 1 commit into
mainfrom
fix/roles-csv-colon-in-action-name

Conversation

@Gawtier

@Gawtier Gawtier commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Context

Reported by a customer (Spendesk) round-tripping the new roles commands: forest roles:export produced a CSV, and forest roles:apply on that same untouched file failed with

Error: Unrecognized CSV column "Organisation:SAML SSO #2: Edit SSO config:trigger".

The smart action exists and the export extracted it verbatim — the asymmetry was in the reader.

Root cause

The export emits one column per smart action as collection:action:suffix. applyHeader read it back with a plain split(':') and only accepted 2 or 3 segments. A smart action whose own name contains a colon — here SAML SSO #2: Edit SSO config — yields 4 segments and falls through to the Unrecognized CSV column throw.

The bug behind the bug

The export path was silently wrong too. buildCellForColumn re-split the header string it had just built; with 4 segments it missed both the 2- and 3-segment cases and hit the trailing return 'false'. Every cell of such an action exported as false, regardless of the actual permissions.

So had the parser not thrown, roles:apply would have silently revoked trigger on that action for every role. The error was acting as an accidental guard rail.

Fix

  • Read path — new parseHeader() keys off the trailing suffix instead of splitting on every colon. The CRUD (browse/read/add/edit/delete/export) and smart-action (trigger/approvalRequired/userApproval/selfApproval/hasConditions) suffix sets are disjoint, so the last segment is enough to tell the two column shapes apart; the action name is whatever sits between the collection and the suffix, colons included.
  • Write pathcollectColumns now returns structured descriptors ({header, collectionName, actionName, suffix}) instead of strings, so a header is never re-parsed to locate its value.

Side effect, for free: collection names containing a colon (schema-prefixed tables) now work on CRUD columns.

Known limitation, documented in a comment: a header where both the collection and the action contain a colon stays ambiguous, and keeps the export's convention that the collection is the first segment. Resolving it properly would mean passing the known collection list into parseWide (roles:apply has it via fullRoles) — out of scope here.

Tests

3 new cases in roles-csv.unit.test.js: export cells for a colon-bearing action name, a verbatim round-trip of the Spendesk column, and a colon-bearing collection name on a CRUD column. Full roles-csv unit suite plus the 4 roles:* command suites pass (32 tests), lint clean.

Also verified end-to-end that export → apply on the Spendesk shape now yields 0 ops, which was the customer's expectation.

Note for support

The CSV the customer already exported is unusable as-is — the cells for that action read false while the real grants are likely true. They need to re-export with this fix rather than re-apply the file they have.

🤖 Generated with Claude Code

Note

Fix roles CSV parsing to support colons in smart-action names

  • Replaces naive header.split(':') logic in collectColumns, buildCellForColumn, and applyHeader with structured column descriptors and a parseHeader helper that uses the last colon to locate the trailing suffix.
  • Smart-action names containing colons are now correctly parsed by extracting collectionName from the first segment and actionName from the remainder, preserving internal colons.
  • Previously, smart actions with colons in their names would produce incorrect false cell values and could be misclassified or rejected during import.
  • Tests in roles-csv.unit.test.js cover colon-containing smart-action round-trips and CRUD collection names with colons.

Macroscope summarized d73adad.

`forest roles:export` emitted a column per smart action as
`collection:action:suffix`, and `roles:apply` read it back with a plain
`split(':')` that only accepted 2 or 3 segments. A smart action whose own
name contains a colon — e.g. "SAML SSO #2: Edit SSO config" — produced 4
segments, so applying a freshly exported CSV failed with
`Unrecognized CSV column`.

The export was silently wrong too: buildCellForColumn re-split the header
string it had just built, fell through the 2/3-segment cases and returned
'false' for every cell of such an action, regardless of the real
permissions. Applying that CSV would have revoked those grants.

Parse the header from its trailing suffix instead of splitting on every
colon (the CRUD and smart-action suffix sets are disjoint, so the last
segment identifies the column shape), and carry structured column
descriptors through the export path so a header is never re-parsed. As a
side effect, collection names containing a colon now work on CRUD columns.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant