Re-scope author super-skill to feature level (al-feature-author)

Turn the object-granularity al-object-author super-skill into the
feature/PR-level al-feature-author - the true authoring counterpart of
al-code-review, which takes a whole PR. feature-spec in -> whole-PR
code-artifact out.

- microsoft/skills/author/al-feature-author.md (renamed from
  al-object-author.md): takes inputs [feature-spec]; decomposes it into a
  cross-referenced object graph, allocates a contiguous object-ID block,
  applies the mandatory affix, resolves cross-object references (API page
  SourceTable = authored master), fans each derived object-spec out to the
  worklisted leaves, rolls up their artifacts, reconciles the refs, and
  enumerates the objects no current leaf authors (List/Card pages,
  permission set, install codeunit) as open-questions. Worked example:
  a Membership feature -> 3 artifacts (master table + setup table from
  al-table-author, API page from al-api-page-author) rolling up to completed.
- skills/do.md (composition v1.1, STABLE-CONTRACT): add feature-spec to the
  standard inputs; extend Relevance so a super-skill's inputs need not equal
  its leaves' - it may derive the leaves' inputs by decomposing its own;
  generalize Action rollup to findings-report OR code-artifact. Review
  (findings) semantics read identically - behavior-preserving.
- .github/scripts/validate_frontmatter.py: add feature-spec to STANDARD_INPUTS.
  #67 R26 generalization unchanged.
- README.md, agent-consumption.md: author family's top-level skill is now
  al-feature-author.

The al-table-author leaf and the R26 generalization from #67 are unchanged.
Knowledge article count unchanged at 199.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Jesper Schulz-Wedde 2026-07-01 14:32:36 +02:00
parent afd33883c0
commit 2401443ef8
6 changed files with 424 additions and 314 deletions

View file

@ -45,7 +45,7 @@ ENTRY_SKILL_REQUIRED_KEYS = {"kind", "id", "version", "title"}
STANDARD_INPUTS = { STANDARD_INPUTS = {
"pr-diff", "object-list", "file-path", "repository", "telemetry-query", "pr-diff", "object-list", "file-path", "repository", "telemetry-query",
"object-spec", "object-spec", "feature-spec",
} }
ALLOWED_OUTPUTS = {"findings-report", "code-artifact"} ALLOWED_OUTPUTS = {"findings-report", "code-artifact"}
VALID_SAMPLE_KINDS = {"good", "bad"} VALID_SAMPLE_KINDS = {"good", "bad"}

View file

@ -52,7 +52,7 @@ Skills define how agents consume knowledge. They come in three flavors:
READ and DO are read on demand — typically when the first dispatched action skill runs. They are not prerequisites for invoking Entry. WRITE is only used when scaffolding new content. READ and DO are read on demand — typically when the first dispatched action skill runs. They are not prerequisites for invoking Entry. WRITE is only used when scaffolding new content.
- **Action skills** — concrete skills that follow the Action Skill template to do real work (review code, audit telemetry, etc.). Action skills live inside the layers that own them (`/microsoft/skills/`, `/community/skills/`, `/custom/skills/`). An action skill is either a **leaf** that evaluates knowledge files directly, or a **super-skill** that composes other action skills (declared via `sub-skills` in frontmatter). The canonical reference is [`microsoft/skills/review/al-code-review.md`](microsoft/skills/review/al-code-review.md) (super-skill), which composes the AL review leaf skills under [`microsoft/skills/review/`](microsoft/skills/review/) — one per knowledge domain. Most action skills today *review* code and emit a findings-report, but the family also includes an `author` group that *generates* code — applying the same knowledge to scaffold new objects and emitting a `code-artifact` instead of findings. The first example is [`microsoft/skills/author/al-api-page-author.md`](microsoft/skills/author/al-api-page-author.md), which generates a Business Central API page from an object spec. A second leaf, [`al-table-author`](microsoft/skills/author/al-table-author.md), generates a master table and its setup table, and the two author leaves are composed by the [`al-object-author`](microsoft/skills/author/al-object-author.md) super-skill — the authoring counterpart to how `al-code-review` composes the review leaves. - **Action skills** — concrete skills that follow the Action Skill template to do real work (review code, audit telemetry, etc.). Action skills live inside the layers that own them (`/microsoft/skills/`, `/community/skills/`, `/custom/skills/`). An action skill is either a **leaf** that evaluates knowledge files directly, or a **super-skill** that composes other action skills (declared via `sub-skills` in frontmatter). The canonical reference is [`microsoft/skills/review/al-code-review.md`](microsoft/skills/review/al-code-review.md) (super-skill), which composes the AL review leaf skills under [`microsoft/skills/review/`](microsoft/skills/review/) — one per knowledge domain. Most action skills today *review* code and emit a findings-report, but the family also includes an `author` group that *generates* code — applying the same knowledge to scaffold new objects and emitting a `code-artifact` instead of findings. Its object-type leaves are [`al-api-page-author`](microsoft/skills/author/al-api-page-author.md) (generates a Business Central API page from an object spec) and [`al-table-author`](microsoft/skills/author/al-table-author.md) (generates a master table and its setup table). The authoring family's top-level skill is the [`al-feature-author`](microsoft/skills/author/al-feature-author.md) super-skill: it takes a `feature-spec`, decomposes it into per-object specs, composes the object-type author leaves, reconciles the cross-object references, and emits a whole-PR `code-artifact` — the authoring counterpart to how `al-code-review` takes a PR and composes the review leaves.
### Agent bootstrapping ### Agent bootstrapping

View file

@ -71,7 +71,7 @@ The output contract is defined in the DO meta-skill so that every action skill
The orchestrator parses this **without skill-specific logic**. This is the point of the contract: orchestrators and action skills evolve independently. The orchestrator parses this **without skill-specific logic**. This is the point of the contract: orchestrators and action skills evolve independently.
> **Authoring skills.** The same four-step pattern also drives *authoring* skills, which generate code rather than review it. They consume an `object-spec` and emit a `code-artifact` (the full generated AL as an escaped string, with per-artifact `references`, `notes`, and task-level `open-questions`) instead of a findings-report. The orchestrator maps each artifact to **file creation or scaffolding** in the target repo rather than to PR comments or build gates. The first example is `microsoft/skills/author/al-api-page-author.md`; the `code-artifact` contract is defined in [`skills/do.md`](skills/do.md). A second leaf, `microsoft/skills/author/al-table-author.md`, generates a master table and its setup table, and both author leaves compose under the `microsoft/skills/author/al-object-author.md` super-skill — the authoring counterpart to how `al-code-review` composes the review leaves. > **Authoring skills.** The same four-step pattern also drives *authoring* skills, which generate code rather than review it. They consume an `object-spec` and emit a `code-artifact` (the full generated AL as an escaped string, with per-artifact `references`, `notes`, and task-level `open-questions`) instead of a findings-report. The orchestrator maps each artifact to **file creation or scaffolding** in the target repo rather than to PR comments or build gates. The object-type leaves are `microsoft/skills/author/al-api-page-author.md` (API page) and `microsoft/skills/author/al-table-author.md` (master table and its setup table); the `code-artifact` contract is defined in [`skills/do.md`](skills/do.md). The authoring family's top-level skill is the `microsoft/skills/author/al-feature-author.md` super-skill: it takes a `feature-spec`, decomposes it into per-object specs, composes the object-type author leaves, reconciles cross-object references, and emits a whole-PR `code-artifact` — the authoring counterpart to how `al-code-review` takes a PR and composes the review leaves.
### 7. Orchestrator integrates ### 7. Orchestrator integrates
The orchestrator turns findings into PR comments, build gates, or IDE diagnostics, and links the references back to the knowledge files so the PR author — human or agent — can read the guidance. The orchestrator turns findings into PR comments, build gates, or IDE diagnostics, and links the references back to the knowledge files so the PR author — human or agent — can read the guidance.

View file

@ -0,0 +1,418 @@
---
kind: action-skill
id: al-feature-author
version: 1
title: AL feature author
description: Generates a coherent set of BC objects (a whole feature) from a feature-spec by decomposing it into object specs, composing the AL author leaf skills, and reconciling cross-object references.
inputs: [feature-spec]
outputs: [code-artifact]
bc-version: [all]
technologies: [al]
countries: [w1]
application-area: [all]
sub-skills:
- microsoft/skills/author/al-api-page-author.md
- microsoft/skills/author/al-table-author.md
---
# AL feature author
Generates a coherent set of Business Central objects - a whole **feature** - from a `feature-spec` by composing the leaf AL author skills. This is a **super-skill** and the feature-level authoring counterpart to `al-code-review`. Where `al-code-review` takes a whole PR and composes the review leaves, rolling up their findings-reports, `al-feature-author` takes a whole feature and composes the author leaves, rolling up their `code-artifact` reports into a single whole-PR `code-artifact`.
The super-skill's own input (`feature-spec`) is coarser than its leaves' input (`object-spec`), and bridging that gap is this skill's job. Its **Action** decomposes the `feature-spec` into a cross-referenced graph of object specs, feeds each object spec to the worklisted leaves, rolls up the artifacts they produce, and reconciles the references across those objects. It does not evaluate knowledge files directly and performs **no agent self-review pass**: `code-artifact` output has no findings channel, so composition here is *decompose -> invoke leaves -> roll up artifacts -> reconcile*.
An orchestrator invokes this skill with a `feature-spec` - an abstract description of the feature's entities, their relationships, the surfaces (pages and APIs) to expose, and generation parameters. The skill produces a single JSON document conforming to the DO `code-artifact` output contract, extended with `sub-results` and - when applicable - `skipped-sub-skills`.
## Source
The sub-skills invoked by this skill are those listed in frontmatter `sub-skills` - the object-type author leaves:
- `microsoft/skills/author/al-api-page-author.md` - generates a `PageType = API` page from an object spec.
- `microsoft/skills/author/al-table-author.md` - generates a master table (and its setup table) from an object spec.
This skill composes those leaves; it does not read knowledge files directly. Additional object-type author leaves are added by updating the `sub-skills` list. The skill does not discover sub-skills implicitly.
## Relevance
A sub-skill is relevant when both of the following hold:
- Its declared `inputs` will be satisfied. The leaves declare `inputs: [object-spec]`; this super-skill declares `inputs: [feature-spec]`. The inputs differ, and that is expected: the feature author will **supply each leaf an `object-spec` derived from decomposing the `feature-spec`** (see Action). Relevance is judged against those derived inputs, not against the raw `feature-spec`. Because every derived object spec is an `object-spec`, both leaves' inputs are satisfied whenever the orchestrator supplies a `feature-spec`.
- The orchestrator has not disabled the sub-skill via configuration.
Per the DO composition contract, the super-skill MUST NOT filter sub-skills by task content. `al-feature-author` does not inspect the `feature-spec` to predict which object types it contains and then pick leaves accordingly. Every worklisted leaf is invoked against every derived object spec; each leaf **self-selects** by returning `outcome: "not-applicable"` when a given object spec is not for its object type (`al-api-page-author` returns `not-applicable` for a table spec; `al-table-author` returns `not-applicable` for an API-page spec), exactly as review leaves signal non-applicability with `not-applicable` / `no-knowledge`. There is no object-type dispatch in the super-skill.
Sub-skills that fail either check are not invoked and are recorded in `skipped-sub-skills`:
- `reason: "configuration"` when the orchestrator disabled the sub-skill.
- `reason: "not-applicable"` when the derived object specs cannot satisfy the sub-skill's declared `inputs`.
## Worklist
The worklist is the list of sub-skills judged relevant by the previous step - the leaves whose (derived) `inputs` will be satisfied and that are not disabled by configuration. Every sub-skill in the worklist is invoked in the Action step, once per derived object spec. Because both leaves accept an `object-spec` and the feature author derives object specs from the `feature-spec`, both are on the worklist whenever a `feature-spec` is supplied and neither is disabled by configuration.
## Action
### Execution discipline (mandatory)
The Action step is a sequence of **discrete iterations**, not one combined generation. Decompose first, then treat each (object spec x sub-skill) invocation as its own pass: read the sub-skill's instructions, apply its Source -> Relevance -> Worklist -> Action steps to that one derived `object-spec`, and produce that invocation's complete `code-artifact` report before moving on. Do not collapse invocations into one shared reasoning step; each leaf has a distinct knowledge subset and generation procedure. The output schema accommodates this - `sub-results` carries one entry per invocation.
### 1. Decompose the feature-spec into an object graph
From the `feature-spec`, build a typed, cross-referenced **object graph**:
- **Enumerate** the objects the feature requires and assign each a type (table, API page, ...).
- **Allocate object IDs**: reserve a contiguous, non-colliding block for the enumerated objects. When the spec supplies no ID range, emit clearly-labeled placeholder IDs with a `TODO` and record the missing range in `open-questions`.
- **Apply the mandatory object-name affix** to every new object name. When the spec supplies no registered affix, treat it as a placeholder and record it in `open-questions` (the affix is required before AppSource submission).
- **Resolve cross-object references** up front so the leaves generate against a consistent graph: the API page's `SourceTable` is the authored master table; the fields the API page exposes are a subset of the master table's fields; the setup table's number-series field feeds the master table's `OnInsert`.
### 2. Fan out each object spec to the worklisted leaves
For each object spec in the graph, run the per-object fan-out - the same discipline the single-object composition uses, now looped over the graph:
1. Invoke every worklisted leaf with that one derived `object-spec`.
2. The matching leaf authors the object; the others return `outcome: "not-applicable"`. Accept the redundant `not-applicable` invocations - that is the cost of contract-clean self-selection (the super-skill never dispatches by object type), the single-object case simply looped over the graph.
### 3. Collect reports and roll up artifacts
- Capture each invocation's complete `code-artifact` report verbatim and append it to `sub-results` (one entry per invocation, including the redundant `not-applicable` ones).
- If an invocation's `outcome` is `failed`, do not copy its artifacts into the top-level `artifacts[]` or count them in `summary.counts`; its report still stays in `sub-results` for traceability.
- Otherwise append each entry from the invocation's `artifacts[]` to the super-skill's top-level `artifacts[]`. A `not-applicable` / `no-knowledge` invocation contributes zero artifacts. Artifacts are rolled up verbatim; the `code-artifact` schema has no per-artifact `from-sub-skill` field, and leaf attribution is preserved through `sub-results`.
- Aggregate `open-questions` across invocations into the top-level `open-questions` (deduplicating identical entries).
### 4. Reconcile cross-object references
After the leaves have produced their artifacts, **verify the cross-object references line up** across the produced code: the API page's `SourceTable` names the authored master table; every field the API page exposes exists on that master table; the setup table's number-series field is the one the master's `OnInsert` reads. Record any residual mismatch or ambiguity in `open-questions` rather than silently papering over it.
### 5. Enumerate feature objects no current leaf can author
Honest coverage: name the objects the feature needs that **no current author leaf produces**, as explicit, clearly-labeled `open-questions` / follow-ups. For a master-data feature these are, at minimum, the master's **List and Card pages**, a **permission set** covering the new tables, and an **install/upgrade codeunit** that seeds the setup singleton row. Listing them defines the next author leaves and keeps the emitted feature honestly partial rather than pretending completeness.
### Summary and rollup
`summary.counts.artifacts` and `summary.counts.objects` are the sums across invocations whose `outcome` is not `failed`; `summary.coverage.knowledge-applied` is the sum of the invocations' `knowledge-applied` counts. `suppressed[]` at the super-skill level remains empty - knowledge-file suppression is reported by each leaf within its own entry in `sub-results`.
Derive `outcome` using the DO *Outcome rollup* rules over the multiset S of all invocations' outcomes. For the Membership feature below, S = {`al-table-author`: `completed`, `al-api-page-author`: `not-applicable`, `al-api-page-author`: `completed`, `al-table-author`: `not-applicable`} rolls up to `completed`. When every invocation returns `not-applicable` (a feature whose objects neither leaf authors), the roll-up is `not-applicable`. `outcome-reason` is required for `partial` and `failed` and SHOULD summarize per-invocation state.
## Output
Output conforms to the DO `code-artifact` output contract, extended with `sub-results` and `skipped-sub-skills`.
A populated example - a `feature-spec` for a **Membership** feature. The feature author decomposes it into two object specs: a `Membership Member` master table and a `Membership Member` API page bound to it. It reserves the contiguous ID block 50100-50102, feeds each object spec to both leaves, and reconciles the API page's `SourceTable` to the authored master. `al-table-author` authors the master and its setup table (2 artifacts); `al-api-page-author` authors the API page (1 artifact); each leaf returns `not-applicable` for the other's object spec. The three artifacts roll up under `completed`, and the List/Card pages, permission set, and install codeunit the feature still needs - which no current leaf authors - are listed as `open-questions`:
```json
{
"skill": {
"id": "al-feature-author",
"version": 1
},
"outcome": "completed",
"summary": {
"counts": {
"artifacts": 3,
"objects": 3
},
"coverage": {
"knowledge-applied": 7
}
},
"artifacts": [
{
"id": "membership-member-table",
"object-type": "table",
"object-name": "Membership Member",
"path": "src/Membership/MembershipMember.Table.al",
"content": "table 50100 \"Membership Member\"\n{\n // TODO: object ID 50100 is a placeholder; replace it with an ID from your assigned range.\n Caption = 'Membership Member';\n DataClassification = CustomerContent;\n\n fields\n {\n field(1; \"No.\"; Code[20])\n {\n Caption = 'No.';\n NotBlank = true;\n\n trigger OnValidate()\n var\n NoSeries: Codeunit \"No. Series\";\n begin\n if \"No.\" = xRec.\"No.\" then\n exit;\n MembershipSetup.Get();\n if not NoSeries.IsManual(MembershipSetup.\"Member Nos.\") then\n Error(ManualNosNotAllowedErr);\n \"No. Series\" := '';\n end;\n }\n field(2; \"No. Series\"; Code[20])\n {\n Caption = 'No. Series';\n Editable = false;\n TableRelation = \"No. Series\";\n }\n field(10; Name; Text[100])\n {\n Caption = 'Name';\n }\n // Blocked is inert data: the master carries the flag but holds no logic that acts on it.\n // Enforcement belongs in referencing code (journal/document lines), out of scope for this table.\n field(20; Blocked; Boolean)\n {\n Caption = 'Blocked';\n }\n field(30; \"Last Date Modified\"; Date)\n {\n Caption = 'Last Date Modified';\n Editable = false;\n }\n }\n\n keys\n {\n key(PK; \"No.\")\n {\n Clustered = true;\n }\n }\n\n var\n MembershipSetup: Record \"Membership Setup\";\n ManualNosNotAllowedErr: Label 'Numbers are assigned automatically. Allow manual numbers on the No. Series to enter one by hand.';\n\n trigger OnInsert()\n var\n NoSeries: Codeunit \"No. Series\";\n begin\n if \"No.\" = '' then begin\n MembershipSetup.Get();\n MembershipSetup.TestField(\"Member Nos.\");\n \"No. Series\" := MembershipSetup.\"Member Nos.\";\n \"No.\" := NoSeries.GetNextNo(\"No. Series\");\n end;\n end;\n\n trigger OnModify()\n begin\n \"Last Date Modified\" := Today();\n end;\n\n trigger OnRename()\n begin\n \"Last Date Modified\" := Today();\n end;\n}\n",
"references": [
{
"path": "microsoft/knowledge/data-modeling/master-table-no-from-number-series-in-oninsert.md"
},
{
"path": "microsoft/knowledge/data-modeling/use-no-series-codeunit-not-noseriesmanagement.md"
},
{
"path": "microsoft/knowledge/data-modeling/set-last-date-modified-in-onmodify-and-onrename.md"
},
{
"path": "microsoft/knowledge/data-modeling/check-blocked-in-referencing-code-not-in-master.md"
}
],
"confidence": "medium",
"notes": "Object ID 50100 is a placeholder the spec did not supply; replace it with an ID from your assigned range before deploying. Blocked is emitted as inert data with no trigger logic: enforcement belongs in referencing code (journal/document lines) per check-blocked-in-referencing-code-not-in-master and is a follow-up outside this table author. Business field set inferred as Name from the spec."
},
{
"id": "membership-setup-table",
"object-type": "table",
"object-name": "Membership Setup",
"path": "src/Membership/MembershipSetup.Table.al",
"content": "table 50101 \"Membership Setup\"\n{\n // TODO: object ID 50101 is a placeholder; replace it with an ID from your assigned range.\n Caption = 'Membership Setup';\n DataClassification = CustomerContent;\n\n fields\n {\n field(1; \"Primary Key\"; Code[10])\n {\n Caption = 'Primary Key';\n }\n field(10; \"Member Nos.\"; Code[20])\n {\n Caption = 'Member Nos.';\n TableRelation = \"No. Series\";\n }\n }\n\n keys\n {\n key(PK; \"Primary Key\")\n {\n Clustered = true;\n }\n }\n\n procedure GetRecordOnce()\n begin\n if Rec.Get() then\n exit;\n Rec.Init();\n Rec.Insert();\n end;\n}\n",
"references": [
{
"path": "microsoft/knowledge/data-modeling/setup-table-is-a-singleton.md"
},
{
"path": "microsoft/knowledge/data-modeling/master-table-no-from-number-series-in-oninsert.md"
}
],
"confidence": "medium",
"notes": "Object ID 50101 is a placeholder; replace it before deploying. This artifact is the setup TABLE only: the singleton is ENFORCED by a Membership Setup Card page with InsertAllowed = false and DeleteAllowed = false (a page object, out of scope for this table author) - see open-questions. The Member Nos. field feeds the master's OnInsert number assignment."
},
{
"id": "membership-member-api-page",
"object-type": "page",
"object-name": "Membership Member Entity",
"path": "src/Membership/MembershipMemberEntity.Page.al",
"content": "page 50102 \"Membership Member Entity\"\n{\n // TODO: object ID 50102 is a placeholder; replace with an ID from your assigned range.\n PageType = API;\n Caption = 'membershipMember';\n APIPublisher = 'PLACEHOLDER-publisher';\n APIGroup = 'PLACEHOLDER-group';\n APIVersion = 'v1.0';\n EntityName = 'membershipMember';\n EntitySetName = 'membershipMembers';\n ODataKeyFields = SystemId;\n SourceTable = \"Membership Member\";\n DelayedInsert = true;\n\n layout\n {\n area(content)\n {\n repeater(records)\n {\n field(id; Rec.SystemId)\n {\n Caption = 'id';\n Editable = false;\n }\n field(number; Rec.\"No.\")\n {\n Caption = 'number';\n }\n field(displayName; Rec.Name)\n {\n Caption = 'displayName';\n }\n }\n }\n }\n}\n",
"references": [
{
"path": "microsoft/knowledge/web-services/set-required-api-page-properties.md"
},
{
"path": "microsoft/knowledge/web-services/expose-systemid-as-the-api-key.md"
}
],
"confidence": "medium",
"notes": "Object ID 50102, APIPublisher, and APIGroup are placeholders the spec did not supply; replace them before deploying. SourceTable is reconciled to the authored Membership Member master table, and the exposed No. and Name fields are a subset of that table's fields."
}
],
"open-questions": [
"Which contiguous object-ID block should the feature use? Placeholders 50100 (Membership Member), 50101 (Membership Setup), and 50102 (Membership Member API page) were emitted; replace them with IDs from your assigned range.",
"What is the publisher's registered object-name affix? It must be applied to all new object names (Membership Member, Membership Setup, Membership Member Entity) before AppSource submission; names are emitted without it as a placeholder.",
"The feature needs a Membership Member List page and a Membership Member Card page to surface the master in the client - no current author leaf generates List/Card pages; scaffold as a follow-up.",
"The feature needs a permission set covering the new Membership Member and Membership Setup tables - no current author leaf generates permission sets; scaffold as a follow-up.",
"The feature needs an install/upgrade codeunit to seed the Membership Setup singleton row - no current author leaf generates install codeunits; scaffold as a follow-up.",
"What APIPublisher and APIGroup identify the Membership Member API endpoint?",
"Is Name the complete business field set for Membership Member, or should additional fields be modeled?"
],
"suppressed": [],
"sub-results": [
{
"skill": {
"id": "al-table-author",
"version": 1
},
"outcome": "completed",
"summary": {
"counts": {
"artifacts": 2,
"objects": 2
},
"coverage": {
"knowledge-applied": 5
}
},
"artifacts": [
{
"id": "membership-member-table",
"object-type": "table",
"object-name": "Membership Member",
"path": "src/Membership/MembershipMember.Table.al",
"content": "table 50100 \"Membership Member\"\n{\n // TODO: object ID 50100 is a placeholder; replace it with an ID from your assigned range.\n Caption = 'Membership Member';\n DataClassification = CustomerContent;\n\n fields\n {\n field(1; \"No.\"; Code[20])\n {\n Caption = 'No.';\n NotBlank = true;\n\n trigger OnValidate()\n var\n NoSeries: Codeunit \"No. Series\";\n begin\n if \"No.\" = xRec.\"No.\" then\n exit;\n MembershipSetup.Get();\n if not NoSeries.IsManual(MembershipSetup.\"Member Nos.\") then\n Error(ManualNosNotAllowedErr);\n \"No. Series\" := '';\n end;\n }\n field(2; \"No. Series\"; Code[20])\n {\n Caption = 'No. Series';\n Editable = false;\n TableRelation = \"No. Series\";\n }\n field(10; Name; Text[100])\n {\n Caption = 'Name';\n }\n // Blocked is inert data: the master carries the flag but holds no logic that acts on it.\n // Enforcement belongs in referencing code (journal/document lines), out of scope for this table.\n field(20; Blocked; Boolean)\n {\n Caption = 'Blocked';\n }\n field(30; \"Last Date Modified\"; Date)\n {\n Caption = 'Last Date Modified';\n Editable = false;\n }\n }\n\n keys\n {\n key(PK; \"No.\")\n {\n Clustered = true;\n }\n }\n\n var\n MembershipSetup: Record \"Membership Setup\";\n ManualNosNotAllowedErr: Label 'Numbers are assigned automatically. Allow manual numbers on the No. Series to enter one by hand.';\n\n trigger OnInsert()\n var\n NoSeries: Codeunit \"No. Series\";\n begin\n if \"No.\" = '' then begin\n MembershipSetup.Get();\n MembershipSetup.TestField(\"Member Nos.\");\n \"No. Series\" := MembershipSetup.\"Member Nos.\";\n \"No.\" := NoSeries.GetNextNo(\"No. Series\");\n end;\n end;\n\n trigger OnModify()\n begin\n \"Last Date Modified\" := Today();\n end;\n\n trigger OnRename()\n begin\n \"Last Date Modified\" := Today();\n end;\n}\n",
"references": [
{
"path": "microsoft/knowledge/data-modeling/master-table-no-from-number-series-in-oninsert.md"
},
{
"path": "microsoft/knowledge/data-modeling/use-no-series-codeunit-not-noseriesmanagement.md"
},
{
"path": "microsoft/knowledge/data-modeling/set-last-date-modified-in-onmodify-and-onrename.md"
},
{
"path": "microsoft/knowledge/data-modeling/check-blocked-in-referencing-code-not-in-master.md"
}
],
"confidence": "medium",
"notes": "Object ID 50100 is a placeholder the spec did not supply; replace it with an ID from your assigned range before deploying. Blocked is emitted as inert data with no trigger logic: enforcement belongs in referencing code (journal/document lines) per check-blocked-in-referencing-code-not-in-master and is a follow-up outside this table author. Business field set inferred as Name from the spec."
},
{
"id": "membership-setup-table",
"object-type": "table",
"object-name": "Membership Setup",
"path": "src/Membership/MembershipSetup.Table.al",
"content": "table 50101 \"Membership Setup\"\n{\n // TODO: object ID 50101 is a placeholder; replace it with an ID from your assigned range.\n Caption = 'Membership Setup';\n DataClassification = CustomerContent;\n\n fields\n {\n field(1; \"Primary Key\"; Code[10])\n {\n Caption = 'Primary Key';\n }\n field(10; \"Member Nos.\"; Code[20])\n {\n Caption = 'Member Nos.';\n TableRelation = \"No. Series\";\n }\n }\n\n keys\n {\n key(PK; \"Primary Key\")\n {\n Clustered = true;\n }\n }\n\n procedure GetRecordOnce()\n begin\n if Rec.Get() then\n exit;\n Rec.Init();\n Rec.Insert();\n end;\n}\n",
"references": [
{
"path": "microsoft/knowledge/data-modeling/setup-table-is-a-singleton.md"
},
{
"path": "microsoft/knowledge/data-modeling/master-table-no-from-number-series-in-oninsert.md"
}
],
"confidence": "medium",
"notes": "Object ID 50101 is a placeholder; replace it before deploying. This artifact is the setup TABLE only: the singleton is ENFORCED by a Membership Setup Card page with InsertAllowed = false and DeleteAllowed = false (a page object, out of scope for this table author) - see open-questions. The Member Nos. field feeds the master's OnInsert number assignment."
}
],
"open-questions": [
"Which object IDs (from the consuming extension's assigned range) should the Membership Member and Membership Setup tables use?",
"A Membership Setup Card page (PageType = Card, InsertAllowed = false, DeleteAllowed = false) is required to enforce the setup singleton and is not authored by this table skill - should it be scaffolded as a follow-up?",
"Is Name the complete business field set for Membership Member, or should additional fields be modeled?"
],
"suppressed": [],
"sub-results": [],
"skipped-sub-skills": []
},
{
"skill": {
"id": "al-api-page-author",
"version": 1
},
"outcome": "not-applicable",
"outcome-reason": "The derived object-spec requests a master table, not a PageType = API page; al-api-page-author authors only API pages.",
"summary": {
"counts": {
"artifacts": 0,
"objects": 0
},
"coverage": {
"knowledge-applied": 0
}
},
"artifacts": [],
"open-questions": [],
"suppressed": [],
"sub-results": [],
"skipped-sub-skills": []
},
{
"skill": {
"id": "al-api-page-author",
"version": 1
},
"outcome": "completed",
"summary": {
"counts": {
"artifacts": 1,
"objects": 1
},
"coverage": {
"knowledge-applied": 2
}
},
"artifacts": [
{
"id": "membership-member-api-page",
"object-type": "page",
"object-name": "Membership Member Entity",
"path": "src/Membership/MembershipMemberEntity.Page.al",
"content": "page 50102 \"Membership Member Entity\"\n{\n // TODO: object ID 50102 is a placeholder; replace with an ID from your assigned range.\n PageType = API;\n Caption = 'membershipMember';\n APIPublisher = 'PLACEHOLDER-publisher';\n APIGroup = 'PLACEHOLDER-group';\n APIVersion = 'v1.0';\n EntityName = 'membershipMember';\n EntitySetName = 'membershipMembers';\n ODataKeyFields = SystemId;\n SourceTable = \"Membership Member\";\n DelayedInsert = true;\n\n layout\n {\n area(content)\n {\n repeater(records)\n {\n field(id; Rec.SystemId)\n {\n Caption = 'id';\n Editable = false;\n }\n field(number; Rec.\"No.\")\n {\n Caption = 'number';\n }\n field(displayName; Rec.Name)\n {\n Caption = 'displayName';\n }\n }\n }\n }\n}\n",
"references": [
{
"path": "microsoft/knowledge/web-services/set-required-api-page-properties.md"
},
{
"path": "microsoft/knowledge/web-services/expose-systemid-as-the-api-key.md"
}
],
"confidence": "medium",
"notes": "Object ID 50102, APIPublisher, and APIGroup are placeholders the spec did not supply; replace them before deploying. SourceTable is reconciled to the authored Membership Member master table, and the exposed No. and Name fields are a subset of that table's fields."
}
],
"open-questions": [
"Which object ID (from the consuming extension's assigned range) should the Membership Member API page use?",
"What APIPublisher and APIGroup identify this endpoint?",
"Is the No. + Name field set the complete API surface for Membership Member?"
],
"suppressed": [],
"sub-results": [],
"skipped-sub-skills": []
},
{
"skill": {
"id": "al-table-author",
"version": 1
},
"outcome": "not-applicable",
"outcome-reason": "The derived object-spec requests a PageType = API page, not a master or setup table; al-table-author authors only tables.",
"summary": {
"counts": {
"artifacts": 0,
"objects": 0
},
"coverage": {
"knowledge-applied": 0
}
},
"artifacts": [],
"open-questions": [],
"suppressed": [],
"sub-results": [],
"skipped-sub-skills": []
}
],
"skipped-sub-skills": []
}
```
The all-not-applicable case - a `feature-spec` whose objects neither leaf authors (for example, a reporting-only feature that decomposes to a single report object spec) - invokes both leaves against that spec, both return `not-applicable`, and the feature rolls up to `not-applicable` with no artifacts:
```json
{
"skill": {
"id": "al-feature-author",
"version": 1
},
"outcome": "not-applicable",
"outcome-reason": "The feature-spec decomposes only to objects no author leaf handles: al-api-page-author authors API pages and al-table-author authors tables; the feature's single report object is neither.",
"summary": {
"counts": {
"artifacts": 0,
"objects": 0
},
"coverage": {
"knowledge-applied": 0
}
},
"artifacts": [],
"open-questions": [
"The feature's reporting object (a Report object) is authored by no current leaf - a report author leaf is a follow-up before this feature can be generated."
],
"suppressed": [],
"sub-results": [
{
"skill": {
"id": "al-api-page-author",
"version": 1
},
"outcome": "not-applicable",
"outcome-reason": "The derived object-spec requests a report, not a PageType = API page.",
"summary": {
"counts": {
"artifacts": 0,
"objects": 0
},
"coverage": {
"knowledge-applied": 0
}
},
"artifacts": [],
"open-questions": [],
"suppressed": [],
"sub-results": [],
"skipped-sub-skills": []
},
{
"skill": {
"id": "al-table-author",
"version": 1
},
"outcome": "not-applicable",
"outcome-reason": "The derived object-spec requests a report, not a master or setup table.",
"summary": {
"counts": {
"artifacts": 0,
"objects": 0
},
"coverage": {
"knowledge-applied": 0
}
},
"artifacts": [],
"open-questions": [],
"suppressed": [],
"sub-results": [],
"skipped-sub-skills": []
}
],
"skipped-sub-skills": []
}
```

View file

@ -1,308 +0,0 @@
---
kind: action-skill
id: al-object-author
version: 1
title: AL object author
description: Generates BC objects from an object spec by composing the AL author leaf skills (API page, table).
inputs: [object-spec]
outputs: [code-artifact]
bc-version: [all]
technologies: [al]
countries: [w1]
application-area: [all]
sub-skills:
- microsoft/skills/author/al-api-page-author.md
- microsoft/skills/author/al-table-author.md
---
# AL object author
Generates Business Central objects from an `object-spec` by composing the leaf AL author skills. This is a **super-skill** — the authoring counterpart to `al-code-review`. Where `al-code-review` composes the review leaves and rolls up their findings-reports, `al-object-author` composes the author leaves and rolls up their `code-artifact` reports.
`al-object-author` does not evaluate knowledge files directly. It invokes each of its sub-skills against the same `object-spec`, collects their `code-artifact` reports, and rolls up their generated `artifacts[]` into a single composed `code-artifact`. Unlike a review super-skill, an author super-skill performs **no agent self-review pass** and emits no agent findings: `code-artifact` output has no findings channel, so composition here is purely *invoke the leaves and roll up their artifacts*.
An orchestrator invokes this skill with an `object-spec` — an abstract description of the object(s) to generate. The skill produces a single JSON document conforming to the DO `code-artifact` output contract, extended with `sub-results` and — when applicable — `skipped-sub-skills`.
## Source
The sub-skills invoked by this skill are those listed in frontmatter `sub-skills`:
- `microsoft/skills/author/al-api-page-author.md` — generates a `PageType = API` page from the spec.
- `microsoft/skills/author/al-table-author.md` — generates a master table (and its setup table) from the spec.
Additional author leaf skills are added by updating the `sub-skills` list. The skill does not discover sub-skills implicitly.
## Relevance
A sub-skill is relevant when both of the following hold:
- The orchestrator has supplied inputs that satisfy the sub-skill's declared `inputs`. Both author leaves declare `inputs: [object-spec]`, so both are relevant whenever the orchestrator supplies an `object-spec`.
- The orchestrator has not disabled the sub-skill via configuration.
Per the DO contract, the super-skill MUST NOT filter sub-skills by task content. `al-object-author` does not inspect the `object-spec` to predict whether it describes a table or an API page. Each leaf is responsible for its own task-level applicability decision: a leaf **self-selects** by returning `outcome: "not-applicable"` when the spec is not for its object type (`al-api-page-author` returns `not-applicable` for a table spec; `al-table-author` returns `not-applicable` for an API-page spec), exactly as review leaves signal non-applicability with `not-applicable` / `no-knowledge`. There is no object-type dispatch in the super-skill.
Sub-skills that fail either check are not invoked and are recorded in `skipped-sub-skills`:
- `reason: "configuration"` when the orchestrator disabled the sub-skill.
- `reason: "not-applicable"` when the orchestrator's inputs do not satisfy the sub-skill's declared `inputs`.
## Worklist
The worklist is the list of sub-skills judged relevant by the previous step. Every sub-skill in the worklist will be invoked in the Action step. Because both leaves declare `inputs: [object-spec]`, both are on the worklist whenever an `object-spec` is supplied and neither is disabled by configuration.
## Action
### Execution discipline (mandatory)
The Action step is a sequence of **discrete iterations**, not one combined generation. Treat each sub-skill in the worklist as its own pass: read the sub-skill's instructions, apply its Source → Relevance → Worklist → Action steps to the supplied `object-spec`, and produce that sub-skill's complete `code-artifact` report before moving on. Do not collapse multiple sub-skills into one shared reasoning step; each leaf has a distinct knowledge subset and a distinct generation procedure. Sub-skills are independent: re-reading the spec once per sub-skill is correct and expected. The output schema accommodates this — `sub-results` carries one entry per invoked sub-skill, each a complete `code-artifact` report.
### Roll up sub-skill artifacts
For each sub-skill in the worklist, executed one at a time per the discipline above:
1. Invoke the sub-skill with the `object-spec`.
2. Capture the sub-skill's complete `code-artifact` report verbatim and append it to `sub-results`.
3. If the sub-skill's `outcome` is `failed`, stop here for this sub-skill: its artifacts are not reliable per the DO contract and MUST NOT be copied into the super-skill's top-level `artifacts[]` or counted in `summary.counts` (its report is still preserved in `sub-results` for traceability).
4. Otherwise, append each entry from the sub-skill's `artifacts[]` to the super-skill's top-level `artifacts[]`. A `not-applicable` or `no-knowledge` leaf contributes zero artifacts. Artifacts are rolled up verbatim; the `code-artifact` schema has no per-artifact `from-sub-skill` field, and leaf attribution is preserved through `sub-results`.
Aggregate `open-questions` across leaves into the super-skill's top-level `open-questions` (deduplicating identical entries). A `not-applicable` / `no-knowledge` leaf contributes none.
### Summary and rollup
`summary.counts.artifacts` and `summary.counts.objects` are the sums across invoked sub-skills whose `outcome` is not `failed`; `summary.coverage.knowledge-applied` is the sum of the leaves' `knowledge-applied` counts. `suppressed[]` at the super-skill level remains empty — knowledge-file suppression is reported by each leaf within its own entry in `sub-results`.
Derive `outcome` using the DO *Outcome rollup* rules over the multiset S of worklisted sub-skills' outcomes. For a master-table spec, S = {`al-api-page-author`: `not-applicable`, `al-table-author`: `completed`} rolls up to `completed`. When every leaf returns `not-applicable` (a spec neither leaf authors), the roll-up is `not-applicable`. `outcome-reason` is required for `partial` and `failed` and SHOULD summarize per-sub-skill state.
## Output
Output conforms to the DO `code-artifact` output contract, extended with `sub-results` and `skipped-sub-skills`. A populated example — an `object-spec` for a master table: `al-api-page-author` self-selects out with `not-applicable`, `al-table-author` generates the two table artifacts, and the super-skill rolls up to `completed`:
```json
{
"skill": {
"id": "al-object-author",
"version": 1
},
"outcome": "completed",
"summary": {
"counts": {
"artifacts": 2,
"objects": 2
},
"coverage": {
"knowledge-applied": 5
}
},
"artifacts": [
{
"id": "membership-member-table",
"object-type": "table",
"object-name": "Membership Member",
"path": "src/Membership/MembershipMember.Table.al",
"content": "table 50100 \"Membership Member\"\n{\n // TODO: object ID 50100 is a placeholder; replace it with an ID from your assigned range.\n Caption = 'Membership Member';\n DataClassification = CustomerContent;\n\n fields\n {\n field(1; \"No.\"; Code[20])\n {\n Caption = 'No.';\n NotBlank = true;\n\n trigger OnValidate()\n var\n NoSeries: Codeunit \"No. Series\";\n begin\n if \"No.\" = xRec.\"No.\" then\n exit;\n MembershipSetup.Get();\n if not NoSeries.IsManual(MembershipSetup.\"Member Nos.\") then\n Error(ManualNosNotAllowedErr);\n \"No. Series\" := '';\n end;\n }\n field(2; \"No. Series\"; Code[20])\n {\n Caption = 'No. Series';\n Editable = false;\n TableRelation = \"No. Series\";\n }\n field(10; Name; Text[100])\n {\n Caption = 'Name';\n }\n // Blocked is inert data: the master carries the flag but holds no logic that acts on it.\n // Enforcement belongs in referencing code (journal/document lines), out of scope for this table.\n field(20; Blocked; Boolean)\n {\n Caption = 'Blocked';\n }\n field(30; \"Last Date Modified\"; Date)\n {\n Caption = 'Last Date Modified';\n Editable = false;\n }\n }\n\n keys\n {\n key(PK; \"No.\")\n {\n Clustered = true;\n }\n }\n\n var\n MembershipSetup: Record \"Membership Setup\";\n ManualNosNotAllowedErr: Label 'Numbers are assigned automatically. Allow manual numbers on the No. Series to enter one by hand.';\n\n trigger OnInsert()\n var\n NoSeries: Codeunit \"No. Series\";\n begin\n if \"No.\" = '' then begin\n MembershipSetup.Get();\n MembershipSetup.TestField(\"Member Nos.\");\n \"No. Series\" := MembershipSetup.\"Member Nos.\";\n \"No.\" := NoSeries.GetNextNo(\"No. Series\");\n end;\n end;\n\n trigger OnModify()\n begin\n \"Last Date Modified\" := Today();\n end;\n\n trigger OnRename()\n begin\n \"Last Date Modified\" := Today();\n end;\n}\n",
"references": [
{
"path": "microsoft/knowledge/data-modeling/master-table-no-from-number-series-in-oninsert.md"
},
{
"path": "microsoft/knowledge/data-modeling/use-no-series-codeunit-not-noseriesmanagement.md"
},
{
"path": "microsoft/knowledge/data-modeling/set-last-date-modified-in-onmodify-and-onrename.md"
},
{
"path": "microsoft/knowledge/data-modeling/check-blocked-in-referencing-code-not-in-master.md"
}
],
"confidence": "medium",
"notes": "Object ID 50100 is a placeholder the spec did not supply; replace it with an ID from your assigned range before deploying. Blocked is emitted as inert data with no trigger logic: enforcement belongs in referencing code (journal/document lines) per check-blocked-in-referencing-code-not-in-master and is a follow-up outside this table author. Business field set inferred as Name from the spec."
},
{
"id": "membership-setup-table",
"object-type": "table",
"object-name": "Membership Setup",
"path": "src/Membership/MembershipSetup.Table.al",
"content": "table 50101 \"Membership Setup\"\n{\n // TODO: object ID 50101 is a placeholder; replace it with an ID from your assigned range.\n Caption = 'Membership Setup';\n DataClassification = CustomerContent;\n\n fields\n {\n field(1; \"Primary Key\"; Code[10])\n {\n Caption = 'Primary Key';\n }\n field(10; \"Member Nos.\"; Code[20])\n {\n Caption = 'Member Nos.';\n TableRelation = \"No. Series\";\n }\n }\n\n keys\n {\n key(PK; \"Primary Key\")\n {\n Clustered = true;\n }\n }\n\n procedure GetRecordOnce()\n begin\n if Rec.Get() then\n exit;\n Rec.Init();\n Rec.Insert();\n end;\n}\n",
"references": [
{
"path": "microsoft/knowledge/data-modeling/setup-table-is-a-singleton.md"
},
{
"path": "microsoft/knowledge/data-modeling/master-table-no-from-number-series-in-oninsert.md"
}
],
"confidence": "medium",
"notes": "Object ID 50101 is a placeholder; replace it before deploying. This artifact is the setup TABLE only: the singleton is ENFORCED by a Membership Setup Card page with InsertAllowed = false and DeleteAllowed = false (a page object, out of scope for this table author) - see open-questions. The Member Nos. field feeds the master's OnInsert number assignment."
}
],
"open-questions": [
"Which object IDs (from the consuming extension's assigned range) should the Membership Member and Membership Setup tables use?",
"A Membership Setup Card page (PageType = Card, InsertAllowed = false, DeleteAllowed = false) is required to enforce the setup singleton and is not authored by this table skill - should it be scaffolded as a follow-up?",
"Is Name the complete business field set for Membership Member, or should additional fields be modeled?"
],
"suppressed": [],
"sub-results": [
{
"skill": {
"id": "al-api-page-author",
"version": 1
},
"outcome": "not-applicable",
"outcome-reason": "The object-spec requests a master table, not a PageType = API page; al-api-page-author authors only API pages.",
"summary": {
"counts": {
"artifacts": 0,
"objects": 0
},
"coverage": {
"knowledge-applied": 0
}
},
"artifacts": [],
"open-questions": [],
"suppressed": [],
"sub-results": [],
"skipped-sub-skills": []
},
{
"skill": {
"id": "al-table-author",
"version": 1
},
"outcome": "completed",
"summary": {
"counts": {
"artifacts": 2,
"objects": 2
},
"coverage": {
"knowledge-applied": 5
}
},
"artifacts": [
{
"id": "membership-member-table",
"object-type": "table",
"object-name": "Membership Member",
"path": "src/Membership/MembershipMember.Table.al",
"content": "table 50100 \"Membership Member\"\n{\n // TODO: object ID 50100 is a placeholder; replace it with an ID from your assigned range.\n Caption = 'Membership Member';\n DataClassification = CustomerContent;\n\n fields\n {\n field(1; \"No.\"; Code[20])\n {\n Caption = 'No.';\n NotBlank = true;\n\n trigger OnValidate()\n var\n NoSeries: Codeunit \"No. Series\";\n begin\n if \"No.\" = xRec.\"No.\" then\n exit;\n MembershipSetup.Get();\n if not NoSeries.IsManual(MembershipSetup.\"Member Nos.\") then\n Error(ManualNosNotAllowedErr);\n \"No. Series\" := '';\n end;\n }\n field(2; \"No. Series\"; Code[20])\n {\n Caption = 'No. Series';\n Editable = false;\n TableRelation = \"No. Series\";\n }\n field(10; Name; Text[100])\n {\n Caption = 'Name';\n }\n // Blocked is inert data: the master carries the flag but holds no logic that acts on it.\n // Enforcement belongs in referencing code (journal/document lines), out of scope for this table.\n field(20; Blocked; Boolean)\n {\n Caption = 'Blocked';\n }\n field(30; \"Last Date Modified\"; Date)\n {\n Caption = 'Last Date Modified';\n Editable = false;\n }\n }\n\n keys\n {\n key(PK; \"No.\")\n {\n Clustered = true;\n }\n }\n\n var\n MembershipSetup: Record \"Membership Setup\";\n ManualNosNotAllowedErr: Label 'Numbers are assigned automatically. Allow manual numbers on the No. Series to enter one by hand.';\n\n trigger OnInsert()\n var\n NoSeries: Codeunit \"No. Series\";\n begin\n if \"No.\" = '' then begin\n MembershipSetup.Get();\n MembershipSetup.TestField(\"Member Nos.\");\n \"No. Series\" := MembershipSetup.\"Member Nos.\";\n \"No.\" := NoSeries.GetNextNo(\"No. Series\");\n end;\n end;\n\n trigger OnModify()\n begin\n \"Last Date Modified\" := Today();\n end;\n\n trigger OnRename()\n begin\n \"Last Date Modified\" := Today();\n end;\n}\n",
"references": [
{
"path": "microsoft/knowledge/data-modeling/master-table-no-from-number-series-in-oninsert.md"
},
{
"path": "microsoft/knowledge/data-modeling/use-no-series-codeunit-not-noseriesmanagement.md"
},
{
"path": "microsoft/knowledge/data-modeling/set-last-date-modified-in-onmodify-and-onrename.md"
},
{
"path": "microsoft/knowledge/data-modeling/check-blocked-in-referencing-code-not-in-master.md"
}
],
"confidence": "medium",
"notes": "Object ID 50100 is a placeholder the spec did not supply; replace it with an ID from your assigned range before deploying. Blocked is emitted as inert data with no trigger logic: enforcement belongs in referencing code (journal/document lines) per check-blocked-in-referencing-code-not-in-master and is a follow-up outside this table author. Business field set inferred as Name from the spec."
},
{
"id": "membership-setup-table",
"object-type": "table",
"object-name": "Membership Setup",
"path": "src/Membership/MembershipSetup.Table.al",
"content": "table 50101 \"Membership Setup\"\n{\n // TODO: object ID 50101 is a placeholder; replace it with an ID from your assigned range.\n Caption = 'Membership Setup';\n DataClassification = CustomerContent;\n\n fields\n {\n field(1; \"Primary Key\"; Code[10])\n {\n Caption = 'Primary Key';\n }\n field(10; \"Member Nos.\"; Code[20])\n {\n Caption = 'Member Nos.';\n TableRelation = \"No. Series\";\n }\n }\n\n keys\n {\n key(PK; \"Primary Key\")\n {\n Clustered = true;\n }\n }\n\n procedure GetRecordOnce()\n begin\n if Rec.Get() then\n exit;\n Rec.Init();\n Rec.Insert();\n end;\n}\n",
"references": [
{
"path": "microsoft/knowledge/data-modeling/setup-table-is-a-singleton.md"
},
{
"path": "microsoft/knowledge/data-modeling/master-table-no-from-number-series-in-oninsert.md"
}
],
"confidence": "medium",
"notes": "Object ID 50101 is a placeholder; replace it before deploying. This artifact is the setup TABLE only: the singleton is ENFORCED by a Membership Setup Card page with InsertAllowed = false and DeleteAllowed = false (a page object, out of scope for this table author) - see open-questions. The Member Nos. field feeds the master's OnInsert number assignment."
}
],
"open-questions": [
"Which object IDs (from the consuming extension's assigned range) should the Membership Member and Membership Setup tables use?",
"A Membership Setup Card page (PageType = Card, InsertAllowed = false, DeleteAllowed = false) is required to enforce the setup singleton and is not authored by this table skill - should it be scaffolded as a follow-up?",
"Is Name the complete business field set for Membership Member, or should additional fields be modeled?"
],
"suppressed": [],
"sub-results": [],
"skipped-sub-skills": []
}
],
"skipped-sub-skills": []
}
```
The all-not-applicable case — an `object-spec` neither leaf authors (for example, an enum or a report object) — rolls up to `not-applicable`:
```json
{
"skill": {
"id": "al-object-author",
"version": 1
},
"outcome": "not-applicable",
"outcome-reason": "Neither author leaf handles the supplied object-spec: al-api-page-author authors API pages and al-table-author authors tables; the spec requests neither.",
"summary": {
"counts": {
"artifacts": 0,
"objects": 0
},
"coverage": {
"knowledge-applied": 0
}
},
"artifacts": [],
"open-questions": [],
"suppressed": [],
"sub-results": [
{
"skill": {
"id": "al-api-page-author",
"version": 1
},
"outcome": "not-applicable",
"outcome-reason": "The object-spec requests an enum, not a PageType = API page.",
"summary": {
"counts": {
"artifacts": 0,
"objects": 0
},
"coverage": {
"knowledge-applied": 0
}
},
"artifacts": [],
"open-questions": [],
"suppressed": [],
"sub-results": [],
"skipped-sub-skills": []
},
{
"skill": {
"id": "al-table-author",
"version": 1
},
"outcome": "not-applicable",
"outcome-reason": "The object-spec requests an enum, not a master or setup table.",
"summary": {
"counts": {
"artifacts": 0,
"objects": 0
},
"coverage": {
"knowledge-applied": 0
}
},
"artifacts": [],
"open-questions": [],
"suppressed": [],
"sub-results": [],
"skipped-sub-skills": []
}
],
"skipped-sub-skills": []
}
```

View file

@ -43,7 +43,7 @@ application-area: [all]
`bc-version`, `technologies`, `countries`, `application-area` are optional filters that let an orchestrator pre-select applicable skills for a task. They follow the same semantics as in READ. `bc-version`, `technologies`, `countries`, `application-area` are optional filters that let an orchestrator pre-select applicable skills for a task. They follow the same semantics as in READ.
`inputs` is a list of abstract input types the skill **accepts**. Standard values: `pr-diff`, `object-list`, `file-path`, `repository`, `telemetry-query`, `object-spec`. `object-spec` is an abstract description of the BC object(s) to generate — the target table, the desired entity naming, the fields to expose, the read-only flag, and any other generation parameters — supplied by the orchestrator for authoring tasks (the authoring counterpart to a review skill's `pr-diff`). Semantics are any-of: the orchestrator supplies whichever listed input types it has, and the skill is invoked with a non-empty subset of its declared `inputs`. A skill that cannot proceed with the supplied subset MUST return `outcome: "not-applicable"`. `outputs` is always a single-element list naming the output kind; two output kinds are defined: `findings-report` (review skills) and `code-artifact` (authoring skills). `inputs` is a list of abstract input types the skill **accepts**. Standard values: `pr-diff`, `object-list`, `file-path`, `repository`, `telemetry-query`, `object-spec`, `feature-spec`. `object-spec` is an abstract description of the BC object(s) to generate — the target table, the desired entity naming, the fields to expose, the read-only flag, and any other generation parameters — supplied by the orchestrator for authoring tasks (the authoring counterpart to a review skill's `pr-diff`). `feature-spec` is an abstract description of a whole BC feature — its entities, their relationships, the surfaces (pages and APIs) to expose, and any generation parameters — from which a feature author **derives** the individual `object-spec`s it feeds to its leaf sub-skills. Semantics are any-of: the orchestrator supplies whichever listed input types it has, and the skill is invoked with a non-empty subset of its declared `inputs`. A skill that cannot proceed with the supplied subset MUST return `outcome: "not-applicable"`. `outputs` is always a single-element list naming the output kind; two output kinds are defined: `findings-report` (review skills) and `code-artifact` (authoring skills).
`sub-skills` is an optional field. When present and non-empty, the skill is a **super-skill** that composes other action skills; see *Composition* below. Values are repo-relative paths to action-skill files. `sub-skills` is an optional field. When present and non-empty, the skill is a **super-skill** that composes other action skills; see *Composition* below. Values are repo-relative paths to action-skill files.
@ -299,9 +299,9 @@ Composition is flat: a super-skill MAY list only leaf skills (skills without the
The five required sections still apply. Their meaning shifts from knowledge files to sub-skills: The five required sections still apply. Their meaning shifts from knowledge files to sub-skills:
- `## Source` — names the sub-skills invoked (mirrors `sub-skills` in frontmatter). - `## Source` — names the sub-skills invoked (mirrors `sub-skills` in frontmatter).
- `## Relevance` — rules for deciding which sub-skills apply to the current task. A sub-skill is relevant when its declared `inputs` are satisfied by the orchestrator's provided inputs and the orchestrator has not disabled it via configuration. The super-skill MUST NOT filter sub-skills by task content (for example, by inspecting the diff or the file). Task-level applicability is the sub-skill's own responsibility; sub-skills signal non-applicability by returning `outcome: "not-applicable"` or `outcome: "no-knowledge"`. - `## Relevance` — rules for deciding which sub-skills apply to the current task. A sub-skill is relevant when its declared `inputs` are satisfied — either directly by the orchestrator's provided inputs, or by inputs the super-skill itself will supply by decomposing its own input — and the orchestrator has not disabled it via configuration. A super-skill's own `inputs` need not equal its leaves' `inputs`; when they differ (for example, a feature author declares `inputs: [feature-spec]` while its leaves declare `inputs: [object-spec]`), the super-skill's `## Action` decomposes its own input into the inputs its leaves require, and relevance is judged against those derived inputs. The super-skill MUST NOT filter sub-skills by task content (for example, by inspecting the diff or the file). Task-level applicability is the sub-skill's own responsibility; sub-skills signal non-applicability by returning `outcome: "not-applicable"` or `outcome: "no-knowledge"`.
- `## Worklist` — the final list of sub-skills to invoke; the rest go to `skipped-sub-skills`. - `## Worklist` — the final list of sub-skills to invoke; the rest go to `skipped-sub-skills`.
- `## Action` — invoke each worklisted sub-skill with the appropriate subset of inputs, collect its findings-report verbatim into `sub-results`, and copy its `findings[]` into the super-skill's top-level `findings[]` with `from-sub-skill` set. Findings from a sub-skill with `outcome: "failed"` MUST NOT be copied into the super-skill's top-level `findings[]` and MUST NOT contribute to the super-skill's `summary.counts` (their report is still preserved in `sub-results` for traceability, consistent with DO's rule that consumers ignore a failed skill's findings). - `## Action` — invoke each worklisted sub-skill with the appropriate subset of inputs (a super-skill whose `inputs` differ from its leaves' derives those leaf inputs here), collect its report verbatim into `sub-results`, and roll its results up into the super-skill's top-level list according to the output kind. For a `findings-report`, copy its `findings[]` into the super-skill's top-level `findings[]` with `from-sub-skill` set. For a `code-artifact`, copy its `artifacts[]` into the super-skill's top-level `artifacts[]` (the `code-artifact` schema has no per-artifact `from-sub-skill` field, so leaf attribution is preserved through `sub-results`). In both cases a sub-skill with `outcome: "failed"` contributes nothing to the super-skill's top-level list (`findings[]` or `artifacts[]`) and MUST NOT contribute to the super-skill's `summary.counts` (its report is still preserved in `sub-results` for traceability, consistent with DO's rule that consumers ignore a failed skill's output).
- `## Output` — the super-skill's output contract, including `sub-results` and, if any, `skipped-sub-skills`. - `## Output` — the super-skill's output contract, including `sub-results` and, if any, `skipped-sub-skills`.
### Outcome rollup ### Outcome rollup