@askdkc/kiokuko 0.1.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.
- package/LICENSE +21 -0
- package/README.ja.md +112 -0
- package/README.ko.md +112 -0
- package/README.md +147 -0
- package/README.zh-CN.md +112 -0
- package/dist/agent-file/atomic-write.d.ts +6 -0
- package/dist/agent-file/atomic-write.d.ts.map +1 -0
- package/dist/agent-file/atomic-write.js +36 -0
- package/dist/agent-file/atomic-write.js.map +1 -0
- package/dist/agent-file/managed-block.d.ts +12 -0
- package/dist/agent-file/managed-block.d.ts.map +1 -0
- package/dist/agent-file/managed-block.js +56 -0
- package/dist/agent-file/managed-block.js.map +1 -0
- package/dist/agent-file/render.d.ts +14 -0
- package/dist/agent-file/render.d.ts.map +1 -0
- package/dist/agent-file/render.js +41 -0
- package/dist/agent-file/render.js.map +1 -0
- package/dist/akinator/domain.d.ts +25 -0
- package/dist/akinator/domain.d.ts.map +1 -0
- package/dist/akinator/domain.js +255 -0
- package/dist/akinator/domain.js.map +1 -0
- package/dist/akinator/orchestrator.d.ts +8 -0
- package/dist/akinator/orchestrator.d.ts.map +1 -0
- package/dist/akinator/orchestrator.js +11 -0
- package/dist/akinator/orchestrator.js.map +1 -0
- package/dist/akinator/service.d.ts +35 -0
- package/dist/akinator/service.d.ts.map +1 -0
- package/dist/akinator/service.js +296 -0
- package/dist/akinator/service.js.map +1 -0
- package/dist/akinator/store.d.ts +96 -0
- package/dist/akinator/store.d.ts.map +1 -0
- package/dist/akinator/store.js +583 -0
- package/dist/akinator/store.js.map +1 -0
- package/dist/akinator/types.d.ts +49 -0
- package/dist/akinator/types.d.ts.map +1 -0
- package/dist/akinator/types.js +2 -0
- package/dist/akinator/types.js.map +1 -0
- package/dist/bin/kiokuko.d.ts +3 -0
- package/dist/bin/kiokuko.d.ts.map +1 -0
- package/dist/bin/kiokuko.js +6 -0
- package/dist/bin/kiokuko.js.map +1 -0
- package/dist/cli.d.ts +10 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +362 -0
- package/dist/cli.js.map +1 -0
- package/dist/client/runtime-discovery.d.ts +15 -0
- package/dist/client/runtime-discovery.d.ts.map +1 -0
- package/dist/client/runtime-discovery.js +45 -0
- package/dist/client/runtime-discovery.js.map +1 -0
- package/dist/client/server-client.d.ts +20 -0
- package/dist/client/server-client.d.ts.map +1 -0
- package/dist/client/server-client.js +424 -0
- package/dist/client/server-client.js.map +1 -0
- package/dist/commands/agent.d.ts +10 -0
- package/dist/commands/agent.d.ts.map +1 -0
- package/dist/commands/agent.js +283 -0
- package/dist/commands/agent.js.map +1 -0
- package/dist/commands/backup.d.ts +5 -0
- package/dist/commands/backup.d.ts.map +1 -0
- package/dist/commands/backup.js +14 -0
- package/dist/commands/backup.js.map +1 -0
- package/dist/commands/call.d.ts +7 -0
- package/dist/commands/call.d.ts.map +1 -0
- package/dist/commands/call.js +10 -0
- package/dist/commands/call.js.map +1 -0
- package/dist/commands/doctor.d.ts +31 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +142 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/export.d.ts +21 -0
- package/dist/commands/export.d.ts.map +1 -0
- package/dist/commands/export.js +68 -0
- package/dist/commands/export.js.map +1 -0
- package/dist/commands/import.d.ts +15 -0
- package/dist/commands/import.d.ts.map +1 -0
- package/dist/commands/import.js +265 -0
- package/dist/commands/import.js.map +1 -0
- package/dist/commands/init.d.ts +14 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +26 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/ledger.d.ts +7 -0
- package/dist/commands/ledger.d.ts.map +1 -0
- package/dist/commands/ledger.js +66 -0
- package/dist/commands/ledger.js.map +1 -0
- package/dist/commands/link.d.ts +6 -0
- package/dist/commands/link.d.ts.map +1 -0
- package/dist/commands/link.js +6 -0
- package/dist/commands/link.js.map +1 -0
- package/dist/commands/promote.d.ts +6 -0
- package/dist/commands/promote.d.ts.map +1 -0
- package/dist/commands/promote.js +6 -0
- package/dist/commands/promote.js.map +1 -0
- package/dist/commands/purge.d.ts +10 -0
- package/dist/commands/purge.d.ts.map +1 -0
- package/dist/commands/purge.js +26 -0
- package/dist/commands/purge.js.map +1 -0
- package/dist/commands/read.d.ts +6 -0
- package/dist/commands/read.d.ts.map +1 -0
- package/dist/commands/read.js +4 -0
- package/dist/commands/read.js.map +1 -0
- package/dist/commands/recall.d.ts +6 -0
- package/dist/commands/recall.d.ts.map +1 -0
- package/dist/commands/recall.js +4 -0
- package/dist/commands/recall.js.map +1 -0
- package/dist/commands/record.d.ts +6 -0
- package/dist/commands/record.d.ts.map +1 -0
- package/dist/commands/record.js +4 -0
- package/dist/commands/record.js.map +1 -0
- package/dist/commands/search.d.ts +6 -0
- package/dist/commands/search.d.ts.map +1 -0
- package/dist/commands/search.js +4 -0
- package/dist/commands/search.js.map +1 -0
- package/dist/commands/serve.d.ts +34 -0
- package/dist/commands/serve.d.ts.map +1 -0
- package/dist/commands/serve.js +120 -0
- package/dist/commands/serve.js.map +1 -0
- package/dist/commands/server-status.d.ts +20 -0
- package/dist/commands/server-status.d.ts.map +1 -0
- package/dist/commands/server-status.js +21 -0
- package/dist/commands/server-status.js.map +1 -0
- package/dist/commands/server.d.ts +13 -0
- package/dist/commands/server.d.ts.map +1 -0
- package/dist/commands/server.js +89 -0
- package/dist/commands/server.js.map +1 -0
- package/dist/commands/setup.d.ts +32 -0
- package/dist/commands/setup.d.ts.map +1 -0
- package/dist/commands/setup.js +121 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/commands/supersede.d.ts +6 -0
- package/dist/commands/supersede.d.ts.map +1 -0
- package/dist/commands/supersede.js +6 -0
- package/dist/commands/supersede.js.map +1 -0
- package/dist/commands/use.d.ts +28 -0
- package/dist/commands/use.d.ts.map +1 -0
- package/dist/commands/use.js +176 -0
- package/dist/commands/use.js.map +1 -0
- package/dist/config/paths.d.ts +18 -0
- package/dist/config/paths.d.ts.map +1 -0
- package/dist/config/paths.js +104 -0
- package/dist/config/paths.js.map +1 -0
- package/dist/config/project-config.d.ts +10 -0
- package/dist/config/project-config.d.ts.map +1 -0
- package/dist/config/project-config.js +59 -0
- package/dist/config/project-config.js.map +1 -0
- package/dist/context/broker.d.ts +80 -0
- package/dist/context/broker.d.ts.map +1 -0
- package/dist/context/broker.js +437 -0
- package/dist/context/broker.js.map +1 -0
- package/dist/context/delivery.d.ts +66 -0
- package/dist/context/delivery.d.ts.map +1 -0
- package/dist/context/delivery.js +758 -0
- package/dist/context/delivery.js.map +1 -0
- package/dist/context/feedback.d.ts +75 -0
- package/dist/context/feedback.d.ts.map +1 -0
- package/dist/context/feedback.js +997 -0
- package/dist/context/feedback.js.map +1 -0
- package/dist/context/ranking.d.ts +98 -0
- package/dist/context/ranking.d.ts.map +1 -0
- package/dist/context/ranking.js +513 -0
- package/dist/context/ranking.js.map +1 -0
- package/dist/context/recommendations.d.ts +85 -0
- package/dist/context/recommendations.d.ts.map +1 -0
- package/dist/context/recommendations.js +342 -0
- package/dist/context/recommendations.js.map +1 -0
- package/dist/db/adapter.d.ts +25 -0
- package/dist/db/adapter.d.ts.map +1 -0
- package/dist/db/adapter.js +37 -0
- package/dist/db/adapter.js.map +1 -0
- package/dist/db/capabilities.d.ts +13 -0
- package/dist/db/capabilities.d.ts.map +1 -0
- package/dist/db/capabilities.js +25 -0
- package/dist/db/capabilities.js.map +1 -0
- package/dist/db/connection.d.ts +6 -0
- package/dist/db/connection.d.ts.map +1 -0
- package/dist/db/connection.js +42 -0
- package/dist/db/connection.js.map +1 -0
- package/dist/db/migrate.d.ts +9 -0
- package/dist/db/migrate.d.ts.map +1 -0
- package/dist/db/migrate.js +102 -0
- package/dist/db/migrate.js.map +1 -0
- package/dist/db/transaction.d.ts +4 -0
- package/dist/db/transaction.d.ts.map +1 -0
- package/dist/db/transaction.js +39 -0
- package/dist/db/transaction.js.map +1 -0
- package/dist/errors.d.ts +9 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +32 -0
- package/dist/errors.js.map +1 -0
- package/dist/gateway/agent-service.d.ts +69 -0
- package/dist/gateway/agent-service.d.ts.map +1 -0
- package/dist/gateway/agent-service.js +581 -0
- package/dist/gateway/agent-service.js.map +1 -0
- package/dist/gateway/checkpoint-service.d.ts +42 -0
- package/dist/gateway/checkpoint-service.d.ts.map +1 -0
- package/dist/gateway/checkpoint-service.js +295 -0
- package/dist/gateway/checkpoint-service.js.map +1 -0
- package/dist/knowledge/sources.d.ts +72 -0
- package/dist/knowledge/sources.d.ts.map +1 -0
- package/dist/knowledge/sources.js +406 -0
- package/dist/knowledge/sources.js.map +1 -0
- package/dist/ledger/archive.d.ts +50 -0
- package/dist/ledger/archive.d.ts.map +1 -0
- package/dist/ledger/archive.js +1072 -0
- package/dist/ledger/archive.js.map +1 -0
- package/dist/ledger/hash.d.ts +22 -0
- package/dist/ledger/hash.d.ts.map +1 -0
- package/dist/ledger/hash.js +25 -0
- package/dist/ledger/hash.js.map +1 -0
- package/dist/ledger/maintenance.d.ts +78 -0
- package/dist/ledger/maintenance.d.ts.map +1 -0
- package/dist/ledger/maintenance.js +683 -0
- package/dist/ledger/maintenance.js.map +1 -0
- package/dist/ledger/projection.d.ts +37 -0
- package/dist/ledger/projection.d.ts.map +1 -0
- package/dist/ledger/projection.js +307 -0
- package/dist/ledger/projection.js.map +1 -0
- package/dist/ledger/promotion.d.ts +28 -0
- package/dist/ledger/promotion.d.ts.map +1 -0
- package/dist/ledger/promotion.js +308 -0
- package/dist/ledger/promotion.js.map +1 -0
- package/dist/ledger/query.d.ts +55 -0
- package/dist/ledger/query.d.ts.map +1 -0
- package/dist/ledger/query.js +321 -0
- package/dist/ledger/query.js.map +1 -0
- package/dist/ledger/redaction.d.ts +8 -0
- package/dist/ledger/redaction.d.ts.map +1 -0
- package/dist/ledger/redaction.js +40 -0
- package/dist/ledger/redaction.js.map +1 -0
- package/dist/ledger/store.d.ts +46 -0
- package/dist/ledger/store.d.ts.map +1 -0
- package/dist/ledger/store.js +324 -0
- package/dist/ledger/store.js.map +1 -0
- package/dist/ledger/types.d.ts +119 -0
- package/dist/ledger/types.d.ts.map +1 -0
- package/dist/ledger/types.js +22 -0
- package/dist/ledger/types.js.map +1 -0
- package/dist/ledger/validate.d.ts +10 -0
- package/dist/ledger/validate.d.ts.map +1 -0
- package/dist/ledger/validate.js +183 -0
- package/dist/ledger/validate.js.map +1 -0
- package/dist/mcp/server.d.ts +9 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +86 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/memory/audit.d.ts +14 -0
- package/dist/memory/audit.d.ts.map +1 -0
- package/dist/memory/audit.js +16 -0
- package/dist/memory/audit.js.map +1 -0
- package/dist/memory/entries.d.ts +65 -0
- package/dist/memory/entries.d.ts.map +1 -0
- package/dist/memory/entries.js +200 -0
- package/dist/memory/entries.js.map +1 -0
- package/dist/memory/lifecycle.d.ts +29 -0
- package/dist/memory/lifecycle.d.ts.map +1 -0
- package/dist/memory/lifecycle.js +61 -0
- package/dist/memory/lifecycle.js.map +1 -0
- package/dist/memory/retrieval.d.ts +44 -0
- package/dist/memory/retrieval.d.ts.map +1 -0
- package/dist/memory/retrieval.js +174 -0
- package/dist/memory/retrieval.js.map +1 -0
- package/dist/memory/scoped-memory.d.ts +41 -0
- package/dist/memory/scoped-memory.d.ts.map +1 -0
- package/dist/memory/scoped-memory.js +65 -0
- package/dist/memory/scoped-memory.js.map +1 -0
- package/dist/memory/secrets.d.ts +6 -0
- package/dist/memory/secrets.d.ts.map +1 -0
- package/dist/memory/secrets.js +17 -0
- package/dist/memory/secrets.js.map +1 -0
- package/dist/memory/workspaces.d.ts +17 -0
- package/dist/memory/workspaces.d.ts.map +1 -0
- package/dist/memory/workspaces.js +92 -0
- package/dist/memory/workspaces.js.map +1 -0
- package/dist/repository/binding.d.ts +19 -0
- package/dist/repository/binding.d.ts.map +1 -0
- package/dist/repository/binding.js +78 -0
- package/dist/repository/binding.js.map +1 -0
- package/dist/repository/detect-root.d.ts +12 -0
- package/dist/repository/detect-root.d.ts.map +1 -0
- package/dist/repository/detect-root.js +57 -0
- package/dist/repository/detect-root.js.map +1 -0
- package/dist/repository/git-origin.d.ts +2 -0
- package/dist/repository/git-origin.d.ts.map +1 -0
- package/dist/repository/git-origin.js +15 -0
- package/dist/repository/git-origin.js.map +1 -0
- package/dist/repository/identity.d.ts +19 -0
- package/dist/repository/identity.d.ts.map +1 -0
- package/dist/repository/identity.js +23 -0
- package/dist/repository/identity.js.map +1 -0
- package/dist/repository/remote-url.d.ts +4 -0
- package/dist/repository/remote-url.d.ts.map +1 -0
- package/dist/repository/remote-url.js +36 -0
- package/dist/repository/remote-url.js.map +1 -0
- package/dist/security/sanitize.d.ts +4 -0
- package/dist/security/sanitize.d.ts.map +1 -0
- package/dist/security/sanitize.js +174 -0
- package/dist/security/sanitize.js.map +1 -0
- package/dist/serialization/envelope.d.ts +20 -0
- package/dist/serialization/envelope.d.ts.map +1 -0
- package/dist/serialization/envelope.js +11 -0
- package/dist/serialization/envelope.js.map +1 -0
- package/dist/serialization/validate.d.ts +31 -0
- package/dist/serialization/validate.d.ts.map +1 -0
- package/dist/serialization/validate.js +168 -0
- package/dist/serialization/validate.js.map +1 -0
- package/dist/server/agent-application.d.ts +6 -0
- package/dist/server/agent-application.d.ts.map +1 -0
- package/dist/server/agent-application.js +60 -0
- package/dist/server/agent-application.js.map +1 -0
- package/dist/server/app.d.ts +5 -0
- package/dist/server/app.d.ts.map +1 -0
- package/dist/server/app.js +87 -0
- package/dist/server/app.js.map +1 -0
- package/dist/server/auth.d.ts +2 -0
- package/dist/server/auth.d.ts.map +1 -0
- package/dist/server/auth.js +22 -0
- package/dist/server/auth.js.map +1 -0
- package/dist/server/http.d.ts +69 -0
- package/dist/server/http.d.ts.map +1 -0
- package/dist/server/http.js +349 -0
- package/dist/server/http.js.map +1 -0
- package/dist/server/idempotency.d.ts +12 -0
- package/dist/server/idempotency.d.ts.map +1 -0
- package/dist/server/idempotency.js +210 -0
- package/dist/server/idempotency.js.map +1 -0
- package/dist/server/instance-lock.d.ts +17 -0
- package/dist/server/instance-lock.d.ts.map +1 -0
- package/dist/server/instance-lock.js +118 -0
- package/dist/server/instance-lock.js.map +1 -0
- package/dist/server/router.d.ts +29 -0
- package/dist/server/router.d.ts.map +1 -0
- package/dist/server/router.js +112 -0
- package/dist/server/router.js.map +1 -0
- package/dist/server/routes/agent-events.d.ts +5 -0
- package/dist/server/routes/agent-events.d.ts.map +1 -0
- package/dist/server/routes/agent-events.js +46 -0
- package/dist/server/routes/agent-events.js.map +1 -0
- package/dist/server/routes/agent-intake.d.ts +5 -0
- package/dist/server/routes/agent-intake.d.ts.map +1 -0
- package/dist/server/routes/agent-intake.js +42 -0
- package/dist/server/routes/agent-intake.js.map +1 -0
- package/dist/server/routes/agent-promotions.d.ts +5 -0
- package/dist/server/routes/agent-promotions.d.ts.map +1 -0
- package/dist/server/routes/agent-promotions.js +52 -0
- package/dist/server/routes/agent-promotions.js.map +1 -0
- package/dist/server/routes/agent-runs.d.ts +25 -0
- package/dist/server/routes/agent-runs.d.ts.map +1 -0
- package/dist/server/routes/agent-runs.js +151 -0
- package/dist/server/routes/agent-runs.js.map +1 -0
- package/dist/server/routes/task5-support.d.ts +11 -0
- package/dist/server/routes/task5-support.d.ts.map +1 -0
- package/dist/server/routes/task5-support.js +22 -0
- package/dist/server/routes/task5-support.js.map +1 -0
- package/dist/server/routes/task5.d.ts +5 -0
- package/dist/server/routes/task5.d.ts.map +1 -0
- package/dist/server/routes/task5.js +72 -0
- package/dist/server/routes/task5.js.map +1 -0
- package/dist/server/runtime-descriptor.d.ts +24 -0
- package/dist/server/runtime-descriptor.d.ts.map +1 -0
- package/dist/server/runtime-descriptor.js +156 -0
- package/dist/server/runtime-descriptor.js.map +1 -0
- package/dist/server/write-queue.d.ts +20 -0
- package/dist/server/write-queue.d.ts.map +1 -0
- package/dist/server/write-queue.js +83 -0
- package/dist/server/write-queue.js.map +1 -0
- package/dist/setup/managed-text.d.ts +6 -0
- package/dist/setup/managed-text.d.ts.map +1 -0
- package/dist/setup/managed-text.js +42 -0
- package/dist/setup/managed-text.js.map +1 -0
- package/dist/setup/opencode-config.d.ts +3 -0
- package/dist/setup/opencode-config.d.ts.map +1 -0
- package/dist/setup/opencode-config.js +26 -0
- package/dist/setup/opencode-config.js.map +1 -0
- package/dist/setup/render.d.ts +8 -0
- package/dist/setup/render.d.ts.map +1 -0
- package/dist/setup/render.js +45 -0
- package/dist/setup/render.js.map +1 -0
- package/dist/web/server.d.ts +16 -0
- package/dist/web/server.d.ts.map +1 -0
- package/dist/web/server.js +424 -0
- package/dist/web/server.js.map +1 -0
- package/dist/web/ui.d.ts +2 -0
- package/dist/web/ui.d.ts.map +1 -0
- package/dist/web/ui.js +271 -0
- package/dist/web/ui.js.map +1 -0
- package/migrations/001_initial.sql +89 -0
- package/migrations/002_fts.sql +66 -0
- package/migrations/003_akinator.sql +30 -0
- package/migrations/004_agent_gateway.sql +259 -0
- package/package.json +45 -0
- package/templates/AGENTS.md +29 -0
|
@@ -0,0 +1,997 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { withImmediateTransaction } from '../db/transaction.js';
|
|
3
|
+
import { KiokukoError } from '../errors.js';
|
|
4
|
+
import { sanitizeJson } from '../security/sanitize.js';
|
|
5
|
+
import { canonicalContentHash } from '../serialization/validate.js';
|
|
6
|
+
export const MAX_FEEDBACK_COMMENT_BYTES = 4 * 1024;
|
|
7
|
+
export const MAX_FEEDBACK_IDENTIFIER_LENGTH = 256;
|
|
8
|
+
export const CONTEXT_FEEDBACK_VERDICTS = ['helpful', 'irrelevant', 'stale', 'conflicting'];
|
|
9
|
+
export const RUN_FEEDBACK_RECOMMENDATION_VERDICTS = ['accepted', 'dismissed', 'resolved'];
|
|
10
|
+
const CONTEXT_INPUT_FIELDS = new Set([
|
|
11
|
+
'workspace', 'feedbackId', 'deliveryId', 'entryId', 'runId', 'verdict',
|
|
12
|
+
'comment', 'actor', 'idempotencyKey', 'createdAt',
|
|
13
|
+
]);
|
|
14
|
+
const CONTEXT_LIST_FIELDS = new Set(['workspace', 'runId', 'deliveryId', 'entryId', 'limit']);
|
|
15
|
+
const RUN_INPUT_FIELDS = new Set([
|
|
16
|
+
'workspace', 'feedbackId', 'runId', 'outcome', 'recommendationCode', 'recommendationVerdict',
|
|
17
|
+
'rating', 'comment', 'actor', 'idempotencyKey', 'createdAt',
|
|
18
|
+
]);
|
|
19
|
+
const RUN_LIST_FIELDS = new Set(['workspace', 'runId', 'limit']);
|
|
20
|
+
const VALIDATION_MESSAGE = 'Feedback input is invalid';
|
|
21
|
+
const NOT_FOUND_MESSAGE = 'Feedback target was not found';
|
|
22
|
+
const CONFLICT_MESSAGE = 'Feedback conflicts with existing record';
|
|
23
|
+
const INTEGRITY_MESSAGE = 'Stored feedback is invalid';
|
|
24
|
+
const DATABASE_MESSAGE = 'Feedback database operation failed';
|
|
25
|
+
function fail(code, message) {
|
|
26
|
+
throw new KiokukoError(code, message);
|
|
27
|
+
}
|
|
28
|
+
function validation() {
|
|
29
|
+
return fail('VALIDATION_ERROR', VALIDATION_MESSAGE);
|
|
30
|
+
}
|
|
31
|
+
function notFound() {
|
|
32
|
+
return fail('NOT_FOUND', NOT_FOUND_MESSAGE);
|
|
33
|
+
}
|
|
34
|
+
function conflict() {
|
|
35
|
+
return fail('CONFLICT', CONFLICT_MESSAGE);
|
|
36
|
+
}
|
|
37
|
+
function integrity() {
|
|
38
|
+
return fail('INTEGRITY_ERROR', INTEGRITY_MESSAGE);
|
|
39
|
+
}
|
|
40
|
+
function databaseFailure() {
|
|
41
|
+
return fail('DATABASE_ERROR', DATABASE_MESSAGE);
|
|
42
|
+
}
|
|
43
|
+
function isPlainObject(value) {
|
|
44
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
45
|
+
return false;
|
|
46
|
+
const prototype = Object.getPrototypeOf(value);
|
|
47
|
+
return prototype === Object.prototype || prototype === null;
|
|
48
|
+
}
|
|
49
|
+
function isKiokukoError(error) {
|
|
50
|
+
return error instanceof KiokukoError;
|
|
51
|
+
}
|
|
52
|
+
function normalizeDatabaseError(error) {
|
|
53
|
+
if (isKiokukoError(error))
|
|
54
|
+
throw error;
|
|
55
|
+
if (error instanceof Error && /unique|constraint/i.test(error.message))
|
|
56
|
+
conflict();
|
|
57
|
+
databaseFailure();
|
|
58
|
+
}
|
|
59
|
+
function requiredString(value) {
|
|
60
|
+
if (typeof value !== 'string' || value.length === 0 || value.length > MAX_FEEDBACK_IDENTIFIER_LENGTH)
|
|
61
|
+
validation();
|
|
62
|
+
return value;
|
|
63
|
+
}
|
|
64
|
+
function optionalText(value, maximum) {
|
|
65
|
+
if (value === undefined || value === null)
|
|
66
|
+
return null;
|
|
67
|
+
if (typeof value !== 'string' || value.length === 0 || value.length > maximum)
|
|
68
|
+
validation();
|
|
69
|
+
return value;
|
|
70
|
+
}
|
|
71
|
+
function normalizeTimestamp(value) {
|
|
72
|
+
if (typeof value !== 'string' || value.length === 0)
|
|
73
|
+
validation();
|
|
74
|
+
const date = new Date(value);
|
|
75
|
+
if (Number.isNaN(date.getTime()))
|
|
76
|
+
validation();
|
|
77
|
+
return date.toISOString();
|
|
78
|
+
}
|
|
79
|
+
function normalizeComment(value, workspace) {
|
|
80
|
+
if (value === undefined || value === null)
|
|
81
|
+
return null;
|
|
82
|
+
if (typeof value !== 'string')
|
|
83
|
+
validation();
|
|
84
|
+
if (value.trim().length === 0)
|
|
85
|
+
return null;
|
|
86
|
+
let sanitized;
|
|
87
|
+
try {
|
|
88
|
+
sanitized = sanitizeJson(value, { workspace }).value;
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
validation();
|
|
92
|
+
}
|
|
93
|
+
if (typeof sanitized !== 'string')
|
|
94
|
+
validation();
|
|
95
|
+
if (sanitized.trim().length === 0)
|
|
96
|
+
return null;
|
|
97
|
+
if (Buffer.byteLength(sanitized, 'utf8') > MAX_FEEDBACK_COMMENT_BYTES)
|
|
98
|
+
validation();
|
|
99
|
+
return sanitized;
|
|
100
|
+
}
|
|
101
|
+
function hashIdempotencyKey(value) {
|
|
102
|
+
return createHash('sha256').update(value, 'utf8').digest('hex');
|
|
103
|
+
}
|
|
104
|
+
function contextBodyHash(input) {
|
|
105
|
+
return canonicalContentHash({
|
|
106
|
+
feedbackId: input.feedbackId,
|
|
107
|
+
workspace: input.workspace,
|
|
108
|
+
deliveryId: input.deliveryId,
|
|
109
|
+
entryId: input.entryId,
|
|
110
|
+
runId: input.runId,
|
|
111
|
+
verdict: input.verdict,
|
|
112
|
+
comment: input.comment,
|
|
113
|
+
actor: input.actor,
|
|
114
|
+
createdAt: input.createdAt,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
function runBodyHash(input) {
|
|
118
|
+
return canonicalContentHash({
|
|
119
|
+
feedbackId: input.feedbackId,
|
|
120
|
+
workspace: input.workspace,
|
|
121
|
+
runId: input.runId,
|
|
122
|
+
outcome: input.outcome,
|
|
123
|
+
recommendationCode: input.recommendationCode,
|
|
124
|
+
recommendationVerdict: input.recommendationVerdict,
|
|
125
|
+
rating: input.rating,
|
|
126
|
+
comment: input.comment,
|
|
127
|
+
actor: input.actor,
|
|
128
|
+
createdAt: input.createdAt,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
function validateContextFeedbackInput(value) {
|
|
132
|
+
try {
|
|
133
|
+
if (!isPlainObject(value))
|
|
134
|
+
validation();
|
|
135
|
+
for (const field of Object.keys(value)) {
|
|
136
|
+
if (!CONTEXT_INPUT_FIELDS.has(field))
|
|
137
|
+
validation();
|
|
138
|
+
}
|
|
139
|
+
const workspace = requiredString(value.workspace);
|
|
140
|
+
const feedbackId = requiredString(value.feedbackId);
|
|
141
|
+
const deliveryId = requiredString(value.deliveryId);
|
|
142
|
+
const entryId = requiredString(value.entryId);
|
|
143
|
+
const runId = requiredString(value.runId);
|
|
144
|
+
if (typeof value.verdict !== 'string' || !CONTEXT_FEEDBACK_VERDICTS.includes(value.verdict))
|
|
145
|
+
validation();
|
|
146
|
+
const verdict = value.verdict;
|
|
147
|
+
const actor = requiredString(value.actor);
|
|
148
|
+
const idempotencyKey = requiredString(value.idempotencyKey);
|
|
149
|
+
const createdAt = normalizeTimestamp(value.createdAt);
|
|
150
|
+
const comment = normalizeComment(value.comment, workspace);
|
|
151
|
+
return {
|
|
152
|
+
feedbackId,
|
|
153
|
+
workspace,
|
|
154
|
+
deliveryId,
|
|
155
|
+
entryId,
|
|
156
|
+
runId,
|
|
157
|
+
verdict,
|
|
158
|
+
comment,
|
|
159
|
+
actor,
|
|
160
|
+
idempotencyKeyHash: hashIdempotencyKey(idempotencyKey),
|
|
161
|
+
createdAt,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
if (isKiokukoError(error) && error.code === 'VALIDATION_ERROR')
|
|
166
|
+
validation();
|
|
167
|
+
validation();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function validateContextFeedbackListInput(value) {
|
|
171
|
+
try {
|
|
172
|
+
if (!isPlainObject(value))
|
|
173
|
+
validation();
|
|
174
|
+
for (const field of Object.keys(value)) {
|
|
175
|
+
if (!CONTEXT_LIST_FIELDS.has(field))
|
|
176
|
+
validation();
|
|
177
|
+
}
|
|
178
|
+
const workspace = requiredString(value.workspace);
|
|
179
|
+
const runId = value.runId === undefined ? undefined : requiredString(value.runId);
|
|
180
|
+
const deliveryId = value.deliveryId === undefined ? undefined : requiredString(value.deliveryId);
|
|
181
|
+
const entryId = value.entryId === undefined ? undefined : requiredString(value.entryId);
|
|
182
|
+
const limit = value.limit === undefined ? 100 : value.limit;
|
|
183
|
+
if (typeof limit !== 'number' || !Number.isInteger(limit) || limit < 1 || limit > 100)
|
|
184
|
+
validation();
|
|
185
|
+
return {
|
|
186
|
+
workspace,
|
|
187
|
+
...(runId === undefined ? {} : { runId }),
|
|
188
|
+
...(deliveryId === undefined ? {} : { deliveryId }),
|
|
189
|
+
...(entryId === undefined ? {} : { entryId }),
|
|
190
|
+
limit,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
if (isKiokukoError(error) && error.code === 'VALIDATION_ERROR')
|
|
195
|
+
validation();
|
|
196
|
+
validation();
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
function validateRunFeedbackListInput(value) {
|
|
200
|
+
try {
|
|
201
|
+
if (!isPlainObject(value))
|
|
202
|
+
validation();
|
|
203
|
+
for (const field of Object.keys(value)) {
|
|
204
|
+
if (!RUN_LIST_FIELDS.has(field))
|
|
205
|
+
validation();
|
|
206
|
+
}
|
|
207
|
+
const workspace = requiredString(value.workspace);
|
|
208
|
+
const runId = value.runId === undefined ? undefined : requiredString(value.runId);
|
|
209
|
+
const limit = value.limit === undefined ? 100 : value.limit;
|
|
210
|
+
if (typeof limit !== 'number' || !Number.isInteger(limit) || limit < 1 || limit > 100)
|
|
211
|
+
validation();
|
|
212
|
+
return { workspace, ...(runId === undefined ? {} : { runId }), limit };
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
if (isKiokukoError(error) && error.code === 'VALIDATION_ERROR')
|
|
216
|
+
validation();
|
|
217
|
+
validation();
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
function validateRunFeedbackInput(value) {
|
|
221
|
+
try {
|
|
222
|
+
if (!isPlainObject(value))
|
|
223
|
+
validation();
|
|
224
|
+
for (const field of Object.keys(value)) {
|
|
225
|
+
if (!RUN_INPUT_FIELDS.has(field))
|
|
226
|
+
validation();
|
|
227
|
+
}
|
|
228
|
+
const workspace = requiredString(value.workspace);
|
|
229
|
+
const feedbackId = requiredString(value.feedbackId);
|
|
230
|
+
const runId = requiredString(value.runId);
|
|
231
|
+
const outcome = optionalText(value.outcome, MAX_FEEDBACK_COMMENT_BYTES);
|
|
232
|
+
const recommendationCode = optionalText(value.recommendationCode, MAX_FEEDBACK_IDENTIFIER_LENGTH);
|
|
233
|
+
const rawRecommendationVerdict = value.recommendationVerdict;
|
|
234
|
+
const recommendationVerdict = rawRecommendationVerdict === undefined || rawRecommendationVerdict === null
|
|
235
|
+
? null
|
|
236
|
+
: typeof rawRecommendationVerdict === 'string' && RUN_FEEDBACK_RECOMMENDATION_VERDICTS.includes(rawRecommendationVerdict)
|
|
237
|
+
? rawRecommendationVerdict
|
|
238
|
+
: validation();
|
|
239
|
+
if ((recommendationCode === null) !== (recommendationVerdict === null))
|
|
240
|
+
validation();
|
|
241
|
+
const rawRating = value.rating;
|
|
242
|
+
const rating = rawRating === undefined || rawRating === null
|
|
243
|
+
? null
|
|
244
|
+
: typeof rawRating === 'number' && Number.isInteger(rawRating) && rawRating >= 1 && rawRating <= 5
|
|
245
|
+
? rawRating
|
|
246
|
+
: validation();
|
|
247
|
+
if (outcome === null && recommendationCode === null && rating === null)
|
|
248
|
+
validation();
|
|
249
|
+
const actor = requiredString(value.actor);
|
|
250
|
+
const idempotencyKey = requiredString(value.idempotencyKey);
|
|
251
|
+
const createdAt = normalizeTimestamp(value.createdAt);
|
|
252
|
+
const comment = normalizeComment(value.comment, workspace);
|
|
253
|
+
return {
|
|
254
|
+
feedbackId,
|
|
255
|
+
workspace,
|
|
256
|
+
runId,
|
|
257
|
+
outcome,
|
|
258
|
+
recommendationCode,
|
|
259
|
+
recommendationVerdict,
|
|
260
|
+
rating,
|
|
261
|
+
comment,
|
|
262
|
+
actor,
|
|
263
|
+
idempotencyKeyHash: hashIdempotencyKey(idempotencyKey),
|
|
264
|
+
createdAt,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
if (isKiokukoError(error) && error.code === 'VALIDATION_ERROR')
|
|
269
|
+
validation();
|
|
270
|
+
validation();
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
function assertContextTarget(database, input) {
|
|
274
|
+
const target = database.prepare(`
|
|
275
|
+
SELECT 1 AS present
|
|
276
|
+
FROM context_deliveries AS cd
|
|
277
|
+
JOIN ledger_runs AS lr ON lr.run_id = cd.run_id
|
|
278
|
+
JOIN context_delivery_entries AS cde
|
|
279
|
+
ON cde.delivery_id = cd.delivery_id AND cde.entry_id = ?
|
|
280
|
+
JOIN entries AS e ON e.id = cde.entry_id
|
|
281
|
+
WHERE cd.delivery_id = ?
|
|
282
|
+
AND cd.run_id = ?
|
|
283
|
+
AND lr.workspace = ?
|
|
284
|
+
AND e.workspace = ?
|
|
285
|
+
`).get(input.entryId, input.deliveryId, input.runId, input.workspace, input.workspace);
|
|
286
|
+
if (!target)
|
|
287
|
+
notFound();
|
|
288
|
+
}
|
|
289
|
+
function selectContextByKey(database, input) {
|
|
290
|
+
return database.prepare(`
|
|
291
|
+
SELECT
|
|
292
|
+
cf.feedback_id, cf.delivery_id, cf.entry_id, cf.run_id, cf.verdict, cf.comment,
|
|
293
|
+
cf.actor, cf.idempotency_key, cf.created_at,
|
|
294
|
+
lr.workspace AS run_workspace,
|
|
295
|
+
cd.run_id AS delivery_run_id,
|
|
296
|
+
cd.delivery_id AS joined_delivery_id,
|
|
297
|
+
cde.delivery_id AS linked_delivery_id,
|
|
298
|
+
cde.entry_id AS linked_entry_id,
|
|
299
|
+
e.workspace AS entry_workspace
|
|
300
|
+
FROM context_feedback AS cf
|
|
301
|
+
LEFT JOIN ledger_runs AS lr ON lr.run_id = cf.run_id
|
|
302
|
+
LEFT JOIN context_deliveries AS cd ON cd.delivery_id = cf.delivery_id
|
|
303
|
+
LEFT JOIN context_delivery_entries AS cde
|
|
304
|
+
ON cde.delivery_id = cf.delivery_id AND cde.entry_id = cf.entry_id
|
|
305
|
+
LEFT JOIN entries AS e ON e.id = cf.entry_id
|
|
306
|
+
WHERE cf.run_id = ? AND cf.actor = ? AND cf.idempotency_key = ?
|
|
307
|
+
`).get(input.runId, input.actor, input.idempotencyKeyHash);
|
|
308
|
+
}
|
|
309
|
+
function hasContextFeedbackId(database, feedbackId) {
|
|
310
|
+
return Boolean(database.prepare('SELECT 1 AS present FROM context_feedback WHERE feedback_id = ?').get(feedbackId));
|
|
311
|
+
}
|
|
312
|
+
function storedString(value) {
|
|
313
|
+
if (typeof value !== 'string' || value.length === 0)
|
|
314
|
+
integrity();
|
|
315
|
+
return value;
|
|
316
|
+
}
|
|
317
|
+
function storedHash(value) {
|
|
318
|
+
if (typeof value !== 'string' || !/^[0-9a-f]{64}$/.test(value))
|
|
319
|
+
integrity();
|
|
320
|
+
return value;
|
|
321
|
+
}
|
|
322
|
+
function storedTimestamp(value) {
|
|
323
|
+
if (typeof value !== 'string')
|
|
324
|
+
integrity();
|
|
325
|
+
const date = new Date(value);
|
|
326
|
+
if (Number.isNaN(date.getTime()))
|
|
327
|
+
integrity();
|
|
328
|
+
const normalized = date.toISOString();
|
|
329
|
+
if (normalized !== value)
|
|
330
|
+
integrity();
|
|
331
|
+
return value;
|
|
332
|
+
}
|
|
333
|
+
function rowToContextFeedback(row, workspace) {
|
|
334
|
+
const feedbackId = storedString(row.feedback_id);
|
|
335
|
+
const deliveryId = storedString(row.delivery_id);
|
|
336
|
+
const entryId = storedString(row.entry_id);
|
|
337
|
+
const runId = storedString(row.run_id);
|
|
338
|
+
const actor = storedString(row.actor);
|
|
339
|
+
const runWorkspace = storedString(row.run_workspace);
|
|
340
|
+
if (runWorkspace !== workspace)
|
|
341
|
+
integrity();
|
|
342
|
+
if (storedString(row.delivery_run_id) !== runId)
|
|
343
|
+
integrity();
|
|
344
|
+
if (storedString(row.joined_delivery_id) !== deliveryId)
|
|
345
|
+
integrity();
|
|
346
|
+
if (storedString(row.linked_delivery_id) !== deliveryId)
|
|
347
|
+
integrity();
|
|
348
|
+
if (storedString(row.linked_entry_id) !== entryId)
|
|
349
|
+
integrity();
|
|
350
|
+
if (storedString(row.entry_workspace) !== workspace)
|
|
351
|
+
integrity();
|
|
352
|
+
if (typeof row.verdict !== 'string' || !CONTEXT_FEEDBACK_VERDICTS.includes(row.verdict))
|
|
353
|
+
integrity();
|
|
354
|
+
const verdict = row.verdict;
|
|
355
|
+
let comment;
|
|
356
|
+
if (row.comment === null) {
|
|
357
|
+
comment = null;
|
|
358
|
+
}
|
|
359
|
+
else if (typeof row.comment === 'string') {
|
|
360
|
+
if (row.comment.length === 0 || Buffer.byteLength(row.comment, 'utf8') > MAX_FEEDBACK_COMMENT_BYTES)
|
|
361
|
+
integrity();
|
|
362
|
+
let sanitized;
|
|
363
|
+
try {
|
|
364
|
+
sanitized = normalizeComment(row.comment, workspace);
|
|
365
|
+
}
|
|
366
|
+
catch {
|
|
367
|
+
integrity();
|
|
368
|
+
}
|
|
369
|
+
if (sanitized !== row.comment)
|
|
370
|
+
integrity();
|
|
371
|
+
comment = row.comment;
|
|
372
|
+
}
|
|
373
|
+
else {
|
|
374
|
+
integrity();
|
|
375
|
+
}
|
|
376
|
+
return {
|
|
377
|
+
feedbackId,
|
|
378
|
+
workspace,
|
|
379
|
+
deliveryId,
|
|
380
|
+
entryId,
|
|
381
|
+
runId,
|
|
382
|
+
verdict,
|
|
383
|
+
comment,
|
|
384
|
+
actor,
|
|
385
|
+
idempotencyKeyHash: storedHash(row.idempotency_key),
|
|
386
|
+
createdAt: storedTimestamp(row.created_at),
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
function writeContextFeedback(database, input) {
|
|
390
|
+
assertContextTarget(database, input);
|
|
391
|
+
const existingByKey = selectContextByKey(database, input);
|
|
392
|
+
if (existingByKey) {
|
|
393
|
+
const existing = rowToContextFeedback(existingByKey, input.workspace);
|
|
394
|
+
if (contextBodyHash(existing) === contextBodyHash(input))
|
|
395
|
+
return existing;
|
|
396
|
+
conflict();
|
|
397
|
+
}
|
|
398
|
+
if (hasContextFeedbackId(database, input.feedbackId))
|
|
399
|
+
conflict();
|
|
400
|
+
database.prepare(`
|
|
401
|
+
INSERT INTO context_feedback (
|
|
402
|
+
feedback_id, delivery_id, entry_id, run_id, verdict, comment, actor, idempotency_key, created_at
|
|
403
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
404
|
+
`).run(input.feedbackId, input.deliveryId, input.entryId, input.runId, input.verdict, input.comment, input.actor, input.idempotencyKeyHash, input.createdAt);
|
|
405
|
+
const row = selectContextByKey(database, input);
|
|
406
|
+
if (!row)
|
|
407
|
+
integrity();
|
|
408
|
+
return rowToContextFeedback(row, input.workspace);
|
|
409
|
+
}
|
|
410
|
+
export function recordContextFeedbackInTransaction(database, input) {
|
|
411
|
+
const validated = validateContextFeedbackInput(input);
|
|
412
|
+
try {
|
|
413
|
+
return writeContextFeedback(database, validated);
|
|
414
|
+
}
|
|
415
|
+
catch (error) {
|
|
416
|
+
normalizeDatabaseError(error);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
export function recordContextFeedback(database, input) {
|
|
420
|
+
const validated = validateContextFeedbackInput(input);
|
|
421
|
+
try {
|
|
422
|
+
return withImmediateTransaction(database, () => writeContextFeedback(database, validated));
|
|
423
|
+
}
|
|
424
|
+
catch (error) {
|
|
425
|
+
normalizeDatabaseError(error);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
function runOptionalStoredText(value, maximum) {
|
|
429
|
+
if (value === null)
|
|
430
|
+
return null;
|
|
431
|
+
if (typeof value !== 'string' || value.length === 0 || value.length > maximum)
|
|
432
|
+
integrity();
|
|
433
|
+
return value;
|
|
434
|
+
}
|
|
435
|
+
function runStoredComment(value, workspace) {
|
|
436
|
+
if (value === null)
|
|
437
|
+
return null;
|
|
438
|
+
if (typeof value !== 'string' || value.length === 0 || Buffer.byteLength(value, 'utf8') > MAX_FEEDBACK_COMMENT_BYTES)
|
|
439
|
+
integrity();
|
|
440
|
+
let sanitized;
|
|
441
|
+
try {
|
|
442
|
+
sanitized = normalizeComment(value, workspace);
|
|
443
|
+
}
|
|
444
|
+
catch {
|
|
445
|
+
integrity();
|
|
446
|
+
}
|
|
447
|
+
if (sanitized !== value)
|
|
448
|
+
integrity();
|
|
449
|
+
return value;
|
|
450
|
+
}
|
|
451
|
+
function runBodyHashFromInput(input) {
|
|
452
|
+
return runBodyHash(input);
|
|
453
|
+
}
|
|
454
|
+
function assertRunTarget(database, input) {
|
|
455
|
+
const target = database.prepare('SELECT 1 AS present FROM ledger_runs WHERE run_id = ? AND workspace = ?').get(input.runId, input.workspace);
|
|
456
|
+
if (!target)
|
|
457
|
+
notFound();
|
|
458
|
+
}
|
|
459
|
+
function selectRunByKey(database, input) {
|
|
460
|
+
return database.prepare(`
|
|
461
|
+
SELECT
|
|
462
|
+
rf.feedback_id, rf.run_id, rf.outcome, rf.recommendation_code, rf.recommendation_verdict,
|
|
463
|
+
rf.rating, rf.comment, rf.actor, rf.idempotency_key, rf.created_at,
|
|
464
|
+
lr.workspace AS run_workspace
|
|
465
|
+
FROM run_feedback AS rf
|
|
466
|
+
LEFT JOIN ledger_runs AS lr ON lr.run_id = rf.run_id
|
|
467
|
+
WHERE rf.run_id = ? AND rf.actor = ? AND rf.idempotency_key = ?
|
|
468
|
+
`).get(input.runId, input.actor, input.idempotencyKeyHash);
|
|
469
|
+
}
|
|
470
|
+
function hasRunFeedbackId(database, feedbackId) {
|
|
471
|
+
return Boolean(database.prepare('SELECT 1 AS present FROM run_feedback WHERE feedback_id = ?').get(feedbackId));
|
|
472
|
+
}
|
|
473
|
+
function rowToRunFeedback(row, workspace) {
|
|
474
|
+
const feedbackId = storedString(row.feedback_id);
|
|
475
|
+
const runId = storedString(row.run_id);
|
|
476
|
+
const runWorkspace = storedString(row.run_workspace);
|
|
477
|
+
if (runWorkspace !== workspace)
|
|
478
|
+
integrity();
|
|
479
|
+
const outcome = runOptionalStoredText(row.outcome, MAX_FEEDBACK_COMMENT_BYTES);
|
|
480
|
+
const recommendationCode = runOptionalStoredText(row.recommendation_code, MAX_FEEDBACK_IDENTIFIER_LENGTH);
|
|
481
|
+
let recommendationVerdict;
|
|
482
|
+
if (row.recommendation_verdict === null) {
|
|
483
|
+
recommendationVerdict = null;
|
|
484
|
+
}
|
|
485
|
+
else if (typeof row.recommendation_verdict === 'string' && RUN_FEEDBACK_RECOMMENDATION_VERDICTS.includes(row.recommendation_verdict)) {
|
|
486
|
+
recommendationVerdict = row.recommendation_verdict;
|
|
487
|
+
}
|
|
488
|
+
else {
|
|
489
|
+
integrity();
|
|
490
|
+
}
|
|
491
|
+
if ((recommendationCode === null) !== (recommendationVerdict === null))
|
|
492
|
+
integrity();
|
|
493
|
+
const rating = row.rating === null
|
|
494
|
+
? null
|
|
495
|
+
: typeof row.rating === 'number' && Number.isInteger(row.rating) && row.rating >= 1 && row.rating <= 5
|
|
496
|
+
? row.rating
|
|
497
|
+
: integrity();
|
|
498
|
+
if (outcome === null && recommendationCode === null && rating === null)
|
|
499
|
+
integrity();
|
|
500
|
+
const actor = storedString(row.actor);
|
|
501
|
+
const comment = runStoredComment(row.comment, workspace);
|
|
502
|
+
return {
|
|
503
|
+
feedbackId,
|
|
504
|
+
workspace,
|
|
505
|
+
runId,
|
|
506
|
+
outcome,
|
|
507
|
+
recommendationCode,
|
|
508
|
+
recommendationVerdict,
|
|
509
|
+
rating,
|
|
510
|
+
comment,
|
|
511
|
+
actor,
|
|
512
|
+
idempotencyKeyHash: storedHash(row.idempotency_key),
|
|
513
|
+
createdAt: storedTimestamp(row.created_at),
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
function writeRunFeedback(database, input) {
|
|
517
|
+
assertRunTarget(database, input);
|
|
518
|
+
const existingByKey = selectRunByKey(database, input);
|
|
519
|
+
if (existingByKey) {
|
|
520
|
+
const existing = rowToRunFeedback(existingByKey, input.workspace);
|
|
521
|
+
if (runBodyHash(existing) === runBodyHashFromInput(input))
|
|
522
|
+
return existing;
|
|
523
|
+
conflict();
|
|
524
|
+
}
|
|
525
|
+
if (hasRunFeedbackId(database, input.feedbackId))
|
|
526
|
+
conflict();
|
|
527
|
+
database.prepare(`
|
|
528
|
+
INSERT INTO run_feedback (
|
|
529
|
+
feedback_id, run_id, outcome, recommendation_code, recommendation_verdict,
|
|
530
|
+
rating, comment, actor, idempotency_key, created_at
|
|
531
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
532
|
+
`).run(input.feedbackId, input.runId, input.outcome, input.recommendationCode, input.recommendationVerdict, input.rating, input.comment, input.actor, input.idempotencyKeyHash, input.createdAt);
|
|
533
|
+
const row = selectRunByKey(database, input);
|
|
534
|
+
if (!row)
|
|
535
|
+
integrity();
|
|
536
|
+
return rowToRunFeedback(row, input.workspace);
|
|
537
|
+
}
|
|
538
|
+
export function recordRunFeedbackInTransaction(database, input) {
|
|
539
|
+
const validated = validateRunFeedbackInput(input);
|
|
540
|
+
try {
|
|
541
|
+
return writeRunFeedback(database, validated);
|
|
542
|
+
}
|
|
543
|
+
catch (error) {
|
|
544
|
+
normalizeDatabaseError(error);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
export function recordRunFeedback(database, input) {
|
|
548
|
+
const validated = validateRunFeedbackInput(input);
|
|
549
|
+
try {
|
|
550
|
+
return withImmediateTransaction(database, () => writeRunFeedback(database, validated));
|
|
551
|
+
}
|
|
552
|
+
catch (error) {
|
|
553
|
+
normalizeDatabaseError(error);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
export function listRunFeedback(database, input) {
|
|
557
|
+
const validated = validateRunFeedbackListInput(input);
|
|
558
|
+
try {
|
|
559
|
+
const conditions = ['(lr.workspace = ? OR lr.workspace IS NULL)'];
|
|
560
|
+
const parameters = [validated.workspace];
|
|
561
|
+
if (validated.runId !== undefined) {
|
|
562
|
+
conditions.push('rf.run_id = ?');
|
|
563
|
+
parameters.push(validated.runId);
|
|
564
|
+
}
|
|
565
|
+
const rows = database.prepare(`
|
|
566
|
+
SELECT
|
|
567
|
+
rf.feedback_id, rf.run_id, rf.outcome, rf.recommendation_code, rf.recommendation_verdict,
|
|
568
|
+
rf.rating, rf.comment, rf.actor, rf.idempotency_key, rf.created_at,
|
|
569
|
+
lr.workspace AS run_workspace
|
|
570
|
+
FROM run_feedback AS rf
|
|
571
|
+
LEFT JOIN ledger_runs AS lr ON lr.run_id = rf.run_id
|
|
572
|
+
WHERE ${conditions.join(' AND ')}
|
|
573
|
+
ORDER BY rf.created_at ASC, rf.feedback_id ASC
|
|
574
|
+
LIMIT ?
|
|
575
|
+
`).all(...parameters, validated.limit + 1);
|
|
576
|
+
const records = rows.map((row) => rowToRunFeedback(row, validated.workspace));
|
|
577
|
+
return { records: records.slice(0, validated.limit), truncated: records.length > validated.limit };
|
|
578
|
+
}
|
|
579
|
+
catch (error) {
|
|
580
|
+
normalizeDatabaseError(error);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
export function listContextFeedback(database, input) {
|
|
584
|
+
const validated = validateContextFeedbackListInput(input);
|
|
585
|
+
try {
|
|
586
|
+
const conditions = ['(lr.workspace = ? OR lr.workspace IS NULL)'];
|
|
587
|
+
const parameters = [validated.workspace];
|
|
588
|
+
if (validated.runId !== undefined) {
|
|
589
|
+
conditions.push('cf.run_id = ?');
|
|
590
|
+
parameters.push(validated.runId);
|
|
591
|
+
}
|
|
592
|
+
if (validated.deliveryId !== undefined) {
|
|
593
|
+
conditions.push('cf.delivery_id = ?');
|
|
594
|
+
parameters.push(validated.deliveryId);
|
|
595
|
+
}
|
|
596
|
+
if (validated.entryId !== undefined) {
|
|
597
|
+
conditions.push('cf.entry_id = ?');
|
|
598
|
+
parameters.push(validated.entryId);
|
|
599
|
+
}
|
|
600
|
+
const rows = database.prepare(`
|
|
601
|
+
SELECT
|
|
602
|
+
cf.feedback_id, cf.delivery_id, cf.entry_id, cf.run_id, cf.verdict, cf.comment,
|
|
603
|
+
cf.actor, cf.idempotency_key, cf.created_at,
|
|
604
|
+
lr.workspace AS run_workspace,
|
|
605
|
+
cd.run_id AS delivery_run_id,
|
|
606
|
+
cd.delivery_id AS joined_delivery_id,
|
|
607
|
+
cde.delivery_id AS linked_delivery_id,
|
|
608
|
+
cde.entry_id AS linked_entry_id,
|
|
609
|
+
e.workspace AS entry_workspace
|
|
610
|
+
FROM context_feedback AS cf
|
|
611
|
+
LEFT JOIN ledger_runs AS lr ON lr.run_id = cf.run_id
|
|
612
|
+
LEFT JOIN context_deliveries AS cd ON cd.delivery_id = cf.delivery_id
|
|
613
|
+
LEFT JOIN context_delivery_entries AS cde
|
|
614
|
+
ON cde.delivery_id = cf.delivery_id AND cde.entry_id = cf.entry_id
|
|
615
|
+
LEFT JOIN entries AS e ON e.id = cf.entry_id
|
|
616
|
+
WHERE ${conditions.join(' AND ')}
|
|
617
|
+
ORDER BY cf.created_at ASC, cf.feedback_id ASC
|
|
618
|
+
LIMIT ?
|
|
619
|
+
`).all(...parameters, validated.limit + 1);
|
|
620
|
+
const records = rows.map((row) => rowToContextFeedback(row, validated.workspace));
|
|
621
|
+
return { records: records.slice(0, validated.limit), truncated: records.length > validated.limit };
|
|
622
|
+
}
|
|
623
|
+
catch (error) {
|
|
624
|
+
normalizeDatabaseError(error);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
export const INTAKE_FEEDBACK_VERDICTS = ['helpful', 'unnecessary', 'corrected'];
|
|
628
|
+
const INTAKE_FEEDBACK_FIELDS = new Set([
|
|
629
|
+
'workspace', 'feedbackId', 'runId', 'sessionId', 'questionId', 'profileField',
|
|
630
|
+
'verdict', 'comment', 'actor', 'idempotencyKey', 'createdAt',
|
|
631
|
+
]);
|
|
632
|
+
const INTAKE_PROFILE_FIELDS = new Set(['taskType', 'target', 'expected', 'constraints']);
|
|
633
|
+
function initialInputFields(value) {
|
|
634
|
+
try {
|
|
635
|
+
if (!isPlainObject(value))
|
|
636
|
+
validation();
|
|
637
|
+
const keys = Reflect.ownKeys(value);
|
|
638
|
+
if (keys.length !== INTAKE_FEEDBACK_FIELDS.size || keys.some((key) => typeof key !== 'string' || !INTAKE_FEEDBACK_FIELDS.has(key)))
|
|
639
|
+
validation();
|
|
640
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
641
|
+
const result = {};
|
|
642
|
+
for (const field of INTAKE_FEEDBACK_FIELDS) {
|
|
643
|
+
const descriptor = descriptors[field];
|
|
644
|
+
if (!descriptor || !('value' in descriptor) || descriptor.enumerable !== true)
|
|
645
|
+
validation();
|
|
646
|
+
result[field] = descriptor.value;
|
|
647
|
+
}
|
|
648
|
+
structuredClone(value);
|
|
649
|
+
return result;
|
|
650
|
+
}
|
|
651
|
+
catch {
|
|
652
|
+
validation();
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
function initialInputIdentifier(value) {
|
|
656
|
+
if (typeof value !== 'string' || value.length === 0 || Buffer.byteLength(value, 'utf8') > MAX_FEEDBACK_IDENTIFIER_LENGTH || /\p{Cc}/u.test(value))
|
|
657
|
+
validation();
|
|
658
|
+
return value;
|
|
659
|
+
}
|
|
660
|
+
function initialInputTimestamp(value) {
|
|
661
|
+
if (typeof value !== 'string' || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(value))
|
|
662
|
+
validation();
|
|
663
|
+
const date = new Date(value);
|
|
664
|
+
if (Number.isNaN(date.getTime()) || date.toISOString() !== value)
|
|
665
|
+
validation();
|
|
666
|
+
return value;
|
|
667
|
+
}
|
|
668
|
+
function initialInputComment(value, workspace) {
|
|
669
|
+
if (value === null)
|
|
670
|
+
return null;
|
|
671
|
+
if (typeof value !== 'string' || value.length === 0)
|
|
672
|
+
validation();
|
|
673
|
+
let sanitized;
|
|
674
|
+
try {
|
|
675
|
+
sanitized = sanitizeJson(value, { workspace }).value;
|
|
676
|
+
}
|
|
677
|
+
catch {
|
|
678
|
+
validation();
|
|
679
|
+
}
|
|
680
|
+
if (typeof sanitized !== 'string' || sanitized.length === 0 || Buffer.byteLength(sanitized, 'utf8') > MAX_FEEDBACK_COMMENT_BYTES)
|
|
681
|
+
validation();
|
|
682
|
+
return sanitized;
|
|
683
|
+
}
|
|
684
|
+
function validateInitialIntakeFeedbackInput(value) {
|
|
685
|
+
const fields = initialInputFields(value);
|
|
686
|
+
const workspace = initialInputIdentifier(fields.workspace);
|
|
687
|
+
const feedbackId = initialInputIdentifier(fields.feedbackId);
|
|
688
|
+
const runId = initialInputIdentifier(fields.runId);
|
|
689
|
+
const sessionId = initialInputIdentifier(fields.sessionId);
|
|
690
|
+
const questionId = fields.questionId === null
|
|
691
|
+
? null
|
|
692
|
+
: typeof fields.questionId === 'string' && INTAKE_PROFILE_FIELDS.has(fields.questionId)
|
|
693
|
+
? fields.questionId
|
|
694
|
+
: validation();
|
|
695
|
+
const profileField = fields.profileField === null
|
|
696
|
+
? null
|
|
697
|
+
: typeof fields.profileField === 'string' && INTAKE_PROFILE_FIELDS.has(fields.profileField)
|
|
698
|
+
? fields.profileField
|
|
699
|
+
: validation();
|
|
700
|
+
if ((questionId === null) === (profileField === null))
|
|
701
|
+
validation();
|
|
702
|
+
if (typeof fields.verdict !== 'string' || !INTAKE_FEEDBACK_VERDICTS.includes(fields.verdict))
|
|
703
|
+
validation();
|
|
704
|
+
const actor = initialInputIdentifier(fields.actor);
|
|
705
|
+
const idempotencyKey = initialInputIdentifier(fields.idempotencyKey);
|
|
706
|
+
return {
|
|
707
|
+
workspace,
|
|
708
|
+
feedbackId,
|
|
709
|
+
runId,
|
|
710
|
+
sessionId,
|
|
711
|
+
questionId,
|
|
712
|
+
profileField,
|
|
713
|
+
verdict: fields.verdict,
|
|
714
|
+
comment: initialInputComment(fields.comment, workspace),
|
|
715
|
+
actor,
|
|
716
|
+
idempotencyKeyHash: hashIdempotencyKey(idempotencyKey),
|
|
717
|
+
createdAt: initialInputTimestamp(fields.createdAt),
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
function initialIntakeTarget(database, input) {
|
|
721
|
+
const linked = database.prepare(`
|
|
722
|
+
SELECT ri.run_id, ri.session_id
|
|
723
|
+
FROM run_intakes AS ri
|
|
724
|
+
JOIN ledger_runs AS lr ON lr.run_id = ri.run_id
|
|
725
|
+
JOIN akinator_sessions AS s ON s.id = ri.session_id
|
|
726
|
+
WHERE ri.run_id = ? AND ri.session_id = ?
|
|
727
|
+
AND lr.workspace = ? AND s.workspace = ?
|
|
728
|
+
`).get(input.runId, input.sessionId, input.workspace, input.workspace);
|
|
729
|
+
if (!linked)
|
|
730
|
+
notFound();
|
|
731
|
+
if (input.questionId !== null) {
|
|
732
|
+
const answer = database.prepare(`
|
|
733
|
+
SELECT 1 AS present
|
|
734
|
+
FROM akinator_answers
|
|
735
|
+
WHERE session_id = ? AND question_id = ?
|
|
736
|
+
`).get(input.sessionId, input.questionId);
|
|
737
|
+
if (!answer)
|
|
738
|
+
notFound();
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
function initialIntakeRecord(input) {
|
|
742
|
+
return {
|
|
743
|
+
feedbackId: input.feedbackId,
|
|
744
|
+
workspace: input.workspace,
|
|
745
|
+
runId: input.runId,
|
|
746
|
+
sessionId: input.sessionId,
|
|
747
|
+
questionId: input.questionId,
|
|
748
|
+
profileField: input.profileField,
|
|
749
|
+
verdict: input.verdict,
|
|
750
|
+
comment: input.comment,
|
|
751
|
+
actor: input.actor,
|
|
752
|
+
idempotencyKeyHash: input.idempotencyKeyHash,
|
|
753
|
+
createdAt: input.createdAt,
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
function initialIntakeBodyHash(input) {
|
|
757
|
+
return canonicalContentHash({
|
|
758
|
+
feedbackId: input.feedbackId,
|
|
759
|
+
workspace: input.workspace,
|
|
760
|
+
runId: input.runId,
|
|
761
|
+
sessionId: input.sessionId,
|
|
762
|
+
questionId: input.questionId,
|
|
763
|
+
profileField: input.profileField,
|
|
764
|
+
verdict: input.verdict,
|
|
765
|
+
comment: input.comment,
|
|
766
|
+
actor: input.actor,
|
|
767
|
+
createdAt: input.createdAt,
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
function selectInitialIntakeByKey(database, input) {
|
|
771
|
+
return database.prepare(`
|
|
772
|
+
SELECT
|
|
773
|
+
ifb.feedback_id, ifb.run_id, ifb.session_id, ifb.question_id, ifb.profile_field,
|
|
774
|
+
ifb.verdict, ifb.comment, ifb.actor, ifb.idempotency_key, ifb.created_at,
|
|
775
|
+
lr.workspace AS run_workspace,
|
|
776
|
+
s.workspace AS session_workspace,
|
|
777
|
+
ri.run_id AS linked_run_id,
|
|
778
|
+
ri.session_id AS linked_session_id,
|
|
779
|
+
aa.session_id AS question_answer_session_id
|
|
780
|
+
FROM intake_feedback AS ifb
|
|
781
|
+
LEFT JOIN ledger_runs AS lr ON lr.run_id = ifb.run_id
|
|
782
|
+
LEFT JOIN akinator_sessions AS s ON s.id = ifb.session_id
|
|
783
|
+
LEFT JOIN run_intakes AS ri ON ri.run_id = ifb.run_id AND ri.session_id = ifb.session_id
|
|
784
|
+
LEFT JOIN akinator_answers AS aa ON aa.session_id = ifb.session_id AND aa.question_id = ifb.question_id
|
|
785
|
+
WHERE ifb.run_id = ? AND ifb.actor = ? AND ifb.idempotency_key = ?
|
|
786
|
+
`).get(input.runId, input.actor, input.idempotencyKeyHash);
|
|
787
|
+
}
|
|
788
|
+
function hasInitialIntakeFeedbackId(database, feedbackId) {
|
|
789
|
+
return Boolean(database.prepare('SELECT 1 AS present FROM intake_feedback WHERE feedback_id = ?').get(feedbackId));
|
|
790
|
+
}
|
|
791
|
+
function initialStoredIdentifier(value) {
|
|
792
|
+
if (typeof value !== 'string' || value.length === 0 || Buffer.byteLength(value, 'utf8') > MAX_FEEDBACK_IDENTIFIER_LENGTH || /\p{Cc}/u.test(value))
|
|
793
|
+
integrity();
|
|
794
|
+
return value;
|
|
795
|
+
}
|
|
796
|
+
function initialStoredHash(value) {
|
|
797
|
+
if (typeof value !== 'string' || !/^[0-9a-f]{64}$/.test(value))
|
|
798
|
+
integrity();
|
|
799
|
+
return value;
|
|
800
|
+
}
|
|
801
|
+
function initialStoredTimestamp(value) {
|
|
802
|
+
if (typeof value !== 'string' || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(value))
|
|
803
|
+
integrity();
|
|
804
|
+
const date = new Date(value);
|
|
805
|
+
if (Number.isNaN(date.getTime()) || date.toISOString() !== value)
|
|
806
|
+
integrity();
|
|
807
|
+
return value;
|
|
808
|
+
}
|
|
809
|
+
function initialStoredComment(value, workspace) {
|
|
810
|
+
if (value === null)
|
|
811
|
+
return null;
|
|
812
|
+
if (typeof value !== 'string' || value.length === 0 || Buffer.byteLength(value, 'utf8') > MAX_FEEDBACK_COMMENT_BYTES)
|
|
813
|
+
integrity();
|
|
814
|
+
let sanitized;
|
|
815
|
+
try {
|
|
816
|
+
sanitized = normalizeComment(value, workspace);
|
|
817
|
+
}
|
|
818
|
+
catch {
|
|
819
|
+
integrity();
|
|
820
|
+
}
|
|
821
|
+
if (sanitized !== value)
|
|
822
|
+
integrity();
|
|
823
|
+
return value;
|
|
824
|
+
}
|
|
825
|
+
function rowToInitialIntakeFeedback(row, workspace) {
|
|
826
|
+
const feedbackId = initialStoredIdentifier(row.feedback_id);
|
|
827
|
+
const runId = initialStoredIdentifier(row.run_id);
|
|
828
|
+
const sessionId = initialStoredIdentifier(row.session_id);
|
|
829
|
+
const actor = initialStoredIdentifier(row.actor);
|
|
830
|
+
const runWorkspace = initialStoredIdentifier(row.run_workspace);
|
|
831
|
+
const sessionWorkspace = initialStoredIdentifier(row.session_workspace);
|
|
832
|
+
const linkedRunId = initialStoredIdentifier(row.linked_run_id);
|
|
833
|
+
const linkedSessionId = initialStoredIdentifier(row.linked_session_id);
|
|
834
|
+
if (runWorkspace !== workspace || sessionWorkspace !== workspace || linkedRunId !== runId || linkedSessionId !== sessionId)
|
|
835
|
+
integrity();
|
|
836
|
+
const questionId = row.question_id === null
|
|
837
|
+
? null
|
|
838
|
+
: typeof row.question_id === 'string' && INTAKE_PROFILE_FIELDS.has(row.question_id)
|
|
839
|
+
? row.question_id
|
|
840
|
+
: integrity();
|
|
841
|
+
const profileField = row.profile_field === null
|
|
842
|
+
? null
|
|
843
|
+
: typeof row.profile_field === 'string' && INTAKE_PROFILE_FIELDS.has(row.profile_field)
|
|
844
|
+
? row.profile_field
|
|
845
|
+
: integrity();
|
|
846
|
+
if ((questionId === null) === (profileField === null))
|
|
847
|
+
integrity();
|
|
848
|
+
if (questionId !== null && initialStoredIdentifier(row.question_answer_session_id) !== sessionId)
|
|
849
|
+
integrity();
|
|
850
|
+
if (typeof row.verdict !== 'string' || !INTAKE_FEEDBACK_VERDICTS.includes(row.verdict))
|
|
851
|
+
integrity();
|
|
852
|
+
return {
|
|
853
|
+
feedbackId,
|
|
854
|
+
workspace,
|
|
855
|
+
runId,
|
|
856
|
+
sessionId,
|
|
857
|
+
questionId,
|
|
858
|
+
profileField,
|
|
859
|
+
verdict: row.verdict,
|
|
860
|
+
comment: initialStoredComment(row.comment, workspace),
|
|
861
|
+
actor,
|
|
862
|
+
idempotencyKeyHash: initialStoredHash(row.idempotency_key),
|
|
863
|
+
createdAt: initialStoredTimestamp(row.created_at),
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
function recordInitialIntakeFeedback(database, input) {
|
|
867
|
+
initialIntakeTarget(database, input);
|
|
868
|
+
const existingByKey = selectInitialIntakeByKey(database, input);
|
|
869
|
+
if (existingByKey) {
|
|
870
|
+
const existing = rowToInitialIntakeFeedback(existingByKey, input.workspace);
|
|
871
|
+
if (initialIntakeBodyHash(existing) === initialIntakeBodyHash(initialIntakeRecord(input)))
|
|
872
|
+
return existing;
|
|
873
|
+
conflict();
|
|
874
|
+
}
|
|
875
|
+
if (hasInitialIntakeFeedbackId(database, input.feedbackId))
|
|
876
|
+
conflict();
|
|
877
|
+
database.prepare(`
|
|
878
|
+
INSERT INTO intake_feedback (
|
|
879
|
+
feedback_id, run_id, session_id, question_id, profile_field, verdict, comment, actor, idempotency_key, created_at
|
|
880
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
881
|
+
`).run(input.feedbackId, input.runId, input.sessionId, input.questionId, input.profileField, input.verdict, input.comment, input.actor, input.idempotencyKeyHash, input.createdAt);
|
|
882
|
+
const row = selectInitialIntakeByKey(database, input);
|
|
883
|
+
if (!row)
|
|
884
|
+
integrity();
|
|
885
|
+
return rowToInitialIntakeFeedback(row, input.workspace);
|
|
886
|
+
}
|
|
887
|
+
export function recordIntakeFeedback(database, input) {
|
|
888
|
+
const validated = validateInitialIntakeFeedbackInput(input);
|
|
889
|
+
try {
|
|
890
|
+
return withImmediateTransaction(database, () => recordInitialIntakeFeedback(database, validated));
|
|
891
|
+
}
|
|
892
|
+
catch (error) {
|
|
893
|
+
normalizeDatabaseError(error);
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
export function recordIntakeFeedbackInTransaction(database, input) {
|
|
897
|
+
const validated = validateInitialIntakeFeedbackInput(input);
|
|
898
|
+
try {
|
|
899
|
+
return recordInitialIntakeFeedback(database, validated);
|
|
900
|
+
}
|
|
901
|
+
catch (error) {
|
|
902
|
+
normalizeDatabaseError(error);
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
const INTAKE_FEEDBACK_LIST_FIELDS = new Set(['workspace', 'runId', 'sessionId', 'questionId', 'profileField', 'limit']);
|
|
906
|
+
function initialListInputFields(value) {
|
|
907
|
+
try {
|
|
908
|
+
if (!isPlainObject(value))
|
|
909
|
+
validation();
|
|
910
|
+
const keys = Reflect.ownKeys(value);
|
|
911
|
+
if (keys.some((key) => typeof key !== 'string' || !INTAKE_FEEDBACK_LIST_FIELDS.has(key)))
|
|
912
|
+
validation();
|
|
913
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
914
|
+
const result = {};
|
|
915
|
+
for (const key of keys) {
|
|
916
|
+
if (typeof key !== 'string')
|
|
917
|
+
validation();
|
|
918
|
+
const descriptor = descriptors[key];
|
|
919
|
+
if (!descriptor || !('value' in descriptor) || descriptor.enumerable !== true)
|
|
920
|
+
validation();
|
|
921
|
+
result[key] = descriptor.value;
|
|
922
|
+
}
|
|
923
|
+
structuredClone(value);
|
|
924
|
+
return result;
|
|
925
|
+
}
|
|
926
|
+
catch {
|
|
927
|
+
validation();
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
function validateInitialIntakeFeedbackListInput(value) {
|
|
931
|
+
const fields = initialListInputFields(value);
|
|
932
|
+
const workspace = initialInputIdentifier(fields.workspace);
|
|
933
|
+
const runId = fields.runId === undefined ? undefined : initialInputIdentifier(fields.runId);
|
|
934
|
+
const sessionId = fields.sessionId === undefined ? undefined : initialInputIdentifier(fields.sessionId);
|
|
935
|
+
const questionId = fields.questionId === undefined ? undefined : initialInputIdentifier(fields.questionId);
|
|
936
|
+
const profileField = fields.profileField === undefined ? undefined : initialInputIdentifier(fields.profileField);
|
|
937
|
+
if (questionId !== undefined && !INTAKE_PROFILE_FIELDS.has(questionId))
|
|
938
|
+
validation();
|
|
939
|
+
if (profileField !== undefined && !INTAKE_PROFILE_FIELDS.has(profileField))
|
|
940
|
+
validation();
|
|
941
|
+
if (questionId !== undefined && profileField !== undefined)
|
|
942
|
+
validation();
|
|
943
|
+
const limit = fields.limit === undefined ? 100 : fields.limit;
|
|
944
|
+
if (typeof limit !== 'number' || !Number.isInteger(limit) || limit < 1 || limit > 100)
|
|
945
|
+
validation();
|
|
946
|
+
return { workspace, ...(runId === undefined ? {} : { runId }), ...(sessionId === undefined ? {} : { sessionId }), ...(questionId === undefined ? {} : { questionId }), ...(profileField === undefined ? {} : { profileField }), limit };
|
|
947
|
+
}
|
|
948
|
+
function selectInitialIntakeRows(database, input) {
|
|
949
|
+
const conditions = ['(lr.workspace = ? OR lr.workspace IS NULL)'];
|
|
950
|
+
const parameters = [input.workspace];
|
|
951
|
+
if (input.runId !== undefined) {
|
|
952
|
+
conditions.push('ifb.run_id = ?');
|
|
953
|
+
parameters.push(input.runId);
|
|
954
|
+
}
|
|
955
|
+
if (input.sessionId !== undefined) {
|
|
956
|
+
conditions.push('ifb.session_id = ?');
|
|
957
|
+
parameters.push(input.sessionId);
|
|
958
|
+
}
|
|
959
|
+
if (input.questionId !== undefined) {
|
|
960
|
+
conditions.push('ifb.question_id = ?');
|
|
961
|
+
parameters.push(input.questionId);
|
|
962
|
+
}
|
|
963
|
+
if (input.profileField !== undefined) {
|
|
964
|
+
conditions.push('ifb.profile_field = ?');
|
|
965
|
+
parameters.push(input.profileField);
|
|
966
|
+
}
|
|
967
|
+
return database.prepare(`
|
|
968
|
+
SELECT
|
|
969
|
+
ifb.feedback_id, ifb.run_id, ifb.session_id, ifb.question_id, ifb.profile_field,
|
|
970
|
+
ifb.verdict, ifb.comment, ifb.actor, ifb.idempotency_key, ifb.created_at,
|
|
971
|
+
lr.workspace AS run_workspace,
|
|
972
|
+
s.workspace AS session_workspace,
|
|
973
|
+
ri.run_id AS linked_run_id,
|
|
974
|
+
ri.session_id AS linked_session_id,
|
|
975
|
+
aa.session_id AS question_answer_session_id
|
|
976
|
+
FROM intake_feedback AS ifb
|
|
977
|
+
LEFT JOIN ledger_runs AS lr ON lr.run_id = ifb.run_id
|
|
978
|
+
LEFT JOIN akinator_sessions AS s ON s.id = ifb.session_id
|
|
979
|
+
LEFT JOIN run_intakes AS ri ON ri.run_id = ifb.run_id AND ri.session_id = ifb.session_id
|
|
980
|
+
LEFT JOIN akinator_answers AS aa ON aa.session_id = ifb.session_id AND aa.question_id = ifb.question_id
|
|
981
|
+
WHERE ${conditions.join(' AND ')}
|
|
982
|
+
ORDER BY ifb.created_at ASC, ifb.feedback_id ASC
|
|
983
|
+
LIMIT ?
|
|
984
|
+
`).all(...parameters, input.limit + 1);
|
|
985
|
+
}
|
|
986
|
+
export function listIntakeFeedback(database, input) {
|
|
987
|
+
const validated = validateInitialIntakeFeedbackListInput(input);
|
|
988
|
+
try {
|
|
989
|
+
const rows = selectInitialIntakeRows(database, validated);
|
|
990
|
+
const records = rows.map((row) => rowToInitialIntakeFeedback(row, validated.workspace));
|
|
991
|
+
return { records: records.slice(0, validated.limit), truncated: records.length > validated.limit };
|
|
992
|
+
}
|
|
993
|
+
catch (error) {
|
|
994
|
+
normalizeDatabaseError(error);
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
//# sourceMappingURL=feedback.js.map
|