Skip to content

Playbook Agent: query catalog (3/8)#574

Draft
rashmiraghunandan wants to merge 3 commits into
roostorg:mainfrom
rashmiraghunandan:playbook-agent/03-query-catalog
Draft

Playbook Agent: query catalog (3/8)#574
rashmiraghunandan wants to merge 3 commits into
roostorg:mainfrom
rashmiraghunandan:playbook-agent/03-query-catalog

Conversation

@rashmiraghunandan
Copy link
Copy Markdown

@rashmiraghunandan rashmiraghunandan commented May 23, 2026

Summary

The query catalog — the trust boundary between the AI agent and the database. The agent requests evidence by catalog_id@version; it never writes SQL or sees table names.

Part 3 of 8 — depends on #573 (playbook loader).

queryCatalog.ts

  • parseCatalogEntry(): extract metadata from SQL comment headers, discover :param tokens
  • QueryCatalog class:
    • resolve() / resolveByString(): look up versioned catalog entries
    • isAllowed(): validate a ref is in the playbook's allowed set
    • renderSql(): substitute :param tokens with positional $N bindings (prevents SQL injection)

Template format

-- catalog_id: login_patterns
-- version: 1.0.0
-- description: Login frequency analysis
SELECT ... WHERE account_id = :account_id

queryCatalog.test.ts — 7 tests

  • Parses SQL template with metadata comments
  • Throws on missing catalog_id or version
  • Deduplicates repeated parameters
  • Resolves by CatalogReference and by string
  • Renders SQL with positional $N parameters
  • Throws on missing required parameter

Test plan

  • npm test -- --testPathPattern=queryCatalog passes (7 tests)

Depends on #573 Playbook loader. Merge #572, #573 first.

Summary by CodeRabbit

  • New Features

    • Added a playbook framework for risk investigations with labels, decision logic, confidence scoring, phase budgeting, and execution results.
    • Added a versioned query catalog for managed SQL templates with safe parameter rendering and catalog reference validation.
    • Added evidence and artifact stores and a pluggable adapter for LLM interactions.
  • Tests

    • Added comprehensive tests covering playbook parsing/validation and query catalog edge cases.

Review Change Stack

rashmiraghunandan and others added 2 commits May 22, 2026 16:33
Introduce the foundational type system for the playbook agent framework:

playbookTypes.ts — TypeScript types for declarative investigation playbooks:
  - SemanticVersion with parsing and formatting
  - CatalogReference for versioned query references (no floating versions)
  - Playbook config: evidence ontology, labels, decision logic, confidence
    computation weights, output contracts
  - PlaybookVerdict and ConfidenceBreakdown result types
  - QueryResult/QueryResults for evidence store responses

interfaces.ts — Abstract contracts that organizations implement:
  - ILLMAdapter: pluggable LLM provider (OpenAI, Anthropic, local, etc.)
  - IEvidenceStore: execute pre-approved queries by catalog ID
  - IArtifactStore: insert-only storage for immutable audit trail
  - IPlaybookRunner: core runner interface

These types enforce the key safety property: playbooks reference queries by
catalog_id@version and never contain SQL, table names, or field mappings.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implement the playbook loader that converts raw YAML/JSON playbook configs
into validated TypeScript Playbook objects.

playbookLoader.ts:
  - parsePlaybook(): snake_case YAML input → camelCase TypeScript output
  - Cross-reference validation:
    - baseline_catalog_refs must be a subset of allowed_catalog_refs
    - Deep-dive module query refs must be in the allowed set
    - Hard rule outcomes must reference valid label IDs
    - Default label must exist in the labels list
    - Confidence computation weights must sum to ~1.0
  - Validates semantic versions, catalog IDs, input field types, severities
  - Rejects forbidden floating versions (latest, head, main, master, current)

playbookLoader.test.ts (7 tests):
  - Parses valid playbook with correct type conversion
  - Applies default values for optional confidence fields
  - Rejects baseline ref not in allowed set
  - Rejects invalid default label
  - Rejects confidence weights that don't sum to ~1.0
  - Rejects invalid semantic versions
  - Validates hard rule outcomes against labels

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 23, 2026 00:05
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 23, 2026

📝 Walkthrough

Walkthrough

This PR introduces a complete playbook agent framework for declarative risk investigation workflows. It establishes public service contracts, defines comprehensive domain schemas with validation helpers, implements robust YAML/JSON parsing with cross-reference validation, and provides a query catalog system for managing SQL templates by semantic versioned references.

Changes

Playbook Agent Framework

Layer / File(s) Summary
Public service contracts
server/services/playbookAgentService/interfaces.ts
Exports ILLMAdapter, IEvidenceStore, IArtifactStore, and IPlaybookRunner interfaces along with supporting types (LLMRequest/LLMResponse, EvidenceQuery, PlaybookArtifact, PlaybookRunInput) that define the public API contract for the playbook orchestration framework.
Playbook domain schema and types
server/services/playbookAgentService/playbookTypes.ts
Defines the complete declarative playbook schema: semantic version and catalog reference parsing/validation/formatting; input context fields with type/default enforcement; evidence ontology and quality definitions; evidence request policy configuration; deep-dive module conditions; label definitions and decision logic (hard rules, scoring guidance); confidence computation weights; phase budgets; output contracts; and runtime result structures (verdicts, confidence breakdown, query results, playbook execution results).
Playbook parsing and validation
server/services/playbookAgentService/playbookLoader.ts, server/services/playbookAgentService/playbookLoader.test.ts
parsePlaybook function converts raw YAML/JSON into strongly typed, validated Playbook instances. Performs structural validation (required fields, field types), catalog reference conversion with semantic-version parsing, input field type normalization, severity validation, and comprehensive cross-reference validation (baseline refs ⊆ allowed refs, deep-dive query refs presence, hard-rule outcomes and default labels reference valid labels, confidence weights sum to ~1.0 ±tolerance). Test suite verifies successful parsing with derived values, default value application, and validation error cases (invalid refs, labels, versions, weights, forbidden catalog IDs).
Query catalog system
server/services/playbookAgentService/queryCatalog.ts, server/services/playbookAgentService/queryCatalog.test.ts
QueryCatalog loads and resolves pre-approved SQL templates by semantic-versioned catalog reference. parseCatalogEntry extracts catalog_id, version, optional description from SQL comment headers and derives ordered parameter names from :field_name tokens. Resolution supports both reference objects and catalogId@version strings; isAllowed validates against playbook allowed refs. renderSql substitutes :param tokens with positional $1, $2, … placeholders and produces ordered bindings; throws on missing parameters. Tests verify metadata extraction, parameter deduplication, reference resolution, SQL rendering with bindings, parameter validation, and catalog size reporting.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The PR description covers the main changes and test plan but lacks detail on testing and rollout considerations. Add a 'Tests' section describing how the 7 unit tests were executed and any manual validation performed. Consider adding a 'Rollout Plan' if there are deployment dependencies or considerations.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The PR title accurately and concisely describes the main change: implementing the query catalog as part 3 of the Playbook Agent framework.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Introduces a playbook agent foundation with typed playbook schemas, YAML parsing/validation, and a query catalog that resolves pre-approved SQL templates with safe parameter binding.

Changes:

  • Added QueryCatalog with SQL template parsing and positional parameter rendering.
  • Added playbook schema types + YAML loader with cross-reference validation.
  • Added framework interfaces (LLM adapter, evidence store, artifact store, runner) and unit tests.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
server/services/playbookAgentService/queryCatalog.ts Adds SQL template parsing, catalog lookup, allowlist checks, and parameter binding renderer
server/services/playbookAgentService/queryCatalog.test.ts Adds unit tests for template parsing, resolving, and SQL rendering
server/services/playbookAgentService/playbookTypes.ts Defines playbook schema/types, semantic versioning, and catalog reference validation
server/services/playbookAgentService/playbookLoader.ts Adds YAML->typed playbook parsing with cross-reference validation
server/services/playbookAgentService/playbookLoader.test.ts Adds unit tests for playbook parsing and validation rules
server/services/playbookAgentService/interfaces.ts Defines adapter interfaces for pluggable infra integrations


const METADATA_PATTERN =
/^--\s*(catalog_id|version|description):\s*(.+)$/;
const PARAMETER_PATTERN = /:([a-z_][a-z0-9_]*)/g;
Comment on lines +144 to +150
const renderedSql = entry.sql.replace(PARAMETER_PATTERN, (_match, name: string) => {
if (!(name in parameters)) {
throw new Error(
`Missing required parameter '${name}' for query '${entry.catalogId}'`,
);
}
bindings.push(parameters[name]);
Comment on lines +141 to +154
// Build ordered bindings array and replace :param with $N positional params
const bindings: unknown[] = [];
let paramIndex = 0;
const renderedSql = entry.sql.replace(PARAMETER_PATTERN, (_match, name: string) => {
if (!(name in parameters)) {
throw new Error(
`Missing required parameter '${name}' for query '${entry.catalogId}'`,
);
}
bindings.push(parameters[name]);
paramIndex++;
return `$${paramIndex}`;
});

Comment on lines +141 to +154
// Build ordered bindings array and replace :param with $N positional params
const bindings: unknown[] = [];
let paramIndex = 0;
const renderedSql = entry.sql.replace(PARAMETER_PATTERN, (_match, name: string) => {
if (!(name in parameters)) {
throw new Error(
`Missing required parameter '${name}' for query '${entry.catalogId}'`,
);
}
bindings.push(parameters[name]);
paramIndex++;
return `$${paramIndex}`;
});

}

get catalogIds(): string[] {
return [...this.#entries.keys()];
Comment on lines +122 to +127
isAllowed(refString: string, playbook: Playbook): boolean {
const allowedKeys = new Set(
playbook.allowedCatalogRefs.map(formatCatalogReference),
);
return allowedKeys.has(refString) && this.#entries.has(refString);
}
Comment on lines +252 to +269
if (
typeof r.use_case_id !== 'string' ||
typeof r.version !== 'string' ||
typeof r.display_name !== 'string' ||
typeof r.description !== 'string' ||
r.evidence_ontology == null ||
!Array.isArray(r.baseline_catalog_refs) ||
!Array.isArray(r.allowed_catalog_refs) ||
r.evidence_request_policy == null ||
!Array.isArray(r.labels) ||
r.decision_logic == null ||
r.confidence_computation == null ||
r.output_contract == null
) {
throw new Error('Playbook is missing required top-level fields');
}

const inputContext: InputContextField[] = (r.input_context ?? []).map(
Comment on lines +139 to +147
function toCatalogReference(raw: RawCatalogRef): CatalogReference {
const ref: CatalogReference = {
catalogId: raw.catalog_id,
version: parseSemanticVersion(raw.version),
catalogType: raw.catalog_type as CatalogType,
};
validateCatalogReference(ref);
return ref;
}
Comment on lines +303 to +305
const evidenceRequestPolicy: EvidenceRequestPolicy = {
querySelectionMode: 'query_id',
maxRounds: r.evidence_request_policy.max_rounds,
Comment on lines +226 to +237
const weightSum =
cc.evidenceCompletenessWeight +
cc.signalStrengthWeight +
cc.signalAgreementWeight +
cc.dataQualityWeight;
if (Math.abs(weightSum - 1.0) > 0.01) {
errors.push({
field: 'confidenceComputation',
message: `Confidence weights sum to ${weightSum}, expected ~1.0`,
});
}

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@server/services/playbookAgentService/playbookLoader.ts`:
- Around line 139-146: The toCatalogReference function currently casts
raw.catalog_type and ignores raw.query_selection_mode; instead validate these
runtime values before building the CatalogReference: check raw.catalog_type is
one of the CatalogType enum values (reject/throw or return an error if not) and
map/parse raw.query_selection_mode into the corresponding SelectionMode value
(or set a safe default) rather than forcing/omitting it; update
toCatalogReference so it uses parseSemanticVersion(raw.version), validates
catalog_type against CatalogType, sets a validated querySelectionMode property
from raw.query_selection_mode, and then calls validateCatalogReference(ref).
- Around line 249-266: The parser currently casts raw to Partial<RawPlaybook>
and then accesses properties which will throw if raw is null or a primitive; add
an explicit non-object guard at the start of parsePlaybook (e.g., verify raw is
non-null and typeof raw === 'object') and throw a clear Error like 'Playbook
must be an object' before performing the existing field checks; reference the
parsePlaybook function and the local variable r to locate where to insert this
guard so subsequent property-type validations (use_case_id, version,
display_name, etc.) are safe.

In `@server/services/playbookAgentService/playbookTypes.ts`:
- Around line 24-37: The parser currently uses Number(...) which accepts
non-canonical forms like "1e2"; update parseSemanticVersion to first validate
each segment string with a strict regex (e.g. /^\d+$/) to allow only ASCII
digits, then convert using parseInt(segment, 10) (instead of Number) and check
for non-negative integers; keep the thrown error messages but reject any segment
that fails the regex so parseSemanticVersion returns a SemanticVersion only for
canonical numeric segments.

In `@server/services/playbookAgentService/queryCatalog.ts`:
- Around line 41-42: The regex PARAMETER_PATTERN is matching PostgreSQL cast
syntax like ::date as a bind parameter; update the pattern to avoid matching a
colon that is directly preceded by another colon by using a negative lookbehind
(e.g. change PARAMETER_PATTERN to use (?<!:):([a-z_][a-z0-9_]*)/g) and replace
any other occurrences of the old pattern (the uses around lines referenced in
the comment) to use this updated pattern so casts like created_at::date are not
treated as parameters.
- Around line 93-97: The constructor for the class building `#entries` currently
silently overwrites duplicates when creating the Map from entries; change it to
detect duplicate `${e.catalogId}@${e.version}` keys while iterating the incoming
entries array (CatalogEntry items) and throw a clear Error if a duplicate key is
encountered (include the duplicate key in the message) instead of allowing the
Map to keep the last entry—this ensures duplicate catalogId@version pairs are
rejected at construction time.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: ad05515a-6c23-4016-a4d2-e4b0e482d003

📥 Commits

Reviewing files that changed from the base of the PR and between 9d6bf65 and 5b09347.

📒 Files selected for processing (6)
  • server/services/playbookAgentService/interfaces.ts
  • server/services/playbookAgentService/playbookLoader.test.ts
  • server/services/playbookAgentService/playbookLoader.ts
  • server/services/playbookAgentService/playbookTypes.ts
  • server/services/playbookAgentService/queryCatalog.test.ts
  • server/services/playbookAgentService/queryCatalog.ts

Comment on lines +139 to +146
function toCatalogReference(raw: RawCatalogRef): CatalogReference {
const ref: CatalogReference = {
catalogId: raw.catalog_id,
version: parseSemanticVersion(raw.version),
catalogType: raw.catalog_type as CatalogType,
};
validateCatalogReference(ref);
return ref;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate enum-like raw fields instead of casting/overriding.

catalog_type is cast without runtime validation, and query_selection_mode is ignored/forced. Invalid playbook values can pass silently.

Suggested fix
 function toCatalogReference(raw: RawCatalogRef): CatalogReference {
+  if (raw.catalog_type !== 'query' && raw.catalog_type !== 'query_bundle') {
+    throw new Error(
+      `Invalid catalog_type '${raw.catalog_type}' for catalog '${raw.catalog_id}'`,
+    );
+  }
   const ref: CatalogReference = {
     catalogId: raw.catalog_id,
     version: parseSemanticVersion(raw.version),
-    catalogType: raw.catalog_type as CatalogType,
+    catalogType: raw.catalog_type,
   };
   validateCatalogReference(ref);
   return ref;
 }
@@
   const evidenceRequestPolicy: EvidenceRequestPolicy = {
-    querySelectionMode: 'query_id',
+    querySelectionMode: (() => {
+      if (r.evidence_request_policy.query_selection_mode !== 'query_id') {
+        throw new Error(
+          `Invalid query_selection_mode '${r.evidence_request_policy.query_selection_mode}'`,
+        );
+      }
+      return 'query_id';
+    })(),

Also applies to: 303-305

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/services/playbookAgentService/playbookLoader.ts` around lines 139 -
146, The toCatalogReference function currently casts raw.catalog_type and
ignores raw.query_selection_mode; instead validate these runtime values before
building the CatalogReference: check raw.catalog_type is one of the CatalogType
enum values (reject/throw or return an error if not) and map/parse
raw.query_selection_mode into the corresponding SelectionMode value (or set a
safe default) rather than forcing/omitting it; update toCatalogReference so it
uses parseSemanticVersion(raw.version), validates catalog_type against
CatalogType, sets a validated querySelectionMode property from
raw.query_selection_mode, and then calls validateCatalogReference(ref).

Comment on lines +249 to +266
export function parsePlaybook(raw: unknown): Playbook {
const r = raw as Partial<RawPlaybook>;

if (
typeof r.use_case_id !== 'string' ||
typeof r.version !== 'string' ||
typeof r.display_name !== 'string' ||
typeof r.description !== 'string' ||
r.evidence_ontology == null ||
!Array.isArray(r.baseline_catalog_refs) ||
!Array.isArray(r.allowed_catalog_refs) ||
r.evidence_request_policy == null ||
!Array.isArray(r.labels) ||
r.decision_logic == null ||
r.confidence_computation == null ||
r.output_contract == null
) {
throw new Error('Playbook is missing required top-level fields');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add an explicit non-object guard at parser entry.

parsePlaybook(null) (or primitive input) can fail before structured validation with a generic runtime property-access error.

Suggested fix
 export function parsePlaybook(raw: unknown): Playbook {
+  if (typeof raw !== 'object' || raw === null) {
+    throw new Error('Playbook must be an object');
+  }
   const r = raw as Partial<RawPlaybook>;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function parsePlaybook(raw: unknown): Playbook {
const r = raw as Partial<RawPlaybook>;
if (
typeof r.use_case_id !== 'string' ||
typeof r.version !== 'string' ||
typeof r.display_name !== 'string' ||
typeof r.description !== 'string' ||
r.evidence_ontology == null ||
!Array.isArray(r.baseline_catalog_refs) ||
!Array.isArray(r.allowed_catalog_refs) ||
r.evidence_request_policy == null ||
!Array.isArray(r.labels) ||
r.decision_logic == null ||
r.confidence_computation == null ||
r.output_contract == null
) {
throw new Error('Playbook is missing required top-level fields');
export function parsePlaybook(raw: unknown): Playbook {
if (typeof raw !== 'object' || raw === null) {
throw new Error('Playbook must be an object');
}
const r = raw as Partial<RawPlaybook>;
if (
typeof r.use_case_id !== 'string' ||
typeof r.version !== 'string' ||
typeof r.display_name !== 'string' ||
typeof r.description !== 'string' ||
r.evidence_ontology == null ||
!Array.isArray(r.baseline_catalog_refs) ||
!Array.isArray(r.allowed_catalog_refs) ||
r.evidence_request_policy == null ||
!Array.isArray(r.labels) ||
r.decision_logic == null ||
r.confidence_computation == null ||
r.output_contract == null
) {
throw new Error('Playbook is missing required top-level fields');
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/services/playbookAgentService/playbookLoader.ts` around lines 249 -
266, The parser currently casts raw to Partial<RawPlaybook> and then accesses
properties which will throw if raw is null or a primitive; add an explicit
non-object guard at the start of parsePlaybook (e.g., verify raw is non-null and
typeof raw === 'object') and throw a clear Error like 'Playbook must be an
object' before performing the existing field checks; reference the parsePlaybook
function and the local variable r to locate where to insert this guard so
subsequent property-type validations (use_case_id, version, display_name, etc.)
are safe.

Comment on lines +24 to +37
export function parseSemanticVersion(input: string): SemanticVersion {
const parts = input.split('.');
if (parts.length !== 3) {
throw new Error(
`Invalid semantic version format: '${input}'. Expected format: 'X.Y.Z'`,
);
}
const [major, minor, patch] = parts.map(Number);
if ([major, minor, patch].some((n) => !Number.isInteger(n) || n < 0)) {
throw new Error(
`Invalid semantic version format: '${input}'. All parts must be non-negative integers.`,
);
}
return { major, minor, patch };
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Harden semantic-version parsing to reject non-canonical numeric segments.

The current Number(...) conversion accepts inputs like 1e2.0.0, which should not be valid semantic versions in this parser.

Suggested fix
 export function parseSemanticVersion(input: string): SemanticVersion {
-  const parts = input.split('.');
-  if (parts.length !== 3) {
-    throw new Error(
-      `Invalid semantic version format: '${input}'. Expected format: 'X.Y.Z'`,
-    );
-  }
-  const [major, minor, patch] = parts.map(Number);
-  if ([major, minor, patch].some((n) => !Number.isInteger(n) || n < 0)) {
+  const match = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.exec(input);
+  if (!match) {
     throw new Error(
-      `Invalid semantic version format: '${input}'. All parts must be non-negative integers.`,
+      `Invalid semantic version format: '${input}'. Expected format: 'X.Y.Z'`,
     );
   }
+  const major = Number(match[1]);
+  const minor = Number(match[2]);
+  const patch = Number(match[3]);
   return { major, minor, patch };
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/services/playbookAgentService/playbookTypes.ts` around lines 24 - 37,
The parser currently uses Number(...) which accepts non-canonical forms like
"1e2"; update parseSemanticVersion to first validate each segment string with a
strict regex (e.g. /^\d+$/) to allow only ASCII digits, then convert using
parseInt(segment, 10) (instead of Number) and check for non-negative integers;
keep the thrown error messages but reject any segment that fails the regex so
parseSemanticVersion returns a SemanticVersion only for canonical numeric
segments.

Comment thread server/services/playbookAgentService/queryCatalog.ts Outdated
Comment thread server/services/playbookAgentService/queryCatalog.ts
Implement the query catalog — the trust boundary between the AI agent and
the database. The agent requests evidence by catalog_id@version; it never
writes SQL or sees table names.

queryCatalog.ts:
  - parseCatalogEntry(): extract metadata (catalog_id, version, description)
    from SQL comment headers and discover :param tokens
  - QueryCatalog class:
    - resolve(): look up CatalogReference → CatalogEntry
    - resolveByString(): look up "catalog_id@version" string
    - isAllowed(): validate a ref is in the playbook's allowed set
    - renderSql(): substitute :param tokens with positional $N bindings
      for safe parameterized execution (prevents SQL injection)

queryCatalog.test.ts (7 tests):
  - Parses SQL template with metadata comments
  - Throws on missing catalog_id or version
  - Deduplicates repeated parameters
  - Resolves by CatalogReference and by string
  - Renders SQL with positional parameters and bindings array
  - Throws on missing required parameter

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants