Skip to content

Commit 2f3b952

Browse files
neo-opus-adatobiu
andauthored
feat(ai): extend retired-primitive guard to block re-adding orchestrator-owned config flags + MCP tools (#12198) (#12200)
The orchestrator daemon is the single source of truth for background memory/KB maintenance. With the Agent OS evolving fast, a backlog ticket older than a week may carry a premise a later migration has obsoleted — a fresh session can naively "re-implement the missing config" and resurrect a per-MCP-instance self-trigger (the pattern that caused duplicate background work across harness-spawned instances). A comment cannot stop that; a red merge-gate can. Extends ai/scripts/diagnostics/check-retired-primitives.mjs from one enforcement category to three: - retired-primitive import (existing — ADR 0004 §2.6 Clean-Cut Pattern), behavior unchanged. - retired config flag (new): scans config.template.mjs for a re-added `<flag>: leaf(` declaration of any of the 8 boot-time auto-* flags removed in #12139 (autoSummarize/autoStartDatabase/autoStartInference/ autoDream/autoGoldenPath/realTimeMemoryParsing/autoIngestFileSystem/autoSync). Line-start-anchored so JSDoc/comment mentions do not false-match. - retired MCP tool (new): scans openapi.yaml for a re-added `operationId:` of manage_database or summarize_sessions (line-end-anchored, prefix-collision-safe). Each category emits a premise-correcting failure message naming the owning subsystem + migration ref, so a developer who hits the guard learns the ticket's premise is obsolete rather than resurrecting the primitive. autoStartInference is the canonical trap (config string-matched, logic never implemented; orchestrator owns inference lifecycle via mlx/lms). Spec extended to 5 tests: clean-pass + a fail-fixture for each category + a JSDoc/comment non-false-match guard (falsifying inputs, not just the happy path). node --check clean; both pre-commit gates pass; the guard is the spec's sole consumer (output-format change breaks nothing else). Resolves #12198. Co-authored-by: tobiu <tobiasuhlig78@gmail.com>
1 parent 17e2255 commit 2f3b952

2 files changed

Lines changed: 249 additions & 192 deletions

File tree

ai/scripts/diagnostics/check-retired-primitives.mjs

Lines changed: 166 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -3,47 +3,92 @@ import path from 'node:path';
33
import process from 'node:process';
44

55
/**
6-
* Pre-Flight (structural fast-path): authoring `ai/scripts/diagnostics/check-retired-primitives.mjs`
7-
* matches sibling pattern of `ai/scripts/diagnostics/check-substrate-size.mjs` and
8-
* `ai/scripts/lint/lint-skill-manifest.mjs` in `ai/scripts/`; all three are mechanical-enforcement /
9-
* CI scripts for agent substrate validation; sibling-file-lift applies; no novel directory
10-
* choice.
6+
* Pre-Flight (structural fast-path): `ai/scripts/diagnostics/check-retired-primitives.mjs`
7+
* matches the sibling pattern of `ai/scripts/diagnostics/check-substrate-size.mjs` and
8+
* `ai/scripts/lint/lint-skill-manifest.mjs` in `ai/scripts/`; all are mechanical-enforcement /
9+
* CI scripts for agent substrate validation; sibling-file-lift applies; no novel directory choice.
1110
*
12-
* @summary CI grep-fail check that retired primitives (ADR 0004 §2.3) are not
13-
* imported from non-spec source files. Complements ADR 0004's discipline-only
14-
* §1.3/§2.6/§5.6 substrate-evolution-guard layer with mechanical enforcement,
15-
* closing the reviewer-time lookback window where dead-code preservation can
16-
* otherwise survive until peer review.
11+
* @summary CI grep-fail check that retired Agent-OS primitives are not re-introduced under `ai/`.
12+
*
13+
* Three enforcement categories:
14+
* 1. **Retired module imports** — a retired primitive imported from a non-spec source file
15+
* (ADR 0004 §2.3 / §2.6 Clean-Cut Pattern).
16+
* 2. **Retired per-MCP-server config flags** — a removed boot-time auto-* flag re-declared as a
17+
* `leaf(...)` in a server `config.template.mjs`.
18+
* 3. **Retired MCP tools** — a removed tool's `operationId` re-added to a server `openapi.yaml`
19+
* (the tool-shape SSOT).
20+
*
21+
* Why categories 2 + 3 exist: the orchestrator daemon is the single source of truth for background
22+
* memory/KB maintenance. The Agent OS evolves fast, so a backlog ticket older than a week may carry
23+
* a premise a later migration has obsoleted. A fresh session acting on such a ticket can naively
24+
* "re-implement the missing config" and resurrect a per-MCP-instance self-trigger — the exact pattern
25+
* that caused duplicate background work across harness-spawned instances. A comment cannot stop that;
26+
* a red merge-gate can. This guard converts that regression class into an unmergeable failure with a
27+
* premise-correcting message that names the owning subsystem.
1728
*
1829
* @see learn/agentos/decisions/0004-github-content-architecture.md §2.6 Clean-Cut Pattern
1930
*/
2031

32+
const SEARCH_ROOT = 'ai/';
33+
2134
/**
22-
* Retired primitives that MUST NOT be imported from non-spec source files.
23-
* Add new entries as ADR 0004 §2.3 RETIRED table grows. Each entry is the import-path
24-
* fragment as it would appear inside a `from '...'` clause.
25-
*
26-
* Anchored to the universal GitHub-content substrate: both files are retired
27-
* once their call sites use `contentPath.mjs` / `contentIndex.mjs`. The §2.6
28-
* Clean-Cut Pattern mandates deletion, not preservation as dead code.
35+
* Orchestrator-SSOT migration references, surfaced in the category 2 + 3 failure messages so a
36+
* developer who hits the guard can find why the primitive was retired and who owns the behavior now.
37+
* Load-bearing (it is the actionable pointer), not decorative archaeology.
38+
*/
39+
const ORCHESTRATOR_REF = '#12139 / #12065';
40+
41+
/**
42+
* Retired module-import fragments that MUST NOT be imported from non-spec source files.
43+
* Each entry is the import-path fragment as it appears inside a `from '...'` clause.
44+
* Add entries as the ADR 0004 §2.3 RETIRED table grows. The §2.6 Clean-Cut Pattern mandates
45+
* deletion, not preservation as dead code.
2946
*/
3047
const RETIRED_PRIMITIVES = [
3148
'shared/chunkPath.mjs',
3249
'shared/archivePath.mjs'
3350
];
3451

35-
const SEARCH_ROOT = 'ai/';
36-
const EXCLUDE_GLOB = ['*.spec.mjs', '*.test.mjs'];
37-
3852
/**
39-
* Escapes ALL regex metacharacters in a string fragment so it can be safely embedded
40-
* in an extended-regex pattern. This keeps future retired-primitive entries
41-
* from becoming regex metacharacter input.
53+
* Retired per-MCP-server boot-time config flags. Removed in #12139; the orchestrator daemon (#12065)
54+
* drives every one of these behaviors on its own schedule (dream / summary / golden-path / chroma /
55+
* kbSync tasks). Re-declaring any as a `leaf(...)` in a server `config.template.mjs` would resurrect
56+
* the per-instance self-trigger that caused duplicate background work across harness-spawned MCP
57+
* instances (Claude Desktop / Codex / Antigravity each spawn N instances per server).
4258
*
43-
* Prior version escaped only `.`, leaving backslashes + other metacharacters (`* + ? ^ $ { } ( ) | [ ] \\`)
44-
* unprotected. While the current `RETIRED_PRIMITIVES` values contain none of these, defensive
45-
* full-metachar escaping makes the function safe for any future fragment a maintainer adds —
46-
* matching MDN's canonical `escapeRegExp` shape.
59+
* `autoStartInference` is the canonical trap: its config was string-matched but the auto-start LOGIC
60+
* was never implemented. A stale ticket reading "implement inference auto-start" would lead a fresh
61+
* session to re-add the flag, not knowing the orchestrator owns inference lifecycle (mlx/lms
62+
* continuous tasks). De-duplicated across servers (`autoStartDatabase` was declared in both
63+
* memory-core and knowledge-base).
64+
*/
65+
const RETIRED_CONFIG_FLAGS = [
66+
'autoSummarize',
67+
'autoStartDatabase',
68+
'autoStartInference',
69+
'autoDream',
70+
'autoGoldenPath',
71+
'realTimeMemoryParsing',
72+
'autoIngestFileSystem',
73+
'autoSync'
74+
];
75+
76+
/**
77+
* Retired MCP tools. Removed in #12139; the orchestrator daemon owns the Chroma database lifecycle
78+
* (`manage_database`) and session summarization (`summarize_sessions`) — the MCP servers are pure
79+
* clients. Re-adding an `operationId` for either to a server `openapi.yaml` re-advertises a tool the
80+
* orchestrator owns (and breaks the derived tool-consistency specs, which assert that every
81+
* `operationId` has a `serviceMapping` handler).
82+
*/
83+
const RETIRED_MCP_TOOLS = [
84+
'manage_database',
85+
'summarize_sessions'
86+
];
87+
88+
/**
89+
* Escapes ALL regex metacharacters in a string fragment so it can be safely embedded in an
90+
* extended-regex pattern, keeping retired-item entries from becoming regex metacharacter input.
91+
* Matches MDN's canonical `escapeRegExp` shape.
4792
*
4893
* @param {string} s Raw fragment string to escape.
4994
* @returns {string} Regex-safe escaped form.
@@ -53,32 +98,80 @@ function escapeRegex(s) {
5398
}
5499

55100
/**
56-
* Builds a single grep extended-regex pattern matching any `from '...<retired>'` style import,
57-
* tolerating single, double, or template-literal quoting. All regex metacharacters in path
58-
* fragments are escaped via `escapeRegex()` so values containing `*`, `[`, `\\`, etc. are
59-
* treated as literals.
60-
* @returns {string}
101+
* Builds the three enforcement categories. Each declares: the retired `items`, the grep extended-regex
102+
* `pattern` that detects a re-introduction, the file `include` globs to scan (empty = all files), the
103+
* `exclude` globs, and a premise-correcting `help` footer printed on a hit. A category with zero items
104+
* is skipped by {@link main}.
105+
*
106+
* - **import** — `from '...<fragment>'` across all of `ai/` (excluding specs), tolerating single,
107+
* double, or template-literal quoting.
108+
* - **config flag** — `<flag>:` as an object key (anchored to line-start indentation, so JSDoc/comment
109+
* mentions like ` * autoSync:` or `// autoSync` do not match), scanned only in `config.template.mjs`.
110+
* - **MCP tool** — `operationId: <tool>` at line end (so `manage_database_v2` does not false-match),
111+
* scanned only in `openapi.yaml`.
112+
*
113+
* @returns {Array<{label:string, items:string[], pattern:string, include:string[], exclude:string[], help:string[]}>}
61114
*/
62-
function buildPattern() {
63-
return RETIRED_PRIMITIVES
64-
.map(fragment => `from[[:space:]]+['"\`].*${escapeRegex(fragment)}`)
65-
.join('|');
115+
function buildCategories() {
116+
return [
117+
{
118+
label : 'retired-primitive import',
119+
items : RETIRED_PRIMITIVES,
120+
pattern: RETIRED_PRIMITIVES.map(f => `from[[:space:]]+['"\`].*${escapeRegex(f)}`).join('|'),
121+
include: [],
122+
exclude: ['*.spec.mjs', '*.test.mjs'],
123+
help : [
124+
'Refer to ADR 0004 §2.6 (Clean-Cut Pattern) and §5.6 (Deprecation-theater anti-pattern):',
125+
' learn/agentos/decisions/0004-github-content-architecture.md',
126+
'Retired primitives must be DELETED, not preserved as dead code after call-site migration.'
127+
]
128+
},
129+
{
130+
label : 'retired config flag',
131+
items : RETIRED_CONFIG_FLAGS,
132+
pattern: RETIRED_CONFIG_FLAGS.map(f => `^[[:space:]]*${escapeRegex(f)}[[:space:]]*:`).join('|'),
133+
include: ['config.template.mjs'],
134+
exclude: [],
135+
help : [
136+
`These boot-time auto-* flags are retired (${ORCHESTRATOR_REF}). The orchestrator daemon is the`,
137+
'single source of truth for dream / summary / golden-path / chroma / kbSync — per-MCP-server',
138+
'self-triggers caused duplicate background work across harness-spawned instances.',
139+
'Do NOT re-add the flag. If a backlog ticket told you to "implement" one of these, that ticket\'s',
140+
'premise is obsolete — close/annotate it rather than resurrect the config. (e.g. autoStartInference',
141+
'never had real logic; the orchestrator owns inference lifecycle via mlx/lms continuous tasks.)'
142+
]
143+
},
144+
{
145+
label : 'retired MCP tool',
146+
items : RETIRED_MCP_TOOLS,
147+
pattern: RETIRED_MCP_TOOLS.map(t => `operationId:[[:space:]]*${escapeRegex(t)}[[:space:]]*$`).join('|'),
148+
include: ['openapi.yaml'],
149+
exclude: [],
150+
help : [
151+
`These MCP tools are retired (${ORCHESTRATOR_REF}). The orchestrator owns the Chroma database`,
152+
'lifecycle (manage_database) and session summarization (summarize_sessions); the MCP servers are',
153+
'pure clients. Do NOT re-add the operationId — the orchestrator drives these behaviors.'
154+
]
155+
}
156+
];
66157
}
67158

68159
/**
69-
* Runs the grep scan. Returns matching lines on hit, empty string on clean.
70-
* Distinguishes "no match" (exit 1) from real grep error (exit 2+).
71-
*
72-
* Uses execFileSync (no shell intermediate) so the pattern can contain all three quote types
73-
* (`'`, `"`, `\``) safely without shell-quoting hazards.
160+
* Runs one grep scan. Returns matching lines on hit, empty string on clean. Distinguishes "no match"
161+
* (grep exit 1, the CLEAN case) from a real grep error (exit 2+, re-thrown). Uses execFileSync (no
162+
* shell intermediate) so the pattern can contain all three quote types (`'`, `"`, `` ` ``) safely.
74163
*
75164
* @param {string} pattern Extended-regex pattern.
165+
* @param {Object} [opts]
166+
* @param {string[]} [opts.include=[]] grep `--include` globs (empty = all files).
167+
* @param {string[]} [opts.exclude=[]] grep `--exclude` globs.
76168
* @returns {string}
77169
*/
78-
function runScan(pattern) {
170+
function runScan(pattern, {include = [], exclude = []} = {}) {
79171
const args = [
80172
'-rnE',
81-
...EXCLUDE_GLOB.map(g => `--exclude=${g}`),
173+
...exclude.map(g => `--exclude=${g}`),
174+
...include.map(g => `--include=${g}`),
82175
pattern,
83176
SEARCH_ROOT
84177
];
@@ -96,33 +189,45 @@ function runScan(pattern) {
96189
}
97190

98191
function main() {
99-
if (RETIRED_PRIMITIVES.length === 0) {
100-
console.log('[checkRetiredPrimitives] PASS: RETIRED_PRIMITIVES table is empty — nothing to enforce.');
192+
const categories = buildCategories().filter(category => category.items.length > 0);
193+
194+
if (categories.length === 0) {
195+
console.log('[checkRetiredPrimitives] PASS: no retired items configured — nothing to enforce.');
101196
process.exit(0);
102197
}
103198

104-
const root = path.resolve(process.cwd());
105-
const pattern = buildPattern();
106-
107-
console.log(`\n🔍 Checking for retired-primitive imports under ${SEARCH_ROOT} ...`);
108-
console.log(` Enforcing ${RETIRED_PRIMITIVES.length} retired primitive(s):`);
109-
RETIRED_PRIMITIVES.forEach(p => console.log(` • ${p}`));
199+
console.log(`\n🔍 Checking for retired Agent-OS primitives under ${SEARCH_ROOT} ...`);
200+
categories.forEach(category => {
201+
console.log(` ${category.label} (${category.items.length}): ${category.items.join(', ')}`);
202+
});
110203
console.log('-'.repeat(80));
111204

112-
const matches = runScan(pattern);
205+
const failures = [];
206+
207+
for (const category of categories) {
208+
const matches = runScan(category.pattern, {include: category.include, exclude: category.exclude});
209+
if (matches.trim() !== '') {
210+
failures.push({category, matches});
211+
}
212+
}
113213

114-
if (matches.trim() === '') {
115-
console.log(`✅ PASS: no retired-primitive imports found under ${SEARCH_ROOT}.`);
116-
console.log(` Substrate is in compliance with ADR 0004 §2.6 Clean-Cut Pattern.\n`);
214+
if (failures.length === 0) {
215+
console.log(`✅ PASS: no retired imports, config flags, or MCP tools found under ${SEARCH_ROOT}.`);
216+
console.log(` Substrate is in compliance (ADR 0004 §2.6 Clean-Cut Pattern + orchestrator-SSOT ${ORCHESTRATOR_REF}).\n`);
117217
process.exit(0);
118218
}
119219

120-
console.error(`❌ FAIL: retired-primitive imports found under ${SEARCH_ROOT}:\n`);
121-
console.error(matches);
122-
console.error(`\nRoot: ${root}`);
123-
console.error(`Refer to ADR 0004 §2.6 (Clean-Cut Pattern) and §5.6 (Deprecation-theater anti-pattern):`);
124-
console.error(` learn/agentos/decisions/0004-github-content-architecture.md`);
125-
console.error(`Retired primitives must be DELETED, not preserved as dead code after call-site migration.\n`);
220+
console.error(`❌ FAIL: retired Agent-OS primitives re-introduced under ${SEARCH_ROOT}:\n`);
221+
222+
for (const {category, matches} of failures) {
223+
console.error(` ▸ ${category.label}:`);
224+
console.error(matches.replace(/^/gm, ' ').trimEnd());
225+
console.error('');
226+
category.help.forEach(line => console.error(` ${line}`));
227+
console.error('');
228+
}
229+
230+
console.error(`Root: ${path.resolve(process.cwd())}\n`);
126231
process.exit(1);
127232
}
128233

0 commit comments

Comments
 (0)