spur-board 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +12 -0
- package/dist/bin.mjs +28 -0
- package/dist/cli.mjs +799 -0
- package/dist/client-C1hJzdYE.mjs +162 -0
- package/dist/client.mjs +2 -0
- package/dist/config-DQmktVi-.mjs +92 -0
- package/dist/daemon.mjs +7602 -0
- package/dist/mcp.mjs +123 -0
- package/dist/migrations/0001_init.sql +197 -0
- package/dist/migrations/0002_delivery.sql +49 -0
- package/dist/migrations/0003_delivery_search.sql +3 -0
- package/dist/migrations/0004_attempt_started.sql +3 -0
- package/dist/migrations/0005_doorbells_items.sql +39 -0
- package/dist/migrations/0006_notification_exhausted.sql +1 -0
- package/dist/migrations/0007_inbound_item_event.sql +1 -0
- package/dist/migrations/0008_observation.sql +49 -0
- package/dist/migrations/0009_operator_tokens.sql +15 -0
- package/dist/src-CqH_W7D8.mjs +2475 -0
- package/package.json +47 -0
package/dist/mcp.mjs
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { O as ERRORS, l as renderMcpError, o as OPERATIONS } from "./src-CqH_W7D8.mjs";
|
|
2
|
+
import { r as CliError, t as requestOperation } from "./client-C1hJzdYE.mjs";
|
|
3
|
+
import { t as loadClientConfig } from "./config-DQmktVi-.mjs";
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
5
|
+
import { readFileSync } from "node:fs";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { McpServer, fromJsonSchema } from "@modelcontextprotocol/server";
|
|
8
|
+
import { serveStdio } from "@modelcontextprotocol/server/stdio";
|
|
9
|
+
//#region src/mcp.ts
|
|
10
|
+
const handlerValidator = { getValidator() {
|
|
11
|
+
return (input) => ({
|
|
12
|
+
valid: true,
|
|
13
|
+
data: input,
|
|
14
|
+
errorMessage: void 0
|
|
15
|
+
});
|
|
16
|
+
} };
|
|
17
|
+
function toolInputSchema(op, agent) {
|
|
18
|
+
const source = z.toJSONSchema(op.input, { io: "input" });
|
|
19
|
+
if (op.kind === "query") return {
|
|
20
|
+
...source,
|
|
21
|
+
type: "object"
|
|
22
|
+
};
|
|
23
|
+
const key = {
|
|
24
|
+
type: "string",
|
|
25
|
+
minLength: 1,
|
|
26
|
+
description: "Key to reuse when retrying this action."
|
|
27
|
+
};
|
|
28
|
+
const withKey = (schema) => ({
|
|
29
|
+
...schema,
|
|
30
|
+
properties: {
|
|
31
|
+
...schema["properties"],
|
|
32
|
+
idempotencyKey: key
|
|
33
|
+
},
|
|
34
|
+
...agent ? { required: [...schema["required"] ?? [], "idempotencyKey"] } : {}
|
|
35
|
+
});
|
|
36
|
+
if (Array.isArray(source["oneOf"])) return {
|
|
37
|
+
...source,
|
|
38
|
+
type: "object",
|
|
39
|
+
oneOf: source["oneOf"].map((entry) => withKey(entry))
|
|
40
|
+
};
|
|
41
|
+
return {
|
|
42
|
+
...withKey(source),
|
|
43
|
+
type: "object"
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function toolDescription(op) {
|
|
47
|
+
const example = op.kind === "mutation" ? {
|
|
48
|
+
...op.examples[0].input,
|
|
49
|
+
idempotencyKey: `${op.mcp.name}-example-001`
|
|
50
|
+
} : op.examples[0].input;
|
|
51
|
+
return [
|
|
52
|
+
op.description,
|
|
53
|
+
`Use when: ${op.mcp.whenToUse}`,
|
|
54
|
+
`When not to use: ${op.mcp.whenNotToUse}`,
|
|
55
|
+
`Example: ${JSON.stringify(example)}`,
|
|
56
|
+
...op.kind === "mutation" ? ["Choose a unique idempotencyKey for each action; reuse it on retries."] : []
|
|
57
|
+
].join("\n");
|
|
58
|
+
}
|
|
59
|
+
function parseArguments(op, argumentsValue, agent) {
|
|
60
|
+
if (!argumentsValue || typeof argumentsValue !== "object" || Array.isArray(argumentsValue)) throw new CliError("usage");
|
|
61
|
+
const { idempotencyKey, ...input } = argumentsValue;
|
|
62
|
+
let key;
|
|
63
|
+
if (op.kind === "mutation") if (idempotencyKey === void 0 && !agent) key = randomUUID();
|
|
64
|
+
else if (typeof idempotencyKey === "string" && idempotencyKey.trim()) key = idempotencyKey;
|
|
65
|
+
else throw new CliError("usage");
|
|
66
|
+
else if (idempotencyKey !== void 0) throw new CliError("usage");
|
|
67
|
+
const parsed = op.input.safeParse(input);
|
|
68
|
+
if (!parsed.success) throw new CliError("usage");
|
|
69
|
+
return {
|
|
70
|
+
input: parsed.data,
|
|
71
|
+
key
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/** One tool per catalog operation, with no daemon/config access until a tool is called. */
|
|
75
|
+
function createMcpServer(env = process.env, loadConfig = loadClientConfig) {
|
|
76
|
+
const { version } = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
77
|
+
const server = new McpServer({
|
|
78
|
+
name: "spur-board",
|
|
79
|
+
version
|
|
80
|
+
});
|
|
81
|
+
const agent = Boolean(env["SPUR_SESSION"]);
|
|
82
|
+
for (const op of OPERATIONS) server.registerTool(op.mcp.name, {
|
|
83
|
+
description: toolDescription(op),
|
|
84
|
+
inputSchema: fromJsonSchema(toolInputSchema(op, agent), handlerValidator),
|
|
85
|
+
outputSchema: op.output,
|
|
86
|
+
annotations: {
|
|
87
|
+
readOnlyHint: op.kind === "query",
|
|
88
|
+
idempotentHint: op.kind === "mutation"
|
|
89
|
+
}
|
|
90
|
+
}, async (argumentsValue) => {
|
|
91
|
+
let key;
|
|
92
|
+
try {
|
|
93
|
+
const parsed = parseArguments(op, argumentsValue, agent);
|
|
94
|
+
key = parsed.key;
|
|
95
|
+
const config = loadConfig(env);
|
|
96
|
+
const value = await requestOperation(op, parsed.input, key, config);
|
|
97
|
+
return {
|
|
98
|
+
content: [{
|
|
99
|
+
type: "text",
|
|
100
|
+
text: JSON.stringify(value)
|
|
101
|
+
}],
|
|
102
|
+
structuredContent: value
|
|
103
|
+
};
|
|
104
|
+
} catch (cause) {
|
|
105
|
+
const error = cause instanceof CliError ? cause : new CliError("unexpected");
|
|
106
|
+
return renderMcpError({
|
|
107
|
+
code: error.code,
|
|
108
|
+
requestId: error.requestId,
|
|
109
|
+
...error.context || key && ERRORS[error.code].exit === 5 ? { context: {
|
|
110
|
+
...error.context,
|
|
111
|
+
...key && ERRORS[error.code].exit === 5 ? { idempotencyKey: key } : {}
|
|
112
|
+
} } : {}
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
return server;
|
|
117
|
+
}
|
|
118
|
+
/** Stdio is reserved for JSON-RPC; callers may log diagnostics only to stderr. */
|
|
119
|
+
function startMcp() {
|
|
120
|
+
serveStdio(() => createMcpServer());
|
|
121
|
+
}
|
|
122
|
+
//#endregion
|
|
123
|
+
export { createMcpServer, startMcp };
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
-- Schema v1 of the board store. Forward only: never edit this file once committed;
|
|
2
|
+
-- add the next numbered file instead. Timestamps are ISO-8601 UTC text, JSON columns
|
|
3
|
+
-- hold text checked by zod on read and write, flags are 0 or 1.
|
|
4
|
+
|
|
5
|
+
CREATE TABLE task (
|
|
6
|
+
id INTEGER PRIMARY KEY,
|
|
7
|
+
project TEXT NOT NULL,
|
|
8
|
+
tracker_kind TEXT NOT NULL,
|
|
9
|
+
tracker_id TEXT NOT NULL,
|
|
10
|
+
key TEXT NOT NULL,
|
|
11
|
+
membership TEXT NOT NULL,
|
|
12
|
+
archive_reason TEXT,
|
|
13
|
+
"column" TEXT NOT NULL,
|
|
14
|
+
shadow_column TEXT,
|
|
15
|
+
column_epoch INTEGER NOT NULL DEFAULT 0,
|
|
16
|
+
left_moves INTEGER NOT NULL DEFAULT 0,
|
|
17
|
+
merged_past_gate INTEGER NOT NULL DEFAULT 0 CHECK (merged_past_gate IN (0, 1)),
|
|
18
|
+
created_at TEXT NOT NULL,
|
|
19
|
+
UNIQUE (project, tracker_kind, tracker_id)
|
|
20
|
+
) STRICT;
|
|
21
|
+
|
|
22
|
+
CREATE TABLE pr_snapshot (
|
|
23
|
+
repo TEXT NOT NULL,
|
|
24
|
+
number INTEGER NOT NULL,
|
|
25
|
+
head_sha TEXT NOT NULL,
|
|
26
|
+
head_branch TEXT NOT NULL,
|
|
27
|
+
state TEXT NOT NULL,
|
|
28
|
+
draft INTEGER NOT NULL CHECK (draft IN (0, 1)),
|
|
29
|
+
mergeable TEXT,
|
|
30
|
+
merge_state TEXT,
|
|
31
|
+
detail TEXT NOT NULL,
|
|
32
|
+
source TEXT NOT NULL,
|
|
33
|
+
observed_at TEXT NOT NULL,
|
|
34
|
+
PRIMARY KEY (repo, number)
|
|
35
|
+
) STRICT;
|
|
36
|
+
|
|
37
|
+
CREATE TABLE ticket_snapshot (
|
|
38
|
+
task_id INTEGER PRIMARY KEY REFERENCES task (id),
|
|
39
|
+
status TEXT NOT NULL,
|
|
40
|
+
assignee TEXT,
|
|
41
|
+
detail TEXT NOT NULL,
|
|
42
|
+
source TEXT NOT NULL,
|
|
43
|
+
observed_at TEXT NOT NULL
|
|
44
|
+
) STRICT;
|
|
45
|
+
|
|
46
|
+
CREATE TABLE session_snapshot (
|
|
47
|
+
id TEXT PRIMARY KEY,
|
|
48
|
+
spur_project TEXT NOT NULL,
|
|
49
|
+
branch TEXT,
|
|
50
|
+
pr TEXT,
|
|
51
|
+
raw_state TEXT NOT NULL,
|
|
52
|
+
state TEXT NOT NULL,
|
|
53
|
+
first_seen TEXT NOT NULL,
|
|
54
|
+
last_seen TEXT NOT NULL,
|
|
55
|
+
ended_at TEXT,
|
|
56
|
+
end_reason TEXT,
|
|
57
|
+
detail TEXT NOT NULL,
|
|
58
|
+
source TEXT NOT NULL,
|
|
59
|
+
observed_at TEXT NOT NULL
|
|
60
|
+
) STRICT;
|
|
61
|
+
|
|
62
|
+
-- A task's link to a PR (entity 'pr') or to a spur session (entity 'session').
|
|
63
|
+
CREATE TABLE link (
|
|
64
|
+
id INTEGER PRIMARY KEY,
|
|
65
|
+
task_id INTEGER NOT NULL REFERENCES task (id),
|
|
66
|
+
entity TEXT NOT NULL,
|
|
67
|
+
repo TEXT,
|
|
68
|
+
number INTEGER,
|
|
69
|
+
pr_state TEXT,
|
|
70
|
+
session_id TEXT,
|
|
71
|
+
role TEXT,
|
|
72
|
+
source TEXT NOT NULL,
|
|
73
|
+
banned INTEGER NOT NULL DEFAULT 0 CHECK (banned IN (0, 1)),
|
|
74
|
+
created_at TEXT NOT NULL,
|
|
75
|
+
-- Unique indexes treat NULLs as distinct, so each entity must carry its identity.
|
|
76
|
+
CHECK (entity <> 'pr' OR (repo IS NOT NULL AND number IS NOT NULL AND pr_state IS NOT NULL)),
|
|
77
|
+
CHECK (entity <> 'session' OR session_id IS NOT NULL)
|
|
78
|
+
) STRICT;
|
|
79
|
+
|
|
80
|
+
CREATE UNIQUE INDEX link_pr_pair ON link (task_id, repo, number) WHERE entity = 'pr';
|
|
81
|
+
CREATE UNIQUE INDEX link_session_pair ON link (task_id, session_id) WHERE entity = 'session';
|
|
82
|
+
-- At most one active, unbanned PR link per task; conflict and banned links are not limited.
|
|
83
|
+
CREATE UNIQUE INDEX link_one_active_pr ON link (task_id)
|
|
84
|
+
WHERE entity = 'pr' AND pr_state = 'active' AND banned = 0;
|
|
85
|
+
|
|
86
|
+
-- A column executor marks its turn finished at a head commit. One visit (epoch) may hold
|
|
87
|
+
-- several rows; the row at the current head commit is the one that counts.
|
|
88
|
+
CREATE TABLE submission (
|
|
89
|
+
id INTEGER PRIMARY KEY,
|
|
90
|
+
task_id INTEGER NOT NULL REFERENCES task (id),
|
|
91
|
+
"column" TEXT NOT NULL,
|
|
92
|
+
epoch INTEGER NOT NULL,
|
|
93
|
+
commit_sha TEXT NOT NULL,
|
|
94
|
+
author TEXT NOT NULL,
|
|
95
|
+
submitted_at TEXT NOT NULL,
|
|
96
|
+
note TEXT
|
|
97
|
+
) STRICT;
|
|
98
|
+
|
|
99
|
+
CREATE INDEX submission_task_column_submitted ON submission (task_id, "column", submitted_at);
|
|
100
|
+
|
|
101
|
+
CREATE TABLE block (
|
|
102
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
103
|
+
task_id INTEGER NOT NULL REFERENCES task (id),
|
|
104
|
+
type TEXT NOT NULL,
|
|
105
|
+
reason TEXT NOT NULL,
|
|
106
|
+
author TEXT NOT NULL,
|
|
107
|
+
addressee_role TEXT NOT NULL,
|
|
108
|
+
target_column TEXT,
|
|
109
|
+
state TEXT NOT NULL,
|
|
110
|
+
outcome TEXT,
|
|
111
|
+
outcome_reason TEXT,
|
|
112
|
+
repeat_of INTEGER REFERENCES block (id),
|
|
113
|
+
unblocked_by TEXT,
|
|
114
|
+
unblocked_at TEXT,
|
|
115
|
+
created_at TEXT NOT NULL
|
|
116
|
+
) STRICT;
|
|
117
|
+
|
|
118
|
+
CREATE INDEX block_task_state ON block (task_id, state);
|
|
119
|
+
|
|
120
|
+
CREATE TABLE comment (
|
|
121
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
122
|
+
task_id INTEGER NOT NULL REFERENCES task (id),
|
|
123
|
+
block_id INTEGER REFERENCES block (id),
|
|
124
|
+
author TEXT NOT NULL,
|
|
125
|
+
text TEXT NOT NULL,
|
|
126
|
+
revision INTEGER NOT NULL DEFAULT 1,
|
|
127
|
+
conversation_ref TEXT,
|
|
128
|
+
message_ref TEXT,
|
|
129
|
+
acked_revision INTEGER,
|
|
130
|
+
created_at TEXT NOT NULL
|
|
131
|
+
) STRICT;
|
|
132
|
+
|
|
133
|
+
CREATE INDEX comment_task_id ON comment (task_id, id);
|
|
134
|
+
|
|
135
|
+
-- Idempotent operator and agent commands, keyed by the caller's idempotency key.
|
|
136
|
+
CREATE TABLE command (
|
|
137
|
+
actor TEXT NOT NULL,
|
|
138
|
+
key TEXT NOT NULL,
|
|
139
|
+
operation TEXT NOT NULL,
|
|
140
|
+
fingerprint TEXT,
|
|
141
|
+
result TEXT NOT NULL,
|
|
142
|
+
at TEXT NOT NULL,
|
|
143
|
+
PRIMARY KEY (actor, key)
|
|
144
|
+
) STRICT;
|
|
145
|
+
|
|
146
|
+
-- The journal. actor_ref is JSON {project, session}, set exactly when actor is 'agent'.
|
|
147
|
+
CREATE TABLE event (
|
|
148
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
149
|
+
at TEXT NOT NULL,
|
|
150
|
+
project TEXT NOT NULL,
|
|
151
|
+
task_id INTEGER REFERENCES task (id),
|
|
152
|
+
kind TEXT NOT NULL,
|
|
153
|
+
actor TEXT NOT NULL,
|
|
154
|
+
actor_ref TEXT,
|
|
155
|
+
payload TEXT NOT NULL,
|
|
156
|
+
v INTEGER NOT NULL,
|
|
157
|
+
request_id TEXT
|
|
158
|
+
) STRICT;
|
|
159
|
+
|
|
160
|
+
CREATE INDEX event_task_id ON event (task_id, id);
|
|
161
|
+
CREATE INDEX event_at ON event (at);
|
|
162
|
+
|
|
163
|
+
CREATE TABLE outbox (
|
|
164
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
165
|
+
kind TEXT NOT NULL,
|
|
166
|
+
dedupe_key TEXT NOT NULL UNIQUE,
|
|
167
|
+
payload TEXT NOT NULL,
|
|
168
|
+
state TEXT NOT NULL,
|
|
169
|
+
attempts INTEGER NOT NULL DEFAULT 0,
|
|
170
|
+
next_at TEXT NOT NULL,
|
|
171
|
+
last_error TEXT,
|
|
172
|
+
external_ref TEXT,
|
|
173
|
+
uncertain INTEGER NOT NULL DEFAULT 0 CHECK (uncertain IN (0, 1))
|
|
174
|
+
) STRICT;
|
|
175
|
+
|
|
176
|
+
CREATE INDEX outbox_state_next_at ON outbox (state, next_at);
|
|
177
|
+
|
|
178
|
+
CREATE TABLE dismissal (
|
|
179
|
+
item_key TEXT PRIMARY KEY,
|
|
180
|
+
fingerprint TEXT NOT NULL,
|
|
181
|
+
dismissed_by TEXT NOT NULL,
|
|
182
|
+
dismissed_at TEXT NOT NULL
|
|
183
|
+
) STRICT;
|
|
184
|
+
|
|
185
|
+
-- Digest and archive slots. The schema version lives in PRAGMA user_version, not here.
|
|
186
|
+
CREATE TABLE meta (
|
|
187
|
+
key TEXT PRIMARY KEY,
|
|
188
|
+
value TEXT NOT NULL
|
|
189
|
+
) STRICT;
|
|
190
|
+
|
|
191
|
+
CREATE TABLE source_state (
|
|
192
|
+
source TEXT PRIMARY KEY,
|
|
193
|
+
initialized INTEGER NOT NULL DEFAULT 0 CHECK (initialized IN (0, 1)),
|
|
194
|
+
last_complete_at TEXT,
|
|
195
|
+
blind_reason TEXT,
|
|
196
|
+
cursor TEXT
|
|
197
|
+
) STRICT;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
-- Schema v2: outbox delivery. Forward only: never edit this file once committed; add the
|
|
2
|
+
-- next numbered file instead. Timestamps are ISO-8601 UTC text, JSON columns hold text
|
|
3
|
+
-- checked by zod on read and write, flags are 0 or 1.
|
|
4
|
+
|
|
5
|
+
-- A conversation on a gateway: the thread of a block (purpose 'block') or of a feed line
|
|
6
|
+
-- (purpose 'feed_line'). A block may have several.
|
|
7
|
+
CREATE TABLE conversation (
|
|
8
|
+
ref TEXT NOT NULL,
|
|
9
|
+
gateway TEXT NOT NULL,
|
|
10
|
+
space TEXT NOT NULL,
|
|
11
|
+
task_id INTEGER REFERENCES task (id),
|
|
12
|
+
block_id INTEGER REFERENCES block (id),
|
|
13
|
+
purpose TEXT NOT NULL,
|
|
14
|
+
state TEXT NOT NULL,
|
|
15
|
+
root_message_ref TEXT NOT NULL,
|
|
16
|
+
after_message_ref TEXT,
|
|
17
|
+
created_at TEXT NOT NULL,
|
|
18
|
+
closed_at TEXT,
|
|
19
|
+
PRIMARY KEY (gateway, ref)
|
|
20
|
+
) STRICT;
|
|
21
|
+
|
|
22
|
+
-- One row per (gateway, space) whose transport is failing; its rows wait while it stands.
|
|
23
|
+
-- probe_at is when the last probe's send started: one probe per minute, kept across restarts.
|
|
24
|
+
CREATE TABLE transport_health (
|
|
25
|
+
gateway TEXT NOT NULL,
|
|
26
|
+
space TEXT NOT NULL,
|
|
27
|
+
reason TEXT NOT NULL,
|
|
28
|
+
since TEXT NOT NULL,
|
|
29
|
+
probe_at TEXT NOT NULL,
|
|
30
|
+
PRIMARY KEY (gateway, space)
|
|
31
|
+
) STRICT;
|
|
32
|
+
|
|
33
|
+
-- When the last send into a (gateway, space) started: the next one waits the space interval,
|
|
34
|
+
-- kept across restarts.
|
|
35
|
+
CREATE TABLE space_slot (
|
|
36
|
+
gateway TEXT NOT NULL,
|
|
37
|
+
space TEXT NOT NULL,
|
|
38
|
+
sent_at TEXT NOT NULL,
|
|
39
|
+
PRIMARY KEY (gateway, space)
|
|
40
|
+
) STRICT;
|
|
41
|
+
|
|
42
|
+
-- A dependent row (reply, close, edit) is sent only after its parent is done.
|
|
43
|
+
ALTER TABLE outbox ADD COLUMN parent_outbox_id INTEGER REFERENCES outbox (id);
|
|
44
|
+
-- The first attempt of the current retry window; NULL while the window is suspended.
|
|
45
|
+
ALTER TABLE outbox ADD COLUMN first_attempt_at TEXT;
|
|
46
|
+
-- NULL for a comment that is not delivered to a gateway.
|
|
47
|
+
ALTER TABLE comment ADD COLUMN delivery_state TEXT;
|
|
48
|
+
|
|
49
|
+
CREATE INDEX outbox_parent_outbox_id ON outbox (parent_outbox_id);
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
-- Notification state tracks distinct delivered rings separately from outbox transport attempts.
|
|
2
|
+
CREATE TABLE notification (
|
|
3
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
4
|
+
source_key TEXT NOT NULL UNIQUE,
|
|
5
|
+
kind TEXT NOT NULL,
|
|
6
|
+
task_id INTEGER NOT NULL REFERENCES task (id),
|
|
7
|
+
block_id INTEGER NOT NULL REFERENCES block (id),
|
|
8
|
+
comment_id INTEGER REFERENCES comment (id),
|
|
9
|
+
revision INTEGER,
|
|
10
|
+
role TEXT NOT NULL,
|
|
11
|
+
author_session TEXT,
|
|
12
|
+
recipient_session TEXT,
|
|
13
|
+
rings INTEGER NOT NULL DEFAULT 0,
|
|
14
|
+
next_at TEXT NOT NULL,
|
|
15
|
+
last_outbox_id INTEGER REFERENCES outbox (id),
|
|
16
|
+
acked_at TEXT,
|
|
17
|
+
created_at TEXT NOT NULL
|
|
18
|
+
) STRICT;
|
|
19
|
+
|
|
20
|
+
CREATE INDEX notification_due ON notification (acked_at, next_at);
|
|
21
|
+
CREATE INDEX notification_block ON notification (block_id, id);
|
|
22
|
+
|
|
23
|
+
-- A new raise of the same item key gets a new raise number and a new feed dedupe key.
|
|
24
|
+
CREATE TABLE operator_item (
|
|
25
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
26
|
+
item_key TEXT NOT NULL,
|
|
27
|
+
raise_no INTEGER NOT NULL,
|
|
28
|
+
kind TEXT NOT NULL,
|
|
29
|
+
text TEXT NOT NULL,
|
|
30
|
+
url TEXT NOT NULL,
|
|
31
|
+
task_id INTEGER REFERENCES task (id),
|
|
32
|
+
block_id INTEGER REFERENCES block (id),
|
|
33
|
+
raised_at TEXT NOT NULL,
|
|
34
|
+
cleared_at TEXT,
|
|
35
|
+
line_outbox_id INTEGER REFERENCES outbox (id),
|
|
36
|
+
UNIQUE (item_key, raise_no)
|
|
37
|
+
) STRICT;
|
|
38
|
+
|
|
39
|
+
CREATE INDEX operator_item_active ON operator_item (item_key, cleared_at);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ALTER TABLE notification ADD COLUMN exhausted_at TEXT;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ALTER TABLE operator_item ADD COLUMN inbound_event_json TEXT;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
-- Observation and operator membership state. Existing task ids and child rows survive.
|
|
2
|
+
CREATE TABLE task_new (
|
|
3
|
+
id INTEGER PRIMARY KEY,
|
|
4
|
+
project TEXT NOT NULL,
|
|
5
|
+
tracker_kind TEXT NOT NULL,
|
|
6
|
+
tracker_id TEXT,
|
|
7
|
+
key TEXT NOT NULL,
|
|
8
|
+
membership TEXT NOT NULL,
|
|
9
|
+
archive_reason TEXT,
|
|
10
|
+
"column" TEXT NOT NULL,
|
|
11
|
+
shadow_column TEXT,
|
|
12
|
+
column_epoch INTEGER NOT NULL DEFAULT 0,
|
|
13
|
+
left_moves INTEGER NOT NULL DEFAULT 0,
|
|
14
|
+
merged_past_gate INTEGER NOT NULL DEFAULT 0 CHECK (merged_past_gate IN (0, 1)),
|
|
15
|
+
created_at TEXT NOT NULL,
|
|
16
|
+
manual_include INTEGER NOT NULL DEFAULT 0 CHECK (manual_include IN (0, 1)),
|
|
17
|
+
manual_exclude INTEGER NOT NULL DEFAULT 0 CHECK (manual_exclude IN (0, 1)),
|
|
18
|
+
last_change_at TEXT,
|
|
19
|
+
terminal_at TEXT,
|
|
20
|
+
archive_slot_at TEXT,
|
|
21
|
+
UNIQUE (project, tracker_kind, key)
|
|
22
|
+
) STRICT;
|
|
23
|
+
INSERT INTO task_new (id, project, tracker_kind, tracker_id, key, membership, archive_reason,
|
|
24
|
+
"column", shadow_column, column_epoch, left_moves, merged_past_gate, created_at)
|
|
25
|
+
SELECT id, project, tracker_kind, tracker_id, key, membership, archive_reason,
|
|
26
|
+
"column", shadow_column, column_epoch, left_moves, merged_past_gate, created_at FROM task;
|
|
27
|
+
DROP TABLE task;
|
|
28
|
+
ALTER TABLE task_new RENAME TO task;
|
|
29
|
+
CREATE UNIQUE INDEX task_stable_tracker_id ON task (project, tracker_kind, tracker_id)
|
|
30
|
+
WHERE tracker_id IS NOT NULL;
|
|
31
|
+
|
|
32
|
+
ALTER TABLE source_state ADD COLUMN observed_at TEXT;
|
|
33
|
+
ALTER TABLE source_state ADD COLUMN blind_message TEXT;
|
|
34
|
+
ALTER TABLE source_state ADD COLUMN gone TEXT NOT NULL DEFAULT '[]';
|
|
35
|
+
|
|
36
|
+
CREATE TABLE source_miss (
|
|
37
|
+
source TEXT NOT NULL REFERENCES source_state (source),
|
|
38
|
+
ref_key TEXT NOT NULL,
|
|
39
|
+
ref TEXT NOT NULL,
|
|
40
|
+
misses INTEGER NOT NULL CHECK (misses >= 0),
|
|
41
|
+
gone INTEGER NOT NULL CHECK (gone IN (0, 1)),
|
|
42
|
+
outcome TEXT,
|
|
43
|
+
PRIMARY KEY (source, ref_key)
|
|
44
|
+
) STRICT;
|
|
45
|
+
|
|
46
|
+
ALTER TABLE operator_item ADD COLUMN fingerprint TEXT;
|
|
47
|
+
ALTER TABLE operator_item ADD COLUMN fact_at TEXT;
|
|
48
|
+
ALTER TABLE operator_item ADD COLUMN source TEXT;
|
|
49
|
+
ALTER TABLE operator_item ADD COLUMN priority INTEGER;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
-- Named operator credentials store hashes only. Expiry is enforced at read and decision time.
|
|
2
|
+
CREATE TABLE operator_token (
|
|
3
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
4
|
+
name TEXT NOT NULL UNIQUE,
|
|
5
|
+
hash TEXT NOT NULL UNIQUE,
|
|
6
|
+
folder TEXT NOT NULL,
|
|
7
|
+
host TEXT NOT NULL,
|
|
8
|
+
status TEXT NOT NULL,
|
|
9
|
+
requested_at TEXT NOT NULL,
|
|
10
|
+
expires_at TEXT NOT NULL,
|
|
11
|
+
decided_at TEXT,
|
|
12
|
+
item_id INTEGER NOT NULL REFERENCES operator_item (id)
|
|
13
|
+
) STRICT;
|
|
14
|
+
|
|
15
|
+
CREATE INDEX operator_token_status_expiry ON operator_token (status, expires_at);
|