Skip to content

Commit e2616e0

Browse files
os-zhuangclaude
andauthored
feat(spec,lint)!: accept ADR-0109, remove agent.tools[], lint agent authoring, model AI exposure in tool resolution (#3820) (#3894)
* feat(spec,lint)!: accept ADR-0109, remove agent.tools[], lint agent authoring, model AI exposure in tool resolution (#3820) Completes the #3820 positioning work. ADR-0109 is now Accepted — implemented (Phase 1), with evidence: the registry, the reference rule, cloud#908's conformance tests, and hotcrm#512 as the first app ported to the model (22 tool references, 0 findings, down from 16 with 10 dead). BREAKING — `agent.tools[]` is removed. ADR-0064's invariant is "an agent's tool set is the union of its surface-compatible skills' tools; nothing falls through to the global registry", and this legacy inline slot was the one seam that broke it: the runtime resolved `agent.tools[].name` against the FULL registry with no surface check, so an `ask`-surface agent could name an authoring tool and get it. Removing the field makes the invariant structural rather than a rule every reader must remember (ADR-0049 design+enforce-or-remove). `AIToolSchema` and the `AITool` type go with it; the json-schema manifest key is deregistered in the same PR, as its gate requires. Authoring `tools` stays a silent no-op, not a parse error, so existing stacks keep parsing. `validate-ai-tool-references` now models AI exposure. It resolved `action_<name>` against every declared action; the runtime is stricter (ADR-0011): a tool materialises only for an action that opts in with `ai.exposed` + `ai.description` AND has a headless path (script/api/flow with a target or body — url/modal/form are UI-only). The permissive version blessed references the agent could never call — the exact failure the rule exists to catch, one layer down. Found while porting HotCRM, where 3 of the 5 actions its skills wanted are `type:'modal'`. Unresolved `action_*` names now get their own message and fix, because "not exposed" and "fictional" need different answers, and the hint says plainly that a modal action staying human-driven is a design answer, not a gap. New rule `validate-ai-agent-authoring` (agent-authoring-withdrawn, warning): flags a stack declaring `stack.agents`. ADR-0063 §2 withdrew tenant agents and the runtime enforces it on both paths, but `defineStack` still accepted the array — so an app could ship agents that parse, validate, and never run (HotCRM did, for months). This is the missing authoring-time signal; the runtime remains the gate. Docs: `content/docs/ai/agents.mdx` now teaches the zero-tool-record default path with a worked Action → skill example, the three conditions that decide whether `action_<name>` exists, and why naming reasoning ("analyse the pipeline") as a tool is the most common authoring mistake. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BHjroNkLkajskKbJaidko4 * docs(ai): use defineAction in the ADR-0109 example (doc-authoring guard, ADR-0059) `check-doc-authoring.mjs` fails any bare metadata literal in a doc code block — the new Action → skill example wrote `export const X: Action = {}`. Switched to `defineAction({ ... })` with its import, which is the pattern the guard exists to teach. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BHjroNkLkajskKbJaidko4 * docs(ai): migrate every agent example from the removed tools[] to skills The prose-example gate caught two `os:check`-marked agent examples still authoring `agent.tools` after this PR removed the field; three more unmarked examples on the same page taught it too. All five now express capability through `skills`, which is the point — these are examples an AI copies verbatim, so leaving them on a slot that no longer exists would have taught the removed pattern to the next generated app. Each rewrite also names WHERE the tools come from (platform data tools, `action_<name>` from an `ai.exposed` Action, `search_knowledge`), so the examples teach the ADR-0109 default path rather than just dropping a field. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BHjroNkLkajskKbJaidko4 * chore(i18n): drop the agent.tools form labels the removed field left behind `check-i18n-bundles` flagged 4 drifted platform-objects bundles: the metadata-form translations still carried a `tools` entry ("Tools" / "Herramientas" / "ツール" / "工具", each with the "legacy mode" helpText) for a form field this PR removed. Regenerated with `scripts/check-i18n-bundles.mjs --write`; the diff is exactly those four deletions, one per locale. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BHjroNkLkajskKbJaidko4 --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent f00d8d4 commit e2616e0

21 files changed

Lines changed: 526 additions & 193 deletions
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
---
2+
"@objectstack/spec": major
3+
"@objectstack/lint": minor
4+
---
5+
6+
feat(spec,lint)!: remove `agent.tools[]`, lint agent authoring, and resolve `action_<name>` only when it actually materialises (#3820, ADR-0109 accepted)
7+
8+
**Breaking — `agent.tools[]` is removed.** ADR-0064's central invariant is
9+
"an agent's tool set is the union of its surface-compatible skills' tools;
10+
nothing falls through to the global registry", and this legacy inline slot
11+
was the one seam that broke it: the runtime resolved `agent.tools[].name`
12+
against the **full** tool registry with no surface check, so an `ask`-surface
13+
agent could name an authoring tool and get it. Removing the field makes the
14+
invariant structural — there is no second slot to disagree with the skills —
15+
rather than a rule every reader has to remember (ADR-0049 "design+enforce or
16+
remove"). `AIToolSchema` / the `AITool` type go with it.
17+
18+
*Migration:* attach capability through `skills`. An agent authoring `tools` is
19+
not a parse error — Zod strips the unknown key — so existing stacks keep
20+
parsing, but the slot no longer does anything.
21+
22+
**`validate-ai-tool-references` now models AI exposure.** The rule previously
23+
resolved `action_<name>` against every declared action. The runtime is far
24+
stricter (ADR-0011): it materialises a tool only when the action opts in with
25+
`ai.exposed: true` + `ai.description` **and** has a headless path (type
26+
`script`/`api`/`flow` with a target or body — `url`/`modal`/`form` are
27+
UI-only). Resolving against all actions therefore blessed references the agent
28+
could never call — the exact failure the rule exists to catch. Unresolved
29+
`action_*` references now get their own message and fix, since "the action
30+
isn't exposed" and "the name is fictional" need different answers.
31+
32+
**New rule `validate-ai-agent-authoring`** (`agent-authoring-withdrawn`,
33+
warning): flags a stack that declares `stack.agents`. Tenant/app-package
34+
agents were withdrawn in ADR-0063 §2 — the runtime filters them from the
35+
catalog and refuses to load them — but `defineStack` still accepted the array,
36+
so an app could ship agents that parse, validate, and never run. This is the
37+
authoring-time signal that was missing (ADR-0078: loud at the producer,
38+
tolerant at the consumer). Joins `REFERENCE_INTEGRITY_RULES`.
39+
40+
ADR-0109 is now **Accepted — implemented (Phase 1)**, and the AI docs teach
41+
the zero-tool-record default path, including the three conditions that decide
42+
whether `action_<name>` exists and why a `modal` action staying human-driven
43+
is a design answer rather than a gap.

content/docs/ai/agents.mdx

Lines changed: 82 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ description: The two platform agents (ask and build), how skills extend them, an
66
# AI Agents
77

88
Part of the [AI module](/docs/ai). In the **open edition**, agents, tools, and
9-
skills are **typed metadata**: you author them as source with `defineAgent` /
10-
`defineSkill` / `defineTool` from the open `@objectstack/spec/ai` package, and an
9+
skills are **typed metadata**. You author **skills** (`defineSkill`) — agents are
10+
platform-owned and tool records are optional (see below) — from the open
11+
`@objectstack/spec/ai` package, and an
1112
external AI client (Claude, Cursor, a local model — any MCP client) reaches your
1213
objects, queries, and business **Actions** through `@objectstack/mcp` (BYO-AI),
1314
all governed by RLS. This page describes that metadata and the `AgentSchema` it
@@ -112,12 +113,74 @@ assistant to *write* your metadata and never run. Same word, two layers.
112113
`*.agent.ts` is **closed to third parties** — the `agent` metadata type is
113114
`allowRuntimeCreate:false, allowOrgOverride:false`, reserved for the two platform
114115
agents and platform-owned subagents (ADR-0063 §2). To give the `ask` agent a new
115-
capability you author an agent **skill** (`*.skill.ts`) whose `tools` reference
116-
your Actions / Flows / queries; it then attaches to `ask`. Every skill declares
116+
capability you author an agent **skill** (`*.skill.ts`). Every skill declares
117117
`surface: 'ask' | 'build' | 'both'`, and an agent's tool set is the **union of its
118118
surface-compatible skills' tools** — there is no global fall-through, so a skill
119119
reaches an agent only when their surfaces match
120120
([ADR-0064](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0064-tool-scoping-to-agent.md)).
121+
`os validate` warns if a stack declares an agent (`agent-authoring-withdrawn`).
122+
123+
### A skill needs **no tool records** — name the Action
124+
125+
Per [ADR-0109](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0109-ai-tool-authoring-model.md)
126+
the default path declares **zero** `defineTool` records. The runtime already
127+
materialises one `action_<name>` tool per AI-exposed Action, so a skill names
128+
that tool directly. The Action is the same one your UI button runs, so
129+
permissions, validation and audit are identical whether a human clicks or the
130+
assistant calls:
131+
132+
```typescript
133+
import { defineAction } from '@objectstack/spec/ui';
134+
import { defineSkill } from '@objectstack/spec/ai';
135+
136+
// 1. The Action you already have — opt it in to AI.
137+
export const ConvertLeadAction = defineAction({
138+
name: 'convert_lead',
139+
label: 'Convert Lead',
140+
objectName: 'crm_lead',
141+
type: 'flow', // headless: script | api | flow
142+
target: 'lead_conversion_flow',
143+
ai: {
144+
exposed: true, // ADR-0011 — opt-in, default off
145+
description: 'Converts a qualified lead into an account, contact and opportunity.',
146+
},
147+
});
148+
149+
// 2. The skill names its materialised tool. No defineTool anywhere.
150+
export const LeadQualificationSkill = defineSkill({
151+
name: 'lead_qualification',
152+
label: 'Lead Qualification',
153+
surface: 'ask',
154+
instructions: `Score the lead with BANT from what you read, then convert it
155+
when the user agrees. Scoring is your judgement — there is no scoring tool.`,
156+
tools: ['get_record', 'query_records', 'action_convert_lead'],
157+
});
158+
```
159+
160+
Three things decide whether `action_<name>` exists, and `os validate` checks
161+
all three (`ai-skill-tool-unresolved`):
162+
163+
| Requirement | Why |
164+
|---|---|
165+
| `ai.exposed: true` + `ai.description` (≥40 chars) | ADR-0011 governance gate — AI reach is opt-in, and the description is the LLM's contract |
166+
| type is `script` / `api` / `flow` | `url` / `modal` / `form` are UI-only: they have no headless path, so they stay human-driven **by design** |
167+
| a `target` (or a `body`, for `script`) | something has to dispatch |
168+
169+
A `modal` Action is not a gap to fix. When the operation should collect input
170+
from a person — an escalation reason, a refund amount — leave it modal and have
171+
the skill's instructions *recommend* the button instead of calling it.
172+
173+
The other tools a skill may name are the **platform** ones the runtime
174+
registers (`describe_object`, `query_records`, `aggregate_data`,
175+
`search_knowledge`, `visualize_data`, …). Between those and your Actions, most
176+
skills need nothing else: "analyse the pipeline", "draft this email" and "score
177+
this lead" are *reasoning* the model does with data — writing them as tool
178+
names is the most common authoring mistake, and produces an assistant that
179+
claims abilities it does not have.
180+
181+
A `defineTool` record is an **optional refinement layer**, not a required step
182+
— reach for one only when the AI-facing surface must differ from the Action
183+
itself (a different LLM-facing description, fewer exposed parameters).
121184
Both `surface:'ask'` and `surface:'build'` skills run only where the in-UI AI
122185
runtime exists — **ObjectOS**. On the open framework
123186
there is no in-product agent to attach them to; author capability
@@ -193,12 +256,9 @@ Always be professional and data-driven.`,
193256
maxTokens: 2000,
194257
},
195258

196-
// References to Actions/Flows exposed as tools (see "Actions as Tools").
197-
tools: [
198-
{ type: 'flow', name: 'analyze_lead', description: 'Analyze a lead and provide a qualification score' },
199-
{ type: 'flow', name: 'suggest_next_action', description: 'Suggest the next best action for an opportunity' },
200-
{ type: 'action', name: 'generate_email', description: 'Generate a personalized email template' },
201-
],
259+
// Capability comes from skills — the only tool-bearing slot (ADR-0064).
260+
// Each skill names the platform tools and `action_<name>` tools it needs.
261+
skills: ['lead_qualification', 'opportunity_coaching', 'email_drafting'],
202262

203263
// RAG access: sources to recruit knowledge from + vector store indexes.
204264
knowledge: {
@@ -233,11 +293,10 @@ Always be empathetic and solution-focused.`,
233293
maxTokens: 1500,
234294
},
235295

236-
// `search_knowledge` is provided by the Knowledge Protocol tool, not declared inline.
237-
tools: [
238-
{ type: 'flow', name: 'triage_case', description: 'Analyze a case and assign priority' },
239-
{ type: 'action', name: 'generate_response', description: 'Generate a customer response' },
240-
],
296+
// Skills carry the tools. `case_triage` names the platform data tools it
297+
// reads with; `knowledge_search` names `search_knowledge` (Knowledge
298+
// Protocol). Neither needs a `defineTool` record.
299+
skills: ['case_triage', 'knowledge_search', 'response_drafting'],
241300

242301
knowledge: {
243302
sources: ['support-kb', 'cases'],
@@ -271,10 +330,10 @@ Use reputable data sources.`,
271330
maxTokens: 1000,
272331
},
273332

274-
tools: [
275-
{ type: 'flow', name: 'lookup_company', description: 'Look up company information' },
276-
{ type: 'flow', name: 'enrich_contact', description: 'Enrich contact information' },
277-
],
333+
// The enrichment Flows are exposed with `ai.exposed`, so the runtime
334+
// materialises `action_lookup_company` / `action_enrich_contact` for the
335+
// skill to name.
336+
skills: ['contact_enrichment'],
278337
});
279338
```
280339

@@ -307,11 +366,10 @@ Use the data tools to query records and aggregate metrics.`,
307366
maxTokens: 3000,
308367
},
309368

310-
// Built-in data tools (query_records / get_record / aggregate_data) are
311-
// available to agents automatically once registered — see "Natural Language Queries".
312-
tools: [
313-
{ type: 'flow', name: 'analyze_pipeline', description: 'Analyze sales pipeline health' },
314-
],
369+
// Pipeline analysis is reasoning over the platform's own data tools
370+
// (query_records / aggregate_data / visualize_data), which the skill
371+
// names directly — see "Natural Language Queries".
372+
skills: ['revenue_forecasting'],
315373

316374
knowledge: {
317375
sources: ['opportunities', 'pipeline'],

content/docs/getting-started/common-patterns.mdx

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -397,23 +397,10 @@ export const supportAgent = defineAgent({
397397
instructions: `You are a helpful customer support agent.
398398
You can search for customer records, look up order status,
399399
and create support tickets. Always be polite and professional.`,
400-
tools: [
401-
{
402-
type: 'query',
403-
name: 'contact',
404-
description: 'Search customer records'
405-
},
406-
{
407-
type: 'query',
408-
name: 'order',
409-
description: 'Look up order status'
410-
},
411-
{
412-
type: 'action',
413-
name: 'create_support_ticket',
414-
description: 'Create a support ticket'
415-
}
416-
]
400+
// Capability lives in skills, never inline on the agent (ADR-0064): a
401+
// skill names the platform data tools plus the `action_<name>` tools
402+
// materialised from your own AI-exposed Actions.
403+
skills: ['customer_lookup', 'order_status', 'ticket_intake']
417404
});
418405
```
419406

content/docs/references/ai/agent.mdx

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ AI Model Configuration
1414
## TypeScript Usage
1515

1616
```typescript
17-
import { AIKnowledge, AIModelConfig, AITool, Agent, StructuredOutputConfig, StructuredOutputFormat, TransformPipelineStep } from '@objectstack/spec/ai';
18-
import type { AIKnowledge, AIModelConfig, AITool, Agent, StructuredOutputConfig, StructuredOutputFormat, TransformPipelineStep } from '@objectstack/spec/ai';
17+
import { AIKnowledge, AIModelConfig, Agent, StructuredOutputConfig, StructuredOutputFormat, TransformPipelineStep } from '@objectstack/spec/ai';
18+
import type { AIKnowledge, AIModelConfig, Agent, StructuredOutputConfig, StructuredOutputFormat, TransformPipelineStep } from '@objectstack/spec/ai';
1919

2020
// Validate data
2121
const result = AIKnowledge.parse(data);
@@ -49,19 +49,6 @@ const result = AIKnowledge.parse(data);
4949
| **topP** | `number` | optional | |
5050

5151

52-
---
53-
54-
## AITool
55-
56-
### Properties
57-
58-
| Property | Type | Required | Description |
59-
| :--- | :--- | :--- | :--- |
60-
| **type** | `Enum<'action' \| 'flow' \| 'query' \| 'vector_search'>` || |
61-
| **name** | `string` || Reference name (Action Name, Flow Name) |
62-
| **description** | `string` | optional | Override description for the LLM |
63-
64-
6552
---
6653

6754
## Agent
@@ -79,7 +66,6 @@ const result = AIKnowledge.parse(data);
7966
| **lifecycle** | `{ id: string; description?: string; contextSchema?: Record<string, any>; initial: string; … }` | optional | [EXPERIMENTAL — not enforced] State machine defining the agent conversation flow and constraints. Parsed but no runtime consumer yet (liveness #1878/#1893). |
8067
| **surface** | `Enum<'ask' \| 'build'>` || Product surface this agent binds ('ask' \| 'build') — ADR-0063 §1 |
8168
| **skills** | `string[]` | optional | Skill names to attach (Agent→Skill→Tool architecture) |
82-
| **tools** | `{ type: Enum<'action' \| 'flow' \| 'query' \| 'vector_search'>; name: string; description?: string }[]` | optional | Direct tool references (legacy fallback) |
8369
| **knowledge** | `{ sources?: string[]; topics?: any; indexes: string[] }` | optional | RAG access |
8470
| **active** | `boolean` || |
8571
| **access** | `string[]` | optional | Who can chat with this agent |

docs/adr/0109-ai-tool-authoring-model.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# ADR-0109: The AI tool authoring model — the default third-party path needs no tool records
22

3-
**Status**: Proposed (2026-07-28; revised same day — see Revision note). Phase 1 (platform tool-name registry + advisory reference lint) is implemented alongside this revision; Phase 2 (the optional refinement layer) awaits acceptance.
3+
**Status**: **Accepted — implemented (Phase 1)** (2026-07-28; revised same day before acceptance — see Revision note).
4+
Evidence: `packages/spec/src/system/constants/platform-tool-names.ts` (registry + `platform-tool-names.test.ts`); `packages/lint/src/validate-ai-tool-references.ts` (+ `.test.ts`, wired into `REFERENCE_INTEGRITY_RULES`); conformance in `../cloud` `service-ai`/`service-ai-studio` (`platform-tool-names-conformance.test.ts`, cloud#908). First app on the model: `../hotcrm` skills — 22 tool references, 0 findings, down from 16 references with 10 dead (hotcrm#512). Phase 2 (the optional refinement layer) remains open and is gated on a real refinement need.
45
**Deciders**: ObjectStack Protocol Architects
56
**Builds on**: [ADR-0063](./0063-two-kernel-agents-skills-are-the-extension-primitive.md) (skills + tools are the third-party extension primitive), [ADR-0064](./0064-tool-scoping-to-agent.md) (an agent's tools are its skills' tools), [ADR-0078](./0078-no-silently-inert-metadata.md) (no silently inert metadata), [ADR-0049](./0049-no-unenforced-security-properties.md) (enforce-or-remove)
67
**Consumers**: `@objectstack/spec` (`system/constants/platform-tool-names.ts`, `ai/tool.zod.ts`, `stack.zod.ts`), `@objectstack/lint` (`validate-ai-tool-references`), `../cloud/service-ai` + `service-ai-studio` (registry conformance)
@@ -130,5 +131,7 @@ lint could not see: no reference rule could be correct in either direction
130131
- [x] `PLATFORM_PROVIDED_TOOL_NAMES` + `PLATFORM_TOOL_FAMILY_PREFIXES` + invariant tests (spec — this change).
131132
- [x] `validate-ai-tool-references`, advisory, in `REFERENCE_INTEGRITY_RULES` (lint — this change).
132133
- [x] `'tools'` into `composeStacks`' concat list (spec — this change).
133-
- [ ] Registry conformance tests in `../cloud` `service-ai` / `service-ai-studio` (cloud PR; activates fully when the cloud `.objectstack-sha` pin advances past this change).
134+
- [x] Registry conformance tests in `../cloud` `service-ai` / `service-ai-studio` (cloud#908; feature-detected, activates when the `.objectstack-sha` pin advances past Phase 1).
135+
- [x] The lint rule resolves `action_<name>` only for actions that ACTUALLY materialise — `ai.exposed` + `ai.description` + a headless type (ADR-0011). Resolving against every declared action blessed references the agent could never call; caught while porting HotCRM, where 3 of 5 referenced actions are `type:'modal'` (UI-only).
136+
- [x] First app ported: `../hotcrm` (hotcrm#512) — 10 fictional tools removed, the two withdrawn agents deleted, state changes routed through `action_<name>`.
134137
- [ ] **Phase 2 (on acceptance + a real refinement need):** `binding` on `ToolSchema`; flow exposure; boot-mirror provenance guard; revisit `tool` metadata-type flags; ratchet the lint rule to error per ADR-0078 once the registry has soaked a release.

packages/lint/src/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,15 @@ export type {
240240
AiToolRefSeverity,
241241
} from './validate-ai-tool-references.js';
242242

243+
export {
244+
validateAiAgentAuthoring,
245+
AGENT_AUTHORING_WITHDRAWN,
246+
} from './validate-ai-agent-authoring.js';
247+
export type {
248+
AiAgentAuthoringFinding,
249+
AiAgentAuthoringSeverity,
250+
} from './validate-ai-agent-authoring.js';
251+
243252
// One entry point for the reference-resolution rules above (#3583 §5 D5).
244253
// Adding a rule to `REFERENCE_INTEGRITY_RULES` runs it on `validate`, `lint`
245254
// and `compile` at once — the CLI call sites do not change.

packages/lint/src/reference-integrity-suite.test.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ describe('reference-integrity suite — membership', () => {
2424
'validateFlowTemplatePaths',
2525
'validateAiSurfaceAffinity',
2626
'validateAiToolReferences',
27+
'validateAiAgentAuthoring',
2728
]);
2829
});
2930

@@ -111,6 +112,8 @@ describe('reference-integrity suite — every member actually runs', () => {
111112
],
112113
// validateAiSurfaceAffinity: an 'ask' agent binding a 'build' skill — the
113114
// runtime throws on this at chat time (ADR-0064 §3).
115+
// validateAiAgentAuthoring: declaring an agent at all is withdrawn
116+
// (ADR-0063 §2) — one fixture, two rules.
114117
agents: [{ name: 'helper', surface: 'ask', skills: ['metadata_authoring'] }],
115118
// validateAiToolReferences: a tool name nothing declares, registers, or
116119
// materialises (the HotCRM fictional-tool class).
@@ -149,6 +152,7 @@ describe('reference-integrity suite — every member actually runs', () => {
149152
expect(rules).toContain('flow-template-unknown-field');
150153
expect(rules).toContain('ai-skill-surface-mismatch');
151154
expect(rules).toContain('ai-skill-tool-unresolved');
155+
expect(rules).toContain('agent-authoring-withdrawn');
152156
});
153157

154158
it('carries a gating flow-template finding through the suite (#3810)', () => {
@@ -170,9 +174,9 @@ describe('reference-integrity suite — every member actually runs', () => {
170174
expect(typeof f.message).toBe('string');
171175
expect(typeof f.hint).toBe('string');
172176
}
173-
// Object references run first, AI tool references last.
177+
// Object references run first, agent-authoring last.
174178
expect(findings[0].rule).toBe('object-reference-unknown');
175-
expect(findings[findings.length - 1].rule).toBe('ai-skill-tool-unresolved');
179+
expect(findings[findings.length - 1].rule).toBe('agent-authoring-withdrawn');
176180
});
177181

178182
it('returns nothing for an empty stack', () => {

packages/lint/src/reference-integrity-suite.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ import { validateTranslationReferences } from './validate-translation-references
5454
import { validateFlowTemplatePaths } from './validate-flow-template-paths.js';
5555
import { validateAiSurfaceAffinity } from './validate-ai-surface-affinity.js';
5656
import { validateAiToolReferences } from './validate-ai-tool-references.js';
57+
import { validateAiAgentAuthoring } from './validate-ai-agent-authoring.js';
5758

5859
export type ReferenceIntegritySeverity = 'error' | 'warning';
5960

@@ -98,6 +99,7 @@ export const REFERENCE_INTEGRITY_RULES: readonly ReferenceIntegrityRule[] = [
9899
{ name: 'validateFlowTemplatePaths', run: validateFlowTemplatePaths },
99100
{ name: 'validateAiSurfaceAffinity', run: validateAiSurfaceAffinity },
100101
{ name: 'validateAiToolReferences', run: validateAiToolReferences },
102+
{ name: 'validateAiAgentAuthoring', run: validateAiAgentAuthoring },
101103
];
102104

103105
/**

0 commit comments

Comments
 (0)