@jungjaehoon/mama-os 0.53.1 → 0.57.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/CHANGELOG.md +64 -0
- package/README.md +62 -259
- package/db/migrations/001-connector-event-index.sql +251 -0
- package/dist/agent/claude-native-tool-policy.d.ts +17 -0
- package/dist/agent/claude-native-tool-policy.d.ts.map +1 -0
- package/dist/agent/claude-native-tool-policy.js +88 -0
- package/dist/agent/claude-native-tool-policy.js.map +1 -0
- package/dist/api/attachment-actions.d.ts +16 -0
- package/dist/api/attachment-actions.d.ts.map +1 -0
- package/dist/api/attachment-actions.js +278 -0
- package/dist/api/attachment-actions.js.map +1 -0
- package/dist/api/auth-middleware.d.ts +15 -55
- package/dist/api/auth-middleware.d.ts.map +1 -1
- package/dist/api/auth-middleware.js +56 -233
- package/dist/api/auth-middleware.js.map +1 -1
- package/dist/api/cf-access.d.ts +6 -0
- package/dist/api/cf-access.d.ts.map +1 -0
- package/dist/api/cf-access.js +121 -0
- package/dist/api/cf-access.js.map +1 -0
- package/dist/api/file-delivery.d.ts +29 -0
- package/dist/api/file-delivery.d.ts.map +1 -0
- package/dist/api/file-delivery.js +90 -0
- package/dist/api/file-delivery.js.map +1 -0
- package/dist/api/owner-timezone-actions.d.ts +10 -0
- package/dist/api/owner-timezone-actions.d.ts.map +1 -0
- package/dist/api/owner-timezone-actions.js +73 -0
- package/dist/api/owner-timezone-actions.js.map +1 -0
- package/dist/api/report-actions.d.ts +22 -0
- package/dist/api/report-actions.d.ts.map +1 -0
- package/dist/api/report-actions.js +137 -0
- package/dist/api/report-actions.js.map +1 -0
- package/dist/api/report-handler.d.ts +15 -11
- package/dist/api/report-handler.d.ts.map +1 -1
- package/dist/api/report-handler.js +48 -120
- package/dist/api/report-handler.js.map +1 -1
- package/dist/api/report-persistence.d.ts +3 -3
- package/dist/api/report-persistence.d.ts.map +1 -1
- package/dist/api/report-persistence.js +35 -10
- package/dist/api/report-persistence.js.map +1 -1
- package/dist/api/report-source-actions.d.ts +11 -0
- package/dist/api/report-source-actions.d.ts.map +1 -0
- package/dist/api/report-source-actions.js +309 -0
- package/dist/api/report-source-actions.js.map +1 -0
- package/dist/api/security-events.d.ts +23 -0
- package/dist/api/security-events.d.ts.map +1 -0
- package/dist/api/security-events.js +71 -0
- package/dist/api/security-events.js.map +1 -0
- package/dist/api/source-actions.d.ts +9 -0
- package/dist/api/source-actions.d.ts.map +1 -0
- package/dist/api/source-actions.js +274 -0
- package/dist/api/source-actions.js.map +1 -0
- package/dist/api/stored-source-reader.d.ts +21 -0
- package/dist/api/stored-source-reader.d.ts.map +1 -0
- package/dist/api/stored-source-reader.js +269 -0
- package/dist/api/stored-source-reader.js.map +1 -0
- package/dist/api/viewer-data.d.ts +143 -0
- package/dist/api/viewer-data.d.ts.map +1 -0
- package/dist/api/viewer-data.js +518 -0
- package/dist/api/viewer-data.js.map +1 -0
- package/dist/api/viewer-request-security.d.ts +12 -0
- package/dist/api/viewer-request-security.d.ts.map +1 -0
- package/dist/api/viewer-request-security.js +217 -0
- package/dist/api/viewer-request-security.js.map +1 -0
- package/dist/api/viewer-server.d.ts +55 -0
- package/dist/api/viewer-server.d.ts.map +1 -0
- package/dist/api/viewer-server.js +933 -0
- package/dist/api/viewer-server.js.map +1 -0
- package/dist/api/wiki-actions.d.ts +20 -0
- package/dist/api/wiki-actions.d.ts.map +1 -0
- package/dist/api/wiki-actions.js +296 -0
- package/dist/api/wiki-actions.js.map +1 -0
- package/dist/api/work-actions.d.ts +109 -0
- package/dist/api/work-actions.d.ts.map +1 -0
- package/dist/api/work-actions.js +987 -0
- package/dist/api/work-actions.js.map +1 -0
- package/dist/cli/commands/daemon.d.ts +90 -0
- package/dist/cli/commands/daemon.d.ts.map +1 -0
- package/dist/cli/commands/daemon.js +620 -0
- package/dist/cli/commands/daemon.js.map +1 -0
- package/dist/cli/commands/init.d.ts +7 -18
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +357 -273
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/replay.d.ts +12 -0
- package/dist/cli/commands/replay.d.ts.map +1 -0
- package/dist/cli/commands/replay.js +145 -0
- package/dist/cli/commands/replay.js.map +1 -0
- package/dist/cli/commands/secret.d.ts +6 -0
- package/dist/cli/commands/secret.d.ts.map +1 -0
- package/dist/cli/commands/secret.js +24 -0
- package/dist/cli/commands/secret.js.map +1 -0
- package/dist/cli/index.d.ts +0 -5
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +52 -95
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/launch-files.d.ts +10 -0
- package/dist/cli/launch-files.d.ts.map +1 -0
- package/dist/cli/launch-files.js +74 -0
- package/dist/cli/launch-files.js.map +1 -0
- package/dist/cli/prompt.d.ts +15 -0
- package/dist/cli/prompt.d.ts.map +1 -0
- package/dist/cli/prompt.js +64 -0
- package/dist/cli/prompt.js.map +1 -0
- package/dist/cli/runtime/action-mcp-config.d.ts +15 -0
- package/dist/cli/runtime/action-mcp-config.d.ts.map +1 -0
- package/dist/cli/runtime/action-mcp-config.js +56 -0
- package/dist/cli/runtime/action-mcp-config.js.map +1 -0
- package/dist/cli/runtime/claude-caller-config.d.ts +4 -0
- package/dist/cli/runtime/claude-caller-config.d.ts.map +1 -0
- package/dist/cli/runtime/claude-caller-config.js +46 -0
- package/dist/cli/runtime/claude-caller-config.js.map +1 -0
- package/dist/cli/secrets.d.ts +9 -0
- package/dist/cli/secrets.d.ts.map +1 -0
- package/dist/cli/secrets.js +94 -0
- package/dist/cli/secrets.js.map +1 -0
- package/dist/connectors/calendar/index.d.ts +11 -10
- package/dist/connectors/calendar/index.d.ts.map +1 -1
- package/dist/connectors/calendar/index.js +171 -97
- package/dist/connectors/calendar/index.js.map +1 -1
- package/dist/connectors/chatwork/index.d.ts +31 -5
- package/dist/connectors/chatwork/index.d.ts.map +1 -1
- package/dist/connectors/chatwork/index.js +184 -20
- package/dist/connectors/chatwork/index.js.map +1 -1
- package/dist/connectors/claude-code/index.d.ts +0 -2
- package/dist/connectors/claude-code/index.d.ts.map +1 -1
- package/dist/connectors/claude-code/index.js +94 -70
- package/dist/connectors/claude-code/index.js.map +1 -1
- package/dist/connectors/config-loader.d.ts +1 -6
- package/dist/connectors/config-loader.d.ts.map +1 -1
- package/dist/connectors/config-loader.js +154 -134
- package/dist/connectors/config-loader.js.map +1 -1
- package/dist/connectors/discord/index.d.ts +8 -1
- package/dist/connectors/discord/index.d.ts.map +1 -1
- package/dist/connectors/discord/index.js +110 -32
- package/dist/connectors/discord/index.js.map +1 -1
- package/dist/connectors/drive/index.d.ts +10 -5
- package/dist/connectors/drive/index.d.ts.map +1 -1
- package/dist/connectors/drive/index.js +145 -88
- package/dist/connectors/drive/index.js.map +1 -1
- package/dist/connectors/framework/attachment-io.d.ts +5 -0
- package/dist/connectors/framework/attachment-io.d.ts.map +1 -0
- package/dist/connectors/framework/attachment-io.js +72 -0
- package/dist/connectors/framework/attachment-io.js.map +1 -0
- package/dist/connectors/framework/attachments.d.ts +32 -0
- package/dist/connectors/framework/attachments.d.ts.map +1 -0
- package/dist/connectors/framework/attachments.js +14 -0
- package/dist/connectors/framework/attachments.js.map +1 -0
- package/dist/connectors/framework/connector-event-types.d.ts +136 -0
- package/dist/connectors/framework/connector-event-types.d.ts.map +1 -0
- package/dist/connectors/framework/connector-event-types.js +3 -0
- package/dist/connectors/framework/connector-event-types.js.map +1 -0
- package/dist/connectors/framework/connector-state.d.ts +3 -0
- package/dist/connectors/framework/connector-state.d.ts.map +1 -0
- package/dist/connectors/framework/connector-state.js +47 -0
- package/dist/connectors/framework/connector-state.js.map +1 -0
- package/dist/connectors/framework/event-index.d.ts +28 -0
- package/dist/connectors/framework/event-index.d.ts.map +1 -0
- package/dist/connectors/framework/event-index.js +354 -0
- package/dist/connectors/framework/event-index.js.map +1 -0
- package/dist/connectors/framework/gws-utils.d.ts +3 -8
- package/dist/connectors/framework/gws-utils.d.ts.map +1 -1
- package/dist/connectors/framework/gws-utils.js +23 -30
- package/dist/connectors/framework/gws-utils.js.map +1 -1
- package/dist/connectors/framework/index.d.ts +4 -2
- package/dist/connectors/framework/index.d.ts.map +1 -1
- package/dist/connectors/framework/index.js +8 -6
- package/dist/connectors/framework/index.js.map +1 -1
- package/dist/connectors/framework/polling-scheduler.d.ts +71 -52
- package/dist/connectors/framework/polling-scheduler.d.ts.map +1 -1
- package/dist/connectors/framework/polling-scheduler.js +227 -168
- package/dist/connectors/framework/polling-scheduler.js.map +1 -1
- package/dist/connectors/framework/raw-query.d.ts +44 -0
- package/dist/connectors/framework/raw-query.d.ts.map +1 -0
- package/dist/connectors/framework/raw-query.js +584 -0
- package/dist/connectors/framework/raw-query.js.map +1 -0
- package/dist/connectors/framework/stored-index-read.d.ts +16 -0
- package/dist/connectors/framework/stored-index-read.d.ts.map +1 -0
- package/dist/connectors/framework/stored-index-read.js +66 -0
- package/dist/connectors/framework/stored-index-read.js.map +1 -0
- package/dist/connectors/framework/types.d.ts +17 -61
- package/dist/connectors/framework/types.d.ts.map +1 -1
- package/dist/connectors/framework/types.js +0 -4
- package/dist/connectors/framework/types.js.map +1 -1
- package/dist/connectors/gmail/index.d.ts +3 -2
- package/dist/connectors/gmail/index.d.ts.map +1 -1
- package/dist/connectors/gmail/index.js +74 -27
- package/dist/connectors/gmail/index.js.map +1 -1
- package/dist/connectors/ical/index.d.ts +29 -0
- package/dist/connectors/ical/index.d.ts.map +1 -0
- package/dist/connectors/ical/index.js +220 -0
- package/dist/connectors/ical/index.js.map +1 -0
- package/dist/connectors/ical/parser.d.ts +13 -0
- package/dist/connectors/ical/parser.d.ts.map +1 -0
- package/dist/connectors/ical/parser.js +117 -0
- package/dist/connectors/ical/parser.js.map +1 -0
- package/dist/connectors/imessage/index.d.ts +3 -1
- package/dist/connectors/imessage/index.d.ts.map +1 -1
- package/dist/connectors/imessage/index.js +31 -16
- package/dist/connectors/imessage/index.js.map +1 -1
- package/dist/connectors/index.d.ts +12 -5
- package/dist/connectors/index.d.ts.map +1 -1
- package/dist/connectors/index.js +67 -18
- package/dist/connectors/index.js.map +1 -1
- package/dist/connectors/kagemusha/index.d.ts +7 -19
- package/dist/connectors/kagemusha/index.d.ts.map +1 -1
- package/dist/connectors/kagemusha/index.js +123 -173
- package/dist/connectors/kagemusha/index.js.map +1 -1
- package/dist/connectors/notion/index.d.ts.map +1 -1
- package/dist/connectors/notion/index.js +79 -36
- package/dist/connectors/notion/index.js.map +1 -1
- package/dist/connectors/obsidian/index.d.ts +1 -1
- package/dist/connectors/obsidian/index.d.ts.map +1 -1
- package/dist/connectors/obsidian/index.js +59 -57
- package/dist/connectors/obsidian/index.js.map +1 -1
- package/dist/connectors/sheets/index.d.ts +8 -4
- package/dist/connectors/sheets/index.d.ts.map +1 -1
- package/dist/connectors/sheets/index.js +155 -143
- package/dist/connectors/sheets/index.js.map +1 -1
- package/dist/connectors/slack/index.d.ts +24 -5
- package/dist/connectors/slack/index.d.ts.map +1 -1
- package/dist/connectors/slack/index.js +177 -39
- package/dist/connectors/slack/index.js.map +1 -1
- package/dist/connectors/telegram/index.d.ts +9 -2
- package/dist/connectors/telegram/index.d.ts.map +1 -1
- package/dist/connectors/telegram/index.js +119 -70
- package/dist/connectors/telegram/index.js.map +1 -1
- package/dist/connectors/trello/index.d.ts +10 -3
- package/dist/connectors/trello/index.d.ts.map +1 -1
- package/dist/connectors/trello/index.js +88 -39
- package/dist/connectors/trello/index.js.map +1 -1
- package/dist/gateways/base-gateway.d.ts +9 -33
- package/dist/gateways/base-gateway.d.ts.map +1 -1
- package/dist/gateways/base-gateway.js +7 -28
- package/dist/gateways/base-gateway.js.map +1 -1
- package/dist/gateways/conversation-record.d.ts +52 -0
- package/dist/gateways/conversation-record.d.ts.map +1 -0
- package/dist/gateways/conversation-record.js +220 -0
- package/dist/gateways/conversation-record.js.map +1 -0
- package/dist/gateways/discord.d.ts +33 -135
- package/dist/gateways/discord.d.ts.map +1 -1
- package/dist/gateways/discord.js +258 -778
- package/dist/gateways/discord.js.map +1 -1
- package/dist/gateways/session-store.d.ts +28 -9
- package/dist/gateways/session-store.d.ts.map +1 -1
- package/dist/gateways/session-store.js +192 -60
- package/dist/gateways/session-store.js.map +1 -1
- package/dist/gateways/slack.d.ts +35 -105
- package/dist/gateways/slack.d.ts.map +1 -1
- package/dist/gateways/slack.js +282 -440
- package/dist/gateways/slack.js.map +1 -1
- package/dist/gateways/telegram-attachments.d.ts +32 -0
- package/dist/gateways/telegram-attachments.d.ts.map +1 -0
- package/dist/gateways/telegram-attachments.js +113 -0
- package/dist/gateways/telegram-attachments.js.map +1 -0
- package/dist/gateways/telegram-errors.d.ts +3 -0
- package/dist/gateways/telegram-errors.d.ts.map +1 -0
- package/dist/gateways/telegram-errors.js +10 -0
- package/dist/gateways/telegram-errors.js.map +1 -0
- package/dist/gateways/telegram-format.d.ts.map +1 -1
- package/dist/gateways/telegram-format.js +9 -1
- package/dist/gateways/telegram-format.js.map +1 -1
- package/dist/gateways/telegram-message-ledger.d.ts +31 -25
- package/dist/gateways/telegram-message-ledger.d.ts.map +1 -1
- package/dist/gateways/telegram-message-ledger.js +122 -146
- package/dist/gateways/telegram-message-ledger.js.map +1 -1
- package/dist/gateways/telegram-response-presenter.d.ts +3 -1
- package/dist/gateways/telegram-response-presenter.d.ts.map +1 -1
- package/dist/gateways/telegram-response-presenter.js +14 -0
- package/dist/gateways/telegram-response-presenter.js.map +1 -1
- package/dist/gateways/telegram-text-entities.d.ts +2 -3
- package/dist/gateways/telegram-text-entities.d.ts.map +1 -1
- package/dist/gateways/telegram-text-entities.js +4 -5
- package/dist/gateways/telegram-text-entities.js.map +1 -1
- package/dist/gateways/telegram.d.ts +39 -101
- package/dist/gateways/telegram.d.ts.map +1 -1
- package/dist/gateways/telegram.js +406 -919
- package/dist/gateways/telegram.js.map +1 -1
- package/dist/gateways/turn-contract.d.ts +27 -87
- package/dist/gateways/turn-contract.d.ts.map +1 -1
- package/dist/gateways/types.d.ts +14 -299
- package/dist/gateways/types.d.ts.map +1 -1
- package/dist/gateways/types.js +0 -3
- package/dist/gateways/types.js.map +1 -1
- package/dist/index.d.ts +1 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -23
- package/dist/index.js.map +1 -1
- package/dist/operator/board-read-views.d.ts +8 -10
- package/dist/operator/board-read-views.d.ts.map +1 -1
- package/dist/operator/board-read-views.js +4 -13
- package/dist/operator/board-read-views.js.map +1 -1
- package/dist/operator/board-slot-instructions.d.ts +10 -11
- package/dist/operator/board-slot-instructions.d.ts.map +1 -1
- package/dist/operator/board-slot-instructions.js +22 -21
- package/dist/operator/board-slot-instructions.js.map +1 -1
- package/dist/replay/import-manifest.d.ts +21 -0
- package/dist/replay/import-manifest.d.ts.map +1 -0
- package/dist/replay/import-manifest.js +157 -0
- package/dist/replay/import-manifest.js.map +1 -0
- package/dist/replay/jev-client.d.ts +35 -0
- package/dist/replay/jev-client.d.ts.map +1 -0
- package/dist/replay/jev-client.js +141 -0
- package/dist/replay/jev-client.js.map +1 -0
- package/dist/replay/kagemusha-import.d.ts +35 -0
- package/dist/replay/kagemusha-import.d.ts.map +1 -0
- package/dist/replay/kagemusha-import.js +543 -0
- package/dist/replay/kagemusha-import.js.map +1 -0
- package/dist/replay/replay-cursor.d.ts +31 -0
- package/dist/replay/replay-cursor.d.ts.map +1 -0
- package/dist/replay/replay-cursor.js +189 -0
- package/dist/replay/replay-cursor.js.map +1 -0
- package/dist/replay/replay-feeder.d.ts +55 -0
- package/dist/replay/replay-feeder.d.ts.map +1 -0
- package/dist/replay/replay-feeder.js +214 -0
- package/dist/replay/replay-feeder.js.map +1 -0
- package/dist/replay/replay-ledger.d.ts +32 -0
- package/dist/replay/replay-ledger.d.ts.map +1 -0
- package/dist/replay/replay-ledger.js +91 -0
- package/dist/replay/replay-ledger.js.map +1 -0
- package/dist/replay/replay-source-catalog.d.ts +66 -0
- package/dist/replay/replay-source-catalog.d.ts.map +1 -0
- package/dist/replay/replay-source-catalog.js +332 -0
- package/dist/replay/replay-source-catalog.js.map +1 -0
- package/dist/replay/trello-import.d.ts +33 -0
- package/dist/replay/trello-import.d.ts.map +1 -0
- package/dist/replay/trello-import.js +204 -0
- package/dist/replay/trello-import.js.map +1 -0
- package/dist/replay/window-queue.d.ts +116 -0
- package/dist/replay/window-queue.d.ts.map +1 -0
- package/dist/replay/window-queue.js +497 -0
- package/dist/replay/window-queue.js.map +1 -0
- package/dist/runtime/action-mcp-server.d.ts +30 -0
- package/dist/runtime/action-mcp-server.d.ts.map +1 -0
- package/dist/runtime/action-mcp-server.js +179 -0
- package/dist/runtime/action-mcp-server.js.map +1 -0
- package/dist/runtime/action-surface.d.ts +47 -0
- package/dist/runtime/action-surface.d.ts.map +1 -0
- package/dist/runtime/action-surface.js +182 -0
- package/dist/runtime/action-surface.js.map +1 -0
- package/dist/runtime/backend-security.d.ts +7 -0
- package/dist/runtime/backend-security.d.ts.map +1 -0
- package/dist/runtime/backend-security.js +28 -0
- package/dist/runtime/backend-security.js.map +1 -0
- package/dist/runtime/claude-caller-hook.d.ts +20 -0
- package/dist/runtime/claude-caller-hook.d.ts.map +1 -0
- package/dist/runtime/claude-caller-hook.js +41 -0
- package/dist/runtime/claude-caller-hook.js.map +1 -0
- package/dist/runtime/config.d.ts +84 -0
- package/dist/runtime/config.d.ts.map +1 -0
- package/dist/runtime/config.js +361 -0
- package/dist/runtime/config.js.map +1 -0
- package/dist/runtime/connectors.d.ts +41 -0
- package/dist/runtime/connectors.d.ts.map +1 -0
- package/dist/runtime/connectors.js +187 -0
- package/dist/runtime/connectors.js.map +1 -0
- package/dist/runtime/core-db.d.ts +8 -0
- package/dist/runtime/core-db.d.ts.map +1 -0
- package/dist/runtime/core-db.js +19 -0
- package/dist/runtime/core-db.js.map +1 -0
- package/dist/runtime/model-cache.d.ts +9 -0
- package/dist/runtime/model-cache.d.ts.map +1 -0
- package/dist/runtime/model-cache.js +22 -0
- package/dist/runtime/model-cache.js.map +1 -0
- package/dist/runtime/native-session.d.ts +80 -0
- package/dist/runtime/native-session.d.ts.map +1 -0
- package/dist/runtime/native-session.js +284 -0
- package/dist/runtime/native-session.js.map +1 -0
- package/dist/runtime/owner-policy.d.ts +10 -0
- package/dist/runtime/owner-policy.d.ts.map +1 -0
- package/dist/runtime/owner-policy.js +35 -0
- package/dist/runtime/owner-policy.js.map +1 -0
- package/dist/runtime/owner-runtime.d.ts +78 -0
- package/dist/runtime/owner-runtime.d.ts.map +1 -0
- package/dist/runtime/owner-runtime.js +255 -0
- package/dist/runtime/owner-runtime.js.map +1 -0
- package/dist/runtime/owner-system-prompt.d.ts +17 -0
- package/dist/runtime/owner-system-prompt.d.ts.map +1 -0
- package/dist/runtime/owner-system-prompt.js +122 -0
- package/dist/runtime/owner-system-prompt.js.map +1 -0
- package/dist/runtime/recent-owner-exchanges.d.ts +10 -0
- package/dist/runtime/recent-owner-exchanges.d.ts.map +1 -0
- package/dist/runtime/recent-owner-exchanges.js +53 -0
- package/dist/runtime/recent-owner-exchanges.js.map +1 -0
- package/dist/runtime/report-prompts.d.ts +14 -0
- package/dist/runtime/report-prompts.d.ts.map +1 -0
- package/dist/runtime/report-prompts.js +69 -0
- package/dist/runtime/report-prompts.js.map +1 -0
- package/dist/runtime/report-scheduler.d.ts +24 -0
- package/dist/runtime/report-scheduler.d.ts.map +1 -0
- package/dist/runtime/report-scheduler.js +97 -0
- package/dist/runtime/report-scheduler.js.map +1 -0
- package/dist/runtime/session-credential.d.ts +5 -0
- package/dist/runtime/session-credential.d.ts.map +1 -0
- package/dist/runtime/session-credential.js +19 -0
- package/dist/runtime/session-credential.js.map +1 -0
- package/dist/runtime/stimulus-delivery.d.ts +85 -0
- package/dist/runtime/stimulus-delivery.d.ts.map +1 -0
- package/dist/runtime/stimulus-delivery.js +590 -0
- package/dist/runtime/stimulus-delivery.js.map +1 -0
- package/dist/runtime/timezone.d.ts +13 -0
- package/dist/runtime/timezone.d.ts.map +1 -0
- package/dist/runtime/timezone.js +119 -0
- package/dist/runtime/timezone.js.map +1 -0
- package/dist/sqlite.d.ts +7 -32
- package/dist/sqlite.d.ts.map +1 -1
- package/dist/sqlite.js +10 -48
- package/dist/sqlite.js.map +1 -1
- package/dist/storage/migrations/raw-item-revisions.d.ts +6 -0
- package/dist/storage/migrations/raw-item-revisions.d.ts.map +1 -0
- package/dist/storage/migrations/raw-item-revisions.js +109 -0
- package/dist/storage/migrations/raw-item-revisions.js.map +1 -0
- package/dist/storage/migrations-source.d.ts +5 -0
- package/dist/storage/migrations-source.d.ts.map +1 -0
- package/dist/storage/migrations-source.js +24 -0
- package/dist/storage/migrations-source.js.map +1 -0
- package/dist/storage/source-archive.d.ts +123 -0
- package/dist/storage/source-archive.d.ts.map +1 -0
- package/dist/storage/source-archive.js +595 -0
- package/dist/storage/source-archive.js.map +1 -0
- package/dist/storage-sqlite.d.ts +59 -0
- package/dist/storage-sqlite.d.ts.map +1 -0
- package/dist/storage-sqlite.js +75 -0
- package/dist/storage-sqlite.js.map +1 -0
- package/dist/utils/untrusted-content.d.ts +5 -35
- package/dist/utils/untrusted-content.d.ts.map +1 -1
- package/dist/utils/untrusted-content.js +16 -75
- package/dist/utils/untrusted-content.js.map +1 -1
- package/dist/wiki/obsidian-writer.d.ts +4 -2
- package/dist/wiki/obsidian-writer.d.ts.map +1 -1
- package/dist/wiki/obsidian-writer.js +85 -4
- package/dist/wiki/obsidian-writer.js.map +1 -1
- package/dist/wiki/path-safety.d.ts +2 -0
- package/dist/wiki/path-safety.d.ts.map +1 -1
- package/dist/wiki/path-safety.js +11 -3
- package/dist/wiki/path-safety.js.map +1 -1
- package/dist/wiki/wiki-edits.d.ts +17 -0
- package/dist/wiki/wiki-edits.d.ts.map +1 -0
- package/dist/wiki/wiki-edits.js +75 -0
- package/dist/wiki/wiki-edits.js.map +1 -0
- package/dist/wiki/wiki-read.d.ts +20 -11
- package/dist/wiki/wiki-read.d.ts.map +1 -1
- package/dist/wiki/wiki-read.js +87 -89
- package/dist/wiki/wiki-read.js.map +1 -1
- package/dist/wiki-artifacts/types.d.ts +1 -1
- package/dist/wiki-artifacts/types.d.ts.map +1 -1
- package/dist/wiki-artifacts/wiki-publish-adapter.d.ts.map +1 -1
- package/dist/wiki-artifacts/wiki-publish-adapter.js +13 -9
- package/dist/wiki-artifacts/wiki-publish-adapter.js.map +1 -1
- package/package.json +11 -41
- package/public/viewer/js/modules/graph.js +111 -41
- package/public/viewer/js/modules/memory.js +4 -31
- package/public/viewer/js/modules/wiki.js +0 -37
- package/public/viewer/js/utils/api.js +4 -50
- package/public/viewer/log-viewer.html +46 -0
- package/public/viewer/operator/operator.css +1 -1
- package/public/viewer/operator/operator.js +10835 -8211
- package/public/viewer/src/modules/graph.ts +133 -41
- package/public/viewer/src/modules/memory.ts +4 -31
- package/public/viewer/src/modules/wiki.ts +0 -42
- package/public/viewer/src/types/global.d.ts +1 -1
- package/public/viewer/src/utils/api.ts +7 -83
- package/public/viewer/viewer.html +7 -96
- package/dist/agent/agent-loop.d.ts +0 -289
- package/dist/agent/agent-loop.d.ts.map +0 -1
- package/dist/agent/agent-loop.js +0 -2842
- package/dist/agent/agent-loop.js.map +0 -1
- package/dist/agent/attachment-text-extractor.d.ts +0 -2
- package/dist/agent/attachment-text-extractor.d.ts.map +0 -1
- package/dist/agent/attachment-text-extractor.js +0 -128
- package/dist/agent/attachment-text-extractor.js.map +0 -1
- package/dist/agent/backend-model-policy.d.ts +0 -58
- package/dist/agent/backend-model-policy.d.ts.map +0 -1
- package/dist/agent/backend-model-policy.js +0 -167
- package/dist/agent/backend-model-policy.js.map +0 -1
- package/dist/agent/backend-model-runner-factory.d.ts +0 -27
- package/dist/agent/backend-model-runner-factory.d.ts.map +0 -1
- package/dist/agent/backend-model-runner-factory.js +0 -88
- package/dist/agent/backend-model-runner-factory.js.map +0 -1
- package/dist/agent/claude-cli-wrapper.d.ts +0 -144
- package/dist/agent/claude-cli-wrapper.d.ts.map +0 -1
- package/dist/agent/claude-cli-wrapper.js +0 -382
- package/dist/agent/claude-cli-wrapper.js.map +0 -1
- package/dist/agent/cli-arg-redaction.d.ts +0 -2
- package/dist/agent/cli-arg-redaction.d.ts.map +0 -1
- package/dist/agent/cli-arg-redaction.js +0 -18
- package/dist/agent/cli-arg-redaction.js.map +0 -1
- package/dist/agent/cline-cli-adapter.d.ts +0 -126
- package/dist/agent/cline-cli-adapter.d.ts.map +0 -1
- package/dist/agent/cline-cli-adapter.js +0 -1042
- package/dist/agent/cline-cli-adapter.js.map +0 -1
- package/dist/agent/cline-native-tool-policy.d.ts +0 -9
- package/dist/agent/cline-native-tool-policy.d.ts.map +0 -1
- package/dist/agent/cline-native-tool-policy.js +0 -61
- package/dist/agent/cline-native-tool-policy.js.map +0 -1
- package/dist/agent/code-act/completed-terminal-result.d.ts +0 -5
- package/dist/agent/code-act/completed-terminal-result.d.ts.map +0 -1
- package/dist/agent/code-act/completed-terminal-result.js +0 -83
- package/dist/agent/code-act/completed-terminal-result.js.map +0 -1
- package/dist/agent/code-act/constants.d.ts +0 -11
- package/dist/agent/code-act/constants.d.ts.map +0 -1
- package/dist/agent/code-act/constants.js +0 -64
- package/dist/agent/code-act/constants.js.map +0 -1
- package/dist/agent/code-act/host-bridge.d.ts +0 -50
- package/dist/agent/code-act/host-bridge.d.ts.map +0 -1
- package/dist/agent/code-act/host-bridge.js +0 -1405
- package/dist/agent/code-act/host-bridge.js.map +0 -1
- package/dist/agent/code-act/index.d.ts +0 -10
- package/dist/agent/code-act/index.d.ts.map +0 -1
- package/dist/agent/code-act/index.js +0 -22
- package/dist/agent/code-act/index.js.map +0 -1
- package/dist/agent/code-act/run-context-registry.d.ts +0 -36
- package/dist/agent/code-act/run-context-registry.d.ts.map +0 -1
- package/dist/agent/code-act/run-context-registry.js +0 -143
- package/dist/agent/code-act/run-context-registry.js.map +0 -1
- package/dist/agent/code-act/sandbox.d.ts +0 -25
- package/dist/agent/code-act/sandbox.d.ts.map +0 -1
- package/dist/agent/code-act/sandbox.js +0 -494
- package/dist/agent/code-act/sandbox.js.map +0 -1
- package/dist/agent/code-act/tool-catalog.d.ts +0 -30
- package/dist/agent/code-act/tool-catalog.d.ts.map +0 -1
- package/dist/agent/code-act/tool-catalog.js +0 -185
- package/dist/agent/code-act/tool-catalog.js.map +0 -1
- package/dist/agent/code-act/tool-policy.d.ts +0 -51
- package/dist/agent/code-act/tool-policy.d.ts.map +0 -1
- package/dist/agent/code-act/tool-policy.js +0 -160
- package/dist/agent/code-act/tool-policy.js.map +0 -1
- package/dist/agent/code-act/type-definition-generator.d.ts +0 -19
- package/dist/agent/code-act/type-definition-generator.d.ts.map +0 -1
- package/dist/agent/code-act/type-definition-generator.js +0 -73
- package/dist/agent/code-act/type-definition-generator.js.map +0 -1
- package/dist/agent/code-act/types.d.ts +0 -57
- package/dist/agent/code-act/types.d.ts.map +0 -1
- package/dist/agent/code-act/types.js +0 -14
- package/dist/agent/code-act/types.js.map +0 -1
- package/dist/agent/codex-app-server-process.d.ts +0 -257
- package/dist/agent/codex-app-server-process.d.ts.map +0 -1
- package/dist/agent/codex-app-server-process.js +0 -2084
- package/dist/agent/codex-app-server-process.js.map +0 -1
- package/dist/agent/codex-auth.d.ts +0 -2
- package/dist/agent/codex-auth.d.ts.map +0 -1
- package/dist/agent/codex-auth.js +0 -17
- package/dist/agent/codex-auth.js.map +0 -1
- package/dist/agent/codex-auxiliary-tools.d.ts +0 -17
- package/dist/agent/codex-auxiliary-tools.d.ts.map +0 -1
- package/dist/agent/codex-auxiliary-tools.js +0 -267
- package/dist/agent/codex-auxiliary-tools.js.map +0 -1
- package/dist/agent/codex-home.d.ts +0 -13
- package/dist/agent/codex-home.d.ts.map +0 -1
- package/dist/agent/codex-home.js +0 -644
- package/dist/agent/codex-home.js.map +0 -1
- package/dist/agent/codex-thread-registry.d.ts +0 -63
- package/dist/agent/codex-thread-registry.d.ts.map +0 -1
- package/dist/agent/codex-thread-registry.js +0 -433
- package/dist/agent/codex-thread-registry.js.map +0 -1
- package/dist/agent/content-dedup.d.ts +0 -86
- package/dist/agent/content-dedup.d.ts.map +0 -1
- package/dist/agent/content-dedup.js +0 -114
- package/dist/agent/content-dedup.js.map +0 -1
- package/dist/agent/context-compile-contract.d.ts +0 -5
- package/dist/agent/context-compile-contract.d.ts.map +0 -1
- package/dist/agent/context-compile-contract.js +0 -8
- package/dist/agent/context-compile-contract.js.map +0 -1
- package/dist/agent/context-compile-service.d.ts +0 -60
- package/dist/agent/context-compile-service.d.ts.map +0 -1
- package/dist/agent/context-compile-service.js +0 -481
- package/dist/agent/context-compile-service.js.map +0 -1
- package/dist/agent/context-prompt-builder.d.ts +0 -24
- package/dist/agent/context-prompt-builder.d.ts.map +0 -1
- package/dist/agent/context-prompt-builder.js +0 -61
- package/dist/agent/context-prompt-builder.js.map +0 -1
- package/dist/agent/contract-extractor.d.ts +0 -124
- package/dist/agent/contract-extractor.d.ts.map +0 -1
- package/dist/agent/contract-extractor.js +0 -465
- package/dist/agent/contract-extractor.js.map +0 -1
- package/dist/agent/drive-tools.d.ts +0 -83
- package/dist/agent/drive-tools.d.ts.map +0 -1
- package/dist/agent/drive-tools.js +0 -338
- package/dist/agent/drive-tools.js.map +0 -1
- package/dist/agent/gateway-tool-catalog.d.ts +0 -14
- package/dist/agent/gateway-tool-catalog.d.ts.map +0 -1
- package/dist/agent/gateway-tool-catalog.js +0 -41
- package/dist/agent/gateway-tool-catalog.js.map +0 -1
- package/dist/agent/gateway-tool-executor.d.ts +0 -353
- package/dist/agent/gateway-tool-executor.d.ts.map +0 -1
- package/dist/agent/gateway-tool-executor.js +0 -5448
- package/dist/agent/gateway-tool-executor.js.map +0 -1
- package/dist/agent/gateway-tools.md +0 -166
- package/dist/agent/image-translation-tools.d.ts +0 -48
- package/dist/agent/image-translation-tools.d.ts.map +0 -1
- package/dist/agent/image-translation-tools.js +0 -274
- package/dist/agent/image-translation-tools.js.map +0 -1
- package/dist/agent/index.d.ts +0 -13
- package/dist/agent/index.d.ts.map +0 -1
- package/dist/agent/index.js +0 -18
- package/dist/agent/index.js.map +0 -1
- package/dist/agent/mama-tool-handlers.d.ts +0 -11
- package/dist/agent/mama-tool-handlers.d.ts.map +0 -1
- package/dist/agent/mama-tool-handlers.js +0 -220
- package/dist/agent/mama-tool-handlers.js.map +0 -1
- package/dist/agent/managed-agent-runtime-sync.d.ts +0 -36
- package/dist/agent/managed-agent-runtime-sync.d.ts.map +0 -1
- package/dist/agent/managed-agent-runtime-sync.js +0 -245
- package/dist/agent/managed-agent-runtime-sync.js.map +0 -1
- package/dist/agent/managed-agent-validation.d.ts +0 -4
- package/dist/agent/managed-agent-validation.d.ts.map +0 -1
- package/dist/agent/managed-agent-validation.js +0 -85
- package/dist/agent/managed-agent-validation.js.map +0 -1
- package/dist/agent/model-runner.d.ts +0 -180
- package/dist/agent/model-runner.d.ts.map +0 -1
- package/dist/agent/model-runner.js +0 -54
- package/dist/agent/model-runner.js.map +0 -1
- package/dist/agent/native-effect-observer.d.ts +0 -20
- package/dist/agent/native-effect-observer.d.ts.map +0 -1
- package/dist/agent/native-effect-observer.js +0 -86
- package/dist/agent/native-effect-observer.js.map +0 -1
- package/dist/agent/obsidian-cli-args.d.ts +0 -14
- package/dist/agent/obsidian-cli-args.d.ts.map +0 -1
- package/dist/agent/obsidian-cli-args.js +0 -57
- package/dist/agent/obsidian-cli-args.js.map +0 -1
- package/dist/agent/os-agent-capabilities.md +0 -367
- package/dist/agent/persistent-cli-adapter.d.ts +0 -173
- package/dist/agent/persistent-cli-adapter.d.ts.map +0 -1
- package/dist/agent/persistent-cli-adapter.js +0 -506
- package/dist/agent/persistent-cli-adapter.js.map +0 -1
- package/dist/agent/persistent-cli-process.d.ts +0 -392
- package/dist/agent/persistent-cli-process.d.ts.map +0 -1
- package/dist/agent/persistent-cli-process.js +0 -1568
- package/dist/agent/persistent-cli-process.js.map +0 -1
- package/dist/agent/post-tool-handler.d.ts +0 -27
- package/dist/agent/post-tool-handler.d.ts.map +0 -1
- package/dist/agent/post-tool-handler.js +0 -194
- package/dist/agent/post-tool-handler.js.map +0 -1
- package/dist/agent/pre-compact-handler.d.ts +0 -36
- package/dist/agent/pre-compact-handler.d.ts.map +0 -1
- package/dist/agent/pre-compact-handler.js +0 -162
- package/dist/agent/pre-compact-handler.js.map +0 -1
- package/dist/agent/prompt-enhancer.d.ts +0 -33
- package/dist/agent/prompt-enhancer.d.ts.map +0 -1
- package/dist/agent/prompt-enhancer.js +0 -811
- package/dist/agent/prompt-enhancer.js.map +0 -1
- package/dist/agent/prompt-size-monitor.d.ts +0 -104
- package/dist/agent/prompt-size-monitor.d.ts.map +0 -1
- package/dist/agent/prompt-size-monitor.js +0 -192
- package/dist/agent/prompt-size-monitor.js.map +0 -1
- package/dist/agent/role-manager.d.ts +0 -122
- package/dist/agent/role-manager.d.ts.map +0 -1
- package/dist/agent/role-manager.js +0 -300
- package/dist/agent/role-manager.js.map +0 -1
- package/dist/agent/session-pool.d.ts +0 -176
- package/dist/agent/session-pool.d.ts.map +0 -1
- package/dist/agent/session-pool.js +0 -368
- package/dist/agent/session-pool.js.map +0 -1
- package/dist/agent/skill-loader.d.ts +0 -87
- package/dist/agent/skill-loader.d.ts.map +0 -1
- package/dist/agent/skill-loader.js +0 -461
- package/dist/agent/skill-loader.js.map +0 -1
- package/dist/agent/stop-continuation-handler.d.ts +0 -106
- package/dist/agent/stop-continuation-handler.d.ts.map +0 -1
- package/dist/agent/stop-continuation-handler.js +0 -230
- package/dist/agent/stop-continuation-handler.js.map +0 -1
- package/dist/agent/subagent-run-reconcile.d.ts +0 -23
- package/dist/agent/subagent-run-reconcile.d.ts.map +0 -1
- package/dist/agent/subagent-run-reconcile.js +0 -56
- package/dist/agent/subagent-run-reconcile.js.map +0 -1
- package/dist/agent/temporal-code-act-breaker.d.ts +0 -26
- package/dist/agent/temporal-code-act-breaker.d.ts.map +0 -1
- package/dist/agent/temporal-code-act-breaker.js +0 -94
- package/dist/agent/temporal-code-act-breaker.js.map +0 -1
- package/dist/agent/token-estimator.d.ts +0 -25
- package/dist/agent/token-estimator.d.ts.map +0 -1
- package/dist/agent/token-estimator.js +0 -75
- package/dist/agent/token-estimator.js.map +0 -1
- package/dist/agent/tool-registry.d.ts +0 -81
- package/dist/agent/tool-registry.d.ts.map +0 -1
- package/dist/agent/tool-registry.js +0 -876
- package/dist/agent/tool-registry.js.map +0 -1
- package/dist/agent/types.d.ts +0 -1261
- package/dist/agent/types.d.ts.map +0 -1
- package/dist/agent/types.js +0 -60
- package/dist/agent/types.js.map +0 -1
- package/dist/agent/yaml-frontmatter.d.ts +0 -75
- package/dist/agent/yaml-frontmatter.d.ts.map +0 -1
- package/dist/agent/yaml-frontmatter.js +0 -166
- package/dist/agent/yaml-frontmatter.js.map +0 -1
- package/dist/api/agent-context-handler.d.ts +0 -10
- package/dist/api/agent-context-handler.d.ts.map +0 -1
- package/dist/api/agent-context-handler.js +0 -216
- package/dist/api/agent-context-handler.js.map +0 -1
- package/dist/api/agent-graph-handler.d.ts +0 -18
- package/dist/api/agent-graph-handler.d.ts.map +0 -1
- package/dist/api/agent-graph-handler.js +0 -569
- package/dist/api/agent-graph-handler.js.map +0 -1
- package/dist/api/agent-handler.d.ts +0 -36
- package/dist/api/agent-handler.d.ts.map +0 -1
- package/dist/api/agent-handler.js +0 -223
- package/dist/api/agent-handler.js.map +0 -1
- package/dist/api/agent-raw-handler.d.ts +0 -70
- package/dist/api/agent-raw-handler.d.ts.map +0 -1
- package/dist/api/agent-raw-handler.js +0 -262
- package/dist/api/agent-raw-handler.js.map +0 -1
- package/dist/api/agent-situation-handler.d.ts +0 -14
- package/dist/api/agent-situation-handler.d.ts.map +0 -1
- package/dist/api/agent-situation-handler.js +0 -383
- package/dist/api/agent-situation-handler.js.map +0 -1
- package/dist/api/connector-feed-handler.d.ts +0 -42
- package/dist/api/connector-feed-handler.d.ts.map +0 -1
- package/dist/api/connector-feed-handler.js +0 -180
- package/dist/api/connector-feed-handler.js.map +0 -1
- package/dist/api/cron-handler.d.ts +0 -44
- package/dist/api/cron-handler.d.ts.map +0 -1
- package/dist/api/cron-handler.js +0 -261
- package/dist/api/cron-handler.js.map +0 -1
- package/dist/api/entity-audit-handler.d.ts +0 -12
- package/dist/api/entity-audit-handler.d.ts.map +0 -1
- package/dist/api/entity-audit-handler.js +0 -119
- package/dist/api/entity-audit-handler.js.map +0 -1
- package/dist/api/entity-audit-queue.d.ts +0 -74
- package/dist/api/entity-audit-queue.d.ts.map +0 -1
- package/dist/api/entity-audit-queue.js +0 -123
- package/dist/api/entity-audit-queue.js.map +0 -1
- package/dist/api/entity-audit-runner.d.ts +0 -9
- package/dist/api/entity-audit-runner.d.ts.map +0 -1
- package/dist/api/entity-audit-runner.js +0 -240
- package/dist/api/entity-audit-runner.js.map +0 -1
- package/dist/api/entity-review-handler.d.ts +0 -18
- package/dist/api/entity-review-handler.d.ts.map +0 -1
- package/dist/api/entity-review-handler.js +0 -521
- package/dist/api/entity-review-handler.js.map +0 -1
- package/dist/api/error-handler.d.ts +0 -22
- package/dist/api/error-handler.d.ts.map +0 -1
- package/dist/api/error-handler.js +0 -104
- package/dist/api/error-handler.js.map +0 -1
- package/dist/api/graph-api-types.d.ts +0 -105
- package/dist/api/graph-api-types.d.ts.map +0 -1
- package/dist/api/graph-api-types.js +0 -6
- package/dist/api/graph-api-types.js.map +0 -1
- package/dist/api/graph-api.d.ts +0 -42
- package/dist/api/graph-api.d.ts.map +0 -1
- package/dist/api/graph-api.js +0 -3460
- package/dist/api/graph-api.js.map +0 -1
- package/dist/api/heartbeat-handler.d.ts +0 -49
- package/dist/api/heartbeat-handler.d.ts.map +0 -1
- package/dist/api/heartbeat-handler.js +0 -106
- package/dist/api/heartbeat-handler.js.map +0 -1
- package/dist/api/index.d.ts +0 -145
- package/dist/api/index.d.ts.map +0 -1
- package/dist/api/index.js +0 -448
- package/dist/api/index.js.map +0 -1
- package/dist/api/intelligence-handler.d.ts +0 -116
- package/dist/api/intelligence-handler.d.ts.map +0 -1
- package/dist/api/intelligence-handler.js +0 -280
- package/dist/api/intelligence-handler.js.map +0 -1
- package/dist/api/memory-provenance-handler.d.ts +0 -3
- package/dist/api/memory-provenance-handler.d.ts.map +0 -1
- package/dist/api/memory-provenance-handler.js +0 -120
- package/dist/api/memory-provenance-handler.js.map +0 -1
- package/dist/api/operator-handler.d.ts +0 -19
- package/dist/api/operator-handler.d.ts.map +0 -1
- package/dist/api/operator-handler.js +0 -106
- package/dist/api/operator-handler.js.map +0 -1
- package/dist/api/operator-summary.d.ts +0 -3
- package/dist/api/operator-summary.d.ts.map +0 -1
- package/dist/api/operator-summary.js +0 -72
- package/dist/api/operator-summary.js.map +0 -1
- package/dist/api/operator-tasks-handler.d.ts +0 -7
- package/dist/api/operator-tasks-handler.d.ts.map +0 -1
- package/dist/api/operator-tasks-handler.js +0 -192
- package/dist/api/operator-tasks-handler.js.map +0 -1
- package/dist/api/runtime-status-handler.d.ts +0 -42
- package/dist/api/runtime-status-handler.d.ts.map +0 -1
- package/dist/api/runtime-status-handler.js +0 -56
- package/dist/api/runtime-status-handler.js.map +0 -1
- package/dist/api/skills-handler.d.ts +0 -12
- package/dist/api/skills-handler.d.ts.map +0 -1
- package/dist/api/skills-handler.js +0 -156
- package/dist/api/skills-handler.js.map +0 -1
- package/dist/api/token-handler.d.ts +0 -33
- package/dist/api/token-handler.d.ts.map +0 -1
- package/dist/api/token-handler.js +0 -118
- package/dist/api/token-handler.js.map +0 -1
- package/dist/api/types.d.ts +0 -155
- package/dist/api/types.d.ts.map +0 -1
- package/dist/api/types.js +0 -49
- package/dist/api/types.js.map +0 -1
- package/dist/api/ui-command-handler.d.ts +0 -48
- package/dist/api/ui-command-handler.d.ts.map +0 -1
- package/dist/api/ui-command-handler.js +0 -160
- package/dist/api/ui-command-handler.js.map +0 -1
- package/dist/api/upload-handler.d.ts +0 -12
- package/dist/api/upload-handler.d.ts.map +0 -1
- package/dist/api/upload-handler.js +0 -321
- package/dist/api/upload-handler.js.map +0 -1
- package/dist/api/wiki-handler.d.ts +0 -19
- package/dist/api/wiki-handler.d.ts.map +0 -1
- package/dist/api/wiki-handler.js +0 -136
- package/dist/api/wiki-handler.js.map +0 -1
- package/dist/api/worker-envelope.d.ts +0 -26
- package/dist/api/worker-envelope.d.ts.map +0 -1
- package/dist/api/worker-envelope.js +0 -176
- package/dist/api/worker-envelope.js.map +0 -1
- package/dist/auth/claude-client.d.ts +0 -12
- package/dist/auth/claude-client.d.ts.map +0 -1
- package/dist/auth/claude-client.js +0 -36
- package/dist/auth/claude-client.js.map +0 -1
- package/dist/auth/claude-code-auth.d.ts +0 -12
- package/dist/auth/claude-code-auth.d.ts.map +0 -1
- package/dist/auth/claude-code-auth.js +0 -85
- package/dist/auth/claude-code-auth.js.map +0 -1
- package/dist/auth/index.d.ts +0 -9
- package/dist/auth/index.d.ts.map +0 -1
- package/dist/auth/index.js +0 -13
- package/dist/auth/index.js.map +0 -1
- package/dist/auth/oauth-manager.d.ts +0 -59
- package/dist/auth/oauth-manager.d.ts.map +0 -1
- package/dist/auth/oauth-manager.js +0 -252
- package/dist/auth/oauth-manager.js.map +0 -1
- package/dist/auth/types.d.ts +0 -92
- package/dist/auth/types.d.ts.map +0 -1
- package/dist/auth/types.js +0 -23
- package/dist/auth/types.js.map +0 -1
- package/dist/cli/commands/connector.d.ts +0 -14
- package/dist/cli/commands/connector.d.ts.map +0 -1
- package/dist/cli/commands/connector.js +0 -174
- package/dist/cli/commands/connector.js.map +0 -1
- package/dist/cli/commands/gateway.d.ts +0 -18
- package/dist/cli/commands/gateway.d.ts.map +0 -1
- package/dist/cli/commands/gateway.js +0 -144
- package/dist/cli/commands/gateway.js.map +0 -1
- package/dist/cli/commands/report.d.ts +0 -14
- package/dist/cli/commands/report.d.ts.map +0 -1
- package/dist/cli/commands/report.js +0 -102
- package/dist/cli/commands/report.js.map +0 -1
- package/dist/cli/commands/run.d.ts +0 -19
- package/dist/cli/commands/run.d.ts.map +0 -1
- package/dist/cli/commands/run.js +0 -123
- package/dist/cli/commands/run.js.map +0 -1
- package/dist/cli/commands/setup.d.ts +0 -3
- package/dist/cli/commands/setup.d.ts.map +0 -1
- package/dist/cli/commands/setup.js +0 -9
- package/dist/cli/commands/setup.js.map +0 -1
- package/dist/cli/commands/start.d.ts +0 -263
- package/dist/cli/commands/start.d.ts.map +0 -1
- package/dist/cli/commands/start.js +0 -2230
- package/dist/cli/commands/start.js.map +0 -1
- package/dist/cli/commands/status.d.ts +0 -18
- package/dist/cli/commands/status.d.ts.map +0 -1
- package/dist/cli/commands/status.js +0 -326
- package/dist/cli/commands/status.js.map +0 -1
- package/dist/cli/commands/stop.d.ts +0 -28
- package/dist/cli/commands/stop.d.ts.map +0 -1
- package/dist/cli/commands/stop.js +0 -597
- package/dist/cli/commands/stop.js.map +0 -1
- package/dist/cli/config/config-manager.d.ts +0 -84
- package/dist/cli/config/config-manager.d.ts.map +0 -1
- package/dist/cli/config/config-manager.js +0 -886
- package/dist/cli/config/config-manager.js.map +0 -1
- package/dist/cli/config/types.d.ts +0 -633
- package/dist/cli/config/types.d.ts.map +0 -1
- package/dist/cli/config/types.js +0 -247
- package/dist/cli/config/types.js.map +0 -1
- package/dist/cli/runtime/agent-loop-init.d.ts +0 -53
- package/dist/cli/runtime/agent-loop-init.d.ts.map +0 -1
- package/dist/cli/runtime/agent-loop-init.js +0 -368
- package/dist/cli/runtime/agent-loop-init.js.map +0 -1
- package/dist/cli/runtime/api-routes-init.d.ts +0 -84
- package/dist/cli/runtime/api-routes-init.d.ts.map +0 -1
- package/dist/cli/runtime/api-routes-init.js +0 -1542
- package/dist/cli/runtime/api-routes-init.js.map +0 -1
- package/dist/cli/runtime/api-server-init.d.ts +0 -59
- package/dist/cli/runtime/api-server-init.d.ts.map +0 -1
- package/dist/cli/runtime/api-server-init.js +0 -159
- package/dist/cli/runtime/api-server-init.js.map +0 -1
- package/dist/cli/runtime/code-act-executor.d.ts +0 -14
- package/dist/cli/runtime/code-act-executor.d.ts.map +0 -1
- package/dist/cli/runtime/code-act-executor.js +0 -108
- package/dist/cli/runtime/code-act-executor.js.map +0 -1
- package/dist/cli/runtime/connector-bootstrap.d.ts +0 -8
- package/dist/cli/runtime/connector-bootstrap.d.ts.map +0 -1
- package/dist/cli/runtime/connector-bootstrap.js +0 -14
- package/dist/cli/runtime/connector-bootstrap.js.map +0 -1
- package/dist/cli/runtime/connector-init.d.ts +0 -64
- package/dist/cli/runtime/connector-init.d.ts.map +0 -1
- package/dist/cli/runtime/connector-init.js +0 -237
- package/dist/cli/runtime/connector-init.js.map +0 -1
- package/dist/cli/runtime/daemon.d.ts +0 -38
- package/dist/cli/runtime/daemon.d.ts.map +0 -1
- package/dist/cli/runtime/daemon.js +0 -263
- package/dist/cli/runtime/daemon.js.map +0 -1
- package/dist/cli/runtime/envelope-bootstrap.d.ts +0 -17
- package/dist/cli/runtime/envelope-bootstrap.d.ts.map +0 -1
- package/dist/cli/runtime/envelope-bootstrap.js +0 -56
- package/dist/cli/runtime/envelope-bootstrap.js.map +0 -1
- package/dist/cli/runtime/gateway-init.d.ts +0 -55
- package/dist/cli/runtime/gateway-init.d.ts.map +0 -1
- package/dist/cli/runtime/gateway-init.js +0 -196
- package/dist/cli/runtime/gateway-init.js.map +0 -1
- package/dist/cli/runtime/gateway-wiring.d.ts +0 -51
- package/dist/cli/runtime/gateway-wiring.d.ts.map +0 -1
- package/dist/cli/runtime/gateway-wiring.js +0 -215
- package/dist/cli/runtime/gateway-wiring.js.map +0 -1
- package/dist/cli/runtime/mama-core-init.d.ts +0 -42
- package/dist/cli/runtime/mama-core-init.d.ts.map +0 -1
- package/dist/cli/runtime/mama-core-init.js +0 -211
- package/dist/cli/runtime/mama-core-init.js.map +0 -1
- package/dist/cli/runtime/message-router-config.d.ts +0 -4
- package/dist/cli/runtime/message-router-config.d.ts.map +0 -1
- package/dist/cli/runtime/message-router-config.js +0 -11
- package/dist/cli/runtime/message-router-config.js.map +0 -1
- package/dist/cli/runtime/metrics-init.d.ts +0 -28
- package/dist/cli/runtime/metrics-init.d.ts.map +0 -1
- package/dist/cli/runtime/metrics-init.js +0 -131
- package/dist/cli/runtime/metrics-init.js.map +0 -1
- package/dist/cli/runtime/owner-backfill.d.ts +0 -24
- package/dist/cli/runtime/owner-backfill.d.ts.map +0 -1
- package/dist/cli/runtime/owner-backfill.js +0 -24
- package/dist/cli/runtime/owner-backfill.js.map +0 -1
- package/dist/cli/runtime/scheduler-init.d.ts +0 -60
- package/dist/cli/runtime/scheduler-init.d.ts.map +0 -1
- package/dist/cli/runtime/scheduler-init.js +0 -173
- package/dist/cli/runtime/scheduler-init.js.map +0 -1
- package/dist/cli/runtime/server-start.d.ts +0 -23
- package/dist/cli/runtime/server-start.d.ts.map +0 -1
- package/dist/cli/runtime/server-start.js +0 -174
- package/dist/cli/runtime/server-start.js.map +0 -1
- package/dist/cli/runtime/shutdown.d.ts +0 -46
- package/dist/cli/runtime/shutdown.d.ts.map +0 -1
- package/dist/cli/runtime/shutdown.js +0 -240
- package/dist/cli/runtime/shutdown.js.map +0 -1
- package/dist/cli/runtime/temporal-init.d.ts +0 -26
- package/dist/cli/runtime/temporal-init.d.ts.map +0 -1
- package/dist/cli/runtime/temporal-init.js +0 -57
- package/dist/cli/runtime/temporal-init.js.map +0 -1
- package/dist/cli/runtime/types.d.ts +0 -127
- package/dist/cli/runtime/types.d.ts.map +0 -1
- package/dist/cli/runtime/types.js +0 -12
- package/dist/cli/runtime/types.js.map +0 -1
- package/dist/cli/runtime/utilities.d.ts +0 -72
- package/dist/cli/runtime/utilities.d.ts.map +0 -1
- package/dist/cli/runtime/utilities.js +0 -472
- package/dist/cli/runtime/utilities.js.map +0 -1
- package/dist/cli/shutdown-utils.d.ts +0 -6
- package/dist/cli/shutdown-utils.d.ts.map +0 -1
- package/dist/cli/shutdown-utils.js +0 -11
- package/dist/cli/shutdown-utils.js.map +0 -1
- package/dist/cli/utils/pid-manager.d.ts +0 -56
- package/dist/cli/utils/pid-manager.d.ts.map +0 -1
- package/dist/cli/utils/pid-manager.js +0 -141
- package/dist/cli/utils/pid-manager.js.map +0 -1
- package/dist/concurrency/index.d.ts +0 -13
- package/dist/concurrency/index.d.ts.map +0 -1
- package/dist/concurrency/index.js +0 -22
- package/dist/concurrency/index.js.map +0 -1
- package/dist/concurrency/lane-manager.d.ts +0 -113
- package/dist/concurrency/lane-manager.d.ts.map +0 -1
- package/dist/concurrency/lane-manager.js +0 -248
- package/dist/concurrency/lane-manager.js.map +0 -1
- package/dist/concurrency/session-key.d.ts +0 -41
- package/dist/concurrency/session-key.d.ts.map +0 -1
- package/dist/concurrency/session-key.js +0 -61
- package/dist/concurrency/session-key.js.map +0 -1
- package/dist/concurrency/types.d.ts +0 -73
- package/dist/concurrency/types.d.ts.map +0 -1
- package/dist/concurrency/types.js +0 -16
- package/dist/concurrency/types.js.map +0 -1
- package/dist/connectors/framework/raw-store.d.ts +0 -63
- package/dist/connectors/framework/raw-store.d.ts.map +0 -1
- package/dist/connectors/framework/raw-store.js +0 -356
- package/dist/connectors/framework/raw-store.js.map +0 -1
- package/dist/connectors/kagemusha/query-tools.d.ts +0 -146
- package/dist/connectors/kagemusha/query-tools.d.ts.map +0 -1
- package/dist/connectors/kagemusha/query-tools.js +0 -467
- package/dist/connectors/kagemusha/query-tools.js.map +0 -1
- package/dist/connectors/private-connector-policy.d.ts +0 -174
- package/dist/connectors/private-connector-policy.d.ts.map +0 -1
- package/dist/connectors/private-connector-policy.js +0 -299
- package/dist/connectors/private-connector-policy.js.map +0 -1
- package/dist/connectors/private-prompt-overlay.d.ts +0 -17
- package/dist/connectors/private-prompt-overlay.d.ts.map +0 -1
- package/dist/connectors/private-prompt-overlay.js +0 -156
- package/dist/connectors/private-prompt-overlay.js.map +0 -1
- package/dist/connectors/trello/query-tools.d.ts +0 -243
- package/dist/connectors/trello/query-tools.d.ts.map +0 -1
- package/dist/connectors/trello/query-tools.js +0 -661
- package/dist/connectors/trello/query-tools.js.map +0 -1
- package/dist/db/agent-store.d.ts +0 -145
- package/dist/db/agent-store.d.ts.map +0 -1
- package/dist/db/agent-store.js +0 -279
- package/dist/db/agent-store.js.map +0 -1
- package/dist/db/migrations/agent-activity-envelope-hash.d.ts +0 -3
- package/dist/db/migrations/agent-activity-envelope-hash.d.ts.map +0 -1
- package/dist/db/migrations/agent-activity-envelope-hash.js +0 -29
- package/dist/db/migrations/agent-activity-envelope-hash.js.map +0 -1
- package/dist/db/migrations/agent-activity-gateway-call-id.d.ts +0 -3
- package/dist/db/migrations/agent-activity-gateway-call-id.d.ts.map +0 -1
- package/dist/db/migrations/agent-activity-gateway-call-id.js +0 -17
- package/dist/db/migrations/agent-activity-gateway-call-id.js.map +0 -1
- package/dist/db/migrations/agent-activity-tool-name.d.ts +0 -8
- package/dist/db/migrations/agent-activity-tool-name.d.ts.map +0 -1
- package/dist/db/migrations/agent-activity-tool-name.js +0 -24
- package/dist/db/migrations/agent-activity-tool-name.js.map +0 -1
- package/dist/db/migrations/agent-activity-validation-columns.d.ts +0 -3
- package/dist/db/migrations/agent-activity-validation-columns.d.ts.map +0 -1
- package/dist/db/migrations/agent-activity-validation-columns.js +0 -22
- package/dist/db/migrations/agent-activity-validation-columns.js.map +0 -1
- package/dist/db/migrations/agent-metrics-response-avg.d.ts +0 -3
- package/dist/db/migrations/agent-metrics-response-avg.d.ts.map +0 -1
- package/dist/db/migrations/agent-metrics-response-avg.js +0 -19
- package/dist/db/migrations/agent-metrics-response-avg.js.map +0 -1
- package/dist/db/migrations/agent-store-tables.d.ts +0 -3
- package/dist/db/migrations/agent-store-tables.d.ts.map +0 -1
- package/dist/db/migrations/agent-store-tables.js +0 -64
- package/dist/db/migrations/agent-store-tables.js.map +0 -1
- package/dist/db/migrations/envelope-tables.d.ts +0 -3
- package/dist/db/migrations/envelope-tables.d.ts.map +0 -1
- package/dist/db/migrations/envelope-tables.js +0 -38
- package/dist/db/migrations/envelope-tables.js.map +0 -1
- package/dist/db/migrations/operator-owner-action-candidates.d.ts +0 -19
- package/dist/db/migrations/operator-owner-action-candidates.d.ts.map +0 -1
- package/dist/db/migrations/operator-owner-action-candidates.js +0 -254
- package/dist/db/migrations/operator-owner-action-candidates.js.map +0 -1
- package/dist/db/migrations/operator-procedures.d.ts +0 -4
- package/dist/db/migrations/operator-procedures.d.ts.map +0 -1
- package/dist/db/migrations/operator-procedures.js +0 -63
- package/dist/db/migrations/operator-procedures.js.map +0 -1
- package/dist/db/migrations/operator-task-classification.d.ts +0 -15
- package/dist/db/migrations/operator-task-classification.d.ts.map +0 -1
- package/dist/db/migrations/operator-task-classification.js +0 -25
- package/dist/db/migrations/operator-task-classification.js.map +0 -1
- package/dist/db/migrations/operator-task-external-lifecycle.d.ts +0 -17
- package/dist/db/migrations/operator-task-external-lifecycle.d.ts.map +0 -1
- package/dist/db/migrations/operator-task-external-lifecycle.js +0 -199
- package/dist/db/migrations/operator-task-external-lifecycle.js.map +0 -1
- package/dist/db/migrations/operator-task-review-lifecycle.d.ts +0 -4
- package/dist/db/migrations/operator-task-review-lifecycle.d.ts.map +0 -1
- package/dist/db/migrations/operator-task-review-lifecycle.js +0 -17
- package/dist/db/migrations/operator-task-review-lifecycle.js.map +0 -1
- package/dist/db/migrations/operator-task-temporal.d.ts +0 -8
- package/dist/db/migrations/operator-task-temporal.d.ts.map +0 -1
- package/dist/db/migrations/operator-task-temporal.js +0 -214
- package/dist/db/migrations/operator-task-temporal.js.map +0 -1
- package/dist/db/migrations/owner-action-effects.d.ts +0 -18
- package/dist/db/migrations/owner-action-effects.d.ts.map +0 -1
- package/dist/db/migrations/owner-action-effects.js +0 -111
- package/dist/db/migrations/owner-action-effects.js.map +0 -1
- package/dist/db/migrations/raw-item-revisions.d.ts +0 -4
- package/dist/db/migrations/raw-item-revisions.d.ts.map +0 -1
- package/dist/db/migrations/raw-item-revisions.js +0 -29
- package/dist/db/migrations/raw-item-revisions.js.map +0 -1
- package/dist/db/migrations/token-usage-agent-version.d.ts +0 -3
- package/dist/db/migrations/token-usage-agent-version.d.ts.map +0 -1
- package/dist/db/migrations/token-usage-agent-version.js +0 -16
- package/dist/db/migrations/token-usage-agent-version.js.map +0 -1
- package/dist/db/migrations/validation-session-tables.d.ts +0 -3
- package/dist/db/migrations/validation-session-tables.d.ts.map +0 -1
- package/dist/db/migrations/validation-session-tables.js +0 -59
- package/dist/db/migrations/validation-session-tables.js.map +0 -1
- package/dist/envelope/authority.d.ts +0 -15
- package/dist/envelope/authority.d.ts.map +0 -1
- package/dist/envelope/authority.js +0 -95
- package/dist/envelope/authority.js.map +0 -1
- package/dist/envelope/canonical.d.ts +0 -14
- package/dist/envelope/canonical.d.ts.map +0 -1
- package/dist/envelope/canonical.js +0 -68
- package/dist/envelope/canonical.js.map +0 -1
- package/dist/envelope/enforcer.d.ts +0 -30
- package/dist/envelope/enforcer.d.ts.map +0 -1
- package/dist/envelope/enforcer.js +0 -286
- package/dist/envelope/enforcer.js.map +0 -1
- package/dist/envelope/expiry.d.ts +0 -2
- package/dist/envelope/expiry.d.ts.map +0 -1
- package/dist/envelope/expiry.js +0 -15
- package/dist/envelope/expiry.js.map +0 -1
- package/dist/envelope/index.d.ts +0 -9
- package/dist/envelope/index.d.ts.map +0 -1
- package/dist/envelope/index.js +0 -25
- package/dist/envelope/index.js.map +0 -1
- package/dist/envelope/key-provider.d.ts +0 -7
- package/dist/envelope/key-provider.d.ts.map +0 -1
- package/dist/envelope/key-provider.js +0 -137
- package/dist/envelope/key-provider.js.map +0 -1
- package/dist/envelope/reactive-config.d.ts +0 -24
- package/dist/envelope/reactive-config.d.ts.map +0 -1
- package/dist/envelope/reactive-config.js +0 -198
- package/dist/envelope/reactive-config.js.map +0 -1
- package/dist/envelope/run-guard.d.ts +0 -10
- package/dist/envelope/run-guard.d.ts.map +0 -1
- package/dist/envelope/run-guard.js +0 -19
- package/dist/envelope/run-guard.js.map +0 -1
- package/dist/envelope/signature.d.ts +0 -15
- package/dist/envelope/signature.d.ts.map +0 -1
- package/dist/envelope/signature.js +0 -75
- package/dist/envelope/signature.js.map +0 -1
- package/dist/envelope/store.d.ts +0 -16
- package/dist/envelope/store.d.ts.map +0 -1
- package/dist/envelope/store.js +0 -118
- package/dist/envelope/store.js.map +0 -1
- package/dist/envelope/tool-connector-scope.d.ts +0 -35
- package/dist/envelope/tool-connector-scope.d.ts.map +0 -1
- package/dist/envelope/tool-connector-scope.js +0 -61
- package/dist/envelope/tool-connector-scope.js.map +0 -1
- package/dist/envelope/types.d.ts +0 -60
- package/dist/envelope/types.d.ts.map +0 -1
- package/dist/envelope/types.js +0 -9
- package/dist/envelope/types.js.map +0 -1
- package/dist/evidence/effects.d.ts +0 -125
- package/dist/evidence/effects.d.ts.map +0 -1
- package/dist/evidence/effects.js +0 -409
- package/dist/evidence/effects.js.map +0 -1
- package/dist/evidence/read.d.ts +0 -130
- package/dist/evidence/read.d.ts.map +0 -1
- package/dist/evidence/read.js +0 -167
- package/dist/evidence/read.js.map +0 -1
- package/dist/gateways/attachment-utils.d.ts +0 -28
- package/dist/gateways/attachment-utils.d.ts.map +0 -1
- package/dist/gateways/attachment-utils.js +0 -489
- package/dist/gateways/attachment-utils.js.map +0 -1
- package/dist/gateways/channel-history.d.ts +0 -148
- package/dist/gateways/channel-history.d.ts.map +0 -1
- package/dist/gateways/channel-history.js +0 -385
- package/dist/gateways/channel-history.js.map +0 -1
- package/dist/gateways/context-injector.d.ts +0 -255
- package/dist/gateways/context-injector.d.ts.map +0 -1
- package/dist/gateways/context-injector.js +0 -285
- package/dist/gateways/context-injector.js.map +0 -1
- package/dist/gateways/image-analyzer.d.ts +0 -28
- package/dist/gateways/image-analyzer.d.ts.map +0 -1
- package/dist/gateways/image-analyzer.js +0 -154
- package/dist/gateways/image-analyzer.js.map +0 -1
- package/dist/gateways/index.d.ts +0 -35
- package/dist/gateways/index.d.ts.map +0 -1
- package/dist/gateways/index.js +0 -55
- package/dist/gateways/index.js.map +0 -1
- package/dist/gateways/member-candidate-store.d.ts +0 -26
- package/dist/gateways/member-candidate-store.d.ts.map +0 -1
- package/dist/gateways/member-candidate-store.js +0 -90
- package/dist/gateways/member-candidate-store.js.map +0 -1
- package/dist/gateways/member-effective-scope.d.ts +0 -25
- package/dist/gateways/member-effective-scope.d.ts.map +0 -1
- package/dist/gateways/member-effective-scope.js +0 -323
- package/dist/gateways/member-effective-scope.js.map +0 -1
- package/dist/gateways/message-router.d.ts +0 -239
- package/dist/gateways/message-router.d.ts.map +0 -1
- package/dist/gateways/message-router.js +0 -1714
- package/dist/gateways/message-router.js.map +0 -1
- package/dist/gateways/plugin-loader.d.ts +0 -80
- package/dist/gateways/plugin-loader.d.ts.map +0 -1
- package/dist/gateways/plugin-loader.js +0 -299
- package/dist/gateways/plugin-loader.js.map +0 -1
- package/dist/gateways/principal.d.ts +0 -37
- package/dist/gateways/principal.d.ts.map +0 -1
- package/dist/gateways/principal.js +0 -95
- package/dist/gateways/principal.js.map +0 -1
- package/dist/gateways/report-projection-v1.d.ts +0 -41
- package/dist/gateways/report-projection-v1.d.ts.map +0 -1
- package/dist/gateways/report-projection-v1.js +0 -160
- package/dist/gateways/report-projection-v1.js.map +0 -1
- package/dist/gateways/telegram-media.d.ts +0 -41
- package/dist/gateways/telegram-media.d.ts.map +0 -1
- package/dist/gateways/telegram-media.js +0 -242
- package/dist/gateways/telegram-media.js.map +0 -1
- package/dist/gateways/telegram-report-context-store.d.ts +0 -131
- package/dist/gateways/telegram-report-context-store.d.ts.map +0 -1
- package/dist/gateways/telegram-report-context-store.js +0 -388
- package/dist/gateways/telegram-report-context-store.js.map +0 -1
- package/dist/gateways/tool-status-tracker.d.ts +0 -87
- package/dist/gateways/tool-status-tracker.d.ts.map +0 -1
- package/dist/gateways/tool-status-tracker.js +0 -284
- package/dist/gateways/tool-status-tracker.js.map +0 -1
- package/dist/mcp/code-act-api-client.d.ts +0 -32
- package/dist/mcp/code-act-api-client.d.ts.map +0 -1
- package/dist/mcp/code-act-api-client.js +0 -80
- package/dist/mcp/code-act-api-client.js.map +0 -1
- package/dist/mcp/code-act-mcp-config.d.ts +0 -49
- package/dist/mcp/code-act-mcp-config.d.ts.map +0 -1
- package/dist/mcp/code-act-mcp-config.js +0 -124
- package/dist/mcp/code-act-mcp-config.js.map +0 -1
- package/dist/mcp/code-act-server.d.ts +0 -14
- package/dist/mcp/code-act-server.d.ts.map +0 -1
- package/dist/mcp/code-act-server.js +0 -204
- package/dist/mcp/code-act-server.js.map +0 -1
- package/dist/mcp/code-act-terminal-transport.d.ts +0 -64
- package/dist/mcp/code-act-terminal-transport.d.ts.map +0 -1
- package/dist/mcp/code-act-terminal-transport.js +0 -176
- package/dist/mcp/code-act-terminal-transport.js.map +0 -1
- package/dist/memory/agent-notice-queue.d.ts +0 -24
- package/dist/memory/agent-notice-queue.d.ts.map +0 -1
- package/dist/memory/agent-notice-queue.js +0 -40
- package/dist/memory/agent-notice-queue.js.map +0 -1
- package/dist/memory/audit-task-queue.d.ts +0 -28
- package/dist/memory/audit-task-queue.d.ts.map +0 -1
- package/dist/memory/audit-task-queue.js +0 -29
- package/dist/memory/audit-task-queue.js.map +0 -1
- package/dist/memory/bootstrap-context.d.ts +0 -12
- package/dist/memory/bootstrap-context.d.ts.map +0 -1
- package/dist/memory/bootstrap-context.js +0 -68
- package/dist/memory/bootstrap-context.js.map +0 -1
- package/dist/memory/history-extractor.d.ts +0 -69
- package/dist/memory/history-extractor.d.ts.map +0 -1
- package/dist/memory/history-extractor.js +0 -210
- package/dist/memory/history-extractor.js.map +0 -1
- package/dist/memory/memory-agent-ack.d.ts +0 -4
- package/dist/memory/memory-agent-ack.d.ts.map +0 -1
- package/dist/memory/memory-agent-ack.js +0 -54
- package/dist/memory/memory-agent-ack.js.map +0 -1
- package/dist/memory/memory-agent-dashboard.d.ts +0 -35
- package/dist/memory/memory-agent-dashboard.d.ts.map +0 -1
- package/dist/memory/memory-agent-dashboard.js +0 -116
- package/dist/memory/memory-agent-dashboard.js.map +0 -1
- package/dist/memory/memory-logger.d.ts +0 -47
- package/dist/memory/memory-logger.d.ts.map +0 -1
- package/dist/memory/memory-logger.js +0 -126
- package/dist/memory/memory-logger.js.map +0 -1
- package/dist/memory/provenance-live.d.ts +0 -111
- package/dist/memory/provenance-live.d.ts.map +0 -1
- package/dist/memory/provenance-live.js +0 -381
- package/dist/memory/provenance-live.js.map +0 -1
- package/dist/memory/provenance-resolver.d.ts +0 -184
- package/dist/memory/provenance-resolver.d.ts.map +0 -1
- package/dist/memory/provenance-resolver.js +0 -152
- package/dist/memory/provenance-resolver.js.map +0 -1
- package/dist/memory/raw-backed-memory-ingest.d.ts +0 -26
- package/dist/memory/raw-backed-memory-ingest.d.ts.map +0 -1
- package/dist/memory/raw-backed-memory-ingest.js +0 -214
- package/dist/memory/raw-backed-memory-ingest.js.map +0 -1
- package/dist/memory/recall-bundle-formatter.d.ts +0 -26
- package/dist/memory/recall-bundle-formatter.d.ts.map +0 -1
- package/dist/memory/recall-bundle-formatter.js +0 -53
- package/dist/memory/recall-bundle-formatter.js.map +0 -1
- package/dist/memory/save-candidate-extractor.d.ts +0 -16
- package/dist/memory/save-candidate-extractor.d.ts.map +0 -1
- package/dist/memory/save-candidate-extractor.js +0 -127
- package/dist/memory/save-candidate-extractor.js.map +0 -1
- package/dist/memory/save-candidate-types.d.ts +0 -16
- package/dist/memory/save-candidate-types.d.ts.map +0 -1
- package/dist/memory/save-candidate-types.js +0 -3
- package/dist/memory/save-candidate-types.js.map +0 -1
- package/dist/memory/scope-context.d.ts +0 -12
- package/dist/memory/scope-context.d.ts.map +0 -1
- package/dist/memory/scope-context.js +0 -18
- package/dist/memory/scope-context.js.map +0 -1
- package/dist/memory/secret-filter.d.ts +0 -23
- package/dist/memory/secret-filter.d.ts.map +0 -1
- package/dist/memory/secret-filter.js +0 -90
- package/dist/memory/secret-filter.js.map +0 -1
- package/dist/multi-agent/agent-event-bus.d.ts +0 -50
- package/dist/multi-agent/agent-event-bus.d.ts.map +0 -1
- package/dist/multi-agent/agent-event-bus.js +0 -101
- package/dist/multi-agent/agent-event-bus.js.map +0 -1
- package/dist/multi-agent/agent-process-manager.d.ts +0 -205
- package/dist/multi-agent/agent-process-manager.d.ts.map +0 -1
- package/dist/multi-agent/agent-process-manager.js +0 -1191
- package/dist/multi-agent/agent-process-manager.js.map +0 -1
- package/dist/multi-agent/bmad-templates.d.ts +0 -49
- package/dist/multi-agent/bmad-templates.d.ts.map +0 -1
- package/dist/multi-agent/bmad-templates.js +0 -121
- package/dist/multi-agent/bmad-templates.js.map +0 -1
- package/dist/multi-agent/runtime-process.d.ts +0 -74
- package/dist/multi-agent/runtime-process.d.ts.map +0 -1
- package/dist/multi-agent/runtime-process.js +0 -160
- package/dist/multi-agent/runtime-process.js.map +0 -1
- package/dist/multi-agent/tool-permission-manager.d.ts +0 -70
- package/dist/multi-agent/tool-permission-manager.d.ts.map +0 -1
- package/dist/multi-agent/tool-permission-manager.js +0 -195
- package/dist/multi-agent/tool-permission-manager.js.map +0 -1
- package/dist/multi-agent/types.d.ts +0 -438
- package/dist/multi-agent/types.d.ts.map +0 -1
- package/dist/multi-agent/types.js +0 -27
- package/dist/multi-agent/types.js.map +0 -1
- package/dist/multi-agent/wiki-agent-persona.d.ts +0 -16
- package/dist/multi-agent/wiki-agent-persona.d.ts.map +0 -1
- package/dist/multi-agent/wiki-agent-persona.js +0 -23
- package/dist/multi-agent/wiki-agent-persona.js.map +0 -1
- package/dist/multi-agent/workflow-types.d.ts +0 -186
- package/dist/multi-agent/workflow-types.d.ts.map +0 -1
- package/dist/multi-agent/workflow-types.js +0 -9
- package/dist/multi-agent/workflow-types.js.map +0 -1
- package/dist/observability/code-audit.d.ts +0 -87
- package/dist/observability/code-audit.d.ts.map +0 -1
- package/dist/observability/code-audit.js +0 -414
- package/dist/observability/code-audit.js.map +0 -1
- package/dist/observability/health-check.d.ts +0 -102
- package/dist/observability/health-check.d.ts.map +0 -1
- package/dist/observability/health-check.js +0 -422
- package/dist/observability/health-check.js.map +0 -1
- package/dist/observability/health-score.d.ts +0 -29
- package/dist/observability/health-score.d.ts.map +0 -1
- package/dist/observability/health-score.js +0 -83
- package/dist/observability/health-score.js.map +0 -1
- package/dist/observability/metrics-cleanup.d.ts +0 -24
- package/dist/observability/metrics-cleanup.d.ts.map +0 -1
- package/dist/observability/metrics-cleanup.js +0 -51
- package/dist/observability/metrics-cleanup.js.map +0 -1
- package/dist/observability/metrics-store.d.ts +0 -52
- package/dist/observability/metrics-store.d.ts.map +0 -1
- package/dist/observability/metrics-store.js +0 -186
- package/dist/observability/metrics-store.js.map +0 -1
- package/dist/observability/operational-issues.d.ts +0 -55
- package/dist/observability/operational-issues.d.ts.map +0 -1
- package/dist/observability/operational-issues.js +0 -170
- package/dist/observability/operational-issues.js.map +0 -1
- package/dist/onboarding/agent-contract.d.ts +0 -42
- package/dist/onboarding/agent-contract.d.ts.map +0 -1
- package/dist/onboarding/agent-contract.js +0 -129
- package/dist/onboarding/agent-contract.js.map +0 -1
- package/dist/onboarding/assess-live.d.ts +0 -22
- package/dist/onboarding/assess-live.d.ts.map +0 -1
- package/dist/onboarding/assess-live.js +0 -200
- package/dist/onboarding/assess-live.js.map +0 -1
- package/dist/onboarding/bootstrap-template.d.ts +0 -5
- package/dist/onboarding/bootstrap-template.d.ts.map +0 -1
- package/dist/onboarding/bootstrap-template.js +0 -34
- package/dist/onboarding/bootstrap-template.js.map +0 -1
- package/dist/operator/action-verifier.d.ts +0 -75
- package/dist/operator/action-verifier.d.ts.map +0 -1
- package/dist/operator/action-verifier.js +0 -149
- package/dist/operator/action-verifier.js.map +0 -1
- package/dist/operator/board-delta-gate.d.ts +0 -131
- package/dist/operator/board-delta-gate.d.ts.map +0 -1
- package/dist/operator/board-delta-gate.js +0 -181
- package/dist/operator/board-delta-gate.js.map +0 -1
- package/dist/operator/board-pipeline-render.d.ts +0 -16
- package/dist/operator/board-pipeline-render.d.ts.map +0 -1
- package/dist/operator/board-pipeline-render.js +0 -69
- package/dist/operator/board-pipeline-render.js.map +0 -1
- package/dist/operator/board-reconcile.d.ts +0 -61
- package/dist/operator/board-reconcile.d.ts.map +0 -1
- package/dist/operator/board-reconcile.js +0 -157
- package/dist/operator/board-reconcile.js.map +0 -1
- package/dist/operator/board-refresh-gate.d.ts +0 -39
- package/dist/operator/board-refresh-gate.d.ts.map +0 -1
- package/dist/operator/board-refresh-gate.js +0 -83
- package/dist/operator/board-refresh-gate.js.map +0 -1
- package/dist/operator/changes-projection.d.ts +0 -72
- package/dist/operator/changes-projection.d.ts.map +0 -1
- package/dist/operator/changes-projection.js +0 -110
- package/dist/operator/changes-projection.js.map +0 -1
- package/dist/operator/connector-delta-repo.d.ts +0 -33
- package/dist/operator/connector-delta-repo.d.ts.map +0 -1
- package/dist/operator/connector-delta-repo.js +0 -0
- package/dist/operator/connector-delta-repo.js.map +0 -1
- package/dist/operator/console-brief.d.ts +0 -40
- package/dist/operator/console-brief.d.ts.map +0 -1
- package/dist/operator/console-brief.js +0 -246
- package/dist/operator/console-brief.js.map +0 -1
- package/dist/operator/experience-evidence.d.ts +0 -18
- package/dist/operator/experience-evidence.d.ts.map +0 -1
- package/dist/operator/experience-evidence.js +0 -82
- package/dist/operator/experience-evidence.js.map +0 -1
- package/dist/operator/experience-hints.d.ts +0 -44
- package/dist/operator/experience-hints.d.ts.map +0 -1
- package/dist/operator/experience-hints.js +0 -98
- package/dist/operator/experience-hints.js.map +0 -1
- package/dist/operator/external-correlation.d.ts +0 -94
- package/dist/operator/external-correlation.d.ts.map +0 -1
- package/dist/operator/external-correlation.js +0 -177
- package/dist/operator/external-correlation.js.map +0 -1
- package/dist/operator/external-lifecycle-candidates.d.ts +0 -70
- package/dist/operator/external-lifecycle-candidates.d.ts.map +0 -1
- package/dist/operator/external-lifecycle-candidates.js +0 -579
- package/dist/operator/external-lifecycle-candidates.js.map +0 -1
- package/dist/operator/external-lifecycle-discovery.d.ts +0 -61
- package/dist/operator/external-lifecycle-discovery.d.ts.map +0 -1
- package/dist/operator/external-lifecycle-discovery.js +0 -103
- package/dist/operator/external-lifecycle-discovery.js.map +0 -1
- package/dist/operator/external-lifecycle.d.ts +0 -105
- package/dist/operator/external-lifecycle.d.ts.map +0 -1
- package/dist/operator/external-lifecycle.js +0 -27
- package/dist/operator/external-lifecycle.js.map +0 -1
- package/dist/operator/file-coordination.d.ts +0 -7
- package/dist/operator/file-coordination.d.ts.map +0 -1
- package/dist/operator/file-coordination.js +0 -84
- package/dist/operator/file-coordination.js.map +0 -1
- package/dist/operator/file-export.d.ts +0 -22
- package/dist/operator/file-export.d.ts.map +0 -1
- package/dist/operator/file-export.js +0 -115
- package/dist/operator/file-export.js.map +0 -1
- package/dist/operator/leg-cadence.d.ts +0 -72
- package/dist/operator/leg-cadence.d.ts.map +0 -1
- package/dist/operator/leg-cadence.js +0 -122
- package/dist/operator/leg-cadence.js.map +0 -1
- package/dist/operator/mama-memory-port.d.ts +0 -13
- package/dist/operator/mama-memory-port.d.ts.map +0 -1
- package/dist/operator/mama-memory-port.js +0 -33
- package/dist/operator/mama-memory-port.js.map +0 -1
- package/dist/operator/operator-interfaces.d.ts +0 -136
- package/dist/operator/operator-interfaces.d.ts.map +0 -1
- package/dist/operator/operator-interfaces.js +0 -15
- package/dist/operator/operator-interfaces.js.map +0 -1
- package/dist/operator/operator-trigger-loop.d.ts +0 -203
- package/dist/operator/operator-trigger-loop.d.ts.map +0 -1
- package/dist/operator/operator-trigger-loop.js +0 -827
- package/dist/operator/operator-trigger-loop.js.map +0 -1
- package/dist/operator/owner-action-effects.d.ts +0 -112
- package/dist/operator/owner-action-effects.d.ts.map +0 -1
- package/dist/operator/owner-action-effects.js +0 -375
- package/dist/operator/owner-action-effects.js.map +0 -1
- package/dist/operator/owner-event-effects.d.ts +0 -65
- package/dist/operator/owner-event-effects.d.ts.map +0 -1
- package/dist/operator/owner-event-effects.js +0 -249
- package/dist/operator/owner-event-effects.js.map +0 -1
- package/dist/operator/owner-event-inbox.d.ts +0 -124
- package/dist/operator/owner-event-inbox.d.ts.map +0 -1
- package/dist/operator/owner-event-inbox.js +0 -440
- package/dist/operator/owner-event-inbox.js.map +0 -1
- package/dist/operator/owner-event-loop.d.ts +0 -99
- package/dist/operator/owner-event-loop.d.ts.map +0 -1
- package/dist/operator/owner-event-loop.js +0 -253
- package/dist/operator/owner-event-loop.js.map +0 -1
- package/dist/operator/owner-event-outcome.d.ts +0 -36
- package/dist/operator/owner-event-outcome.d.ts.map +0 -1
- package/dist/operator/owner-event-outcome.js +0 -174
- package/dist/operator/owner-event-outcome.js.map +0 -1
- package/dist/operator/owner-event-policy.d.ts +0 -40
- package/dist/operator/owner-event-policy.d.ts.map +0 -1
- package/dist/operator/owner-event-policy.js +0 -75
- package/dist/operator/owner-event-policy.js.map +0 -1
- package/dist/operator/owner-event-prompt.d.ts +0 -21
- package/dist/operator/owner-event-prompt.d.ts.map +0 -1
- package/dist/operator/owner-event-prompt.js +0 -69
- package/dist/operator/owner-event-prompt.js.map +0 -1
- package/dist/operator/owner-runtime-journal.d.ts +0 -21
- package/dist/operator/owner-runtime-journal.d.ts.map +0 -1
- package/dist/operator/owner-runtime-journal.js +0 -135
- package/dist/operator/owner-runtime-journal.js.map +0 -1
- package/dist/operator/owner-runtime.d.ts +0 -48
- package/dist/operator/owner-runtime.d.ts.map +0 -1
- package/dist/operator/owner-runtime.js +0 -99
- package/dist/operator/owner-runtime.js.map +0 -1
- package/dist/operator/pending-report-store.d.ts +0 -99
- package/dist/operator/pending-report-store.d.ts.map +0 -1
- package/dist/operator/pending-report-store.js +0 -554
- package/dist/operator/pending-report-store.js.map +0 -1
- package/dist/operator/procedure-activation.d.ts +0 -7
- package/dist/operator/procedure-activation.d.ts.map +0 -1
- package/dist/operator/procedure-activation.js +0 -132
- package/dist/operator/procedure-activation.js.map +0 -1
- package/dist/operator/procedure-projection.d.ts +0 -25
- package/dist/operator/procedure-projection.d.ts.map +0 -1
- package/dist/operator/procedure-projection.js +0 -80
- package/dist/operator/procedure-projection.js.map +0 -1
- package/dist/operator/procedure-runtime.d.ts +0 -45
- package/dist/operator/procedure-runtime.d.ts.map +0 -1
- package/dist/operator/procedure-runtime.js +0 -496
- package/dist/operator/procedure-runtime.js.map +0 -1
- package/dist/operator/procedure-store.d.ts +0 -97
- package/dist/operator/procedure-store.d.ts.map +0 -1
- package/dist/operator/procedure-store.js +0 -368
- package/dist/operator/procedure-store.js.map +0 -1
- package/dist/operator/provenance-lookup.d.ts +0 -18
- package/dist/operator/provenance-lookup.d.ts.map +0 -1
- package/dist/operator/provenance-lookup.js +0 -54
- package/dist/operator/provenance-lookup.js.map +0 -1
- package/dist/operator/repair-request.d.ts +0 -29
- package/dist/operator/repair-request.d.ts.map +0 -1
- package/dist/operator/repair-request.js +0 -115
- package/dist/operator/repair-request.js.map +0 -1
- package/dist/operator/report-carry-delivery.d.ts +0 -35
- package/dist/operator/report-carry-delivery.d.ts.map +0 -1
- package/dist/operator/report-carry-delivery.js +0 -55
- package/dist/operator/report-carry-delivery.js.map +0 -1
- package/dist/operator/report-carry.d.ts +0 -66
- package/dist/operator/report-carry.d.ts.map +0 -1
- package/dist/operator/report-carry.js +0 -352
- package/dist/operator/report-carry.js.map +0 -1
- package/dist/operator/report-delivery-coordinator.d.ts +0 -84
- package/dist/operator/report-delivery-coordinator.d.ts.map +0 -1
- package/dist/operator/report-delivery-coordinator.js +0 -146
- package/dist/operator/report-delivery-coordinator.js.map +0 -1
- package/dist/operator/report-run.d.ts +0 -52
- package/dist/operator/report-run.d.ts.map +0 -1
- package/dist/operator/report-run.js +0 -32
- package/dist/operator/report-run.js.map +0 -1
- package/dist/operator/report-scheduler.d.ts +0 -66
- package/dist/operator/report-scheduler.d.ts.map +0 -1
- package/dist/operator/report-scheduler.js +0 -135
- package/dist/operator/report-scheduler.js.map +0 -1
- package/dist/operator/runtime-config.d.ts +0 -10
- package/dist/operator/runtime-config.d.ts.map +0 -1
- package/dist/operator/runtime-config.js +0 -21
- package/dist/operator/runtime-config.js.map +0 -1
- package/dist/operator/situation-report.d.ts +0 -197
- package/dist/operator/situation-report.d.ts.map +0 -1
- package/dist/operator/situation-report.js +0 -462
- package/dist/operator/situation-report.js.map +0 -1
- package/dist/operator/subagent-stimulus.d.ts +0 -34
- package/dist/operator/subagent-stimulus.d.ts.map +0 -1
- package/dist/operator/subagent-stimulus.js +0 -145
- package/dist/operator/subagent-stimulus.js.map +0 -1
- package/dist/operator/task-ledger.d.ts +0 -879
- package/dist/operator/task-ledger.d.ts.map +0 -1
- package/dist/operator/task-ledger.js +0 -3601
- package/dist/operator/task-ledger.js.map +0 -1
- package/dist/operator/task-list-views.d.ts +0 -91
- package/dist/operator/task-list-views.d.ts.map +0 -1
- package/dist/operator/task-list-views.js +0 -532
- package/dist/operator/task-list-views.js.map +0 -1
- package/dist/operator/task-temporal.d.ts +0 -24
- package/dist/operator/task-temporal.d.ts.map +0 -1
- package/dist/operator/task-temporal.js +0 -123
- package/dist/operator/task-temporal.js.map +0 -1
- package/dist/operator/temporal-effect.d.ts +0 -65
- package/dist/operator/temporal-effect.d.ts.map +0 -1
- package/dist/operator/temporal-effect.js +0 -71
- package/dist/operator/temporal-effect.js.map +0 -1
- package/dist/operator/temporal-reconcile.d.ts +0 -49
- package/dist/operator/temporal-reconcile.d.ts.map +0 -1
- package/dist/operator/temporal-reconcile.js +0 -231
- package/dist/operator/temporal-reconcile.js.map +0 -1
- package/dist/operator/temporal-runtime.d.ts +0 -51
- package/dist/operator/temporal-runtime.d.ts.map +0 -1
- package/dist/operator/temporal-runtime.js +0 -104
- package/dist/operator/temporal-runtime.js.map +0 -1
- package/dist/operator/temporal-worker.d.ts +0 -18
- package/dist/operator/temporal-worker.d.ts.map +0 -1
- package/dist/operator/temporal-worker.js +0 -82
- package/dist/operator/temporal-worker.js.map +0 -1
- package/dist/operator/thread-brief-memory.d.ts +0 -29
- package/dist/operator/thread-brief-memory.d.ts.map +0 -1
- package/dist/operator/thread-brief-memory.js +0 -61
- package/dist/operator/thread-brief-memory.js.map +0 -1
- package/dist/operator/trigger-author.d.ts +0 -63
- package/dist/operator/trigger-author.d.ts.map +0 -1
- package/dist/operator/trigger-author.js +0 -384
- package/dist/operator/trigger-author.js.map +0 -1
- package/dist/operator/trigger-evolve.d.ts +0 -31
- package/dist/operator/trigger-evolve.d.ts.map +0 -1
- package/dist/operator/trigger-evolve.js +0 -51
- package/dist/operator/trigger-evolve.js.map +0 -1
- package/dist/operator/trigger-fire.d.ts +0 -29
- package/dist/operator/trigger-fire.d.ts.map +0 -1
- package/dist/operator/trigger-fire.js +0 -41
- package/dist/operator/trigger-fire.js.map +0 -1
- package/dist/operator/trigger-matcher.d.ts +0 -19
- package/dist/operator/trigger-matcher.d.ts.map +0 -1
- package/dist/operator/trigger-matcher.js +0 -65
- package/dist/operator/trigger-matcher.js.map +0 -1
- package/dist/operator/trigger-registry.d.ts +0 -58
- package/dist/operator/trigger-registry.d.ts.map +0 -1
- package/dist/operator/trigger-registry.js +0 -349
- package/dist/operator/trigger-registry.js.map +0 -1
- package/dist/operator/trigger-review.d.ts +0 -23
- package/dist/operator/trigger-review.d.ts.map +0 -1
- package/dist/operator/trigger-review.js +0 -193
- package/dist/operator/trigger-review.js.map +0 -1
- package/dist/operator/trigger-types.d.ts +0 -95
- package/dist/operator/trigger-types.d.ts.map +0 -1
- package/dist/operator/trigger-types.js +0 -14
- package/dist/operator/trigger-types.js.map +0 -1
- package/dist/operator/wiki-continuity.d.ts +0 -153
- package/dist/operator/wiki-continuity.d.ts.map +0 -1
- package/dist/operator/wiki-continuity.js +0 -221
- package/dist/operator/wiki-continuity.js.map +0 -1
- package/dist/operator/worker-run.d.ts +0 -109
- package/dist/operator/worker-run.d.ts.map +0 -1
- package/dist/operator/worker-run.js +0 -131
- package/dist/operator/worker-run.js.map +0 -1
- package/dist/operator/workorder-consumer.d.ts +0 -294
- package/dist/operator/workorder-consumer.d.ts.map +0 -1
- package/dist/operator/workorder-consumer.js +0 -1108
- package/dist/operator/workorder-consumer.js.map +0 -1
- package/dist/operator/workorder-hooks.d.ts +0 -162
- package/dist/operator/workorder-hooks.d.ts.map +0 -1
- package/dist/operator/workorder-hooks.js +0 -342
- package/dist/operator/workorder-hooks.js.map +0 -1
- package/dist/operator/workorder-publishers.d.ts +0 -117
- package/dist/operator/workorder-publishers.d.ts.map +0 -1
- package/dist/operator/workorder-publishers.js +0 -544
- package/dist/operator/workorder-publishers.js.map +0 -1
- package/dist/runners/cli-runner.d.ts +0 -59
- package/dist/runners/cli-runner.d.ts.map +0 -1
- package/dist/runners/cli-runner.js +0 -191
- package/dist/runners/cli-runner.js.map +0 -1
- package/dist/runners/index.d.ts +0 -11
- package/dist/runners/index.d.ts.map +0 -1
- package/dist/runners/index.js +0 -15
- package/dist/runners/index.js.map +0 -1
- package/dist/runners/types.d.ts +0 -81
- package/dist/runners/types.d.ts.map +0 -1
- package/dist/runners/types.js +0 -31
- package/dist/runners/types.js.map +0 -1
- package/dist/scheduler/cron-result-router.d.ts +0 -24
- package/dist/scheduler/cron-result-router.d.ts.map +0 -1
- package/dist/scheduler/cron-result-router.js +0 -63
- package/dist/scheduler/cron-result-router.js.map +0 -1
- package/dist/scheduler/cron-scheduler.d.ts +0 -115
- package/dist/scheduler/cron-scheduler.d.ts.map +0 -1
- package/dist/scheduler/cron-scheduler.js +0 -336
- package/dist/scheduler/cron-scheduler.js.map +0 -1
- package/dist/scheduler/cron-worker.d.ts +0 -39
- package/dist/scheduler/cron-worker.d.ts.map +0 -1
- package/dist/scheduler/cron-worker.js +0 -94
- package/dist/scheduler/cron-worker.js.map +0 -1
- package/dist/scheduler/heartbeat.d.ts +0 -64
- package/dist/scheduler/heartbeat.d.ts.map +0 -1
- package/dist/scheduler/heartbeat.js +0 -197
- package/dist/scheduler/heartbeat.js.map +0 -1
- package/dist/scheduler/index.d.ts +0 -26
- package/dist/scheduler/index.d.ts.map +0 -1
- package/dist/scheduler/index.js +0 -37
- package/dist/scheduler/index.js.map +0 -1
- package/dist/scheduler/job-lock.d.ts +0 -85
- package/dist/scheduler/job-lock.d.ts.map +0 -1
- package/dist/scheduler/job-lock.js +0 -137
- package/dist/scheduler/job-lock.js.map +0 -1
- package/dist/scheduler/recovery.d.ts +0 -78
- package/dist/scheduler/recovery.d.ts.map +0 -1
- package/dist/scheduler/recovery.js +0 -124
- package/dist/scheduler/recovery.js.map +0 -1
- package/dist/scheduler/schedule-store.d.ts +0 -112
- package/dist/scheduler/schedule-store.d.ts.map +0 -1
- package/dist/scheduler/schedule-store.js +0 -259
- package/dist/scheduler/schedule-store.js.map +0 -1
- package/dist/scheduler/token-keep-alive.d.ts +0 -49
- package/dist/scheduler/token-keep-alive.d.ts.map +0 -1
- package/dist/scheduler/token-keep-alive.js +0 -102
- package/dist/scheduler/token-keep-alive.js.map +0 -1
- package/dist/scheduler/types.d.ts +0 -98
- package/dist/scheduler/types.d.ts.map +0 -1
- package/dist/scheduler/types.js +0 -21
- package/dist/scheduler/types.js.map +0 -1
- package/dist/security/security-monitor.d.ts +0 -56
- package/dist/security/security-monitor.d.ts.map +0 -1
- package/dist/security/security-monitor.js +0 -779
- package/dist/security/security-monitor.js.map +0 -1
- package/dist/security/trusted-proxy.d.ts +0 -9
- package/dist/security/trusted-proxy.d.ts.map +0 -1
- package/dist/security/trusted-proxy.js +0 -36
- package/dist/security/trusted-proxy.js.map +0 -1
- package/dist/skills/index.d.ts +0 -10
- package/dist/skills/index.d.ts.map +0 -1
- package/dist/skills/index.js +0 -26
- package/dist/skills/index.js.map +0 -1
- package/dist/skills/skill-executor.d.ts +0 -48
- package/dist/skills/skill-executor.d.ts.map +0 -1
- package/dist/skills/skill-executor.js +0 -483
- package/dist/skills/skill-executor.js.map +0 -1
- package/dist/skills/skill-loader.d.ts +0 -40
- package/dist/skills/skill-loader.d.ts.map +0 -1
- package/dist/skills/skill-loader.js +0 -233
- package/dist/skills/skill-loader.js.map +0 -1
- package/dist/skills/skill-matcher.d.ts +0 -33
- package/dist/skills/skill-matcher.d.ts.map +0 -1
- package/dist/skills/skill-matcher.js +0 -190
- package/dist/skills/skill-matcher.js.map +0 -1
- package/dist/skills/skill-registry.d.ts +0 -117
- package/dist/skills/skill-registry.d.ts.map +0 -1
- package/dist/skills/skill-registry.js +0 -627
- package/dist/skills/skill-registry.js.map +0 -1
- package/dist/skills/types.d.ts +0 -131
- package/dist/skills/types.d.ts.map +0 -1
- package/dist/skills/types.js +0 -12
- package/dist/skills/types.js.map +0 -1
- package/dist/utils/log-sanitizer.d.ts +0 -79
- package/dist/utils/log-sanitizer.d.ts.map +0 -1
- package/dist/utils/log-sanitizer.js +0 -215
- package/dist/utils/log-sanitizer.js.map +0 -1
- package/dist/validation/session-service.d.ts +0 -72
- package/dist/validation/session-service.d.ts.map +0 -1
- package/dist/validation/session-service.js +0 -298
- package/dist/validation/session-service.js.map +0 -1
- package/dist/validation/store.d.ts +0 -25
- package/dist/validation/store.d.ts.map +0 -1
- package/dist/validation/store.js +0 -200
- package/dist/validation/store.js.map +0 -1
- package/dist/validation/types.d.ts +0 -119
- package/dist/validation/types.d.ts.map +0 -1
- package/dist/validation/types.js +0 -57
- package/dist/validation/types.js.map +0 -1
- package/dist/wiki/wiki-turn-contract.d.ts +0 -19
- package/dist/wiki/wiki-turn-contract.d.ts.map +0 -1
- package/dist/wiki/wiki-turn-contract.js +0 -60
- package/dist/wiki/wiki-turn-contract.js.map +0 -1
- package/public/viewer/js/utils/ui-commands.js +0 -88
- package/public/viewer/src/utils/ui-commands.ts +0 -114
- package/scripts/attachment/extract-office-text.js +0 -179
- package/scripts/attachment/extract-pdf-text.swift +0 -36
- package/scripts/backfill-channel-keys.ts +0 -509
- package/scripts/crack-failure-digests.ts +0 -55
- package/scripts/generate-gateway-tools.ts +0 -131
- package/scripts/image/fb-overlay.py +0 -128
- package/scripts/image/ocr-image.py +0 -38
- package/scripts/postinstall.js +0 -161
- package/scripts/setup-ocr.js +0 -120
- package/templates/AGENTS.claude.md +0 -23
- package/templates/AGENTS.codex.md +0 -11
- package/templates/bmad/LICENSE +0 -28
- package/templates/bmad/architecture.md +0 -343
- package/templates/bmad/bmm-workflow-status.template.yaml +0 -66
- package/templates/bmad/prd.md +0 -198
- package/templates/bmad/product-brief.md +0 -149
- package/templates/bmad/sprint-status.template.yaml +0 -35
- package/templates/bmad/tech-spec.md +0 -151
- package/templates/entity-audit-fixtures/cross-language-aliases.json +0 -28
- package/templates/entity-audit-fixtures/gold-canonical-identities.json +0 -46
- package/templates/skills/danggeun-monitor.md +0 -88
- package/templates/skills/document-analyze.md +0 -63
- package/templates/skills/frontend-design.md +0 -71
- package/templates/skills/image-translate.md +0 -62
- package/templates/skills/multi-agent-collab.md +0 -145
- package/templates/skills/scheduling.md +0 -107
- package/templates/workspace/skill-forge/DESIGN.md +0 -115
- package/templates/workspace/skill-forge/agents/architect.ts +0 -295
- package/templates/workspace/skill-forge/agents/developer.ts +0 -364
- package/templates/workspace/skill-forge/agents/qa.ts +0 -313
- package/templates/workspace/skill-forge/claude-api.ts +0 -353
- package/templates/workspace/skill-forge/discord-ui.ts +0 -580
- package/templates/workspace/skill-forge/error-handler.ts +0 -354
- package/templates/workspace/skill-forge/mama-integration.ts +0 -357
- package/templates/workspace/skill-forge/orchestrator.ts +0 -495
- package/templates/workspace/skill-forge/output/generated-skills/skills/hello-world/README.md +0 -24
- package/templates/workspace/skill-forge/output/generated-skills/skills/hello-world/index.ts +0 -79
- package/templates/workspace/skill-forge/output/generated-skills/skills/hello-world/types.ts +0 -17
- package/templates/workspace/skill-forge/package.json +0 -21
- package/templates/workspace/skill-forge/state/session.json +0 -132
- package/templates/workspace/skill-forge/test-e2e.ts +0 -139
- package/templates/workspace/skill-forge/tsconfig.json +0 -20
- package/templates/workspace/skill-forge/types.ts +0 -159
|
@@ -1,3601 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* TaskLedger - the operator-owned native work-item ledger (M8 Task 0.1).
|
|
4
|
-
*
|
|
5
|
-
* Extends the shape of Kagemusha's proven task store with assignment,
|
|
6
|
-
* idempotency, temporal scheduling, durable generation, effect receipt, and
|
|
7
|
-
* workorder state. Implements the pre-existing `TaskSource` interface
|
|
8
|
-
* (operator-interfaces.ts) so the board projects one task model, not two.
|
|
9
|
-
*
|
|
10
|
-
* Owner conversations create rows through task_create. Reconcile runs update or
|
|
11
|
-
* reclassify existing rows through the task_update/task_reclassify gateway
|
|
12
|
-
* tools; the pipeline board slot is a projection of `list({order:
|
|
13
|
-
* 'deadline_priority'})`. The agent proposes task changes, while this ledger
|
|
14
|
-
* enforces revisions, temporal ownership, idempotency, and atomic receipts.
|
|
15
|
-
*
|
|
16
|
-
* Schema-extension note: CREATE TABLE IF NOT EXISTS is a no-op on existing
|
|
17
|
-
* tables. Any post-ship column addition needs an explicit ALTER TABLE guarded
|
|
18
|
-
* by a PRAGMA table_info check, added to runMigration().
|
|
19
|
-
*
|
|
20
|
-
* The db handle is SHARED with TriggerRegistry and owned by the caller
|
|
21
|
-
* (start.ts opens and closes it once) - deliberately no close() here.
|
|
22
|
-
*/
|
|
23
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
-
exports.TaskLedger = exports.TASK_COMPLETION_CRITERIA_MAX_LENGTH = exports.TASK_RECLASSIFY_REASON_MAX_LENGTH = exports.TASK_RECLASSIFY_DISPOSITIONS = exports.TASK_RESOLUTION_KINDS = exports.WORKORDER_CHANNEL_PREFIX = exports.TEMPORAL_WORKORDER_MAX_ATTEMPTS = exports.WORKORDER_DELEGATED_AT_KEY = exports.WORKORDER_KINDS = exports.TASK_KINDS = exports.TASK_PRIORITIES = exports.TASK_STATUSES = void 0;
|
|
25
|
-
exports.publisherPayloadOfStoredWorkOrder = publisherPayloadOfStoredWorkOrder;
|
|
26
|
-
const node_crypto_1 = require("node:crypto");
|
|
27
|
-
const operator_task_temporal_js_1 = require("../db/migrations/operator-task-temporal.js");
|
|
28
|
-
const operator_task_external_lifecycle_js_1 = require("../db/migrations/operator-task-external-lifecycle.js");
|
|
29
|
-
const operator_task_review_lifecycle_js_1 = require("../db/migrations/operator-task-review-lifecycle.js");
|
|
30
|
-
const operator_task_classification_js_1 = require("../db/migrations/operator-task-classification.js");
|
|
31
|
-
const operator_owner_action_candidates_js_1 = require("../db/migrations/operator-owner-action-candidates.js");
|
|
32
|
-
const effects_js_1 = require("../evidence/effects.js");
|
|
33
|
-
const temporal_effect_js_1 = require("./temporal-effect.js");
|
|
34
|
-
const task_temporal_js_1 = require("./task-temporal.js");
|
|
35
|
-
const external_lifecycle_candidates_js_1 = require("./external-lifecycle-candidates.js");
|
|
36
|
-
const workorder_publishers_js_1 = require("./workorder-publishers.js");
|
|
37
|
-
/** listPage() only: a page size large enough to be the whole board, so the limit+1
|
|
38
|
-
* next-page probe stays intact when the caller passes no limit. list() has no LIMIT. */
|
|
39
|
-
const WHOLE_BOARD_LIMIT = 100_000;
|
|
40
|
-
exports.TASK_STATUSES = [
|
|
41
|
-
'pending',
|
|
42
|
-
'in_progress',
|
|
43
|
-
'review',
|
|
44
|
-
'blocked',
|
|
45
|
-
'done',
|
|
46
|
-
'cancelled',
|
|
47
|
-
// System-only terminal for workorder rows. Owner rows can never reach it:
|
|
48
|
-
// external create/update guards reject it, so it stays out of owner surfaces.
|
|
49
|
-
'failed',
|
|
50
|
-
];
|
|
51
|
-
exports.TASK_PRIORITIES = ['high', 'normal', 'low'];
|
|
52
|
-
function scheduledPromotionReservationKeys(idempotencyKey) {
|
|
53
|
-
const legacy = /^promotion:(\d+)$/.exec(idempotencyKey);
|
|
54
|
-
if (legacy) {
|
|
55
|
-
return [idempotencyKey, `promotion:v2:${workorder_publishers_js_1.DEFAULT_PROMOTION_INTERVAL_MS}:${legacy[1]}`];
|
|
56
|
-
}
|
|
57
|
-
const versioned = /^promotion:v2:(\d+):(\d+)$/.exec(idempotencyKey);
|
|
58
|
-
if (!versioned)
|
|
59
|
-
return null;
|
|
60
|
-
const [, intervalMs, slot] = versioned;
|
|
61
|
-
return Number(intervalMs) === workorder_publishers_js_1.DEFAULT_PROMOTION_INTERVAL_MS
|
|
62
|
-
? [idempotencyKey, `promotion:${slot}`]
|
|
63
|
-
: [idempotencyKey];
|
|
64
|
-
}
|
|
65
|
-
exports.TASK_KINDS = ['owner', 'system'];
|
|
66
|
-
exports.WORKORDER_KINDS = [
|
|
67
|
-
'board',
|
|
68
|
-
'wiki',
|
|
69
|
-
'memory-curation',
|
|
70
|
-
'temporal',
|
|
71
|
-
'self-check',
|
|
72
|
-
];
|
|
73
|
-
/**
|
|
74
|
-
* Ledger-managed payload key holding the delegation timestamp. Like `attempts`, it is never
|
|
75
|
-
* valid publisher input (validateWorkOrderPayload rejects unknown fields at enqueue) and it
|
|
76
|
-
* is stripped from a requeued replacement, which has delegated nothing.
|
|
77
|
-
*/
|
|
78
|
-
exports.WORKORDER_DELEGATED_AT_KEY = 'delegated_at';
|
|
79
|
-
/**
|
|
80
|
-
* Publisher-shaped view of a STORED workorder payload.
|
|
81
|
-
*
|
|
82
|
-
* Stored rows carry ledger-managed keys (`attempts`, `delegated_at`) that the enqueue
|
|
83
|
-
* validator rejects by design. Revalidating a stored row must therefore check only what a
|
|
84
|
-
* publisher could have supplied; validating the raw stored payload turned a
|
|
85
|
-
* delegated-and-verified board attempt into a permanently "unresolved" receipt state
|
|
86
|
-
* (live, 2026-09-09, workorder #4765). Enqueue input is still rejected loudly.
|
|
87
|
-
*/
|
|
88
|
-
function publisherPayloadOfStoredWorkOrder(payload) {
|
|
89
|
-
const { attempts: _attempts, [exports.WORKORDER_DELEGATED_AT_KEY]: _delegatedAt, ...publisherInput } = payload;
|
|
90
|
-
return publisherInput;
|
|
91
|
-
}
|
|
92
|
-
exports.TEMPORAL_WORKORDER_MAX_ATTEMPTS = 3;
|
|
93
|
-
/** source_channel namespace for workorder rows: 'workorder:<workKind>'. */
|
|
94
|
-
exports.WORKORDER_CHANNEL_PREFIX = 'workorder:';
|
|
95
|
-
/** SQL variable chunk for cause lookups (SQLite default limit is 999). */
|
|
96
|
-
const EFFECT_CAUSE_CHUNK = 500;
|
|
97
|
-
const KNOWN_WORKORDER_CHANNELS = exports.WORKORDER_KINDS.map((workKind) => `${exports.WORKORDER_CHANNEL_PREFIX}${workKind}`);
|
|
98
|
-
const KNOWN_WORKORDER_CHANNELS_SQL = KNOWN_WORKORDER_CHANNELS.map(() => '?').join(',');
|
|
99
|
-
/**
|
|
100
|
-
* Why a row reached its terminal state. Without this the agent cannot tell
|
|
101
|
-
* completed work from an item that was never a task, which is how connector
|
|
102
|
-
* observations, principles and open questions accumulated as owner rows.
|
|
103
|
-
*/
|
|
104
|
-
exports.TASK_RESOLUTION_KINDS = [
|
|
105
|
-
'completed_evidence',
|
|
106
|
-
'completed_no_issue',
|
|
107
|
-
'non_task_record',
|
|
108
|
-
'non_task_memory',
|
|
109
|
-
];
|
|
110
|
-
/** The recorrection an agent may apply to an existing owner row. */
|
|
111
|
-
exports.TASK_RECLASSIFY_DISPOSITIONS = [
|
|
112
|
-
'completed_evidence',
|
|
113
|
-
'completed_no_issue',
|
|
114
|
-
'non_task_record',
|
|
115
|
-
'non_task_memory',
|
|
116
|
-
'reopen',
|
|
117
|
-
];
|
|
118
|
-
/** Upper bound on the preserved reason, matching latest_event's practical size. */
|
|
119
|
-
exports.TASK_RECLASSIFY_REASON_MAX_LENGTH = 2000;
|
|
120
|
-
exports.TASK_COMPLETION_CRITERIA_MAX_LENGTH = 500;
|
|
121
|
-
const PRIORITY_RANK = { high: 0, normal: 1, low: 2 };
|
|
122
|
-
function encodeListCursor(payload) {
|
|
123
|
-
return Buffer.from(JSON.stringify(payload), 'utf8').toString('base64url');
|
|
124
|
-
}
|
|
125
|
-
/** Loud on anything malformed or from a different ordering: a cursor silently
|
|
126
|
-
* reinterpreted under another order would skip or repeat rows, which is exactly
|
|
127
|
-
* the incompleteness this page contract exists to rule out. */
|
|
128
|
-
function decodeListCursor(cursor, order) {
|
|
129
|
-
let parsed;
|
|
130
|
-
try {
|
|
131
|
-
parsed = JSON.parse(Buffer.from(cursor, 'base64url').toString('utf8'));
|
|
132
|
-
}
|
|
133
|
-
catch {
|
|
134
|
-
throw new Error('[task-ledger] malformed list cursor');
|
|
135
|
-
}
|
|
136
|
-
const payload = parsed;
|
|
137
|
-
if (!payload || payload.order !== order || !Array.isArray(payload.k)) {
|
|
138
|
-
throw new Error(`[task-ledger] list cursor does not belong to order '${order}'`);
|
|
139
|
-
}
|
|
140
|
-
const expected = order === 'deadline_priority' ? 4 : 2;
|
|
141
|
-
if (payload.k.length !== expected || payload.k.some((value) => value === null)) {
|
|
142
|
-
throw new Error('[task-ledger] malformed list cursor');
|
|
143
|
-
}
|
|
144
|
-
return payload;
|
|
145
|
-
}
|
|
146
|
-
const ISO_DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
|
|
147
|
-
function isoToEpochMs(iso) {
|
|
148
|
-
if (!iso)
|
|
149
|
-
return null;
|
|
150
|
-
const ms = Date.parse(`${iso}T00:00:00Z`);
|
|
151
|
-
return Number.isNaN(ms) ? null : ms;
|
|
152
|
-
}
|
|
153
|
-
const OWNER_ACTION_IDENTITY_MAX_LENGTH = 512;
|
|
154
|
-
function requireOwnerActionIdentity(value, field) {
|
|
155
|
-
if (typeof value !== 'string') {
|
|
156
|
-
throw new Error(`owner action ${field} must be a non-blank string`);
|
|
157
|
-
}
|
|
158
|
-
if (value.trim().length === 0 || value.trim() !== value) {
|
|
159
|
-
throw new Error(`owner action ${field} must be a non-blank string without surrounding space`);
|
|
160
|
-
}
|
|
161
|
-
if (value.length > OWNER_ACTION_IDENTITY_MAX_LENGTH) {
|
|
162
|
-
throw new Error(`owner action ${field} must be at most ${OWNER_ACTION_IDENTITY_MAX_LENGTH} characters`);
|
|
163
|
-
}
|
|
164
|
-
return value;
|
|
165
|
-
}
|
|
166
|
-
/**
|
|
167
|
-
* Same identity rules as OwnerActionEffectLedger: every field is a trusted
|
|
168
|
-
* host identity, never model text. Checked before any storage is touched.
|
|
169
|
-
*/
|
|
170
|
-
function requireOwnerActionContext(context) {
|
|
171
|
-
if (context === null || typeof context !== 'object') {
|
|
172
|
-
throw new Error('owner action context is required');
|
|
173
|
-
}
|
|
174
|
-
const workOrderAttemptId = context.workOrderAttemptId;
|
|
175
|
-
if (workOrderAttemptId !== undefined &&
|
|
176
|
-
(!Number.isSafeInteger(workOrderAttemptId) || workOrderAttemptId <= 0)) {
|
|
177
|
-
throw new Error('owner action workOrderAttemptId must be a positive integer when present');
|
|
178
|
-
}
|
|
179
|
-
return {
|
|
180
|
-
ownerScope: requireOwnerActionIdentity(context.ownerScope, 'ownerScope'),
|
|
181
|
-
occurrenceKey: requireOwnerActionIdentity(context.occurrenceKey, 'occurrenceKey'),
|
|
182
|
-
modelRunId: requireOwnerActionIdentity(context.modelRunId, 'modelRunId'),
|
|
183
|
-
envelopeHash: requireOwnerActionIdentity(context.envelopeHash, 'envelopeHash'),
|
|
184
|
-
workOrderAttemptId: workOrderAttemptId ?? null,
|
|
185
|
-
};
|
|
186
|
-
}
|
|
187
|
-
function assertEnum(value, allowed, field) {
|
|
188
|
-
if (!allowed.includes(value)) {
|
|
189
|
-
throw new Error(`${field} must be one of ${allowed.join('|')}, got: ${value}`);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
function assertIsoDate(value, field) {
|
|
193
|
-
const match = ISO_DATE_PATTERN.exec(value);
|
|
194
|
-
if (!match) {
|
|
195
|
-
throw new Error(`${field} must be an ISO date (YYYY-MM-DD), got: ${value}`);
|
|
196
|
-
}
|
|
197
|
-
const [year, month, day] = value.split('-').map(Number);
|
|
198
|
-
const date = new Date(Date.UTC(year, month - 1, day));
|
|
199
|
-
const isRoundTrip = date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 && date.getUTCDate() === day;
|
|
200
|
-
if (!isRoundTrip) {
|
|
201
|
-
throw new Error(`${field} must be an ISO date (YYYY-MM-DD), got: ${value}`);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
function normalizeCompletionCriteria(value, operation) {
|
|
205
|
-
if (value === undefined || value === null)
|
|
206
|
-
return null;
|
|
207
|
-
if (typeof value !== 'string' || value.trim().length === 0) {
|
|
208
|
-
throw new Error(`${operation}: completion_criteria must be a non-empty string`);
|
|
209
|
-
}
|
|
210
|
-
const normalized = value.trim();
|
|
211
|
-
if (normalized.length > exports.TASK_COMPLETION_CRITERIA_MAX_LENGTH) {
|
|
212
|
-
throw new Error(`${operation}: completion_criteria must be at most ${exports.TASK_COMPLETION_CRITERIA_MAX_LENGTH} characters`);
|
|
213
|
-
}
|
|
214
|
-
return normalized;
|
|
215
|
-
}
|
|
216
|
-
function rowToRecord(row, now, timeZone) {
|
|
217
|
-
return {
|
|
218
|
-
id: row.id,
|
|
219
|
-
title: row.title,
|
|
220
|
-
status: row.status,
|
|
221
|
-
priority: row.priority,
|
|
222
|
-
kind: (row.kind ?? 'owner'),
|
|
223
|
-
deadline: isoToEpochMs(row.deadline),
|
|
224
|
-
deadlineIso: row.deadline,
|
|
225
|
-
assignee: row.assignee,
|
|
226
|
-
sourceChannel: row.source_channel,
|
|
227
|
-
sourceEventId: row.source_event_id,
|
|
228
|
-
latestEvent: row.latest_event,
|
|
229
|
-
autoCreated: row.auto_created === 1,
|
|
230
|
-
confirmed: row.confirmed === 1,
|
|
231
|
-
dueAt: row.due_at,
|
|
232
|
-
deadlineOffsetMinutes: row.deadline_offset_minutes,
|
|
233
|
-
revision: row.revision,
|
|
234
|
-
temporalEpoch: row.temporal_epoch,
|
|
235
|
-
temporalReconciledOccurrenceKey: row.temporal_reconciled_occurrence_key,
|
|
236
|
-
lastTemporalCheckedAt: row.last_temporal_checked_at,
|
|
237
|
-
nextTemporalCheckAt: row.next_temporal_check_at,
|
|
238
|
-
lastTemporalAttemptId: row.last_temporal_attempt_id,
|
|
239
|
-
temporalState: (0, task_temporal_js_1.deriveTemporalState)({
|
|
240
|
-
status: row.status,
|
|
241
|
-
dueAt: row.due_at,
|
|
242
|
-
deadlineIso: row.deadline,
|
|
243
|
-
deadlineOffsetMinutes: row.deadline_offset_minutes,
|
|
244
|
-
}, now, timeZone),
|
|
245
|
-
reviewStartedAt: row.review_started_at,
|
|
246
|
-
reviewAnchorEventId: row.review_anchor_event_id,
|
|
247
|
-
reviewAnchorSourceChannel: row.review_anchor_source_channel ?? null,
|
|
248
|
-
completionCriteria: row.completion_criteria ?? null,
|
|
249
|
-
resolutionKind: (row.resolution_kind ?? null),
|
|
250
|
-
createdAt: row.created_at,
|
|
251
|
-
updatedAt: row.updated_at,
|
|
252
|
-
};
|
|
253
|
-
}
|
|
254
|
-
class TaskLedger {
|
|
255
|
-
db;
|
|
256
|
-
now;
|
|
257
|
-
timeZone;
|
|
258
|
-
onOwnerTaskChangeCommitted;
|
|
259
|
-
pendingOwnerTaskGeneration = null;
|
|
260
|
-
ownerTaskNotificationScheduled = false;
|
|
261
|
-
constructor(db, options = {}) {
|
|
262
|
-
this.db = db;
|
|
263
|
-
this.now = options.now ?? Date.now;
|
|
264
|
-
this.timeZone = options.timeZone ?? Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
265
|
-
this.onOwnerTaskChangeCommitted = options.onOwnerTaskChangeCommitted;
|
|
266
|
-
// Validate the injected or boot-resolved zone once, before reads depend on it.
|
|
267
|
-
new Intl.DateTimeFormat('en-US', { timeZone: this.timeZone }).format(0);
|
|
268
|
-
this.runMigration();
|
|
269
|
-
}
|
|
270
|
-
toRecord(row) {
|
|
271
|
-
return rowToRecord(row, this.now(), this.timeZone);
|
|
272
|
-
}
|
|
273
|
-
toRecordAt(row, observedAt) {
|
|
274
|
-
return rowToRecord(row, observedAt, this.timeZone);
|
|
275
|
-
}
|
|
276
|
-
temporalStateAt(task, observedAt) {
|
|
277
|
-
return (0, task_temporal_js_1.deriveTemporalState)(task, observedAt, this.timeZone);
|
|
278
|
-
}
|
|
279
|
-
runMigration() {
|
|
280
|
-
// Both construction sites (start.ts boot + operator-handler lazy) run this;
|
|
281
|
-
// busy_timeout here covers BOTH connections against the rebuild race.
|
|
282
|
-
this.db.pragma('busy_timeout = 5000');
|
|
283
|
-
this.db.pragma('foreign_keys = ON');
|
|
284
|
-
this.db.exec(`
|
|
285
|
-
CREATE TABLE IF NOT EXISTS operator_tasks (${TaskLedger.TABLE_COLUMNS_SQL});
|
|
286
|
-
${TaskLedger.INDEXES_SQL}
|
|
287
|
-
|
|
288
|
-
CREATE TABLE IF NOT EXISTS operator_no_update_notes (
|
|
289
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
290
|
-
scope TEXT NOT NULL,
|
|
291
|
-
reason TEXT NOT NULL,
|
|
292
|
-
created_at INTEGER NOT NULL
|
|
293
|
-
);
|
|
294
|
-
CREATE INDEX IF NOT EXISTS idx_operator_no_update_scope
|
|
295
|
-
ON operator_no_update_notes(scope, id);
|
|
296
|
-
`);
|
|
297
|
-
// Same database as the tasks, so a change and its cause commit or roll back together.
|
|
298
|
-
// A ledger that can disagree with the rows it describes is worse than no ledger.
|
|
299
|
-
(0, effects_js_1.ensureEffectLedger)(this.db);
|
|
300
|
-
this.upgradeSchema();
|
|
301
|
-
const foreignKeyViolations = this.db.pragma('foreign_key_check');
|
|
302
|
-
if (foreignKeyViolations.length > 0) {
|
|
303
|
-
throw new Error(`operator task migration found ${foreignKeyViolations.length} foreign key violation(s)`);
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
/** Full current column set - single source for CREATE and the rebuild copy. */
|
|
307
|
-
static TABLE_COLUMNS_SQL = `
|
|
308
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
309
|
-
title TEXT NOT NULL,
|
|
310
|
-
status TEXT NOT NULL DEFAULT 'pending'
|
|
311
|
-
CHECK (status IN ('pending','in_progress','review','blocked','done','cancelled','failed')),
|
|
312
|
-
priority TEXT NOT NULL DEFAULT 'normal' CHECK (priority IN ('high','normal','low')),
|
|
313
|
-
kind TEXT NOT NULL DEFAULT 'owner' CHECK (kind IN ('owner','system')),
|
|
314
|
-
payload TEXT,
|
|
315
|
-
assignee TEXT,
|
|
316
|
-
deadline TEXT,
|
|
317
|
-
source_channel TEXT,
|
|
318
|
-
source_event_id TEXT,
|
|
319
|
-
latest_event TEXT,
|
|
320
|
-
auto_created INTEGER NOT NULL DEFAULT 1,
|
|
321
|
-
confirmed INTEGER NOT NULL DEFAULT 0,
|
|
322
|
-
due_at INTEGER,
|
|
323
|
-
deadline_offset_minutes INTEGER
|
|
324
|
-
CHECK (deadline_offset_minutes BETWEEN -840 AND 840),
|
|
325
|
-
revision INTEGER NOT NULL DEFAULT 0 CHECK (revision >= 0),
|
|
326
|
-
temporal_epoch INTEGER NOT NULL DEFAULT 0 CHECK (temporal_epoch >= 0),
|
|
327
|
-
temporal_reconciled_occurrence_key TEXT,
|
|
328
|
-
last_temporal_checked_at INTEGER,
|
|
329
|
-
next_temporal_check_at INTEGER,
|
|
330
|
-
last_temporal_attempt_id INTEGER,
|
|
331
|
-
review_started_at INTEGER,
|
|
332
|
-
review_anchor_event_id TEXT,
|
|
333
|
-
review_anchor_source_channel TEXT,
|
|
334
|
-
completion_criteria TEXT,
|
|
335
|
-
resolution_kind TEXT,
|
|
336
|
-
created_at INTEGER NOT NULL,
|
|
337
|
-
updated_at INTEGER NOT NULL`;
|
|
338
|
-
// Unique-index predicate excludes terminal rows so a terminal keyed workorder
|
|
339
|
-
// frees its idempotency slot for a fresh insert (plan M1); owner upsert is a
|
|
340
|
-
// SELECT probe, unaffected.
|
|
341
|
-
static INDEXES_SQL = `
|
|
342
|
-
CREATE UNIQUE INDEX IF NOT EXISTS idx_operator_tasks_source
|
|
343
|
-
ON operator_tasks(source_channel, source_event_id)
|
|
344
|
-
WHERE source_event_id IS NOT NULL
|
|
345
|
-
AND status NOT IN ('done','failed','cancelled');
|
|
346
|
-
CREATE INDEX IF NOT EXISTS idx_operator_tasks_status ON operator_tasks(status);
|
|
347
|
-
CREATE INDEX IF NOT EXISTS idx_operator_tasks_deadline ON operator_tasks(deadline);`;
|
|
348
|
-
/**
|
|
349
|
-
* Stage-2 in-place upgrade for pre-existing tables. Guards are re-checked
|
|
350
|
-
* INSIDE `BEGIN IMMEDIATE`: two connections construct TaskLedger (boot +
|
|
351
|
-
* lazy API handler) and both run this - the loser must see the winner's
|
|
352
|
-
* finished work, not rebuild an already-migrated table.
|
|
353
|
-
*/
|
|
354
|
-
upgradeSchema() {
|
|
355
|
-
this.db.exec('BEGIN IMMEDIATE');
|
|
356
|
-
try {
|
|
357
|
-
const cols = this.db.prepare(`PRAGMA table_info(operator_tasks)`).all();
|
|
358
|
-
if (!cols.some((c) => c.name === 'kind')) {
|
|
359
|
-
this.db.exec(`ALTER TABLE operator_tasks ADD COLUMN kind TEXT NOT NULL DEFAULT 'owner'
|
|
360
|
-
CHECK (kind IN ('owner','system'))`);
|
|
361
|
-
}
|
|
362
|
-
if (!cols.some((c) => c.name === 'payload')) {
|
|
363
|
-
this.db.exec(`ALTER TABLE operator_tasks ADD COLUMN payload TEXT`);
|
|
364
|
-
}
|
|
365
|
-
// 'failed' lives in an inline CHECK -> table rebuild (copy-swap) required.
|
|
366
|
-
const tableSql = this.db
|
|
367
|
-
.prepare(`SELECT sql FROM sqlite_master WHERE type='table' AND name='operator_tasks'`)
|
|
368
|
-
.get().sql;
|
|
369
|
-
if (!tableSql.includes(`'failed'`)) {
|
|
370
|
-
const seqRow = this.db
|
|
371
|
-
.prepare(`SELECT seq FROM sqlite_sequence WHERE name='operator_tasks'`)
|
|
372
|
-
.get();
|
|
373
|
-
this.db.exec(`
|
|
374
|
-
CREATE TABLE operator_tasks_new (${TaskLedger.TABLE_COLUMNS_SQL});
|
|
375
|
-
INSERT INTO operator_tasks_new
|
|
376
|
-
(id, title, status, priority, kind, payload, assignee, deadline, source_channel,
|
|
377
|
-
source_event_id, latest_event, auto_created, confirmed, created_at, updated_at)
|
|
378
|
-
SELECT id, title, status, priority, kind, payload, assignee, deadline,
|
|
379
|
-
source_channel, source_event_id, latest_event, auto_created, confirmed,
|
|
380
|
-
created_at, updated_at
|
|
381
|
-
FROM operator_tasks;
|
|
382
|
-
DROP TABLE operator_tasks;
|
|
383
|
-
ALTER TABLE operator_tasks_new RENAME TO operator_tasks;
|
|
384
|
-
`);
|
|
385
|
-
if (seqRow) {
|
|
386
|
-
// sqlite_sequence has NO unique key on name - INSERT OR REPLACE
|
|
387
|
-
// would APPEND a duplicate row (review m2). UPDATE first; insert
|
|
388
|
-
// only when the rename left no row behind.
|
|
389
|
-
const updated = this.db
|
|
390
|
-
.prepare(`UPDATE sqlite_sequence SET seq = ? WHERE name = 'operator_tasks'`)
|
|
391
|
-
.run(seqRow.seq);
|
|
392
|
-
if (updated.changes === 0) {
|
|
393
|
-
this.db
|
|
394
|
-
.prepare(`INSERT INTO sqlite_sequence (name, seq) VALUES ('operator_tasks', ?)`)
|
|
395
|
-
.run(seqRow.seq);
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
(0, operator_task_temporal_js_1.applyOperatorTaskTemporalMigration)(this.db);
|
|
400
|
-
(0, operator_task_external_lifecycle_js_1.applyOperatorTaskExternalLifecycleMigration)(this.db);
|
|
401
|
-
(0, operator_owner_action_candidates_js_1.applyOperatorOwnerActionCandidatesMigration)(this.db);
|
|
402
|
-
(0, operator_task_review_lifecycle_js_1.applyOperatorTaskReviewLifecycleMigration)(this.db);
|
|
403
|
-
(0, operator_task_classification_js_1.applyOperatorTaskClassificationMigration)(this.db);
|
|
404
|
-
// Old-predicate unique index (no terminal exclusion) -> swap in place.
|
|
405
|
-
const idxRow = this.db
|
|
406
|
-
.prepare(`SELECT sql FROM sqlite_master WHERE type='index' AND name='idx_operator_tasks_source'`)
|
|
407
|
-
.get();
|
|
408
|
-
if (!idxRow || !idxRow.sql.includes('status NOT IN')) {
|
|
409
|
-
this.db.exec(`DROP INDEX IF EXISTS idx_operator_tasks_source;`);
|
|
410
|
-
}
|
|
411
|
-
this.db.exec(TaskLedger.INDEXES_SQL);
|
|
412
|
-
this.db.exec('COMMIT');
|
|
413
|
-
}
|
|
414
|
-
catch (error) {
|
|
415
|
-
this.db.exec('ROLLBACK');
|
|
416
|
-
throw error;
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
/** TaskSource conformance: open items in canonical board order. */
|
|
420
|
-
getTasks() {
|
|
421
|
-
return this.list({ order: 'deadline_priority' });
|
|
422
|
-
}
|
|
423
|
-
countOpenUnconfirmed() {
|
|
424
|
-
const row = this.db
|
|
425
|
-
.prepare(`SELECT COUNT(*) AS count
|
|
426
|
-
FROM operator_tasks
|
|
427
|
-
WHERE kind = 'owner'
|
|
428
|
-
AND auto_created = 1
|
|
429
|
-
AND confirmed = 0
|
|
430
|
-
AND status NOT IN ('done', 'cancelled')`)
|
|
431
|
-
.get();
|
|
432
|
-
return row.count;
|
|
433
|
-
}
|
|
434
|
-
list(filter = {}) {
|
|
435
|
-
const observedAt = this.temporalAsOf(filter.temporalAsOfMs);
|
|
436
|
-
const { where, params } = this.buildListPredicate({
|
|
437
|
-
...filter,
|
|
438
|
-
temporalAsOfMs: observedAt,
|
|
439
|
-
});
|
|
440
|
-
const rawLimit = Number(filter.limit);
|
|
441
|
-
// No LIMIT at all unless the caller asks for one: the whole board is the read, and the
|
|
442
|
-
// 50/200 bound is what made the board turn judge 216 tasks from 50 (owner, 2026-09-04).
|
|
443
|
-
const limit = Number.isFinite(rawLimit) ? Math.max(1, Math.floor(rawLimit)) : null;
|
|
444
|
-
const rows = this.db
|
|
445
|
-
.prepare(`SELECT * FROM operator_tasks
|
|
446
|
-
${where.length > 0 ? `WHERE ${where.join(' AND ')}` : ''}
|
|
447
|
-
ORDER BY ${TaskLedger.orderSql(filter.order)}
|
|
448
|
-
${limit === null ? '' : 'LIMIT ?'}`)
|
|
449
|
-
.all(...params, ...(limit === null ? [] : [limit]));
|
|
450
|
-
return rows.map((row) => this.toRecordAt(row, observedAt));
|
|
451
|
-
}
|
|
452
|
-
temporalAsOf(value) {
|
|
453
|
-
const observedAt = value ?? this.now();
|
|
454
|
-
if (!Number.isSafeInteger(observedAt)) {
|
|
455
|
-
throw new Error('task list temporal as-of must be a finite integer epoch millisecond');
|
|
456
|
-
}
|
|
457
|
-
return observedAt;
|
|
458
|
-
}
|
|
459
|
-
/** Shared filter predicate. Owner surface only - system workorder rows never appear in
|
|
460
|
-
* board/REST/gateway listings (Stage-2 kind filter; workorders have dedicated readers). */
|
|
461
|
-
buildListPredicate(filter) {
|
|
462
|
-
const where = [`kind = 'owner'`];
|
|
463
|
-
const params = [];
|
|
464
|
-
if (filter.status) {
|
|
465
|
-
where.push('status = ?');
|
|
466
|
-
params.push(filter.status);
|
|
467
|
-
}
|
|
468
|
-
else if (filter.includeTerminal === false) {
|
|
469
|
-
where.push("status NOT IN ('done', 'cancelled')");
|
|
470
|
-
}
|
|
471
|
-
if (filter.channel) {
|
|
472
|
-
where.push('source_channel = ?');
|
|
473
|
-
params.push(filter.channel);
|
|
474
|
-
}
|
|
475
|
-
if (filter.search) {
|
|
476
|
-
where.push('(title LIKE ? OR latest_event LIKE ? OR assignee LIKE ?)');
|
|
477
|
-
const like = `%${filter.search}%`;
|
|
478
|
-
params.push(like, like, like);
|
|
479
|
-
}
|
|
480
|
-
if (filter.assignee !== undefined) {
|
|
481
|
-
where.push('assignee = ?');
|
|
482
|
-
params.push(filter.assignee);
|
|
483
|
-
}
|
|
484
|
-
if (filter.priority !== undefined) {
|
|
485
|
-
where.push('priority = ?');
|
|
486
|
-
params.push(filter.priority);
|
|
487
|
-
}
|
|
488
|
-
if (filter.dueBeforeMs !== undefined) {
|
|
489
|
-
where.push('due_at IS NOT NULL AND due_at < ?');
|
|
490
|
-
params.push(filter.dueBeforeMs);
|
|
491
|
-
}
|
|
492
|
-
if (filter.dueAfterMs !== undefined) {
|
|
493
|
-
where.push('due_at IS NOT NULL AND due_at >= ?');
|
|
494
|
-
params.push(filter.dueAfterMs);
|
|
495
|
-
}
|
|
496
|
-
if (filter.updatedSinceMs !== undefined) {
|
|
497
|
-
where.push('updated_at >= ?');
|
|
498
|
-
params.push(filter.updatedSinceMs);
|
|
499
|
-
}
|
|
500
|
-
if (filter.updatedBeforeMs !== undefined) {
|
|
501
|
-
where.push('updated_at < ?');
|
|
502
|
-
params.push(filter.updatedBeforeMs);
|
|
503
|
-
}
|
|
504
|
-
if (filter.qualification === 'qualified') {
|
|
505
|
-
where.push('completion_criteria IS NOT NULL');
|
|
506
|
-
}
|
|
507
|
-
else if (filter.qualification === 'legacy_unqualified') {
|
|
508
|
-
where.push('completion_criteria IS NULL');
|
|
509
|
-
}
|
|
510
|
-
if (filter.dueBucket !== undefined) {
|
|
511
|
-
const observedAt = this.temporalAsOf(filter.temporalAsOfMs);
|
|
512
|
-
const rows = this.db
|
|
513
|
-
.prepare(`SELECT id, status, due_at, deadline, deadline_offset_minutes
|
|
514
|
-
FROM operator_tasks WHERE ${where.join(' AND ')}`)
|
|
515
|
-
.all(...params);
|
|
516
|
-
const ids = rows
|
|
517
|
-
.filter((row) => {
|
|
518
|
-
const state = (0, task_temporal_js_1.deriveTemporalState)({
|
|
519
|
-
status: row.status,
|
|
520
|
-
dueAt: row.due_at,
|
|
521
|
-
deadlineIso: row.deadline,
|
|
522
|
-
deadlineOffsetMinutes: row.deadline_offset_minutes,
|
|
523
|
-
}, observedAt, this.timeZone);
|
|
524
|
-
return (0, task_temporal_js_1.dueBucketForTemporalState)(state) === filter.dueBucket;
|
|
525
|
-
})
|
|
526
|
-
.map((row) => row.id);
|
|
527
|
-
// One JSON parameter avoids SQLite's variable limit even on a large board.
|
|
528
|
-
where.push('id IN (SELECT CAST(value AS INTEGER) FROM json_each(?))');
|
|
529
|
-
params.push(JSON.stringify(ids));
|
|
530
|
-
}
|
|
531
|
-
return { where, params };
|
|
532
|
-
}
|
|
533
|
-
/** ORDER BY for both list() and listPage(); the trailing id makes it a TOTAL order,
|
|
534
|
-
* which is what lets a keyset cursor resume without skipping or repeating a row.
|
|
535
|
-
* LIMIT applies AFTER ordering, so a bounded read still returns the true top-N. */
|
|
536
|
-
static orderSql(order) {
|
|
537
|
-
return order === 'updated'
|
|
538
|
-
? 'updated_at DESC, id DESC'
|
|
539
|
-
: // deadline asc NULLS LAST, then priority high>normal>low, then id.
|
|
540
|
-
`CASE WHEN deadline IS NULL THEN 1 ELSE 0 END ASC, COALESCE(deadline, '') ASC,
|
|
541
|
-
CASE priority WHEN 'high' THEN 0 WHEN 'normal' THEN 1 ELSE 2 END ASC, id ASC`;
|
|
542
|
-
}
|
|
543
|
-
static cursorFor(record, order) {
|
|
544
|
-
if (order === 'updated') {
|
|
545
|
-
return { order, k: [record.updatedAt, record.id] };
|
|
546
|
-
}
|
|
547
|
-
return {
|
|
548
|
-
order,
|
|
549
|
-
k: [
|
|
550
|
-
record.deadlineIso === null ? 1 : 0,
|
|
551
|
-
record.deadlineIso ?? '',
|
|
552
|
-
PRIORITY_RANK[record.priority] ?? 2,
|
|
553
|
-
record.id,
|
|
554
|
-
],
|
|
555
|
-
};
|
|
556
|
-
}
|
|
557
|
-
/**
|
|
558
|
-
* One page of the same ordered set, with the totals that make completeness provable.
|
|
559
|
-
*
|
|
560
|
-
* Keyset, not OFFSET: an offset page silently skips or repeats rows when the set
|
|
561
|
-
* changes between pages, which would leave a "whole situation" claim resting on a
|
|
562
|
-
* read that quietly lost items.
|
|
563
|
-
*/
|
|
564
|
-
listPage(filter = {}) {
|
|
565
|
-
const observedAt = this.temporalAsOf(filter.temporalAsOfMs);
|
|
566
|
-
const order = filter.order === 'updated' ? 'updated' : 'deadline_priority';
|
|
567
|
-
const { where, params } = this.buildListPredicate({
|
|
568
|
-
...filter,
|
|
569
|
-
temporalAsOfMs: observedAt,
|
|
570
|
-
});
|
|
571
|
-
const rawLimit = Number(filter.limit);
|
|
572
|
-
// No cap and a whole-board default: Kagemusha's task_list is selectAll, and the 50/200
|
|
573
|
-
// bound is what made the board turn judge 216 tasks from 50 (owner, 2026-09-04). The
|
|
574
|
-
// large default keeps the limit+1 next-page probe below intact.
|
|
575
|
-
const limit = Number.isFinite(rawLimit) ? Math.max(1, Math.floor(rawLimit)) : WHOLE_BOARD_LIMIT;
|
|
576
|
-
const totalRow = this.db
|
|
577
|
-
.prepare(`SELECT COUNT(*) AS total FROM operator_tasks WHERE ${where.join(' AND ')}`)
|
|
578
|
-
.get(...params);
|
|
579
|
-
const pageWhere = [...where];
|
|
580
|
-
const pageParams = [...params];
|
|
581
|
-
if (filter.cursor) {
|
|
582
|
-
const { k } = decodeListCursor(filter.cursor, order);
|
|
583
|
-
if (order === 'updated') {
|
|
584
|
-
pageWhere.push('(updated_at, id) < (?, ?)');
|
|
585
|
-
}
|
|
586
|
-
else {
|
|
587
|
-
pageWhere.push(`(CASE WHEN deadline IS NULL THEN 1 ELSE 0 END, COALESCE(deadline, ''),
|
|
588
|
-
CASE priority WHEN 'high' THEN 0 WHEN 'normal' THEN 1 ELSE 2 END, id) > (?, ?, ?, ?)`);
|
|
589
|
-
}
|
|
590
|
-
pageParams.push(...k);
|
|
591
|
-
}
|
|
592
|
-
// limit + 1 probes for a next page instead of guessing from a full page, so the
|
|
593
|
-
// final page never hands out a cursor that resolves to nothing.
|
|
594
|
-
const rows = this.db
|
|
595
|
-
.prepare(`SELECT * FROM operator_tasks
|
|
596
|
-
WHERE ${pageWhere.join(' AND ')}
|
|
597
|
-
ORDER BY ${TaskLedger.orderSql(filter.order)}
|
|
598
|
-
LIMIT ?`)
|
|
599
|
-
.all(...pageParams, limit + 1);
|
|
600
|
-
const hasMore = rows.length > limit;
|
|
601
|
-
const tasks = rows.slice(0, limit).map((row) => this.toRecordAt(row, observedAt));
|
|
602
|
-
const last = tasks[tasks.length - 1];
|
|
603
|
-
return {
|
|
604
|
-
tasks,
|
|
605
|
-
total: totalRow.total,
|
|
606
|
-
returned: tasks.length,
|
|
607
|
-
nextCursor: hasMore && last ? encodeListCursor(TaskLedger.cursorFor(last, order)) : null,
|
|
608
|
-
};
|
|
609
|
-
}
|
|
610
|
-
/**
|
|
611
|
-
* One `listPage`, with the read generation and observation instant captured
|
|
612
|
-
* in the SAME synchronous call so total/rows/version cannot straddle a write
|
|
613
|
-
* from this process. The progressive `items` view pins its cursor to this
|
|
614
|
-
* readVersion and restarts if a later page observes a different one.
|
|
615
|
-
*/
|
|
616
|
-
itemsPage(filter = {}) {
|
|
617
|
-
// One DEFERRED read transaction: the generation, the total and the rows all
|
|
618
|
-
// read the SAME SQLite snapshot, so another connection committing between the
|
|
619
|
-
// statements cannot make the returned readVersion disagree with the page it
|
|
620
|
-
// describes. Synchronous JS alone is not a cross-connection snapshot.
|
|
621
|
-
return this.readSnapshot(() => {
|
|
622
|
-
const observedAt = this.temporalAsOf(filter.temporalAsOfMs);
|
|
623
|
-
const readVersion = this.readGeneration();
|
|
624
|
-
const page = this.listPage({ ...filter, temporalAsOfMs: observedAt });
|
|
625
|
-
return { ...page, readVersion, observedAt };
|
|
626
|
-
});
|
|
627
|
-
}
|
|
628
|
-
/** The ledger's clock (injected in tests); progressive views stamp observedAt with it. */
|
|
629
|
-
nowMs() {
|
|
630
|
-
return this.now();
|
|
631
|
-
}
|
|
632
|
-
/**
|
|
633
|
-
* Run a read-only body inside a single DEFERRED transaction so every statement
|
|
634
|
-
* shares one snapshot. better-sqlite3 nests via SAVEPOINT, so this is safe even
|
|
635
|
-
* if a caller is already inside a transaction.
|
|
636
|
-
*/
|
|
637
|
-
readSnapshot(body) {
|
|
638
|
-
const runner = this.db.transaction(body);
|
|
639
|
-
return runner();
|
|
640
|
-
}
|
|
641
|
-
/**
|
|
642
|
-
* A conservative change signature for the whole owner board, derived from
|
|
643
|
-
* existing columns - no snapshot table. COUNT catches inserts/deletes,
|
|
644
|
-
* SUM(revision) catches every owner update (each bumps revision), MAX(id)
|
|
645
|
-
* catches inserts, MAX(updated_at) is a coarse tie-breaker. It is NOT an
|
|
646
|
-
* immutable snapshot id: it only lets a paged read reject its own cursor once
|
|
647
|
-
* ANY owner row has changed, so a "whole board" claim never spans a write.
|
|
648
|
-
* System workorder churn is deliberately excluded (kind='owner').
|
|
649
|
-
*/
|
|
650
|
-
readGeneration() {
|
|
651
|
-
const row = this.db
|
|
652
|
-
.prepare(`SELECT COUNT(*) AS c, COALESCE(SUM(revision), 0) AS r,
|
|
653
|
-
COALESCE(MAX(id), 0) AS m, COALESCE(MAX(updated_at), 0) AS u
|
|
654
|
-
FROM operator_tasks WHERE kind = 'owner'`)
|
|
655
|
-
.get();
|
|
656
|
-
return (0, node_crypto_1.createHash)('sha256')
|
|
657
|
-
.update(`${row.c}:${row.r}:${row.m}:${row.u}`)
|
|
658
|
-
.digest('base64url')
|
|
659
|
-
.slice(0, 22);
|
|
660
|
-
}
|
|
661
|
-
notifyOwnerTaskCommit(previousGeneration) {
|
|
662
|
-
if (!this.onOwnerTaskChangeCommitted) {
|
|
663
|
-
return;
|
|
664
|
-
}
|
|
665
|
-
const generation = this.readGeneration();
|
|
666
|
-
if (generation === previousGeneration) {
|
|
667
|
-
return;
|
|
668
|
-
}
|
|
669
|
-
this.pendingOwnerTaskGeneration = generation;
|
|
670
|
-
if (this.ownerTaskNotificationScheduled) {
|
|
671
|
-
return;
|
|
672
|
-
}
|
|
673
|
-
this.ownerTaskNotificationScheduled = true;
|
|
674
|
-
queueMicrotask(() => this.flushOwnerTaskNotification());
|
|
675
|
-
}
|
|
676
|
-
flushOwnerTaskNotification() {
|
|
677
|
-
this.ownerTaskNotificationScheduled = false;
|
|
678
|
-
const expected = this.pendingOwnerTaskGeneration;
|
|
679
|
-
this.pendingOwnerTaskGeneration = null;
|
|
680
|
-
if (!expected || !this.onOwnerTaskChangeCommitted) {
|
|
681
|
-
return;
|
|
682
|
-
}
|
|
683
|
-
const current = this.readGeneration();
|
|
684
|
-
if (current !== expected) {
|
|
685
|
-
this.pendingOwnerTaskGeneration = current;
|
|
686
|
-
this.ownerTaskNotificationScheduled = true;
|
|
687
|
-
queueMicrotask(() => this.flushOwnerTaskNotification());
|
|
688
|
-
return;
|
|
689
|
-
}
|
|
690
|
-
this.onOwnerTaskChangeCommitted(current);
|
|
691
|
-
}
|
|
692
|
-
/**
|
|
693
|
-
* Aggregate counts over the same owner predicate `listPage` filters by. Facets
|
|
694
|
-
* come from SQL GROUP BY (each partitions total); due buckets are computed in
|
|
695
|
-
* one JS pass over minimal rows using the SAME deriveTemporalState `items`
|
|
696
|
-
* uses, so an overview bucket and an item's temporal_state never disagree.
|
|
697
|
-
* readGeneration is captured in the same synchronous call as the counts.
|
|
698
|
-
*/
|
|
699
|
-
overview(filter = {}) {
|
|
700
|
-
return this.readSnapshot(() => this.overviewInSnapshot(filter));
|
|
701
|
-
}
|
|
702
|
-
overviewInSnapshot(filter) {
|
|
703
|
-
// One captured time for every derived due bucket; one snapshot for every count.
|
|
704
|
-
const now = this.temporalAsOf(filter.temporalAsOfMs);
|
|
705
|
-
const readVersion = this.readGeneration();
|
|
706
|
-
const { where, params } = this.buildListPredicate({ ...filter, temporalAsOfMs: now });
|
|
707
|
-
const whereSql = where.join(' AND ');
|
|
708
|
-
const groupBy = (column) => this.db
|
|
709
|
-
.prepare(`SELECT ${column} AS key, COUNT(*) AS count FROM operator_tasks
|
|
710
|
-
WHERE ${whereSql} GROUP BY ${column}`)
|
|
711
|
-
.all(...params);
|
|
712
|
-
const total = this.db
|
|
713
|
-
.prepare(`SELECT COUNT(*) AS total FROM operator_tasks WHERE ${whereSql}`)
|
|
714
|
-
.get(...params).total;
|
|
715
|
-
const status = {};
|
|
716
|
-
for (const row of groupBy('status'))
|
|
717
|
-
status[String(row.key)] = row.count;
|
|
718
|
-
const priority = {};
|
|
719
|
-
for (const row of groupBy('priority'))
|
|
720
|
-
priority[String(row.key)] = row.count;
|
|
721
|
-
const channels = groupBy('source_channel').map((row) => ({
|
|
722
|
-
channel: row.key,
|
|
723
|
-
count: row.count,
|
|
724
|
-
}));
|
|
725
|
-
const assignees = groupBy('assignee').map((row) => ({ assignee: row.key, count: row.count }));
|
|
726
|
-
const due = { missing: 0, overdue: 0, upcoming: 0, closed: 0 };
|
|
727
|
-
const rows = this.db
|
|
728
|
-
.prepare(`SELECT status, due_at, deadline, deadline_offset_minutes
|
|
729
|
-
FROM operator_tasks WHERE ${whereSql}`)
|
|
730
|
-
.all(...params);
|
|
731
|
-
for (const row of rows) {
|
|
732
|
-
const state = (0, task_temporal_js_1.deriveTemporalState)({
|
|
733
|
-
status: row.status,
|
|
734
|
-
dueAt: row.due_at,
|
|
735
|
-
deadlineIso: row.deadline,
|
|
736
|
-
deadlineOffsetMinutes: row.deadline_offset_minutes,
|
|
737
|
-
}, now, this.timeZone);
|
|
738
|
-
due[(0, task_temporal_js_1.dueBucketForTemporalState)(state)] += 1;
|
|
739
|
-
}
|
|
740
|
-
return { total, observedAt: now, readVersion, status, priority, channels, assignees, due };
|
|
741
|
-
}
|
|
742
|
-
/** Internal bounded page for temporal reconciliation; excludes rows that can never be candidates. */
|
|
743
|
-
listTemporalScanPage(input) {
|
|
744
|
-
const rawLimit = Number(input.limit);
|
|
745
|
-
const rawAfterId = Number(input.afterId);
|
|
746
|
-
const limit = Number.isFinite(rawLimit)
|
|
747
|
-
? Math.max(1, Math.min(200, Math.floor(rawLimit)))
|
|
748
|
-
: 200;
|
|
749
|
-
const afterId = Number.isFinite(rawAfterId) ? Math.max(0, Math.floor(rawAfterId)) : 0;
|
|
750
|
-
const rows = this.db
|
|
751
|
-
.prepare(`SELECT * FROM operator_tasks INDEXED BY idx_operator_tasks_temporal_scan_id
|
|
752
|
-
WHERE kind = 'owner'
|
|
753
|
-
AND status IN ('pending','in_progress','review','blocked')
|
|
754
|
-
AND id > ?
|
|
755
|
-
AND (due_at IS NOT NULL OR deadline IS NOT NULL OR next_temporal_check_at IS NOT NULL)
|
|
756
|
-
ORDER BY id ASC
|
|
757
|
-
LIMIT ?`)
|
|
758
|
-
.all(afterId, limit);
|
|
759
|
-
return rows.map((row) => this.toRecord(row));
|
|
760
|
-
}
|
|
761
|
-
/** Owner rows only - system workorder rows are invisible to external reads. */
|
|
762
|
-
getById(id) {
|
|
763
|
-
const record = this.getRowById(id);
|
|
764
|
-
return record && record.kind === 'owner' ? record : null;
|
|
765
|
-
}
|
|
766
|
-
/** Internal fetch without the kind filter (guards and workorder paths). */
|
|
767
|
-
getRowById(id) {
|
|
768
|
-
const row = this.db.prepare(`SELECT * FROM operator_tasks WHERE id = ?`).get(id);
|
|
769
|
-
return row ? this.toRecord(row) : null;
|
|
770
|
-
}
|
|
771
|
-
/** Returns one active, explicitly receipted external identity for an owner task. */
|
|
772
|
-
getExternalBinding(taskId) {
|
|
773
|
-
const row = this.db
|
|
774
|
-
.prepare(`SELECT id, revision, task_id, connector, source_type, external_source_id,
|
|
775
|
-
last_observation_seq, created_by_attempt_id, created_by_run_id,
|
|
776
|
-
created_by_owner_scope, created_at
|
|
777
|
-
FROM operator_external_task_bindings
|
|
778
|
-
WHERE task_id = ? AND active = 1`)
|
|
779
|
-
.get(taskId);
|
|
780
|
-
return row ? this.externalBindingFromRow(row) : null;
|
|
781
|
-
}
|
|
782
|
-
/**
|
|
783
|
-
* Host-only lookup material for immutable external-lifecycle candidates.
|
|
784
|
-
* The caller supplies exact event/source ids obtained from the connector index;
|
|
785
|
-
* no connector prose is read through this ledger surface.
|
|
786
|
-
*/
|
|
787
|
-
getExternalLifecycleCandidateSupport(eventIds, externalSourceIds) {
|
|
788
|
-
const uniqueEventIds = [...new Set(eventIds)];
|
|
789
|
-
const uniqueSourceIds = [...new Set(externalSourceIds)];
|
|
790
|
-
const directTaskIdsByEventId = new Map();
|
|
791
|
-
const effectTaskIdsByEventId = new Map();
|
|
792
|
-
const bindings = [];
|
|
793
|
-
if (uniqueEventIds.length > 0) {
|
|
794
|
-
const placeholders = uniqueEventIds.map(() => '?').join(',');
|
|
795
|
-
const directRows = this.db
|
|
796
|
-
.prepare(`SELECT source_event_id, id FROM operator_tasks
|
|
797
|
-
WHERE kind = 'owner' AND source_event_id IN (${placeholders})`)
|
|
798
|
-
.all(...uniqueEventIds);
|
|
799
|
-
for (const row of directRows) {
|
|
800
|
-
const ids = directTaskIdsByEventId.get(row.source_event_id) ?? [];
|
|
801
|
-
directTaskIdsByEventId.set(row.source_event_id, [...ids, row.id]);
|
|
802
|
-
}
|
|
803
|
-
const effectRows = this.db
|
|
804
|
-
.prepare(`SELECT DISTINCT json_each.value AS event_id, evidence_effects.target_id AS task_id
|
|
805
|
-
FROM evidence_effects, json_each(evidence_effects.source_event_ids_json)
|
|
806
|
-
WHERE evidence_effects.target_type = 'task'
|
|
807
|
-
AND json_each.value IN (${placeholders})`)
|
|
808
|
-
.all(...uniqueEventIds);
|
|
809
|
-
for (const row of effectRows) {
|
|
810
|
-
const taskId = Number(row.task_id);
|
|
811
|
-
if (!Number.isSafeInteger(taskId) || taskId < 1)
|
|
812
|
-
continue;
|
|
813
|
-
const ids = effectTaskIdsByEventId.get(row.event_id) ?? [];
|
|
814
|
-
effectTaskIdsByEventId.set(row.event_id, [...ids, taskId]);
|
|
815
|
-
}
|
|
816
|
-
}
|
|
817
|
-
if (uniqueSourceIds.length > 0) {
|
|
818
|
-
const placeholders = uniqueSourceIds.map(() => '?').join(',');
|
|
819
|
-
const rows = this.db
|
|
820
|
-
.prepare(`SELECT id, revision, task_id, connector, source_type, external_source_id,
|
|
821
|
-
last_observation_seq
|
|
822
|
-
FROM operator_external_task_bindings
|
|
823
|
-
WHERE active = 1 AND connector = 'kagemusha' AND source_type = 'kanban_card'
|
|
824
|
-
AND external_source_id IN (${placeholders})`)
|
|
825
|
-
.all(...uniqueSourceIds);
|
|
826
|
-
for (const row of rows) {
|
|
827
|
-
bindings.push({
|
|
828
|
-
bindingId: row.id,
|
|
829
|
-
bindingRevision: row.revision,
|
|
830
|
-
taskId: row.task_id,
|
|
831
|
-
connector: row.connector,
|
|
832
|
-
sourceType: row.source_type,
|
|
833
|
-
externalSourceId: row.external_source_id,
|
|
834
|
-
lastObservationSeq: row.last_observation_seq,
|
|
835
|
-
});
|
|
836
|
-
}
|
|
837
|
-
}
|
|
838
|
-
const taskIds = new Set();
|
|
839
|
-
for (const ids of directTaskIdsByEventId.values())
|
|
840
|
-
ids.forEach((id) => taskIds.add(id));
|
|
841
|
-
for (const ids of effectTaskIdsByEventId.values())
|
|
842
|
-
ids.forEach((id) => taskIds.add(id));
|
|
843
|
-
for (const binding of bindings)
|
|
844
|
-
taskIds.add(binding.taskId);
|
|
845
|
-
const tasksById = new Map();
|
|
846
|
-
if (taskIds.size > 0) {
|
|
847
|
-
const ids = [...taskIds];
|
|
848
|
-
const placeholders = ids.map(() => '?').join(',');
|
|
849
|
-
const rows = this.db
|
|
850
|
-
.prepare(`SELECT id, revision FROM operator_tasks WHERE kind = 'owner' AND id IN (${placeholders})`)
|
|
851
|
-
.all(...ids);
|
|
852
|
-
for (const row of rows)
|
|
853
|
-
tasksById.set(row.id, { taskId: row.id, revision: row.revision });
|
|
854
|
-
}
|
|
855
|
-
return { taskHints: { directTaskIdsByEventId, effectTaskIdsByEventId }, tasksById, bindings };
|
|
856
|
-
}
|
|
857
|
-
getReceiptedExternalCandidateIds(candidateIds) {
|
|
858
|
-
const ids = [...new Set(candidateIds)];
|
|
859
|
-
if (ids.length === 0)
|
|
860
|
-
return new Set();
|
|
861
|
-
const placeholders = ids.map(() => '?').join(',');
|
|
862
|
-
const rows = this.db
|
|
863
|
-
.prepare(`SELECT candidate_id FROM operator_external_binding_receipts WHERE candidate_id IN (${placeholders})
|
|
864
|
-
UNION
|
|
865
|
-
SELECT candidate_id FROM operator_external_lifecycle_receipts WHERE candidate_id IN (${placeholders})`)
|
|
866
|
-
.all(...ids, ...ids);
|
|
867
|
-
return new Set(rows.map((row) => row.candidate_id));
|
|
868
|
-
}
|
|
869
|
-
/**
|
|
870
|
-
* Recover a candidate only from the payload of the exact currently claimed
|
|
871
|
-
* board attempt. Revalidate the serialized payload: database bytes are not
|
|
872
|
-
* a capability merely because they originated in a previous host process.
|
|
873
|
-
*/
|
|
874
|
-
loadBoardCandidate(attemptId, candidateId, kind) {
|
|
875
|
-
const attempt = this.getWorkOrderById(attemptId);
|
|
876
|
-
if (!attempt || attempt.workKind !== 'board' || attempt.status !== 'in_progress') {
|
|
877
|
-
throw new Error(`external lifecycle candidate requires claimed board attempt ${attemptId}`);
|
|
878
|
-
}
|
|
879
|
-
const payload = publisherPayloadOfStoredWorkOrder(attempt.payload);
|
|
880
|
-
(0, workorder_publishers_js_1.validateWorkOrderPayload)('board', payload);
|
|
881
|
-
if (payload.mode !== 'reconcile' || !payload.candidates) {
|
|
882
|
-
throw new Error(`external lifecycle candidate ${candidateId} is absent from attempt ${attemptId}`);
|
|
883
|
-
}
|
|
884
|
-
const candidatePayload = payload.candidates;
|
|
885
|
-
const candidates = kind === 'binding'
|
|
886
|
-
? candidatePayload.bindingCandidates
|
|
887
|
-
: candidatePayload.lifecycleCandidates;
|
|
888
|
-
const candidate = candidates.find((value) => value.candidateId === candidateId);
|
|
889
|
-
if (!candidate) {
|
|
890
|
-
throw new Error(`external lifecycle ${kind} candidate ${candidateId} is absent from attempt ${attemptId}`);
|
|
891
|
-
}
|
|
892
|
-
return candidate;
|
|
893
|
-
}
|
|
894
|
-
/**
|
|
895
|
-
* Read the complete, immutable candidate set for a board attempt against
|
|
896
|
-
* its durable decision receipts. This deliberately works for stale claims
|
|
897
|
-
* as well as live in-progress attempts: recovery must never infer effects
|
|
898
|
-
* from a runner result when the database can answer exactly.
|
|
899
|
-
*/
|
|
900
|
-
inspectBoardCandidateAttempt(attemptId) {
|
|
901
|
-
const attempt = this.getWorkOrderById(attemptId);
|
|
902
|
-
if (!attempt || attempt.workKind !== 'board') {
|
|
903
|
-
throw new Error(`board candidate inspection requires board attempt ${attemptId}`);
|
|
904
|
-
}
|
|
905
|
-
const payload = publisherPayloadOfStoredWorkOrder(attempt.payload);
|
|
906
|
-
(0, workorder_publishers_js_1.validateWorkOrderPayload)('board', payload);
|
|
907
|
-
if (payload.mode !== 'reconcile' || !payload.candidates) {
|
|
908
|
-
return { disposition: 'none' };
|
|
909
|
-
}
|
|
910
|
-
const candidates = payload.candidates;
|
|
911
|
-
const all = [...candidates.bindingCandidates, ...candidates.lifecycleCandidates];
|
|
912
|
-
if (all.length === 0) {
|
|
913
|
-
return { disposition: 'none' };
|
|
914
|
-
}
|
|
915
|
-
// Receipts are global by candidate id, so a decision settled by an owner
|
|
916
|
-
// run is seen here exactly like one settled by this attempt.
|
|
917
|
-
return this.candidateReceiptDisposition(all);
|
|
918
|
-
}
|
|
919
|
-
/**
|
|
920
|
-
* Resolve one candidate decision's authority (Task B2).
|
|
921
|
-
*
|
|
922
|
-
* A plain attempt id is the legacy Board wrapper. A discriminated source is
|
|
923
|
-
* either that same Board path or a verified owner run whose host-attested
|
|
924
|
-
* snapshot must match the caller's scope, run and envelope exactly. Both
|
|
925
|
-
* return the SAME decision inputs; the receipt records which one settled it.
|
|
926
|
-
*/
|
|
927
|
-
resolveExternalCandidateAuthority(source, kind, origin) {
|
|
928
|
-
const resolved = typeof source === 'number' ? { kind: 'board', attemptId: source } : source;
|
|
929
|
-
const label = kind === 'binding' ? 'external binding' : 'external lifecycle';
|
|
930
|
-
if (resolved.kind === 'board') {
|
|
931
|
-
const attemptId = resolved.attemptId;
|
|
932
|
-
if (origin.workOrderAttemptId !== attemptId) {
|
|
933
|
-
throw new Error(`${label} decision requires trusted attempt origin ${attemptId}`);
|
|
934
|
-
}
|
|
935
|
-
return {
|
|
936
|
-
source: resolved,
|
|
937
|
-
receiptOrigin: {
|
|
938
|
-
attemptId,
|
|
939
|
-
runId: origin.runId ?? null,
|
|
940
|
-
ownerScope: null,
|
|
941
|
-
envelopeHash: null,
|
|
942
|
-
},
|
|
943
|
-
load: (candidateId) => {
|
|
944
|
-
const attempt = this.getWorkOrderById(attemptId);
|
|
945
|
-
return {
|
|
946
|
-
candidate: this.loadBoardCandidate(attemptId, candidateId, kind),
|
|
947
|
-
snapshotAt: attempt?.createdAt ?? 0,
|
|
948
|
-
};
|
|
949
|
-
},
|
|
950
|
-
};
|
|
951
|
-
}
|
|
952
|
-
const context = requireOwnerActionContext(resolved.context);
|
|
953
|
-
if (origin.runId !== context.modelRunId) {
|
|
954
|
-
throw new Error(`${label} decision requires trusted owner run origin ${context.modelRunId}`);
|
|
955
|
-
}
|
|
956
|
-
if ((origin.workOrderAttemptId ?? null) !== context.workOrderAttemptId) {
|
|
957
|
-
throw new Error(`${label} decision attempt ${String(origin.workOrderAttemptId)} does not match the attesting owner run`);
|
|
958
|
-
}
|
|
959
|
-
if (context.workOrderAttemptId !== null) {
|
|
960
|
-
this.assertWorkOrderAttemptActive(label, context.workOrderAttemptId);
|
|
961
|
-
}
|
|
962
|
-
return {
|
|
963
|
-
source: resolved,
|
|
964
|
-
receiptOrigin: {
|
|
965
|
-
attemptId: context.workOrderAttemptId,
|
|
966
|
-
runId: context.modelRunId,
|
|
967
|
-
ownerScope: context.ownerScope,
|
|
968
|
-
envelopeHash: context.envelopeHash,
|
|
969
|
-
},
|
|
970
|
-
load: (candidateId) => {
|
|
971
|
-
const attested = this.loadOwnerAttestedCandidate(context, candidateId, kind);
|
|
972
|
-
return { candidate: attested.candidate, snapshotAt: attested.attestedAt };
|
|
973
|
-
},
|
|
974
|
-
};
|
|
975
|
-
}
|
|
976
|
-
/** Same contract as loadBoardCandidate, for either authority source. */
|
|
977
|
-
loadExternalCandidate(source, candidateId, kind) {
|
|
978
|
-
const resolved = typeof source === 'number' ? { kind: 'board', attemptId: source } : source;
|
|
979
|
-
return resolved.kind === 'board'
|
|
980
|
-
? this.loadBoardCandidate(resolved.attemptId, candidateId, kind)
|
|
981
|
-
: this.loadOwnerActionCandidate(resolved.context, candidateId, kind);
|
|
982
|
-
}
|
|
983
|
-
/**
|
|
984
|
-
* Persist host-built candidate snapshots under a verified owner run.
|
|
985
|
-
*
|
|
986
|
-
* Every snapshot is re-validated as host-built (exact shape, Kagemusha
|
|
987
|
-
* mapping, fixed evidence summary, content-derived id) before it is stored;
|
|
988
|
-
* model-authored bytes never enter this table. Rows are immutable. The same
|
|
989
|
-
* run attesting the same candidate again with identical bytes is a no-op;
|
|
990
|
-
* different bytes under the same id are a conflict, not a second row.
|
|
991
|
-
*/
|
|
992
|
-
attestOwnerActionCandidates(context, candidates) {
|
|
993
|
-
const verified = requireOwnerActionContext(context);
|
|
994
|
-
const prepared = [
|
|
995
|
-
...candidates.bindingCandidates.map((candidate) => (0, external_lifecycle_candidates_js_1.parseHostBuiltExternalLifecycleCandidate)('binding', candidate)),
|
|
996
|
-
...candidates.lifecycleCandidates.map((candidate) => (0, external_lifecycle_candidates_js_1.parseHostBuiltExternalLifecycleCandidate)('lifecycle', candidate)),
|
|
997
|
-
].map((candidate) => {
|
|
998
|
-
const json = (0, external_lifecycle_candidates_js_1.canonicalExternalLifecycleCandidateJson)(candidate);
|
|
999
|
-
return { candidate, json, sha256: (0, node_crypto_1.createHash)('sha256').update(json).digest('hex') };
|
|
1000
|
-
});
|
|
1001
|
-
let attested = 0;
|
|
1002
|
-
let alreadyAttested = 0;
|
|
1003
|
-
this.db.exec('BEGIN IMMEDIATE');
|
|
1004
|
-
try {
|
|
1005
|
-
for (const entry of prepared) {
|
|
1006
|
-
const existing = this.db
|
|
1007
|
-
.prepare(`SELECT owner_scope, envelope_hash, occurrence_key, workorder_attempt_id,
|
|
1008
|
-
candidate_kind, candidate_sha256
|
|
1009
|
-
FROM ${operator_owner_action_candidates_js_1.OWNER_ACTION_CANDIDATES_TABLE}
|
|
1010
|
-
WHERE model_run_id = ? AND candidate_id = ?`)
|
|
1011
|
-
.get(verified.modelRunId, entry.candidate.candidateId);
|
|
1012
|
-
if (existing) {
|
|
1013
|
-
if (existing.owner_scope !== verified.ownerScope ||
|
|
1014
|
-
existing.envelope_hash !== verified.envelopeHash ||
|
|
1015
|
-
existing.occurrence_key !== verified.occurrenceKey ||
|
|
1016
|
-
existing.workorder_attempt_id !== verified.workOrderAttemptId ||
|
|
1017
|
-
existing.candidate_kind !== entry.candidate.kind ||
|
|
1018
|
-
existing.candidate_sha256 !== entry.sha256) {
|
|
1019
|
-
throw new Error(`owner action candidate ${entry.candidate.candidateId} attestation conflict: run ${verified.modelRunId} already holds a snapshot that differs`);
|
|
1020
|
-
}
|
|
1021
|
-
alreadyAttested += 1;
|
|
1022
|
-
continue;
|
|
1023
|
-
}
|
|
1024
|
-
const task = this.getRowById(entry.candidate.taskId);
|
|
1025
|
-
if (!task || task.kind !== 'owner') {
|
|
1026
|
-
throw new Error(`owner action candidate ${entry.candidate.candidateId} references no owner task`);
|
|
1027
|
-
}
|
|
1028
|
-
this.db
|
|
1029
|
-
.prepare(`INSERT INTO ${operator_owner_action_candidates_js_1.OWNER_ACTION_CANDIDATES_TABLE}
|
|
1030
|
-
(model_run_id, candidate_id, owner_scope, envelope_hash, occurrence_key,
|
|
1031
|
-
workorder_attempt_id, candidate_kind, task_id, event_id, candidate_json,
|
|
1032
|
-
candidate_sha256, created_at)
|
|
1033
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
1034
|
-
.run(verified.modelRunId, entry.candidate.candidateId, verified.ownerScope, verified.envelopeHash, verified.occurrenceKey, verified.workOrderAttemptId, entry.candidate.kind, entry.candidate.taskId, entry.candidate.eventId, entry.json, entry.sha256, this.now());
|
|
1035
|
-
attested += 1;
|
|
1036
|
-
}
|
|
1037
|
-
this.db.exec('COMMIT');
|
|
1038
|
-
}
|
|
1039
|
-
catch (error) {
|
|
1040
|
-
this.db.exec('ROLLBACK');
|
|
1041
|
-
throw error;
|
|
1042
|
-
}
|
|
1043
|
-
return { attested, alreadyAttested };
|
|
1044
|
-
}
|
|
1045
|
-
/** Bounded, intent-free listing of what one owner run attested. */
|
|
1046
|
-
listOwnerActionCandidates(context) {
|
|
1047
|
-
const verified = requireOwnerActionContext(context);
|
|
1048
|
-
return this.db
|
|
1049
|
-
.prepare(`SELECT candidate_id, candidate_kind, task_id, event_id
|
|
1050
|
-
FROM ${operator_owner_action_candidates_js_1.OWNER_ACTION_CANDIDATES_TABLE}
|
|
1051
|
-
WHERE model_run_id = ? AND owner_scope = ? AND envelope_hash = ?
|
|
1052
|
-
AND occurrence_key = ? AND workorder_attempt_id IS ?
|
|
1053
|
-
ORDER BY created_at ASC, candidate_kind ASC, candidate_id ASC`)
|
|
1054
|
-
.all(verified.modelRunId, verified.ownerScope, verified.envelopeHash, verified.occurrenceKey, verified.workOrderAttemptId).map((row) => ({
|
|
1055
|
-
candidateId: row.candidate_id,
|
|
1056
|
-
kind: row.candidate_kind,
|
|
1057
|
-
taskId: row.task_id,
|
|
1058
|
-
eventId: row.event_id,
|
|
1059
|
-
}));
|
|
1060
|
-
}
|
|
1061
|
-
/**
|
|
1062
|
-
* Receipt view of one owner run's attested candidates, in the same shape as
|
|
1063
|
-
* inspectBoardCandidateAttempt so recovery can treat both sources alike.
|
|
1064
|
-
*/
|
|
1065
|
-
inspectOwnerActionCandidateRun(context) {
|
|
1066
|
-
const all = this.listOwnerActionCandidates(context);
|
|
1067
|
-
if (all.length === 0) {
|
|
1068
|
-
return { disposition: 'none' };
|
|
1069
|
-
}
|
|
1070
|
-
return this.candidateReceiptDisposition(all);
|
|
1071
|
-
}
|
|
1072
|
-
loadOwnerActionCandidate(context, candidateId, kind) {
|
|
1073
|
-
return this.loadOwnerAttestedCandidate(requireOwnerActionContext(context), candidateId, kind)
|
|
1074
|
-
.candidate;
|
|
1075
|
-
}
|
|
1076
|
-
/**
|
|
1077
|
-
* Recover a candidate only from the attestation the exact owner run made.
|
|
1078
|
-
* Stored bytes are revalidated: sha256 over the canonical JSON, exact shape,
|
|
1079
|
-
* and the content-derived candidate id must all still agree.
|
|
1080
|
-
*/
|
|
1081
|
-
loadOwnerAttestedCandidate(context, candidateId, kind) {
|
|
1082
|
-
const row = this.db
|
|
1083
|
-
.prepare(`SELECT owner_scope, envelope_hash, occurrence_key, workorder_attempt_id,
|
|
1084
|
-
candidate_kind, task_id, event_id, candidate_json, candidate_sha256, created_at
|
|
1085
|
-
FROM ${operator_owner_action_candidates_js_1.OWNER_ACTION_CANDIDATES_TABLE}
|
|
1086
|
-
WHERE model_run_id = ? AND candidate_id = ?`)
|
|
1087
|
-
.get(context.modelRunId, candidateId);
|
|
1088
|
-
if (!row) {
|
|
1089
|
-
throw new Error(`external lifecycle ${kind} candidate ${candidateId} is not attested for owner run ${context.modelRunId}`);
|
|
1090
|
-
}
|
|
1091
|
-
if (row.owner_scope !== context.ownerScope) {
|
|
1092
|
-
throw new Error(`external lifecycle ${kind} candidate ${candidateId} was attested under a different owner scope`);
|
|
1093
|
-
}
|
|
1094
|
-
if (row.envelope_hash !== context.envelopeHash) {
|
|
1095
|
-
throw new Error(`external lifecycle ${kind} candidate ${candidateId} was attested under a different envelope`);
|
|
1096
|
-
}
|
|
1097
|
-
if (row.occurrence_key !== context.occurrenceKey) {
|
|
1098
|
-
throw new Error(`external lifecycle ${kind} candidate ${candidateId} was attested under a different occurrence`);
|
|
1099
|
-
}
|
|
1100
|
-
if (row.workorder_attempt_id !== context.workOrderAttemptId) {
|
|
1101
|
-
throw new Error(`external lifecycle ${kind} candidate ${candidateId} was attested under a different workorder attempt`);
|
|
1102
|
-
}
|
|
1103
|
-
if (row.candidate_kind !== kind) {
|
|
1104
|
-
throw new Error(`external lifecycle candidate ${candidateId} has the wrong kind (attested as ${row.candidate_kind})`);
|
|
1105
|
-
}
|
|
1106
|
-
const digest = (0, node_crypto_1.createHash)('sha256').update(row.candidate_json).digest('hex');
|
|
1107
|
-
if (digest !== row.candidate_sha256) {
|
|
1108
|
-
throw new Error(`external lifecycle ${kind} candidate ${candidateId} failed attestation integrity (sha256 mismatch)`);
|
|
1109
|
-
}
|
|
1110
|
-
let parsed;
|
|
1111
|
-
try {
|
|
1112
|
-
parsed = JSON.parse(row.candidate_json);
|
|
1113
|
-
}
|
|
1114
|
-
catch {
|
|
1115
|
-
throw new Error(`external lifecycle ${kind} candidate ${candidateId} failed attestation integrity (unreadable)`);
|
|
1116
|
-
}
|
|
1117
|
-
const candidate = (0, external_lifecycle_candidates_js_1.parseHostBuiltExternalLifecycleCandidate)(kind, parsed);
|
|
1118
|
-
if (candidate.candidateId !== candidateId ||
|
|
1119
|
-
candidate.taskId !== row.task_id ||
|
|
1120
|
-
candidate.eventId !== row.event_id ||
|
|
1121
|
-
(0, external_lifecycle_candidates_js_1.canonicalExternalLifecycleCandidateJson)(candidate) !== row.candidate_json) {
|
|
1122
|
-
throw new Error(`external lifecycle ${kind} candidate ${candidateId} failed attestation integrity (identity mismatch)`);
|
|
1123
|
-
}
|
|
1124
|
-
return { candidate, attestedAt: row.created_at };
|
|
1125
|
-
}
|
|
1126
|
-
candidateReceiptDisposition(all) {
|
|
1127
|
-
const outcomes = [];
|
|
1128
|
-
const missingCandidateIds = [];
|
|
1129
|
-
for (const candidate of all) {
|
|
1130
|
-
const receipt = this.getExternalCandidateReceipt(candidate.candidateId);
|
|
1131
|
-
if (!receipt) {
|
|
1132
|
-
missingCandidateIds.push(candidate.candidateId);
|
|
1133
|
-
continue;
|
|
1134
|
-
}
|
|
1135
|
-
if (receipt.kind !== candidate.kind) {
|
|
1136
|
-
throw new Error(`external lifecycle candidate ${candidate.candidateId} has a receipt of the wrong kind`);
|
|
1137
|
-
}
|
|
1138
|
-
outcomes.push(receipt.outcome);
|
|
1139
|
-
}
|
|
1140
|
-
if (missingCandidateIds.length === 0) {
|
|
1141
|
-
return { disposition: 'complete', outcomes };
|
|
1142
|
-
}
|
|
1143
|
-
if (missingCandidateIds.length === all.length) {
|
|
1144
|
-
return { disposition: 'zero' };
|
|
1145
|
-
}
|
|
1146
|
-
return { disposition: 'partial', missingCandidateIds };
|
|
1147
|
-
}
|
|
1148
|
-
applyExternalBindingDecision(source, input, origin) {
|
|
1149
|
-
(0, external_lifecycle_candidates_js_1.validateExternalLifecycleDecision)('binding', input);
|
|
1150
|
-
const authority = this.resolveExternalCandidateAuthority(source, 'binding', origin);
|
|
1151
|
-
const receiptOrigin = authority.receiptOrigin;
|
|
1152
|
-
let receipt = null;
|
|
1153
|
-
this.db.exec('BEGIN IMMEDIATE');
|
|
1154
|
-
try {
|
|
1155
|
-
const { candidate } = authority.load(input.candidate_id);
|
|
1156
|
-
if (candidate.kind !== 'binding') {
|
|
1157
|
-
throw new Error(`external lifecycle candidate ${input.candidate_id} has the wrong kind`);
|
|
1158
|
-
}
|
|
1159
|
-
if (input.expected_revision !== candidate.taskRevision) {
|
|
1160
|
-
throw new Error(`external binding candidate ${candidate.candidateId} revision does not match its host snapshot`);
|
|
1161
|
-
}
|
|
1162
|
-
if (origin.causeEventIds?.length !== 1 || origin.causeEventIds[0] !== candidate.eventId) {
|
|
1163
|
-
throw new Error(`external binding candidate ${candidate.candidateId} requires its exact host origin event`);
|
|
1164
|
-
}
|
|
1165
|
-
const existing = this.getExternalCandidateReceipt(candidate.candidateId);
|
|
1166
|
-
if (existing) {
|
|
1167
|
-
if (existing.kind !== 'binding') {
|
|
1168
|
-
throw new Error(`external lifecycle candidate ${candidate.candidateId} already has another receipt kind`);
|
|
1169
|
-
}
|
|
1170
|
-
this.assertBindingReplayMatches(existing, candidate, input);
|
|
1171
|
-
receipt = existing;
|
|
1172
|
-
}
|
|
1173
|
-
else {
|
|
1174
|
-
const task = this.getRowById(candidate.taskId);
|
|
1175
|
-
if (!task || task.kind !== 'owner') {
|
|
1176
|
-
throw new Error(`external binding candidate ${candidate.candidateId} references no owner task`);
|
|
1177
|
-
}
|
|
1178
|
-
const outcome = task.revision === candidate.taskRevision
|
|
1179
|
-
? input.decision === 'bind'
|
|
1180
|
-
? 'bound'
|
|
1181
|
-
: 'declined'
|
|
1182
|
-
: 'superseded';
|
|
1183
|
-
let bindingId;
|
|
1184
|
-
if (outcome === 'bound') {
|
|
1185
|
-
const collision = this.db
|
|
1186
|
-
.prepare(`SELECT id, task_id, external_source_id FROM operator_external_task_bindings
|
|
1187
|
-
WHERE active = 1 AND (task_id = ? OR (connector = ? AND source_type = ? AND external_source_id = ?))`)
|
|
1188
|
-
.get(candidate.taskId, candidate.connector, candidate.sourceType, candidate.externalSourceId);
|
|
1189
|
-
if (collision) {
|
|
1190
|
-
throw new Error(`external binding conflict for task ${candidate.taskId} or ${candidate.externalSourceId}`);
|
|
1191
|
-
}
|
|
1192
|
-
const now = this.now();
|
|
1193
|
-
const inserted = this.db
|
|
1194
|
-
.prepare(`INSERT INTO operator_external_task_bindings
|
|
1195
|
-
(task_id, connector, source_type, external_source_id, last_observation_seq,
|
|
1196
|
-
created_by_attempt_id, created_by_run_id, created_by_owner_scope,
|
|
1197
|
-
created_by_envelope_hash, active, created_at, updated_at)
|
|
1198
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?)`)
|
|
1199
|
-
.run(candidate.taskId, candidate.connector, candidate.sourceType, candidate.externalSourceId, candidate.operatorObservationSeq, receiptOrigin.attemptId, receiptOrigin.runId, receiptOrigin.ownerScope, receiptOrigin.envelopeHash, now, now);
|
|
1200
|
-
bindingId = Number(inserted.lastInsertRowid);
|
|
1201
|
-
}
|
|
1202
|
-
this.insertBindingReceipt(candidate, receiptOrigin, input, outcome, bindingId, origin);
|
|
1203
|
-
receipt = {
|
|
1204
|
-
kind: 'binding',
|
|
1205
|
-
candidateId: candidate.candidateId,
|
|
1206
|
-
workOrderAttemptId: receiptOrigin.attemptId,
|
|
1207
|
-
originRunId: receiptOrigin.runId,
|
|
1208
|
-
originOwnerScope: receiptOrigin.ownerScope,
|
|
1209
|
-
taskId: candidate.taskId,
|
|
1210
|
-
outcome,
|
|
1211
|
-
reason: input.reason,
|
|
1212
|
-
...(bindingId === undefined ? {} : { bindingId }),
|
|
1213
|
-
};
|
|
1214
|
-
}
|
|
1215
|
-
this.db.exec('COMMIT');
|
|
1216
|
-
}
|
|
1217
|
-
catch (error) {
|
|
1218
|
-
this.db.exec('ROLLBACK');
|
|
1219
|
-
throw error;
|
|
1220
|
-
}
|
|
1221
|
-
return receipt;
|
|
1222
|
-
}
|
|
1223
|
-
applyExternalLifecycleDecision(source, input, origin) {
|
|
1224
|
-
(0, external_lifecycle_candidates_js_1.validateExternalLifecycleDecision)('lifecycle', input);
|
|
1225
|
-
const authority = this.resolveExternalCandidateAuthority(source, 'lifecycle', origin);
|
|
1226
|
-
const receiptOrigin = authority.receiptOrigin;
|
|
1227
|
-
let receipt = null;
|
|
1228
|
-
const previousGeneration = this.onOwnerTaskChangeCommitted ? this.readGeneration() : null;
|
|
1229
|
-
this.db.exec('BEGIN IMMEDIATE');
|
|
1230
|
-
try {
|
|
1231
|
-
const { candidate, snapshotAt } = authority.load(input.candidate_id);
|
|
1232
|
-
if (candidate.kind !== 'lifecycle') {
|
|
1233
|
-
throw new Error(`external lifecycle candidate ${input.candidate_id} has the wrong kind`);
|
|
1234
|
-
}
|
|
1235
|
-
if (input.expected_revision !== candidate.taskRevision) {
|
|
1236
|
-
throw new Error(`external lifecycle candidate ${candidate.candidateId} revision does not match its host snapshot`);
|
|
1237
|
-
}
|
|
1238
|
-
if (origin.causeEventIds?.length !== 1 || origin.causeEventIds[0] !== candidate.eventId) {
|
|
1239
|
-
throw new Error(`external lifecycle candidate ${candidate.candidateId} requires its exact host origin event`);
|
|
1240
|
-
}
|
|
1241
|
-
const existing = this.getExternalCandidateReceipt(candidate.candidateId);
|
|
1242
|
-
if (existing) {
|
|
1243
|
-
if (existing.kind !== 'lifecycle') {
|
|
1244
|
-
throw new Error(`external lifecycle candidate ${candidate.candidateId} already has another receipt kind`);
|
|
1245
|
-
}
|
|
1246
|
-
this.assertLifecycleReplayMatches(existing, candidate, input);
|
|
1247
|
-
receipt = existing;
|
|
1248
|
-
}
|
|
1249
|
-
else {
|
|
1250
|
-
const task = this.getRowById(candidate.taskId);
|
|
1251
|
-
const binding = this.getExternalBinding(candidate.taskId);
|
|
1252
|
-
if (!task || task.kind !== 'owner' || !binding) {
|
|
1253
|
-
throw new Error(`external lifecycle candidate ${candidate.candidateId} has no active binding`);
|
|
1254
|
-
}
|
|
1255
|
-
// A binding that came into being after this candidate's snapshot cannot
|
|
1256
|
-
// be the binding the snapshot described. Board attempts keep the exact
|
|
1257
|
-
// legacy ordering rule; when either side lacks an attempt id (owner-run
|
|
1258
|
-
// decisions), the ledger clock decides.
|
|
1259
|
-
const bindingPostdatesSnapshot = authority.source.kind === 'board' && binding.createdByAttemptId !== null
|
|
1260
|
-
? binding.createdByAttemptId >= authority.source.attemptId
|
|
1261
|
-
: binding.createdAt > snapshotAt;
|
|
1262
|
-
const superseded = binding.id !== candidate.bindingId ||
|
|
1263
|
-
binding.revision !== candidate.bindingRevision ||
|
|
1264
|
-
binding.connector !== candidate.connector ||
|
|
1265
|
-
binding.sourceType !== candidate.sourceType ||
|
|
1266
|
-
binding.externalSourceId !== candidate.externalSourceId ||
|
|
1267
|
-
bindingPostdatesSnapshot ||
|
|
1268
|
-
task.revision !== candidate.taskRevision ||
|
|
1269
|
-
candidate.operatorObservationSeq <= binding.lastObservationSeq;
|
|
1270
|
-
const taskRevisionBefore = task.revision;
|
|
1271
|
-
let taskRevisionAfter = task.revision;
|
|
1272
|
-
let outcome;
|
|
1273
|
-
if (superseded) {
|
|
1274
|
-
outcome = 'superseded';
|
|
1275
|
-
}
|
|
1276
|
-
else if (input.decision === 'apply') {
|
|
1277
|
-
const terminalEvidence = candidate.proposedStatus === 'done' || candidate.proposedStatus === 'cancelled';
|
|
1278
|
-
const transitioned = this.transitionTaskInTransaction(candidate.taskId, {
|
|
1279
|
-
status: candidate.proposedStatus,
|
|
1280
|
-
latest_event: candidate.evidenceSummary,
|
|
1281
|
-
// This candidate is a host-issued current authoritative lifecycle
|
|
1282
|
-
// observation. Preserve its semantic completion, or clear an earlier
|
|
1283
|
-
// terminal classification when later feedback reopens the same row.
|
|
1284
|
-
resolution_kind: terminalEvidence ? 'completed_evidence' : null,
|
|
1285
|
-
}, origin, true);
|
|
1286
|
-
taskRevisionAfter = transitioned.revision;
|
|
1287
|
-
outcome = 'applied';
|
|
1288
|
-
}
|
|
1289
|
-
else {
|
|
1290
|
-
outcome = 'retained';
|
|
1291
|
-
}
|
|
1292
|
-
if (outcome !== 'superseded') {
|
|
1293
|
-
const updated = this.db
|
|
1294
|
-
.prepare(`UPDATE operator_external_task_bindings
|
|
1295
|
-
SET last_observation_seq = ?, revision = revision + 1, updated_at = ?
|
|
1296
|
-
WHERE id = ? AND revision = ? AND last_observation_seq < ?`)
|
|
1297
|
-
.run(candidate.operatorObservationSeq, this.now(), candidate.bindingId, candidate.bindingRevision, candidate.operatorObservationSeq);
|
|
1298
|
-
if (updated.changes !== 1) {
|
|
1299
|
-
throw new Error(`external lifecycle candidate ${candidate.candidateId} binding watermark changed`);
|
|
1300
|
-
}
|
|
1301
|
-
}
|
|
1302
|
-
this.insertLifecycleReceipt(candidate, receiptOrigin, input, outcome, taskRevisionBefore, taskRevisionAfter, origin);
|
|
1303
|
-
receipt = {
|
|
1304
|
-
kind: 'lifecycle',
|
|
1305
|
-
candidateId: candidate.candidateId,
|
|
1306
|
-
workOrderAttemptId: receiptOrigin.attemptId,
|
|
1307
|
-
originRunId: receiptOrigin.runId,
|
|
1308
|
-
originOwnerScope: receiptOrigin.ownerScope,
|
|
1309
|
-
taskId: candidate.taskId,
|
|
1310
|
-
outcome,
|
|
1311
|
-
reason: input.reason,
|
|
1312
|
-
taskRevisionBefore,
|
|
1313
|
-
taskRevisionAfter,
|
|
1314
|
-
};
|
|
1315
|
-
}
|
|
1316
|
-
this.db.exec('COMMIT');
|
|
1317
|
-
}
|
|
1318
|
-
catch (error) {
|
|
1319
|
-
this.db.exec('ROLLBACK');
|
|
1320
|
-
throw error;
|
|
1321
|
-
}
|
|
1322
|
-
this.notifyOwnerTaskCommit(previousGeneration);
|
|
1323
|
-
return receipt;
|
|
1324
|
-
}
|
|
1325
|
-
getExternalCandidateReceipt(candidateId) {
|
|
1326
|
-
const binding = this.db
|
|
1327
|
-
.prepare(`SELECT candidate_id, workorder_attempt_id, origin_run_id, origin_owner_scope, task_id,
|
|
1328
|
-
outcome, reason, binding_id
|
|
1329
|
-
FROM operator_external_binding_receipts WHERE candidate_id = ?`)
|
|
1330
|
-
.get(candidateId);
|
|
1331
|
-
const lifecycle = this.db
|
|
1332
|
-
.prepare(`SELECT candidate_id, workorder_attempt_id, origin_run_id, origin_owner_scope, task_id,
|
|
1333
|
-
outcome, reason, task_revision_before, task_revision_after
|
|
1334
|
-
FROM operator_external_lifecycle_receipts WHERE candidate_id = ?`)
|
|
1335
|
-
.get(candidateId);
|
|
1336
|
-
if (binding && lifecycle) {
|
|
1337
|
-
throw new Error(`external lifecycle candidate ${candidateId} has duplicate global receipts`);
|
|
1338
|
-
}
|
|
1339
|
-
const receiptKind = binding ? 'binding' : lifecycle ? 'lifecycle' : null;
|
|
1340
|
-
const identity = this.db
|
|
1341
|
-
.prepare(`SELECT receipt_kind FROM operator_external_receipt_identities WHERE candidate_id = ?`)
|
|
1342
|
-
.get(candidateId);
|
|
1343
|
-
if (receiptKind === null) {
|
|
1344
|
-
if (identity) {
|
|
1345
|
-
throw new Error(`external lifecycle candidate ${candidateId} has an orphan receipt identity`);
|
|
1346
|
-
}
|
|
1347
|
-
return null;
|
|
1348
|
-
}
|
|
1349
|
-
if (!identity || identity.receipt_kind !== receiptKind) {
|
|
1350
|
-
throw new Error(`external lifecycle candidate ${candidateId} has inconsistent global receipt identity`);
|
|
1351
|
-
}
|
|
1352
|
-
if (binding) {
|
|
1353
|
-
return {
|
|
1354
|
-
kind: 'binding',
|
|
1355
|
-
candidateId: binding.candidate_id,
|
|
1356
|
-
workOrderAttemptId: binding.workorder_attempt_id,
|
|
1357
|
-
originRunId: binding.origin_run_id,
|
|
1358
|
-
originOwnerScope: binding.origin_owner_scope,
|
|
1359
|
-
taskId: binding.task_id,
|
|
1360
|
-
outcome: binding.outcome,
|
|
1361
|
-
reason: binding.reason,
|
|
1362
|
-
...(binding.binding_id === null ? {} : { bindingId: binding.binding_id }),
|
|
1363
|
-
};
|
|
1364
|
-
}
|
|
1365
|
-
return lifecycle
|
|
1366
|
-
? {
|
|
1367
|
-
kind: 'lifecycle',
|
|
1368
|
-
candidateId: lifecycle.candidate_id,
|
|
1369
|
-
workOrderAttemptId: lifecycle.workorder_attempt_id,
|
|
1370
|
-
originRunId: lifecycle.origin_run_id,
|
|
1371
|
-
originOwnerScope: lifecycle.origin_owner_scope,
|
|
1372
|
-
taskId: lifecycle.task_id,
|
|
1373
|
-
outcome: lifecycle.outcome,
|
|
1374
|
-
reason: lifecycle.reason,
|
|
1375
|
-
taskRevisionBefore: lifecycle.task_revision_before,
|
|
1376
|
-
taskRevisionAfter: lifecycle.task_revision_after,
|
|
1377
|
-
}
|
|
1378
|
-
: null;
|
|
1379
|
-
}
|
|
1380
|
-
externalBindingFromRow(row) {
|
|
1381
|
-
return {
|
|
1382
|
-
id: row.id,
|
|
1383
|
-
revision: row.revision,
|
|
1384
|
-
taskId: row.task_id,
|
|
1385
|
-
connector: row.connector,
|
|
1386
|
-
sourceType: row.source_type,
|
|
1387
|
-
externalSourceId: row.external_source_id,
|
|
1388
|
-
lastObservationSeq: row.last_observation_seq,
|
|
1389
|
-
createdByAttemptId: row.created_by_attempt_id,
|
|
1390
|
-
createdByRunId: row.created_by_run_id,
|
|
1391
|
-
createdByOwnerScope: row.created_by_owner_scope,
|
|
1392
|
-
createdAt: row.created_at,
|
|
1393
|
-
};
|
|
1394
|
-
}
|
|
1395
|
-
assertBindingReplayMatches(receipt, candidate, input) {
|
|
1396
|
-
const stored = this.db
|
|
1397
|
-
.prepare(`SELECT decision FROM operator_external_binding_receipts WHERE candidate_id = ?`)
|
|
1398
|
-
.get(candidate.candidateId);
|
|
1399
|
-
if (!stored ||
|
|
1400
|
-
stored.decision !== input.decision ||
|
|
1401
|
-
receipt.taskId !== candidate.taskId ||
|
|
1402
|
-
receipt.reason !== input.reason) {
|
|
1403
|
-
throw new Error(`external binding receipt ${candidate.candidateId} conflicts with replay decision`);
|
|
1404
|
-
}
|
|
1405
|
-
}
|
|
1406
|
-
insertBindingReceipt(candidate, receiptOrigin, input, outcome, bindingId, origin) {
|
|
1407
|
-
this.db
|
|
1408
|
-
.prepare(`INSERT INTO operator_external_binding_receipts
|
|
1409
|
-
(candidate_id, decision, workorder_attempt_id, task_id, event_id, connector, source_type,
|
|
1410
|
-
external_source_id, channel_partition, content_sha256, source_timestamp_ms,
|
|
1411
|
-
operator_ingest_seq, operator_observation_seq, task_revision, outcome, reason, binding_id,
|
|
1412
|
-
origin_run_id, origin_cause_event_ids, origin_owner_scope, origin_envelope_hash,
|
|
1413
|
-
created_at)
|
|
1414
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
1415
|
-
.run(candidate.candidateId, input.decision, receiptOrigin.attemptId, candidate.taskId, candidate.eventId, candidate.connector, candidate.sourceType, candidate.externalSourceId, candidate.channelPartition, candidate.contentSha256, candidate.sourceTimestampMs, candidate.operatorIngestSeq, candidate.operatorObservationSeq, candidate.taskRevision, outcome, input.reason, bindingId ?? null, receiptOrigin.runId, JSON.stringify(origin.causeEventIds ?? []), receiptOrigin.ownerScope, receiptOrigin.envelopeHash, this.now());
|
|
1416
|
-
}
|
|
1417
|
-
assertLifecycleReplayMatches(receipt, candidate, input) {
|
|
1418
|
-
const stored = this.db
|
|
1419
|
-
.prepare(`SELECT decision FROM operator_external_lifecycle_receipts WHERE candidate_id = ?`)
|
|
1420
|
-
.get(candidate.candidateId);
|
|
1421
|
-
if (!stored ||
|
|
1422
|
-
stored.decision !== input.decision ||
|
|
1423
|
-
receipt.taskId !== candidate.taskId ||
|
|
1424
|
-
receipt.reason !== input.reason) {
|
|
1425
|
-
throw new Error(`external lifecycle receipt ${candidate.candidateId} conflicts with replay decision`);
|
|
1426
|
-
}
|
|
1427
|
-
}
|
|
1428
|
-
insertLifecycleReceipt(candidate, receiptOrigin, input, outcome, taskRevisionBefore, taskRevisionAfter, origin) {
|
|
1429
|
-
this.db
|
|
1430
|
-
.prepare(`INSERT INTO operator_external_lifecycle_receipts
|
|
1431
|
-
(candidate_id, decision, workorder_attempt_id, task_id, event_id, connector, source_type,
|
|
1432
|
-
external_source_id, channel_partition, content_sha256, source_timestamp_ms,
|
|
1433
|
-
operator_ingest_seq, operator_observation_seq, binding_id, binding_revision,
|
|
1434
|
-
task_revision_before, task_revision_after, outcome, reason, origin_run_id,
|
|
1435
|
-
origin_cause_event_ids, origin_owner_scope, origin_envelope_hash, created_at)
|
|
1436
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
1437
|
-
.run(candidate.candidateId, input.decision, receiptOrigin.attemptId, candidate.taskId, candidate.eventId, candidate.connector, candidate.sourceType, candidate.externalSourceId, candidate.channelPartition, candidate.contentSha256, candidate.sourceTimestampMs, candidate.operatorIngestSeq, candidate.operatorObservationSeq, candidate.bindingId, candidate.bindingRevision, taskRevisionBefore, taskRevisionAfter, outcome, input.reason, receiptOrigin.runId, JSON.stringify(origin.causeEventIds ?? []), receiptOrigin.ownerScope, receiptOrigin.envelopeHash, this.now());
|
|
1438
|
-
}
|
|
1439
|
-
/**
|
|
1440
|
-
* Create a task. Idempotent under at-least-once delivery: a duplicate
|
|
1441
|
-
* (source_channel, source_event_id) UPSERTS - the existing row gets the new
|
|
1442
|
-
* latest_event (and title stays) instead of a near-duplicate row appearing.
|
|
1443
|
-
*/
|
|
1444
|
-
create(input, origin = {}) {
|
|
1445
|
-
if (!input.title || input.title.trim() === '') {
|
|
1446
|
-
throw new Error('task title must be a non-empty string');
|
|
1447
|
-
}
|
|
1448
|
-
if (input.status !== undefined)
|
|
1449
|
-
assertEnum(input.status, exports.TASK_STATUSES, 'status');
|
|
1450
|
-
if (input.status === 'failed') {
|
|
1451
|
-
throw new Error(`task_create: 'failed' is a system-only status`);
|
|
1452
|
-
}
|
|
1453
|
-
if (input.status === 'review') {
|
|
1454
|
-
// review carries a host-verified submission anchor (review_started_at,
|
|
1455
|
-
// review_anchor_event_id) that only the task_update path can supply. A row
|
|
1456
|
-
// created directly in review would sit in the unanchored state reserved for
|
|
1457
|
-
// legacy rows and could never enter the Temporal review lifecycle.
|
|
1458
|
-
throw new Error(`task_create: 'review' requires verified submission evidence; create the task, then task_update it to review`);
|
|
1459
|
-
}
|
|
1460
|
-
// Namespace reservation (review m3): the occurrence keys are deterministic
|
|
1461
|
-
// (epoch slots, readable from OSS source) - an agent-created owner row on
|
|
1462
|
-
// a 'workorder:' (channel,event) pair would collide with the system
|
|
1463
|
-
// INSERT via the shared unique index and DoS that schedule slot.
|
|
1464
|
-
if (input.source_channel?.startsWith(exports.WORKORDER_CHANNEL_PREFIX)) {
|
|
1465
|
-
throw new Error(`task_create: source_channel namespace '${exports.WORKORDER_CHANNEL_PREFIX}*' is reserved for system workorders`);
|
|
1466
|
-
}
|
|
1467
|
-
if (input.priority !== undefined)
|
|
1468
|
-
assertEnum(input.priority, exports.TASK_PRIORITIES, 'priority');
|
|
1469
|
-
if (input.deadline !== undefined)
|
|
1470
|
-
assertIsoDate(input.deadline, 'deadline');
|
|
1471
|
-
const exactDue = input.due_at !== undefined ? (0, task_temporal_js_1.parseExactDueAt)(input.due_at) : null;
|
|
1472
|
-
if (exactDue && input.deadline !== undefined && input.deadline !== exactDue.deadline) {
|
|
1473
|
-
throw new Error('task_create: due_at and deadline conflict');
|
|
1474
|
-
}
|
|
1475
|
-
const normalizedDeadline = exactDue?.deadline ?? input.deadline ?? null;
|
|
1476
|
-
const initialTemporalEpoch = normalizedDeadline !== null ? 1 : 0;
|
|
1477
|
-
// Optional HERE on purpose: the PUBLIC task_create boundary requires it, while
|
|
1478
|
-
// internal/system seeding and legacy callers stay backward compatible.
|
|
1479
|
-
const normalizedCompletionCriteria = normalizeCompletionCriteria(input.completion_criteria, 'task_create');
|
|
1480
|
-
const now = this.now();
|
|
1481
|
-
if (input.source_channel && input.source_event_id) {
|
|
1482
|
-
// kind='owner' probe: an agent-supplied (channel, event) pair can never
|
|
1483
|
-
// reach a system workorder row (Stage-2 tamper guard).
|
|
1484
|
-
const existing = this.db
|
|
1485
|
-
.prepare(`SELECT * FROM operator_tasks
|
|
1486
|
-
WHERE source_channel = ? AND source_event_id = ? AND kind = 'owner'`)
|
|
1487
|
-
.get(input.source_channel, input.source_event_id);
|
|
1488
|
-
if (existing) {
|
|
1489
|
-
// Duplicate source delivery uses the same mutation boundary, including
|
|
1490
|
-
// exact-time normalization and no-op detection. The original title stays
|
|
1491
|
-
// stable across retries for backward compatibility.
|
|
1492
|
-
return this.update(existing.id, {
|
|
1493
|
-
...(input.status !== undefined ? { status: input.status } : {}),
|
|
1494
|
-
...(input.priority !== undefined ? { priority: input.priority } : {}),
|
|
1495
|
-
...(input.assignee !== undefined ? { assignee: input.assignee } : {}),
|
|
1496
|
-
...(input.deadline !== undefined ? { deadline: input.deadline } : {}),
|
|
1497
|
-
...(input.due_at !== undefined ? { due_at: input.due_at } : {}),
|
|
1498
|
-
...(input.confirmed !== undefined ? { confirmed: input.confirmed } : {}),
|
|
1499
|
-
...(input.latest_event !== undefined ? { latest_event: input.latest_event } : {}),
|
|
1500
|
-
...(normalizedCompletionCriteria !== null
|
|
1501
|
-
? { completion_criteria: normalizedCompletionCriteria }
|
|
1502
|
-
: {}),
|
|
1503
|
-
...(input.expected_revision !== undefined
|
|
1504
|
-
? { expected_revision: input.expected_revision }
|
|
1505
|
-
: {}),
|
|
1506
|
-
},
|
|
1507
|
-
// A duplicate delivery of the same event. The HOST batch still wins
|
|
1508
|
-
// (review: the agent-supplied source_event_id was overriding it here,
|
|
1509
|
-
// letting a model forge an attributed cause on every judgment-borne
|
|
1510
|
-
// create); the keyed event is only the fallback when the host
|
|
1511
|
-
// carried nothing.
|
|
1512
|
-
{
|
|
1513
|
-
...origin,
|
|
1514
|
-
taskCreateUpsert: true,
|
|
1515
|
-
causeEventIds: origin.causeEventIds?.length
|
|
1516
|
-
? origin.causeEventIds
|
|
1517
|
-
: input.source_event_id
|
|
1518
|
-
? [input.source_event_id]
|
|
1519
|
-
: origin.causeEventIds,
|
|
1520
|
-
});
|
|
1521
|
-
}
|
|
1522
|
-
}
|
|
1523
|
-
let createdId = 0;
|
|
1524
|
-
const previousGeneration = this.onOwnerTaskChangeCommitted ? this.readGeneration() : null;
|
|
1525
|
-
const write = this.db.transaction(() => {
|
|
1526
|
-
const result = this.db
|
|
1527
|
-
.prepare(`INSERT INTO operator_tasks
|
|
1528
|
-
(title, status, priority, assignee, deadline, due_at, deadline_offset_minutes,
|
|
1529
|
-
revision, temporal_epoch, source_channel, source_event_id, latest_event,
|
|
1530
|
-
auto_created, confirmed, completion_criteria, created_at, updated_at)
|
|
1531
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
1532
|
-
.run(input.title.trim(), input.status ?? 'pending', input.priority ?? 'normal', input.assignee ?? null, normalizedDeadline, exactDue?.dueAt ?? null, exactDue?.offsetMinutes ?? null, initialTemporalEpoch, input.source_channel ?? null, input.source_event_id ?? null, input.latest_event ?? null, 1, input.confirmed ? 1 : 0, normalizedCompletionCriteria, now, now);
|
|
1533
|
-
createdId = Number(result.lastInsertRowid);
|
|
1534
|
-
this.recordTaskChange({
|
|
1535
|
-
kind: 'task_create',
|
|
1536
|
-
taskId: createdId,
|
|
1537
|
-
// The HOST batch wins. `source_event_id` arrives on agent-authored task_create
|
|
1538
|
-
// input, so letting it take precedence made the attribution the agent's to choose:
|
|
1539
|
-
// any 1-200 character string passes the ledger's shape check, and the change was
|
|
1540
|
-
// then stamped `attributed` on a cause nobody could resolve. The run id is already
|
|
1541
|
-
// protected for exactly this reason; the cause was not. Found in review.
|
|
1542
|
-
//
|
|
1543
|
-
// The agent's field is still USED when the host handed the run no batch - a named
|
|
1544
|
-
// source event is better evidence than none, and an unbounded run has nothing else.
|
|
1545
|
-
// It is weaker evidence, not worthless.
|
|
1546
|
-
//
|
|
1547
|
-
// A plain spread would be wrong in the other direction: an absent caller field must
|
|
1548
|
-
// not erase what the host knows.
|
|
1549
|
-
origin: {
|
|
1550
|
-
...origin,
|
|
1551
|
-
causeEventIds: origin.causeEventIds && origin.causeEventIds.length > 0
|
|
1552
|
-
? origin.causeEventIds
|
|
1553
|
-
: input.source_event_id
|
|
1554
|
-
? [input.source_event_id]
|
|
1555
|
-
: origin.causeEventIds,
|
|
1556
|
-
},
|
|
1557
|
-
channel: input.source_channel ?? null,
|
|
1558
|
-
// Everything the INSERT wrote. A hash over two of fifteen columns cannot tell two
|
|
1559
|
-
// materially different writes apart, which is most of what a receipt is for.
|
|
1560
|
-
payload: {
|
|
1561
|
-
title: input.title.trim(),
|
|
1562
|
-
status: input.status ?? 'pending',
|
|
1563
|
-
priority: input.priority ?? 'normal',
|
|
1564
|
-
assignee: input.assignee ?? null,
|
|
1565
|
-
deadline: normalizedDeadline,
|
|
1566
|
-
due_at: exactDue?.dueAt ?? null,
|
|
1567
|
-
deadline_offset_minutes: exactDue?.offsetMinutes ?? null,
|
|
1568
|
-
temporal_epoch: initialTemporalEpoch,
|
|
1569
|
-
source_channel: input.source_channel ?? null,
|
|
1570
|
-
source_event_id: input.source_event_id ?? null,
|
|
1571
|
-
latest_event: input.latest_event ?? null,
|
|
1572
|
-
confirmed: input.confirmed ? 1 : 0,
|
|
1573
|
-
completion_criteria: normalizedCompletionCriteria,
|
|
1574
|
-
},
|
|
1575
|
-
atMs: now,
|
|
1576
|
-
});
|
|
1577
|
-
}, 'immediate');
|
|
1578
|
-
write();
|
|
1579
|
-
this.notifyOwnerTaskCommit(previousGeneration);
|
|
1580
|
-
const created = this.getById(createdId);
|
|
1581
|
-
if (!created)
|
|
1582
|
-
throw new Error('task_create: inserted row could not be read back');
|
|
1583
|
-
return created;
|
|
1584
|
-
}
|
|
1585
|
-
/**
|
|
1586
|
-
* Write the effect row for a task change.
|
|
1587
|
-
*
|
|
1588
|
-
* Called from inside the same transaction as the write it describes. The cause is the
|
|
1589
|
-
* event the caller already recorded on the row - the tool has always accepted it, and
|
|
1590
|
-
* 178 of the 179 owner tasks that carry one resolve to a real indexed event, so this is
|
|
1591
|
-
* attribution that already exists rather than a new burden on the agent.
|
|
1592
|
-
*
|
|
1593
|
-
* When there is no cause the change is still recorded, marked unattributed. That is the
|
|
1594
|
-
* honest reading of `task_update`, which has never had a cause field at all: the system
|
|
1595
|
-
* has been changing owner work items without recording why, and the ledger's job is to
|
|
1596
|
-
* show how often, not to pretend otherwise or to start refusing writes.
|
|
1597
|
-
*/
|
|
1598
|
-
recordTaskChange(input) {
|
|
1599
|
-
const common = {
|
|
1600
|
-
runId: input.origin.runId ?? null,
|
|
1601
|
-
channelId: input.channel,
|
|
1602
|
-
kind: input.kind,
|
|
1603
|
-
targetType: 'task',
|
|
1604
|
-
targetId: String(input.taskId),
|
|
1605
|
-
payload: input.payload,
|
|
1606
|
-
atMs: input.atMs,
|
|
1607
|
-
};
|
|
1608
|
-
// A cause the ledger cannot use makes the change unattributed - it never makes the
|
|
1609
|
-
// change fail. Some live rows carry 400-character legacy source identifiers, and an
|
|
1610
|
-
// operator that cannot close a work item because its upstream id is malformed is a
|
|
1611
|
-
// worse system than one that admits it does not know why the item changed.
|
|
1612
|
-
const causes = (input.origin.causeEventIds ?? []).filter(effects_js_1.isUsableCause);
|
|
1613
|
-
if (causes.length > 0) {
|
|
1614
|
-
(0, effects_js_1.recordEffect)(this.db, { ...common, sourceEventIds: causes });
|
|
1615
|
-
return;
|
|
1616
|
-
}
|
|
1617
|
-
(0, effects_js_1.recordUnattributedChange)(this.db, common, input.origin.causeKind ?? 'owner_message');
|
|
1618
|
-
}
|
|
1619
|
-
update(id, patch, origin = {}) {
|
|
1620
|
-
this.validateTaskUpdatePatch(patch);
|
|
1621
|
-
const previousGeneration = this.onOwnerTaskChangeCommitted ? this.readGeneration() : null;
|
|
1622
|
-
const updated = this.db.transaction(() => this.transitionTaskInTransaction(id, patch, origin), 'immediate')();
|
|
1623
|
-
this.notifyOwnerTaskCommit(previousGeneration);
|
|
1624
|
-
return updated;
|
|
1625
|
-
}
|
|
1626
|
-
/**
|
|
1627
|
-
* Recorrect an existing owner row with a NAMED disposition.
|
|
1628
|
-
*
|
|
1629
|
-
* Records and tasks are separate. Without a semantic terminal reason the agent
|
|
1630
|
-
* could not distinguish completed work from an item that was never a task, so
|
|
1631
|
-
* connector observations, principles, lessons and open questions accumulated as
|
|
1632
|
-
* owner rows and could only be "closed" as if they had been done.
|
|
1633
|
-
*
|
|
1634
|
-
* Every path requires the exact current revision, a non-empty bounded reason
|
|
1635
|
-
* preserved as `latest_event`, an OWNER row, and the same workorder-attempt
|
|
1636
|
-
* currency and causal-provenance checks as task_update (TG-03/TG-04/TG-06) -
|
|
1637
|
-
* a terminal attempt of any kind cannot mutate a row, and an owner-event
|
|
1638
|
-
* decision cannot rest on a fabricated cause. Scheduler hint pages and the
|
|
1639
|
-
* causal batch's channel select what the host READS; they do not narrow the
|
|
1640
|
-
* authenticated owner's decisions. The whole transition, its
|
|
1641
|
-
* `resolution_kind` and its effect receipt commit as ONE transaction.
|
|
1642
|
-
*/
|
|
1643
|
-
reclassify(id, input, origin = {}) {
|
|
1644
|
-
const disposition = input?.disposition;
|
|
1645
|
-
if (!exports.TASK_RECLASSIFY_DISPOSITIONS.includes(disposition)) {
|
|
1646
|
-
throw new Error(`task_reclassify: disposition must be one of ${exports.TASK_RECLASSIFY_DISPOSITIONS.join('|')}, got: ${String(disposition)}`);
|
|
1647
|
-
}
|
|
1648
|
-
const reason = typeof input.reason === 'string' ? input.reason.trim() : '';
|
|
1649
|
-
if (reason.length === 0) {
|
|
1650
|
-
throw new Error('task_reclassify: reason must be a non-empty string');
|
|
1651
|
-
}
|
|
1652
|
-
if (reason.length > exports.TASK_RECLASSIFY_REASON_MAX_LENGTH) {
|
|
1653
|
-
throw new Error(`task_reclassify: reason must be at most ${exports.TASK_RECLASSIFY_REASON_MAX_LENGTH} characters`);
|
|
1654
|
-
}
|
|
1655
|
-
if (!Number.isSafeInteger(input.expected_revision) || input.expected_revision < 0) {
|
|
1656
|
-
throw new Error('task_reclassify: expected_revision must be a non-negative integer');
|
|
1657
|
-
}
|
|
1658
|
-
const previousGeneration = this.onOwnerTaskChangeCommitted ? this.readGeneration() : null;
|
|
1659
|
-
let record;
|
|
1660
|
-
this.db.exec('BEGIN IMMEDIATE');
|
|
1661
|
-
try {
|
|
1662
|
-
const existing = this.db.prepare('SELECT * FROM operator_tasks WHERE id = ?').get(id);
|
|
1663
|
-
if (!existing)
|
|
1664
|
-
throw new Error(`task_reclassify: no task with id ${id}`);
|
|
1665
|
-
if (existing.kind === 'system') {
|
|
1666
|
-
throw new Error(`task_reclassify: task ${id} is a system workorder row (owner rows only)`);
|
|
1667
|
-
}
|
|
1668
|
-
if (input.expected_revision !== existing.revision) {
|
|
1669
|
-
throw new Error(`task_reclassify: expected revision ${input.expected_revision}, current ${existing.revision}`);
|
|
1670
|
-
}
|
|
1671
|
-
this.assertTaskReclassificationAuthorized(origin);
|
|
1672
|
-
const patch = this.buildReclassifyPatch(existing, disposition, reason);
|
|
1673
|
-
// Same primitive as task_update: candidate guard, board-attempt liveness,
|
|
1674
|
-
// revision-checked UPDATE, effect receipt, temporal generation supersession.
|
|
1675
|
-
record = this.transitionTaskInTransaction(id, patch, origin);
|
|
1676
|
-
this.db.exec('COMMIT');
|
|
1677
|
-
}
|
|
1678
|
-
catch (error) {
|
|
1679
|
-
this.db.exec('ROLLBACK');
|
|
1680
|
-
throw error;
|
|
1681
|
-
}
|
|
1682
|
-
this.notifyOwnerTaskCommit(previousGeneration);
|
|
1683
|
-
return record;
|
|
1684
|
-
}
|
|
1685
|
-
assertTaskReclassificationAuthorized(origin) {
|
|
1686
|
-
if (origin.workOrderAttemptId !== undefined) {
|
|
1687
|
-
// A carried attempt must be CURRENT. Its kind is scheduling metadata: a
|
|
1688
|
-
// wiki or temporal run that discovers a record-shaped owner row may
|
|
1689
|
-
// reclassify it under the same CAS rules as a Board run. Its candidate
|
|
1690
|
-
// page is a bounded-read hint, so a row outside the page is not refused.
|
|
1691
|
-
this.assertWorkOrderAttemptActive('task_reclassify', origin.workOrderAttemptId);
|
|
1692
|
-
}
|
|
1693
|
-
if (origin.reclassificationCauseBound) {
|
|
1694
|
-
const eventIds = (origin.causeEventIds ?? []).filter(effects_js_1.isUsableCause);
|
|
1695
|
-
if (eventIds.length === 0) {
|
|
1696
|
-
throw new Error('task_reclassify: owner-event reclassification requires causal events');
|
|
1697
|
-
}
|
|
1698
|
-
// Provenance: every cause must be an event a durable inbox batch actually
|
|
1699
|
-
// carried. The batch's channel is not compared with the task's source -
|
|
1700
|
-
// cross-source relevance is MAMA's judgment under the owner's grant.
|
|
1701
|
-
const unknown = this.findCausalEventsWithoutInboxBatch(eventIds);
|
|
1702
|
-
if (unknown.length > 0) {
|
|
1703
|
-
throw new Error(`task_reclassify: causal event(s) ${unknown.join(', ')} were not carried by any owner-event inbox batch`);
|
|
1704
|
-
}
|
|
1705
|
-
}
|
|
1706
|
-
}
|
|
1707
|
-
/**
|
|
1708
|
-
* A retained attempt context outlives its workorder: a late direct or nested
|
|
1709
|
-
* tool call can still carry it after the row reached a terminal status. Any
|
|
1710
|
-
* lifecycle judgment made under a carried attempt requires that attempt to be
|
|
1711
|
-
* the claimed, in-progress one, whatever its work kind.
|
|
1712
|
-
*/
|
|
1713
|
-
assertWorkOrderAttemptActive(tool, attemptId) {
|
|
1714
|
-
const attempt = this.getWorkOrderById(attemptId);
|
|
1715
|
-
if (!attempt) {
|
|
1716
|
-
throw new Error(`${tool}: workorder ${String(attemptId)} is no longer active (missing)`);
|
|
1717
|
-
}
|
|
1718
|
-
if (attempt.status !== 'in_progress') {
|
|
1719
|
-
throw new Error(`${tool}: ${attempt.workKind} workorder ${String(attemptId)} is no longer active (${attempt.status})`);
|
|
1720
|
-
}
|
|
1721
|
-
return attempt;
|
|
1722
|
-
}
|
|
1723
|
-
findCausalEventsWithoutInboxBatch(eventIds) {
|
|
1724
|
-
const placeholders = eventIds.map(() => '?').join(',');
|
|
1725
|
-
const known = new Set(this.db
|
|
1726
|
-
.prepare(`SELECT DISTINCT event.value AS event_id
|
|
1727
|
-
FROM owner_event_inbox AS inbox, json_each(inbox.event_ids_json) AS event
|
|
1728
|
-
WHERE event.value IN (${placeholders})`)
|
|
1729
|
-
.all(...eventIds).map((row) => row.event_id));
|
|
1730
|
-
return eventIds.filter((eventId) => !known.has(eventId));
|
|
1731
|
-
}
|
|
1732
|
-
buildReclassifyPatch(existing, disposition, reason) {
|
|
1733
|
-
const base = {
|
|
1734
|
-
latest_event: reason,
|
|
1735
|
-
expected_revision: existing.revision,
|
|
1736
|
-
};
|
|
1737
|
-
if (disposition === 'reopen') {
|
|
1738
|
-
if (existing.status !== 'done' && existing.status !== 'cancelled') {
|
|
1739
|
-
throw new Error(`task_reclassify: reopen requires a terminal owner row, task is '${existing.status}'`);
|
|
1740
|
-
}
|
|
1741
|
-
// A reopened row must not inherit a deadline that already passed: it would
|
|
1742
|
-
// read as overdue the moment it reopens. A still-future deadline is kept.
|
|
1743
|
-
const stale = this.isDeadlineInThePast(existing);
|
|
1744
|
-
return {
|
|
1745
|
-
...base,
|
|
1746
|
-
status: 'pending',
|
|
1747
|
-
resolution_kind: null,
|
|
1748
|
-
...(stale ? { deadline: null, due_at: null } : {}),
|
|
1749
|
-
};
|
|
1750
|
-
}
|
|
1751
|
-
if (existing.status === 'done' || existing.status === 'cancelled') {
|
|
1752
|
-
throw new Error(`task_reclassify: ${disposition} requires an active owner row, task is '${existing.status}'`);
|
|
1753
|
-
}
|
|
1754
|
-
if (disposition === 'completed_evidence') {
|
|
1755
|
-
return { ...base, status: 'done', resolution_kind: 'completed_evidence' };
|
|
1756
|
-
}
|
|
1757
|
-
if (disposition === 'completed_no_issue') {
|
|
1758
|
-
// Owner policy: a PAST deadline plus a complete relevant-source check with no
|
|
1759
|
-
// issue is enough. Without a passed deadline there is nothing to conclude from.
|
|
1760
|
-
if (!this.isDeadlineInThePast(existing)) {
|
|
1761
|
-
throw new Error('task_reclassify: completed_no_issue requires a deadline or due_at already in the past');
|
|
1762
|
-
}
|
|
1763
|
-
return { ...base, status: 'done', resolution_kind: 'completed_no_issue' };
|
|
1764
|
-
}
|
|
1765
|
-
// non_task_record / non_task_memory: this was never a task. Cancelled, not done,
|
|
1766
|
-
// so accounting never reads it as finished work.
|
|
1767
|
-
return {
|
|
1768
|
-
...base,
|
|
1769
|
-
status: 'cancelled',
|
|
1770
|
-
completion_criteria: null,
|
|
1771
|
-
resolution_kind: disposition,
|
|
1772
|
-
};
|
|
1773
|
-
}
|
|
1774
|
-
/** True when the row carries a deadline/due_at that has already passed. */
|
|
1775
|
-
isDeadlineInThePast(existing) {
|
|
1776
|
-
const state = (0, task_temporal_js_1.deriveTemporalState)({
|
|
1777
|
-
// Terminal rows derive 'closed', which would hide a genuinely past deadline;
|
|
1778
|
-
// ask the temporal question about the CLOCK fields only.
|
|
1779
|
-
status: 'pending',
|
|
1780
|
-
dueAt: existing.due_at,
|
|
1781
|
-
deadlineIso: existing.deadline,
|
|
1782
|
-
deadlineOffsetMinutes: existing.deadline_offset_minutes,
|
|
1783
|
-
}, this.now(), this.timeZone);
|
|
1784
|
-
return state === 'exact_overdue' || state === 'date_overdue';
|
|
1785
|
-
}
|
|
1786
|
-
/**
|
|
1787
|
-
* The one mutation primitive for owner-task state. Callers own the outer
|
|
1788
|
-
* transaction so lifecycle receipts, effects, generation ownership, and the
|
|
1789
|
-
* row revision can commit or roll back as one unit.
|
|
1790
|
-
*/
|
|
1791
|
-
transitionTaskInTransaction(id, patch, origin, skipCandidateGuard = false) {
|
|
1792
|
-
this.validateTaskUpdatePatch(patch);
|
|
1793
|
-
if (!skipCandidateGuard) {
|
|
1794
|
-
this.assertCandidateTaskMutationAllowed(id, patch, origin);
|
|
1795
|
-
}
|
|
1796
|
-
const existing = this.db.prepare('SELECT * FROM operator_tasks WHERE id = ?').get(id);
|
|
1797
|
-
if (!existing)
|
|
1798
|
-
throw new Error(`task_update: no task with id ${id}`);
|
|
1799
|
-
if (existing.kind === 'system') {
|
|
1800
|
-
throw new Error(`task_update: task ${id} is a system workorder row (host-managed)`);
|
|
1801
|
-
}
|
|
1802
|
-
const hasLifecyclePatch = Object.prototype.hasOwnProperty.call(patch, 'status') ||
|
|
1803
|
-
Object.prototype.hasOwnProperty.call(patch, 'due_at') ||
|
|
1804
|
-
Object.prototype.hasOwnProperty.call(patch, 'latest_event') ||
|
|
1805
|
-
Object.prototype.hasOwnProperty.call(patch, 'completion_criteria');
|
|
1806
|
-
// Direct-ABI mutation under a carried attempt: the attempt must be current
|
|
1807
|
-
// for EVERY patch shape (a stale attempt renaming a task is as stale as one
|
|
1808
|
-
// closing it). Work kind is scheduling metadata, not owner authority.
|
|
1809
|
-
// Internal receipted primitives (external lifecycle apply) do not set the
|
|
1810
|
-
// direct-ABI guard and validate their own claimed attempt separately.
|
|
1811
|
-
const isCarriedDirectMutation = origin.requiresExpectedRevision === true && origin.workOrderAttemptId !== undefined;
|
|
1812
|
-
if (isCarriedDirectMutation) {
|
|
1813
|
-
this.assertWorkOrderAttemptActive('task_update', origin.workOrderAttemptId);
|
|
1814
|
-
}
|
|
1815
|
-
// A lifecycle judgment additionally proves the revision actually read plus a reason.
|
|
1816
|
-
const isWorkorderLifecycleJudgment = isCarriedDirectMutation && hasLifecyclePatch;
|
|
1817
|
-
if (isWorkorderLifecycleJudgment) {
|
|
1818
|
-
if (!Number.isSafeInteger(patch.expected_revision) || patch.expected_revision < 0) {
|
|
1819
|
-
throw new Error('task_update: lifecycle workorder requires expected_revision');
|
|
1820
|
-
}
|
|
1821
|
-
if (patch.expected_revision !== existing.revision) {
|
|
1822
|
-
throw new Error(`task_update: expected revision ${patch.expected_revision}, current ${existing.revision}`);
|
|
1823
|
-
}
|
|
1824
|
-
if (typeof patch.latest_event !== 'string' || patch.latest_event.trim().length === 0) {
|
|
1825
|
-
throw new Error('task_update: lifecycle workorder requires a plain latest_event reason');
|
|
1826
|
-
}
|
|
1827
|
-
}
|
|
1828
|
-
return this.transitionOwnerTaskRowInTransaction(id, existing, patch, origin);
|
|
1829
|
-
}
|
|
1830
|
-
validateTaskUpdatePatch(patch) {
|
|
1831
|
-
if (patch.status === 'failed') {
|
|
1832
|
-
throw new Error(`task_update: 'failed' is a system-only status`);
|
|
1833
|
-
}
|
|
1834
|
-
if (patch.status !== undefined)
|
|
1835
|
-
assertEnum(patch.status, exports.TASK_STATUSES, 'status');
|
|
1836
|
-
if (patch.priority !== undefined)
|
|
1837
|
-
assertEnum(patch.priority, exports.TASK_PRIORITIES, 'priority');
|
|
1838
|
-
if (patch.deadline !== undefined && patch.deadline !== null) {
|
|
1839
|
-
assertIsoDate(patch.deadline, 'deadline');
|
|
1840
|
-
}
|
|
1841
|
-
if (patch.title !== undefined && patch.title.trim() === '') {
|
|
1842
|
-
throw new Error('task title must be a non-empty string');
|
|
1843
|
-
}
|
|
1844
|
-
}
|
|
1845
|
-
transitionOwnerTaskRowInTransaction(id, existing, patch, origin) {
|
|
1846
|
-
const next = {
|
|
1847
|
-
title: existing.title,
|
|
1848
|
-
status: existing.status,
|
|
1849
|
-
priority: existing.priority,
|
|
1850
|
-
assignee: existing.assignee,
|
|
1851
|
-
deadline: existing.deadline,
|
|
1852
|
-
due_at: existing.due_at,
|
|
1853
|
-
deadline_offset_minutes: existing.deadline_offset_minutes,
|
|
1854
|
-
latest_event: existing.latest_event,
|
|
1855
|
-
confirmed: existing.confirmed,
|
|
1856
|
-
temporal_epoch: existing.temporal_epoch,
|
|
1857
|
-
temporal_reconciled_occurrence_key: existing.temporal_reconciled_occurrence_key,
|
|
1858
|
-
last_temporal_checked_at: existing.last_temporal_checked_at,
|
|
1859
|
-
next_temporal_check_at: existing.next_temporal_check_at,
|
|
1860
|
-
last_temporal_attempt_id: existing.last_temporal_attempt_id,
|
|
1861
|
-
review_started_at: existing.review_started_at,
|
|
1862
|
-
review_anchor_event_id: existing.review_anchor_event_id,
|
|
1863
|
-
review_anchor_source_channel: existing.review_anchor_source_channel,
|
|
1864
|
-
completion_criteria: existing.completion_criteria,
|
|
1865
|
-
resolution_kind: existing.resolution_kind,
|
|
1866
|
-
};
|
|
1867
|
-
if (patch.title !== undefined)
|
|
1868
|
-
next.title = patch.title.trim();
|
|
1869
|
-
if (patch.status !== undefined)
|
|
1870
|
-
next.status = patch.status;
|
|
1871
|
-
if (patch.priority !== undefined)
|
|
1872
|
-
next.priority = patch.priority;
|
|
1873
|
-
if (patch.assignee !== undefined)
|
|
1874
|
-
next.assignee = patch.assignee;
|
|
1875
|
-
if (patch.latest_event !== undefined)
|
|
1876
|
-
next.latest_event = patch.latest_event;
|
|
1877
|
-
if (patch.confirmed !== undefined)
|
|
1878
|
-
next.confirmed = patch.confirmed ? 1 : 0;
|
|
1879
|
-
if (patch.completion_criteria !== undefined) {
|
|
1880
|
-
next.completion_criteria = normalizeCompletionCriteria(patch.completion_criteria, 'task_update');
|
|
1881
|
-
}
|
|
1882
|
-
// Host-internal (reclassify only); the public task_update surface rejects it.
|
|
1883
|
-
if (patch.resolution_kind !== undefined)
|
|
1884
|
-
next.resolution_kind = patch.resolution_kind;
|
|
1885
|
-
const leavingVerifiedReview = existing.status === 'review' &&
|
|
1886
|
-
patch.status !== undefined &&
|
|
1887
|
-
patch.status !== 'review' &&
|
|
1888
|
-
existing.review_started_at !== null &&
|
|
1889
|
-
typeof existing.review_anchor_event_id === 'string' &&
|
|
1890
|
-
existing.review_anchor_event_id.trim().length > 0;
|
|
1891
|
-
if (leavingVerifiedReview) {
|
|
1892
|
-
next.review_started_at = null;
|
|
1893
|
-
next.review_anchor_event_id = null;
|
|
1894
|
-
next.review_anchor_source_channel = null;
|
|
1895
|
-
next.due_at = null;
|
|
1896
|
-
next.deadline = null;
|
|
1897
|
-
next.deadline_offset_minutes = null;
|
|
1898
|
-
}
|
|
1899
|
-
const reviewAttempt = origin.requiresExpectedRevision
|
|
1900
|
-
? this.getWorkOrderById(origin.workOrderAttemptId ?? -1)
|
|
1901
|
-
: null;
|
|
1902
|
-
if (patch.status === 'review' &&
|
|
1903
|
-
((reviewAttempt?.workKind === 'board' && reviewAttempt.status === 'in_progress') ||
|
|
1904
|
-
origin.verifiedReviewEvidence !== undefined)) {
|
|
1905
|
-
if (Object.prototype.hasOwnProperty.call(patch, 'due_at') ||
|
|
1906
|
-
Object.prototype.hasOwnProperty.call(patch, 'deadline')) {
|
|
1907
|
-
throw new Error('task_update: review due_at and deadline are computed from verified evidence');
|
|
1908
|
-
}
|
|
1909
|
-
const evidence = origin.verifiedReviewEvidence;
|
|
1910
|
-
if (!evidence) {
|
|
1911
|
-
throw new Error('task_update: review requires host-verified submission evidence');
|
|
1912
|
-
}
|
|
1913
|
-
if (!evidence.sourceChannel.trim()) {
|
|
1914
|
-
throw new Error('task_update: review evidence source is required');
|
|
1915
|
-
}
|
|
1916
|
-
if (!Number.isSafeInteger(evidence.sourceTimestampMs) || evidence.sourceTimestampMs < 0) {
|
|
1917
|
-
throw new Error('task_update: review evidence timestamp is invalid');
|
|
1918
|
-
}
|
|
1919
|
-
next.review_started_at = evidence.sourceTimestampMs;
|
|
1920
|
-
next.review_anchor_event_id = evidence.eventIndexId;
|
|
1921
|
-
next.review_anchor_source_channel = evidence.sourceChannel;
|
|
1922
|
-
next.due_at = evidence.sourceTimestampMs + 14 * 24 * 60 * 60 * 1000;
|
|
1923
|
-
next.deadline = new Date(next.due_at).toISOString().slice(0, 10);
|
|
1924
|
-
next.deadline_offset_minutes = 0;
|
|
1925
|
-
}
|
|
1926
|
-
const hasDueAt = Object.prototype.hasOwnProperty.call(patch, 'due_at');
|
|
1927
|
-
const hasDeadline = Object.prototype.hasOwnProperty.call(patch, 'deadline');
|
|
1928
|
-
if (hasDueAt && patch.due_at !== null && patch.due_at !== undefined) {
|
|
1929
|
-
const exactDue = (0, task_temporal_js_1.parseExactDueAt)(patch.due_at);
|
|
1930
|
-
if (hasDeadline && patch.deadline !== exactDue.deadline) {
|
|
1931
|
-
throw new Error('task_update: due_at and deadline conflict');
|
|
1932
|
-
}
|
|
1933
|
-
next.due_at = exactDue.dueAt;
|
|
1934
|
-
next.deadline = exactDue.deadline;
|
|
1935
|
-
next.deadline_offset_minutes = exactDue.offsetMinutes;
|
|
1936
|
-
}
|
|
1937
|
-
else if (hasDeadline) {
|
|
1938
|
-
next.deadline = patch.deadline ?? null;
|
|
1939
|
-
next.due_at = null;
|
|
1940
|
-
if (patch.deadline === null) {
|
|
1941
|
-
next.deadline_offset_minutes = null;
|
|
1942
|
-
}
|
|
1943
|
-
}
|
|
1944
|
-
else if (hasDueAt) {
|
|
1945
|
-
next.due_at = null;
|
|
1946
|
-
}
|
|
1947
|
-
const temporalChanged = next.due_at !== existing.due_at || next.deadline !== existing.deadline;
|
|
1948
|
-
const terminalToOpen = (existing.status === 'done' || existing.status === 'cancelled') &&
|
|
1949
|
-
next.status !== 'done' &&
|
|
1950
|
-
next.status !== 'cancelled';
|
|
1951
|
-
if (terminalToOpen && existing.resolution_kind !== null && patch.resolution_kind !== null) {
|
|
1952
|
-
throw new Error('task_update: a classified terminal row must reopen through task_reclassify');
|
|
1953
|
-
}
|
|
1954
|
-
if (patch.status !== undefined &&
|
|
1955
|
-
next.status !== existing.status &&
|
|
1956
|
-
patch.resolution_kind === undefined) {
|
|
1957
|
-
// A generic status change has no semantic reclassification authority. Clear an
|
|
1958
|
-
// old terminal meaning rather than rendering contradictory status/reason pairs.
|
|
1959
|
-
next.resolution_kind = null;
|
|
1960
|
-
}
|
|
1961
|
-
const openToTerminal = existing.status !== 'done' &&
|
|
1962
|
-
existing.status !== 'cancelled' &&
|
|
1963
|
-
(next.status === 'done' || next.status === 'cancelled');
|
|
1964
|
-
if (temporalChanged || terminalToOpen) {
|
|
1965
|
-
next.temporal_epoch = existing.temporal_epoch + 1;
|
|
1966
|
-
next.temporal_reconciled_occurrence_key = null;
|
|
1967
|
-
next.last_temporal_checked_at = null;
|
|
1968
|
-
next.next_temporal_check_at = null;
|
|
1969
|
-
next.last_temporal_attempt_id = null;
|
|
1970
|
-
}
|
|
1971
|
-
const persistedColumns = [
|
|
1972
|
-
'title',
|
|
1973
|
-
'status',
|
|
1974
|
-
'priority',
|
|
1975
|
-
'assignee',
|
|
1976
|
-
'deadline',
|
|
1977
|
-
'due_at',
|
|
1978
|
-
'deadline_offset_minutes',
|
|
1979
|
-
'latest_event',
|
|
1980
|
-
'confirmed',
|
|
1981
|
-
'temporal_epoch',
|
|
1982
|
-
'temporal_reconciled_occurrence_key',
|
|
1983
|
-
'last_temporal_checked_at',
|
|
1984
|
-
'next_temporal_check_at',
|
|
1985
|
-
'last_temporal_attempt_id',
|
|
1986
|
-
'review_started_at',
|
|
1987
|
-
'review_anchor_event_id',
|
|
1988
|
-
'review_anchor_source_channel',
|
|
1989
|
-
'completion_criteria',
|
|
1990
|
-
'resolution_kind',
|
|
1991
|
-
];
|
|
1992
|
-
const changedColumns = persistedColumns.filter((column) => next[column] !== existing[column]);
|
|
1993
|
-
if (changedColumns.length === 0) {
|
|
1994
|
-
return this.toRecord(existing);
|
|
1995
|
-
}
|
|
1996
|
-
else {
|
|
1997
|
-
const nextRevision = existing.revision + 1;
|
|
1998
|
-
const sets = changedColumns.map((column) => `${column} = ?`);
|
|
1999
|
-
const values = changedColumns.map((column) => next[column]);
|
|
2000
|
-
const updatedAt = this.now();
|
|
2001
|
-
sets.push('revision = ?', 'updated_at = ?');
|
|
2002
|
-
values.push(nextRevision, updatedAt);
|
|
2003
|
-
const updated = this.db
|
|
2004
|
-
.prepare(`UPDATE operator_tasks SET ${sets.join(', ')} WHERE id = ? AND revision = ?`)
|
|
2005
|
-
.run(...values, id, existing.revision);
|
|
2006
|
-
if (updated.changes !== 1) {
|
|
2007
|
-
throw new Error(`task_update: task ${id} revision changed during transition`);
|
|
2008
|
-
}
|
|
2009
|
-
this.recordTaskChange({
|
|
2010
|
-
kind: 'task_update',
|
|
2011
|
-
taskId: id,
|
|
2012
|
-
origin,
|
|
2013
|
-
channel: existing.source_channel,
|
|
2014
|
-
payload: {
|
|
2015
|
-
revision: nextRevision,
|
|
2016
|
-
changed: Object.fromEntries(changedColumns.map((column) => [column, next[column]])),
|
|
2017
|
-
},
|
|
2018
|
-
atMs: updatedAt,
|
|
2019
|
-
});
|
|
2020
|
-
if (temporalChanged || terminalToOpen) {
|
|
2021
|
-
this.supersedeTemporalGenerationsInTransaction(id, Number(next.temporal_epoch));
|
|
2022
|
-
}
|
|
2023
|
-
else if (openToTerminal) {
|
|
2024
|
-
this.supersedeAllActiveTemporalGenerationsInTransaction(id);
|
|
2025
|
-
}
|
|
2026
|
-
const transitioned = this.getRowById(id);
|
|
2027
|
-
if (!transitioned)
|
|
2028
|
-
throw new Error(`task_update: task ${id} disappeared during transition`);
|
|
2029
|
-
return transitioned;
|
|
2030
|
-
}
|
|
2031
|
-
}
|
|
2032
|
-
assertCandidateTaskMutationAllowed(taskId, patch, origin) {
|
|
2033
|
-
if (origin.workOrderAttemptId === undefined ||
|
|
2034
|
-
(!Object.prototype.hasOwnProperty.call(patch, 'status') &&
|
|
2035
|
-
!Object.prototype.hasOwnProperty.call(patch, 'latest_event') &&
|
|
2036
|
-
!Object.prototype.hasOwnProperty.call(patch, 'completion_criteria'))) {
|
|
2037
|
-
return;
|
|
2038
|
-
}
|
|
2039
|
-
const attempt = this.getWorkOrderById(origin.workOrderAttemptId);
|
|
2040
|
-
// Unlike a lifecycle decision, this is a standing mutation guard. A late
|
|
2041
|
-
// direct or nested tool call can retain the host-issued attempt context
|
|
2042
|
-
// after its board workorder has reached a terminal status, and its durable
|
|
2043
|
-
// candidate payload must remain authoritative in that case. Decision
|
|
2044
|
-
// application still calls loadBoardCandidate(), which requires a claimed
|
|
2045
|
-
// in-progress attempt.
|
|
2046
|
-
//
|
|
2047
|
-
// The payload's reclassificationCandidates page is NOT consulted here: it is
|
|
2048
|
-
// the scheduler's bounded-read hint, and qualifying a legacy row the owner
|
|
2049
|
-
// discovered outside that page is an ordinary owner decision guarded by
|
|
2050
|
-
// attempt currency, expected_revision and a plain reason (see
|
|
2051
|
-
// transitionTaskInTransaction). Only host-attested external binding /
|
|
2052
|
-
// lifecycle candidates keep their receipted-decision-only rule below.
|
|
2053
|
-
if (!attempt || attempt.workKind !== 'board') {
|
|
2054
|
-
return;
|
|
2055
|
-
}
|
|
2056
|
-
const payload = publisherPayloadOfStoredWorkOrder(attempt.payload);
|
|
2057
|
-
(0, workorder_publishers_js_1.validateWorkOrderPayload)('board', payload);
|
|
2058
|
-
if (payload.mode !== 'reconcile' || !payload.candidates) {
|
|
2059
|
-
return;
|
|
2060
|
-
}
|
|
2061
|
-
const candidates = payload.candidates;
|
|
2062
|
-
if ([...candidates.bindingCandidates, ...candidates.lifecycleCandidates].some((candidate) => candidate.taskId === taskId)) {
|
|
2063
|
-
throw new Error(`task_update: candidate-bound lifecycle task ${taskId} may change status/latest_event only through its receipted decision`);
|
|
2064
|
-
}
|
|
2065
|
-
}
|
|
2066
|
-
supersedeTemporalGenerationsInTransaction(taskId, currentEpoch, excludeGenerationKey, updatedAt = this.now()) {
|
|
2067
|
-
const reason = 'task temporal occurrence superseded';
|
|
2068
|
-
const exclusion = excludeGenerationKey ? ' AND generation_key != ?' : '';
|
|
2069
|
-
const generationParams = excludeGenerationKey
|
|
2070
|
-
? [taskId, currentEpoch, excludeGenerationKey]
|
|
2071
|
-
: [taskId, currentEpoch];
|
|
2072
|
-
this.db
|
|
2073
|
-
.prepare(`UPDATE operator_tasks SET status = 'cancelled', latest_event = ?, updated_at = ?
|
|
2074
|
-
WHERE kind = 'system' AND status IN ('pending','in_progress') AND id IN (
|
|
2075
|
-
SELECT last_workorder_id FROM operator_temporal_generations
|
|
2076
|
-
WHERE task_id = ? AND disposition = 'active' AND temporal_epoch < ?${exclusion}
|
|
2077
|
-
)`)
|
|
2078
|
-
.run(reason, updatedAt, ...generationParams);
|
|
2079
|
-
this.db
|
|
2080
|
-
.prepare(`UPDATE operator_temporal_generations
|
|
2081
|
-
SET disposition = 'superseded', reason = ?, updated_at = ?
|
|
2082
|
-
WHERE task_id = ? AND disposition = 'active' AND temporal_epoch < ?${exclusion}`)
|
|
2083
|
-
.run(reason, updatedAt, ...generationParams);
|
|
2084
|
-
}
|
|
2085
|
-
supersedeAllActiveTemporalGenerationsInTransaction(taskId, updatedAt = this.now()) {
|
|
2086
|
-
const reason = 'owner task closed';
|
|
2087
|
-
this.db
|
|
2088
|
-
.prepare(`UPDATE operator_tasks SET status = 'cancelled', latest_event = ?, updated_at = ?
|
|
2089
|
-
WHERE kind = 'system' AND status IN ('pending','in_progress') AND id IN (
|
|
2090
|
-
SELECT last_workorder_id FROM operator_temporal_generations
|
|
2091
|
-
WHERE task_id = ? AND disposition = 'active'
|
|
2092
|
-
)`)
|
|
2093
|
-
.run(reason, updatedAt, taskId);
|
|
2094
|
-
this.db
|
|
2095
|
-
.prepare(`UPDATE operator_temporal_generations
|
|
2096
|
-
SET disposition = 'superseded', reason = ?, updated_at = ?
|
|
2097
|
-
WHERE task_id = ? AND disposition = 'active'`)
|
|
2098
|
-
.run(reason, updatedAt, taskId);
|
|
2099
|
-
}
|
|
2100
|
-
/**
|
|
2101
|
-
* Stable hash over ordered OWNER rows - the Phase-2 verifier's ledger
|
|
2102
|
-
* snapshot. kind filter keeps concurrent system enqueues from shaking the
|
|
2103
|
-
* hash mid-bracket (evidence-only signal, but noise is noise).
|
|
2104
|
-
*/
|
|
2105
|
-
payloadHash() {
|
|
2106
|
-
const rows = this.db
|
|
2107
|
-
.prepare(`SELECT id, title, status, priority, assignee, deadline, latest_event, confirmed,
|
|
2108
|
-
updated_at
|
|
2109
|
-
FROM operator_tasks WHERE kind = 'owner' ORDER BY id ASC`)
|
|
2110
|
-
.all();
|
|
2111
|
-
return (0, node_crypto_1.createHash)('sha256').update(JSON.stringify(rows)).digest('hex');
|
|
2112
|
-
}
|
|
2113
|
-
// ── Workorder API (Stage 2) ─────────────────────────────────────────────
|
|
2114
|
-
// System rows only. These are the ONLY paths that create or transition
|
|
2115
|
-
// kind='system' rows; external create/update guards reject everything else.
|
|
2116
|
-
/**
|
|
2117
|
-
* Enqueue a workorder. Idempotent per occurrence key: an open (pending or
|
|
2118
|
-
* in_progress) keyed row dedups. Completed owner-event occurrences and
|
|
2119
|
-
* scheduled promotion slots also dedup; other terminal rows free the key
|
|
2120
|
-
* and a fresh row is inserted.
|
|
2121
|
-
*/
|
|
2122
|
-
enqueueWorkOrder(order) {
|
|
2123
|
-
if (order.workKind === 'temporal') {
|
|
2124
|
-
throw new Error('enqueueWorkOrder: temporal work requires enqueueTemporalGeneration');
|
|
2125
|
-
}
|
|
2126
|
-
if (Object.prototype.hasOwnProperty.call(order.input, 'attempts')) {
|
|
2127
|
-
throw new Error('enqueueWorkOrder: attempts is ledger-managed and cannot be supplied');
|
|
2128
|
-
}
|
|
2129
|
-
return this.insertWorkOrder(order, 1);
|
|
2130
|
-
}
|
|
2131
|
-
/** Read a durable workorder acceptance by its host-issued occurrence key. */
|
|
2132
|
-
findWorkOrderByOccurrence(workKind, idempotencyKey) {
|
|
2133
|
-
const row = this.db
|
|
2134
|
-
.prepare(`SELECT * FROM operator_tasks
|
|
2135
|
-
WHERE kind = 'system' AND source_channel = ? AND source_event_id = ?
|
|
2136
|
-
ORDER BY id ASC LIMIT 1`)
|
|
2137
|
-
.get(`${exports.WORKORDER_CHANNEL_PREFIX}${workKind}`, idempotencyKey);
|
|
2138
|
-
return row ? this.rowToWorkOrder(row) : null;
|
|
2139
|
-
}
|
|
2140
|
-
/**
|
|
2141
|
-
* The newest COMPLETED full-board-class run - the baseline the scheduled full-board producer
|
|
2142
|
-
* gates against (board-delta-gate.ts). A `delta` run counts too: it publishes the same three
|
|
2143
|
-
* judgment slots from the same accumulated state, so excluding it would leave the previous
|
|
2144
|
-
* full run as the baseline forever and re-trigger a delta on every tick.
|
|
2145
|
-
*
|
|
2146
|
-
* Only `status='done'` full/delta rows count: an open or failed run has not
|
|
2147
|
-
* discharged its interval. `watermark` is null when the run captured none
|
|
2148
|
-
* (an owner-forced refresh), which makes the next scheduled tick enqueue
|
|
2149
|
-
* rather than skip.
|
|
2150
|
-
*/
|
|
2151
|
-
lastCompletedBoardFullRun() {
|
|
2152
|
-
const row = this.db
|
|
2153
|
-
.prepare(`SELECT json_extract(payload, '$.deltaWatermark') AS watermark, created_at, updated_at
|
|
2154
|
-
FROM operator_tasks
|
|
2155
|
-
WHERE kind = 'system' AND source_channel = ? AND status = 'done'
|
|
2156
|
-
AND json_extract(payload, '$.mode') IN ('full', 'delta')
|
|
2157
|
-
ORDER BY id DESC LIMIT 1`)
|
|
2158
|
-
.get(`${exports.WORKORDER_CHANNEL_PREFIX}board`);
|
|
2159
|
-
if (!row)
|
|
2160
|
-
return null;
|
|
2161
|
-
return {
|
|
2162
|
-
watermark: typeof row.watermark === 'string' && row.watermark.length > 0 ? row.watermark : null,
|
|
2163
|
-
createdAt: row.created_at,
|
|
2164
|
-
completedAt: row.updated_at,
|
|
2165
|
-
};
|
|
2166
|
-
}
|
|
2167
|
-
/**
|
|
2168
|
-
* The newest COMPLETED wiki run - the baseline the wiki daily-continuity
|
|
2169
|
-
* planner gates against (wiki-continuity.ts). Only `status='done'` rows count.
|
|
2170
|
-
*
|
|
2171
|
-
* `ownerDate`/`sourceWatermark` are read straight from the completed run's
|
|
2172
|
-
* payload and are null for a LEGACY row (one enqueued before the continuity
|
|
2173
|
-
* fields existed): the planner treats a null-field baseline as "no baseline"
|
|
2174
|
-
* and re-runs from the owner day's start rather than fabricating a boundary.
|
|
2175
|
-
*/
|
|
2176
|
-
lastCompletedWikiRun() {
|
|
2177
|
-
const row = this.db
|
|
2178
|
-
.prepare(`SELECT json_extract(payload, '$.ownerDate') AS owner_date,
|
|
2179
|
-
json_extract(payload, '$.sourceWatermark') AS source_watermark,
|
|
2180
|
-
json_extract(payload, '$.range.end_ms') AS covered_through_ms,
|
|
2181
|
-
updated_at
|
|
2182
|
-
FROM operator_tasks
|
|
2183
|
-
WHERE kind = 'system' AND source_channel = ? AND status = 'done'
|
|
2184
|
-
ORDER BY id DESC LIMIT 1`)
|
|
2185
|
-
.get(`${exports.WORKORDER_CHANNEL_PREFIX}wiki`);
|
|
2186
|
-
if (!row)
|
|
2187
|
-
return null;
|
|
2188
|
-
return {
|
|
2189
|
-
ownerDate: typeof row.owner_date === 'string' && row.owner_date.length > 0 ? row.owner_date : null,
|
|
2190
|
-
sourceWatermark: typeof row.source_watermark === 'string' && row.source_watermark.length > 0
|
|
2191
|
-
? row.source_watermark
|
|
2192
|
-
: null,
|
|
2193
|
-
// The prior run's range.end_ms - where the next same-day run resumes.
|
|
2194
|
-
// Null for a legacy row with no range; never fabricated.
|
|
2195
|
-
coveredThroughMs: typeof row.covered_through_ms === 'number' && Number.isFinite(row.covered_through_ms)
|
|
2196
|
-
? row.covered_through_ms
|
|
2197
|
-
: null,
|
|
2198
|
-
completedAt: row.updated_at,
|
|
2199
|
-
};
|
|
2200
|
-
}
|
|
2201
|
-
/**
|
|
2202
|
-
* Board-input term for the NATIVE owner task ledger - the pipeline slot's
|
|
2203
|
-
* projection source. Reuses payloadHash(): it already digests exactly the
|
|
2204
|
-
* owner-row fields the pipeline slot renders, so a status or assignee edit
|
|
2205
|
-
* moves the term even within the same millisecond as the row's creation,
|
|
2206
|
-
* which a MAX(updated_at) marker would miss. System workorder rows are
|
|
2207
|
-
* excluded by that same query - they are pipeline plumbing, not board truth.
|
|
2208
|
-
*/
|
|
2209
|
-
ownerTaskTerm() {
|
|
2210
|
-
return `t:${this.payloadHash()}`;
|
|
2211
|
-
}
|
|
2212
|
-
insertWorkOrder(order, attempts) {
|
|
2213
|
-
assertEnum(order.workKind, exports.WORKORDER_KINDS, 'workKind');
|
|
2214
|
-
if (order.priority !== undefined) {
|
|
2215
|
-
assertEnum(order.priority, exports.TASK_PRIORITIES, 'priority');
|
|
2216
|
-
}
|
|
2217
|
-
if (!order.idempotencyKey || order.idempotencyKey.trim() === '') {
|
|
2218
|
-
throw new Error('enqueueWorkOrder: idempotencyKey must be non-empty');
|
|
2219
|
-
}
|
|
2220
|
-
const channel = `${exports.WORKORDER_CHANNEL_PREFIX}${order.workKind}`;
|
|
2221
|
-
// Owner-event occurrences and scheduled six-hour promotion slots stay
|
|
2222
|
-
// reserved after they COMPLETE. The former prevents post-enqueue/pre-ACK
|
|
2223
|
-
// replay; the latter prevents daemon restarts from repeating an already
|
|
2224
|
-
// discharged slot. Manual promotions use promotion:manual:<time> and do
|
|
2225
|
-
// not enter this branch. 'failed' and 'cancelled' must NOT reserve either
|
|
2226
|
-
// occurrence: those rows are dead and must remain retryable.
|
|
2227
|
-
const durableOwnerEvent = order.idempotencyKey.startsWith('owner-event:');
|
|
2228
|
-
const scheduledPromotionKeys = order.workKind === 'memory-curation'
|
|
2229
|
-
? scheduledPromotionReservationKeys(order.idempotencyKey)
|
|
2230
|
-
: null;
|
|
2231
|
-
const completedScheduledPromotion = scheduledPromotionKeys !== null;
|
|
2232
|
-
const reservingStatuses = durableOwnerEvent || completedScheduledPromotion
|
|
2233
|
-
? "'pending','in_progress','done'"
|
|
2234
|
-
: "'pending','in_progress'";
|
|
2235
|
-
const reservationKeys = scheduledPromotionKeys ?? [order.idempotencyKey];
|
|
2236
|
-
const reservationPlaceholders = reservationKeys.map(() => '?').join(',');
|
|
2237
|
-
const open = this.db
|
|
2238
|
-
.prepare(`SELECT * FROM operator_tasks
|
|
2239
|
-
WHERE kind = 'system' AND source_channel = ?
|
|
2240
|
-
AND source_event_id IN (${reservationPlaceholders})
|
|
2241
|
-
AND status IN (${reservingStatuses})
|
|
2242
|
-
ORDER BY id ASC LIMIT 1`)
|
|
2243
|
-
.get(channel, ...reservationKeys);
|
|
2244
|
-
if (open) {
|
|
2245
|
-
return this.rowToWorkOrder(open);
|
|
2246
|
-
}
|
|
2247
|
-
if (!Number.isSafeInteger(attempts) || attempts < 1) {
|
|
2248
|
-
throw new Error(`insertWorkOrder: attempts must be a positive integer, got: ${attempts}`);
|
|
2249
|
-
}
|
|
2250
|
-
const payload = JSON.stringify({ ...order.input, attempts });
|
|
2251
|
-
const now = this.now();
|
|
2252
|
-
const result = this.db
|
|
2253
|
-
.prepare(`INSERT INTO operator_tasks
|
|
2254
|
-
(title, status, priority, kind, payload, source_channel, source_event_id,
|
|
2255
|
-
auto_created, confirmed, created_at, updated_at)
|
|
2256
|
-
VALUES (?, 'pending', ?, 'system', ?, ?, ?, 1, 0, ?, ?)`)
|
|
2257
|
-
.run(`workorder:${order.workKind}`, order.priority ?? 'normal', payload, channel, order.idempotencyKey, now, now);
|
|
2258
|
-
return this.getWorkOrderById(Number(result.lastInsertRowid));
|
|
2259
|
-
}
|
|
2260
|
-
enqueueTemporalGeneration(input) {
|
|
2261
|
-
this.validateTemporalGenerationInput(input);
|
|
2262
|
-
const sourceChannelRef = this.temporalSourceIdentifierRef(input.sourceChannel);
|
|
2263
|
-
const sourceEventIdRef = this.temporalSourceIdentifierRef(input.sourceEventId);
|
|
2264
|
-
let result = null;
|
|
2265
|
-
this.db.exec('BEGIN IMMEDIATE');
|
|
2266
|
-
try {
|
|
2267
|
-
const existing = this.getTemporalGeneration(input.generationKey);
|
|
2268
|
-
if (existing) {
|
|
2269
|
-
if (existing.taskId !== input.taskId ||
|
|
2270
|
-
existing.temporalEpoch !== input.temporalEpoch ||
|
|
2271
|
-
existing.occurrenceKey !== input.occurrenceKey ||
|
|
2272
|
-
existing.checkAt !== input.checkAt) {
|
|
2273
|
-
throw new Error(`temporal generation '${input.generationKey}' conflicts with its stored identity`);
|
|
2274
|
-
}
|
|
2275
|
-
if (existing.lastWorkOrderId === null) {
|
|
2276
|
-
throw new Error(`temporal generation '${input.generationKey}' has no owning workorder attempt`);
|
|
2277
|
-
}
|
|
2278
|
-
const workOrder = this.getWorkOrderById(existing.lastWorkOrderId);
|
|
2279
|
-
if (!workOrder) {
|
|
2280
|
-
throw new Error(`temporal generation '${input.generationKey}' owns missing workorder ${existing.lastWorkOrderId}`);
|
|
2281
|
-
}
|
|
2282
|
-
this.assertTemporalPayloadMatches(workOrder, existing);
|
|
2283
|
-
if (workOrder.payload.sourceChannel !== sourceChannelRef ||
|
|
2284
|
-
workOrder.payload.sourceEventId !== sourceEventIdRef) {
|
|
2285
|
-
throw new Error(`temporal generation '${input.generationKey}' conflicts with stored source identifiers`);
|
|
2286
|
-
}
|
|
2287
|
-
result = { generation: existing, workOrder, created: false };
|
|
2288
|
-
}
|
|
2289
|
-
else {
|
|
2290
|
-
const task = this.getRowById(input.taskId);
|
|
2291
|
-
if (!task || task.kind !== 'owner') {
|
|
2292
|
-
throw new Error(`temporal generation: owner task ${input.taskId} does not exist`);
|
|
2293
|
-
}
|
|
2294
|
-
if (task.status === 'done' || task.status === 'cancelled') {
|
|
2295
|
-
throw new Error(`temporal generation: task ${input.taskId} is closed`);
|
|
2296
|
-
}
|
|
2297
|
-
if (task.status === 'review' &&
|
|
2298
|
-
(task.reviewStartedAt === null ||
|
|
2299
|
-
!Number.isSafeInteger(task.reviewStartedAt) ||
|
|
2300
|
-
typeof task.reviewAnchorEventId !== 'string' ||
|
|
2301
|
-
task.reviewAnchorEventId.trim().length === 0)) {
|
|
2302
|
-
throw new Error(`temporal generation: review task ${input.taskId} has no verified review anchor`);
|
|
2303
|
-
}
|
|
2304
|
-
const currentOccurrence = (0, task_temporal_js_1.occurrenceKeyForTask)(task);
|
|
2305
|
-
if (task.temporalEpoch !== input.temporalEpoch ||
|
|
2306
|
-
currentOccurrence !== input.occurrenceKey) {
|
|
2307
|
-
throw new Error(`temporal generation: task occurrence no longer matches enqueue input`);
|
|
2308
|
-
}
|
|
2309
|
-
if (this.temporalSourceIdentifierRef(task.status === 'review'
|
|
2310
|
-
? (task.reviewAnchorSourceChannel ?? task.sourceChannel)
|
|
2311
|
-
: task.sourceChannel) !== sourceChannelRef ||
|
|
2312
|
-
this.temporalSourceIdentifierRef(task.status === 'review' && task.reviewAnchorEventId
|
|
2313
|
-
? task.reviewAnchorEventId
|
|
2314
|
-
: task.sourceEventId) !== sourceEventIdRef) {
|
|
2315
|
-
throw new Error(`temporal generation: source identifiers do not match owner task`);
|
|
2316
|
-
}
|
|
2317
|
-
const openCollision = this.db
|
|
2318
|
-
.prepare(`SELECT id FROM operator_tasks
|
|
2319
|
-
WHERE kind = 'system' AND source_channel = 'workorder:temporal'
|
|
2320
|
-
AND source_event_id = ? AND status IN ('pending','in_progress')`)
|
|
2321
|
-
.get(input.generationKey);
|
|
2322
|
-
if (openCollision) {
|
|
2323
|
-
throw new Error(`temporal generation '${input.generationKey}' has orphan open workorder ${openCollision.id}`);
|
|
2324
|
-
}
|
|
2325
|
-
const timestamp = this.now();
|
|
2326
|
-
this.db
|
|
2327
|
-
.prepare(`INSERT INTO operator_temporal_generations
|
|
2328
|
-
(generation_key, task_id, temporal_epoch, occurrence_key, check_at,
|
|
2329
|
-
disposition, created_at, updated_at)
|
|
2330
|
-
VALUES (?, ?, ?, ?, ?, 'active', ?, ?)`)
|
|
2331
|
-
.run(input.generationKey, input.taskId, input.temporalEpoch, input.occurrenceKey, input.checkAt, timestamp, timestamp);
|
|
2332
|
-
const workOrder = this.insertWorkOrder({
|
|
2333
|
-
workKind: 'temporal',
|
|
2334
|
-
idempotencyKey: input.generationKey,
|
|
2335
|
-
input: {
|
|
2336
|
-
generationKey: input.generationKey,
|
|
2337
|
-
taskId: input.taskId,
|
|
2338
|
-
temporalEpoch: input.temporalEpoch,
|
|
2339
|
-
occurrenceKey: input.occurrenceKey,
|
|
2340
|
-
checkAt: input.checkAt,
|
|
2341
|
-
sourceChannel: sourceChannelRef,
|
|
2342
|
-
sourceEventId: sourceEventIdRef,
|
|
2343
|
-
},
|
|
2344
|
-
priority: input.priority ?? 'normal',
|
|
2345
|
-
}, 1);
|
|
2346
|
-
this.db
|
|
2347
|
-
.prepare(`UPDATE operator_temporal_generations
|
|
2348
|
-
SET last_workorder_id = ?, updated_at = ? WHERE generation_key = ?`)
|
|
2349
|
-
.run(workOrder.id, timestamp, input.generationKey);
|
|
2350
|
-
const generation = this.getTemporalGeneration(input.generationKey);
|
|
2351
|
-
if (!generation) {
|
|
2352
|
-
throw new Error(`temporal generation '${input.generationKey}' could not be read back`);
|
|
2353
|
-
}
|
|
2354
|
-
result = { generation, workOrder, created: true };
|
|
2355
|
-
}
|
|
2356
|
-
this.db.exec('COMMIT');
|
|
2357
|
-
}
|
|
2358
|
-
catch (error) {
|
|
2359
|
-
this.db.exec('ROLLBACK');
|
|
2360
|
-
throw error;
|
|
2361
|
-
}
|
|
2362
|
-
if (!result) {
|
|
2363
|
-
throw new Error(`temporal generation '${input.generationKey}' produced no result`);
|
|
2364
|
-
}
|
|
2365
|
-
return result;
|
|
2366
|
-
}
|
|
2367
|
-
getTemporalGeneration(generationKey) {
|
|
2368
|
-
const row = this.db
|
|
2369
|
-
.prepare(`SELECT * FROM operator_temporal_generations WHERE generation_key = ?`)
|
|
2370
|
-
.get(generationKey);
|
|
2371
|
-
if (!row)
|
|
2372
|
-
return null;
|
|
2373
|
-
return {
|
|
2374
|
-
generationKey: row.generation_key,
|
|
2375
|
-
taskId: row.task_id,
|
|
2376
|
-
temporalEpoch: row.temporal_epoch,
|
|
2377
|
-
occurrenceKey: row.occurrence_key,
|
|
2378
|
-
checkAt: row.check_at,
|
|
2379
|
-
disposition: row.disposition,
|
|
2380
|
-
lastWorkOrderId: row.last_workorder_id,
|
|
2381
|
-
reason: row.reason,
|
|
2382
|
-
createdAt: row.created_at,
|
|
2383
|
-
updatedAt: row.updated_at,
|
|
2384
|
-
};
|
|
2385
|
-
}
|
|
2386
|
-
loadTemporalWorkContext(attemptId) {
|
|
2387
|
-
return this.loadTemporalWorkContextInternal(attemptId);
|
|
2388
|
-
}
|
|
2389
|
-
assertTemporalWorkContextActive(suppliedContext) {
|
|
2390
|
-
const trustedContext = this.loadTemporalWorkContextInternal(suppliedContext.attemptId);
|
|
2391
|
-
this.assertTemporalContextMatches(suppliedContext, trustedContext);
|
|
2392
|
-
return trustedContext;
|
|
2393
|
-
}
|
|
2394
|
-
/**
|
|
2395
|
-
* What this system changed, and what it changed it for.
|
|
2396
|
-
*
|
|
2397
|
-
* Reads live beside the writes because the effect ledger shares the tasks database on
|
|
2398
|
-
* purpose. Exposed here rather than by handing out the connection: the ledger owns both
|
|
2399
|
-
* halves, so a change and its account of itself cannot drift apart.
|
|
2400
|
-
*/
|
|
2401
|
-
listChanges(query = {}) {
|
|
2402
|
-
return (0, effects_js_1.listEffects)(this.db, query);
|
|
2403
|
-
}
|
|
2404
|
-
/** Owner rows created since `sinceMs` (the ledger stagnation probe's numerator). */
|
|
2405
|
-
countOwnerTasksCreatedSince(sinceMs) {
|
|
2406
|
-
const row = this.db
|
|
2407
|
-
.prepare(`SELECT count(*) AS n FROM operator_tasks WHERE kind = 'owner' AND created_at >= ?`)
|
|
2408
|
-
.get(sinceMs);
|
|
2409
|
-
return Number(row?.n ?? 0);
|
|
2410
|
-
}
|
|
2411
|
-
/** Receipt for a run the host stopped on its token budget; target is the run id. */
|
|
2412
|
-
recordBudgetStop(input) {
|
|
2413
|
-
(0, effects_js_1.recordUnattributedChange)(this.db, {
|
|
2414
|
-
runId: input.modelRunId,
|
|
2415
|
-
channelId: null,
|
|
2416
|
-
kind: 'run_budget_stop',
|
|
2417
|
-
targetType: 'run',
|
|
2418
|
-
targetId: input.modelRunId ?? `run:${this.now()}`,
|
|
2419
|
-
payload: { budgetTokens: input.budgetTokens, runTokenBudget: input.runTokenBudget },
|
|
2420
|
-
atMs: this.now(),
|
|
2421
|
-
}, 'clock');
|
|
2422
|
-
}
|
|
2423
|
-
/** Receipt for a repair request filed by the self-check turn; target is the issue id. */
|
|
2424
|
-
recordRepairRequest(input) {
|
|
2425
|
-
const change = {
|
|
2426
|
-
runId: input.runId,
|
|
2427
|
-
channelId: null,
|
|
2428
|
-
kind: 'repair_request',
|
|
2429
|
-
targetType: 'issue',
|
|
2430
|
-
targetId: input.issueId,
|
|
2431
|
-
payload: { repairId: input.repairId },
|
|
2432
|
-
atMs: this.now(),
|
|
2433
|
-
};
|
|
2434
|
-
const causes = (input.causeEventIds ?? []).filter(effects_js_1.isUsableCause);
|
|
2435
|
-
if (causes.length > 0) {
|
|
2436
|
-
(0, effects_js_1.recordEffect)(this.db, { ...change, sourceEventIds: causes });
|
|
2437
|
-
}
|
|
2438
|
-
else {
|
|
2439
|
-
(0, effects_js_1.recordUnattributedChange)(this.db, change, 'clock');
|
|
2440
|
-
}
|
|
2441
|
-
}
|
|
2442
|
-
/** Receipt for a deliverable file the owner can receive; target is the content hash. */
|
|
2443
|
-
recordFileExport(input) {
|
|
2444
|
-
const change = {
|
|
2445
|
-
runId: input.runId,
|
|
2446
|
-
channelId: input.channelId ?? null,
|
|
2447
|
-
kind: 'file_export',
|
|
2448
|
-
targetType: 'file',
|
|
2449
|
-
targetId: input.sha256,
|
|
2450
|
-
payload: input.payload,
|
|
2451
|
-
atMs: this.now(),
|
|
2452
|
-
};
|
|
2453
|
-
const causes = (input.causeEventIds ?? []).filter(effects_js_1.isUsableCause);
|
|
2454
|
-
if (causes.length > 0) {
|
|
2455
|
-
(0, effects_js_1.recordEffect)(this.db, { ...change, sourceEventIds: causes });
|
|
2456
|
-
}
|
|
2457
|
-
else {
|
|
2458
|
-
(0, effects_js_1.recordUnattributedChange)(this.db, change, 'owner_message');
|
|
2459
|
-
}
|
|
2460
|
-
}
|
|
2461
|
-
/**
|
|
2462
|
-
* Receipt for a memory write made inside a bounded run. Without it a crash between
|
|
2463
|
-
* mama_save and the batch ACK re-runs the batch and saves the decision twice; with it
|
|
2464
|
-
* the crash-recovery resolver (effectsCausedBy) sees the write and ACKs without a run.
|
|
2465
|
-
* Unattributed when the run had no cause: the count of those stays visible.
|
|
2466
|
-
*/
|
|
2467
|
-
recordMemoryWrite(input) {
|
|
2468
|
-
const change = {
|
|
2469
|
-
runId: input.runId,
|
|
2470
|
-
channelId: input.channelId ?? null,
|
|
2471
|
-
kind: 'memory_write',
|
|
2472
|
-
targetType: 'memory',
|
|
2473
|
-
targetId: input.memoryRef,
|
|
2474
|
-
payload: input.payload,
|
|
2475
|
-
atMs: this.now(),
|
|
2476
|
-
};
|
|
2477
|
-
const causes = (input.causeEventIds ?? []).filter(effects_js_1.isUsableCause);
|
|
2478
|
-
if (causes.length > 0) {
|
|
2479
|
-
(0, effects_js_1.recordEffect)(this.db, { ...change, sourceEventIds: causes });
|
|
2480
|
-
}
|
|
2481
|
-
else {
|
|
2482
|
-
(0, effects_js_1.recordUnattributedChange)(this.db, change, 'owner_message');
|
|
2483
|
-
}
|
|
2484
|
-
}
|
|
2485
|
-
/**
|
|
2486
|
-
* Effect kinds this system recorded with ANY of the given events as cause.
|
|
2487
|
-
* The owner-event crash-recovery receipt reads it: a ledger write that named
|
|
2488
|
-
* the batch's events is a durable outcome even when the run died afterwards.
|
|
2489
|
-
*/
|
|
2490
|
-
effectsCausedBy(eventIds, sinceMs = 0) {
|
|
2491
|
-
const ids = [...new Set(eventIds.filter((id) => typeof id === 'string' && id.length > 0))];
|
|
2492
|
-
if (ids.length === 0) {
|
|
2493
|
-
return [];
|
|
2494
|
-
}
|
|
2495
|
-
const kinds = new Set();
|
|
2496
|
-
// Chunked like the inbox's SEEN_CHUNK: a backfill batch must not hit SQLite's
|
|
2497
|
-
// variable limit and abort the whole tick. Time-bounded to the batch: effects are
|
|
2498
|
-
// never pruned, inbox dedupe is, so a re-delivered old event must not match its
|
|
2499
|
-
// own year-old effect row and skip the model.
|
|
2500
|
-
for (let offset = 0; offset < ids.length; offset += EFFECT_CAUSE_CHUNK) {
|
|
2501
|
-
const chunk = ids.slice(offset, offset + EFFECT_CAUSE_CHUNK);
|
|
2502
|
-
const rows = this.db
|
|
2503
|
-
.prepare(`SELECT DISTINCT effect_kind FROM evidence_effects
|
|
2504
|
-
WHERE cause_state = 'attributed'
|
|
2505
|
-
AND created_at >= ?
|
|
2506
|
-
AND EXISTS (
|
|
2507
|
-
SELECT 1 FROM json_each(evidence_effects.source_event_ids_json)
|
|
2508
|
-
WHERE json_each.value IN (${chunk.map(() => '?').join(', ')})
|
|
2509
|
-
)`)
|
|
2510
|
-
.all(sinceMs, ...chunk);
|
|
2511
|
-
for (const row of rows)
|
|
2512
|
-
kinds.add(row.effect_kind);
|
|
2513
|
-
}
|
|
2514
|
-
return [...kinds].sort();
|
|
2515
|
-
}
|
|
2516
|
-
/** Of what this system changed, how much rests on evidence. */
|
|
2517
|
-
changeCoverage(sinceMs, targetType) {
|
|
2518
|
-
return (0, effects_js_1.changeCoverage)(this.db, sinceMs, targetType);
|
|
2519
|
-
}
|
|
2520
|
-
getTemporalEffect(attemptId) {
|
|
2521
|
-
const row = this.db
|
|
2522
|
-
.prepare(`SELECT * FROM operator_temporal_effects WHERE workorder_attempt_id = ?`)
|
|
2523
|
-
.get(attemptId);
|
|
2524
|
-
if (!row)
|
|
2525
|
-
return null;
|
|
2526
|
-
const changedFields = JSON.parse(row.changed_fields);
|
|
2527
|
-
if (!Array.isArray(changedFields) ||
|
|
2528
|
-
!changedFields.every((field) => typeof field === 'string')) {
|
|
2529
|
-
throw new Error(`temporal effect ${attemptId} has invalid changed_fields`);
|
|
2530
|
-
}
|
|
2531
|
-
if (!['resolved', 'final_no_update', 'deferred'].includes(row.outcome)) {
|
|
2532
|
-
throw new Error(`temporal effect ${attemptId} has invalid outcome '${row.outcome}'`);
|
|
2533
|
-
}
|
|
2534
|
-
return {
|
|
2535
|
-
workorderAttemptId: row.workorder_attempt_id,
|
|
2536
|
-
taskId: row.task_id,
|
|
2537
|
-
generationKey: row.generation_key,
|
|
2538
|
-
occurrenceKey: row.occurrence_key,
|
|
2539
|
-
outcome: row.outcome,
|
|
2540
|
-
beforeRevision: row.before_revision,
|
|
2541
|
-
afterRevision: row.after_revision,
|
|
2542
|
-
changedFields,
|
|
2543
|
-
reason: row.reason,
|
|
2544
|
-
attestationVersion: row.attestation_version === 1 ? 1 : 0,
|
|
2545
|
-
contextPacketId: row.context_packet_id ?? '',
|
|
2546
|
-
contextPacketSha256: row.context_packet_sha256 ?? '',
|
|
2547
|
-
packetCreatedAt: row.packet_created_at ?? null,
|
|
2548
|
-
nextTemporalCheckAt: row.next_temporal_check_at,
|
|
2549
|
-
createdAt: row.created_at,
|
|
2550
|
-
};
|
|
2551
|
-
}
|
|
2552
|
-
/**
|
|
2553
|
-
* Read and validate the durable state that decides a temporal attempt's
|
|
2554
|
-
* terminal outcome. This intentionally does not require an active attempt:
|
|
2555
|
-
* consumers must be able to arbitrate committed, superseded, and exhausted
|
|
2556
|
-
* attempts after runner/auditor failures and daemon restarts.
|
|
2557
|
-
*/
|
|
2558
|
-
inspectTemporalAttempt(attemptId) {
|
|
2559
|
-
const workOrder = this.getWorkOrderById(attemptId);
|
|
2560
|
-
if (!workOrder || workOrder.workKind !== 'temporal') {
|
|
2561
|
-
throw new Error(`temporal attempt state: no temporal attempt ${attemptId}`);
|
|
2562
|
-
}
|
|
2563
|
-
const generationKey = this.requirePayloadString(workOrder.payload, 'generationKey', attemptId);
|
|
2564
|
-
const generation = this.getTemporalGeneration(generationKey);
|
|
2565
|
-
if (!generation) {
|
|
2566
|
-
throw new Error(`temporal attempt state: generation '${generationKey}' is missing`);
|
|
2567
|
-
}
|
|
2568
|
-
this.assertTemporalPayloadMatches(workOrder, generation);
|
|
2569
|
-
const receipt = this.getTemporalEffect(attemptId);
|
|
2570
|
-
if (receipt) {
|
|
2571
|
-
const receiptError = (0, temporal_effect_js_1.temporalReceiptInvariantError)(receipt, {
|
|
2572
|
-
attemptId,
|
|
2573
|
-
taskId: generation.taskId,
|
|
2574
|
-
generationKey: generation.generationKey,
|
|
2575
|
-
occurrenceKey: generation.occurrenceKey,
|
|
2576
|
-
});
|
|
2577
|
-
if (receiptError) {
|
|
2578
|
-
throw new Error(`temporal attempt state: ${receiptError}`);
|
|
2579
|
-
}
|
|
2580
|
-
if (workOrder.status !== 'done' ||
|
|
2581
|
-
generation.lastWorkOrderId !== attemptId ||
|
|
2582
|
-
generation.disposition !== receipt.outcome) {
|
|
2583
|
-
throw new Error(`temporal attempt state: receipt ${attemptId} is not atomically committed`);
|
|
2584
|
-
}
|
|
2585
|
-
const task = this.getById(generation.taskId);
|
|
2586
|
-
if (!task || task.revision < receipt.afterRevision) {
|
|
2587
|
-
throw new Error(`temporal attempt state: receipt ${attemptId} owner revision is invalid`);
|
|
2588
|
-
}
|
|
2589
|
-
if (receipt.outcome !== 'resolved') {
|
|
2590
|
-
const scope = (0, temporal_effect_js_1.temporalNoUpdateScope)({
|
|
2591
|
-
attemptId,
|
|
2592
|
-
generationKey: generation.generationKey,
|
|
2593
|
-
taskId: generation.taskId,
|
|
2594
|
-
temporalEpoch: generation.temporalEpoch,
|
|
2595
|
-
occurrenceKey: generation.occurrenceKey,
|
|
2596
|
-
checkAt: generation.checkAt,
|
|
2597
|
-
revision: receipt.beforeRevision,
|
|
2598
|
-
sourceChannel: null,
|
|
2599
|
-
sourceEventId: null,
|
|
2600
|
-
});
|
|
2601
|
-
const note = this.db
|
|
2602
|
-
.prepare(`SELECT id FROM operator_no_update_notes
|
|
2603
|
-
WHERE scope = ? AND reason = ? AND created_at = ? LIMIT 1`)
|
|
2604
|
-
.get(scope, receipt.reason, receipt.createdAt);
|
|
2605
|
-
if (!note) {
|
|
2606
|
-
throw new Error(`temporal attempt state: exact-scope no-update note is missing`);
|
|
2607
|
-
}
|
|
2608
|
-
}
|
|
2609
|
-
if (task.revision === receipt.afterRevision) {
|
|
2610
|
-
if (task.lastTemporalAttemptId !== attemptId ||
|
|
2611
|
-
task.lastTemporalCheckedAt !== receipt.createdAt) {
|
|
2612
|
-
throw new Error(`temporal attempt state: owner attempt markers are invalid`);
|
|
2613
|
-
}
|
|
2614
|
-
if (receipt.outcome === 'resolved' || receipt.outcome === 'final_no_update') {
|
|
2615
|
-
if (task.temporalReconciledOccurrenceKey !== generation.occurrenceKey ||
|
|
2616
|
-
task.nextTemporalCheckAt !== null) {
|
|
2617
|
-
throw new Error(`temporal attempt state: owner final markers are invalid`);
|
|
2618
|
-
}
|
|
2619
|
-
}
|
|
2620
|
-
else if (task.nextTemporalCheckAt !== receipt.nextTemporalCheckAt) {
|
|
2621
|
-
throw new Error(`temporal attempt state: owner deferred check is invalid`);
|
|
2622
|
-
}
|
|
2623
|
-
}
|
|
2624
|
-
}
|
|
2625
|
-
else if (workOrder.status === 'done') {
|
|
2626
|
-
throw new Error(`temporal attempt state: done attempt ${attemptId} has no receipt`);
|
|
2627
|
-
}
|
|
2628
|
-
if (workOrder.status === 'in_progress') {
|
|
2629
|
-
if (receipt ||
|
|
2630
|
-
generation.disposition !== 'active' ||
|
|
2631
|
-
generation.lastWorkOrderId !== attemptId) {
|
|
2632
|
-
throw new Error(`temporal attempt state: active attempt ${attemptId} lost ownership`);
|
|
2633
|
-
}
|
|
2634
|
-
}
|
|
2635
|
-
if (workOrder.status === 'cancelled' &&
|
|
2636
|
-
generation.disposition !== 'superseded' &&
|
|
2637
|
-
(generation.disposition !== 'active' || generation.lastWorkOrderId !== attemptId)) {
|
|
2638
|
-
throw new Error(`temporal attempt state: cancelled attempt ${attemptId} has invalid ownership`);
|
|
2639
|
-
}
|
|
2640
|
-
if (workOrder.status === 'failed' &&
|
|
2641
|
-
generation.disposition === 'active' &&
|
|
2642
|
-
generation.lastWorkOrderId !== attemptId) {
|
|
2643
|
-
const replacement = generation.lastWorkOrderId === null
|
|
2644
|
-
? null
|
|
2645
|
-
: this.getWorkOrderById(generation.lastWorkOrderId);
|
|
2646
|
-
if (!replacement ||
|
|
2647
|
-
replacement.workKind !== 'temporal' ||
|
|
2648
|
-
(replacement.status !== 'pending' && replacement.status !== 'in_progress')) {
|
|
2649
|
-
throw new Error(`temporal attempt state: retry replacement is missing or terminal`);
|
|
2650
|
-
}
|
|
2651
|
-
this.assertTemporalPayloadMatches(replacement, generation);
|
|
2652
|
-
}
|
|
2653
|
-
if (generation.disposition === 'superseded' &&
|
|
2654
|
-
workOrder.status !== 'cancelled' &&
|
|
2655
|
-
workOrder.status !== 'failed') {
|
|
2656
|
-
throw new Error(`temporal attempt state: superseded attempt ${attemptId} is still open`);
|
|
2657
|
-
}
|
|
2658
|
-
return { workOrder, generation, receipt };
|
|
2659
|
-
}
|
|
2660
|
-
applyTemporalEffect(suppliedContext, input, evidence, now = this.now()) {
|
|
2661
|
-
if (!Number.isSafeInteger(now)) {
|
|
2662
|
-
throw new Error('temporal effect time must be an epoch millisecond integer');
|
|
2663
|
-
}
|
|
2664
|
-
this.validateTemporalEffectInput(input, now);
|
|
2665
|
-
if (!evidence ||
|
|
2666
|
-
typeof evidence.contextPacketId !== 'string' ||
|
|
2667
|
-
evidence.contextPacketId.trim().length < 1 ||
|
|
2668
|
-
evidence.contextPacketId.length > 300 ||
|
|
2669
|
-
!/^[a-f0-9]{64}$/.test(evidence.contextPacketSha256)) {
|
|
2670
|
-
throw new Error('temporal effect requires a valid host evidence attestation');
|
|
2671
|
-
}
|
|
2672
|
-
let receipt = null;
|
|
2673
|
-
const previousGeneration = this.onOwnerTaskChangeCommitted ? this.readGeneration() : null;
|
|
2674
|
-
this.db.exec('BEGIN IMMEDIATE');
|
|
2675
|
-
try {
|
|
2676
|
-
const context = this.assertTemporalWorkContextActive(suppliedContext);
|
|
2677
|
-
if (input.expected_revision !== context.revision) {
|
|
2678
|
-
throw new Error(`temporal effect revision mismatch: expected ${input.expected_revision}, current ${context.revision}`);
|
|
2679
|
-
}
|
|
2680
|
-
const existing = this.db
|
|
2681
|
-
.prepare(`SELECT * FROM operator_tasks WHERE id = ?`)
|
|
2682
|
-
.get(context.taskId);
|
|
2683
|
-
if (!existing || existing.kind !== 'owner' || existing.revision !== context.revision) {
|
|
2684
|
-
throw new Error(`temporal effect owner task no longer matches trusted context`);
|
|
2685
|
-
}
|
|
2686
|
-
const next = {
|
|
2687
|
-
status: existing.status,
|
|
2688
|
-
deadline: existing.deadline,
|
|
2689
|
-
due_at: existing.due_at,
|
|
2690
|
-
deadline_offset_minutes: existing.deadline_offset_minutes,
|
|
2691
|
-
temporal_epoch: existing.temporal_epoch,
|
|
2692
|
-
temporal_reconciled_occurrence_key: existing.temporal_reconciled_occurrence_key,
|
|
2693
|
-
last_temporal_checked_at: now,
|
|
2694
|
-
next_temporal_check_at: null,
|
|
2695
|
-
last_temporal_attempt_id: context.attemptId,
|
|
2696
|
-
review_started_at: existing.review_started_at,
|
|
2697
|
-
review_anchor_event_id: existing.review_anchor_event_id,
|
|
2698
|
-
review_anchor_source_channel: existing.review_anchor_source_channel,
|
|
2699
|
-
};
|
|
2700
|
-
const auditParts = [`reason=${input.reason.trim()}`];
|
|
2701
|
-
if (input.outcome === 'final_no_update') {
|
|
2702
|
-
auditParts.push(`evidence=${input.evidence_summary.trim()}`);
|
|
2703
|
-
}
|
|
2704
|
-
const receiptReason = this.temporalAuditText(`effect-${input.outcome}`, auditParts);
|
|
2705
|
-
let nextCheck = null;
|
|
2706
|
-
let rescheduled = false;
|
|
2707
|
-
if (input.outcome === 'resolved') {
|
|
2708
|
-
if (input.status !== undefined)
|
|
2709
|
-
next.status = input.status;
|
|
2710
|
-
if (existing.status === 'review' && next.status !== 'review') {
|
|
2711
|
-
// Leaving verified review through the Temporal path: the submission
|
|
2712
|
-
// anchor and its derived 14-day due time do not survive the exit, exactly
|
|
2713
|
-
// as the task_update path clears them. A later reopen re-verifies.
|
|
2714
|
-
next.review_started_at = null;
|
|
2715
|
-
next.review_anchor_event_id = null;
|
|
2716
|
-
next.review_anchor_source_channel = null;
|
|
2717
|
-
if (existing.review_started_at !== null &&
|
|
2718
|
-
!Object.prototype.hasOwnProperty.call(input, 'due_at')) {
|
|
2719
|
-
next.due_at = null;
|
|
2720
|
-
next.deadline = null;
|
|
2721
|
-
next.deadline_offset_minutes = null;
|
|
2722
|
-
}
|
|
2723
|
-
}
|
|
2724
|
-
if (Object.prototype.hasOwnProperty.call(input, 'due_at')) {
|
|
2725
|
-
if (input.due_at === null) {
|
|
2726
|
-
next.due_at = null;
|
|
2727
|
-
}
|
|
2728
|
-
else if (input.due_at !== undefined) {
|
|
2729
|
-
const exactDue = (0, task_temporal_js_1.parseExactDueAt)(input.due_at);
|
|
2730
|
-
if (exactDue.dueAt !== existing.due_at) {
|
|
2731
|
-
next.due_at = exactDue.dueAt;
|
|
2732
|
-
next.deadline = exactDue.deadline;
|
|
2733
|
-
next.deadline_offset_minutes = exactDue.offsetMinutes;
|
|
2734
|
-
}
|
|
2735
|
-
}
|
|
2736
|
-
}
|
|
2737
|
-
const resolvedEffectChanged = next.status !== existing.status || next.due_at !== existing.due_at;
|
|
2738
|
-
if (!resolvedEffectChanged) {
|
|
2739
|
-
throw new Error('temporal resolved outcome requires an actual status or due_at change');
|
|
2740
|
-
}
|
|
2741
|
-
rescheduled = next.due_at !== existing.due_at;
|
|
2742
|
-
if (rescheduled)
|
|
2743
|
-
next.temporal_epoch = existing.temporal_epoch + 1;
|
|
2744
|
-
next.temporal_reconciled_occurrence_key = context.occurrenceKey;
|
|
2745
|
-
}
|
|
2746
|
-
else if (input.outcome === 'final_no_update') {
|
|
2747
|
-
next.temporal_reconciled_occurrence_key = context.occurrenceKey;
|
|
2748
|
-
}
|
|
2749
|
-
else {
|
|
2750
|
-
nextCheck = (0, task_temporal_js_1.parseExactDueAt)(input.next_temporal_check_at).dueAt;
|
|
2751
|
-
next.next_temporal_check_at = nextCheck;
|
|
2752
|
-
}
|
|
2753
|
-
const persistedColumns = [
|
|
2754
|
-
'status',
|
|
2755
|
-
'deadline',
|
|
2756
|
-
'due_at',
|
|
2757
|
-
'deadline_offset_minutes',
|
|
2758
|
-
'temporal_epoch',
|
|
2759
|
-
'temporal_reconciled_occurrence_key',
|
|
2760
|
-
'last_temporal_checked_at',
|
|
2761
|
-
'next_temporal_check_at',
|
|
2762
|
-
'last_temporal_attempt_id',
|
|
2763
|
-
'review_started_at',
|
|
2764
|
-
'review_anchor_event_id',
|
|
2765
|
-
'review_anchor_source_channel',
|
|
2766
|
-
];
|
|
2767
|
-
const changedFields = persistedColumns.filter((column) => next[column] !== existing[column]);
|
|
2768
|
-
const afterRevision = existing.revision + 1;
|
|
2769
|
-
const sets = changedFields.map((column) => `${column} = ?`);
|
|
2770
|
-
const values = changedFields.map((column) => next[column]);
|
|
2771
|
-
sets.push('revision = ?', 'updated_at = ?');
|
|
2772
|
-
values.push(afterRevision, now);
|
|
2773
|
-
const ownerUpdate = this.db
|
|
2774
|
-
.prepare(`UPDATE operator_tasks SET ${sets.join(', ')} WHERE id = ? AND kind = 'owner' AND revision = ?`)
|
|
2775
|
-
.run(...values, context.taskId, existing.revision);
|
|
2776
|
-
if (ownerUpdate.changes !== 1) {
|
|
2777
|
-
throw new Error(`temporal effect lost owner task revision for task ${context.taskId}`);
|
|
2778
|
-
}
|
|
2779
|
-
// The same ledger as every other owner-task change. This is the canonical case -
|
|
2780
|
-
// a scheduled run closing a work item - and leaving it out would have meant the
|
|
2781
|
-
// effect ledger could not see the one change type it was built to account for,
|
|
2782
|
-
// with its receipt filed in a second table nobody joins against.
|
|
2783
|
-
//
|
|
2784
|
-
// Unattributed, and correctly so: a temporal check fires because a clock advanced,
|
|
2785
|
-
// not because an event arrived. Its evidence is the context packet, which today
|
|
2786
|
-
// carries recalled memories and zero connector events - measured, not assumed.
|
|
2787
|
-
// Recording it as attributed would name a cause that does not exist.
|
|
2788
|
-
this.recordTaskChange({
|
|
2789
|
-
kind: 'task_update',
|
|
2790
|
-
taskId: context.taskId,
|
|
2791
|
-
origin: { runId: null, causeKind: 'clock' },
|
|
2792
|
-
channel: existing.source_channel,
|
|
2793
|
-
payload: {
|
|
2794
|
-
revision: afterRevision,
|
|
2795
|
-
outcome: input.outcome,
|
|
2796
|
-
changed: Object.fromEntries(changedFields.map((column) => [column, next[column]])),
|
|
2797
|
-
},
|
|
2798
|
-
atMs: now,
|
|
2799
|
-
});
|
|
2800
|
-
if (rescheduled) {
|
|
2801
|
-
this.supersedeTemporalGenerationsInTransaction(context.taskId, Number(next.temporal_epoch), context.generationKey, now);
|
|
2802
|
-
}
|
|
2803
|
-
const generationUpdate = this.db
|
|
2804
|
-
.prepare(`UPDATE operator_temporal_generations
|
|
2805
|
-
SET disposition = ?, reason = ?, updated_at = ?
|
|
2806
|
-
WHERE generation_key = ? AND disposition = 'active' AND last_workorder_id = ?`)
|
|
2807
|
-
.run(input.outcome, receiptReason, now, context.generationKey, context.attemptId);
|
|
2808
|
-
if (generationUpdate.changes !== 1) {
|
|
2809
|
-
throw new Error(`temporal effect lost generation ownership for attempt ${context.attemptId}`);
|
|
2810
|
-
}
|
|
2811
|
-
if (input.outcome === 'resolved' && (next.status === 'done' || next.status === 'cancelled')) {
|
|
2812
|
-
this.supersedeAllActiveTemporalGenerationsInTransaction(context.taskId, now);
|
|
2813
|
-
}
|
|
2814
|
-
if (input.outcome !== 'resolved') {
|
|
2815
|
-
this.insertNoUpdateNote((0, temporal_effect_js_1.temporalNoUpdateScope)(context), receiptReason, now);
|
|
2816
|
-
}
|
|
2817
|
-
this.db
|
|
2818
|
-
.prepare(`INSERT INTO operator_temporal_effects
|
|
2819
|
-
(workorder_attempt_id, task_id, generation_key, occurrence_key, outcome,
|
|
2820
|
-
before_revision, after_revision, changed_fields, reason,
|
|
2821
|
-
attestation_version, context_packet_id, context_packet_sha256,
|
|
2822
|
-
packet_created_at, next_temporal_check_at, created_at)
|
|
2823
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?)`)
|
|
2824
|
-
.run(context.attemptId, context.taskId, context.generationKey, context.occurrenceKey, input.outcome, existing.revision, afterRevision, JSON.stringify(changedFields), receiptReason, evidence.contextPacketId, evidence.contextPacketSha256, evidence.packetCreatedAt, nextCheck, now);
|
|
2825
|
-
const workOrderUpdate = this.db
|
|
2826
|
-
.prepare(`UPDATE operator_tasks SET status = 'done', latest_event = ?, updated_at = ?
|
|
2827
|
-
WHERE id = ? AND kind = 'system' AND status = 'in_progress'
|
|
2828
|
-
AND source_channel = 'workorder:temporal' AND source_event_id = ?`)
|
|
2829
|
-
.run(receiptReason, now, context.attemptId, context.generationKey);
|
|
2830
|
-
if (workOrderUpdate.changes !== 1) {
|
|
2831
|
-
throw new Error(`temporal effect lost active workorder ${context.attemptId}`);
|
|
2832
|
-
}
|
|
2833
|
-
receipt = this.getTemporalEffect(context.attemptId);
|
|
2834
|
-
if (!receipt)
|
|
2835
|
-
throw new Error(`temporal effect receipt could not be read back`);
|
|
2836
|
-
const receiptError = (0, temporal_effect_js_1.temporalReceiptInvariantError)(receipt, {
|
|
2837
|
-
attemptId: context.attemptId,
|
|
2838
|
-
taskId: context.taskId,
|
|
2839
|
-
generationKey: context.generationKey,
|
|
2840
|
-
occurrenceKey: context.occurrenceKey,
|
|
2841
|
-
beforeRevision: context.revision,
|
|
2842
|
-
});
|
|
2843
|
-
if (receiptError) {
|
|
2844
|
-
throw new Error(`temporal effect receipt invariant failed: ${receiptError}`);
|
|
2845
|
-
}
|
|
2846
|
-
this.db.exec('COMMIT');
|
|
2847
|
-
}
|
|
2848
|
-
catch (error) {
|
|
2849
|
-
this.db.exec('ROLLBACK');
|
|
2850
|
-
throw error;
|
|
2851
|
-
}
|
|
2852
|
-
this.notifyOwnerTaskCommit(previousGeneration);
|
|
2853
|
-
if (!receipt)
|
|
2854
|
-
throw new Error(`temporal effect produced no receipt`);
|
|
2855
|
-
return receipt;
|
|
2856
|
-
}
|
|
2857
|
-
requeueTemporalWorkOrder(attemptId, reason) {
|
|
2858
|
-
this.assertTemporalReason(reason);
|
|
2859
|
-
const auditReason = this.temporalAuditText('worker-failure', [`failure=${reason}`]);
|
|
2860
|
-
let replacement = null;
|
|
2861
|
-
this.db.exec('BEGIN IMMEDIATE');
|
|
2862
|
-
try {
|
|
2863
|
-
const context = this.loadTemporalWorkContextInternal(attemptId);
|
|
2864
|
-
const workOrder = this.getWorkOrderById(attemptId);
|
|
2865
|
-
if (workOrder.payload.attempts >= exports.TEMPORAL_WORKORDER_MAX_ATTEMPTS) {
|
|
2866
|
-
throw new Error(`temporal retry budget exhausted for attempt ${attemptId}`);
|
|
2867
|
-
}
|
|
2868
|
-
replacement = this.requeueTemporalWorkOrderInTransaction(context, workOrder, auditReason);
|
|
2869
|
-
this.db.exec('COMMIT');
|
|
2870
|
-
}
|
|
2871
|
-
catch (error) {
|
|
2872
|
-
this.db.exec('ROLLBACK');
|
|
2873
|
-
throw error;
|
|
2874
|
-
}
|
|
2875
|
-
if (!replacement)
|
|
2876
|
-
throw new Error(`temporal retry for attempt ${attemptId} produced no row`);
|
|
2877
|
-
return replacement;
|
|
2878
|
-
}
|
|
2879
|
-
exhaustTemporalWorkOrder(attemptId, reason) {
|
|
2880
|
-
this.assertTemporalReason(reason);
|
|
2881
|
-
const auditReason = this.temporalAuditText('worker-failure', [`failure=${reason}`]);
|
|
2882
|
-
this.db.exec('BEGIN IMMEDIATE');
|
|
2883
|
-
try {
|
|
2884
|
-
const context = this.loadTemporalWorkContextInternal(attemptId);
|
|
2885
|
-
const workOrder = this.getWorkOrderById(attemptId);
|
|
2886
|
-
if (workOrder.payload.attempts !== exports.TEMPORAL_WORKORDER_MAX_ATTEMPTS) {
|
|
2887
|
-
throw new Error(`temporal exhaustion requires attempt ${exports.TEMPORAL_WORKORDER_MAX_ATTEMPTS}, got ${workOrder.payload.attempts}`);
|
|
2888
|
-
}
|
|
2889
|
-
this.exhaustTemporalWorkOrderInTransaction(context, auditReason);
|
|
2890
|
-
this.db.exec('COMMIT');
|
|
2891
|
-
}
|
|
2892
|
-
catch (error) {
|
|
2893
|
-
this.db.exec('ROLLBACK');
|
|
2894
|
-
throw error;
|
|
2895
|
-
}
|
|
2896
|
-
}
|
|
2897
|
-
failTemporalWorkOrder(attemptId, reason, allowRetry = true) {
|
|
2898
|
-
this.assertTemporalReason(reason);
|
|
2899
|
-
const auditReason = this.temporalAuditText('worker-failure', [`failure=${reason}`]);
|
|
2900
|
-
let result = null;
|
|
2901
|
-
this.db.exec('BEGIN IMMEDIATE');
|
|
2902
|
-
try {
|
|
2903
|
-
const repairedAttempt = this.repairClosedTemporalOwnershipInTransaction(attemptId);
|
|
2904
|
-
if (repairedAttempt !== null) {
|
|
2905
|
-
result = {
|
|
2906
|
-
disposition: 'superseded',
|
|
2907
|
-
attempt: repairedAttempt,
|
|
2908
|
-
maxAttempts: exports.TEMPORAL_WORKORDER_MAX_ATTEMPTS,
|
|
2909
|
-
};
|
|
2910
|
-
this.db.exec('COMMIT');
|
|
2911
|
-
return result;
|
|
2912
|
-
}
|
|
2913
|
-
const context = this.loadTemporalWorkContextInternal(attemptId);
|
|
2914
|
-
const workOrder = this.getWorkOrderById(attemptId);
|
|
2915
|
-
const attempt = workOrder.payload.attempts;
|
|
2916
|
-
if (!allowRetry) {
|
|
2917
|
-
this.exhaustTemporalWorkOrderInTransaction(context, auditReason);
|
|
2918
|
-
result = {
|
|
2919
|
-
disposition: 'exhausted',
|
|
2920
|
-
attempt,
|
|
2921
|
-
maxAttempts: exports.TEMPORAL_WORKORDER_MAX_ATTEMPTS,
|
|
2922
|
-
retrySuppressed: true,
|
|
2923
|
-
};
|
|
2924
|
-
}
|
|
2925
|
-
else if (attempt < exports.TEMPORAL_WORKORDER_MAX_ATTEMPTS) {
|
|
2926
|
-
result = {
|
|
2927
|
-
disposition: 'requeued',
|
|
2928
|
-
replacement: this.requeueTemporalWorkOrderInTransaction(context, workOrder, auditReason),
|
|
2929
|
-
attempt,
|
|
2930
|
-
maxAttempts: exports.TEMPORAL_WORKORDER_MAX_ATTEMPTS,
|
|
2931
|
-
};
|
|
2932
|
-
}
|
|
2933
|
-
else if (attempt === exports.TEMPORAL_WORKORDER_MAX_ATTEMPTS) {
|
|
2934
|
-
this.exhaustTemporalWorkOrderInTransaction(context, auditReason);
|
|
2935
|
-
result = {
|
|
2936
|
-
disposition: 'exhausted',
|
|
2937
|
-
attempt,
|
|
2938
|
-
maxAttempts: exports.TEMPORAL_WORKORDER_MAX_ATTEMPTS,
|
|
2939
|
-
};
|
|
2940
|
-
}
|
|
2941
|
-
else {
|
|
2942
|
-
throw new Error(`temporal attempt ${attemptId} exceeds retry budget: ${attempt}`);
|
|
2943
|
-
}
|
|
2944
|
-
this.db.exec('COMMIT');
|
|
2945
|
-
}
|
|
2946
|
-
catch (error) {
|
|
2947
|
-
this.db.exec('ROLLBACK');
|
|
2948
|
-
throw error;
|
|
2949
|
-
}
|
|
2950
|
-
if (!result)
|
|
2951
|
-
throw new Error(`temporal failure for attempt ${attemptId} produced no result`);
|
|
2952
|
-
return result;
|
|
2953
|
-
}
|
|
2954
|
-
repairClosedTemporalOwnershipInTransaction(attemptId) {
|
|
2955
|
-
const workOrder = this.getWorkOrderById(attemptId);
|
|
2956
|
-
if (!workOrder || workOrder.workKind !== 'temporal') {
|
|
2957
|
-
return null;
|
|
2958
|
-
}
|
|
2959
|
-
const generationKey = this.requirePayloadString(workOrder.payload, 'generationKey', attemptId);
|
|
2960
|
-
const generation = this.getTemporalGeneration(generationKey);
|
|
2961
|
-
if (!generation || generation.lastWorkOrderId !== attemptId) {
|
|
2962
|
-
return null;
|
|
2963
|
-
}
|
|
2964
|
-
const task = this.getById(generation.taskId);
|
|
2965
|
-
if (!task || (task.status !== 'done' && task.status !== 'cancelled')) {
|
|
2966
|
-
return null;
|
|
2967
|
-
}
|
|
2968
|
-
const openAttempt = workOrder.status === 'pending' || workOrder.status === 'in_progress';
|
|
2969
|
-
if (generation.disposition === 'active' && openAttempt) {
|
|
2970
|
-
this.supersedeAllActiveTemporalGenerationsInTransaction(task.id);
|
|
2971
|
-
}
|
|
2972
|
-
else if (generation.disposition !== 'superseded' || workOrder.status !== 'cancelled') {
|
|
2973
|
-
return null;
|
|
2974
|
-
}
|
|
2975
|
-
return workOrder.payload.attempts;
|
|
2976
|
-
}
|
|
2977
|
-
/** Repair pre-fix databases where a terminal owner still has active temporal ownership. */
|
|
2978
|
-
repairClosedTemporalGenerations() {
|
|
2979
|
-
let repaired = 0;
|
|
2980
|
-
this.db.exec('BEGIN IMMEDIATE');
|
|
2981
|
-
try {
|
|
2982
|
-
const rows = this.db
|
|
2983
|
-
.prepare(`SELECT DISTINCT generation.task_id
|
|
2984
|
-
FROM operator_temporal_generations generation
|
|
2985
|
-
JOIN operator_tasks owner ON owner.id = generation.task_id
|
|
2986
|
-
WHERE generation.disposition = 'active'
|
|
2987
|
-
AND owner.kind = 'owner'
|
|
2988
|
-
AND owner.status IN ('done','cancelled')
|
|
2989
|
-
ORDER BY generation.task_id ASC`)
|
|
2990
|
-
.all();
|
|
2991
|
-
const updatedAt = this.now();
|
|
2992
|
-
for (const row of rows) {
|
|
2993
|
-
this.supersedeAllActiveTemporalGenerationsInTransaction(row.task_id, updatedAt);
|
|
2994
|
-
}
|
|
2995
|
-
repaired = rows.length;
|
|
2996
|
-
this.db.exec('COMMIT');
|
|
2997
|
-
}
|
|
2998
|
-
catch (error) {
|
|
2999
|
-
this.db.exec('ROLLBACK');
|
|
3000
|
-
throw error;
|
|
3001
|
-
}
|
|
3002
|
-
return repaired;
|
|
3003
|
-
}
|
|
3004
|
-
/** Control-plane pause: cancel open attempts but keep generations resumable. */
|
|
3005
|
-
pauseActiveTemporalWork(reason) {
|
|
3006
|
-
this.assertTemporalReason(reason);
|
|
3007
|
-
let changed = 0;
|
|
3008
|
-
this.db.exec('BEGIN IMMEDIATE');
|
|
3009
|
-
try {
|
|
3010
|
-
const result = this.db
|
|
3011
|
-
.prepare(`UPDATE operator_tasks
|
|
3012
|
-
SET status = 'cancelled', latest_event = ?, updated_at = ?
|
|
3013
|
-
WHERE kind = 'system' AND source_channel = 'workorder:temporal'
|
|
3014
|
-
AND status IN ('pending','in_progress')`)
|
|
3015
|
-
.run(reason, this.now());
|
|
3016
|
-
changed = result.changes;
|
|
3017
|
-
this.db.exec('COMMIT');
|
|
3018
|
-
}
|
|
3019
|
-
catch (error) {
|
|
3020
|
-
this.db.exec('ROLLBACK');
|
|
3021
|
-
throw error;
|
|
3022
|
-
}
|
|
3023
|
-
return changed;
|
|
3024
|
-
}
|
|
3025
|
-
/** Resume paused active generations without spending another model attempt. */
|
|
3026
|
-
resumePausedTemporalWork() {
|
|
3027
|
-
const resumed = [];
|
|
3028
|
-
this.db.exec('BEGIN IMMEDIATE');
|
|
3029
|
-
try {
|
|
3030
|
-
const rows = this.db
|
|
3031
|
-
.prepare(`SELECT generation_key, last_workorder_id
|
|
3032
|
-
FROM operator_temporal_generations generation
|
|
3033
|
-
WHERE disposition = 'active' AND last_workorder_id IS NOT NULL
|
|
3034
|
-
AND NOT EXISTS (
|
|
3035
|
-
SELECT 1 FROM operator_tasks open_attempt
|
|
3036
|
-
WHERE open_attempt.kind = 'system'
|
|
3037
|
-
AND open_attempt.source_channel = 'workorder:temporal'
|
|
3038
|
-
AND open_attempt.source_event_id = generation.generation_key
|
|
3039
|
-
AND open_attempt.status IN ('pending','in_progress')
|
|
3040
|
-
)
|
|
3041
|
-
ORDER BY generation_key ASC`)
|
|
3042
|
-
.all();
|
|
3043
|
-
for (const row of rows) {
|
|
3044
|
-
const generation = this.getTemporalGeneration(row.generation_key);
|
|
3045
|
-
const previous = this.getWorkOrderById(row.last_workorder_id);
|
|
3046
|
-
if (!generation || !previous || previous.status !== 'cancelled') {
|
|
3047
|
-
throw new Error(`temporal resume: active generation '${row.generation_key}' has no paused attempt`);
|
|
3048
|
-
}
|
|
3049
|
-
this.assertTemporalPayloadMatches(previous, generation);
|
|
3050
|
-
const { attempts, ...payload } = previous.payload;
|
|
3051
|
-
const replacement = this.insertWorkOrder({
|
|
3052
|
-
workKind: 'temporal',
|
|
3053
|
-
idempotencyKey: generation.generationKey,
|
|
3054
|
-
input: payload,
|
|
3055
|
-
priority: previous.priority,
|
|
3056
|
-
}, attempts);
|
|
3057
|
-
const ownership = this.db
|
|
3058
|
-
.prepare(`UPDATE operator_temporal_generations
|
|
3059
|
-
SET last_workorder_id = ?, updated_at = ?
|
|
3060
|
-
WHERE generation_key = ? AND disposition = 'active' AND last_workorder_id = ?`)
|
|
3061
|
-
.run(replacement.id, this.now(), generation.generationKey, previous.id);
|
|
3062
|
-
if (ownership.changes !== 1) {
|
|
3063
|
-
throw new Error(`temporal resume lost ownership for generation '${generation.generationKey}'`);
|
|
3064
|
-
}
|
|
3065
|
-
resumed.push(replacement);
|
|
3066
|
-
}
|
|
3067
|
-
this.db.exec('COMMIT');
|
|
3068
|
-
}
|
|
3069
|
-
catch (error) {
|
|
3070
|
-
this.db.exec('ROLLBACK');
|
|
3071
|
-
throw error;
|
|
3072
|
-
}
|
|
3073
|
-
return resumed;
|
|
3074
|
-
}
|
|
3075
|
-
requeueTemporalWorkOrderInTransaction(context, workOrder, reason) {
|
|
3076
|
-
this.transitionWorkOrder(workOrder.id, 'failed', reason);
|
|
3077
|
-
const payload = publisherPayloadOfStoredWorkOrder(workOrder.payload);
|
|
3078
|
-
const replacement = this.insertWorkOrder({
|
|
3079
|
-
workKind: 'temporal',
|
|
3080
|
-
idempotencyKey: context.generationKey,
|
|
3081
|
-
input: payload,
|
|
3082
|
-
priority: workOrder.priority,
|
|
3083
|
-
}, workOrder.payload.attempts + 1);
|
|
3084
|
-
const ownership = this.db
|
|
3085
|
-
.prepare(`UPDATE operator_temporal_generations
|
|
3086
|
-
SET last_workorder_id = ?, updated_at = ?
|
|
3087
|
-
WHERE generation_key = ? AND disposition = 'active' AND last_workorder_id = ?`)
|
|
3088
|
-
.run(replacement.id, this.now(), context.generationKey, workOrder.id);
|
|
3089
|
-
if (ownership.changes !== 1) {
|
|
3090
|
-
throw new Error(`temporal retry lost generation ownership for attempt ${workOrder.id}`);
|
|
3091
|
-
}
|
|
3092
|
-
return replacement;
|
|
3093
|
-
}
|
|
3094
|
-
exhaustTemporalWorkOrderInTransaction(context, reason) {
|
|
3095
|
-
this.transitionWorkOrder(context.attemptId, 'failed', reason);
|
|
3096
|
-
const update = this.db
|
|
3097
|
-
.prepare(`UPDATE operator_temporal_generations
|
|
3098
|
-
SET disposition = 'exhausted', reason = ?, updated_at = ?
|
|
3099
|
-
WHERE generation_key = ? AND disposition = 'active' AND last_workorder_id = ?`)
|
|
3100
|
-
.run(reason, this.now(), context.generationKey, context.attemptId);
|
|
3101
|
-
if (update.changes !== 1) {
|
|
3102
|
-
throw new Error(`temporal exhaustion lost ownership for attempt ${context.attemptId}`);
|
|
3103
|
-
}
|
|
3104
|
-
}
|
|
3105
|
-
supersedeTemporalGenerations(taskId, currentEpoch, excludeGenerationKey) {
|
|
3106
|
-
this.db.exec('BEGIN IMMEDIATE');
|
|
3107
|
-
try {
|
|
3108
|
-
this.supersedeTemporalGenerationsInTransaction(taskId, currentEpoch, excludeGenerationKey);
|
|
3109
|
-
this.db.exec('COMMIT');
|
|
3110
|
-
}
|
|
3111
|
-
catch (error) {
|
|
3112
|
-
this.db.exec('ROLLBACK');
|
|
3113
|
-
throw error;
|
|
3114
|
-
}
|
|
3115
|
-
}
|
|
3116
|
-
validateTemporalGenerationInput(input) {
|
|
3117
|
-
const bounded = (value, field, max) => {
|
|
3118
|
-
if (value.length < 1 || value.length > max) {
|
|
3119
|
-
throw new Error(`temporal generation: ${field} must contain 1-${max} characters`);
|
|
3120
|
-
}
|
|
3121
|
-
};
|
|
3122
|
-
bounded(input.generationKey, 'generationKey', 500);
|
|
3123
|
-
bounded(input.occurrenceKey, 'occurrenceKey', 300);
|
|
3124
|
-
if (!Number.isSafeInteger(input.taskId) || input.taskId < 1) {
|
|
3125
|
-
throw new Error(`temporal generation: taskId must be a positive integer`);
|
|
3126
|
-
}
|
|
3127
|
-
if (!Number.isSafeInteger(input.temporalEpoch) || input.temporalEpoch < 0) {
|
|
3128
|
-
throw new Error(`temporal generation: temporalEpoch must be a non-negative integer`);
|
|
3129
|
-
}
|
|
3130
|
-
if (!Number.isSafeInteger(input.checkAt)) {
|
|
3131
|
-
throw new Error(`temporal generation: checkAt must be an epoch millisecond integer`);
|
|
3132
|
-
}
|
|
3133
|
-
const canonicalKey = (0, task_temporal_js_1.temporalGenerationKey)(input.taskId, input.occurrenceKey, input.checkAt);
|
|
3134
|
-
if (input.generationKey !== canonicalKey) {
|
|
3135
|
-
throw new Error(`temporal generation: generationKey must equal canonical identity key`);
|
|
3136
|
-
}
|
|
3137
|
-
for (const [field, value] of [
|
|
3138
|
-
['sourceChannel', input.sourceChannel],
|
|
3139
|
-
['sourceEventId', input.sourceEventId],
|
|
3140
|
-
]) {
|
|
3141
|
-
if (value !== null && typeof value !== 'string') {
|
|
3142
|
-
throw new Error(`temporal generation: ${field} must be a string or null`);
|
|
3143
|
-
}
|
|
3144
|
-
}
|
|
3145
|
-
if (input.priority !== undefined)
|
|
3146
|
-
assertEnum(input.priority, exports.TASK_PRIORITIES, 'priority');
|
|
3147
|
-
}
|
|
3148
|
-
/**
|
|
3149
|
-
* Workorder payloads carry only bounded references to owner-source identifiers.
|
|
3150
|
-
* Owner rows created by older releases may contain empty or arbitrarily long
|
|
3151
|
-
* connector identifiers; hashing preserves exact identity without making one
|
|
3152
|
-
* legacy row able to abort temporal boot or inflate every retry payload.
|
|
3153
|
-
*/
|
|
3154
|
-
temporalSourceIdentifierRef(value) {
|
|
3155
|
-
if (value === null)
|
|
3156
|
-
return null;
|
|
3157
|
-
return `sha256:${(0, node_crypto_1.createHash)('sha256').update(value).digest('hex')}`;
|
|
3158
|
-
}
|
|
3159
|
-
validateTemporalEffectInput(input, now) {
|
|
3160
|
-
if (!input || typeof input !== 'object' || Array.isArray(input)) {
|
|
3161
|
-
throw new Error('temporal effect input must be an object');
|
|
3162
|
-
}
|
|
3163
|
-
if (!Number.isSafeInteger(input.expected_revision) || input.expected_revision < 0) {
|
|
3164
|
-
throw new Error('temporal effect expected_revision must be a non-negative integer');
|
|
3165
|
-
}
|
|
3166
|
-
this.assertTemporalReason(input.reason);
|
|
3167
|
-
const baseKeys = ['expected_revision', 'outcome', 'reason'];
|
|
3168
|
-
let allowedKeys;
|
|
3169
|
-
if (input.outcome === 'resolved') {
|
|
3170
|
-
allowedKeys = [...baseKeys, 'status', 'due_at'];
|
|
3171
|
-
if (input.status !== undefined) {
|
|
3172
|
-
assertEnum(input.status, exports.TASK_STATUSES, 'temporal effect status');
|
|
3173
|
-
if (String(input.status) === 'failed') {
|
|
3174
|
-
throw new Error(`temporal effect: 'failed' is a system-only status`);
|
|
3175
|
-
}
|
|
3176
|
-
}
|
|
3177
|
-
if (input.due_at !== undefined && input.due_at !== null)
|
|
3178
|
-
(0, task_temporal_js_1.parseExactDueAt)(input.due_at);
|
|
3179
|
-
}
|
|
3180
|
-
else if (input.outcome === 'final_no_update') {
|
|
3181
|
-
allowedKeys = [...baseKeys, 'evidence_summary'];
|
|
3182
|
-
if (typeof input.evidence_summary !== 'string' ||
|
|
3183
|
-
input.evidence_summary.trim().length < 1 ||
|
|
3184
|
-
input.evidence_summary.length > 1_000) {
|
|
3185
|
-
throw new Error('temporal effect evidence_summary must contain 1-1000 characters');
|
|
3186
|
-
}
|
|
3187
|
-
}
|
|
3188
|
-
else if (input.outcome === 'deferred') {
|
|
3189
|
-
allowedKeys = [...baseKeys, 'next_temporal_check_at'];
|
|
3190
|
-
if (typeof input.next_temporal_check_at !== 'string') {
|
|
3191
|
-
throw new Error('temporal effect next_temporal_check_at must be RFC 3339');
|
|
3192
|
-
}
|
|
3193
|
-
const nextCheck = (0, task_temporal_js_1.parseExactDueAt)(input.next_temporal_check_at).dueAt;
|
|
3194
|
-
if (nextCheck <= now) {
|
|
3195
|
-
throw new Error('temporal effect deferred check must be strictly in the future');
|
|
3196
|
-
}
|
|
3197
|
-
}
|
|
3198
|
-
else {
|
|
3199
|
-
throw new Error(`temporal effect outcome is unknown or forbidden`);
|
|
3200
|
-
}
|
|
3201
|
-
const unknownKeys = Object.keys(input).filter((key) => !allowedKeys.includes(key));
|
|
3202
|
-
if (unknownKeys.length > 0) {
|
|
3203
|
-
throw new Error(`temporal effect contains unknown or forbidden fields: ${unknownKeys.join(', ')}`);
|
|
3204
|
-
}
|
|
3205
|
-
}
|
|
3206
|
-
assertTemporalContextMatches(supplied, trusted) {
|
|
3207
|
-
const fields = [
|
|
3208
|
-
'attemptId',
|
|
3209
|
-
'generationKey',
|
|
3210
|
-
'taskId',
|
|
3211
|
-
'temporalEpoch',
|
|
3212
|
-
'occurrenceKey',
|
|
3213
|
-
'checkAt',
|
|
3214
|
-
'revision',
|
|
3215
|
-
'sourceChannel',
|
|
3216
|
-
'sourceEventId',
|
|
3217
|
-
];
|
|
3218
|
-
if (fields.some((field) => supplied[field] !== trusted[field])) {
|
|
3219
|
-
throw new Error('temporal effect supplied context does not match trusted host context');
|
|
3220
|
-
}
|
|
3221
|
-
}
|
|
3222
|
-
loadTemporalWorkContextInternal(attemptId) {
|
|
3223
|
-
const workOrder = this.getWorkOrderById(attemptId);
|
|
3224
|
-
if (!workOrder || workOrder.workKind !== 'temporal') {
|
|
3225
|
-
throw new Error(`temporal context: no temporal attempt ${attemptId}`);
|
|
3226
|
-
}
|
|
3227
|
-
if (workOrder.status !== 'in_progress') {
|
|
3228
|
-
throw new Error(`temporal context: attempt ${attemptId} is '${workOrder.status}', expected active attempt`);
|
|
3229
|
-
}
|
|
3230
|
-
const payload = workOrder.payload;
|
|
3231
|
-
const generationKey = this.requirePayloadString(payload, 'generationKey', attemptId);
|
|
3232
|
-
const taskId = this.requirePayloadInteger(payload, 'taskId', attemptId, 1);
|
|
3233
|
-
const temporalEpoch = this.requirePayloadInteger(payload, 'temporalEpoch', attemptId, 0);
|
|
3234
|
-
const occurrenceKey = this.requirePayloadString(payload, 'occurrenceKey', attemptId);
|
|
3235
|
-
const checkAt = this.requirePayloadInteger(payload, 'checkAt', attemptId);
|
|
3236
|
-
const sourceChannel = this.requireNullablePayloadString(payload, 'sourceChannel', attemptId);
|
|
3237
|
-
const sourceEventId = this.requireNullablePayloadString(payload, 'sourceEventId', attemptId);
|
|
3238
|
-
const generation = this.getTemporalGeneration(generationKey);
|
|
3239
|
-
if (!generation || generation.disposition !== 'active') {
|
|
3240
|
-
throw new Error(`temporal context: generation '${generationKey}' is not active`);
|
|
3241
|
-
}
|
|
3242
|
-
if (generation.lastWorkOrderId !== attemptId) {
|
|
3243
|
-
throw new Error(`temporal context: attempt ${attemptId} no longer owns generation`);
|
|
3244
|
-
}
|
|
3245
|
-
this.assertTemporalPayloadMatches(workOrder, generation);
|
|
3246
|
-
const task = this.getRowById(taskId);
|
|
3247
|
-
if (!task || task.kind !== 'owner') {
|
|
3248
|
-
throw new Error(`temporal context: owner task ${taskId} does not exist`);
|
|
3249
|
-
}
|
|
3250
|
-
if (task.status === 'done' || task.status === 'cancelled') {
|
|
3251
|
-
throw new Error(`temporal context: owner task ${taskId} is closed`);
|
|
3252
|
-
}
|
|
3253
|
-
if (task.status === 'review' &&
|
|
3254
|
-
(task.reviewStartedAt === null ||
|
|
3255
|
-
!Number.isSafeInteger(task.reviewStartedAt) ||
|
|
3256
|
-
typeof task.reviewAnchorEventId !== 'string' ||
|
|
3257
|
-
task.reviewAnchorEventId.trim().length === 0)) {
|
|
3258
|
-
throw new Error(`temporal context: review task ${taskId} has no verified review anchor`);
|
|
3259
|
-
}
|
|
3260
|
-
if (task.temporalEpoch !== temporalEpoch ||
|
|
3261
|
-
(0, task_temporal_js_1.occurrenceKeyForTask)(task) !== occurrenceKey ||
|
|
3262
|
-
this.temporalSourceIdentifierRef(task.status === 'review'
|
|
3263
|
-
? (task.reviewAnchorSourceChannel ?? task.sourceChannel)
|
|
3264
|
-
: task.sourceChannel) !== sourceChannel ||
|
|
3265
|
-
this.temporalSourceIdentifierRef(task.status === 'review' && task.reviewAnchorEventId
|
|
3266
|
-
? task.reviewAnchorEventId
|
|
3267
|
-
: task.sourceEventId) !== sourceEventId ||
|
|
3268
|
-
generation.taskId !== taskId ||
|
|
3269
|
-
generation.temporalEpoch !== temporalEpoch ||
|
|
3270
|
-
generation.occurrenceKey !== occurrenceKey ||
|
|
3271
|
-
generation.checkAt !== checkAt) {
|
|
3272
|
-
throw new Error(`temporal context: attempt ${attemptId} payload no longer matches ownership`);
|
|
3273
|
-
}
|
|
3274
|
-
return {
|
|
3275
|
-
attemptId,
|
|
3276
|
-
generationKey,
|
|
3277
|
-
taskId,
|
|
3278
|
-
temporalEpoch,
|
|
3279
|
-
occurrenceKey,
|
|
3280
|
-
checkAt,
|
|
3281
|
-
revision: task.revision,
|
|
3282
|
-
sourceChannel,
|
|
3283
|
-
sourceEventId,
|
|
3284
|
-
};
|
|
3285
|
-
}
|
|
3286
|
-
assertTemporalPayloadMatches(workOrder, generation) {
|
|
3287
|
-
const payload = workOrder.payload;
|
|
3288
|
-
if (payload.generationKey !== generation.generationKey ||
|
|
3289
|
-
payload.taskId !== generation.taskId ||
|
|
3290
|
-
payload.temporalEpoch !== generation.temporalEpoch ||
|
|
3291
|
-
payload.occurrenceKey !== generation.occurrenceKey ||
|
|
3292
|
-
payload.checkAt !== generation.checkAt) {
|
|
3293
|
-
throw new Error(`temporal attempt ${workOrder.id} payload does not match generation ownership`);
|
|
3294
|
-
}
|
|
3295
|
-
}
|
|
3296
|
-
requirePayloadString(payload, field, attemptId) {
|
|
3297
|
-
const value = payload[field];
|
|
3298
|
-
if (typeof value !== 'string' || value.length === 0) {
|
|
3299
|
-
throw new Error(`temporal attempt ${attemptId}: ${field} must be a non-empty string`);
|
|
3300
|
-
}
|
|
3301
|
-
return value;
|
|
3302
|
-
}
|
|
3303
|
-
requireNullablePayloadString(payload, field, attemptId) {
|
|
3304
|
-
const value = payload[field];
|
|
3305
|
-
if (value !== null && typeof value !== 'string') {
|
|
3306
|
-
throw new Error(`temporal attempt ${attemptId}: ${field} must be a string or null`);
|
|
3307
|
-
}
|
|
3308
|
-
return value;
|
|
3309
|
-
}
|
|
3310
|
-
requirePayloadInteger(payload, field, attemptId, minimum = Number.MIN_SAFE_INTEGER) {
|
|
3311
|
-
const value = payload[field];
|
|
3312
|
-
if (!Number.isSafeInteger(value) || value < minimum) {
|
|
3313
|
-
throw new Error(`temporal attempt ${attemptId}: ${field} must be an integer`);
|
|
3314
|
-
}
|
|
3315
|
-
return value;
|
|
3316
|
-
}
|
|
3317
|
-
assertTemporalReason(reason) {
|
|
3318
|
-
if (reason.trim().length < 1 || reason.length > 500) {
|
|
3319
|
-
throw new Error('temporal workorder reason must contain 1-500 characters');
|
|
3320
|
-
}
|
|
3321
|
-
}
|
|
3322
|
-
temporalAuditText(kind, values) {
|
|
3323
|
-
const refs = values.map((value, index) => {
|
|
3324
|
-
const separator = value.indexOf('=');
|
|
3325
|
-
const label = separator > 0 ? value.slice(0, separator) : `value${index + 1}`;
|
|
3326
|
-
const text = separator > 0 ? value.slice(separator + 1) : value;
|
|
3327
|
-
const digest = (0, node_crypto_1.createHash)('sha256').update(text).digest('hex');
|
|
3328
|
-
return `${label}_sha256=${digest};${label}_length=${text.length}`;
|
|
3329
|
-
});
|
|
3330
|
-
return `temporal-${kind};${refs.join(';')}`;
|
|
3331
|
-
}
|
|
3332
|
-
/**
|
|
3333
|
-
* Claim the next pending workorder: priority high>normal>low, then id ASC
|
|
3334
|
-
* (plan D2/E2 - CASE mapping, never lexicographic on the TEXT enum).
|
|
3335
|
-
* pending -> in_progress. Single serial consumer; transaction for atomicity.
|
|
3336
|
-
*/
|
|
3337
|
-
claimNextWorkOrder() {
|
|
3338
|
-
let claimedId = null;
|
|
3339
|
-
this.db.exec('BEGIN IMMEDIATE');
|
|
3340
|
-
try {
|
|
3341
|
-
const claimAt = this.now();
|
|
3342
|
-
const row = this.db
|
|
3343
|
-
.prepare(`SELECT * FROM operator_tasks
|
|
3344
|
-
WHERE kind = 'system' AND status = 'pending'
|
|
3345
|
-
AND source_channel IN (${KNOWN_WORKORDER_CHANNELS_SQL})
|
|
3346
|
-
AND (due_at IS NULL OR due_at <= ?)
|
|
3347
|
-
ORDER BY CASE priority WHEN 'high' THEN 0 WHEN 'normal' THEN 1 ELSE 2 END ASC,
|
|
3348
|
-
id ASC
|
|
3349
|
-
LIMIT 1`)
|
|
3350
|
-
.get(...KNOWN_WORKORDER_CHANNELS, claimAt);
|
|
3351
|
-
if (row) {
|
|
3352
|
-
this.db
|
|
3353
|
-
.prepare(`UPDATE operator_tasks SET status = 'in_progress', updated_at = ? WHERE id = ?`)
|
|
3354
|
-
.run(claimAt, row.id);
|
|
3355
|
-
claimedId = row.id;
|
|
3356
|
-
}
|
|
3357
|
-
this.db.exec('COMMIT');
|
|
3358
|
-
}
|
|
3359
|
-
catch (error) {
|
|
3360
|
-
this.db.exec('ROLLBACK');
|
|
3361
|
-
throw error;
|
|
3362
|
-
}
|
|
3363
|
-
// Read-back OUTSIDE the try: a throw here (corrupt payload) must not
|
|
3364
|
-
// trigger ROLLBACK after COMMIT (PR bot round - masked error class).
|
|
3365
|
-
return claimedId === null ? null : this.getWorkOrderById(claimedId);
|
|
3366
|
-
}
|
|
3367
|
-
/**
|
|
3368
|
-
* Atomic fail-and-requeue (PR bot round): the failure mark and the
|
|
3369
|
-
* replacement row commit together - a crash between the two would lose
|
|
3370
|
-
* the retry (the old row terminal, the new one never inserted). The
|
|
3371
|
-
* replacement can only be inserted AFTER the old row leaves the partial
|
|
3372
|
-
* unique index, hence one transaction, not two calls.
|
|
3373
|
-
*/
|
|
3374
|
-
requeueWorkOrder(wo, reason) {
|
|
3375
|
-
if (wo.workKind === 'temporal') {
|
|
3376
|
-
return this.requeueTemporalWorkOrder(wo.id, reason);
|
|
3377
|
-
}
|
|
3378
|
-
this.db.exec('BEGIN IMMEDIATE');
|
|
3379
|
-
try {
|
|
3380
|
-
const current = this.getWorkOrderById(wo.id);
|
|
3381
|
-
if (!current || current.workKind !== wo.workKind) {
|
|
3382
|
-
throw new Error(`workorder retry: claimed row ${wo.id} no longer matches input`);
|
|
3383
|
-
}
|
|
3384
|
-
this.transitionWorkOrder(current.id, 'failed', reason);
|
|
3385
|
-
const input = publisherPayloadOfStoredWorkOrder(current.payload);
|
|
3386
|
-
const replacement = this.insertWorkOrder({
|
|
3387
|
-
workKind: current.workKind,
|
|
3388
|
-
idempotencyKey: current.idempotencyKey,
|
|
3389
|
-
input,
|
|
3390
|
-
priority: current.priority,
|
|
3391
|
-
}, current.payload.attempts + 1);
|
|
3392
|
-
this.db.exec('COMMIT');
|
|
3393
|
-
return replacement;
|
|
3394
|
-
}
|
|
3395
|
-
catch (error) {
|
|
3396
|
-
this.db.exec('ROLLBACK');
|
|
3397
|
-
throw error;
|
|
3398
|
-
}
|
|
3399
|
-
}
|
|
3400
|
-
/**
|
|
3401
|
-
* Record that a claimed attempt is waiting on a native subagent.
|
|
3402
|
-
*
|
|
3403
|
-
* The status stays `in_progress`, which is the truth: the attempt is open and its
|
|
3404
|
-
* idempotency slot is still held, so nothing re-enqueues the same occurrence underneath
|
|
3405
|
-
* the child. Only the durable verification (or the consumer's timeout) moves it on.
|
|
3406
|
-
*/
|
|
3407
|
-
markWorkOrderDelegated(id, delegatedAt) {
|
|
3408
|
-
if (!Number.isSafeInteger(delegatedAt) || delegatedAt < 0) {
|
|
3409
|
-
throw new Error('workorder delegation timestamp must be a non-negative safe integer');
|
|
3410
|
-
}
|
|
3411
|
-
const row = this.getWorkOrderById(id);
|
|
3412
|
-
if (!row)
|
|
3413
|
-
throw new Error(`workorder delegation: no system row with id ${id}`);
|
|
3414
|
-
if (row.status !== 'in_progress') {
|
|
3415
|
-
throw new Error(`workorder delegation: row ${id} is '${row.status}', expected in_progress`);
|
|
3416
|
-
}
|
|
3417
|
-
const payload = { ...row.payload, [exports.WORKORDER_DELEGATED_AT_KEY]: delegatedAt };
|
|
3418
|
-
const result = this.db
|
|
3419
|
-
.prepare(`UPDATE operator_tasks SET payload = ?, latest_event = ?, updated_at = ?
|
|
3420
|
-
WHERE id = ? AND kind = 'system' AND status = 'in_progress'`)
|
|
3421
|
-
.run(JSON.stringify(payload), 'delegated to a native subagent', this.now(), id);
|
|
3422
|
-
// The row can leave in_progress between the read above and this write. A no-op UPDATE
|
|
3423
|
-
// is not a delegation: say so instead of reporting a mark that was never stored.
|
|
3424
|
-
if (result.changes !== 1) {
|
|
3425
|
-
throw new Error(`workorder delegation: row ${id} left in_progress before the mark`);
|
|
3426
|
-
}
|
|
3427
|
-
}
|
|
3428
|
-
completeWorkOrder(id) {
|
|
3429
|
-
this.transitionWorkOrder(id, 'done', null);
|
|
3430
|
-
}
|
|
3431
|
-
failWorkOrder(id, reason) {
|
|
3432
|
-
this.transitionWorkOrder(id, 'failed', reason);
|
|
3433
|
-
}
|
|
3434
|
-
countPendingWorkOrders() {
|
|
3435
|
-
const row = this.db
|
|
3436
|
-
.prepare(`SELECT COUNT(*) AS count FROM operator_tasks
|
|
3437
|
-
WHERE kind = 'system' AND status = 'pending'
|
|
3438
|
-
AND source_channel IN (${KNOWN_WORKORDER_CHANNELS_SQL})`)
|
|
3439
|
-
.get(...KNOWN_WORKORDER_CHANNELS);
|
|
3440
|
-
return row.count;
|
|
3441
|
-
}
|
|
3442
|
-
countOpenWorkOrders(kind) {
|
|
3443
|
-
const row = (kind
|
|
3444
|
-
? this.db
|
|
3445
|
-
.prepare(`SELECT COUNT(*) AS count FROM operator_tasks
|
|
3446
|
-
WHERE kind = 'system' AND status IN ('pending','in_progress')
|
|
3447
|
-
AND source_channel = ?`)
|
|
3448
|
-
.get(`${exports.WORKORDER_CHANNEL_PREFIX}${kind}`)
|
|
3449
|
-
: this.db
|
|
3450
|
-
.prepare(`SELECT COUNT(*) AS count FROM operator_tasks
|
|
3451
|
-
WHERE kind = 'system' AND status IN ('pending','in_progress')`)
|
|
3452
|
-
.get());
|
|
3453
|
-
return row.count;
|
|
3454
|
-
}
|
|
3455
|
-
findTemporalGenerationKeys(generationKeys) {
|
|
3456
|
-
if (generationKeys.length === 0)
|
|
3457
|
-
return new Set();
|
|
3458
|
-
if (generationKeys.length > 500) {
|
|
3459
|
-
throw new Error('findTemporalGenerationKeys accepts at most 500 keys');
|
|
3460
|
-
}
|
|
3461
|
-
const rows = this.db
|
|
3462
|
-
.prepare(`SELECT generation_key FROM operator_temporal_generations
|
|
3463
|
-
WHERE generation_key IN (${generationKeys.map(() => '?').join(',')})`)
|
|
3464
|
-
.all(...generationKeys);
|
|
3465
|
-
return new Set(rows.map((row) => row.generation_key));
|
|
3466
|
-
}
|
|
3467
|
-
/** In-progress system rows at boot = crash artifacts (single serial consumer). */
|
|
3468
|
-
listStaleClaims() {
|
|
3469
|
-
const rows = this.db
|
|
3470
|
-
.prepare(`SELECT * FROM operator_tasks
|
|
3471
|
-
WHERE kind = 'system' AND status = 'in_progress'
|
|
3472
|
-
AND source_channel IN (${KNOWN_WORKORDER_CHANNELS_SQL})`)
|
|
3473
|
-
.all(...KNOWN_WORKORDER_CHANNELS);
|
|
3474
|
-
return rows.map((row) => this.rowToWorkOrder(row));
|
|
3475
|
-
}
|
|
3476
|
-
/**
|
|
3477
|
-
* Boot cleanup (plan D3 + review N4): open system rows -> cancelled. A
|
|
3478
|
-
* rollback is not a failure - excluded from failed counters/alarms; caller
|
|
3479
|
-
* logs ONE summary line with the returned count. `onlyKinds` scopes the
|
|
3480
|
-
* cancellation.
|
|
3481
|
-
*/
|
|
3482
|
-
cancelOpenWorkOrders(reason, onlyKinds) {
|
|
3483
|
-
// An explicit empty scope means "cancel nothing", never "cancel all"
|
|
3484
|
-
// (review F2 - the fall-through would silently widen a scoped call).
|
|
3485
|
-
if (onlyKinds !== undefined && onlyKinds.length === 0) {
|
|
3486
|
-
return 0;
|
|
3487
|
-
}
|
|
3488
|
-
const selectedKinds = onlyKinds ?? exports.WORKORDER_KINDS;
|
|
3489
|
-
const kindFilter = ` AND source_channel IN (${selectedKinds.map(() => '?').join(',')})`;
|
|
3490
|
-
const kindParams = selectedKinds.map((kind) => `${exports.WORKORDER_CHANNEL_PREFIX}${kind}`);
|
|
3491
|
-
const result = this.db
|
|
3492
|
-
.prepare(`UPDATE operator_tasks
|
|
3493
|
-
SET status = 'cancelled', latest_event = ?, updated_at = ?
|
|
3494
|
-
WHERE kind = 'system' AND status IN ('pending','in_progress')${kindFilter}`)
|
|
3495
|
-
.run(reason, this.now(), ...kindParams);
|
|
3496
|
-
return result.changes;
|
|
3497
|
-
}
|
|
3498
|
-
/** Per-kind last run and failure counts of the system lanes (operator UI and tests). */
|
|
3499
|
-
workOrderStats() {
|
|
3500
|
-
return exports.WORKORDER_KINDS.map((workKind) => {
|
|
3501
|
-
const channel = `${exports.WORKORDER_CHANNEL_PREFIX}${workKind}`;
|
|
3502
|
-
const last = this.db
|
|
3503
|
-
.prepare(`SELECT status, updated_at FROM operator_tasks
|
|
3504
|
-
WHERE kind = 'system' AND source_channel = ?
|
|
3505
|
-
ORDER BY updated_at DESC, id DESC LIMIT 1`)
|
|
3506
|
-
.get(channel);
|
|
3507
|
-
const failed = this.db
|
|
3508
|
-
.prepare(`SELECT COUNT(*) AS count FROM operator_tasks
|
|
3509
|
-
WHERE kind = 'system' AND source_channel = ? AND status = 'failed'`)
|
|
3510
|
-
.get(channel);
|
|
3511
|
-
const lastFailure = this.db
|
|
3512
|
-
.prepare(`SELECT latest_event FROM operator_tasks
|
|
3513
|
-
WHERE kind = 'system' AND source_channel = ? AND status = 'failed'
|
|
3514
|
-
ORDER BY updated_at DESC, id DESC LIMIT 1`)
|
|
3515
|
-
.get(channel);
|
|
3516
|
-
return {
|
|
3517
|
-
workKind,
|
|
3518
|
-
lastRunAt: last?.updated_at ?? null,
|
|
3519
|
-
lastStatus: last?.status ?? null,
|
|
3520
|
-
failedCount: failed.count,
|
|
3521
|
-
lastFailureReason: lastFailure?.latest_event ?? null,
|
|
3522
|
-
};
|
|
3523
|
-
});
|
|
3524
|
-
}
|
|
3525
|
-
getWorkOrderById(id) {
|
|
3526
|
-
const row = this.db
|
|
3527
|
-
.prepare(`SELECT * FROM operator_tasks WHERE id = ? AND kind = 'system'`)
|
|
3528
|
-
.get(id);
|
|
3529
|
-
return row ? this.rowToWorkOrder(row) : null;
|
|
3530
|
-
}
|
|
3531
|
-
transitionWorkOrder(id, status, reason) {
|
|
3532
|
-
const row = this.getWorkOrderById(id);
|
|
3533
|
-
if (!row)
|
|
3534
|
-
throw new Error(`workorder transition: no system row with id ${id}`);
|
|
3535
|
-
if (row.status !== 'in_progress') {
|
|
3536
|
-
throw new Error(`workorder transition: row ${id} is '${row.status}', expected in_progress`);
|
|
3537
|
-
}
|
|
3538
|
-
this.db
|
|
3539
|
-
.prepare(`UPDATE operator_tasks SET status = ?, latest_event = COALESCE(?, latest_event),
|
|
3540
|
-
updated_at = ? WHERE id = ?`)
|
|
3541
|
-
.run(status, reason, this.now(), id);
|
|
3542
|
-
}
|
|
3543
|
-
rowToWorkOrder(row) {
|
|
3544
|
-
const workKind = (row.source_channel ?? '').slice(exports.WORKORDER_CHANNEL_PREFIX.length);
|
|
3545
|
-
assertEnum(workKind, exports.WORKORDER_KINDS, 'workKind');
|
|
3546
|
-
let payload;
|
|
3547
|
-
try {
|
|
3548
|
-
payload = row.payload ? JSON.parse(row.payload) : {};
|
|
3549
|
-
}
|
|
3550
|
-
catch (error) {
|
|
3551
|
-
// No-fallback: a corrupt payload is a real fault, surface it loudly.
|
|
3552
|
-
throw new Error(`workorder ${row.id}: corrupt payload JSON (${error instanceof Error ? error.message : String(error)})`);
|
|
3553
|
-
}
|
|
3554
|
-
// Strict reads (PR bot round): silently normalizing corrupt ownership or
|
|
3555
|
-
// retry metadata would mask a real fault - every row here was written by
|
|
3556
|
-
// enqueueWorkOrder, which guarantees both fields.
|
|
3557
|
-
if (typeof payload.attempts !== 'number' || payload.attempts < 1) {
|
|
3558
|
-
throw new Error(`workorder ${row.id}: invalid attempts in payload (${String(payload.attempts)})`);
|
|
3559
|
-
}
|
|
3560
|
-
if (!row.source_event_id) {
|
|
3561
|
-
throw new Error(`workorder ${row.id}: missing idempotency key (source_event_id)`);
|
|
3562
|
-
}
|
|
3563
|
-
return {
|
|
3564
|
-
id: row.id,
|
|
3565
|
-
workKind: workKind,
|
|
3566
|
-
status: row.status,
|
|
3567
|
-
priority: row.priority,
|
|
3568
|
-
idempotencyKey: row.source_event_id,
|
|
3569
|
-
payload: { ...payload, attempts: payload.attempts },
|
|
3570
|
-
delegatedAt: typeof payload[exports.WORKORDER_DELEGATED_AT_KEY] === 'number'
|
|
3571
|
-
? payload[exports.WORKORDER_DELEGATED_AT_KEY]
|
|
3572
|
-
: null,
|
|
3573
|
-
createdAt: row.created_at,
|
|
3574
|
-
updatedAt: row.updated_at,
|
|
3575
|
-
};
|
|
3576
|
-
}
|
|
3577
|
-
/** contract_no_update: silence as a verifiable judgment, scoped to one reconcile run. */
|
|
3578
|
-
recordNoUpdate(scope, reason) {
|
|
3579
|
-
if (!scope || !reason) {
|
|
3580
|
-
throw new Error('contract_no_update requires both scope and reason');
|
|
3581
|
-
}
|
|
3582
|
-
return this.insertNoUpdateNote(scope, reason, this.now());
|
|
3583
|
-
}
|
|
3584
|
-
insertNoUpdateNote(scope, reason, createdAt) {
|
|
3585
|
-
const result = this.db
|
|
3586
|
-
.prepare(`INSERT INTO operator_no_update_notes (scope, reason, created_at) VALUES (?, ?, ?)`)
|
|
3587
|
-
.run(scope, reason, createdAt);
|
|
3588
|
-
return { id: Number(result.lastInsertRowid) };
|
|
3589
|
-
}
|
|
3590
|
-
/** Max no-update note id, optionally scoped - the verifier's note snapshot. */
|
|
3591
|
-
maxNoUpdateId(scope) {
|
|
3592
|
-
const row = (scope
|
|
3593
|
-
? this.db
|
|
3594
|
-
.prepare(`SELECT MAX(id) AS max_id FROM operator_no_update_notes WHERE scope = ?`)
|
|
3595
|
-
.get(scope)
|
|
3596
|
-
: this.db.prepare(`SELECT MAX(id) AS max_id FROM operator_no_update_notes`).get());
|
|
3597
|
-
return row.max_id ?? 0;
|
|
3598
|
-
}
|
|
3599
|
-
}
|
|
3600
|
-
exports.TaskLedger = TaskLedger;
|
|
3601
|
-
//# sourceMappingURL=task-ledger.js.map
|