sdlc-agent-4-enterprise-server 1.47.1 → 1.48.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/.kiro/agents/prompts/sm-agent.md +13 -6
  2. package/.kiro/agents/sm-agent.md +13 -6
  3. package/Dockerfile +5 -2
  4. package/README.md +3 -2
  5. package/dist/admin/db/config.js +5 -3
  6. package/dist/admin/db/config.js.map +1 -1
  7. package/dist/admin/sanitize.d.ts +12 -0
  8. package/dist/admin/sanitize.js +19 -0
  9. package/dist/admin/sanitize.js.map +1 -1
  10. package/dist/database/adapters/PostgresAdapter.d.ts +9 -1
  11. package/dist/database/adapters/PostgresAdapter.js +40 -41
  12. package/dist/database/adapters/PostgresAdapter.js.map +1 -1
  13. package/dist/database/adapters/PostgresAsyncAdapter.d.ts +1 -1
  14. package/dist/database/adapters/PostgresAsyncAdapter.js +3 -3
  15. package/dist/database/adapters/PostgresAsyncAdapter.js.map +1 -1
  16. package/dist/database/adapters/pg-sql-utils.d.ts +31 -0
  17. package/dist/database/adapters/pg-sql-utils.js +78 -0
  18. package/dist/database/adapters/pg-sql-utils.js.map +1 -0
  19. package/dist/database/migration/pg-schema-ensure.js +11 -3
  20. package/dist/database/migration/pg-schema-ensure.js.map +1 -1
  21. package/dist/engine/context/token-budget-manager.d.ts +1 -0
  22. package/dist/engine/context/token-budget-manager.js +4 -0
  23. package/dist/engine/context/token-budget-manager.js.map +1 -1
  24. package/dist/engine/enrichment/CodeEnrichmentHandler.js +21 -1
  25. package/dist/engine/enrichment/CodeEnrichmentHandler.js.map +1 -1
  26. package/dist/engine/enrichment/CodeEnrichmentPromptBuilder.d.ts +19 -1
  27. package/dist/engine/enrichment/CodeEnrichmentPromptBuilder.js +66 -6
  28. package/dist/engine/enrichment/CodeEnrichmentPromptBuilder.js.map +1 -1
  29. package/dist/engine/enrichment/budget-guard.d.ts +20 -0
  30. package/dist/engine/enrichment/budget-guard.js +32 -0
  31. package/dist/engine/enrichment/budget-guard.js.map +1 -0
  32. package/dist/engine/enrichment/reduction-pipeline.d.ts +39 -0
  33. package/dist/engine/enrichment/reduction-pipeline.js +39 -0
  34. package/dist/engine/enrichment/reduction-pipeline.js.map +1 -0
  35. package/dist/engine/graph/graph-schema-ddl.js +1 -0
  36. package/dist/engine/graph/graph-schema-ddl.js.map +1 -1
  37. package/dist/models/pega-schema.models.d.ts +36 -36
  38. package/dist/modules/memory/dispatchers/analytics.js +8 -2
  39. package/dist/modules/memory/dispatchers/analytics.js.map +1 -1
  40. package/dist/modules/memory/dispatchers/crud.js +12 -4
  41. package/dist/modules/memory/dispatchers/crud.js.map +1 -1
  42. package/dist/modules/memory/llm/LLMInitializer.js +18 -0
  43. package/dist/modules/memory/llm/LLMInitializer.js.map +1 -1
  44. package/dist/modules/memory/llm/chunked-reduce.d.ts +11 -0
  45. package/dist/modules/memory/llm/chunked-reduce.js +29 -0
  46. package/dist/modules/memory/llm/chunked-reduce.js.map +1 -0
  47. package/dist/modules/memory/llm/context-window.d.ts +22 -0
  48. package/dist/modules/memory/llm/context-window.js +160 -0
  49. package/dist/modules/memory/llm/context-window.js.map +1 -0
  50. package/dist/modules/memory/llm/error-classifier.d.ts +23 -0
  51. package/dist/modules/memory/llm/error-classifier.js +127 -0
  52. package/dist/modules/memory/llm/error-classifier.js.map +1 -0
  53. package/dist/modules/memory/llm/ollama-adapter.js +3 -1
  54. package/dist/modules/memory/llm/ollama-adapter.js.map +1 -1
  55. package/dist/modules/memory/llm/openai-adapter.js +3 -1
  56. package/dist/modules/memory/llm/openai-adapter.js.map +1 -1
  57. package/dist/modules/memory/task-queue/PendingTaskRepository.d.ts +14 -1
  58. package/dist/modules/memory/task-queue/PendingTaskRepository.js +28 -2
  59. package/dist/modules/memory/task-queue/PendingTaskRepository.js.map +1 -1
  60. package/dist/modules/memory/task-queue/TaskWorker.d.ts +25 -0
  61. package/dist/modules/memory/task-queue/TaskWorker.js +129 -1
  62. package/dist/modules/memory/task-queue/TaskWorker.js.map +1 -1
  63. package/dist/modules/pega/AstDigestBuilder.d.ts +11 -0
  64. package/dist/modules/pega/AstDigestBuilder.js +17 -0
  65. package/dist/modules/pega/AstDigestBuilder.js.map +1 -0
  66. package/dist/modules/pega/PegaContentExtractor.d.ts +28 -0
  67. package/dist/modules/pega/PegaContentExtractor.js +64 -5
  68. package/dist/modules/pega/PegaContentExtractor.js.map +1 -1
  69. package/dist/modules/pega/PegaIndexer.js +2 -1
  70. package/dist/modules/pega/PegaIndexer.js.map +1 -1
  71. package/dist/modules/pega/PegaSymbolSync.d.ts +1 -1
  72. package/dist/modules/pega/PegaSymbolSync.js +53 -8
  73. package/dist/modules/pega/PegaSymbolSync.js.map +1 -1
  74. package/dist/modules/pega/models.d.ts +8 -0
  75. package/dist/server/routes/admin/config.js +21 -4
  76. package/dist/server/routes/admin/config.js.map +1 -1
  77. package/dist/server/routes/admin/kb-entries.js +7 -2
  78. package/dist/server/routes/admin/kb-entries.js.map +1 -1
  79. package/dist/server/routes/api-index-ingest.d.ts +76 -0
  80. package/dist/server/routes/api-index-ingest.js +165 -0
  81. package/dist/server/routes/api-index-ingest.js.map +1 -0
  82. package/dist/server/routes/api-index.js +23 -39
  83. package/dist/server/routes/api-index.js.map +1 -1
  84. package/dist/server/routes/enrichment-auth-guard.d.ts +49 -0
  85. package/dist/server/routes/enrichment-auth-guard.js +68 -0
  86. package/dist/server/routes/enrichment-auth-guard.js.map +1 -0
  87. package/dist/server/routes/enrichment-status-routes.js +69 -17
  88. package/dist/server/routes/enrichment-status-routes.js.map +1 -1
  89. package/docker-compose.local-kb.yml +99 -0
  90. package/docker-compose.test.yml +8 -1
  91. package/knexfile.cjs +8 -6
  92. package/package.json +2 -2
  93. package/src/admin/__tests__/sanitize.test.ts +36 -0
  94. package/src/admin/db/__tests__/config-masking.test.ts +43 -0
  95. package/src/admin/db/config.ts +5 -3
  96. package/src/admin/sanitize.ts +21 -0
  97. package/src/database/adapters/PostgresAdapter.ts +44 -40
  98. package/src/database/adapters/PostgresAsyncAdapter.ts +3 -3
  99. package/src/database/adapters/__tests__/PostgresAdapter.test.ts +53 -22
  100. package/src/database/adapters/__tests__/pg-sql-utils.test.ts +75 -0
  101. package/src/database/adapters/pg-sql-utils.ts +77 -0
  102. package/src/database/migration/pg-schema-ensure.ts +12 -3
  103. package/src/engine/context/token-budget-manager.ts +5 -0
  104. package/src/engine/enrichment/CodeEnrichmentHandler.ts +24 -1
  105. package/src/engine/enrichment/CodeEnrichmentPromptBuilder.ts +72 -6
  106. package/src/engine/enrichment/__tests__/pega-enrichment-prompt.test.ts +24 -1
  107. package/src/engine/enrichment/budget-guard.ts +49 -0
  108. package/src/engine/enrichment/reduction-pipeline.ts +60 -0
  109. package/src/engine/graph/graph-schema-ddl.ts +1 -0
  110. package/src/modules/memory/dispatchers/analytics.ts +8 -3
  111. package/src/modules/memory/dispatchers/crud.ts +13 -4
  112. package/src/modules/memory/ingest/__tests__/enrich-existing-tags-preserve.it.test.ts +175 -0
  113. package/src/modules/memory/ingest/__tests__/ingest-file-tags-fallback.it.test.ts +194 -0
  114. package/src/modules/memory/llm/LLMInitializer.ts +21 -0
  115. package/src/modules/memory/llm/__tests__/error-classifier.test.ts +121 -0
  116. package/src/modules/memory/llm/chunked-reduce.ts +35 -0
  117. package/src/modules/memory/llm/context-window.ts +173 -0
  118. package/src/modules/memory/llm/error-classifier.ts +127 -0
  119. package/src/modules/memory/llm/ollama-adapter.ts +3 -1
  120. package/src/modules/memory/llm/openai-adapter.ts +3 -1
  121. package/src/modules/memory/task-queue/PendingTaskRepository.ts +29 -3
  122. package/src/modules/memory/task-queue/TaskWorker.ts +132 -1
  123. package/src/modules/memory/task-queue/__tests__/PendingTaskRepository.retry.test.ts +139 -0
  124. package/src/modules/memory/task-queue/__tests__/TaskWorker.fallback-tag.test.ts +263 -0
  125. package/src/modules/pega/AstDigestBuilder.ts +26 -0
  126. package/src/modules/pega/PegaContentExtractor.ts +79 -5
  127. package/src/modules/pega/PegaIndexer.ts +2 -1
  128. package/src/modules/pega/PegaSymbolSync.ts +56 -9
  129. package/src/modules/pega/__tests__/PegaContentExtractor.test.ts +38 -1
  130. package/src/modules/pega/__tests__/PegaSymbolSync.test.ts +43 -0
  131. package/src/modules/pega/models.ts +8 -0
  132. package/src/server/routes/__tests__/admin-config-mask.test.ts +156 -0
  133. package/src/server/routes/__tests__/api-index-errors.test.ts +242 -1
  134. package/src/server/routes/__tests__/enrichment-status-routes.test.ts +209 -31
  135. package/src/server/routes/admin/config.ts +19 -4
  136. package/src/server/routes/admin/kb-entries.ts +8 -3
  137. package/src/server/routes/api-index-ingest.ts +195 -0
  138. package/src/server/routes/api-index.ts +27 -36
  139. package/src/server/routes/enrichment-auth-guard.ts +81 -0
  140. package/src/server/routes/enrichment-status-routes.ts +66 -17
@@ -31,7 +31,7 @@ At the very beginning of your execution, use `find_tools` to discover tools. Use
31
31
 
32
32
  Fallbacks:
33
33
  - **Project tracker unavailable** → Skip transitions, manage status via STATUS.json only
34
- - **KB unavailable** → Skip KB verification, rely on file checks
34
+ - **KB unavailable** → Attempt MCP bootstrap first (tool-usage-dynamic Step 0). If still down → report to user explicitly ("⚠️ KB unavailable — fallback to file checks, KB verification skipped"), then rely on file checks
35
35
  - **DOCX export unavailable** → Skip DOCX export, attach markdown or skip attachment
36
36
 
37
37
  ### Discovery Report
@@ -506,7 +506,14 @@ invokeSubAgent(
506
506
  - If exists → resume from `currentPhase`
507
507
  - If not exists → scan for existing files to build initial status
508
508
 
509
- 2. **Scan existing files** (when STATUS.json doesn't exist):
509
+ 2. **KB-first check** (MANDATORY — before deciding next steps):
510
+ - Use the discovered KB "search" tool (query: "{TICKET} BRD FSD TDD") — cross-check STATUS.json against documents actually in KB (detect stale/corrupt status)
511
+ - Use the discovered KB "search" tool (query: "personalized rules preferences conventions") — load user's personalized rules (agent-self-learning Rule #7)
512
+ - Use the discovered KB "search" tool (query: "{PROJECT} lessons learned") — check prior lessons/error patterns relevant to this ticket
513
+ - If any KB finding contradicts STATUS.json → report the conflict to user before proceeding
514
+ - If KB unavailable → attempt MCP bootstrap first, then report to user before falling back to file checks
515
+
516
+ 3. **Scan existing files** (when STATUS.json doesn't exist):
510
517
  ```
511
518
  documents/{TICKET}/BRD.md exists? → requirements: done
512
519
  documents/{TICKET}/FSD.md exists? → specification: done
@@ -516,7 +523,7 @@ invokeSubAgent(
516
523
  ```
517
524
  Create STATUS.json from scan results.
518
525
 
519
- 3. **Check Jira ticket status** (MANDATORY on every resume):
526
+ 4. **Check Jira ticket status** (MANDATORY on every resume):
520
527
  ```
521
528
  issue = the discovered project tracker "get issue" tool (issue_key: "{TICKET}")
522
529
  jiraStatus = issue.status // "To Do", "Docs Review", "In Progress", "In Review", "QA Test", "UAT", "Ready For Product", "Done"
@@ -537,7 +544,7 @@ invokeSubAgent(
537
544
 
538
545
  **Quan trọng:** Nếu Jira status đã advance (ví dụ: reviewer đã chuyển DOCS REVIEW → IN PROGRESS), SM tự động tiếp tục phase tương ứng mà KHÔNG cần user nói lại.
539
546
 
540
- 4. **Read Jira comments** (MANDATORY on every resume):
547
+ 5. **Read Jira comments** (MANDATORY on every resume):
541
548
  ```
542
549
  the discovered project tracker "get issue" tool (issue_key: "{TICKET}", comment_limit: 10)
543
550
  ```
@@ -575,7 +582,7 @@ invokeSubAgent(
575
582
  - If comment contains rejection/feedback → SM MUST report to user before taking action
576
583
  - Store last processed comment timestamp in STATUS.json: `"lastCommentProcessed": "2026-05-01T18:00:00Z"`
577
584
 
578
- 5. **Report current status to user:**
585
+ 6. **Report current status to user:**
579
586
  ```
580
587
  📋 {TICKET} — Status Report
581
588
 
@@ -593,7 +600,7 @@ invokeSubAgent(
593
600
  ➡️ Tiếp tục Phase 3 (Design)?
594
601
  ```
595
602
 
596
- 6. **Wait for user confirmation** before proceeding.
603
+ 7. **Wait for user confirmation** before proceeding.
597
604
 
598
605
  ### Step 1: Execute Phase — Requirements (BA → BRD)
599
606
 
@@ -43,7 +43,7 @@ At the very beginning of your execution, use `find_tools` to discover tools. Use
43
43
 
44
44
  Fallbacks:
45
45
  - **Project tracker unavailable** → Skip transitions, manage status via STATUS.json only
46
- - **KB unavailable** → Skip KB verification, rely on file checks
46
+ - **KB unavailable** → Attempt MCP bootstrap first (tool-usage-dynamic Step 0). If still down → report to user explicitly ("⚠️ KB unavailable — fallback to file checks, KB verification skipped"), then rely on file checks
47
47
  - **DOCX export unavailable** → Skip DOCX export, attach markdown or skip attachment
48
48
 
49
49
  ### Discovery Report
@@ -523,7 +523,14 @@ invokeSubAgent(
523
523
  - If exists → resume from `currentPhase`
524
524
  - If not exists → scan for existing files to build initial status
525
525
 
526
- 2. **Scan existing files** (when STATUS.json doesn't exist):
526
+ 2. **KB-first check** (MANDATORY — before deciding next steps):
527
+ - Use the discovered KB "search" tool (query: "{TICKET} BRD FSD TDD") — cross-check STATUS.json against documents actually in KB (detect stale/corrupt status)
528
+ - Use the discovered KB "search" tool (query: "personalized rules preferences conventions") — load user's personalized rules (agent-self-learning Rule #7)
529
+ - Use the discovered KB "search" tool (query: "{PROJECT} lessons learned") — check prior lessons/error patterns relevant to this ticket
530
+ - If any KB finding contradicts STATUS.json → report the conflict to user before proceeding
531
+ - If KB unavailable → attempt MCP bootstrap first, then report to user before falling back to file checks
532
+
533
+ 3. **Scan existing files** (when STATUS.json doesn't exist):
527
534
  ```
528
535
  documents/{TICKET}/BRD.md exists? → requirements: done
529
536
  documents/{TICKET}/FSD.md exists? → specification: done
@@ -533,7 +540,7 @@ invokeSubAgent(
533
540
  ```
534
541
  Create STATUS.json from scan results.
535
542
 
536
- 3. **Check Jira ticket status** (MANDATORY on every resume):
543
+ 4. **Check Jira ticket status** (MANDATORY on every resume):
537
544
  ```
538
545
  issue = the discovered project tracker "get issue" tool (issue_key: "{TICKET}")
539
546
  jiraStatus = issue.status // "To Do", "Docs Review", "In Progress", "In Review", "QA Test", "UAT", "Ready For Product", "Done"
@@ -554,7 +561,7 @@ invokeSubAgent(
554
561
 
555
562
  **Quan trọng:** Nếu Jira status đã advance (ví dụ: reviewer đã chuyển DOCS REVIEW → IN PROGRESS), SM tự động tiếp tục phase tương ứng mà KHÔNG cần user nói lại.
556
563
 
557
- 4. **Read Jira comments** (MANDATORY on every resume):
564
+ 5. **Read Jira comments** (MANDATORY on every resume):
558
565
  ```
559
566
  the discovered project tracker "get issue" tool (issue_key: "{TICKET}", comment_limit: 10)
560
567
  ```
@@ -592,7 +599,7 @@ invokeSubAgent(
592
599
  - If comment contains rejection/feedback → SM MUST report to user before taking action
593
600
  - Store last processed comment timestamp in STATUS.json: `"lastCommentProcessed": "2026-05-01T18:00:00Z"`
594
601
 
595
- 5. **Report current status to user:**
602
+ 6. **Report current status to user:**
596
603
  ```
597
604
  📋 {TICKET} — Status Report
598
605
 
@@ -610,7 +617,7 @@ invokeSubAgent(
610
617
  ➡️ Tiếp tục Phase 3 (Design)?
611
618
  ```
612
619
 
613
- 6. **Wait for user confirmation** before proceeding.
620
+ 7. **Wait for user confirmation** before proceeding.
614
621
 
615
622
  ### Step 1: Execute Phase — Requirements (BA → BRD)
616
623
 
package/Dockerfile CHANGED
@@ -94,9 +94,12 @@ RUN npm install -g npm@${NPM_VERSION} --no-audit --no-fund --loglevel=error \
94
94
  && rm -rf /tmp/*.tgz \
95
95
  && node -e "require('${NM}/brace-expansion'); require('${NM}/undici')" \
96
96
  && npm --version \
97
- # SEC-04: upgrade Debian packages to fix CVE-2026-103111 (libpcre2-8-0 HIGH) \
97
+ # SEC-04: upgrade Debian packages to fix CVE-2026-103111 (libpcre2-8-0 HIGH)
98
+ # and the perl CVE set found by Trivy on 2026-10-10 (CVE-2026-13221 CRITICAL,
99
+ # CVE-2026-42497 HIGH, CVE-2026-42496, CVE-2026-8376, CVE-2026-48962,
100
+ # CVE-2026-57432, CVE-2026-57433 — perl-base/perl-modules in node:XX-slim)
98
101
  && apt-get update \
99
- && apt-get upgrade -y --no-install-recommends libpcre2-8-0 \
102
+ && apt-get upgrade -y --no-install-recommends libpcre2-8-0 perl-base perl-modules-5.36 \
100
103
  && apt-get clean \
101
104
  && rm -rf /var/lib/apt/lists/*
102
105
 
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
  </p>
7
7
 
8
8
  <p align="center">
9
- <img src="https://img.shields.io/badge/version-1.47.1-blue?style=for-the-badge" alt="Version">
9
+ <img src="https://img.shields.io/badge/version-1.48.0-blue?style=for-the-badge" alt="Version">
10
10
  <img src="https://img.shields.io/badge/tools-66+-teal?style=for-the-badge" alt="Tools">
11
11
  <img src="https://img.shields.io/badge/node-%3E%3D18-green?style=for-the-badge" alt="Node">
12
12
  </p>
@@ -38,7 +38,7 @@ Server starts at **http://localhost:48721**. Verify it's running:
38
38
 
39
39
  ```bash
40
40
  curl http://localhost:48721/health
41
- # → {"status":"healthy","version":"1.47.1","uptime":5,"tools_loaded":52}
41
+ # → {"status":"healthy","version":"1.48.0","uptime":5,"tools_loaded":52}
42
42
  ```
43
43
 
44
44
  ### Development Mode (auto-reload)
@@ -233,6 +233,7 @@ The embedding model is expected at `.code-intel/models/model.onnx`. Download it:
233
233
 
234
234
  | Version | Date | Ticket | Changes |
235
235
  |---------|------|--------|---------|
236
+ | 1.48.0 | 2026-10-10 | SA4E-341 | Version sync (E2E testing framework delivered in standalone `e2e/` package — no backend runtime changes). Also includes SA4E-335 DB-layer 6 bugfixes (memoized id-column check, literal-safe placeholder translation, identifier quoting SQLi guard, safeExec SQLSTATE filter, writable test-backend data dir), SA4E-349 auth 401 fail-loud fixes, SA4E-350 Pega '@baseclass' appliesTo passthrough fix, enrichment retry scoped via X-Project-Id + runtime settings module. |
236
237
  | 1.47.1 | 2026-10-04 | — | Fix Docker CI fail (Trivy CVE-2026-103111 libpcre2-8-0 HIGH), defensive JSON parse on `/auth/refresh`, SA4E-100 Pega BELONGS_TO edges. |
237
238
  | 1.47.0 | 2026-10-02 | — | New endpoint `GET /api/v1/enrichment/failures?limit=N` (clamp 1–1000, default 200) returns full FAILED enrichment task list from `pending_tasks` with resolved source, error, retry count, timestamp (`PendingTaskRepository.listFailedDetailed`). Surfaces ingest failures to the extension beyond the 10-item status tooltip. |
238
239
  | 1.46.2 | 2026-09-28 | — | Version sync (no backend code changes; packaging fix lives in `extension`: missing direct `yargs` dep for vendored devtools barrel). |
@@ -3,14 +3,16 @@
3
3
  * SA4E-50: All functions are async; use getDbAdapter() for multi-DB support.
4
4
  */
5
5
  import { getDbAdapter } from './core.js';
6
- /** Map raw DB row to typed ConfigChange. */
6
+ import { maskSecret, isSecretConfigKey } from '../sanitize.js';
7
+ /** Map raw DB row to typed ConfigChange. SA4E-338 S4 (D-SEC-06): secret rows are masked on read. */
7
8
  function rowToConfigChange(r) {
9
+ const secret = isSecretConfigKey(r.section, r.key);
8
10
  return {
9
11
  id: r.id,
10
12
  section: r.section,
11
13
  key: r.key,
12
- oldValue: r.old_value,
13
- newValue: r.new_value,
14
+ oldValue: secret ? maskSecret(r.old_value) : r.old_value,
15
+ newValue: secret ? maskSecret(r.new_value) : r.new_value,
14
16
  changedBy: r.changed_by,
15
17
  changedAt: r.changed_at,
16
18
  requiresRestart: !!r.requires_restart,
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/admin/db/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/admin/db/config.ts"],"names":[],"mappings":"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"}
@@ -24,3 +24,15 @@ export declare function escapeHtml(value: unknown): string;
24
24
  * Returns a new object; does not mutate the input.
25
25
  */
26
26
  export declare function sanitizeKbEntry(entry: Record<string, unknown>): Record<string, unknown>;
27
+ /** Sentinel written in place of secret values in API responses, history and audit entries. */
28
+ export declare const MASKED_VALUE = "***";
29
+ /**
30
+ * SA4E-338 S4 (D-SEC-06): mask a secret value for any API / history / audit output.
31
+ * @returns `***` when a non-empty value is present, `''` when empty/absent.
32
+ */
33
+ export declare function maskSecret(value: unknown): string;
34
+ /**
35
+ * SA4E-338 S4 (D-SEC-06): config keys whose values are secrets — never returned,
36
+ * echoed or audited in plaintext.
37
+ */
38
+ export declare function isSecretConfigKey(section: string, key: string): boolean;
@@ -58,4 +58,23 @@ export function sanitizeKbEntry(entry) {
58
58
  }
59
59
  return clean;
60
60
  }
61
+ /** Sentinel written in place of secret values in API responses, history and audit entries. */
62
+ export const MASKED_VALUE = '***';
63
+ /**
64
+ * SA4E-338 S4 (D-SEC-06): mask a secret value for any API / history / audit output.
65
+ * @returns `***` when a non-empty value is present, `''` when empty/absent.
66
+ */
67
+ export function maskSecret(value) {
68
+ if (value === undefined || value === null)
69
+ return '';
70
+ const s = String(value);
71
+ return s.length > 0 ? MASKED_VALUE : '';
72
+ }
73
+ /**
74
+ * SA4E-338 S4 (D-SEC-06): config keys whose values are secrets — never returned,
75
+ * echoed or audited in plaintext.
76
+ */
77
+ export function isSecretConfigKey(section, key) {
78
+ return (section === 'llm' && key === 'apiKey') || (section === 'auth' && key === 'entraClientSecret');
79
+ }
61
80
  //# sourceMappingURL=sanitize.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"sanitize.js","sourceRoot":"","sources":["../../src/admin/sanitize.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,4EAA4E;AAC5E,MAAM,WAAW,GAAG,yBAAyB,CAAC;AAE9C,yCAAyC;AACzC,MAAM,YAAY,GAAG,gCAAgC,CAAC;AAEtD;;GAEG;AACH,MAAM,UAAU,UAAU,CAAC,GAAY;IACrC,OAAO,OAAO,GAAG,KAAK,QAAQ,IAAI,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC1D,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,IAAe;IAC5C,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;YAAE,OAAO,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACnE,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,KAAc;IACzC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC/D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,KAAc;IACvC,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,EAAE,CAAC;IACzC,OAAO,KAAK;SACT,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAC7B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,KAA8B;IAC5D,MAAM,KAAK,GAAG,EAAE,GAAG,KAAK,EAAE,CAAC;IAC3B,KAAK,MAAM,KAAK,IAAI,CAAC,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC;QACvE,IAAI,OAAO,KAAK,CAAC,KAAK,CAAC,KAAK,QAAQ,EAAE,CAAC;YACrC,KAAK,CAAC,KAAK,CAAC,GAAG,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;QAC1C,CAAC;IACH,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9B,KAAK,CAAC,IAAI,GAAI,KAAK,CAAC,IAAkB,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAC5D,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
1
+ {"version":3,"file":"sanitize.js","sourceRoot":"","sources":["../../src/admin/sanitize.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,4EAA4E;AAC5E,MAAM,WAAW,GAAG,yBAAyB,CAAC;AAE9C,yCAAyC;AACzC,MAAM,YAAY,GAAG,gCAAgC,CAAC;AAEtD;;GAEG;AACH,MAAM,UAAU,UAAU,CAAC,GAAY;IACrC,OAAO,OAAO,GAAG,KAAK,QAAQ,IAAI,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC1D,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,IAAe;IAC5C,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;YAAE,OAAO,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACnE,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,KAAc;IACzC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC/D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,KAAc;IACvC,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,EAAE,CAAC;IACzC,OAAO,KAAK;SACT,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAC7B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,KAA8B;IAC5D,MAAM,KAAK,GAAG,EAAE,GAAG,KAAK,EAAE,CAAC;IAC3B,KAAK,MAAM,KAAK,IAAI,CAAC,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC;QACvE,IAAI,OAAO,KAAK,CAAC,KAAK,CAAC,KAAK,QAAQ,EAAE,CAAC;YACrC,KAAK,CAAC,KAAK,CAAC,GAAG,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;QAC1C,CAAC;IACH,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9B,KAAK,CAAC,IAAI,GAAI,KAAK,CAAC,IAAkB,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAC5D,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,8FAA8F;AAC9F,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,CAAC;AAElC;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,KAAc;IACvC,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,EAAE,CAAC;IACrD,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IACxB,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;AAC1C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAe,EAAE,GAAW;IAC5D,OAAO,CAAC,OAAO,KAAK,KAAK,IAAI,GAAG,KAAK,QAAQ,CAAC,IAAI,CAAC,OAAO,KAAK,MAAM,IAAI,GAAG,KAAK,mBAAmB,CAAC,CAAC;AACxG,CAAC"}
@@ -22,6 +22,8 @@ export declare class PostgresAdapter implements DatabaseAdapter {
22
22
  private pool;
23
23
  private connected;
24
24
  private serverVersion;
25
+ /** Memoized per-table result of the information_schema `id` column check. */
26
+ private idColumnCache;
25
27
  constructor(config: PostgresConfig);
26
28
  connect(): Promise<void>;
27
29
  disconnect(): Promise<void>;
@@ -40,6 +42,13 @@ export declare class PostgresAdapter implements DatabaseAdapter {
40
42
  */
41
43
  private getQueryFn;
42
44
  runAsync(sql: string, params?: unknown[]): Promise<RunResult>;
45
+ /**
46
+ * Memoized per-table check: does the target of this INSERT have an `id` column?
47
+ * Runs on the active query fn (tx client inside transactions) so tables created
48
+ * in the same transaction are visible. Check failure → false (plain INSERT is
49
+ * always safe); the cache costs one metadata query per table per lifetime.
50
+ */
51
+ private tableHasIdColumn;
43
52
  getAsync<T = unknown>(sql: string, params?: unknown[]): Promise<T | undefined>;
44
53
  allAsync<T = unknown>(sql: string, params?: unknown[]): Promise<T[]>;
45
54
  execAsync(sql: string): Promise<void>;
@@ -53,5 +62,4 @@ export declare class PostgresAdapter implements DatabaseAdapter {
53
62
  getVersion(): Promise<string>;
54
63
  getTableNames(): Promise<string[]>;
55
64
  getRowCount(table: string): Promise<number>;
56
- private translateParams;
57
65
  }
@@ -5,6 +5,7 @@
5
5
  * Implements: SA4E-33
6
6
  */
7
7
  import { AsyncLocalStorage } from 'node:async_hooks';
8
+ import { parseInsertTarget, quoteIdentifier, translatePlaceholders } from './pg-sql-utils.js';
8
9
  /** AsyncLocalStorage to scope queries to a dedicated client during transactions. */
9
10
  const txClientStorage = new AsyncLocalStorage();
10
11
  export class PostgresAdapter {
@@ -12,6 +13,8 @@ export class PostgresAdapter {
12
13
  pool = null;
13
14
  connected = false;
14
15
  serverVersion = '';
16
+ /** Memoized per-table result of the information_schema `id` column check. */
17
+ idColumnCache = new Map();
15
18
  constructor(config) {
16
19
  this.config = config;
17
20
  }
@@ -67,53 +70,53 @@ export class PostgresAdapter {
67
70
  }
68
71
  // Async methods
69
72
  async runAsync(sql, params) {
70
- const translated = this.translateParams(sql);
73
+ const translated = translatePlaceholders(sql);
71
74
  const queryFn = this.getQueryFn();
72
- const inTransaction = !!txClientStorage.getStore();
73
- // SA4E-104: If INSERT without RETURNING, add RETURNING id to get lastInsertRowid
74
75
  const isInsert = /^\s*INSERT/i.test(sql);
75
76
  const hasReturning = /RETURNING/i.test(sql);
76
- if (isInsert && !hasReturning) {
77
- const withReturning = translated + ' RETURNING id';
78
- if (inTransaction) {
79
- // Inside transaction: attempt RETURNING id — if table has no 'id' column,
80
- // suppress that specific error and retry without RETURNING on same client.
81
- // PostgreSQL aborts tx on error, BUT "column does not exist" is a planning error
82
- // that does NOT actually abort the transaction in all PG versions.
83
- // If it does abort: the error propagates → transactionAsync() ROLLBACK is correct.
84
- const r = await queryFn(withReturning, params);
85
- const insertedId = r.rows?.[0]?.id ?? 0;
86
- return { changes: r.rowCount ?? 0, lastInsertRowid: insertedId };
87
- }
88
- // Outside transaction: attempt RETURNING id with dedicated client fallback.
89
- // BUG FIX: pool.query() uses implicit transaction — if RETURNING id fails,
90
- // the connection is "poisoned" (aborted tx). We MUST use a fresh client for fallback.
91
- try {
92
- const r = await queryFn(withReturning, params);
93
- const insertedId = r.rows?.[0]?.id ?? 0;
94
- return { changes: r.rowCount ?? 0, lastInsertRowid: insertedId };
95
- }
96
- catch {
97
- // Fallback: table may not have 'id' column — use dedicated client to avoid poisoned connection.
98
- const client = await this.pool.connect();
99
- try {
100
- const r = await client.query(translated, params);
101
- return { changes: r.rowCount ?? 0, lastInsertRowid: 0 };
102
- }
103
- finally {
104
- client.release();
105
- }
106
- }
77
+ // SA4E-104: append RETURNING id to get lastInsertRowid — but ONLY when the
78
+ // table actually has an `id` column. A failed RETURNING inside a transaction
79
+ // aborts it (25P02), so we never gamble: the memoized check decides upfront.
80
+ if (isInsert && !hasReturning && (await this.tableHasIdColumn(sql, queryFn))) {
81
+ const r = await queryFn(`${translated} RETURNING id`, params);
82
+ return { changes: r.rowCount ?? 0, lastInsertRowid: r.rows?.[0]?.id ?? 0 };
107
83
  }
84
+ // Id-less table / unparsable target / check failed → plain INSERT, which can
85
+ // never poison a transaction or a pooled connection.
108
86
  const r = await queryFn(translated, params);
109
87
  return { changes: r.rowCount ?? 0, lastInsertRowid: 0 };
110
88
  }
89
+ /**
90
+ * Memoized per-table check: does the target of this INSERT have an `id` column?
91
+ * Runs on the active query fn (tx client inside transactions) so tables created
92
+ * in the same transaction are visible. Check failure → false (plain INSERT is
93
+ * always safe); the cache costs one metadata query per table per lifetime.
94
+ */
95
+ async tableHasIdColumn(sql, queryFn) {
96
+ const target = parseInsertTarget(sql);
97
+ if (!target)
98
+ return false;
99
+ const cached = this.idColumnCache.get(target.key);
100
+ if (cached !== undefined)
101
+ return cached;
102
+ try {
103
+ const r = await queryFn(`SELECT 1 FROM information_schema.columns WHERE table_schema = $1 AND table_name = $2 AND column_name = 'id' LIMIT 1`, [target.schema, target.table]);
104
+ const exists = (r.rows?.length ?? 0) > 0;
105
+ this.idColumnCache.set(target.key, exists);
106
+ return exists;
107
+ }
108
+ catch (err) {
109
+ console.warn('[PostgresAdapter] id-column lookup failed — defaulting to plain INSERT:', err.message);
110
+ this.idColumnCache.set(target.key, false);
111
+ return false;
112
+ }
113
+ }
111
114
  async getAsync(sql, params) {
112
- const r = await this.getQueryFn()(this.translateParams(sql), params);
115
+ const r = await this.getQueryFn()(translatePlaceholders(sql), params);
113
116
  return r.rows[0];
114
117
  }
115
118
  async allAsync(sql, params) {
116
- const r = await this.getQueryFn()(this.translateParams(sql), params);
119
+ const r = await this.getQueryFn()(translatePlaceholders(sql), params);
117
120
  return r.rows;
118
121
  }
119
122
  async execAsync(sql) {
@@ -152,12 +155,8 @@ export class PostgresAdapter {
152
155
  return r.rows.map((row) => row.tablename);
153
156
  }
154
157
  async getRowCount(table) {
155
- const r = await this.getQueryFn()(`SELECT COUNT(*) as cnt FROM "${table}"`);
158
+ const r = await this.getQueryFn()(`SELECT COUNT(*) as cnt FROM ${quoteIdentifier(table)}`);
156
159
  return parseInt(r.rows[0]?.cnt || '0', 10);
157
160
  }
158
- translateParams(sql) {
159
- let idx = 0;
160
- return sql.replace(/\?/g, () => `$${++idx}`);
161
- }
162
161
  }
163
162
  //# sourceMappingURL=PostgresAdapter.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"PostgresAdapter.js","sourceRoot":"","sources":["../../../src/database/adapters/PostgresAdapter.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAmBrD,oFAAoF;AACpF,MAAM,eAAe,GAAG,IAAI,iBAAiB,EAAgE,CAAC;AAE9G,MAAM,OAAO,eAAe;IAKG;IAJrB,IAAI,GAAQ,IAAI,CAAC;IACjB,SAAS,GAAG,KAAK,CAAC;IAClB,aAAa,GAAG,EAAE,CAAC;IAE3B,YAA6B,MAAsB;QAAtB,WAAM,GAAN,MAAM,CAAgB;IAAG,CAAC;IAEvD,KAAK,CAAC,OAAO;QACX,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,CAAC,IAAI,GAAG,IAAI,IAAI,CAAC;YACnB,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI;YACtB,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI;YACtB,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ;YAC1B,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ;YAC9B,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ;YAC9B,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,kBAAkB,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK;YAC5D,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC;YAC/B,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,IAAI,EAAE;SACjC,CAAC,CAAC;QACH,mFAAmF;QACnF,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;YACjB,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAU,EAAE,EAAE;gBACnC,OAAO,CAAC,KAAK,CAAC,qDAAqD,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;YACpF,CAAC,CAAC,CAAC;QACL,CAAC;QACD,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC;QACtD,IAAI,CAAC,aAAa,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,OAAO,IAAI,YAAY,CAAC;QAC1D,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;IACxB,CAAC;IAED,KAAK,CAAC,UAAU;QACd,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YAAC,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;YAAC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;YAAC,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;QAAC,CAAC;IACrF,CAAC;IAED,WAAW,KAAc,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;IAEjD,SAAS;QACP,OAAO,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,IAAI,CAAC,aAAa;YACnF,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC;IAClG,CAAC;IAED,qDAAqD;IACrD,GAAG,CAAC,IAAY,EAAE,OAAmB,IAAe,MAAM,IAAI,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC;IACtF,GAAG,CAAc,IAAY,EAAE,OAAmB,IAAmB,MAAM,IAAI,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC;IACvG,GAAG,CAAc,IAAY,EAAE,OAAmB,IAAS,MAAM,IAAI,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC;IAC7F,IAAI,CAAC,IAAY,IAAU,MAAM,IAAI,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IAC9D,WAAW,CAAI,GAAY,IAAO,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC;IAC5E,OAAO,CAAC,IAAY,IAAuB,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC;IAElF;;;;OAIG;IACK,UAAU;QAChB,MAAM,QAAQ,GAAG,eAAe,CAAC,QAAQ,EAAE,CAAC;QAC5C,OAAO,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACrE,CAAC;IAED,gBAAgB;IAChB,KAAK,CAAC,QAAQ,CAAC,GAAW,EAAE,MAAkB;QAC5C,MAAM,UAAU,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;QAC7C,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;QAClC,MAAM,aAAa,GAAG,CAAC,CAAC,eAAe,CAAC,QAAQ,EAAE,CAAC;QACnD,iFAAiF;QACjF,MAAM,QAAQ,GAAG,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACzC,MAAM,YAAY,GAAG,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC5C,IAAI,QAAQ,IAAI,CAAC,YAAY,EAAE,CAAC;YAC9B,MAAM,aAAa,GAAG,UAAU,GAAG,eAAe,CAAC;YACnD,IAAI,aAAa,EAAE,CAAC;gBAClB,0EAA0E;gBAC1E,2EAA2E;gBAC3E,iFAAiF;gBACjF,mEAAmE;gBACnE,mFAAmF;gBACnF,MAAM,CAAC,GAAG,MAAM,OAAO,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;gBAC/C,MAAM,UAAU,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;gBACxC,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC,QAAQ,IAAI,CAAC,EAAE,eAAe,EAAE,UAAU,EAAE,CAAC;YACnE,CAAC;YACD,4EAA4E;YAC5E,2EAA2E;YAC3E,sFAAsF;YACtF,IAAI,CAAC;gBACH,MAAM,CAAC,GAAG,MAAM,OAAO,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;gBAC/C,MAAM,UAAU,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;gBACxC,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC,QAAQ,IAAI,CAAC,EAAE,eAAe,EAAE,UAAU,EAAE,CAAC;YACnE,CAAC;YAAC,MAAM,CAAC;gBACP,gGAAgG;gBAChG,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;gBACzC,IAAI,CAAC;oBACH,MAAM,CAAC,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;oBACjD,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC,QAAQ,IAAI,CAAC,EAAE,eAAe,EAAE,CAAC,EAAE,CAAC;gBAC1D,CAAC;wBAAS,CAAC;oBACT,MAAM,CAAC,OAAO,EAAE,CAAC;gBACnB,CAAC;YACH,CAAC;QACH,CAAC;QACD,MAAM,CAAC,GAAG,MAAM,OAAO,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QAC5C,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC,QAAQ,IAAI,CAAC,EAAE,eAAe,EAAE,CAAC,EAAE,CAAC;IAC1D,CAAC;IAED,KAAK,CAAC,QAAQ,CAAc,GAAW,EAAE,MAAkB;QACzD,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,UAAU,EAAE,CAAC,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC;QACrE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAkB,CAAC;IACpC,CAAC;IAED,KAAK,CAAC,QAAQ,CAAc,GAAW,EAAE,MAAkB;QACzD,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,UAAU,EAAE,CAAC,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC;QACrE,OAAO,CAAC,CAAC,IAAW,CAAC;IACvB,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,GAAW;QACzB,MAAM,IAAI,CAAC,UAAU,EAAE,CAAC,GAAG,CAAC,CAAC;IAC/B,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,gBAAgB,CAAI,EAAoB;QAC5C,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;QACzC,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAC5B,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,GAAG,CACtC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,EACpC,EAAE,CACH,CAAC;YACF,MAAM,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;YAC7B,OAAO,MAAM,CAAC;QAChB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC;gBAAC,MAAM,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;YAAC,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBAAC,OAAO,CAAC,KAAK,CAAC,2CAA2C,EAAG,GAAa,CAAC,OAAO,CAAC,CAAC;YAAC,CAAC;YAC3I,MAAM,GAAG,CAAC;QACZ,CAAC;gBAAS,CAAC;YACT,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,CAAC;IACH,CAAC;IAED,SAAS,KAAqB,OAAO,YAAY,CAAC,CAAC,CAAC;IACpD,KAAK,CAAC,UAAU,KAAsB,OAAO,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC;IAElE,KAAK,CAAC,aAAa;QACjB,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,UAAU,EAAE,CAAC,8EAA8E,CAAC,CAAC;QAClH,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAQ,EAAE,EAAE,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IACjD,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,KAAa;QAC7B,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,UAAU,EAAE,CAAC,gCAAgC,KAAK,GAAG,CAAC,CAAC;QAC5E,OAAO,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,GAAG,EAAE,EAAE,CAAC,CAAC;IAC7C,CAAC;IAEO,eAAe,CAAC,GAAW;QACjC,IAAI,GAAG,GAAG,CAAC,CAAC;QACZ,OAAO,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;IAC/C,CAAC;CACF"}
1
+ {"version":3,"file":"PostgresAdapter.js","sourceRoot":"","sources":["../../../src/database/adapters/PostgresAdapter.ts"],"names":[],"mappings":"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"}
@@ -31,6 +31,6 @@ export declare class PostgresAsyncAdapter implements AsyncDatabaseAdapter {
31
31
  all<T = unknown>(sql: string, params?: unknown[]): Promise<T[]>;
32
32
  exec(sql: string): Promise<void>;
33
33
  transaction<T>(fn: () => Promise<T>): Promise<T>;
34
- /** Convert ? placeholders to $1, $2, etc. for pg driver. */
34
+ /** Convert ? placeholders to $1, $2, etc. for pg driver — skips `?` in literals. */
35
35
  private translateParams;
36
36
  }
@@ -3,6 +3,7 @@
3
3
  * SA4E-44: Native async PostgreSQL adapter for MemoryModule and other async-first consumers.
4
4
  * Cleaner than PostgresAdapter: no sync stubs that throw, pure async contract.
5
5
  */
6
+ import { translatePlaceholders } from './pg-sql-utils.js';
6
7
  export class PostgresAsyncAdapter {
7
8
  config;
8
9
  pool = null;
@@ -75,10 +76,9 @@ export class PostgresAsyncAdapter {
75
76
  client.release();
76
77
  }
77
78
  }
78
- /** Convert ? placeholders to $1, $2, etc. for pg driver. */
79
+ /** Convert ? placeholders to $1, $2, etc. for pg driver — skips `?` in literals. */
79
80
  translateParams(sql) {
80
- let idx = 0;
81
- return sql.replace(/\?/g, () => `$${++idx}`);
81
+ return translatePlaceholders(sql);
82
82
  }
83
83
  }
84
84
  //# sourceMappingURL=PostgresAsyncAdapter.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"PostgresAsyncAdapter.js","sourceRoot":"","sources":["../../../src/database/adapters/PostgresAsyncAdapter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"PostgresAsyncAdapter.js","sourceRoot":"","sources":["../../../src/database/adapters/PostgresAsyncAdapter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Pure SQL-string helpers shared by the PostgreSQL adapters — SA4E-335 hardening.
3
+ *
4
+ * Why a separate module: PostgresAdapter sits at its 200-line budget, and both
5
+ * PG adapters shared the same naive `?`→$n replacement, which corrupts `?`
6
+ * inside string literals. Pure functions keep the adapters lean and testable.
7
+ */
8
+ export interface InsertTarget {
9
+ /** Stable cache key (as parsed). */
10
+ key: string;
11
+ /** Schema for the information_schema lookup (default: public). */
12
+ schema: string;
13
+ /** Unqualified table name. */
14
+ table: string;
15
+ }
16
+ /**
17
+ * Convert ? placeholders to $1, $2, ... for the pg driver — skipping any `?`
18
+ * inside single-quoted string literals ('' doubling is the literal escape).
19
+ */
20
+ export declare function translatePlaceholders(sql: string): string;
21
+ /**
22
+ * Extract the target table of an INSERT statement (bare or quoted, optional
23
+ * schema). Bare identifiers are lowercased (PG semantics); returns null when
24
+ * the target is unparsable so callers can fall back to plain INSERT.
25
+ */
26
+ export declare function parseInsertTarget(sql: string): InsertTarget | null;
27
+ /**
28
+ * Validate and quote a table identifier — throws on anything unsafe so a
29
+ * caller-controlled name can never reach SQL as raw interpolation (SQLi guard).
30
+ */
31
+ export declare function quoteIdentifier(name: string): string;
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Pure SQL-string helpers shared by the PostgreSQL adapters — SA4E-335 hardening.
3
+ *
4
+ * Why a separate module: PostgresAdapter sits at its 200-line budget, and both
5
+ * PG adapters shared the same naive `?`→$n replacement, which corrupts `?`
6
+ * inside string literals. Pure functions keep the adapters lean and testable.
7
+ */
8
+ /**
9
+ * Convert ? placeholders to $1, $2, ... for the pg driver — skipping any `?`
10
+ * inside single-quoted string literals ('' doubling is the literal escape).
11
+ */
12
+ export function translatePlaceholders(sql) {
13
+ let idx = 0;
14
+ let out = '';
15
+ let inString = false;
16
+ for (let i = 0; i < sql.length; i++) {
17
+ const ch = sql[i];
18
+ if (inString) {
19
+ out += ch;
20
+ if (ch === "'") {
21
+ if (sql[i + 1] === "'") {
22
+ out += "'";
23
+ i++;
24
+ }
25
+ else {
26
+ inString = false;
27
+ }
28
+ }
29
+ continue;
30
+ }
31
+ if (ch === "'") {
32
+ inString = true;
33
+ out += ch;
34
+ }
35
+ else if (ch === '?') {
36
+ out += `$${++idx}`;
37
+ }
38
+ else {
39
+ out += ch;
40
+ }
41
+ }
42
+ return out;
43
+ }
44
+ /**
45
+ * Extract the target table of an INSERT statement (bare or quoted, optional
46
+ * schema). Bare identifiers are lowercased (PG semantics); returns null when
47
+ * the target is unparsable so callers can fall back to plain INSERT.
48
+ */
49
+ export function parseInsertTarget(sql) {
50
+ const m = /^\s*INSERT\s+INTO\s+(?:"([^"]+)"|([A-Za-z_][A-Za-z0-9_.$]*))/i.exec(sql);
51
+ if (!m)
52
+ return null;
53
+ if (m[1] !== undefined)
54
+ return { key: `"${m[1]}"`, schema: 'public', table: m[1] };
55
+ const name = m[2].toLowerCase();
56
+ const dot = name.lastIndexOf('.');
57
+ if (dot === -1)
58
+ return { key: name, schema: 'public', table: name };
59
+ return { key: name, schema: name.slice(0, dot), table: name.slice(dot + 1) };
60
+ }
61
+ /**
62
+ * Validate and quote a table identifier — throws on anything unsafe so a
63
+ * caller-controlled name can never reach SQL as raw interpolation (SQLi guard).
64
+ */
65
+ export function quoteIdentifier(name) {
66
+ const t = name.trim();
67
+ if (!t)
68
+ throw new Error('Empty table identifier');
69
+ return t.split('.').map((seg) => {
70
+ const isQuoted = seg.length >= 2 && seg.startsWith('"') && seg.endsWith('"');
71
+ const bare = isQuoted ? seg.slice(1, -1) : seg;
72
+ if (!/^[A-Za-z_][A-Za-z0-9_$]*$/.test(bare) && !isQuoted) {
73
+ throw new Error(`Unsafe table identifier: ${name}`);
74
+ }
75
+ return `"${bare.replace(/"/g, '""')}"`;
76
+ }).join('.');
77
+ }
78
+ //# sourceMappingURL=pg-sql-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pg-sql-utils.js","sourceRoot":"","sources":["../../../src/database/adapters/pg-sql-utils.ts"],"names":[],"mappings":"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"}
@@ -168,13 +168,21 @@ export async function ensurePostgresIndexSchema(adapter) {
168
168
  logger.error({ err }, '[pg-schema-ensure] Schema ensure failed (non-fatal)');
169
169
  }
170
170
  }
171
- /** Execute SQL silently — log and continue on error. */
171
+ /** PG SQLSTATE codes meaning "already exists / nothing to do" — safe to ignore. */
172
+ const IDEMPOTENT_SQLSTATES = new Set(['42P07', '42710', '42701', '42P01']);
173
+ /**
174
+ * Execute DDL: redundant statements (already exists) are ignored by SQLSTATE
175
+ * code; real errors (permission, syntax, …) are logged loudly, not swallowed.
176
+ */
172
177
  async function safeExec(adapter, sql) {
173
178
  try {
174
179
  await adapter.runAsync(sql, []);
175
180
  }
176
- catch {
177
- // Ignore — statement may be redundant (column/index already exists)
181
+ catch (err) {
182
+ const code = err.code;
183
+ if (code && IDEMPOTENT_SQLSTATES.has(code))
184
+ return;
185
+ logger.error({ err, sql: sql.trim().slice(0, 120) }, '[pg-schema-ensure] DDL failed — schema may be incomplete');
178
186
  }
179
187
  }
180
188
  //# sourceMappingURL=pg-schema-ensure.js.map