@jungjaehoon/mama-core 2.4.1 → 4.0.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/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 +1968 -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/commitments.d.ts +188 -0
- package/dist/knowledge/commitments.js +370 -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/judgments.d.ts +118 -0
- package/dist/knowledge/judgments.js +808 -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 +155 -0
- package/dist/runtime/drivers/claude-cli-wrapper.js +392 -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 +424 -0
- package/dist/runtime/drivers/persistent-cli-process.js +1543 -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,565 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The durable record of what this system actually changed, and what caused it.
|
|
4
|
+
*
|
|
5
|
+
* One table, `evidence_effects`. A `cause_state` CHECK forbids both "attributed
|
|
6
|
+
* with no cause" and "unattributed with a cause", and a BEFORE INSERT trigger
|
|
7
|
+
* rejects unusable cause ids, so the numerator and the denominator of coverage
|
|
8
|
+
* cannot be separated.
|
|
9
|
+
*
|
|
10
|
+
* This lives in core, next to the commitments it describes, for one reason: a
|
|
11
|
+
* change and its cause have to commit or roll back together. While owner work
|
|
12
|
+
* was stored in the daemon's operator database and this ledger beside it, that
|
|
13
|
+
* held. Moving owner work into the knowledge database without moving this would
|
|
14
|
+
* have put a change in one database and its cause in another, and a ledger that
|
|
15
|
+
* can disagree with the rows it describes is worse than no ledger.
|
|
16
|
+
*
|
|
17
|
+
* Every function here takes its adapter, so the caller decides which database is
|
|
18
|
+
* being recorded; nothing is resolved from a global.
|
|
19
|
+
*
|
|
20
|
+
* @module runtime/operations
|
|
21
|
+
*/
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.CHANGES_READ_CAUSE_STATES = exports.CHANGES_READ_TARGET_TYPES = exports.OperationError = exports.EffectWithoutCauseError = exports.EVIDENCE_EFFECTS_DDL = void 0;
|
|
24
|
+
exports.isUsableCause = isUsableCause;
|
|
25
|
+
exports.ensureEffectLedger = ensureEffectLedger;
|
|
26
|
+
exports.payloadHash = payloadHash;
|
|
27
|
+
exports.recordEffect = recordEffect;
|
|
28
|
+
exports.recordUnattributedChange = recordUnattributedChange;
|
|
29
|
+
exports.listEffects = listEffects;
|
|
30
|
+
exports.changeCoverage = changeCoverage;
|
|
31
|
+
exports.readOperation = readOperation;
|
|
32
|
+
exports.parseChangesSince = parseChangesSince;
|
|
33
|
+
exports.readChanges = readChanges;
|
|
34
|
+
const node_crypto_1 = require("node:crypto");
|
|
35
|
+
const EFFECT_KINDS = [
|
|
36
|
+
'task_create',
|
|
37
|
+
'task_update',
|
|
38
|
+
'report_update',
|
|
39
|
+
'report_publish',
|
|
40
|
+
'memory_write',
|
|
41
|
+
'wiki_write',
|
|
42
|
+
'file_export',
|
|
43
|
+
'repair_request',
|
|
44
|
+
'run_budget_stop',
|
|
45
|
+
];
|
|
46
|
+
const EFFECT_TARGETS = [
|
|
47
|
+
'task',
|
|
48
|
+
'report_slot',
|
|
49
|
+
'memory',
|
|
50
|
+
'wiki_page',
|
|
51
|
+
'file',
|
|
52
|
+
'issue',
|
|
53
|
+
'run',
|
|
54
|
+
];
|
|
55
|
+
/** Long enough for any upstream id, short enough that the column cannot carry content. */
|
|
56
|
+
const MAX_EVENT_ID_LENGTH = 200;
|
|
57
|
+
const PAYLOAD_HASH_LENGTH = 32;
|
|
58
|
+
/**
|
|
59
|
+
* Whether a value can serve as a cause at all.
|
|
60
|
+
*
|
|
61
|
+
* Callers ask before writing rather than discovering it as a constraint violation,
|
|
62
|
+
* because the answer changes what they record, not whether they may proceed: a malformed
|
|
63
|
+
* cause makes a change unattributed, it does not make the change illegitimate. Legacy
|
|
64
|
+
* rows really do carry 400-character source identifiers, and refusing to let the operator
|
|
65
|
+
* update a work item because its upstream id is the wrong shape would be enforcement
|
|
66
|
+
* bought with the owner's work.
|
|
67
|
+
*/
|
|
68
|
+
function isUsableCause(value) {
|
|
69
|
+
if (typeof value !== 'string')
|
|
70
|
+
return false;
|
|
71
|
+
const trimmed = value.trim();
|
|
72
|
+
return trimmed.length > 0 && trimmed.length <= MAX_EVENT_ID_LENGTH;
|
|
73
|
+
}
|
|
74
|
+
const quoted = (values) => values.map((v) => `'${v}'`).join(', ');
|
|
75
|
+
exports.EVIDENCE_EFFECTS_DDL = `
|
|
76
|
+
CREATE TABLE IF NOT EXISTS evidence_effects (
|
|
77
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
78
|
+
run_id TEXT,
|
|
79
|
+
channel_id TEXT,
|
|
80
|
+
cause_state TEXT NOT NULL CHECK (cause_state IN ('attributed', 'unattributed')),
|
|
81
|
+
cause_kind TEXT NOT NULL
|
|
82
|
+
CHECK (cause_kind IN ('event', 'owner_message', 'clock', 'card_transition')),
|
|
83
|
+
source_event_ids_json TEXT NOT NULL
|
|
84
|
+
CHECK (
|
|
85
|
+
json_valid(source_event_ids_json)
|
|
86
|
+
AND (
|
|
87
|
+
(cause_state = 'attributed' AND json_array_length(source_event_ids_json) >= 1)
|
|
88
|
+
OR (cause_state = 'unattributed' AND json_array_length(source_event_ids_json) = 0)
|
|
89
|
+
)
|
|
90
|
+
),
|
|
91
|
+
effect_kind TEXT NOT NULL CHECK (effect_kind IN (${quoted(EFFECT_KINDS)})),
|
|
92
|
+
target_type TEXT NOT NULL CHECK (target_type IN (${quoted(EFFECT_TARGETS)})),
|
|
93
|
+
target_id TEXT NOT NULL CHECK (length(trim(target_id)) > 0),
|
|
94
|
+
payload_hash TEXT NOT NULL CHECK (length(payload_hash) = ${PAYLOAD_HASH_LENGTH}),
|
|
95
|
+
created_at INTEGER NOT NULL CHECK (typeof(created_at) = 'integer' AND created_at >= 0)
|
|
96
|
+
)
|
|
97
|
+
`;
|
|
98
|
+
/**
|
|
99
|
+
* Per-element shape of the cause list.
|
|
100
|
+
*
|
|
101
|
+
* A trigger rather than a CHECK because SQLite forbids subqueries in CHECK, and checking
|
|
102
|
+
* each element needs `json_each`. Without this the column enforces only "non-empty array",
|
|
103
|
+
* which `[""]` and `[null]` and `[0]` all satisfy while naming nothing.
|
|
104
|
+
*
|
|
105
|
+
* The length bound is not cosmetic: an unbounded cause id turns an audit ledger into a
|
|
106
|
+
* content channel, which is the one thing the payload is hashed to avoid.
|
|
107
|
+
*/
|
|
108
|
+
const EVIDENCE_EFFECTS_CAUSE_TRIGGER = `
|
|
109
|
+
CREATE TRIGGER IF NOT EXISTS evidence_effects_cause_shape
|
|
110
|
+
BEFORE INSERT ON evidence_effects
|
|
111
|
+
WHEN EXISTS (
|
|
112
|
+
SELECT 1 FROM json_each(NEW.source_event_ids_json)
|
|
113
|
+
WHERE json_each.type <> 'text'
|
|
114
|
+
OR trim(json_each.value) = ''
|
|
115
|
+
OR length(json_each.value) > ${MAX_EVENT_ID_LENGTH}
|
|
116
|
+
)
|
|
117
|
+
BEGIN
|
|
118
|
+
SELECT RAISE(ABORT, 'evidence_effects: a cause must be a non-empty event id');
|
|
119
|
+
END
|
|
120
|
+
`;
|
|
121
|
+
/**
|
|
122
|
+
* kind <-> ids cross-shape. A trigger (not a table CHECK) so it applies
|
|
123
|
+
* identically to fresh tables and ALTERed old ones: `event` must carry ids,
|
|
124
|
+
* the id-less kinds must not - a clock that names events or an event that
|
|
125
|
+
* names none is a fabricated cause either way.
|
|
126
|
+
*/
|
|
127
|
+
const EVIDENCE_EFFECTS_KIND_TRIGGER = `
|
|
128
|
+
CREATE TRIGGER IF NOT EXISTS evidence_effects_kind_shape
|
|
129
|
+
BEFORE INSERT ON evidence_effects
|
|
130
|
+
WHEN (NEW.cause_kind = 'event' AND json_array_length(NEW.source_event_ids_json) = 0)
|
|
131
|
+
OR (NEW.cause_kind <> 'event' AND json_array_length(NEW.source_event_ids_json) > 0)
|
|
132
|
+
BEGIN
|
|
133
|
+
SELECT RAISE(ABORT, 'evidence_effects: cause_kind and source_event_ids disagree');
|
|
134
|
+
END
|
|
135
|
+
`;
|
|
136
|
+
const INDEXES = [
|
|
137
|
+
`CREATE INDEX IF NOT EXISTS idx_evidence_effects_run
|
|
138
|
+
ON evidence_effects(run_id, created_at DESC)`,
|
|
139
|
+
`CREATE INDEX IF NOT EXISTS idx_evidence_effects_target
|
|
140
|
+
ON evidence_effects(target_type, target_id, created_at DESC)`,
|
|
141
|
+
`CREATE INDEX IF NOT EXISTS idx_evidence_effects_channel
|
|
142
|
+
ON evidence_effects(channel_id, created_at DESC)`,
|
|
143
|
+
// Coverage is meant to be cheap enough that nobody has an excuse not to look.
|
|
144
|
+
`CREATE INDEX IF NOT EXISTS idx_evidence_effects_coverage
|
|
145
|
+
ON evidence_effects(created_at DESC, cause_state)`,
|
|
146
|
+
];
|
|
147
|
+
function ensureEffectLedger(adapter) {
|
|
148
|
+
adapter.prepare(exports.EVIDENCE_EFFECTS_DDL).run();
|
|
149
|
+
migrateCauseKind(adapter);
|
|
150
|
+
widenClosedSets(adapter);
|
|
151
|
+
adapter.prepare(EVIDENCE_EFFECTS_CAUSE_TRIGGER).run();
|
|
152
|
+
adapter.prepare(EVIDENCE_EFFECTS_KIND_TRIGGER).run();
|
|
153
|
+
for (const sql of INDEXES) {
|
|
154
|
+
adapter.prepare(sql).run();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* S2 migration: add cause_kind to pre-existing ledgers and backfill by
|
|
159
|
+
* DISCRIMINATOR, never blanket (review #3 - "104 -> clock" would stamp owner-
|
|
160
|
+
* and API-driven changes with a fabricated clock cause):
|
|
161
|
+
* attributed -> event (ids >= 1, DB-checked)
|
|
162
|
+
* unattributed + temporal-effect join -> clock (a temporal check fired)
|
|
163
|
+
* unattributed + run_id present -> clock (scheduled board:full run)
|
|
164
|
+
* remaining unattributed -> owner_message (console/API writes
|
|
165
|
+
* carry no run and no batch)
|
|
166
|
+
* ALTER ADD COLUMN with CHECK is legal SQLite (verified 2026-07-31; the
|
|
167
|
+
* prohibition covers PK/UNIQUE) - no table rebuild.
|
|
168
|
+
*/
|
|
169
|
+
function migrateCauseKind(adapter) {
|
|
170
|
+
const columns = adapter.prepare(`PRAGMA table_info(evidence_effects)`).all();
|
|
171
|
+
if (columns.some((column) => column.name === 'cause_kind')) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
adapter
|
|
175
|
+
.prepare(`ALTER TABLE evidence_effects ADD COLUMN cause_kind TEXT NOT NULL DEFAULT 'clock'
|
|
176
|
+
CHECK (cause_kind IN ('event', 'owner_message', 'clock', 'card_transition'))`)
|
|
177
|
+
.run();
|
|
178
|
+
adapter
|
|
179
|
+
.prepare(`UPDATE evidence_effects SET cause_kind = 'event' WHERE cause_state = 'attributed'`)
|
|
180
|
+
.run();
|
|
181
|
+
const hasTemporalTable = (adapter
|
|
182
|
+
.prepare(`SELECT 1 AS x FROM sqlite_master WHERE type='table' AND name='operator_temporal_effects'`)
|
|
183
|
+
.all().length ?? 0) > 0;
|
|
184
|
+
if (hasTemporalTable) {
|
|
185
|
+
adapter
|
|
186
|
+
.prepare(`UPDATE evidence_effects SET cause_kind = 'clock'
|
|
187
|
+
WHERE cause_state = 'unattributed'
|
|
188
|
+
AND EXISTS (SELECT 1 FROM operator_temporal_effects t
|
|
189
|
+
WHERE CAST(t.task_id AS TEXT) = evidence_effects.target_id)`)
|
|
190
|
+
.run();
|
|
191
|
+
}
|
|
192
|
+
// run_id-bearing unattributed rows are scheduled runs (board:full and
|
|
193
|
+
// friends): the ALTER's DEFAULT already left them 'clock', which is the
|
|
194
|
+
// intended label - no UPDATE needed (a `cause_kind <> 'clock'` predicate
|
|
195
|
+
// here would be dead code, review).
|
|
196
|
+
adapter
|
|
197
|
+
.prepare(hasTemporalTable
|
|
198
|
+
? `UPDATE evidence_effects SET cause_kind = 'owner_message'
|
|
199
|
+
WHERE cause_state = 'unattributed' AND run_id IS NULL
|
|
200
|
+
AND NOT EXISTS (SELECT 1 FROM operator_temporal_effects t
|
|
201
|
+
WHERE CAST(t.task_id AS TEXT) = evidence_effects.target_id)`
|
|
202
|
+
: `UPDATE evidence_effects SET cause_kind = 'owner_message'
|
|
203
|
+
WHERE cause_state = 'unattributed' AND run_id IS NULL`)
|
|
204
|
+
.run();
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Phase 3 migration: the effect_kind / target_type CHECKs are closed sets baked into the
|
|
208
|
+
* table, and SQLite cannot alter a CHECK in place. When an installed ledger's CHECK is
|
|
209
|
+
* narrower than the current constants, rebuild the table inside one transaction: copy every
|
|
210
|
+
* row with identical ids and created_at, keep the cause_state CHECK and both triggers
|
|
211
|
+
* byte-identical (they are the invariant the ledger rests on), recreate the indices.
|
|
212
|
+
* Detection reads the stored DDL, so a ledger that already carries the current sets is
|
|
213
|
+
* untouched on every later boot.
|
|
214
|
+
*/
|
|
215
|
+
function widenClosedSets(adapter) {
|
|
216
|
+
const row = adapter
|
|
217
|
+
.prepare(`SELECT sql FROM sqlite_master WHERE type='table' AND name='evidence_effects'`)
|
|
218
|
+
.all()[0];
|
|
219
|
+
const storedDdl = row?.sql ?? '';
|
|
220
|
+
const missing = [...EFFECT_KINDS, ...EFFECT_TARGETS].filter((value) => !storedDdl.includes(`'${value}'`));
|
|
221
|
+
if (missing.length === 0) {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
const run = (sql) => adapter.prepare(sql).run();
|
|
225
|
+
run('BEGIN IMMEDIATE');
|
|
226
|
+
try {
|
|
227
|
+
run(exports.EVIDENCE_EFFECTS_DDL.replace('evidence_effects (', 'evidence_effects_new ('));
|
|
228
|
+
run(`INSERT INTO evidence_effects_new
|
|
229
|
+
(id, run_id, channel_id, cause_state, cause_kind, source_event_ids_json,
|
|
230
|
+
effect_kind, target_type, target_id, payload_hash, created_at)
|
|
231
|
+
SELECT id, run_id, channel_id, cause_state, cause_kind, source_event_ids_json,
|
|
232
|
+
effect_kind, target_type, target_id, payload_hash, created_at
|
|
233
|
+
FROM evidence_effects`);
|
|
234
|
+
run('DROP TABLE evidence_effects');
|
|
235
|
+
run('ALTER TABLE evidence_effects_new RENAME TO evidence_effects');
|
|
236
|
+
run(EVIDENCE_EFFECTS_CAUSE_TRIGGER);
|
|
237
|
+
run(EVIDENCE_EFFECTS_KIND_TRIGGER);
|
|
238
|
+
for (const sql of INDEXES) {
|
|
239
|
+
run(sql);
|
|
240
|
+
}
|
|
241
|
+
run('COMMIT');
|
|
242
|
+
}
|
|
243
|
+
catch (error) {
|
|
244
|
+
try {
|
|
245
|
+
run('ROLLBACK');
|
|
246
|
+
}
|
|
247
|
+
catch {
|
|
248
|
+
// BEGIN itself may have failed (busy lock, nested transaction): keep the original error.
|
|
249
|
+
}
|
|
250
|
+
throw error;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
class EffectWithoutCauseError extends Error {
|
|
254
|
+
constructor(kind, targetId) {
|
|
255
|
+
super(`Refusing to record ${kind} on ${targetId} with no source events: a change that cannot name its cause is not an effect.`);
|
|
256
|
+
this.name = 'EffectWithoutCauseError';
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
exports.EffectWithoutCauseError = EffectWithoutCauseError;
|
|
260
|
+
/** Stable hash of what was written. The ledger proves a change happened, not what it said. */
|
|
261
|
+
function payloadHash(payload) {
|
|
262
|
+
return (0, node_crypto_1.createHash)('sha256')
|
|
263
|
+
.update(JSON.stringify(payload ?? null))
|
|
264
|
+
.digest('hex')
|
|
265
|
+
.slice(0, PAYLOAD_HASH_LENGTH);
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Record a durable change.
|
|
269
|
+
*
|
|
270
|
+
* Throws rather than returning a failure when the cause is missing. A caller that wrote
|
|
271
|
+
* something and then could not say why must not proceed as if the write were accounted
|
|
272
|
+
* for - silently skipping the ledger row is how 1,169 runs closed as `done` while five
|
|
273
|
+
* receipts existed.
|
|
274
|
+
*/
|
|
275
|
+
function recordEffect(adapter, input) {
|
|
276
|
+
const sourceEventIds = [
|
|
277
|
+
...new Set(input.sourceEventIds.filter(isUsableCause).map((id) => id.trim())),
|
|
278
|
+
];
|
|
279
|
+
if (sourceEventIds.length === 0) {
|
|
280
|
+
throw new EffectWithoutCauseError(input.kind, input.targetId);
|
|
281
|
+
}
|
|
282
|
+
return insertChange(adapter, input, 'event', sourceEventIds);
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Record a durable change that could not name what caused it.
|
|
286
|
+
*
|
|
287
|
+
* Deliberately a separate function with an uncomfortable name. Every call is an admission
|
|
288
|
+
* that the system changed something it cannot explain, and the point is that the count of
|
|
289
|
+
* these is visible next to the count of real effects rather than absent from both.
|
|
290
|
+
*/
|
|
291
|
+
function recordUnattributedChange(adapter, input, causeKind) {
|
|
292
|
+
return insertChange(adapter, input, causeKind, []);
|
|
293
|
+
}
|
|
294
|
+
function insertChange(adapter, input, causeKind, sourceEventIds) {
|
|
295
|
+
const result = adapter
|
|
296
|
+
.prepare(`INSERT INTO evidence_effects
|
|
297
|
+
(run_id, channel_id, cause_state, cause_kind, source_event_ids_json,
|
|
298
|
+
effect_kind, target_type, target_id, payload_hash, created_at)
|
|
299
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
300
|
+
.run(input.runId ?? null, input.channelId ?? null, causeKind === 'event' ? 'attributed' : 'unattributed', causeKind, JSON.stringify(sourceEventIds), input.kind, input.targetType, input.targetId, payloadHash(input.payload), input.atMs);
|
|
301
|
+
return Number(result.lastInsertRowid ?? 0);
|
|
302
|
+
}
|
|
303
|
+
/** Read effects back, newest first - the substrate a report projects from. */
|
|
304
|
+
function listEffects(adapter, query = {}) {
|
|
305
|
+
const clauses = [];
|
|
306
|
+
const params = [];
|
|
307
|
+
if (query.id !== undefined) {
|
|
308
|
+
clauses.push('id = ?');
|
|
309
|
+
params.push(query.id);
|
|
310
|
+
}
|
|
311
|
+
if (query.runId !== undefined) {
|
|
312
|
+
clauses.push('run_id = ?');
|
|
313
|
+
params.push(query.runId);
|
|
314
|
+
}
|
|
315
|
+
if (query.targetType !== undefined) {
|
|
316
|
+
clauses.push('target_type = ?');
|
|
317
|
+
params.push(query.targetType);
|
|
318
|
+
}
|
|
319
|
+
if (query.targetId !== undefined) {
|
|
320
|
+
clauses.push('target_id = ?');
|
|
321
|
+
params.push(query.targetId);
|
|
322
|
+
}
|
|
323
|
+
if (query.causeState !== undefined) {
|
|
324
|
+
clauses.push('cause_state = ?');
|
|
325
|
+
params.push(query.causeState);
|
|
326
|
+
}
|
|
327
|
+
if (query.sinceMs !== undefined) {
|
|
328
|
+
clauses.push('created_at >= ?');
|
|
329
|
+
params.push(query.sinceMs);
|
|
330
|
+
}
|
|
331
|
+
params.push(Math.min(Math.max(query.limit ?? 200, 1), 1000));
|
|
332
|
+
const rows = adapter
|
|
333
|
+
.prepare(`SELECT * FROM evidence_effects
|
|
334
|
+
${clauses.length > 0 ? `WHERE ${clauses.join(' AND ')}` : ''}
|
|
335
|
+
ORDER BY created_at DESC, id DESC
|
|
336
|
+
LIMIT ?`)
|
|
337
|
+
.all(...params);
|
|
338
|
+
return rows.map((row) => ({
|
|
339
|
+
id: Number(row.id),
|
|
340
|
+
runId: typeof row.run_id === 'string' ? row.run_id : null,
|
|
341
|
+
channelId: typeof row.channel_id === 'string' ? row.channel_id : null,
|
|
342
|
+
causeState: String(row.cause_state),
|
|
343
|
+
causeKind: String(row.cause_kind),
|
|
344
|
+
sourceEventIds: JSON.parse(String(row.source_event_ids_json)),
|
|
345
|
+
kind: String(row.effect_kind),
|
|
346
|
+
targetType: String(row.target_type),
|
|
347
|
+
targetId: String(row.target_id),
|
|
348
|
+
payloadHash: String(row.payload_hash),
|
|
349
|
+
atMs: Number(row.created_at),
|
|
350
|
+
}));
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* How much of what the system changed rests on evidence.
|
|
354
|
+
*
|
|
355
|
+
* The single number this ledger exists to make answerable, and the one the previous shape
|
|
356
|
+
* of the system could not produce at all.
|
|
357
|
+
*/
|
|
358
|
+
function changeCoverage(adapter, sinceMs, targetType) {
|
|
359
|
+
// Coverage must describe the same population as the rows a caller is looking at, or the
|
|
360
|
+
// two halves of one answer disagree and the reader cannot tell which is wrong.
|
|
361
|
+
const clauses = [];
|
|
362
|
+
const params = [];
|
|
363
|
+
if (sinceMs !== undefined) {
|
|
364
|
+
clauses.push('created_at >= ?');
|
|
365
|
+
params.push(sinceMs);
|
|
366
|
+
}
|
|
367
|
+
if (targetType !== undefined) {
|
|
368
|
+
clauses.push('target_type = ?');
|
|
369
|
+
params.push(targetType);
|
|
370
|
+
}
|
|
371
|
+
const rows = adapter
|
|
372
|
+
.prepare(`SELECT cause_state, COUNT(*) AS n FROM evidence_effects
|
|
373
|
+
${clauses.length > 0 ? `WHERE ${clauses.join(' AND ')}` : ''}
|
|
374
|
+
GROUP BY cause_state`)
|
|
375
|
+
.all(...params);
|
|
376
|
+
const coverage = { attributed: 0, unattributed: 0 };
|
|
377
|
+
for (const row of rows) {
|
|
378
|
+
if (row.cause_state === 'attributed')
|
|
379
|
+
coverage.attributed = Number(row.n);
|
|
380
|
+
if (row.cause_state === 'unattributed')
|
|
381
|
+
coverage.unattributed = Number(row.n);
|
|
382
|
+
}
|
|
383
|
+
return coverage;
|
|
384
|
+
}
|
|
385
|
+
class OperationError extends Error {
|
|
386
|
+
code;
|
|
387
|
+
constructor(code, message) {
|
|
388
|
+
super(message);
|
|
389
|
+
this.name = 'OperationError';
|
|
390
|
+
this.code = code;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
exports.OperationError = OperationError;
|
|
394
|
+
/**
|
|
395
|
+
* §293: start from the command binding — principal, action, original receipt
|
|
396
|
+
* ref — check the caller's authority, then read the actual result. An absent
|
|
397
|
+
* binding and one bound to another principal answer identically: the caller
|
|
398
|
+
* may not learn the id exists.
|
|
399
|
+
*/
|
|
400
|
+
function readOperation(adapter, operationId, access) {
|
|
401
|
+
const binding = adapter
|
|
402
|
+
.prepare(`SELECT principal_id, action, payload_hash, receipt_kind, receipt_key, created_at
|
|
403
|
+
FROM command_bindings WHERE command_id = ?`)
|
|
404
|
+
.get(operationId);
|
|
405
|
+
if (binding === undefined || binding.principal_id !== access.principalId) {
|
|
406
|
+
throw new OperationError('OPERATION_UNAVAILABLE', `operation ${operationId} is not available`);
|
|
407
|
+
}
|
|
408
|
+
let receipt = null;
|
|
409
|
+
if (binding.receipt_kind === 'judgment') {
|
|
410
|
+
const row = adapter
|
|
411
|
+
.prepare('SELECT receipt_json FROM judgment_commands WHERE command_id = ?')
|
|
412
|
+
.get(operationId);
|
|
413
|
+
if (row !== undefined) {
|
|
414
|
+
let parsed;
|
|
415
|
+
try {
|
|
416
|
+
parsed = JSON.parse(row.receipt_json);
|
|
417
|
+
}
|
|
418
|
+
catch (error) {
|
|
419
|
+
throw new Error('judgment_commands.receipt_json is malformed', { cause: error });
|
|
420
|
+
}
|
|
421
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
422
|
+
throw new Error('judgment_commands.receipt_json must contain an object');
|
|
423
|
+
}
|
|
424
|
+
receipt = parsed;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
return {
|
|
428
|
+
operationId,
|
|
429
|
+
action: binding.action,
|
|
430
|
+
principalId: binding.principal_id,
|
|
431
|
+
payloadHash: binding.payload_hash,
|
|
432
|
+
boundAt: binding.created_at,
|
|
433
|
+
receiptKind: binding.receipt_kind,
|
|
434
|
+
receiptKey: binding.receipt_key,
|
|
435
|
+
receipt,
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
// ── Changes read — the reporting projection over this ledger ───────────────
|
|
439
|
+
// The read side of the effect ledger: what this system changed, and on what
|
|
440
|
+
// evidence. The failure this projection was rewritten for: rows are capped,
|
|
441
|
+
// coverage is not. With 40 attributed and 80 unattributed changes in a window,
|
|
442
|
+
// a default call returned 50 rows - all unattributed, because the newest
|
|
443
|
+
// changes skew that way while `task_update` has no cause field - beside a
|
|
444
|
+
// coverage count saying 40 were explainable, and nothing anywhere saying 120
|
|
445
|
+
// matched. A model reading its own output could truthfully report "nothing I
|
|
446
|
+
// did is explainable". So the counts and the rows must describe the same
|
|
447
|
+
// population, the cap must be visible, and a filter the caller misspells must
|
|
448
|
+
// fail loudly rather than return zero rows - "no rows" and "nothing changed"
|
|
449
|
+
// are the same sentence to a reader, and only one of them is ever true.
|
|
450
|
+
const DEFAULT_WINDOW_MS = 24 * 60 * 60 * 1000;
|
|
451
|
+
const DEFAULT_LIMIT = 50;
|
|
452
|
+
const MAX_LIMIT = 200;
|
|
453
|
+
/** Below this a window is not a window; it is a corrupted number reaching toISOString. */
|
|
454
|
+
const MIN_SINCE_MS = 0;
|
|
455
|
+
// Narrower than EFFECT_TARGETS on purpose: the read filter names only the
|
|
456
|
+
// targets this ledger has ever recorded — file/issue/run effects are writable
|
|
457
|
+
// but no writer produces them, so accepting them as filters would be a promise
|
|
458
|
+
// the ledger cannot keep.
|
|
459
|
+
exports.CHANGES_READ_TARGET_TYPES = [
|
|
460
|
+
'task',
|
|
461
|
+
'report_slot',
|
|
462
|
+
'memory',
|
|
463
|
+
'wiki_page',
|
|
464
|
+
];
|
|
465
|
+
exports.CHANGES_READ_CAUSE_STATES = ['attributed', 'unattributed'];
|
|
466
|
+
/**
|
|
467
|
+
* The window a read covers.
|
|
468
|
+
*
|
|
469
|
+
* Unparseable input falls back to the default and the resolved window is returned to the
|
|
470
|
+
* caller, so whatever was asked for, what was actually read is stated. Non-string input is
|
|
471
|
+
* not a window at all - a model emitting epoch milliseconds used to crash the tool on
|
|
472
|
+
* `.trim()`.
|
|
473
|
+
*/
|
|
474
|
+
function parseChangesSince(since, nowMs) {
|
|
475
|
+
const raw = typeof since === 'string' ? since.trim() : '';
|
|
476
|
+
if (raw === '')
|
|
477
|
+
return nowMs - DEFAULT_WINDOW_MS;
|
|
478
|
+
// Minutes are here because a caller writing "30m" is entirely ordinary, and without
|
|
479
|
+
// the unit it fell through to the 24-hour default - a window 48x wider than asked for.
|
|
480
|
+
const relative = /^(\d+)\s*([dhm])$/i.exec(raw);
|
|
481
|
+
if (relative) {
|
|
482
|
+
const unit = relative[2].toLowerCase();
|
|
483
|
+
const unitMs = unit === 'd' ? 24 * 60 * 60 * 1000 : unit === 'h' ? 60 * 60 * 1000 : 60 * 1000;
|
|
484
|
+
// Clamped: a window of 99999999999 days is not a request, and an unclamped one
|
|
485
|
+
// produces a timestamp that throws on the way back out.
|
|
486
|
+
return clampSince(nowMs - Number(relative[1]) * unitMs, nowMs);
|
|
487
|
+
}
|
|
488
|
+
const parsed = Date.parse(raw);
|
|
489
|
+
if (Number.isNaN(parsed))
|
|
490
|
+
return nowMs - DEFAULT_WINDOW_MS;
|
|
491
|
+
return clampSince(parsed, nowMs);
|
|
492
|
+
}
|
|
493
|
+
/** A future window returns nothing, and nothing reads as "nothing changed". */
|
|
494
|
+
function clampSince(sinceMs, nowMs) {
|
|
495
|
+
return Math.min(Math.max(sinceMs, MIN_SINCE_MS), nowMs);
|
|
496
|
+
}
|
|
497
|
+
function parseLimit(limit) {
|
|
498
|
+
if (limit === undefined || limit === null)
|
|
499
|
+
return DEFAULT_LIMIT;
|
|
500
|
+
const value = typeof limit === 'string' ? Number(limit.trim()) : limit;
|
|
501
|
+
if (typeof value !== 'number' || !Number.isInteger(value) || value < 1) {
|
|
502
|
+
return invalid(`limit must be a positive whole number, got: ${String(limit)}`);
|
|
503
|
+
}
|
|
504
|
+
return Math.min(value, MAX_LIMIT);
|
|
505
|
+
}
|
|
506
|
+
function parseEnum(value, allowed, field) {
|
|
507
|
+
if (value === undefined || value === null || value === '')
|
|
508
|
+
return undefined;
|
|
509
|
+
if (typeof value === 'string' && allowed.includes(value))
|
|
510
|
+
return value;
|
|
511
|
+
// Silently returning zero rows for a misspelt filter is the worst answer available:
|
|
512
|
+
// it is indistinguishable from "nothing changed".
|
|
513
|
+
return invalid(`${field} must be one of ${allowed.join(', ')}, got: ${String(value)}`);
|
|
514
|
+
}
|
|
515
|
+
function invalid(error) {
|
|
516
|
+
return { success: false, code: 'invalid_argument', error };
|
|
517
|
+
}
|
|
518
|
+
function isChangesFailure(value) {
|
|
519
|
+
return (typeof value === 'object' &&
|
|
520
|
+
value !== null &&
|
|
521
|
+
value.success === false);
|
|
522
|
+
}
|
|
523
|
+
function readChanges(ledger, input, nowMs) {
|
|
524
|
+
const limit = parseLimit(input.limit);
|
|
525
|
+
if (isChangesFailure(limit))
|
|
526
|
+
return limit;
|
|
527
|
+
const targetType = parseEnum(input.target_type, exports.CHANGES_READ_TARGET_TYPES, 'target_type');
|
|
528
|
+
if (isChangesFailure(targetType))
|
|
529
|
+
return targetType;
|
|
530
|
+
const causeState = parseEnum(input.cause_state, exports.CHANGES_READ_CAUSE_STATES, 'cause_state');
|
|
531
|
+
if (isChangesFailure(causeState))
|
|
532
|
+
return causeState;
|
|
533
|
+
const sinceMs = parseChangesSince(input.since, nowMs);
|
|
534
|
+
// Coverage is the cause_state breakdown, so it answers within the SAME target scope the
|
|
535
|
+
// rows were drawn from. It deliberately ignores cause_state: filtering the breakdown by
|
|
536
|
+
// one of its own terms would report a zero that only means "I asked for the other one".
|
|
537
|
+
const coverage = ledger.changeCoverage(sinceMs, targetType);
|
|
538
|
+
const matching = coverage.attributed + coverage.unattributed;
|
|
539
|
+
const total = causeState === undefined
|
|
540
|
+
? matching
|
|
541
|
+
: causeState === 'attributed'
|
|
542
|
+
? coverage.attributed
|
|
543
|
+
: coverage.unattributed;
|
|
544
|
+
const changes = ledger.listChanges({ sinceMs, targetType, causeState, limit });
|
|
545
|
+
return {
|
|
546
|
+
success: true,
|
|
547
|
+
coverage,
|
|
548
|
+
since: new Date(sinceMs).toISOString(),
|
|
549
|
+
total,
|
|
550
|
+
returned: changes.length,
|
|
551
|
+
changes: changes.map((change) => ({
|
|
552
|
+
effect_id: change.id,
|
|
553
|
+
kind: change.kind,
|
|
554
|
+
target_type: change.targetType,
|
|
555
|
+
target_id: change.targetId,
|
|
556
|
+
cause_state: change.causeState,
|
|
557
|
+
cause_kind: change.causeKind,
|
|
558
|
+
source_event_ids: change.sourceEventIds,
|
|
559
|
+
channel: change.channelId,
|
|
560
|
+
run_id: change.runId,
|
|
561
|
+
at: new Date(change.atMs).toISOString(),
|
|
562
|
+
})),
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
//# sourceMappingURL=operations.js.map
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* After a tool runs, turn what it wrote into memory records, bounded and deduplicated,
|
|
3
|
+
* and fire-and-forget.
|
|
4
|
+
*
|
|
5
|
+
* What is here is the mechanism: notice that an editing tool ran, find the file it
|
|
6
|
+
* touched and the content it produced, ask for candidate records, keep at most a
|
|
7
|
+
* stated number of them, skip the ones already stored, and write the rest. What a
|
|
8
|
+
* candidate *is* is not here. A candidate is vocabulary, and vocabulary belongs to
|
|
9
|
+
* the caller: it supplies the source, and it names the two tools this handler calls.
|
|
10
|
+
*
|
|
11
|
+
* With no source, the handler is inert. That is the intended resting state once the
|
|
12
|
+
* caller's own extractor is gone.
|
|
13
|
+
*/
|
|
14
|
+
/** A record the caller wants written when a tool's output yields one. */
|
|
15
|
+
export interface PostToolMemoryCandidate {
|
|
16
|
+
topic: string;
|
|
17
|
+
decision: string;
|
|
18
|
+
reasoning: string;
|
|
19
|
+
confidence: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The only thing this handler reads off the caller's execution context: somewhere to
|
|
23
|
+
* register the background task it starts, so the turn that produced the tool result
|
|
24
|
+
* can wait for the write instead of racing it.
|
|
25
|
+
*/
|
|
26
|
+
export interface PostToolExecutionContext {
|
|
27
|
+
backgroundTasks?: {
|
|
28
|
+
register(task: Promise<unknown>): void;
|
|
29
|
+
} | null;
|
|
30
|
+
}
|
|
31
|
+
/** Where candidates come from. The caller owns every judgment this makes. */
|
|
32
|
+
export interface PostToolMemorySource {
|
|
33
|
+
/**
|
|
34
|
+
* Candidates this content yields. Return an empty array when the content, the
|
|
35
|
+
* path, or the caller's own rules say nothing should be written.
|
|
36
|
+
*/
|
|
37
|
+
candidates(content: string, filePath: string): readonly PostToolMemoryCandidate[];
|
|
38
|
+
}
|
|
39
|
+
export interface PostToolHandlerConfig {
|
|
40
|
+
enabled: boolean;
|
|
41
|
+
/** Tool names whose output is read. A tool matches when its name contains one of these. */
|
|
42
|
+
editTools: readonly string[];
|
|
43
|
+
/** The tool this handler calls to look for an existing record. */
|
|
44
|
+
searchTool: string;
|
|
45
|
+
/** The tool this handler calls to write one. */
|
|
46
|
+
saveTool: string;
|
|
47
|
+
/** At most this many candidates are written per tool result. */
|
|
48
|
+
saveLimit?: number;
|
|
49
|
+
}
|
|
50
|
+
type ExecuteToolFn<Ctx> = (name: string, input: Record<string, unknown>, executionContext?: Ctx | null) => Promise<unknown>;
|
|
51
|
+
export declare class PostToolHandler<Ctx extends PostToolExecutionContext = PostToolExecutionContext> {
|
|
52
|
+
private readonly executeTool;
|
|
53
|
+
private readonly enabled;
|
|
54
|
+
private readonly editTools;
|
|
55
|
+
private readonly searchTool;
|
|
56
|
+
private readonly saveTool;
|
|
57
|
+
private readonly saveLimit;
|
|
58
|
+
private readonly source;
|
|
59
|
+
constructor(executeTool: ExecuteToolFn<Ctx>, config: PostToolHandlerConfig, source?: PostToolMemorySource | null);
|
|
60
|
+
/**
|
|
61
|
+
* Called from the tool-result path. MUST NOT be async, MUST NOT return a promise
|
|
62
|
+
* and MUST NOT throw: a memory write is not allowed to decide whether a turn
|
|
63
|
+
* continues. The task is registered so the turn can still wait for it.
|
|
64
|
+
*/
|
|
65
|
+
processInBackground(toolName: string, input: unknown, result: unknown, executionContext?: Ctx | null): void;
|
|
66
|
+
private processAsync;
|
|
67
|
+
private isEditTool;
|
|
68
|
+
private extractFilePath;
|
|
69
|
+
private extractContent;
|
|
70
|
+
private isDuplicate;
|
|
71
|
+
private save;
|
|
72
|
+
}
|
|
73
|
+
export {};
|
|
74
|
+
//# sourceMappingURL=post-tool-handler.d.ts.map
|