@jungjaehoon/mama-core 2.4.1 → 4.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/README.md +76 -239
- package/db/migrations/030-case-first-memory-substrate.sql +13 -98
- package/db/migrations/034-add-connector-event-scope-columns.sql +9 -13
- package/db/migrations/039-add-connector-event-operator-ingest-seq.sql +9 -95
- package/db/migrations/062-refresh-connector-event-sequences.sql +9 -113
- package/db/migrations/063-refresh-legacy-connector-event-updates.sql +9 -32
- package/db/migrations/067-add-connector-event-source-entity-id.sql +9 -13
- package/db/migrations/069-create-registry-nodes.sql +52 -0
- package/db/migrations/070-record-identity.sql +35 -0
- package/db/migrations/071-service-operation-origins.sql +187 -0
- package/db/migrations/072-observation-versions.sql +29 -0
- package/db/migrations/073-registry-corrections.sql +45 -0
- package/db/migrations/074-work-graph-ref-kinds.sql +39 -0
- package/db/migrations/075-agent-judgments.sql +58 -0
- package/db/migrations/076-scoped-checkpoint-bindings.sql +15 -0
- package/db/migrations/077-legacy-record-kind.sql +6 -0
- package/db/migrations/078-source-ingest-commands.sql +18 -0
- package/db/migrations/079-twin-edge-relations.sql +43 -0
- package/db/migrations/080-restore-duration-days.sql +7 -0
- package/db/migrations/081-mailbox.sql +61 -0
- package/db/migrations/082-commitments-row-id.sql +12 -0
- package/db/migrations/083-observation-is-the-evidence.sql +49 -0
- package/db/migrations/084-scope-kind-is-not-the-cores-to-list.sql +34 -0
- package/db/migrations/085-a-migration-has-a-source.sql +37 -0
- package/db/migrations/086-the-mailbox-is-generic.sql +110 -0
- package/db/migrations/087-mailbox-payload.sql +5 -0
- package/db/migrations/088-native-input-journal.sql +14 -0
- package/db/migrations/089-native-turn-receipt-lookup.sql +3 -0
- package/db/migrations/090-model-run-native-inputs.sql +29 -0
- package/db/migrations/091-native-turn-results.sql +11 -0
- package/db/migrations/092-restore-decision-fts.sql +34 -0
- package/db/migrations/093-native-turn-primary-kind.sql +14 -0
- package/db/migrations/094-commitment-run-provenance.sql +9 -0
- package/db/migrations/095-model-run-native-input-identity.sql +39 -0
- package/db/migrations/096-drop-connector-event-index-fts.sql +9 -0
- package/db/migrations/097-native-input-json-validity.sql +37 -0
- package/db/migrations/098-workflow-memory-kind.sql +105 -0
- package/db/migrations/099-commitment-revision-graph.sql +2 -0
- package/dist/action-contracts.d.ts +212 -0
- package/dist/action-contracts.js +10 -0
- package/dist/api/catalog.d.ts +118 -0
- package/dist/api/catalog.js +1756 -0
- package/dist/api/dispatch.d.ts +76 -0
- package/dist/api/dispatch.js +372 -0
- package/dist/canonicalize.d.ts +2 -2
- package/dist/canonicalize.js +3 -3
- package/dist/client/client.d.ts +33 -0
- package/dist/client/client.js +123 -0
- package/dist/client/ipc.d.ts +81 -0
- package/dist/client/ipc.js +314 -0
- package/dist/db-adapter/base-adapter.d.ts +6 -66
- package/dist/db-adapter/base-adapter.js +6 -6
- package/dist/db-adapter/index.d.ts +5 -5
- package/dist/db-adapter/index.js +2 -5
- package/dist/db-adapter/node-sqlite-adapter.d.ts +85 -15
- package/dist/db-adapter/node-sqlite-adapter.js +1985 -570
- package/dist/db-manager.d.ts +53 -97
- package/dist/db-manager.js +121 -603
- package/dist/embedding/embedder.d.ts +132 -0
- package/dist/embedding/embedder.js +299 -0
- package/dist/identity/principal-repository.d.ts +2 -2
- package/dist/index.d.ts +43 -58
- package/dist/index.js +97 -145
- package/dist/{context-compile/channel-grant.d.ts → knowledge/access.d.ts} +12 -17
- package/dist/knowledge/access.js +663 -0
- package/dist/knowledge/case-errors.d.ts +31 -0
- package/dist/knowledge/case-errors.js +40 -0
- package/dist/{cases/search-rollup.d.ts → knowledge/case-search-rollup.d.ts} +5 -4
- package/dist/{cases/search-rollup.js → knowledge/case-search-rollup.js} +6 -8
- package/dist/{cases/store.d.ts → knowledge/case-store.d.ts} +3 -6
- package/dist/{cases/store.js → knowledge/case-store.js} +7 -42
- package/dist/{cases/timeline-range.d.ts → knowledge/case-timeline-range.d.ts} +3 -3
- package/dist/{cases/timeline-range.js → knowledge/case-timeline-range.js} +47 -25
- package/dist/{cases/types.d.ts → knowledge/case-types.d.ts} +8 -2
- package/dist/{cases/types.js → knowledge/case-types.js} +1 -1
- package/dist/knowledge/commitment-revision-migration.d.ts +4 -0
- package/dist/knowledge/commitment-revision-migration.js +50 -0
- package/dist/knowledge/commitments.d.ts +192 -0
- package/dist/knowledge/commitments.js +386 -0
- package/dist/knowledge/decision-edges.d.ts +70 -0
- package/dist/knowledge/decision-edges.js +123 -0
- package/dist/{agent-graph/types.d.ts → knowledge/graph-query.d.ts} +80 -70
- package/dist/knowledge/graph-query.js +1585 -0
- package/dist/knowledge/identity.d.ts +16 -0
- package/dist/knowledge/identity.js +22 -0
- package/dist/knowledge/index.d.ts +47 -0
- package/dist/knowledge/index.js +90 -0
- package/dist/knowledge/judgment-edge.d.ts +6 -0
- package/dist/knowledge/judgment-edge.js +22 -0
- package/dist/knowledge/judgments.d.ts +118 -0
- package/dist/knowledge/judgments.js +802 -0
- package/dist/knowledge/observations.d.ts +160 -0
- package/dist/knowledge/observations.js +449 -0
- package/dist/knowledge/question-type.d.ts +34 -0
- package/dist/knowledge/question-type.js +70 -0
- package/dist/{search → knowledge}/ranker-features.js +1 -1
- package/dist/{search → knowledge}/search-quality.d.ts +0 -1
- package/dist/knowledge/search.d.ts +38 -0
- package/dist/knowledge/search.js +91 -0
- package/dist/knowledge/source-ingest.d.ts +65 -0
- package/dist/knowledge/source-ingest.js +174 -0
- package/dist/{edges/types.d.ts → knowledge/twin-edge-types.d.ts} +31 -4
- package/dist/{edges/types.js → knowledge/twin-edge-types.js} +13 -2
- package/dist/knowledge/work-dates.d.ts +49 -0
- package/dist/knowledge/work-dates.js +120 -0
- package/dist/mama-api.d.ts +114 -778
- package/dist/mama-api.js +313 -2461
- package/dist/memory/api.d.ts +264 -24
- package/dist/memory/api.js +1633 -843
- package/dist/memory/bootstrap-builder.d.ts +2 -1
- package/dist/memory/bootstrap-builder.js +5 -5
- package/dist/memory/channel-summary-state-store.d.ts +3 -2
- package/dist/memory/channel-summary-state-store.js +53 -51
- package/dist/memory/channel-summary-store.d.ts +5 -3
- package/dist/memory/channel-summary-store.js +3 -8
- package/dist/memory/dashboard-read.d.ts +46 -0
- package/dist/memory/dashboard-read.js +94 -0
- package/dist/memory/event-store.d.ts +3 -3
- package/dist/memory/event-store.js +3 -10
- package/dist/memory/finding-store.d.ts +3 -2
- package/dist/memory/finding-store.js +2 -7
- package/dist/memory/graph-read.d.ts +52 -0
- package/dist/memory/graph-read.js +130 -0
- package/dist/memory/judgment-types.d.ts +394 -0
- package/dist/memory/judgment-types.js +16 -0
- package/dist/memory/provenance-audit.d.ts +5 -2
- package/dist/memory/provenance-audit.js +8 -14
- package/dist/memory/provenance-live.d.ts +141 -0
- package/dist/memory/provenance-live.js +409 -0
- package/dist/memory/provenance-query.d.ts +5 -4
- package/dist/memory/provenance-query.js +21 -29
- package/dist/memory/provenance-resolver.d.ts +191 -0
- package/dist/memory/provenance-resolver.js +155 -0
- package/dist/memory/provenance.d.ts +1 -11
- package/dist/memory/provenance.js +2 -33
- package/dist/memory/recall-sanitize.d.ts +55 -0
- package/dist/memory/recall-sanitize.js +144 -0
- package/dist/memory/secret-filter.d.ts +32 -0
- package/dist/memory/secret-filter.js +126 -0
- package/dist/memory/truth-store.d.ts +2 -1
- package/dist/memory/truth-store.js +1 -4
- package/dist/memory/types.d.ts +74 -24
- package/dist/memory/types.js +75 -5
- package/dist/memory/write-adapters.d.ts +98 -0
- package/dist/memory/write-adapters.js +242 -0
- package/dist/operations/owner-action-effects.d.ts +53 -0
- package/dist/operations/owner-action-effects.js +96 -0
- package/dist/provenance/source-ref.d.ts +1 -1
- package/dist/provenance/source-ref.js +0 -2
- package/dist/registry/corrections.d.ts +31 -0
- package/dist/registry/corrections.js +332 -0
- package/dist/registry/record-identity.d.ts +49 -0
- package/dist/registry/record-identity.js +133 -0
- package/dist/registry/store.d.ts +101 -0
- package/dist/registry/store.js +395 -0
- package/dist/registry/types.d.ts +28 -0
- package/dist/registry/types.js +5 -0
- package/dist/relevance-scorer.js +2 -2
- package/dist/runtime/agent-event-bus.d.ts +50 -0
- package/dist/runtime/agent-event-bus.js +101 -0
- package/dist/runtime/drivers/claude-cli-wrapper.d.ts +152 -0
- package/dist/runtime/drivers/claude-cli-wrapper.js +375 -0
- package/dist/runtime/drivers/claude-effort.d.ts +4 -0
- package/dist/runtime/drivers/claude-effort.js +16 -0
- package/dist/runtime/drivers/cli-arg-redaction.d.ts +2 -0
- package/dist/runtime/drivers/cli-arg-redaction.js +18 -0
- package/dist/runtime/drivers/cli-secret-redaction.d.ts +17 -0
- package/dist/runtime/drivers/cli-secret-redaction.js +126 -0
- package/dist/runtime/drivers/codex-app-server-process.d.ts +277 -0
- package/dist/runtime/drivers/codex-app-server-process.js +2342 -0
- package/dist/runtime/drivers/codex-auxiliary-tools.d.ts +17 -0
- package/dist/runtime/drivers/codex-auxiliary-tools.js +267 -0
- package/dist/runtime/drivers/codex-home.d.ts +20 -0
- package/dist/runtime/drivers/codex-home.js +674 -0
- package/dist/runtime/drivers/codex-thread-registry.d.ts +63 -0
- package/dist/runtime/drivers/codex-thread-registry.js +433 -0
- package/dist/runtime/drivers/persistent-cli-adapter.d.ts +160 -0
- package/dist/runtime/drivers/persistent-cli-adapter.js +396 -0
- package/dist/runtime/drivers/persistent-cli-process.d.ts +425 -0
- package/dist/runtime/drivers/persistent-cli-process.js +1529 -0
- package/dist/runtime/drivers/types.d.ts +456 -0
- package/dist/runtime/drivers/types.js +110 -0
- package/dist/runtime/mailbox.d.ts +223 -0
- package/dist/runtime/mailbox.js +582 -0
- package/dist/runtime/model-run-store.d.ts +28 -0
- package/dist/{model-runs/store.js → runtime/model-run-store.js} +81 -41
- package/dist/{model-runs/types.d.ts → runtime/model-run-types.d.ts} +33 -4
- package/dist/{model-runs/types.js → runtime/model-run-types.js} +1 -1
- package/dist/runtime/native-effect-observer.d.ts +22 -0
- package/dist/runtime/native-effect-observer.js +103 -0
- package/dist/runtime/native-input-journal.d.ts +46 -0
- package/dist/runtime/native-input-journal.js +308 -0
- package/dist/runtime/native-prompt.d.ts +118 -0
- package/dist/runtime/native-prompt.js +496 -0
- package/dist/runtime/native-session.d.ts +10 -0
- package/dist/{agent-graph/index.js → runtime/native-session.js} +9 -6
- package/dist/runtime/native-tool-trace-observer.d.ts +5 -0
- package/dist/runtime/native-tool-trace-observer.js +65 -0
- package/dist/runtime/native-turn.d.ts +272 -0
- package/dist/runtime/native-turn.js +1041 -0
- package/dist/runtime/operations.d.ts +236 -0
- package/dist/runtime/operations.js +565 -0
- package/dist/runtime/post-tool-handler.d.ts +74 -0
- package/dist/runtime/post-tool-handler.js +137 -0
- package/dist/runtime/prompt-layers.d.ts +126 -0
- package/dist/runtime/prompt-layers.js +239 -0
- package/dist/runtime/runtime-process.d.ts +102 -0
- package/dist/runtime/runtime-process.js +206 -0
- package/dist/runtime/runtime.d.ts +278 -0
- package/dist/runtime/runtime.js +563 -0
- package/dist/runtime/session-pool.d.ts +176 -0
- package/dist/runtime/session-pool.js +369 -0
- package/dist/runtime/stimulus-payload.d.ts +5 -0
- package/dist/runtime/stimulus-payload.js +50 -0
- package/dist/runtime/subagent-bridge.d.ts +24 -0
- package/dist/{agent-graph/types.js → runtime/subagent-bridge.js} +1 -1
- package/dist/runtime/text-completion.d.ts +22 -0
- package/dist/{agent-situation/types.js → runtime/text-completion.js} +1 -1
- package/dist/runtime/token-estimator.d.ts +25 -0
- package/dist/runtime/token-estimator.js +75 -0
- package/dist/runtime/tool-trace-store.d.ts +22 -0
- package/dist/{model-runs → runtime}/tool-trace-store.js +46 -27
- package/dist/runtime/trace-summary.d.ts +5 -0
- package/dist/runtime/trace-summary.js +74 -0
- package/dist/runtime/turn-text.d.ts +23 -0
- package/dist/runtime/turn-text.js +41 -0
- package/dist/storage/database.d.ts +67 -0
- package/dist/storage/database.js +158 -0
- package/package.json +20 -34
- package/dist/agent-graph/alias-write.d.ts +0 -3
- package/dist/agent-graph/alias-write.js +0 -234
- package/dist/agent-graph/entity-resolve.d.ts +0 -3
- package/dist/agent-graph/entity-resolve.js +0 -271
- package/dist/agent-graph/errors.d.ts +0 -4
- package/dist/agent-graph/errors.js +0 -11
- package/dist/agent-graph/graph-query.d.ts +0 -5
- package/dist/agent-graph/graph-query.js +0 -360
- package/dist/agent-graph/index.d.ts +0 -6
- package/dist/agent-situation/builder.d.ts +0 -8
- package/dist/agent-situation/builder.js +0 -239
- package/dist/agent-situation/cache-key.d.ts +0 -4
- package/dist/agent-situation/cache-key.js +0 -64
- package/dist/agent-situation/index.d.ts +0 -7
- package/dist/agent-situation/index.js +0 -28
- package/dist/agent-situation/packet-store.d.ts +0 -27
- package/dist/agent-situation/packet-store.js +0 -228
- package/dist/agent-situation/ranking-policy.d.ts +0 -18
- package/dist/agent-situation/ranking-policy.js +0 -75
- package/dist/agent-situation/source-readers.d.ts +0 -52
- package/dist/agent-situation/source-readers.js +0 -300
- package/dist/agent-situation/types.d.ts +0 -195
- package/dist/cases/wiki-page-index.d.ts +0 -37
- package/dist/cases/wiki-page-index.js +0 -201
- package/dist/config-loader.d.ts +0 -68
- package/dist/config-loader.js +0 -239
- package/dist/connectors/event-index.d.ts +0 -24
- package/dist/connectors/event-index.js +0 -245
- package/dist/connectors/raw-query.d.ts +0 -40
- package/dist/connectors/raw-query.js +0 -412
- package/dist/connectors/types.d.ts +0 -117
- package/dist/connectors/types.js +0 -3
- package/dist/context-compile/boundary-defaults.d.ts +0 -12
- package/dist/context-compile/boundary-defaults.js +0 -105
- package/dist/context-compile/channel-grant.js +0 -66
- package/dist/context-compile/compiler-policy.d.ts +0 -39
- package/dist/context-compile/compiler-policy.js +0 -147
- package/dist/context-compile/compiler.d.ts +0 -18
- package/dist/context-compile/compiler.js +0 -404
- package/dist/context-compile/index.d.ts +0 -10
- package/dist/context-compile/index.js +0 -26
- package/dist/context-compile/packet-store.d.ts +0 -8
- package/dist/context-compile/packet-store.js +0 -239
- package/dist/context-compile/ref.d.ts +0 -7
- package/dist/context-compile/ref.js +0 -102
- package/dist/context-compile/source-readers.d.ts +0 -70
- package/dist/context-compile/source-readers.js +0 -840
- package/dist/context-compile/types.d.ts +0 -155
- package/dist/context-compile/types.js +0 -11
- package/dist/context-compile/visibility.d.ts +0 -21
- package/dist/context-compile/visibility.js +0 -224
- package/dist/decision-tracker.d.ts +0 -206
- package/dist/decision-tracker.js +0 -481
- package/dist/edges/ref-validation.d.ts +0 -7
- package/dist/edges/ref-validation.js +0 -321
- package/dist/edges/store.d.ts +0 -8
- package/dist/edges/store.js +0 -129
- package/dist/embedding-client.d.ts +0 -28
- package/dist/embedding-client.js +0 -53
- package/dist/embedding-server/index.d.ts +0 -67
- package/dist/embedding-server/index.js +0 -405
- package/dist/embedding-server/mobile/auth.d.ts +0 -44
- package/dist/embedding-server/mobile/auth.js +0 -150
- package/dist/embedding-server/mobile/daemon.d.ts +0 -129
- package/dist/embedding-server/mobile/daemon.js +0 -313
- package/dist/embedding-server/mobile/output-parser.d.ts +0 -115
- package/dist/embedding-server/mobile/output-parser.js +0 -241
- package/dist/embedding-server/mobile/session-api.d.ts +0 -57
- package/dist/embedding-server/mobile/session-api.js +0 -261
- package/dist/embedding-server/mobile/session-manager.d.ts +0 -138
- package/dist/embedding-server/mobile/session-manager.js +0 -378
- package/dist/embedding-server/mobile/websocket-handler.d.ts +0 -137
- package/dist/embedding-server/mobile/websocket-handler.js +0 -486
- package/dist/embeddings.d.ts +0 -68
- package/dist/embeddings.js +0 -244
- package/dist/entities/audit-metrics.d.ts +0 -68
- package/dist/entities/audit-metrics.js +0 -124
- package/dist/entities/candidate-generator.d.ts +0 -8
- package/dist/entities/candidate-generator.js +0 -24
- package/dist/entities/entity-linked-decision-counts.d.ts +0 -2
- package/dist/entities/entity-linked-decision-counts.js +0 -29
- package/dist/entities/entity-list.d.ts +0 -22
- package/dist/entities/entity-list.js +0 -3
- package/dist/entities/entity-orphan-list.d.ts +0 -17
- package/dist/entities/entity-orphan-list.js +0 -3
- package/dist/entities/entity-search.d.ts +0 -20
- package/dist/entities/entity-search.js +0 -3
- package/dist/entities/errors.d.ts +0 -55
- package/dist/entities/errors.js +0 -100
- package/dist/entities/lineage-store.d.ts +0 -28
- package/dist/entities/lineage-store.js +0 -204
- package/dist/entities/normalization.d.ts +0 -14
- package/dist/entities/normalization.js +0 -63
- package/dist/entities/policy-store.d.ts +0 -8
- package/dist/entities/policy-store.js +0 -75
- package/dist/entities/policy-types.d.ts +0 -72
- package/dist/entities/policy-types.js +0 -59
- package/dist/entities/projection.d.ts +0 -8
- package/dist/entities/projection.js +0 -82
- package/dist/entities/provenance-query.d.ts +0 -32
- package/dist/entities/provenance-query.js +0 -3
- package/dist/entities/read-identity.d.ts +0 -31
- package/dist/entities/read-identity.js +0 -264
- package/dist/entities/recall-bridge.d.ts +0 -5
- package/dist/entities/recall-bridge.js +0 -171
- package/dist/entities/resolution-engine.d.ts +0 -8
- package/dist/entities/resolution-engine.js +0 -3
- package/dist/entities/rollback-preview.d.ts +0 -35
- package/dist/entities/rollback-preview.js +0 -3
- package/dist/entities/source-locator.d.ts +0 -3
- package/dist/entities/source-locator.js +0 -32
- package/dist/entities/store.d.ts +0 -69
- package/dist/entities/store.js +0 -544
- package/dist/entities/types.d.ts +0 -166
- package/dist/entities/types.js +0 -30
- package/dist/memory/extraction-prompt.d.ts +0 -4
- package/dist/memory/extraction-prompt.js +0 -89
- package/dist/memory/scope-backfill.d.ts +0 -14
- package/dist/memory/scope-backfill.js +0 -3
- package/dist/memory/scope-store.d.ts +0 -4
- package/dist/memory/scope-store.js +0 -9
- package/dist/memory-store.d.ts +0 -87
- package/dist/memory-store.js +0 -78
- package/dist/model-runs/store.d.ts +0 -13
- package/dist/model-runs/tool-trace-store.d.ts +0 -8
- package/dist/notification-manager.d.ts +0 -7
- package/dist/notification-manager.js +0 -12
- package/dist/outcome-tracker.d.ts +0 -151
- package/dist/outcome-tracker.js +0 -271
- package/dist/query-intent.d.ts +0 -23
- package/dist/query-intent.js +0 -114
- package/dist/runtime/trusted-provenance.d.ts +0 -3
- package/dist/runtime/trusted-provenance.js +0 -6
- package/dist/search/question-type.d.ts +0 -5
- package/dist/search/question-type.js +0 -41
- package/dist/test-utils.d.ts +0 -80
- package/dist/test-utils.js +0 -218
- package/dist/tier-validator.d.ts +0 -43
- package/dist/tier-validator.js +0 -159
- package/dist/time-formatter.d.ts +0 -25
- package/dist/time-formatter.js +0 -93
- /package/dist/{search → knowledge}/feedback-store.d.ts +0 -0
- /package/dist/{search → knowledge}/feedback-store.js +0 -0
- /package/dist/{search → knowledge}/ranker-features.d.ts +0 -0
- /package/dist/{search → knowledge}/ranker-rescore.d.ts +0 -0
- /package/dist/{search → knowledge}/ranker-rescore.js +0 -0
- /package/dist/{search → knowledge}/ranker-trainer.d.ts +0 -0
- /package/dist/{search → knowledge}/ranker-trainer.js +0 -0
- /package/dist/{search → knowledge}/search-quality.js +0 -0
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.appendIdentityCorrection = appendIdentityCorrection;
|
|
4
|
+
exports.readIdentityAssignments = readIdentityAssignments;
|
|
5
|
+
exports.newCorrectionCommandId = newCorrectionCommandId;
|
|
6
|
+
const node_crypto_1 = require("node:crypto");
|
|
7
|
+
const observations_js_1 = require("../knowledge/observations.js");
|
|
8
|
+
const access_js_1 = require("../knowledge/access.js");
|
|
9
|
+
const store_js_1 = require("./store.js");
|
|
10
|
+
function stable(value) {
|
|
11
|
+
if (Array.isArray(value)) {
|
|
12
|
+
return value.map(stable);
|
|
13
|
+
}
|
|
14
|
+
if (value && typeof value === 'object') {
|
|
15
|
+
return Object.fromEntries(Object.entries(value)
|
|
16
|
+
.sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0))
|
|
17
|
+
.map(([key, item]) => [key, stable(item)]));
|
|
18
|
+
}
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
function canonicalJson(value) {
|
|
22
|
+
return JSON.stringify(stable(value));
|
|
23
|
+
}
|
|
24
|
+
function scopeKey(scope) {
|
|
25
|
+
return `${scope.kind}\0${scope.id}`;
|
|
26
|
+
}
|
|
27
|
+
function requireTrustedContext(trusted) {
|
|
28
|
+
if (!trusted.principalId.trim()) {
|
|
29
|
+
throw new store_js_1.RegistryError('invalid_principal', 'A trusted principal is required');
|
|
30
|
+
}
|
|
31
|
+
if (!trusted.agentId.trim()) {
|
|
32
|
+
throw new store_js_1.RegistryError('invalid_agent', 'A trusted agent is required');
|
|
33
|
+
}
|
|
34
|
+
if (trusted.scopes.length === 0) {
|
|
35
|
+
throw new store_js_1.RegistryError('invalid_scope', 'At least one signed scope is required');
|
|
36
|
+
}
|
|
37
|
+
for (const scope of trusted.scopes) {
|
|
38
|
+
if (!scope.id.trim()) {
|
|
39
|
+
throw new store_js_1.RegistryError('invalid_scope', 'Signed scope ids must be nonblank');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function effectiveScopes(correction, trusted) {
|
|
44
|
+
requireTrustedContext(trusted);
|
|
45
|
+
const requested = correction.scopes ?? trusted.scopes;
|
|
46
|
+
if (requested.length === 0) {
|
|
47
|
+
throw new store_js_1.RegistryError('invalid_scope', 'At least one effective scope is required');
|
|
48
|
+
}
|
|
49
|
+
const allowed = new Set(trusted.scopes.map(scopeKey));
|
|
50
|
+
for (const scope of requested) {
|
|
51
|
+
if (!scope.id.trim() || !allowed.has(scopeKey(scope))) {
|
|
52
|
+
throw new store_js_1.RegistryError('scope_denied', 'Requested registry scope is unavailable');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return [...new Map(requested.map((scope) => [scopeKey(scope), scope])).values()];
|
|
56
|
+
}
|
|
57
|
+
function genericTargetUnavailable() {
|
|
58
|
+
throw new store_js_1.RegistryError('registry_target_unavailable', 'Registry target is unavailable');
|
|
59
|
+
}
|
|
60
|
+
function nodeBindings(db, nodeId) {
|
|
61
|
+
return db
|
|
62
|
+
.prepare('SELECT scope_kind AS kind, scope_id AS id FROM registry_scope_bindings WHERE node_id = ?')
|
|
63
|
+
.all(nodeId);
|
|
64
|
+
}
|
|
65
|
+
function requireVisibleNode(db, nodeId, scopes) {
|
|
66
|
+
const node = (0, store_js_1.resolveNodeById)(db, nodeId);
|
|
67
|
+
if (!node) {
|
|
68
|
+
genericTargetUnavailable();
|
|
69
|
+
}
|
|
70
|
+
const allowed = new Set(scopes.map(scopeKey));
|
|
71
|
+
if (!nodeBindings(db, nodeId).some((scope) => allowed.has(scopeKey(scope)))) {
|
|
72
|
+
genericTargetUnavailable();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function requireFullNodeAuthority(db, nodeId, scopes) {
|
|
76
|
+
requireVisibleNode(db, nodeId, scopes);
|
|
77
|
+
const allowed = new Set(scopes.map(scopeKey));
|
|
78
|
+
const bindings = nodeBindings(db, nodeId);
|
|
79
|
+
if (bindings.length === 0 || bindings.some((scope) => !allowed.has(scopeKey(scope)))) {
|
|
80
|
+
genericTargetUnavailable();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function requireVisibleEdge(db, edgeId, trusted, scopes) {
|
|
84
|
+
try {
|
|
85
|
+
(0, access_js_1.assertTwinRefsVisible)(db, [{ kind: 'edge', id: edgeId }], {
|
|
86
|
+
scopes: [...scopes],
|
|
87
|
+
connectors: [...trusted.connectors],
|
|
88
|
+
principalId: trusted.principalId,
|
|
89
|
+
agentId: trusted.agentId,
|
|
90
|
+
...(trusted.channels ? { channels: trusted.channels } : {}),
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
if (error instanceof access_js_1.TwinRefNotVisibleError) {
|
|
95
|
+
genericTargetUnavailable();
|
|
96
|
+
}
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function edgeSlot(db, edgeId, endpoint) {
|
|
101
|
+
const row = db
|
|
102
|
+
.prepare('SELECT subject_kind, subject_id, object_kind, object_id FROM twin_edges WHERE edge_id = ?')
|
|
103
|
+
.get(edgeId);
|
|
104
|
+
if (!row) {
|
|
105
|
+
genericTargetUnavailable();
|
|
106
|
+
}
|
|
107
|
+
return endpoint === 'from'
|
|
108
|
+
? { kind: row.subject_kind, id: row.subject_id }
|
|
109
|
+
: { kind: row.object_kind, id: row.object_id };
|
|
110
|
+
}
|
|
111
|
+
function validateEvidence(db, correction, scopes, trusted) {
|
|
112
|
+
if ((correction.evidence?.length ?? 0) > 0) {
|
|
113
|
+
const table = db
|
|
114
|
+
.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'observation_versions'")
|
|
115
|
+
.get();
|
|
116
|
+
if (!table) {
|
|
117
|
+
throw new store_js_1.RegistryError('missing_observation_schema', 'observation_versions is required to validate supplied evidence');
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
for (const ref of correction.evidence ?? []) {
|
|
121
|
+
if (ref.kind !== 'observation' ||
|
|
122
|
+
!(0, observations_js_1.isObservationVersionVisible)(db, ref.id, {
|
|
123
|
+
principalId: trusted.principalId,
|
|
124
|
+
agentId: trusted.agentId,
|
|
125
|
+
scopes,
|
|
126
|
+
connectors: trusted.connectors,
|
|
127
|
+
channels: trusted.channels,
|
|
128
|
+
})) {
|
|
129
|
+
throw new store_js_1.RegistryError('correction_evidence_unavailable', 'Correction evidence is unavailable');
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function validateAccess(db, correction, scopes, trusted) {
|
|
134
|
+
if (correction.operation === 'add_alias') {
|
|
135
|
+
requireVisibleNode(db, correction.nodeId, scopes);
|
|
136
|
+
}
|
|
137
|
+
if (correction.operation === 'merge') {
|
|
138
|
+
requireFullNodeAuthority(db, correction.survivorId, scopes);
|
|
139
|
+
for (const memberId of correction.memberIds) {
|
|
140
|
+
requireFullNodeAuthority(db, memberId, scopes);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (correction.operation === 'split' || correction.operation === 'assign_refs') {
|
|
144
|
+
requireFullNodeAuthority(db, correction.parentId, scopes);
|
|
145
|
+
for (const assignment of correction.assignments) {
|
|
146
|
+
requireVisibleEdge(db, assignment.edgeId, trusted, scopes);
|
|
147
|
+
if (assignment.targetNodeId !== undefined && assignment.targetNodeId !== null) {
|
|
148
|
+
requireVisibleNode(db, assignment.targetNodeId, scopes);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
validateEvidence(db, correction, scopes, trusted);
|
|
153
|
+
}
|
|
154
|
+
function parseScopes(value) {
|
|
155
|
+
if (typeof value !== 'string') {
|
|
156
|
+
throw new Error('registry_corrections.scope_json must be JSON text');
|
|
157
|
+
}
|
|
158
|
+
const parsed = JSON.parse(value);
|
|
159
|
+
if (!Array.isArray(parsed) ||
|
|
160
|
+
parsed.some((scope) => !scope ||
|
|
161
|
+
typeof scope !== 'object' ||
|
|
162
|
+
typeof scope.kind !== 'string' ||
|
|
163
|
+
typeof scope.id !== 'string')) {
|
|
164
|
+
throw new Error('registry_corrections.scope_json must contain scope references');
|
|
165
|
+
}
|
|
166
|
+
return parsed;
|
|
167
|
+
}
|
|
168
|
+
function validateCommon(correction) {
|
|
169
|
+
if (!correction.commandId.trim()) {
|
|
170
|
+
throw new store_js_1.RegistryError('invalid_command', 'Correction command id must be nonblank');
|
|
171
|
+
}
|
|
172
|
+
if (!Number.isSafeInteger(correction.expectedRevision) || correction.expectedRevision < 0) {
|
|
173
|
+
throw new store_js_1.RegistryError('invalid_revision', 'Correction revision must be a nonnegative integer');
|
|
174
|
+
}
|
|
175
|
+
if (!correction.reason.trim()) {
|
|
176
|
+
throw new store_js_1.RegistryError('missing_reason', 'A correction reason is required');
|
|
177
|
+
}
|
|
178
|
+
if (correction.operation === 'split' || correction.operation === 'assign_refs') {
|
|
179
|
+
const slots = correction.assignments.map((assignment) => `${assignment.edgeId}\0${assignment.endpoint}`);
|
|
180
|
+
if (new Set(slots).size !== slots.length) {
|
|
181
|
+
throw new store_js_1.RegistryError('invalid_assignment', 'A correction cannot assign the same edge endpoint more than once');
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if (correction.operation === 'merge' && correction.memberIds.length === 0) {
|
|
185
|
+
throw new store_js_1.RegistryError('invalid_merge', 'A merge requires at least one member');
|
|
186
|
+
}
|
|
187
|
+
if (correction.operation === 'split') {
|
|
188
|
+
if (correction.children.length < 2) {
|
|
189
|
+
throw new store_js_1.RegistryError('split_too_small', 'A split requires at least two children');
|
|
190
|
+
}
|
|
191
|
+
const keys = correction.children.map((child, index) => child.clientKey ?? String(index));
|
|
192
|
+
if (keys.some((key) => !key.trim()) || new Set(keys).size !== keys.length) {
|
|
193
|
+
throw new store_js_1.RegistryError('invalid_client_key', 'Split child client keys must be unique');
|
|
194
|
+
}
|
|
195
|
+
const knownKeys = new Set(keys);
|
|
196
|
+
for (const assignment of correction.assignments) {
|
|
197
|
+
if (assignment.targetClientKey !== undefined && !knownKeys.has(assignment.targetClientKey)) {
|
|
198
|
+
throw new store_js_1.RegistryError('invalid_client_key', 'Split assignment client key is unknown');
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (correction.operation === 'assign_refs' &&
|
|
203
|
+
correction.assignments.some((assignment) => assignment.targetClientKey !== undefined)) {
|
|
204
|
+
throw new store_js_1.RegistryError('invalid_client_key', 'Client keys are valid only during split');
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
function appendIdentityCorrection(adapter, correction, trusted) {
|
|
208
|
+
validateCommon(correction);
|
|
209
|
+
const db = adapter;
|
|
210
|
+
const requestedScopes = effectiveScopes(correction, trusted);
|
|
211
|
+
const payloadJson = canonicalJson(correction);
|
|
212
|
+
return db.transaction(() => {
|
|
213
|
+
const replay = db
|
|
214
|
+
.prepare(`SELECT principal_id, agent_id, payload_json, scope_json, receipt_json
|
|
215
|
+
FROM registry_corrections WHERE command_id = ?`)
|
|
216
|
+
.get(correction.commandId);
|
|
217
|
+
if (replay) {
|
|
218
|
+
if (replay.principal_id !== trusted.principalId ||
|
|
219
|
+
replay.agent_id !== trusted.agentId ||
|
|
220
|
+
replay.payload_json !== payloadJson ||
|
|
221
|
+
replay.scope_json !== canonicalJson(requestedScopes)) {
|
|
222
|
+
throw new store_js_1.RegistryError('COMMAND_CONFLICT', 'Correction command has different authority or payload');
|
|
223
|
+
}
|
|
224
|
+
const persistedScopes = parseScopes(replay.scope_json);
|
|
225
|
+
validateAccess(db, correction, persistedScopes, trusted);
|
|
226
|
+
return JSON.parse(replay.receipt_json);
|
|
227
|
+
}
|
|
228
|
+
const current = (0, store_js_1.currentIdentityRevision)(db);
|
|
229
|
+
if (current !== correction.expectedRevision) {
|
|
230
|
+
throw new store_js_1.RegistryError('REVISION_CONFLICT', `Expected identity revision ${correction.expectedRevision}, current revision is ${current}`);
|
|
231
|
+
}
|
|
232
|
+
validateAccess(db, correction, requestedScopes, trusted);
|
|
233
|
+
const committedRevision = current + 1;
|
|
234
|
+
const now = Date.now();
|
|
235
|
+
const children = [];
|
|
236
|
+
if (correction.operation === 'add_alias') {
|
|
237
|
+
(0, store_js_1.addAliases)(db, correction.nodeId, [correction.alias], requestedScopes);
|
|
238
|
+
}
|
|
239
|
+
else if (correction.operation === 'merge') {
|
|
240
|
+
for (const memberId of correction.memberIds) {
|
|
241
|
+
(0, store_js_1.mergeNodes)(db, {
|
|
242
|
+
loser: memberId,
|
|
243
|
+
survivor: correction.survivorId,
|
|
244
|
+
reason: correction.reason,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
else if (correction.operation === 'split') {
|
|
249
|
+
for (const [index, child] of correction.children.entries()) {
|
|
250
|
+
const childId = (0, store_js_1.createNode)(db, {
|
|
251
|
+
kind: (0, store_js_1.resolveNodeById)(db, correction.parentId)?.kind ?? '',
|
|
252
|
+
name: child.name,
|
|
253
|
+
aliases: child.aliases,
|
|
254
|
+
parentId: correction.parentId,
|
|
255
|
+
note: correction.reason,
|
|
256
|
+
scopes: requestedScopes,
|
|
257
|
+
});
|
|
258
|
+
children.push({
|
|
259
|
+
clientKey: child.clientKey ?? String(index),
|
|
260
|
+
ref: { kind: 'registry', id: childId },
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
const childIds = new Map(children.map((child) => [child.clientKey, child.ref.id]));
|
|
265
|
+
const resolvedAssignments = correction.operation === 'split' || correction.operation === 'assign_refs'
|
|
266
|
+
? correction.assignments.map((assignment) => ({
|
|
267
|
+
edgeId: assignment.edgeId,
|
|
268
|
+
endpoint: assignment.endpoint,
|
|
269
|
+
targetNodeId: assignment.targetClientKey !== undefined
|
|
270
|
+
? (childIds.get(assignment.targetClientKey) ?? genericTargetUnavailable())
|
|
271
|
+
: assignment.targetNodeId === undefined
|
|
272
|
+
? genericTargetUnavailable()
|
|
273
|
+
: assignment.targetNodeId,
|
|
274
|
+
}))
|
|
275
|
+
: [];
|
|
276
|
+
const receipt = {
|
|
277
|
+
commandId: correction.commandId,
|
|
278
|
+
identityRevision: committedRevision,
|
|
279
|
+
children,
|
|
280
|
+
changedSlots: correction.operation === 'split' || correction.operation === 'assign_refs'
|
|
281
|
+
? resolvedAssignments.map(({ edgeId, endpoint }) => ({ edgeId, endpoint }))
|
|
282
|
+
: [],
|
|
283
|
+
unresolved: correction.operation === 'split' || correction.operation === 'assign_refs'
|
|
284
|
+
? resolvedAssignments
|
|
285
|
+
.filter((assignment) => assignment.targetNodeId === null)
|
|
286
|
+
.map(({ edgeId, endpoint }) => ({ edgeId, endpoint }))
|
|
287
|
+
: [],
|
|
288
|
+
};
|
|
289
|
+
db.prepare(`INSERT INTO registry_corrections
|
|
290
|
+
(command_id, operation, expected_revision, committed_revision, principal_id, agent_id,
|
|
291
|
+
origin, payload_json, reason, evidence_json, scope_json, receipt_json, created_at)
|
|
292
|
+
VALUES (?, ?, ?, ?, ?, ?, 'trusted', ?, ?, ?, ?, ?, ?)`).run(correction.commandId, correction.operation, correction.expectedRevision, committedRevision, trusted.principalId, trusted.agentId, payloadJson, correction.reason.trim(), canonicalJson(correction.evidence ?? []), canonicalJson(requestedScopes), canonicalJson(receipt), now);
|
|
293
|
+
if (correction.operation === 'split' || correction.operation === 'assign_refs') {
|
|
294
|
+
for (const assignment of resolvedAssignments) {
|
|
295
|
+
const original = edgeSlot(db, assignment.edgeId, assignment.endpoint);
|
|
296
|
+
db.prepare(`INSERT INTO registry_ref_assignments
|
|
297
|
+
(command_id, edge_id, endpoint, original_kind, original_id, resolved_node_id,
|
|
298
|
+
committed_revision, created_at)
|
|
299
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(correction.commandId, assignment.edgeId, assignment.endpoint, original.kind, original.id, assignment.targetNodeId, committedRevision, now);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
db.prepare('UPDATE registry_identity_state SET revision = ? WHERE singleton = 1').run(committedRevision);
|
|
303
|
+
return receipt;
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
function readIdentityAssignments(adapter, edgeId) {
|
|
307
|
+
const rows = adapter
|
|
308
|
+
.prepare(`SELECT command_id, edge_id, endpoint, original_kind, original_id, resolved_node_id,
|
|
309
|
+
committed_revision
|
|
310
|
+
FROM registry_ref_assignments
|
|
311
|
+
WHERE edge_id = ?
|
|
312
|
+
AND committed_revision = (
|
|
313
|
+
SELECT MAX(current_assignment.committed_revision)
|
|
314
|
+
FROM registry_ref_assignments current_assignment
|
|
315
|
+
WHERE current_assignment.edge_id = registry_ref_assignments.edge_id
|
|
316
|
+
AND current_assignment.endpoint = registry_ref_assignments.endpoint
|
|
317
|
+
)
|
|
318
|
+
ORDER BY endpoint`)
|
|
319
|
+
.all(edgeId);
|
|
320
|
+
return rows.map((row) => ({
|
|
321
|
+
edgeId: row.edge_id,
|
|
322
|
+
endpoint: row.endpoint,
|
|
323
|
+
originalRef: { kind: row.original_kind, id: row.original_id },
|
|
324
|
+
resolvedRef: row.resolved_node_id === null ? null : { kind: 'registry', id: row.resolved_node_id },
|
|
325
|
+
identityRevision: row.committed_revision,
|
|
326
|
+
commandId: row.command_id,
|
|
327
|
+
}));
|
|
328
|
+
}
|
|
329
|
+
function newCorrectionCommandId() {
|
|
330
|
+
return `correction_${(0, node_crypto_1.randomUUID)().replace(/-/g, '')}`;
|
|
331
|
+
}
|
|
332
|
+
//# sourceMappingURL=corrections.js.map
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Binding a record to what it is about and who was involved.
|
|
3
|
+
*
|
|
4
|
+
* The subject of a record used to be the text in `topic`, leaving alternate spellings
|
|
5
|
+
* unjoinable. Here the subject is a
|
|
6
|
+
* registry node id, and the people are rows with their roles.
|
|
7
|
+
*
|
|
8
|
+
* Everything this module refuses, it refuses loudly. An unknown node, a person id in the item
|
|
9
|
+
* slot, an empty role: all raise. A dangling reference stored quietly would surface later as a
|
|
10
|
+
* timeline that is missing history, which is the failure mode this whole design exists to remove.
|
|
11
|
+
*
|
|
12
|
+
* The binding is written only inside the transaction that appends the record it belongs to, so
|
|
13
|
+
* this module offers no separate entry point that rewrites the identity of a record already
|
|
14
|
+
* stored. Changing what a stored record is about is a correction, not an overwrite.
|
|
15
|
+
*/
|
|
16
|
+
import type { DatabaseAdapter } from '../db-manager.js';
|
|
17
|
+
import type { RecordActor, RecordIdentity, RegistryScopeRef } from './types.js';
|
|
18
|
+
export type { RecordActor, RecordIdentity };
|
|
19
|
+
export declare class RecordIdentityError extends Error {
|
|
20
|
+
readonly code: string;
|
|
21
|
+
constructor(code: string, message: string);
|
|
22
|
+
}
|
|
23
|
+
type RecordIdentityAdapter = Pick<DatabaseAdapter, 'prepare'>;
|
|
24
|
+
/** Resolve every supplied identity reference before a record write begins. */
|
|
25
|
+
export declare function validateRecordIdentityReferences(adapter: RecordIdentityAdapter, input: {
|
|
26
|
+
itemId?: string | null;
|
|
27
|
+
actors?: readonly RecordActor[];
|
|
28
|
+
scopes?: readonly RegistryScopeRef[];
|
|
29
|
+
}): void;
|
|
30
|
+
export declare function writeRecordIdentity(db: RecordIdentityAdapter, input: {
|
|
31
|
+
recordId: string;
|
|
32
|
+
itemId?: string | null;
|
|
33
|
+
actors?: readonly RecordActor[];
|
|
34
|
+
scopes?: readonly RegistryScopeRef[];
|
|
35
|
+
}): void;
|
|
36
|
+
export declare function readRecordIdentity(adapter: RecordIdentityAdapter, recordId: string): RecordIdentity | null;
|
|
37
|
+
export declare function listActors(adapter: RecordIdentityAdapter, recordId: string): RecordActor[];
|
|
38
|
+
/** Records bound to one item, newest first. The read `item_timeline` is built on. */
|
|
39
|
+
export declare function listRecordIdsForItem(adapter: RecordIdentityAdapter, itemId: string, options?: {
|
|
40
|
+
limit?: number;
|
|
41
|
+
before?: {
|
|
42
|
+
timestamp: number;
|
|
43
|
+
id: string;
|
|
44
|
+
};
|
|
45
|
+
}): Array<{
|
|
46
|
+
id: string;
|
|
47
|
+
timestamp: number;
|
|
48
|
+
}>;
|
|
49
|
+
//# sourceMappingURL=record-identity.d.ts.map
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Binding a record to what it is about and who was involved.
|
|
4
|
+
*
|
|
5
|
+
* The subject of a record used to be the text in `topic`, leaving alternate spellings
|
|
6
|
+
* unjoinable. Here the subject is a
|
|
7
|
+
* registry node id, and the people are rows with their roles.
|
|
8
|
+
*
|
|
9
|
+
* Everything this module refuses, it refuses loudly. An unknown node, a person id in the item
|
|
10
|
+
* slot, an empty role: all raise. A dangling reference stored quietly would surface later as a
|
|
11
|
+
* timeline that is missing history, which is the failure mode this whole design exists to remove.
|
|
12
|
+
*
|
|
13
|
+
* The binding is written only inside the transaction that appends the record it belongs to, so
|
|
14
|
+
* this module offers no separate entry point that rewrites the identity of a record already
|
|
15
|
+
* stored. Changing what a stored record is about is a correction, not an overwrite.
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.RecordIdentityError = void 0;
|
|
19
|
+
exports.validateRecordIdentityReferences = validateRecordIdentityReferences;
|
|
20
|
+
exports.writeRecordIdentity = writeRecordIdentity;
|
|
21
|
+
exports.readRecordIdentity = readRecordIdentity;
|
|
22
|
+
exports.listActors = listActors;
|
|
23
|
+
exports.listRecordIdsForItem = listRecordIdsForItem;
|
|
24
|
+
const store_js_1 = require("./store.js");
|
|
25
|
+
class RecordIdentityError extends Error {
|
|
26
|
+
code;
|
|
27
|
+
constructor(code, message) {
|
|
28
|
+
super(message);
|
|
29
|
+
this.code = code;
|
|
30
|
+
this.name = 'RecordIdentityError';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
exports.RecordIdentityError = RecordIdentityError;
|
|
34
|
+
/** The live node for an id, following merges. Raises when it is absent or the wrong kind. */
|
|
35
|
+
function requireNode(adapter, id, kind, slot, scopes) {
|
|
36
|
+
const node = (0, store_js_1.resolveNodeById)(adapter, id);
|
|
37
|
+
if (!node) {
|
|
38
|
+
throw new RecordIdentityError('unknown_node', `${slot} references ${id}, which is not a registry node. Register it before binding.`);
|
|
39
|
+
}
|
|
40
|
+
if (node.kind !== kind) {
|
|
41
|
+
throw new RecordIdentityError('wrong_kind', `${slot} expects a ${kind} node; ${id} is a ${node.kind}.`);
|
|
42
|
+
}
|
|
43
|
+
const bindings = adapter
|
|
44
|
+
.prepare('SELECT scope_kind, scope_id FROM registry_scope_bindings WHERE node_id = ?')
|
|
45
|
+
.all(node.id);
|
|
46
|
+
const visible = bindings.length === 0
|
|
47
|
+
? true
|
|
48
|
+
: scopes !== undefined &&
|
|
49
|
+
scopes.some((scope) => bindings.some((binding) => binding.scope_kind === scope.kind && binding.scope_id === scope.id));
|
|
50
|
+
if (!visible) {
|
|
51
|
+
throw new RecordIdentityError('hidden_node', `${slot} references a node outside save scope`);
|
|
52
|
+
}
|
|
53
|
+
return node.id;
|
|
54
|
+
}
|
|
55
|
+
/** Resolve every supplied identity reference before a record write begins. */
|
|
56
|
+
function validateRecordIdentityReferences(adapter, input) {
|
|
57
|
+
if (input.itemId !== undefined && input.itemId !== null) {
|
|
58
|
+
requireNode(adapter, input.itemId, 'item', 'item_id', input.scopes);
|
|
59
|
+
}
|
|
60
|
+
for (const actor of input.actors ?? []) {
|
|
61
|
+
if (!actor.role.trim()) {
|
|
62
|
+
throw new RecordIdentityError('empty_role', 'An actor needs a role.');
|
|
63
|
+
}
|
|
64
|
+
requireNode(adapter, actor.personId, 'person', 'actor', input.scopes);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function writeRecordIdentity(db, input) {
|
|
68
|
+
const itemId = input.itemId === undefined || input.itemId === null
|
|
69
|
+
? null
|
|
70
|
+
: requireNode(db, input.itemId, 'item', 'item_id', input.scopes);
|
|
71
|
+
const actors = (input.actors ?? []).map((actor, position) => ({
|
|
72
|
+
personId: requireNode(db, actor.personId, 'person', 'actor', input.scopes),
|
|
73
|
+
role: actor.role.trim(),
|
|
74
|
+
position,
|
|
75
|
+
}));
|
|
76
|
+
for (const actor of actors) {
|
|
77
|
+
if (!actor.role) {
|
|
78
|
+
throw new RecordIdentityError('empty_role', 'An actor needs a role.');
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
db.prepare('UPDATE decisions SET item_id = ? WHERE id = ?').run(itemId, input.recordId);
|
|
82
|
+
db.prepare('DELETE FROM record_actors WHERE record_id = ?').run(input.recordId);
|
|
83
|
+
const now = Date.now();
|
|
84
|
+
for (const actor of actors) {
|
|
85
|
+
db.prepare(`INSERT INTO record_actors (record_id, person_id, role, position, created_at)
|
|
86
|
+
VALUES (?, ?, ?, ?, ?)`).run(input.recordId, actor.personId, actor.role, actor.position, now);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function readRecordIdentity(adapter, recordId) {
|
|
90
|
+
const row = adapter.prepare('SELECT item_id FROM decisions WHERE id = ?').get(recordId);
|
|
91
|
+
if (!row) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
// Canonicalise on read: a merge moves aliases but rewrites no consumer, so an id stored
|
|
95
|
+
// before the merge still has to answer with the surviving node.
|
|
96
|
+
const itemId = row.item_id ? ((0, store_js_1.resolveNodeById)(adapter, row.item_id)?.id ?? null) : null;
|
|
97
|
+
return { itemId };
|
|
98
|
+
}
|
|
99
|
+
function listActors(adapter, recordId) {
|
|
100
|
+
const rows = adapter
|
|
101
|
+
.prepare('SELECT person_id, role FROM record_actors WHERE record_id = ? ORDER BY position, rowid')
|
|
102
|
+
.all(recordId);
|
|
103
|
+
return rows.map((row) => ({
|
|
104
|
+
personId: (0, store_js_1.resolveNodeById)(adapter, row.person_id)?.id ?? row.person_id,
|
|
105
|
+
role: row.role,
|
|
106
|
+
}));
|
|
107
|
+
}
|
|
108
|
+
/** Records bound to one item, newest first. The read `item_timeline` is built on. */
|
|
109
|
+
function listRecordIdsForItem(adapter, itemId, options) {
|
|
110
|
+
const node = (0, store_js_1.resolveNodeById)(adapter, itemId);
|
|
111
|
+
if (!node) {
|
|
112
|
+
throw new RecordIdentityError('unknown_node', `No registry node ${itemId}.`);
|
|
113
|
+
}
|
|
114
|
+
const limit = Math.min(Math.max(options?.limit ?? 20, 1), 100);
|
|
115
|
+
const before = options?.before;
|
|
116
|
+
const rows = adapter
|
|
117
|
+
.prepare(`WITH RECURSIVE item_ids(id) AS (
|
|
118
|
+
SELECT ?
|
|
119
|
+
UNION
|
|
120
|
+
SELECT registry_nodes.id FROM registry_nodes
|
|
121
|
+
JOIN item_ids ON registry_nodes.merged_into = item_ids.id
|
|
122
|
+
)
|
|
123
|
+
SELECT id, COALESCE(event_datetime, created_at) AS ts FROM decisions
|
|
124
|
+
WHERE item_id IN (SELECT id FROM item_ids)
|
|
125
|
+
${before ? 'AND (COALESCE(event_datetime, created_at) < ? OR (COALESCE(event_datetime, created_at) = ? AND id < ?))' : ''}
|
|
126
|
+
ORDER BY ts DESC, id DESC
|
|
127
|
+
LIMIT ?`)
|
|
128
|
+
.all(...(before
|
|
129
|
+
? [node.id, before.timestamp, before.timestamp, before.id, limit]
|
|
130
|
+
: [node.id, limit]));
|
|
131
|
+
return rows.map((row) => ({ id: row.id, timestamp: row.ts }));
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=record-identity.js.map
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registry store: work items, people and clients as nodes with the names they go by.
|
|
3
|
+
*
|
|
4
|
+
* Identity used to live in strings such as task titles and decision topics, so alternate
|
|
5
|
+
* spellings could not join without an explicit durable identity.
|
|
6
|
+
*
|
|
7
|
+
* A node here is cheap: an id, a kind, a display name, and the aliases it answers to.
|
|
8
|
+
* What it buys is that a record can point at identity instead of spelling it.
|
|
9
|
+
*
|
|
10
|
+
* Two decisions this module refuses to make on its own:
|
|
11
|
+
* - It never merges by similarity. `mergeNodes` happens because a caller decided, and the
|
|
12
|
+
* reason is stored with it.
|
|
13
|
+
* - It never reassigns a contested alias. Claiming an alias another node holds raises, so
|
|
14
|
+
* the caller resolves it (merge, or pick a different alias) rather than the store
|
|
15
|
+
* silently moving identity from under existing records.
|
|
16
|
+
*/
|
|
17
|
+
import type { DatabaseAdapter } from '../db-manager.js';
|
|
18
|
+
import { REGISTRY_KINDS, type RegistryKind, type RegistryNode, type RegistryScopeRef } from './types.js';
|
|
19
|
+
export { REGISTRY_KINDS };
|
|
20
|
+
export type { RegistryKind, RegistryNode };
|
|
21
|
+
export type { RegistryScopeRef };
|
|
22
|
+
export declare class RegistryError extends Error {
|
|
23
|
+
readonly code: string;
|
|
24
|
+
constructor(code: string, message: string);
|
|
25
|
+
}
|
|
26
|
+
type RegistryReadAdapter = Pick<DatabaseAdapter, 'prepare'>;
|
|
27
|
+
type RegistryStoreAdapter = Pick<DatabaseAdapter, 'prepare' | 'transaction'>;
|
|
28
|
+
export declare function currentIdentityRevision(adapter: RegistryReadAdapter): number;
|
|
29
|
+
export declare function rebuildRegistryProjections(adapter: RegistryReadAdapter): void;
|
|
30
|
+
/**
|
|
31
|
+
* The lookup key for an alias: case-folded, inner runs of whitespace collapsed, trimmed.
|
|
32
|
+
*
|
|
33
|
+
* Deliberately conservative - it folds the differences that are the same string typed
|
|
34
|
+
* twice, and nothing else. Hyphens, underscores and script differences stay significant,
|
|
35
|
+
* because "a-0001" and "a_0001" being the same thing is a judgement about the work, not
|
|
36
|
+
* about text, and belongs in an alias row someone added.
|
|
37
|
+
*/
|
|
38
|
+
export declare function normalizeAlias(value: string): string;
|
|
39
|
+
export declare function createNode(adapter: RegistryStoreAdapter, input: {
|
|
40
|
+
kind: RegistryKind | string;
|
|
41
|
+
name: string;
|
|
42
|
+
aliases?: readonly string[];
|
|
43
|
+
parentId?: string | null;
|
|
44
|
+
note?: string | null;
|
|
45
|
+
scopes?: readonly RegistryScopeRef[];
|
|
46
|
+
}): string;
|
|
47
|
+
export declare function addAliases(adapter: RegistryStoreAdapter, nodeId: string, aliases: readonly string[], scopes?: readonly RegistryScopeRef[]): void;
|
|
48
|
+
export declare function upsertNode(adapter: RegistryStoreAdapter, input: {
|
|
49
|
+
kind: RegistryKind | string;
|
|
50
|
+
name: string;
|
|
51
|
+
aliases?: readonly string[];
|
|
52
|
+
note?: string | null;
|
|
53
|
+
scopes?: readonly RegistryScopeRef[];
|
|
54
|
+
children?: ReadonlyArray<{
|
|
55
|
+
name: string;
|
|
56
|
+
aliases?: readonly string[];
|
|
57
|
+
}>;
|
|
58
|
+
}): {
|
|
59
|
+
id: string;
|
|
60
|
+
created: boolean;
|
|
61
|
+
children: string[];
|
|
62
|
+
};
|
|
63
|
+
export declare function addAlias(adapter: RegistryReadAdapter, nodeId: string, alias: string): void;
|
|
64
|
+
/** The live node behind an id, following merges. Null when the id is unknown. */
|
|
65
|
+
export declare function resolveNodeById(adapter: RegistryReadAdapter, id: string): RegistryNode | null;
|
|
66
|
+
/** The node an alias names, following merges. `kind` narrows when the same word is both. */
|
|
67
|
+
export declare function resolveAlias(adapter: RegistryReadAdapter, alias: string, kind?: RegistryKind | string, scopes?: readonly RegistryScopeRef[]): RegistryNode | null;
|
|
68
|
+
export declare function resolveAliasCandidates(adapter: RegistryReadAdapter, alias: string, options?: {
|
|
69
|
+
kind?: RegistryKind | string;
|
|
70
|
+
scopes?: readonly RegistryScopeRef[];
|
|
71
|
+
}): RegistryNode[];
|
|
72
|
+
export declare function listNodes(adapter: RegistryReadAdapter, filter?: {
|
|
73
|
+
kind?: RegistryKind | string;
|
|
74
|
+
parentId?: string | null;
|
|
75
|
+
includeMerged?: boolean;
|
|
76
|
+
scopes?: readonly RegistryScopeRef[];
|
|
77
|
+
}): RegistryNode[];
|
|
78
|
+
/**
|
|
79
|
+
* Point one node's identity at another. The loser keeps its row as a tombstone so records
|
|
80
|
+
* that still reference it resolve forward instead of dangling.
|
|
81
|
+
*/
|
|
82
|
+
export declare function mergeNodes(adapter: RegistryStoreAdapter, input: {
|
|
83
|
+
loser: string;
|
|
84
|
+
survivor: string;
|
|
85
|
+
reason: string;
|
|
86
|
+
}): void;
|
|
87
|
+
/**
|
|
88
|
+
* Split one item into the children it really covers, keeping the parent as the umbrella.
|
|
89
|
+
*
|
|
90
|
+
* Nothing is deleted: the parent stays resolvable by its own aliases, and existing records
|
|
91
|
+
* keep pointing at it until someone decides which child they belong to.
|
|
92
|
+
*/
|
|
93
|
+
export declare function splitNode(adapter: RegistryStoreAdapter, input: {
|
|
94
|
+
parent: string;
|
|
95
|
+
children: ReadonlyArray<{
|
|
96
|
+
name: string;
|
|
97
|
+
aliases?: readonly string[];
|
|
98
|
+
}>;
|
|
99
|
+
reason: string;
|
|
100
|
+
}): string[];
|
|
101
|
+
//# sourceMappingURL=store.d.ts.map
|