@askdkc/kiokuko 0.1.16 → 0.1.19
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/README.ja.md +91 -112
- package/README.ko.md +90 -102
- package/README.md +103 -260
- package/README.zh-CN.md +88 -102
- package/dist/agent-file/atomic-write.d.ts +90 -3
- package/dist/agent-file/atomic-write.d.ts.map +1 -1
- package/dist/agent-file/atomic-write.js +990 -14
- package/dist/agent-file/atomic-write.js.map +1 -1
- package/dist/agent-file/managed-block.d.ts +12 -1
- package/dist/agent-file/managed-block.d.ts.map +1 -1
- package/dist/agent-file/managed-block.js +49 -4
- package/dist/agent-file/managed-block.js.map +1 -1
- package/dist/agent-file/render.d.ts +1 -1
- package/dist/agent-file/render.d.ts.map +1 -1
- package/dist/agent-file/render.js +10 -7
- package/dist/agent-file/render.js.map +1 -1
- package/dist/akinator/agent-task.d.ts +14 -24
- package/dist/akinator/agent-task.d.ts.map +1 -1
- package/dist/akinator/agent-task.js +460 -103
- package/dist/akinator/agent-task.js.map +1 -1
- package/dist/akinator/capabilities.d.ts +52 -7
- package/dist/akinator/capabilities.d.ts.map +1 -1
- package/dist/akinator/capabilities.js +226 -28
- package/dist/akinator/capabilities.js.map +1 -1
- package/dist/akinator/capability-binding.d.ts +11 -0
- package/dist/akinator/capability-binding.d.ts.map +1 -0
- package/dist/akinator/capability-binding.js +56 -0
- package/dist/akinator/capability-binding.js.map +1 -0
- package/dist/akinator/domain.js +2 -2
- package/dist/akinator/domain.js.map +1 -1
- package/dist/akinator/knowledge-path.d.ts.map +1 -1
- package/dist/akinator/knowledge-path.js +409 -39
- package/dist/akinator/knowledge-path.js.map +1 -1
- package/dist/akinator/orchestrator.d.ts +3 -4
- package/dist/akinator/orchestrator.d.ts.map +1 -1
- package/dist/akinator/orchestrator.js +1 -4
- package/dist/akinator/orchestrator.js.map +1 -1
- package/dist/akinator/service.d.ts +0 -4
- package/dist/akinator/service.d.ts.map +1 -1
- package/dist/akinator/service.js +19 -35
- package/dist/akinator/service.js.map +1 -1
- package/dist/akinator/skill-discovery-attempt.d.ts +20 -0
- package/dist/akinator/skill-discovery-attempt.d.ts.map +1 -0
- package/dist/akinator/skill-discovery-attempt.js +390 -0
- package/dist/akinator/skill-discovery-attempt.js.map +1 -0
- package/dist/akinator/store.d.ts.map +1 -1
- package/dist/akinator/store.js +107 -52
- package/dist/akinator/store.js.map +1 -1
- package/dist/akinator/types.d.ts +0 -12
- package/dist/akinator/types.d.ts.map +1 -1
- package/dist/bin/kiokuko.js +0 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +561 -144
- package/dist/cli.js.map +1 -1
- package/dist/client/server-client.d.ts +5 -3
- package/dist/client/server-client.d.ts.map +1 -1
- package/dist/client/server-client.js +486 -181
- package/dist/client/server-client.js.map +1 -1
- package/dist/commands/agent.d.ts +2 -0
- package/dist/commands/agent.d.ts.map +1 -1
- package/dist/commands/agent.js +861 -86
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/backup.d.ts +2 -1
- package/dist/commands/backup.d.ts.map +1 -1
- package/dist/commands/backup.js +64 -8
- package/dist/commands/backup.js.map +1 -1
- package/dist/commands/doctor.d.ts +5 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +70 -26
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/export.d.ts +0 -4
- package/dist/commands/export.d.ts.map +1 -1
- package/dist/commands/export.js +191 -32
- package/dist/commands/export.js.map +1 -1
- package/dist/commands/import.d.ts +10 -1
- package/dist/commands/import.d.ts.map +1 -1
- package/dist/commands/import.js +592 -113
- package/dist/commands/import.js.map +1 -1
- package/dist/commands/init.d.ts +19 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +569 -22
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/purge.d.ts.map +1 -1
- package/dist/commands/purge.js +3 -0
- package/dist/commands/purge.js.map +1 -1
- package/dist/commands/setup.d.ts +34 -8
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +418 -86
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/skills.d.ts +9 -0
- package/dist/commands/skills.d.ts.map +1 -0
- package/dist/commands/skills.js +317 -0
- package/dist/commands/skills.js.map +1 -0
- package/dist/commands/use.d.ts +10 -2
- package/dist/commands/use.d.ts.map +1 -1
- package/dist/commands/use.js +851 -73
- package/dist/commands/use.js.map +1 -1
- package/dist/config/paths.d.ts +11 -1
- package/dist/config/paths.d.ts.map +1 -1
- package/dist/config/paths.js +64 -29
- package/dist/config/paths.js.map +1 -1
- package/dist/config/project-config.d.ts +1 -0
- package/dist/config/project-config.d.ts.map +1 -1
- package/dist/config/project-config.js +70 -32
- package/dist/config/project-config.js.map +1 -1
- package/dist/context/broker.d.ts +30 -14
- package/dist/context/broker.d.ts.map +1 -1
- package/dist/context/broker.js +628 -150
- package/dist/context/broker.js.map +1 -1
- package/dist/context/delivery.d.ts +8 -22
- package/dist/context/delivery.d.ts.map +1 -1
- package/dist/context/delivery.js +204 -152
- package/dist/context/delivery.js.map +1 -1
- package/dist/context/feedback.d.ts +11 -12
- package/dist/context/feedback.d.ts.map +1 -1
- package/dist/context/feedback.js +250 -268
- package/dist/context/feedback.js.map +1 -1
- package/dist/context/origin.d.ts +9 -0
- package/dist/context/origin.d.ts.map +1 -0
- package/dist/context/origin.js +15 -0
- package/dist/context/origin.js.map +1 -0
- package/dist/context/ranking.d.ts +4 -13
- package/dist/context/ranking.d.ts.map +1 -1
- package/dist/context/ranking.js +21 -5
- package/dist/context/ranking.js.map +1 -1
- package/dist/context/recommendations.d.ts +0 -5
- package/dist/context/recommendations.d.ts.map +1 -1
- package/dist/context/recommendations.js +0 -1
- package/dist/context/recommendations.js.map +1 -1
- package/dist/context/run-state.d.ts +26 -0
- package/dist/context/run-state.d.ts.map +1 -0
- package/dist/context/run-state.js +399 -0
- package/dist/context/run-state.js.map +1 -0
- package/dist/context/scoped-broker.d.ts +19 -6
- package/dist/context/scoped-broker.d.ts.map +1 -1
- package/dist/context/scoped-broker.js +530 -131
- package/dist/context/scoped-broker.js.map +1 -1
- package/dist/context/selection-state.d.ts +19 -0
- package/dist/context/selection-state.d.ts.map +1 -0
- package/dist/context/selection-state.js +250 -0
- package/dist/context/selection-state.js.map +1 -0
- package/dist/db/adapter.d.ts +5 -3
- package/dist/db/adapter.d.ts.map +1 -1
- package/dist/db/adapter.js +6 -3
- package/dist/db/adapter.js.map +1 -1
- package/dist/db/capabilities.d.ts.map +1 -1
- package/dist/db/capabilities.js +34 -16
- package/dist/db/capabilities.js.map +1 -1
- package/dist/db/connection.d.ts +8 -0
- package/dist/db/connection.d.ts.map +1 -1
- package/dist/db/connection.js +55 -26
- package/dist/db/connection.js.map +1 -1
- package/dist/db/migrate.d.ts +28 -7
- package/dist/db/migrate.d.ts.map +1 -1
- package/dist/db/migrate.js +106 -78
- package/dist/db/migrate.js.map +1 -1
- package/dist/db/sqlite-retry.d.ts +11 -0
- package/dist/db/sqlite-retry.d.ts.map +1 -0
- package/dist/db/sqlite-retry.js +66 -0
- package/dist/db/sqlite-retry.js.map +1 -0
- package/dist/db/transaction.d.ts +13 -0
- package/dist/db/transaction.d.ts.map +1 -1
- package/dist/db/transaction.js +53 -24
- package/dist/db/transaction.js.map +1 -1
- package/dist/db/upgrade-backup.d.ts +58 -2
- package/dist/db/upgrade-backup.d.ts.map +1 -1
- package/dist/db/upgrade-backup.js +757 -28
- package/dist/db/upgrade-backup.js.map +1 -1
- package/dist/errors.js +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/gateway/agent-service.d.ts +4 -1
- package/dist/gateway/agent-service.d.ts.map +1 -1
- package/dist/gateway/agent-service.js +71 -58
- package/dist/gateway/agent-service.js.map +1 -1
- package/dist/gateway/checkpoint-service.d.ts +1 -0
- package/dist/gateway/checkpoint-service.d.ts.map +1 -1
- package/dist/gateway/checkpoint-service.js +47 -24
- package/dist/gateway/checkpoint-service.js.map +1 -1
- package/dist/ledger/archive.d.ts +1 -4
- package/dist/ledger/archive.d.ts.map +1 -1
- package/dist/ledger/archive.js +134 -54
- package/dist/ledger/archive.js.map +1 -1
- package/dist/ledger/maintenance.d.ts +0 -11
- package/dist/ledger/maintenance.d.ts.map +1 -1
- package/dist/ledger/maintenance.js +41 -44
- package/dist/ledger/maintenance.js.map +1 -1
- package/dist/ledger/projection.d.ts +0 -1
- package/dist/ledger/projection.d.ts.map +1 -1
- package/dist/ledger/projection.js.map +1 -1
- package/dist/ledger/promotion.d.ts +0 -9
- package/dist/ledger/promotion.d.ts.map +1 -1
- package/dist/ledger/promotion.js.map +1 -1
- package/dist/ledger/query.d.ts +1 -15
- package/dist/ledger/query.d.ts.map +1 -1
- package/dist/ledger/query.js.map +1 -1
- package/dist/ledger/store.d.ts +0 -1
- package/dist/ledger/store.d.ts.map +1 -1
- package/dist/ledger/store.js +11 -8
- package/dist/ledger/store.js.map +1 -1
- package/dist/ledger/types.d.ts +0 -4
- package/dist/ledger/types.d.ts.map +1 -1
- package/dist/ledger/types.js.map +1 -1
- package/dist/ledger/validate.d.ts.map +1 -1
- package/dist/ledger/validate.js +0 -5
- package/dist/ledger/validate.js.map +1 -1
- package/dist/mcp/bounded-stdio-transport.d.ts +23 -0
- package/dist/mcp/bounded-stdio-transport.d.ts.map +1 -0
- package/dist/mcp/bounded-stdio-transport.js +109 -0
- package/dist/mcp/bounded-stdio-transport.js.map +1 -0
- package/dist/mcp/server.d.ts +4 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +111 -75
- package/dist/mcp/server.js.map +1 -1
- package/dist/memory/audit.d.ts +0 -1
- package/dist/memory/audit.d.ts.map +1 -1
- package/dist/memory/audit.js +0 -1
- package/dist/memory/audit.js.map +1 -1
- package/dist/memory/curator.d.ts +45 -1
- package/dist/memory/curator.d.ts.map +1 -1
- package/dist/memory/curator.js +290 -68
- package/dist/memory/curator.js.map +1 -1
- package/dist/memory/entries.d.ts +12 -2
- package/dist/memory/entries.d.ts.map +1 -1
- package/dist/memory/entries.js +198 -114
- package/dist/memory/entries.js.map +1 -1
- package/dist/memory/federated-retrieval.d.ts +78 -0
- package/dist/memory/federated-retrieval.d.ts.map +1 -0
- package/dist/memory/federated-retrieval.js +441 -0
- package/dist/memory/federated-retrieval.js.map +1 -0
- package/dist/memory/hybrid-retrieval.d.ts +5 -11
- package/dist/memory/hybrid-retrieval.d.ts.map +1 -1
- package/dist/memory/hybrid-retrieval.js +78 -58
- package/dist/memory/hybrid-retrieval.js.map +1 -1
- package/dist/memory/lifecycle.d.ts.map +1 -1
- package/dist/memory/lifecycle.js +17 -13
- package/dist/memory/lifecycle.js.map +1 -1
- package/dist/memory/portability.d.ts +10 -0
- package/dist/memory/portability.d.ts.map +1 -0
- package/dist/memory/portability.js +44 -0
- package/dist/memory/portability.js.map +1 -0
- package/dist/memory/rebuild-search.d.ts +5 -0
- package/dist/memory/rebuild-search.d.ts.map +1 -1
- package/dist/memory/rebuild-search.js +101 -76
- package/dist/memory/rebuild-search.js.map +1 -1
- package/dist/memory/retrieval-query.d.ts +0 -5
- package/dist/memory/retrieval-query.d.ts.map +1 -1
- package/dist/memory/retrieval-query.js +0 -5
- package/dist/memory/retrieval-query.js.map +1 -1
- package/dist/memory/retrieval.d.ts +11 -0
- package/dist/memory/retrieval.d.ts.map +1 -1
- package/dist/memory/retrieval.js +34 -120
- package/dist/memory/retrieval.js.map +1 -1
- package/dist/memory/revisions.d.ts +76 -1
- package/dist/memory/revisions.d.ts.map +1 -1
- package/dist/memory/revisions.js +539 -35
- package/dist/memory/revisions.js.map +1 -1
- package/dist/memory/scoped-memory.d.ts +22 -13
- package/dist/memory/scoped-memory.d.ts.map +1 -1
- package/dist/memory/scoped-memory.js +248 -139
- package/dist/memory/scoped-memory.js.map +1 -1
- package/dist/memory/secrets.d.ts +2 -0
- package/dist/memory/secrets.d.ts.map +1 -1
- package/dist/memory/secrets.js +36 -0
- package/dist/memory/secrets.js.map +1 -1
- package/dist/memory/structured-memory.d.ts +8 -1
- package/dist/memory/structured-memory.d.ts.map +1 -1
- package/dist/memory/structured-memory.js +74 -20
- package/dist/memory/structured-memory.js.map +1 -1
- package/dist/memory/workspaces.d.ts +9 -0
- package/dist/memory/workspaces.d.ts.map +1 -1
- package/dist/memory/workspaces.js +67 -0
- package/dist/memory/workspaces.js.map +1 -1
- package/dist/package-version.d.ts +2 -0
- package/dist/package-version.d.ts.map +1 -0
- package/dist/package-version.js +7 -0
- package/dist/package-version.js.map +1 -0
- package/dist/repository/binding.d.ts +5 -0
- package/dist/repository/binding.d.ts.map +1 -1
- package/dist/repository/binding.js +117 -17
- package/dist/repository/binding.js.map +1 -1
- package/dist/repository/framework-version.d.ts +8 -0
- package/dist/repository/framework-version.d.ts.map +1 -0
- package/dist/repository/framework-version.js +29 -0
- package/dist/repository/framework-version.js.map +1 -0
- package/dist/repository/identity-value.d.ts +4 -0
- package/dist/repository/identity-value.d.ts.map +1 -0
- package/dist/repository/identity-value.js +24 -0
- package/dist/repository/identity-value.js.map +1 -0
- package/dist/repository/identity.d.ts.map +1 -1
- package/dist/repository/identity.js +2 -0
- package/dist/repository/identity.js.map +1 -1
- package/dist/repository/project-fingerprint.d.ts +33 -0
- package/dist/repository/project-fingerprint.d.ts.map +1 -0
- package/dist/repository/project-fingerprint.js +429 -0
- package/dist/repository/project-fingerprint.js.map +1 -0
- package/dist/repository/remote-url.d.ts +0 -1
- package/dist/repository/remote-url.d.ts.map +1 -1
- package/dist/repository/remote-url.js +0 -1
- package/dist/repository/remote-url.js.map +1 -1
- package/dist/runtime-version.d.ts +2 -1
- package/dist/runtime-version.d.ts.map +1 -1
- package/dist/runtime-version.js +21 -4
- package/dist/runtime-version.js.map +1 -1
- package/dist/serialization/boundary-json.d.ts +20 -0
- package/dist/serialization/boundary-json.d.ts.map +1 -0
- package/dist/serialization/boundary-json.js +129 -0
- package/dist/serialization/boundary-json.js.map +1 -0
- package/dist/serialization/envelope.js +1 -1
- package/dist/serialization/envelope.js.map +1 -1
- package/dist/serialization/validate.d.ts +7 -0
- package/dist/serialization/validate.d.ts.map +1 -1
- package/dist/serialization/validate.js +53 -13
- package/dist/serialization/validate.js.map +1 -1
- package/dist/server/app.js +1 -1
- package/dist/server/app.js.map +1 -1
- package/dist/server/http.d.ts +4 -3
- package/dist/server/http.d.ts.map +1 -1
- package/dist/server/http.js +90 -50
- package/dist/server/http.js.map +1 -1
- package/dist/server/idempotency.d.ts +0 -6
- package/dist/server/idempotency.d.ts.map +1 -1
- package/dist/server/idempotency.js.map +1 -1
- package/dist/server/router.d.ts.map +1 -1
- package/dist/server/router.js +21 -4
- package/dist/server/router.js.map +1 -1
- package/dist/server/routes/agent-capability-gate.d.ts +40 -0
- package/dist/server/routes/agent-capability-gate.d.ts.map +1 -0
- package/dist/server/routes/agent-capability-gate.js +182 -0
- package/dist/server/routes/agent-capability-gate.js.map +1 -0
- package/dist/server/routes/agent-events.d.ts.map +1 -1
- package/dist/server/routes/agent-events.js +8 -1
- package/dist/server/routes/agent-events.js.map +1 -1
- package/dist/server/routes/agent-intake.d.ts.map +1 -1
- package/dist/server/routes/agent-intake.js +10 -2
- package/dist/server/routes/agent-intake.js.map +1 -1
- package/dist/server/routes/agent-promotions.d.ts.map +1 -1
- package/dist/server/routes/agent-promotions.js +5 -2
- package/dist/server/routes/agent-promotions.js.map +1 -1
- package/dist/server/routes/agent-runs.d.ts.map +1 -1
- package/dist/server/routes/agent-runs.js +18 -4
- package/dist/server/routes/agent-runs.js.map +1 -1
- package/dist/server/routes/request-binding.d.ts +5 -0
- package/dist/server/routes/request-binding.d.ts.map +1 -0
- package/dist/server/routes/request-binding.js +49 -0
- package/dist/server/routes/request-binding.js.map +1 -0
- package/dist/server/routes/task5-support.d.ts +1 -8
- package/dist/server/routes/task5-support.d.ts.map +1 -1
- package/dist/server/routes/task5-support.js +1 -17
- package/dist/server/routes/task5-support.js.map +1 -1
- package/dist/server/routes/task5.d.ts.map +1 -1
- package/dist/server/routes/task5.js +106 -36
- package/dist/server/routes/task5.js.map +1 -1
- package/dist/server/write-queue.js +2 -2
- package/dist/server/write-queue.js.map +1 -1
- package/dist/setup/claude-config.d.ts +2 -1
- package/dist/setup/claude-config.d.ts.map +1 -1
- package/dist/setup/claude-config.js +54 -4
- package/dist/setup/claude-config.js.map +1 -1
- package/dist/setup/client-detection.d.ts +7 -2
- package/dist/setup/client-detection.d.ts.map +1 -1
- package/dist/setup/client-detection.js +27 -9
- package/dist/setup/client-detection.js.map +1 -1
- package/dist/setup/hermes-config.d.ts +2 -1
- package/dist/setup/hermes-config.d.ts.map +1 -1
- package/dist/setup/hermes-config.js +53 -12
- package/dist/setup/hermes-config.js.map +1 -1
- package/dist/setup/legacy-client-cleanup.d.ts +6 -0
- package/dist/setup/legacy-client-cleanup.d.ts.map +1 -0
- package/dist/setup/legacy-client-cleanup.js +152 -0
- package/dist/setup/legacy-client-cleanup.js.map +1 -0
- package/dist/setup/managed-text.d.ts.map +1 -1
- package/dist/setup/managed-text.js +11 -1
- package/dist/setup/managed-text.js.map +1 -1
- package/dist/setup/opencode-config.d.ts +2 -1
- package/dist/setup/opencode-config.d.ts.map +1 -1
- package/dist/setup/opencode-config.js +57 -5
- package/dist/setup/opencode-config.js.map +1 -1
- package/dist/setup/render.d.ts +2 -1
- package/dist/setup/render.d.ts.map +1 -1
- package/dist/setup/render.js +91 -17
- package/dist/setup/render.js.map +1 -1
- package/dist/setup/standard-skills.d.ts +16 -2
- package/dist/setup/standard-skills.d.ts.map +1 -1
- package/dist/setup/standard-skills.js +37 -13
- package/dist/setup/standard-skills.js.map +1 -1
- package/dist/setup/strict-json.d.ts +7 -0
- package/dist/setup/strict-json.d.ts.map +1 -0
- package/dist/setup/strict-json.js +98 -0
- package/dist/setup/strict-json.js.map +1 -0
- package/dist/setup/strict-toml.d.ts +7 -0
- package/dist/setup/strict-toml.d.ts.map +1 -0
- package/dist/setup/strict-toml.js +470 -0
- package/dist/setup/strict-toml.js.map +1 -0
- package/dist/skills/cache.d.ts +15 -0
- package/dist/skills/cache.d.ts.map +1 -0
- package/dist/skills/cache.js +19 -0
- package/dist/skills/cache.js.map +1 -0
- package/dist/skills/candidate-ranking.d.ts +15 -0
- package/dist/skills/candidate-ranking.d.ts.map +1 -0
- package/dist/skills/candidate-ranking.js +52 -0
- package/dist/skills/candidate-ranking.js.map +1 -0
- package/dist/skills/chunking.d.ts +11 -0
- package/dist/skills/chunking.d.ts.map +1 -0
- package/dist/skills/chunking.js +291 -0
- package/dist/skills/chunking.js.map +1 -0
- package/dist/skills/config.d.ts +19 -0
- package/dist/skills/config.d.ts.map +1 -0
- package/dist/skills/config.js +103 -0
- package/dist/skills/config.js.map +1 -0
- package/dist/skills/discovery-service.d.ts +26 -0
- package/dist/skills/discovery-service.d.ts.map +1 -0
- package/dist/skills/discovery-service.js +921 -0
- package/dist/skills/discovery-service.js.map +1 -0
- package/dist/skills/external-transport.d.ts +2 -0
- package/dist/skills/external-transport.d.ts.map +1 -0
- package/dist/skills/external-transport.js +30 -0
- package/dist/skills/external-transport.js.map +1 -0
- package/dist/skills/find.d.ts +16 -0
- package/dist/skills/find.d.ts.map +1 -0
- package/dist/skills/find.js +58 -0
- package/dist/skills/find.js.map +1 -0
- package/dist/skills/gap-detection.d.ts +23 -0
- package/dist/skills/gap-detection.d.ts.map +1 -0
- package/dist/skills/gap-detection.js +62 -0
- package/dist/skills/gap-detection.js.map +1 -0
- package/dist/skills/import-preparation.d.ts +4 -0
- package/dist/skills/import-preparation.d.ts.map +1 -0
- package/dist/skills/import-preparation.js +20 -0
- package/dist/skills/import-preparation.js.map +1 -0
- package/dist/skills/materialization-authority.d.ts +11 -0
- package/dist/skills/materialization-authority.d.ts.map +1 -0
- package/dist/skills/materialization-authority.js +92 -0
- package/dist/skills/materialization-authority.js.map +1 -0
- package/dist/skills/materialization-service.d.ts +22 -0
- package/dist/skills/materialization-service.d.ts.map +1 -0
- package/dist/skills/materialization-service.js +101 -0
- package/dist/skills/materialization-service.js.map +1 -0
- package/dist/skills/official-catalog.d.ts +31 -0
- package/dist/skills/official-catalog.d.ts.map +1 -0
- package/dist/skills/official-catalog.js +110 -0
- package/dist/skills/official-catalog.js.map +1 -0
- package/dist/skills/providers/schema.d.ts +30 -0
- package/dist/skills/providers/schema.d.ts.map +1 -0
- package/dist/skills/providers/schema.js +354 -0
- package/dist/skills/providers/schema.js.map +1 -0
- package/dist/skills/providers/skills-sh-compat.d.ts +17 -0
- package/dist/skills/providers/skills-sh-compat.d.ts.map +1 -0
- package/dist/skills/providers/skills-sh-compat.js +71 -0
- package/dist/skills/providers/skills-sh-compat.js.map +1 -0
- package/dist/skills/providers/skills-sh-v1.d.ts +23 -0
- package/dist/skills/providers/skills-sh-v1.d.ts.map +1 -0
- package/dist/skills/providers/skills-sh-v1.js +134 -0
- package/dist/skills/providers/skills-sh-v1.js.map +1 -0
- package/dist/skills/query-builder.d.ts +18 -0
- package/dist/skills/query-builder.d.ts.map +1 -0
- package/dist/skills/query-builder.js +93 -0
- package/dist/skills/query-builder.js.map +1 -0
- package/dist/skills/source/errors.d.ts +9 -0
- package/dist/skills/source/errors.d.ts.map +1 -0
- package/dist/skills/source/errors.js +15 -0
- package/dist/skills/source/errors.js.map +1 -0
- package/dist/skills/source/frontmatter.d.ts +9 -0
- package/dist/skills/source/frontmatter.d.ts.map +1 -0
- package/dist/skills/source/frontmatter.js +77 -0
- package/dist/skills/source/frontmatter.js.map +1 -0
- package/dist/skills/source/github-fetcher.d.ts +20 -0
- package/dist/skills/source/github-fetcher.d.ts.map +1 -0
- package/dist/skills/source/github-fetcher.js +292 -0
- package/dist/skills/source/github-fetcher.js.map +1 -0
- package/dist/skills/source/snapshot-validator.d.ts +28 -0
- package/dist/skills/source/snapshot-validator.d.ts.map +1 -0
- package/dist/skills/source/snapshot-validator.js +186 -0
- package/dist/skills/source/snapshot-validator.js.map +1 -0
- package/dist/skills/store.d.ts +181 -0
- package/dist/skills/store.d.ts.map +1 -0
- package/dist/skills/store.js +1710 -0
- package/dist/skills/store.js.map +1 -0
- package/dist/skills/types.d.ts +169 -0
- package/dist/skills/types.d.ts.map +1 -0
- package/dist/skills/types.js +2 -0
- package/dist/skills/types.js.map +1 -0
- package/dist/web/i18n.d.ts +56 -8
- package/dist/web/i18n.d.ts.map +1 -1
- package/dist/web/i18n.js +224 -32
- package/dist/web/i18n.js.map +1 -1
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +458 -42
- package/dist/web/server.js.map +1 -1
- package/dist/web/ui.d.ts +4 -0
- package/dist/web/ui.d.ts.map +1 -1
- package/dist/web/ui.js +697 -61
- package/dist/web/ui.js.map +1 -1
- package/migrations/008_federated_memory.sql +46 -0
- package/migrations/009_external_skill_discovery.sql +126 -0
- package/package.json +6 -3
- package/skills/kiokuko-single-purpose-functions/SKILL.md +288 -0
- package/skills/kiokuko-single-purpose-functions/references/kiokuko-patterns.md +344 -0
- package/skills/kiokuko-single-purpose-functions/references/review-checklist.md +97 -0
- package/skills/kiokuko-ui-design-soul/SKILL.md +1186 -28
- package/templates/AGENTS.md +8 -7
- package/dist/client/runtime-discovery.d.ts +0 -15
- package/dist/client/runtime-discovery.d.ts.map +0 -1
- package/dist/client/runtime-discovery.js +0 -45
- package/dist/client/runtime-discovery.js.map +0 -1
- package/dist/commands/call.d.ts +0 -7
- package/dist/commands/call.d.ts.map +0 -1
- package/dist/commands/call.js +0 -10
- package/dist/commands/call.js.map +0 -1
- package/dist/commands/link.d.ts +0 -6
- package/dist/commands/link.d.ts.map +0 -1
- package/dist/commands/link.js +0 -6
- package/dist/commands/link.js.map +0 -1
- package/dist/commands/promote.d.ts +0 -6
- package/dist/commands/promote.d.ts.map +0 -1
- package/dist/commands/promote.js +0 -6
- package/dist/commands/promote.js.map +0 -1
- package/dist/commands/read.d.ts +0 -6
- package/dist/commands/read.d.ts.map +0 -1
- package/dist/commands/read.js +0 -4
- package/dist/commands/read.js.map +0 -1
- package/dist/commands/recall.d.ts +0 -6
- package/dist/commands/recall.d.ts.map +0 -1
- package/dist/commands/recall.js +0 -4
- package/dist/commands/recall.js.map +0 -1
- package/dist/commands/record.d.ts +0 -6
- package/dist/commands/record.d.ts.map +0 -1
- package/dist/commands/record.js +0 -4
- package/dist/commands/record.js.map +0 -1
- package/dist/commands/search.d.ts +0 -6
- package/dist/commands/search.d.ts.map +0 -1
- package/dist/commands/search.js +0 -4
- package/dist/commands/search.js.map +0 -1
- package/dist/commands/supersede.d.ts +0 -6
- package/dist/commands/supersede.d.ts.map +0 -1
- package/dist/commands/supersede.js +0 -6
- package/dist/commands/supersede.js.map +0 -1
- package/dist/knowledge/sources.d.ts +0 -72
- package/dist/knowledge/sources.d.ts.map +0 -1
- package/dist/knowledge/sources.js +0 -388
- package/dist/knowledge/sources.js.map +0 -1
- package/dist/setup/opencode-evidence.d.ts +0 -37
- package/dist/setup/opencode-evidence.d.ts.map +0 -1
- package/dist/setup/opencode-evidence.js +0 -58
- package/dist/setup/opencode-evidence.js.map +0 -1
- package/dist/setup/opencode-loop-guard.d.ts +0 -9
- package/dist/setup/opencode-loop-guard.d.ts.map +0 -1
- package/dist/setup/opencode-loop-guard.js +0 -248
- package/dist/setup/opencode-loop-guard.js.map +0 -1
package/dist/commands/import.js
CHANGED
|
@@ -1,74 +1,369 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { constants } from 'node:fs';
|
|
3
|
+
import { lstat, open } from 'node:fs/promises';
|
|
4
|
+
import { TextDecoder } from 'node:util';
|
|
5
|
+
import { withDeferredReadTransaction, withImmediateTransaction } from '../db/transaction.js';
|
|
4
6
|
import { KiokukoError } from '../errors.js';
|
|
5
|
-
import {
|
|
7
|
+
import { findSecretInValue } from '../memory/secrets.js';
|
|
8
|
+
import { readEntry } from '../memory/entries.js';
|
|
6
9
|
import { insertEntryRevisionInTransaction } from '../memory/revisions.js';
|
|
7
10
|
import { syncEntrySearchProjection } from '../memory/structured-memory.js';
|
|
8
|
-
import {
|
|
11
|
+
import { isWellFormedUnicode } from '../serialization/boundary-json.js';
|
|
12
|
+
import { canonicalEntryRevisionContentHash, canonicalJson, requireWorkspace, validateRecordInput, } from '../serialization/validate.js';
|
|
13
|
+
import { parseStrictJson } from '../setup/strict-json.js';
|
|
14
|
+
export const WORKSPACE_ARCHIVE_MAX_BYTES = 64 * 1024 * 1024;
|
|
15
|
+
export const WORKSPACE_ARCHIVE_MAX_LINES = 10_000;
|
|
16
|
+
export const WORKSPACE_ARCHIVE_MAX_LINE_BYTES = 512 * 1024;
|
|
9
17
|
const RELATIONS = new Set(['supports', 'contradicts', 'derived_from', 'related_to']);
|
|
10
18
|
const AUDIT_OPERATIONS = new Set(['record', 'promote', 'supersede', 'link', 'import', 'purge']);
|
|
19
|
+
const HASH_PATTERN = /^[0-9a-f]{64}$/u;
|
|
20
|
+
const TIMESTAMP_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/u;
|
|
21
|
+
const RECORD_FIELDS = {
|
|
22
|
+
checksum: ['type', 'sha256'],
|
|
23
|
+
manifest: ['type', 'apiVersion', 'workspace', 'format', 'version', 'counts'],
|
|
24
|
+
entry: [
|
|
25
|
+
'type', 'id', 'workspace', 'kind', 'status', 'title', 'body', 'summary',
|
|
26
|
+
'scope_json', 'provenance_json', 'trust_level', 'confidence', 'content_hash',
|
|
27
|
+
'revision', 'superseded_by', 'created_by', 'created_at', 'updated_at', 'verified_at',
|
|
28
|
+
],
|
|
29
|
+
tag: ['type', 'entry_id', 'tag'],
|
|
30
|
+
link: ['type', 'from_entry_id', 'to_entry_id', 'relation', 'created_at', 'created_by'],
|
|
31
|
+
audit: ['type', 'event_id', 'entry_id', 'workspace', 'operation', 'actor', 'details_json', 'created_at'],
|
|
32
|
+
};
|
|
33
|
+
const COUNT_FIELDS = ['entries', 'tags', 'links', 'audit'];
|
|
34
|
+
const STRICT_JSON_OPTIONS = {
|
|
35
|
+
allowTrailingComma: false,
|
|
36
|
+
disallowComments: true,
|
|
37
|
+
allowEmptyContent: false,
|
|
38
|
+
};
|
|
39
|
+
function archiveChanged() {
|
|
40
|
+
return new KiokukoError('CONFLICT', 'Import input changed while it was being read');
|
|
41
|
+
}
|
|
42
|
+
function isSameArchiveFileState(left, right) {
|
|
43
|
+
return left.isFile()
|
|
44
|
+
&& right.isFile()
|
|
45
|
+
&& left.dev === right.dev
|
|
46
|
+
&& left.ino === right.ino
|
|
47
|
+
&& left.mode === right.mode
|
|
48
|
+
&& left.nlink === right.nlink
|
|
49
|
+
&& left.uid === right.uid
|
|
50
|
+
&& left.gid === right.gid
|
|
51
|
+
&& left.rdev === right.rdev
|
|
52
|
+
&& left.size === right.size
|
|
53
|
+
&& left.mtimeNs === right.mtimeNs
|
|
54
|
+
&& left.ctimeNs === right.ctimeNs;
|
|
55
|
+
}
|
|
56
|
+
function assertBindableArchiveFile(state) {
|
|
57
|
+
if (!state.isFile())
|
|
58
|
+
throw new KiokukoError('VALIDATION_ERROR', 'Import input must be a regular file');
|
|
59
|
+
if (state.ino === 0n) {
|
|
60
|
+
throw new KiokukoError('SECURITY_REJECTION', 'Import input does not expose a stable file identity');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function archiveOpenFlags() {
|
|
64
|
+
if (process.platform === 'win32')
|
|
65
|
+
return constants.O_RDONLY;
|
|
66
|
+
if (!Number.isSafeInteger(constants.O_NOFOLLOW) || constants.O_NOFOLLOW <= 0
|
|
67
|
+
|| !Number.isSafeInteger(constants.O_NONBLOCK) || constants.O_NONBLOCK <= 0) {
|
|
68
|
+
throw new KiokukoError('SECURITY_REJECTION', 'This platform cannot safely open an import archive');
|
|
69
|
+
}
|
|
70
|
+
return constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK;
|
|
71
|
+
}
|
|
72
|
+
async function readBoundedArchive(filePath, dependencies) {
|
|
73
|
+
const planned = await lstat(filePath, { bigint: true });
|
|
74
|
+
assertBindableArchiveFile(planned);
|
|
75
|
+
if (planned.size > BigInt(WORKSPACE_ARCHIVE_MAX_BYTES)) {
|
|
76
|
+
throw new KiokukoError('VALIDATION_ERROR', `Workspace archive exceeds the ${WORKSPACE_ARCHIVE_MAX_BYTES}-byte maximum`);
|
|
77
|
+
}
|
|
78
|
+
await dependencies.afterInputPlanned?.(filePath);
|
|
79
|
+
const handle = await open(filePath, archiveOpenFlags());
|
|
80
|
+
let result;
|
|
81
|
+
let operationError;
|
|
82
|
+
try {
|
|
83
|
+
const initial = await handle.stat({ bigint: true });
|
|
84
|
+
assertBindableArchiveFile(initial);
|
|
85
|
+
if (!isSameArchiveFileState(planned, initial))
|
|
86
|
+
throw archiveChanged();
|
|
87
|
+
await dependencies.afterInputBound?.(filePath);
|
|
88
|
+
if (initial.size > BigInt(WORKSPACE_ARCHIVE_MAX_BYTES)) {
|
|
89
|
+
throw new KiokukoError('VALIDATION_ERROR', `Workspace archive exceeds the ${WORKSPACE_ARCHIVE_MAX_BYTES}-byte maximum`);
|
|
90
|
+
}
|
|
91
|
+
const expectedSize = Number(initial.size);
|
|
92
|
+
const buffer = Buffer.alloc(expectedSize);
|
|
93
|
+
let offset = 0;
|
|
94
|
+
while (offset < expectedSize) {
|
|
95
|
+
const { bytesRead } = await handle.read(buffer, offset, expectedSize - offset, offset);
|
|
96
|
+
if (bytesRead === 0)
|
|
97
|
+
throw archiveChanged();
|
|
98
|
+
offset += bytesRead;
|
|
99
|
+
}
|
|
100
|
+
const extra = Buffer.allocUnsafe(1);
|
|
101
|
+
const { bytesRead: extraBytesRead } = await handle.read(extra, 0, 1, expectedSize);
|
|
102
|
+
if (extraBytesRead !== 0)
|
|
103
|
+
throw archiveChanged();
|
|
104
|
+
await dependencies.afterInputRead?.(filePath);
|
|
105
|
+
const final = await handle.stat({ bigint: true });
|
|
106
|
+
if (!isSameArchiveFileState(initial, final))
|
|
107
|
+
throw archiveChanged();
|
|
108
|
+
const finalPath = await lstat(filePath, { bigint: true });
|
|
109
|
+
if (!isSameArchiveFileState(final, finalPath))
|
|
110
|
+
throw archiveChanged();
|
|
111
|
+
result = buffer;
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
operationError = error;
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
await handle.close();
|
|
118
|
+
}
|
|
119
|
+
catch (closeError) {
|
|
120
|
+
if (operationError !== undefined) {
|
|
121
|
+
throw new AggregateError([operationError, closeError], 'Import read failed and the input file could not be closed');
|
|
122
|
+
}
|
|
123
|
+
throw closeError;
|
|
124
|
+
}
|
|
125
|
+
if (operationError !== undefined)
|
|
126
|
+
throw operationError;
|
|
127
|
+
if (result === undefined)
|
|
128
|
+
throw new KiokukoError('INTEGRITY_ERROR', 'Import read produced no result');
|
|
129
|
+
return result;
|
|
130
|
+
}
|
|
131
|
+
export function validateWorkspaceArchiveByteLayout(bytes) {
|
|
132
|
+
if (bytes.byteLength > WORKSPACE_ARCHIVE_MAX_BYTES) {
|
|
133
|
+
throw new KiokukoError('VALIDATION_ERROR', `Workspace archive exceeds the ${WORKSPACE_ARCHIVE_MAX_BYTES}-byte maximum`);
|
|
134
|
+
}
|
|
135
|
+
let lineBytes = 0;
|
|
136
|
+
let lines = 0;
|
|
137
|
+
for (const byte of bytes) {
|
|
138
|
+
if (byte === 0x0a) {
|
|
139
|
+
lines += 1;
|
|
140
|
+
lineBytes = 0;
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
lineBytes += 1;
|
|
144
|
+
if (lineBytes > WORKSPACE_ARCHIVE_MAX_LINE_BYTES) {
|
|
145
|
+
throw new KiokukoError('VALIDATION_ERROR', `Workspace archive line exceeds the ${WORKSPACE_ARCHIVE_MAX_LINE_BYTES}-byte maximum`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (lines > WORKSPACE_ARCHIVE_MAX_LINES) {
|
|
149
|
+
throw new KiokukoError('VALIDATION_ERROR', `Workspace archive exceeds the ${WORKSPACE_ARCHIVE_MAX_LINES}-line maximum`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (lineBytes > 0)
|
|
153
|
+
lines += 1;
|
|
154
|
+
if (lines > WORKSPACE_ARCHIVE_MAX_LINES) {
|
|
155
|
+
throw new KiokukoError('VALIDATION_ERROR', `Workspace archive exceeds the ${WORKSPACE_ARCHIVE_MAX_LINES}-line maximum`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
11
158
|
function objectValue(value, label) {
|
|
12
159
|
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
13
160
|
throw new KiokukoError('VALIDATION_ERROR', `${label} must be a JSON object`);
|
|
14
161
|
}
|
|
15
162
|
return value;
|
|
16
163
|
}
|
|
164
|
+
function exactFields(value, fields, label) {
|
|
165
|
+
const actual = Object.keys(value);
|
|
166
|
+
const allowed = new Set(fields);
|
|
167
|
+
if (actual.length !== fields.length || actual.some((field) => !allowed.has(field))) {
|
|
168
|
+
throw new KiokukoError('VALIDATION_ERROR', `${label} has invalid fields`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function rejectSecretValue(value) {
|
|
172
|
+
const finding = findSecretInValue(value);
|
|
173
|
+
if (finding !== undefined) {
|
|
174
|
+
throw new KiokukoError('SECURITY_REJECTION', 'Import contains secret-like content', { kind: finding.kind });
|
|
175
|
+
}
|
|
176
|
+
}
|
|
17
177
|
function stringValue(value, label, allowEmpty = false) {
|
|
18
178
|
if (typeof value !== 'string' || (!allowEmpty && value.length === 0)) {
|
|
19
179
|
throw new KiokukoError('VALIDATION_ERROR', `${label} must be a ${allowEmpty ? '' : 'non-empty '}string`);
|
|
20
180
|
}
|
|
181
|
+
if (!isWellFormedUnicode(value)) {
|
|
182
|
+
throw new KiokukoError('VALIDATION_ERROR', `${label} must contain well-formed Unicode`);
|
|
183
|
+
}
|
|
21
184
|
return value;
|
|
22
185
|
}
|
|
186
|
+
function inputPathValue(value) {
|
|
187
|
+
const filePath = stringValue(value, 'input');
|
|
188
|
+
if (/\p{Cc}/u.test(filePath)) {
|
|
189
|
+
throw new KiokukoError('VALIDATION_ERROR', 'input must not contain control characters');
|
|
190
|
+
}
|
|
191
|
+
return filePath;
|
|
192
|
+
}
|
|
23
193
|
function nullableString(value, label) {
|
|
24
|
-
if (value === null
|
|
194
|
+
if (value === null)
|
|
25
195
|
return null;
|
|
26
196
|
return stringValue(value, label, true);
|
|
27
197
|
}
|
|
28
198
|
function parsedJsonObject(value, label) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
|
|
199
|
+
const text = stringValue(value, label);
|
|
200
|
+
const parsed = parseStrictJson(text, STRICT_JSON_OPTIONS, `${label} must contain valid JSON`);
|
|
201
|
+
const object = objectValue(parsed, label);
|
|
202
|
+
rejectSecretValue(object);
|
|
203
|
+
if (canonicalJson(object) !== text) {
|
|
204
|
+
throw new KiokukoError('VALIDATION_ERROR', `${label} must use canonical JSON encoding`);
|
|
205
|
+
}
|
|
206
|
+
return object;
|
|
33
207
|
}
|
|
34
|
-
function
|
|
35
|
-
|
|
36
|
-
|
|
208
|
+
function numberValue(value, label) {
|
|
209
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
210
|
+
throw new KiokukoError('VALIDATION_ERROR', `${label} must be a finite number`);
|
|
37
211
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
212
|
+
return value;
|
|
213
|
+
}
|
|
214
|
+
function integerValue(value, label, minimum = 0) {
|
|
215
|
+
if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < minimum) {
|
|
216
|
+
throw new KiokukoError('VALIDATION_ERROR', `${label} must be an integer greater than or equal to ${minimum}`);
|
|
217
|
+
}
|
|
218
|
+
return value;
|
|
219
|
+
}
|
|
220
|
+
function timestampValue(value, label) {
|
|
221
|
+
const timestamp = stringValue(value, label);
|
|
222
|
+
const parsed = Date.parse(timestamp);
|
|
223
|
+
if (!TIMESTAMP_PATTERN.test(timestamp) || !Number.isFinite(parsed) || new Date(parsed).toISOString() !== timestamp) {
|
|
224
|
+
throw new KiokukoError('VALIDATION_ERROR', `${label} must be a canonical ISO-8601 UTC timestamp`);
|
|
225
|
+
}
|
|
226
|
+
return timestamp;
|
|
227
|
+
}
|
|
228
|
+
function hashValue(value, label) {
|
|
229
|
+
const hash = stringValue(value, label);
|
|
230
|
+
if (!HASH_PATTERN.test(hash))
|
|
231
|
+
throw new KiokukoError('VALIDATION_ERROR', `${label} must be a lowercase SHA-256 hash`);
|
|
232
|
+
return hash;
|
|
233
|
+
}
|
|
234
|
+
function parseJsonLine(text, label) {
|
|
235
|
+
const parsed = parseStrictJson(text, STRICT_JSON_OPTIONS, `${label} contains invalid JSON`);
|
|
236
|
+
const object = objectValue(parsed, label);
|
|
237
|
+
if (canonicalJson(object) !== text) {
|
|
238
|
+
throw new KiokukoError('VALIDATION_ERROR', `${label} must use canonical JSON encoding`);
|
|
42
239
|
}
|
|
240
|
+
return object;
|
|
241
|
+
}
|
|
242
|
+
function validateEntryRecord(record, workspace) {
|
|
243
|
+
if (record.type !== 'entry')
|
|
244
|
+
throw new KiokukoError('VALIDATION_ERROR', 'Entry record type is invalid');
|
|
245
|
+
stringValue(record.id, 'entry.id');
|
|
246
|
+
if (stringValue(record.workspace, 'entry.workspace') !== workspace) {
|
|
247
|
+
throw new KiokukoError('VALIDATION_ERROR', 'Entry workspace does not match manifest workspace');
|
|
248
|
+
}
|
|
249
|
+
stringValue(record.kind, 'entry.kind');
|
|
250
|
+
stringValue(record.status, 'entry.status');
|
|
251
|
+
stringValue(record.title, 'entry.title');
|
|
252
|
+
stringValue(record.body, 'entry.body', true);
|
|
253
|
+
nullableString(record.summary, 'entry.summary');
|
|
254
|
+
parsedJsonObject(record.scope_json, 'entry.scope_json');
|
|
255
|
+
parsedJsonObject(record.provenance_json, 'entry.provenance_json');
|
|
256
|
+
stringValue(record.trust_level, 'entry.trust_level');
|
|
257
|
+
numberValue(record.confidence, 'entry.confidence');
|
|
258
|
+
hashValue(record.content_hash, 'entry.content_hash');
|
|
259
|
+
if (integerValue(record.revision, 'entry.revision', 1) !== 1) {
|
|
260
|
+
throw new KiokukoError('VALIDATION_ERROR', 'Workspace archive v2 can import only revision 1 snapshots; higher revisions require a history-preserving archive format');
|
|
261
|
+
}
|
|
262
|
+
nullableString(record.superseded_by, 'entry.superseded_by');
|
|
263
|
+
stringValue(record.created_by, 'entry.created_by');
|
|
264
|
+
timestampValue(record.created_at, 'entry.created_at');
|
|
265
|
+
timestampValue(record.updated_at, 'entry.updated_at');
|
|
266
|
+
if (record.verified_at !== null)
|
|
267
|
+
timestampValue(record.verified_at, 'entry.verified_at');
|
|
268
|
+
}
|
|
269
|
+
function validateTagRecord(record) {
|
|
270
|
+
if (record.type !== 'tag')
|
|
271
|
+
throw new KiokukoError('VALIDATION_ERROR', 'Tag record type is invalid');
|
|
272
|
+
stringValue(record.entry_id, 'tag.entry_id');
|
|
273
|
+
stringValue(record.tag, 'tag.tag');
|
|
274
|
+
}
|
|
275
|
+
function validateLinkRecord(record) {
|
|
276
|
+
if (record.type !== 'link')
|
|
277
|
+
throw new KiokukoError('VALIDATION_ERROR', 'Link record type is invalid');
|
|
278
|
+
stringValue(record.from_entry_id, 'link.from_entry_id');
|
|
279
|
+
stringValue(record.to_entry_id, 'link.to_entry_id');
|
|
280
|
+
stringValue(record.relation, 'link.relation');
|
|
281
|
+
timestampValue(record.created_at, 'link.created_at');
|
|
282
|
+
stringValue(record.created_by, 'link.created_by');
|
|
283
|
+
}
|
|
284
|
+
function validateAuditRecord(record, workspace) {
|
|
285
|
+
if (record.type !== 'audit')
|
|
286
|
+
throw new KiokukoError('VALIDATION_ERROR', 'Audit record type is invalid');
|
|
287
|
+
stringValue(record.event_id, 'audit.event_id');
|
|
288
|
+
nullableString(record.entry_id, 'audit.entry_id');
|
|
289
|
+
if (stringValue(record.workspace, 'audit.workspace') !== workspace) {
|
|
290
|
+
throw new KiokukoError('VALIDATION_ERROR', 'Audit workspace does not match manifest workspace');
|
|
291
|
+
}
|
|
292
|
+
stringValue(record.operation, 'audit.operation');
|
|
293
|
+
stringValue(record.actor, 'audit.actor');
|
|
294
|
+
parsedJsonObject(record.details_json, 'audit.details_json');
|
|
295
|
+
timestampValue(record.created_at, 'audit.created_at');
|
|
43
296
|
}
|
|
44
297
|
function parseImport(text) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if (lines.length < 2)
|
|
49
|
-
throw new KiokukoError('VALIDATION_ERROR', 'Import file is empty or
|
|
50
|
-
|
|
51
|
-
|
|
298
|
+
if (!text.endsWith('\n'))
|
|
299
|
+
throw new KiokukoError('VALIDATION_ERROR', 'Import must end with exactly one newline');
|
|
300
|
+
const lines = text.slice(0, -1).split('\n');
|
|
301
|
+
if (lines.length < 2 || lines.some((line) => line.length === 0)) {
|
|
302
|
+
throw new KiokukoError('VALIDATION_ERROR', 'Import file is empty, truncated, or contains blank lines');
|
|
303
|
+
}
|
|
304
|
+
const checksumLine = parseJsonLine(lines[0], 'Import checksum line');
|
|
305
|
+
exactFields(checksumLine, RECORD_FIELDS.checksum, 'Import checksum line');
|
|
306
|
+
if (checksumLine.type !== 'checksum') {
|
|
52
307
|
throw new KiokukoError('VALIDATION_ERROR', 'Import checksum line is invalid');
|
|
53
308
|
}
|
|
309
|
+
const checksum = hashValue(checksumLine.sha256, 'Import checksum');
|
|
54
310
|
const payload = `${lines.slice(1).join('\n')}\n`;
|
|
55
311
|
const actual = createHash('sha256').update(payload, 'utf8').digest('hex');
|
|
56
|
-
if (actual !==
|
|
312
|
+
if (actual !== checksum)
|
|
57
313
|
throw new KiokukoError('INTEGRITY_ERROR', 'Import checksum mismatch');
|
|
58
|
-
const payloadLines = lines.slice(1).map(
|
|
59
|
-
const manifest = payloadLines
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if (manifest.format !== 'kiokuko-jsonl' || manifest.apiVersion !== '1') {
|
|
314
|
+
const payloadLines = lines.slice(1).map((line, index) => parseJsonLine(line, `Import payload line ${index + 1}`));
|
|
315
|
+
const manifest = payloadLines[0];
|
|
316
|
+
exactFields(manifest, RECORD_FIELDS.manifest, 'Import manifest');
|
|
317
|
+
if (manifest.format !== 'kiokuko-jsonl' || manifest.apiVersion !== '1' || manifest.version !== 2) {
|
|
63
318
|
throw new KiokukoError('VALIDATION_ERROR', 'Unsupported import format');
|
|
64
319
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const
|
|
69
|
-
const counts = objectValue(manifest.counts
|
|
320
|
+
if (manifest.type !== 'manifest')
|
|
321
|
+
throw new KiokukoError('VALIDATION_ERROR', 'Import manifest is invalid');
|
|
322
|
+
rejectSecretValue(manifest);
|
|
323
|
+
const workspace = requireWorkspace(manifest.workspace);
|
|
324
|
+
const counts = objectValue(manifest.counts, 'manifest.counts');
|
|
325
|
+
exactFields(counts, COUNT_FIELDS, 'manifest.counts');
|
|
326
|
+
for (const field of COUNT_FIELDS)
|
|
327
|
+
integerValue(counts[field], `manifest.counts.${field}`);
|
|
328
|
+
const entries = [];
|
|
329
|
+
const tags = [];
|
|
330
|
+
const links = [];
|
|
331
|
+
const audit = [];
|
|
332
|
+
for (const line of payloadLines.slice(1)) {
|
|
333
|
+
if (line.type === 'manifest' || line.type === 'checksum') {
|
|
334
|
+
throw new KiokukoError('INTEGRITY_ERROR', `Import contains a duplicate ${String(line.type)} record`);
|
|
335
|
+
}
|
|
336
|
+
switch (line.type) {
|
|
337
|
+
case 'entry':
|
|
338
|
+
exactFields(line, RECORD_FIELDS.entry, 'Import entry record');
|
|
339
|
+
validateEntryRecord(line, workspace);
|
|
340
|
+
rejectSecretValue(line);
|
|
341
|
+
entries.push(line);
|
|
342
|
+
break;
|
|
343
|
+
case 'tag':
|
|
344
|
+
exactFields(line, RECORD_FIELDS.tag, 'Import tag record');
|
|
345
|
+
validateTagRecord(line);
|
|
346
|
+
rejectSecretValue(line);
|
|
347
|
+
tags.push(line);
|
|
348
|
+
break;
|
|
349
|
+
case 'link':
|
|
350
|
+
exactFields(line, RECORD_FIELDS.link, 'Import link record');
|
|
351
|
+
validateLinkRecord(line);
|
|
352
|
+
rejectSecretValue(line);
|
|
353
|
+
links.push(line);
|
|
354
|
+
break;
|
|
355
|
+
case 'audit':
|
|
356
|
+
exactFields(line, RECORD_FIELDS.audit, 'Import audit record');
|
|
357
|
+
validateAuditRecord(line, workspace);
|
|
358
|
+
rejectSecretValue(line);
|
|
359
|
+
audit.push(line);
|
|
360
|
+
break;
|
|
361
|
+
default:
|
|
362
|
+
throw new KiokukoError('VALIDATION_ERROR', `Import contains unsupported record type ${String(line.type)}`);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
70
365
|
for (const [key, actualCount] of Object.entries({ entries, tags, links, audit })) {
|
|
71
|
-
if (counts[key] !==
|
|
366
|
+
if (counts[key] !== actualCount.length) {
|
|
72
367
|
throw new KiokukoError('INTEGRITY_ERROR', `Import ${key} count does not match manifest`);
|
|
73
368
|
}
|
|
74
369
|
}
|
|
@@ -84,15 +379,15 @@ function importedEntry(raw, workspace) {
|
|
|
84
379
|
const scope = parsedJsonObject(raw.scope_json, 'entry.scope_json');
|
|
85
380
|
const provenance = parsedJsonObject(raw.provenance_json, 'entry.provenance_json');
|
|
86
381
|
const trustLevel = stringValue(raw.trust_level, 'entry.trust_level');
|
|
87
|
-
const confidence =
|
|
88
|
-
const
|
|
382
|
+
const confidence = numberValue(raw.confidence, 'entry.confidence');
|
|
383
|
+
const persistedTags = Array.isArray(raw.tags) ? raw.tags : [];
|
|
89
384
|
const createdBy = stringValue(raw.created_by, 'entry.created_by');
|
|
90
385
|
const supersededBy = nullableString(raw.superseded_by, 'entry.superseded_by');
|
|
91
|
-
const createdAt =
|
|
92
|
-
const updatedAt =
|
|
93
|
-
const verifiedAt =
|
|
94
|
-
const revision =
|
|
95
|
-
const contentHash =
|
|
386
|
+
const createdAt = timestampValue(raw.created_at, 'entry.created_at');
|
|
387
|
+
const updatedAt = timestampValue(raw.updated_at, 'entry.updated_at');
|
|
388
|
+
const verifiedAt = raw.verified_at === null ? null : timestampValue(raw.verified_at, 'entry.verified_at');
|
|
389
|
+
const revision = integerValue(raw.revision, 'entry.revision', 1);
|
|
390
|
+
const contentHash = hashValue(raw.content_hash, 'entry.content_hash');
|
|
96
391
|
const validated = validateRecordInput({
|
|
97
392
|
workspace,
|
|
98
393
|
kind,
|
|
@@ -104,14 +399,26 @@ function importedEntry(raw, workspace) {
|
|
|
104
399
|
provenance,
|
|
105
400
|
trustLevel,
|
|
106
401
|
confidence,
|
|
107
|
-
tags,
|
|
402
|
+
tags: persistedTags,
|
|
108
403
|
createdBy,
|
|
109
404
|
});
|
|
110
|
-
if (status === 'superseded'
|
|
111
|
-
throw new KiokukoError('VALIDATION_ERROR', '
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
405
|
+
if ((status === 'superseded') !== (supersededBy !== null)) {
|
|
406
|
+
throw new KiokukoError('VALIDATION_ERROR', 'entry.status and entry.superseded_by are inconsistent');
|
|
407
|
+
}
|
|
408
|
+
if (status === 'candidate' && verifiedAt !== null) {
|
|
409
|
+
throw new KiokukoError('VALIDATION_ERROR', 'Candidate entries cannot have entry.verified_at');
|
|
410
|
+
}
|
|
411
|
+
if (status === 'verified' && verifiedAt === null) {
|
|
412
|
+
throw new KiokukoError('VALIDATION_ERROR', 'Verified entries require entry.verified_at');
|
|
413
|
+
}
|
|
414
|
+
if (createdAt > updatedAt
|
|
415
|
+
|| verifiedAt !== null && (verifiedAt < createdAt || verifiedAt > updatedAt)) {
|
|
416
|
+
throw new KiokukoError('VALIDATION_ERROR', 'Entry lifecycle timestamps are inconsistent');
|
|
417
|
+
}
|
|
418
|
+
if (revision !== 1) {
|
|
419
|
+
throw new KiokukoError('VALIDATION_ERROR', 'Workspace archive v2 can import only revision 1 snapshots; higher revisions require a history-preserving archive format');
|
|
420
|
+
}
|
|
421
|
+
const hashInput = {
|
|
115
422
|
kind: validated.kind,
|
|
116
423
|
title: validated.title,
|
|
117
424
|
body: validated.body,
|
|
@@ -119,19 +426,15 @@ function importedEntry(raw, workspace) {
|
|
|
119
426
|
scope: validated.scope,
|
|
120
427
|
provenance: validated.provenance,
|
|
121
428
|
tags: validated.tags,
|
|
122
|
-
}
|
|
123
|
-
|
|
429
|
+
};
|
|
430
|
+
const expectedHash = canonicalEntryRevisionContentHash(hashInput);
|
|
431
|
+
if (expectedHash !== contentHash) {
|
|
124
432
|
throw new KiokukoError('INTEGRITY_ERROR', `Content hash mismatch for entry ${id}`);
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
provenance: validated.provenance,
|
|
131
|
-
tags: validated.tags,
|
|
132
|
-
}));
|
|
133
|
-
if (secretFinding)
|
|
134
|
-
throw new KiokukoError('SECURITY_REJECTION', 'Import contains secret-like content', { kind: secretFinding.kind });
|
|
433
|
+
}
|
|
434
|
+
if (persistedTags.length !== validated.tags.length
|
|
435
|
+
|| persistedTags.some((tag, index) => tag !== validated.tags[index])) {
|
|
436
|
+
throw new KiokukoError('VALIDATION_ERROR', `Tags for entry ${id} are not in canonical archive order`);
|
|
437
|
+
}
|
|
135
438
|
return {
|
|
136
439
|
id,
|
|
137
440
|
workspace,
|
|
@@ -140,8 +443,8 @@ function importedEntry(raw, workspace) {
|
|
|
140
443
|
title: validated.title,
|
|
141
444
|
body: validated.body,
|
|
142
445
|
summary: validated.summary,
|
|
143
|
-
|
|
144
|
-
|
|
446
|
+
scope: validated.scope,
|
|
447
|
+
provenance: validated.provenance,
|
|
145
448
|
trustLevel: validated.trustLevel,
|
|
146
449
|
confidence: validated.confidence,
|
|
147
450
|
contentHash,
|
|
@@ -154,13 +457,44 @@ function importedEntry(raw, workspace) {
|
|
|
154
457
|
tags: validated.tags,
|
|
155
458
|
};
|
|
156
459
|
}
|
|
460
|
+
function validateSupersessionGraph(entries) {
|
|
461
|
+
const replacements = new Map(entries.map((entry) => [entry.id, entry.supersededBy]));
|
|
462
|
+
const state = new Map();
|
|
463
|
+
for (const entry of entries) {
|
|
464
|
+
if (state.get(entry.id) === 'complete')
|
|
465
|
+
continue;
|
|
466
|
+
const path = [];
|
|
467
|
+
let current = entry.id;
|
|
468
|
+
while (current !== null) {
|
|
469
|
+
if (!replacements.has(current)) {
|
|
470
|
+
throw new KiokukoError('VALIDATION_ERROR', `Supersession references unknown entry ${current}`);
|
|
471
|
+
}
|
|
472
|
+
const currentState = state.get(current);
|
|
473
|
+
if (currentState === 'visiting')
|
|
474
|
+
throw new KiokukoError('VALIDATION_ERROR', 'Import contains a supersession cycle');
|
|
475
|
+
if (currentState === 'complete')
|
|
476
|
+
break;
|
|
477
|
+
state.set(current, 'visiting');
|
|
478
|
+
path.push(current);
|
|
479
|
+
current = replacements.get(current) ?? null;
|
|
480
|
+
}
|
|
481
|
+
for (const visited of path)
|
|
482
|
+
state.set(visited, 'complete');
|
|
483
|
+
}
|
|
484
|
+
}
|
|
157
485
|
function validateRelatedLines(document, entryIds) {
|
|
486
|
+
const seenTags = new Set();
|
|
158
487
|
for (const tag of document.tags) {
|
|
159
488
|
const entryId = stringValue(tag.entry_id, 'tag.entry_id');
|
|
160
|
-
stringValue(tag.tag, 'tag.tag');
|
|
489
|
+
const value = stringValue(tag.tag, 'tag.tag');
|
|
161
490
|
if (!entryIds.has(entryId))
|
|
162
491
|
throw new KiokukoError('VALIDATION_ERROR', `Tag references unknown entry ${entryId}`);
|
|
492
|
+
const identity = `${entryId}\u0000${value}`;
|
|
493
|
+
if (seenTags.has(identity))
|
|
494
|
+
throw new KiokukoError('VALIDATION_ERROR', 'Import contains a duplicate tag record');
|
|
495
|
+
seenTags.add(identity);
|
|
163
496
|
}
|
|
497
|
+
const seenLinks = new Set();
|
|
164
498
|
for (const link of document.links) {
|
|
165
499
|
const from = stringValue(link.from_entry_id, 'link.from_entry_id');
|
|
166
500
|
const to = stringValue(link.to_entry_id, 'link.to_entry_id');
|
|
@@ -168,7 +502,12 @@ function validateRelatedLines(document, entryIds) {
|
|
|
168
502
|
if (!entryIds.has(from) || !entryIds.has(to) || !RELATIONS.has(relation)) {
|
|
169
503
|
throw new KiokukoError('VALIDATION_ERROR', 'Import contains an invalid link');
|
|
170
504
|
}
|
|
505
|
+
const identity = `${from}\u0000${to}\u0000${relation}`;
|
|
506
|
+
if (seenLinks.has(identity))
|
|
507
|
+
throw new KiokukoError('VALIDATION_ERROR', 'Import contains a duplicate link record');
|
|
508
|
+
seenLinks.add(identity);
|
|
171
509
|
}
|
|
510
|
+
const seenAuditEvents = new Set();
|
|
172
511
|
for (const event of document.audit) {
|
|
173
512
|
const entryId = nullableString(event.entry_id, 'audit.entry_id');
|
|
174
513
|
const operation = stringValue(event.operation, 'audit.operation');
|
|
@@ -176,25 +515,157 @@ function validateRelatedLines(document, entryIds) {
|
|
|
176
515
|
throw new KiokukoError('VALIDATION_ERROR', `Audit references unknown entry ${entryId}`);
|
|
177
516
|
if (!AUDIT_OPERATIONS.has(operation))
|
|
178
517
|
throw new KiokukoError('VALIDATION_ERROR', `Unsupported audit operation ${operation}`);
|
|
179
|
-
stringValue(event.event_id, 'audit.event_id');
|
|
518
|
+
const eventId = stringValue(event.event_id, 'audit.event_id');
|
|
519
|
+
if (seenAuditEvents.has(eventId))
|
|
520
|
+
throw new KiokukoError('VALIDATION_ERROR', 'Import contains a duplicate audit event');
|
|
521
|
+
seenAuditEvents.add(eventId);
|
|
180
522
|
stringValue(event.workspace, 'audit.workspace');
|
|
181
523
|
stringValue(event.actor, 'audit.actor');
|
|
182
524
|
stringValue(event.details_json, 'audit.details_json', true);
|
|
183
525
|
stringValue(event.created_at, 'audit.created_at');
|
|
184
526
|
}
|
|
185
527
|
}
|
|
186
|
-
function
|
|
187
|
-
return
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
528
|
+
function sameStringArray(left, right) {
|
|
529
|
+
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
530
|
+
}
|
|
531
|
+
function entryMatchesArchive(existing, imported) {
|
|
532
|
+
return existing.id === imported.id
|
|
533
|
+
&& existing.workspace === imported.workspace
|
|
534
|
+
&& existing.kind === imported.kind
|
|
535
|
+
&& existing.status === imported.status
|
|
536
|
+
&& existing.title === imported.title
|
|
537
|
+
&& existing.body === imported.body
|
|
538
|
+
&& existing.summary === imported.summary
|
|
539
|
+
&& canonicalJson(existing.scope) === canonicalJson(imported.scope)
|
|
540
|
+
&& canonicalJson(existing.provenance) === canonicalJson(imported.provenance)
|
|
541
|
+
&& existing.trustLevel === imported.trustLevel
|
|
542
|
+
&& existing.confidence === imported.confidence
|
|
543
|
+
&& existing.contentHash === imported.contentHash
|
|
544
|
+
&& existing.revision === imported.revision
|
|
545
|
+
&& existing.supersededBy === imported.supersededBy
|
|
546
|
+
&& existing.createdBy === imported.createdBy
|
|
547
|
+
&& existing.createdAt === imported.createdAt
|
|
548
|
+
&& existing.updatedAt === imported.updatedAt
|
|
549
|
+
&& existing.verifiedAt === imported.verifiedAt
|
|
550
|
+
&& sameStringArray(existing.tags, imported.tags);
|
|
551
|
+
}
|
|
552
|
+
function planEntryImport(database, entries, workspace) {
|
|
553
|
+
const newEntries = [];
|
|
554
|
+
const duplicateIds = new Set();
|
|
555
|
+
if (database === undefined) {
|
|
556
|
+
return { newEntries: [...entries], duplicates: 0 };
|
|
557
|
+
}
|
|
558
|
+
for (const entry of entries) {
|
|
559
|
+
const owner = database.prepare('SELECT workspace FROM entries WHERE id = ?').get(entry.id);
|
|
560
|
+
if (owner !== undefined) {
|
|
561
|
+
if (typeof owner.workspace !== 'string')
|
|
562
|
+
throw new KiokukoError('INTEGRITY_ERROR', 'Stored entry workspace is invalid');
|
|
563
|
+
if (owner.workspace !== workspace) {
|
|
564
|
+
throw new KiokukoError('CONFLICT', `Entry ID ${entry.id} already belongs to another workspace`);
|
|
565
|
+
}
|
|
566
|
+
const existing = readEntry(database, { workspace, entryId: entry.id });
|
|
567
|
+
if (existing.contentHash !== entry.contentHash) {
|
|
568
|
+
throw new KiokukoError('CONFLICT', `Entry ID ${entry.id} already contains different content`);
|
|
569
|
+
}
|
|
570
|
+
duplicateIds.add(entry.id);
|
|
571
|
+
continue;
|
|
572
|
+
}
|
|
573
|
+
const hashMatch = database.prepare(`
|
|
574
|
+
SELECT entry_id
|
|
575
|
+
FROM entry_revisions
|
|
576
|
+
WHERE workspace = ? AND content_hash = ?
|
|
577
|
+
`).get(workspace, entry.contentHash);
|
|
578
|
+
if (hashMatch !== undefined) {
|
|
579
|
+
if (typeof hashMatch.entry_id !== 'string') {
|
|
580
|
+
throw new KiokukoError('INTEGRITY_ERROR', 'Stored content identity is invalid');
|
|
581
|
+
}
|
|
582
|
+
if (hashMatch.entry_id === entry.id) {
|
|
583
|
+
throw new KiokukoError('INTEGRITY_ERROR', 'Stored revision has no owning entry');
|
|
584
|
+
}
|
|
585
|
+
throw new KiokukoError('CONFLICT', 'Archive content already belongs to a different entry ID');
|
|
586
|
+
}
|
|
587
|
+
newEntries.push(entry);
|
|
588
|
+
}
|
|
589
|
+
for (const entry of entries) {
|
|
590
|
+
if (!duplicateIds.has(entry.id))
|
|
591
|
+
continue;
|
|
592
|
+
const existing = readEntry(database, { workspace, entryId: entry.id });
|
|
593
|
+
if (!entryMatchesArchive(existing, entry)) {
|
|
594
|
+
throw new KiokukoError('CONFLICT', `Entry ${entry.id} already exists with different archive metadata`);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
return { newEntries, duplicates: duplicateIds.size };
|
|
598
|
+
}
|
|
599
|
+
function planRelatedImport(database, document, workspace) {
|
|
600
|
+
const links = [];
|
|
601
|
+
for (const link of document.links) {
|
|
602
|
+
const planned = {
|
|
603
|
+
from: stringValue(link.from_entry_id, 'link.from_entry_id'),
|
|
604
|
+
to: stringValue(link.to_entry_id, 'link.to_entry_id'),
|
|
605
|
+
relation: stringValue(link.relation, 'link.relation'),
|
|
606
|
+
createdAt: timestampValue(link.created_at, 'link.created_at'),
|
|
607
|
+
createdBy: stringValue(link.created_by, 'link.created_by'),
|
|
608
|
+
};
|
|
609
|
+
const existing = database?.prepare(`
|
|
610
|
+
SELECT created_at, created_by
|
|
611
|
+
FROM entry_links
|
|
612
|
+
WHERE from_entry_id = ? AND to_entry_id = ? AND relation = ?
|
|
613
|
+
`).get(planned.from, planned.to, planned.relation);
|
|
614
|
+
if (existing !== undefined) {
|
|
615
|
+
if (existing.created_at !== planned.createdAt || existing.created_by !== planned.createdBy) {
|
|
616
|
+
throw new KiokukoError('CONFLICT', 'Existing link has different archive metadata');
|
|
617
|
+
}
|
|
618
|
+
continue;
|
|
619
|
+
}
|
|
620
|
+
links.push(planned);
|
|
621
|
+
}
|
|
622
|
+
const audit = [];
|
|
623
|
+
for (const event of document.audit) {
|
|
624
|
+
const planned = {
|
|
625
|
+
eventId: stringValue(event.event_id, 'audit.event_id'),
|
|
626
|
+
entryId: nullableString(event.entry_id, 'audit.entry_id'),
|
|
627
|
+
operation: stringValue(event.operation, 'audit.operation'),
|
|
628
|
+
actor: stringValue(event.actor, 'audit.actor'),
|
|
629
|
+
detailsJson: stringValue(event.details_json, 'audit.details_json'),
|
|
630
|
+
createdAt: timestampValue(event.created_at, 'audit.created_at'),
|
|
631
|
+
};
|
|
632
|
+
const existing = database?.prepare(`
|
|
633
|
+
SELECT entry_id, workspace, operation, actor, details_json, created_at
|
|
634
|
+
FROM audit_events
|
|
635
|
+
WHERE event_id = ?
|
|
636
|
+
`).get(planned.eventId);
|
|
637
|
+
if (existing !== undefined) {
|
|
638
|
+
if (existing.entry_id !== planned.entryId
|
|
639
|
+
|| existing.workspace !== workspace
|
|
640
|
+
|| existing.operation !== planned.operation
|
|
641
|
+
|| existing.actor !== planned.actor
|
|
642
|
+
|| existing.details_json !== planned.detailsJson
|
|
643
|
+
|| existing.created_at !== planned.createdAt) {
|
|
644
|
+
throw new KiokukoError('CONFLICT', `Audit event ${planned.eventId} already contains different metadata`);
|
|
645
|
+
}
|
|
646
|
+
continue;
|
|
647
|
+
}
|
|
648
|
+
audit.push(planned);
|
|
649
|
+
}
|
|
650
|
+
return { links, audit };
|
|
193
651
|
}
|
|
194
|
-
export async function importWorkspace(database, options) {
|
|
195
|
-
const
|
|
652
|
+
export async function importWorkspace(database, options, dependencies = {}) {
|
|
653
|
+
const archiveBytes = await readBoundedArchive(inputPathValue(options.input), dependencies);
|
|
654
|
+
validateWorkspaceArchiveByteLayout(archiveBytes);
|
|
655
|
+
if (archiveBytes[0] === 0xEF && archiveBytes[1] === 0xBB && archiveBytes[2] === 0xBF) {
|
|
656
|
+
throw new KiokukoError('VALIDATION_ERROR', 'Import must not begin with a UTF-8 BOM');
|
|
657
|
+
}
|
|
658
|
+
let archiveText;
|
|
659
|
+
try {
|
|
660
|
+
archiveText = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(archiveBytes);
|
|
661
|
+
}
|
|
662
|
+
catch {
|
|
663
|
+
throw new KiokukoError('VALIDATION_ERROR', 'Import is not valid UTF-8');
|
|
664
|
+
}
|
|
665
|
+
const parsed = parseImport(archiveText);
|
|
196
666
|
const sourceWorkspace = requireWorkspace(parsed.manifest.workspace);
|
|
197
|
-
const workspace = requireWorkspace(options.workspace
|
|
667
|
+
const workspace = requireWorkspace(options.workspace === undefined ? sourceWorkspace : stringValue(options.workspace, 'workspace'));
|
|
668
|
+
rejectSecretValue(workspace);
|
|
198
669
|
const tagsByEntry = new Map();
|
|
199
670
|
for (const tag of parsed.tags) {
|
|
200
671
|
const entryId = stringValue(tag.entry_id, 'tag.entry_id');
|
|
@@ -206,40 +677,33 @@ export async function importWorkspace(database, options) {
|
|
|
206
677
|
const sourceIds = new Set(entries.map((entry) => entry.id));
|
|
207
678
|
if (sourceIds.size !== entries.length)
|
|
208
679
|
throw new KiokukoError('VALIDATION_ERROR', 'Import contains duplicate entry IDs');
|
|
680
|
+
if (new Set(entries.map((entry) => entry.contentHash)).size !== entries.length) {
|
|
681
|
+
throw new KiokukoError('VALIDATION_ERROR', 'Import contains duplicate entry content');
|
|
682
|
+
}
|
|
683
|
+
validateSupersessionGraph(entries);
|
|
209
684
|
validateRelatedLines(parsed, sourceIds);
|
|
210
685
|
if (database === undefined && !options.dryRun)
|
|
211
686
|
throw new KiokukoError('DATABASE_ERROR', 'Database is required for a non-dry import');
|
|
212
|
-
const idMap = new Map();
|
|
213
|
-
const newEntries = [];
|
|
214
|
-
let duplicates = 0;
|
|
215
|
-
for (const entry of entries) {
|
|
216
|
-
if (!database) {
|
|
217
|
-
idMap.set(entry.id, entry.id);
|
|
218
|
-
newEntries.push(entry);
|
|
219
|
-
continue;
|
|
220
|
-
}
|
|
221
|
-
const byId = existingEntry(database, entry.id);
|
|
222
|
-
if (byId) {
|
|
223
|
-
if (byId.content_hash !== entry.contentHash)
|
|
224
|
-
throw new KiokukoError('CONFLICT', `Entry ID ${entry.id} already contains different content`);
|
|
225
|
-
idMap.set(entry.id, entry.id);
|
|
226
|
-
duplicates += 1;
|
|
227
|
-
continue;
|
|
228
|
-
}
|
|
229
|
-
const byHash = database.prepare('SELECT entry_id AS id FROM entry_revisions WHERE workspace = ? AND content_hash = ? LIMIT 1').get(workspace, entry.contentHash);
|
|
230
|
-
if (byHash) {
|
|
231
|
-
idMap.set(entry.id, byHash.id);
|
|
232
|
-
duplicates += 1;
|
|
233
|
-
continue;
|
|
234
|
-
}
|
|
235
|
-
idMap.set(entry.id, entry.id);
|
|
236
|
-
newEntries.push(entry);
|
|
237
|
-
}
|
|
238
687
|
if (options.dryRun) {
|
|
239
|
-
|
|
688
|
+
const preflight = () => {
|
|
689
|
+
const entryPlan = planEntryImport(database, entries, workspace);
|
|
690
|
+
planRelatedImport(database, parsed, workspace);
|
|
691
|
+
return entryPlan;
|
|
692
|
+
};
|
|
693
|
+
const plan = database === undefined ? preflight() : withDeferredReadTransaction(database, preflight);
|
|
694
|
+
return {
|
|
695
|
+
count: entries.length,
|
|
696
|
+
imported: plan.newEntries.length,
|
|
697
|
+
duplicates: plan.duplicates,
|
|
698
|
+
dryRun: true,
|
|
699
|
+
workspace,
|
|
700
|
+
};
|
|
240
701
|
}
|
|
241
702
|
const db = database;
|
|
242
|
-
withImmediateTransaction(db, () => {
|
|
703
|
+
const result = withImmediateTransaction(db, () => {
|
|
704
|
+
const plan = planEntryImport(db, entries, workspace);
|
|
705
|
+
const related = planRelatedImport(db, parsed, workspace);
|
|
706
|
+
const { newEntries } = plan;
|
|
243
707
|
const insertEntry = db.prepare(`
|
|
244
708
|
INSERT INTO entries (
|
|
245
709
|
id, workspace, status, trust_level, confidence, current_revision,
|
|
@@ -247,7 +711,7 @@ export async function importWorkspace(database, options) {
|
|
|
247
711
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
248
712
|
`);
|
|
249
713
|
for (const entry of newEntries) {
|
|
250
|
-
insertEntry.run(entry.id, workspace,
|
|
714
|
+
insertEntry.run(entry.id, workspace, 'candidate', entry.trustLevel, entry.confidence, 1, null, entry.createdBy, entry.createdAt, entry.updatedAt, null);
|
|
251
715
|
insertEntryRevisionInTransaction(db, {
|
|
252
716
|
entryId: entry.id,
|
|
253
717
|
workspace,
|
|
@@ -256,8 +720,8 @@ export async function importWorkspace(database, options) {
|
|
|
256
720
|
title: entry.title,
|
|
257
721
|
body: entry.body,
|
|
258
722
|
summary: entry.summary,
|
|
259
|
-
scope:
|
|
260
|
-
provenance:
|
|
723
|
+
scope: entry.scope,
|
|
724
|
+
provenance: entry.provenance,
|
|
261
725
|
tags: entry.tags,
|
|
262
726
|
contentHash: entry.contentHash,
|
|
263
727
|
createdBy: entry.createdBy,
|
|
@@ -269,24 +733,39 @@ export async function importWorkspace(database, options) {
|
|
|
269
733
|
body: entry.body,
|
|
270
734
|
summary: entry.summary,
|
|
271
735
|
tags: entry.tags,
|
|
272
|
-
scope:
|
|
736
|
+
scope: entry.scope,
|
|
273
737
|
});
|
|
274
738
|
}
|
|
739
|
+
const applyLifecycle = db.prepare(`
|
|
740
|
+
UPDATE entries
|
|
741
|
+
SET status = ?, superseded_by = ?, verified_at = ?
|
|
742
|
+
WHERE id = ? AND workspace = ? AND current_revision = 1
|
|
743
|
+
`);
|
|
744
|
+
for (const entry of newEntries) {
|
|
745
|
+
applyLifecycle.run(entry.status, entry.supersededBy, entry.verifiedAt, entry.id, workspace);
|
|
746
|
+
}
|
|
275
747
|
const insertLink = db.prepare(`
|
|
276
|
-
INSERT
|
|
748
|
+
INSERT INTO entry_links (from_entry_id, to_entry_id, relation, created_at, created_by)
|
|
277
749
|
VALUES (?, ?, ?, ?, ?)
|
|
278
750
|
`);
|
|
279
|
-
for (const link of
|
|
280
|
-
insertLink.run(
|
|
751
|
+
for (const link of related.links) {
|
|
752
|
+
insertLink.run(link.from, link.to, link.relation, link.createdAt, link.createdBy);
|
|
281
753
|
}
|
|
282
754
|
const insertAudit = db.prepare(`
|
|
283
|
-
INSERT
|
|
755
|
+
INSERT INTO audit_events (event_id, entry_id, workspace, operation, actor, details_json, created_at)
|
|
284
756
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
285
757
|
`);
|
|
286
|
-
for (const event of
|
|
287
|
-
insertAudit.run(
|
|
758
|
+
for (const event of related.audit) {
|
|
759
|
+
insertAudit.run(event.eventId, event.entryId, workspace, event.operation, event.actor, event.detailsJson, event.createdAt);
|
|
288
760
|
}
|
|
761
|
+
return plan;
|
|
289
762
|
});
|
|
290
|
-
return {
|
|
763
|
+
return {
|
|
764
|
+
count: entries.length,
|
|
765
|
+
imported: result.newEntries.length,
|
|
766
|
+
duplicates: result.duplicates,
|
|
767
|
+
dryRun: false,
|
|
768
|
+
workspace,
|
|
769
|
+
};
|
|
291
770
|
}
|
|
292
771
|
//# sourceMappingURL=import.js.map
|