agentleFS
emgreppiemgreppi/Business-Central-AI-Skill/controlling-al-access/SKILL.md

controlling-al-access

Skill1 starsChanged 9 months agoMITView on GitHub

Controls visibility of AL objects and fields using Access property (Public, Internal, Protected, Local), hides internal helpers from external apps, exposes public API surfaces, and configures internalsVisibleTo for multi-app scenarios. Use when making codeunits private, hiding staging tables, protecting fields from partners, or sharing internals between companion apps.

The file

We split this skill into the rules it gives an agent

2 rules. About 0 are common, 1 is distinctive, and 1 is very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈1

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 1

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Designer note: Only Public fields appear in in-client Designer.

    line 68
  2. Distinctive:

    Guidelines: Default to Internal • Public = supported contract • Changing Public → Internal is breaking

    line 140

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Skill: AL Access Modifiers

Validation Gates

  1. After Step 1: Internal objects compile, external apps get "not accessible" compile error
  2. After Step 2: Field access levels work (test: Local field inaccessible outside table)
  3. Final: internalsVisibleTo apps can access internal objects

Note: BC 2019 Wave 2+ (Runtime 4.0). Compile-time only - RecordRef/FieldRef bypass at runtime.

Output: Objects with correct Access property, app.json with internalsVisibleTo (if multi-app).

Supported objects: Codeunit, Table, Table field, Query, Enum, Interface, PermissionSet

Procedure

Step 1: Set Object Access

// Internal table - hidden from external apps
table <ID> "<PREFIX> Internal Staging"
{
    Access = Internal;
    // ... fields
}

// Internal codeunit - helper not exposed to partners
codeunit <ID> "<PREFIX> Internal Helper"
{
    Access = Internal;
    // ... procedures
}

Step 2: Set Field Access

table <ID> "<PREFIX> Order Extension"
{
    Access = Public;
    fields
    {
        field(1; "Order No."; Code[20]) { Access = Public; }      // Stable API
        field(2; "Processing Flags"; Integer) { Access = Internal; } // Same app only
        field(3; "Extension Point"; Code[50]) { Access = Protected; } // Table extensions
        field(4; "Internal Counter"; Integer) { Access = Local; }    // This table only
    }
}

Access Level Summary

LevelSame TableTable ExtensionSame AppOther Apps
Local✓
Protected✓✓
Internal✓✓✓
Public✓✓✓✓

Designer note: Only Public fields appear in in-client Designer.

Step 3: Configure internalsVisibleTo (Multi-App)

In app.json of the core app:

{
    "internalsVisibleTo": [
        { "id": "<companion-app-guid>", "name": "Companion App", "publisher": "My Publisher" },
        { "id": "<test-app-guid>", "name": "Test App", "publisher": "My Publisher" }
    ]
}

Use cases: Core + Companion apps, Test apps accessing internals, Modular architecture.

Important Notes

⚠️ NOT a security boundary: Access is compile-time only. RecordRef/FieldRef bypass at runtime. Use permission sets for data security.

Design Patterns

Public API + Internal Implementation

codeunit <ID> "<PREFIX> Public API"
{
    Access = Public;
    procedure ProcessOperation(InputData: Text): Boolean
    begin
        exit(InternalProcessor.DoProcess(InputData));
    end;
    var
        InternalProcessor: Codeunit "<PREFIX> Internal Processor";
}

codeunit <ID+1> "<PREFIX> Internal Processor"
{
    Access = Internal;
    procedure DoProcess(InputData: Text): Boolean
    begin
        // Can be refactored freely - not exposed
    end;
}

Secret Management

codeunit <ID> "<PREFIX> Secret Manager"
{
    Access = Internal;
    [NonDebuggable]
    procedure GetClientSecret(): Text
    begin
        // Use IsolatedStorage with DataScope::Module
    end;
}

Quick Reference

ScenarioRecommended Access
Stable API for partnersPublic
Helper codeunitsInternal
Staging/buffer tablesInternal
Fields for table extensionsProtected
Fields in table triggers onlyLocal
Secret managementInternal + [NonDebuggable]

Guidelines: Default to Internal • Public = supported contract • Changing Public → Internal is breaking

Limitations: Cannot set on Pages, Reports, XMLports, Control add-ins.

External Documentation

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.