| name | edit-resource |
| description | Edit or create resources in the Flexibility Information System. Use when the user asks to add a new resource or modify an existing one. This can be changing fields, constraints, permissions, documentation, UI pages, tests, or any other aspect of a resource. |
| metadata | {"author":"enzo.crance","version":"1.0.0"} |
Edit resource
End-to-end workflow for editing or creating a resource in the FIS project.
Scoping the change
Before starting, identify which layers of the stack are actually affected.
Not every change requires touching all layers.
For example, a purely frontend change (e.g., displaying an already-accessible field on a new page)
does not require documentation, database, backend, or test changes.
Conversely, adding a new field to a resource will typically touch every layer.
When the change only affects the frontend, check that the data the UI will fetch is already
accessible to the relevant roles by verifying:
- The permissions CSV grants
R on the fields for the roles that will see the data.
- The API views / RLS policies have branches for those roles.
If both are satisfied, proceed directly to the frontend section.
If not, discuss the required backend/permission changes with the user before proceeding.
Project structure
The project is organised with the following components, to edit in the same order:
Documentation
This is a research project, so this is the most important part and must be valid at all times.
The docs directory contains Markdown files for overall architecture, design decisions, and
resource documentation.
Resource documentation is in docs/resources, with one file per resource.
The FIS project relies heavily on code generation from source-of-truth inputs.
Each resource file includes auto-generated field tables and permission tables, along with
hand-written sections on business rules and notifications.
Any change to a resource should start with a change in the documentation when it concerns its
fields or business rules for example.
As it is generated, the first file to change is openapi/resources.yml and
local/input/permissions.csv, which are the source of truth for the field definitions and
permissions, respectively.
There is a section in this skill for each of these two files, and a section for the resource
documentation file.
Then run just openapi and just permissions to update the generated documentation tables.
Database
The PostgreSQL database in db is organised in several schemas.
A flex schema is used to define the internal tables and data layout of our resources.
It also contains RLS policies and internal views that can be practical to avoid very verbose joins
and reduce query complexity.
An api schema is laid on top and contains views and functions that will later be exposed in our
API.
This allows changing the internal data layout without impacting the API, as long as we maintain the
same API views.
The other schemas are very specific to a certain aspect of the system and usually they do not need
to be checked when editing a resource.
All stateful changes to the database must be made through Liquibase migrations, which are defined in
changelog files.
Most of the resources that have been modified at least once have a dedicated changelog file next to
their definition file.
We also maintain the definitions for documentation purposes, but also to be able to deploy a new
instance of the system without having to replay all the migrations.
A section of this skill is dedicated to editing the flex tables and RLS policies.
The api view will usually be generated, except in very specific cases.
Another section covers the migrations.
Backend
The backend is a Go application in backend/, with a PostgREST proxy for standard CRUD operations
and custom handlers for specific endpoints.
Usually, modifications do not require changes to the endpoints, but resource creation requires to
add new endpoints which usually look like this (when all CRUD operations are enabled):
mux.Handle("GET /<resource>", dataListPostgRESTHandler)
mux.Handle("POST /<resource>", dataPostgRESTHandler)
mux.Handle("GET /<resource>/{id}", dataPostgRESTHandler)
mux.Handle("PATCH /<resource>/{id}", dataPostgRESTHandler)
mux.Handle("DELETE /<resource>/{id}", dataPostgRESTHandler)
mux.Handle("GET /<resource>_history", dataListPostgRESTHandler)
mux.Handle("GET /<resource>_history/{id}", dataPostgRESTHandler)
Data models are generated by sqlc from the database schema.
The just sqlc command should be run after any change to the database schemas to update the models.
Touching a resource through the API will trigger events and notifications to the users.
Custom code is implemented in backend/event to identify notification recipients based on the
resource and operation performed (a new case may be needed in the dispatcher, with a new query in
the models.sql file).
If the user mentions notifications, this should be part of the workflow.
Otherwise, this can be done as a separate task.
A Kotlin backend is being developed in parallel, but it has not yet taken over, so it should not be
modified as part of this workflow.
Tests
Tests to be edited are the Python API tests in test/api_client_tests.
They use the auto-generated Python API client from test/flex, auto-generated from the OpenAPI
specification, which allows for easy and up-to-date API tests.
API tests should be added or modified for any change that impacts the API contract, such as field
changes, validation rules, or permissions.
just test --api should always pass after editing or creating a resource.
There is usually one file per resource to test.
A setup fixture is used to create any necessary data that might be needed in all the tests in the
file, such as creating dependent resources, etc.
Tests should cover the happy path for all allowed operations (e.g., create, read, update, delete),
but also some error cases based on the validation rules and RLS policies defined on the resource
(for instance, a resource depending on some qualification or status to be at the right place in
order to be created or edited, or the user having a specific role).
There should be some test data for each resource in the database (db/test_data/test_data.sql).
It should be modified when needed, so that the tests have the necessary data to run, if it is data
that cannot be created in a fixture.
Frontend
The frontend is a React application in frontend, using React Admin as an initial framework but
under step by step refactoring towards the EDS design system and custom components.
Each resource has its own folder in frontend/src/, with the React Admin pages (List, Show, Input,
History).
The React Admin resource registration is done in frontend/src/resources/index.tsx, but each
resource is responsible for its own registration and subresources in a dedicated file under
frontend/src/resources/.
If the resource is a relation, it probably has a parent and a child resource, then the registration
should be in the parent's file.
Changes to the UI may require changes to the API, or even changes to the RLS policies, so you
should always consider such implications and discuss them with the user.
After any frontend change, run the TypeScript compiler to verify there are no type errors:
npx tsc --noEmit
Check the frontend part of the details section if frontend changes are needed in your task.
Details about some aspects of the workflow
Resources YAML
This is one of the main source of truth files.
It defines all the fields for each resource at the api schema level, along with their description,
type, some constraints, name in other languages for internationalisation, and extra metadata used
for generation.
Each resource is an entry in the top-level resources list.
Internationalisation is given as a {language->translation} map in the x-intl key at both resource
and property level.
Enum values also have their own translations for each value.
Resource-level attributes
CRUD operations to generate are defined as a list in the operations key, operations for the
history resource being defined in the history key.
Setting audit to true will enable the audit trail, which means that the system will automatically
record the author and timestamp of each change to the resource, and make them available in the API.
Setting generate_views to false will skip the generation of the API views, which is useful for
resources with hand-written API views that do not follow the standard patterns.
history_rls works the same way by disabling generation of RLS policies for the history resource,
which is also useful in cases with more customisation.
Setting comments to true will automatically add a comment sub-resource, which is a common pattern
in the system.
Property-level attributes
Attributes at this level are usually mapped to OpenAPI schema properties, so the usual JSON Schema
validation keywords apply: readOnly, nullable, format, type, enum, minimum, maximum,
maxLength, etc.
readOnly means the field won't be included in the Create and Update request schemas, but it will
be in the response schema.
nullable means the field can be null in the database and in the API.
The combination of the right values for these attributes determines whether a field is required or
not.
Always give an example value for better documentation and easier testing.
When an optional field has a default value, do not forget to add it in the schema.
For more complex rules, we use x-* fields that will not be included in the OpenAPI specification,
but will be used when generating code.
x-filter is used to mark fields that should be filterable in the list endpoints.
x-foreign-key is used to mark foreign key references, and it should include the referenced
resource and field.
x-no-update is used to mark fields that can be set on create but not updated afterwards, which is
a common pattern for fields like reference IDs that define the record but should not be changed
afterwards.
x-details includes extra documentation that is included in the generated Markdown field table.
recorded_at and recorded_by fields are generated fields, they should never appear in this file.
They will be generated if audit is set to true on the resource.
Permissions CSV
This is the other main source of truth file, for permissions.
It is just a CSV file giving the CRUD permissions for each role at both resource and field level.
The resource-level permissions are used for the special lookup permission (L) and delete
permission (D), while the field-level permissions are used for read (R), create (C), and
update (U) permissions.
Header: RESOURCE;FIELD;ANON;BRP;ES;EU;FISO;MO;SO;SP;TP;ORG;ENT;IEN;ID
Role abbreviations:
ANON = Anonymous
BRP = Balance Responsible Party
ES = Energy Supplier
EU = End User
FISO = FIS Operator
MO = Market Operator
SO = System Operator
SP = Service Provider
TP = Third Party
ORG = Organisation
ENT = Entity
IEN = Internal Event Notification
ID = Internal Data
A resource-level row (empty FIELD) defines resource-level permissions, and field-level rows define
per-field permissions.
Not having R permission on a field will cause errors in the frontend, so make sure to always
include it for roles that can log in (not internal) if the field is supposed to be shown in the UI.
Anyway, further RLS rules will be defined so a simple R does not mean giving full read access.
The permissions file is also used at the api schema level, so it should reflect API fields, not
table fields.
For instance, recorded_at and recorded_by should always be included for resources with audit,
but record_time_range is just an internal implementation detail of the audit trail, so it should
not appear there.
If the resource has history, also add the resource name to the history_enabled list in
the three permissions_to_*.py scripts, so permissions are generated correctly.
Resource documentation file
Make sure a Markdown file exists for the resource.
Copy the structure from an existing resource (e.g., docs/resources/party.md).
Required sections:
- Title and description -- what the resource is
- Business Identifiers -- how the resource is identified
- Relevant links -- API docs link, download link
- Fields -- you can copy the table, it will be regenerated anyway
- Validation Rules -- hand-written table of business rules
- Notifications -- hand-written table of who gets notified on actions
- Authorization -- two sub-sections:
- Resource Level Authorization (RLA) -- hand-written per-role policy tables
- Field Level Authorization (FLA) -- you can also copy this one, it will be regenerated
Tables and RLS
Creating or modifying a resource may require changes to the database tables and RLS policies.
First, always check that there is a valid Liquibase header in the created files so they will be
recognised as such.
They should also be added to the changelog, in the correct order (after what they depend on).
- include:
file: ./<resource>.sql
relativeToChangelogFile: true
Each SQL instruction will be in a changeset.
Always use runOnChange:true with idempotent changesets (IF NOT EXISTS, OR REPLACE, etc).
You can copy the table structure from other similar resources.
Make sure to explicitly name constraints, to allow changing them later, and to avoid the database
generating a name for them.
Audit fields should be added manually if audit is set to true in the resource YAML file, and they
always look like this:
record_time_range tstzrange NOT NULL DEFAULT tstzrange(
localtimestamp, null, '[)'
),
recorded_by bigint NOT NULL DEFAULT current_identity(),
Add the required triggers after the table if needed.
There will often be at least the event trigger generating events when the resource is changed:
CREATE OR REPLACE TRIGGER <resource>_event
AFTER INSERT OR UPDATE ON <resource>
FOR EACH ROW
EXECUTE FUNCTION capture_event();
RLS policies control which rows each role can see/modify.
For new resource without instructions about RLS, you should suggest only one rule that is: FISO can
do everything, and nobody else can do anything.
Start by enabling RLS on the table so that we deny by default.
If notifications are planned on this resource, the internal role should be able to read it.
ALTER TABLE IF EXISTS <resource> ENABLE ROW LEVEL SECURITY;
GRANT SELECT ON <resource> TO flex_internal_event_notification;
CREATE POLICY "<ACRONYM>_INTERNAL_EVENT_NOTIFICATION" ON <resource>
FOR SELECT
TO flex_internal_event_notification
USING (true);
Then, add policies one by one :
GRANT <operation> ON <resource> TO flex_<role>;
CREATE POLICY "<ACRONYM>_<ROLE_ABBR><NNN>" ON <resource>
FOR <operation>
TO flex_<role>
USING (<condition>);
Policies execute as the user (SECURITY INVOKER), so check that referenced resources are readable
by the same user, otherwise use functions with SECURITY DEFINER to break reference cycles.
For history tables, consider whether to base access on current data or history data.
Special care must be taken when the resource is deletable, as the history will be the only way to
access past data.
Add -- RLS: <POLICY_KEY> comments above each policy for test coverage tracking.
Migrations
Stateful resource modifications (not only API-level) require a migration in addition to modifying
the table definition for documentation purposes.
An append-only <resource>_migrations.sql file should exist to contain migrations related to the
resource, and it should be added to the Liquibase changelog.
There, migrations should use preconditions so that they can be safely applied on any instance at
any time.
Triggers should be disabled, then re-enabled after the changes.
Tests are performed with just test-migrations from the current branch.
It will checkout main, reset the project, then switch to your branch and see that the migrations
work, then run them again to check idempotency, then reset to check the initialisation also has been
correctly updated.
Frontend
The project uses custom field components in frontend/src/components/EDS-ra/fields/ that wrap
React Admin primitives with the EDS design system.
You should always use EDS components when possible for new content.
If touching a legacy page with React Admin components, all the page should be migrated to EDS
components as part of the workflow.
All EDS-ra field components are built on BaseField (BaseField.tsx), which checks field-level
permissions and uses the source prop combined with the current resource context to generate
labels.
Labels and translations are in frontend/src/intl:
field-labels.ts -- field display names keyed as resource.field, used via useTranslateField
or React Admin's translate("field.resource.field").
enum-labels.ts -- enum value display names keyed as resource.field.value, used via
translate("enum.resource.field.value").
text.ts -- free-form UI text keyed as custom identifiers.
These files are generated from openapi/resources.yml (via just openapi).
Do not edit them manually.
When displaying enum values in custom components (outside of EnumField), use the useTranslate
hook from ra-core:
const translate = useTranslate();
translate(`enum.accounting_point_bidding_zone.bidding_zone.${value}`);
Show pages for resources with complex data dependencies use a view model hook
(useXxxViewModel.ts) that fetches the main resource record, resolves related data through relation
resources, and returns a typed view model object consumed by the Show page components.
When adding new data to a Show page, extend the existing view model rather than adding fetch logic
directly in the component.
Resolving data through intermediate resources
A common pattern is displaying data that is not directly on the resource but reachable through
a chain of relations (e.g., CU → Accounting Point → AP-BZ → bidding zone).
On Show pages, this is handled in the view model: fetch the intermediate resource, find the
currently valid record, and expose the resolved value in the view model type.
On List pages, the standard React Admin fields (ReferenceField, ReferenceManyField) only
handle direct foreign keys.
For indirect references, create a custom cell component that:
- Reads the current record via
useRecordContext().
- Uses
useGetList (from ra-core) to fetch the intermediate resource filtered by the relevant
foreign key (e.g., accounting_point_id).
- Finds the currently valid record from the result set.
- Renders the resolved value (e.g., with
BodyText for plain values, or wraps in
RecordContextProvider + ReferenceField for further FK resolution).
Finding the currently valid record
Several view models and custom components need to pick the currently valid record from a list of
time-ranged records (with valid_from / valid_to fields).
A helper function for this exists in accounting_point/show/useAccountingPointViewModel.ts
(findCurrentlyValidRecord).
Consider reusing or extracting this utility rather than duplicating the logic.
General guardrails and instructions
- Ask for clarifications if something does not make sense in the request
- Just make the changes -- no Git operation as part of this workflow.
- Take inspiration from existing resources -- follow the patterns and conventions used in
existing similar resources. The project is designed to be consistent and predictable, so there is
usually a clear way to follow.
- Do not edit generated files -- they will be overwritten.
- If changes to other resources are also needed, tell the user about it before proceeding -- we
need to avoid a cascade of changes that can make the workflow very long and complex.
- Remember audit fields and history tables -- it is easy to forget such aspects when creating
or editing a resource.
- Never write a checksum-based migration -- this is painful to maintain.
- RLS comments -- add comments in the right format before RLS definitions and in the tests for
test coverage tracking.
- Changelog -- any stateful change to the API (visible externally) should be registered in the
changelog Markdown file for user documentation purposes.
- Do not hesitate to suggest improvements to the skill -- if you see any aspect of the workflow
that causes errors or can be improved, suggest it to the user and propose to update the skill
accordingly. If everything went smoothly, tell the user that no changes are required.