@stackbone/cli 0.6.2 → 0.6.3

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.
@@ -0,0 +1,61 @@
1
+ -- 0094_rag_document_versions.sql — RAG document versions (STA3-8)
2
+ -- Every version of a document is its own row, with its own `source` (its storage
3
+ -- key) and its own chunks, so rollback flips a flag and re-embeds nothing. The
4
+ -- versions of one document share a `document_key`; the agent reads only the
5
+ -- `is_active` one.
6
+ --
7
+ -- A document that existed before this is its own version 1, published, keyed by
8
+ -- its `source`.
9
+
10
+ ALTER TABLE stackbone_platform.rag_documents
11
+ ADD COLUMN IF NOT EXISTS document_key text,
12
+ ADD COLUMN IF NOT EXISTS version integer NOT NULL DEFAULT 1,
13
+ ADD COLUMN IF NOT EXISTS is_active boolean NOT NULL DEFAULT true;
14
+
15
+ -- Two overlapping ingests of one source could leave two rows before PLA-2327's
16
+ -- lock. The newest keeps the source as its key; the older ones become documents
17
+ -- of their own, still published, so nothing the agent reads changes and the
18
+ -- unique index below can build. Deleting them would lose data.
19
+ UPDATE stackbone_platform.rag_documents d
20
+ SET document_key = CASE WHEN ranked.rn = 1 THEN d.source ELSE d.source || '#' || d.id END
21
+ FROM (
22
+ SELECT id, row_number() OVER (
23
+ PARTITION BY collection_id, source ORDER BY created_at DESC, id DESC
24
+ ) AS rn
25
+ FROM stackbone_platform.rag_documents
26
+ WHERE document_key IS NULL
27
+ ) ranked
28
+ WHERE d.id = ranked.id;
29
+
30
+ -- Postgres cannot default one column to another. A workspace on an SDK older
31
+ -- than this migration inserts without the key, so the trigger fills it in
32
+ -- instead of failing every ingest on NOT NULL.
33
+ CREATE OR REPLACE FUNCTION stackbone_platform.rag_documents_default_key()
34
+ RETURNS trigger
35
+ LANGUAGE plpgsql
36
+ AS $$
37
+ BEGIN
38
+ NEW.document_key := COALESCE(NEW.document_key, NEW.source);
39
+ RETURN NEW;
40
+ END;
41
+ $$;
42
+
43
+ DROP TRIGGER IF EXISTS rag_documents_default_key ON stackbone_platform.rag_documents;
44
+ CREATE TRIGGER rag_documents_default_key
45
+ BEFORE INSERT ON stackbone_platform.rag_documents
46
+ FOR EACH ROW EXECUTE FUNCTION stackbone_platform.rag_documents_default_key();
47
+
48
+ ALTER TABLE stackbone_platform.rag_documents
49
+ ALTER COLUMN document_key SET NOT NULL;
50
+
51
+ -- At most one published version per document. Non-deferrable, so publishing is
52
+ -- two statements (unpublish the siblings, then publish the target): one swapping
53
+ -- UPDATE can reach the target before the sibling and fail on the check.
54
+ CREATE UNIQUE INDEX IF NOT EXISTS rag_documents_active_key_uq
55
+ ON stackbone_platform.rag_documents (collection_id, document_key)
56
+ WHERE is_active;
57
+
58
+ -- Covers unpublished versions too: a writer that misses the key lock and hands
59
+ -- out a version number twice fails here instead of leaving two v2s behind.
60
+ CREATE UNIQUE INDEX IF NOT EXISTS rag_documents_key_version_uq
61
+ ON stackbone_platform.rag_documents (collection_id, document_key, version);
@@ -0,0 +1,22 @@
1
+ -- 0095_rag_document_version_counters.sql — a version number is never reused (STA3-8 D11)
2
+ -- The last version number each document handed out. Numbering from the rows
3
+ -- left (`max(version) + 1`) gave a deleted draft's number to the next upload,
4
+ -- and the activity log then showed "deleted v3" next to a live, different v3.
5
+ -- A new version takes this counter or the rows' highest version, whichever is
6
+ -- greater, plus one, under the document's key lock. Deleting the whole document
7
+ -- drops its row; deleting one version leaves it alone.
8
+
9
+ CREATE TABLE IF NOT EXISTS stackbone_platform.rag_document_version_counters (
10
+ collection_id uuid NOT NULL REFERENCES stackbone_platform.rag_collections (id) ON DELETE CASCADE,
11
+ document_key text NOT NULL,
12
+ last_version integer NOT NULL,
13
+ PRIMARY KEY (collection_id, document_key)
14
+ );
15
+
16
+ -- A document from before this starts from the highest version it still holds:
17
+ -- a number deleted before 0095 cannot be told apart from one never handed out.
18
+ INSERT INTO stackbone_platform.rag_document_version_counters (collection_id, document_key, last_version)
19
+ SELECT collection_id, document_key, max(version)
20
+ FROM stackbone_platform.rag_documents
21
+ GROUP BY collection_id, document_key
22
+ ON CONFLICT (collection_id, document_key) DO NOTHING;
@@ -0,0 +1,29 @@
1
+ -- 0096_trigger_subscriptions_target_kind.sql — a link's destination carries a kind (PLA-967)
2
+ -- A connector trigger is about to start a deep agent as well as a workflow (PLA-951),
3
+ -- and `workflow_name` (0021) gave the destination a name and no kind. The column
4
+ -- becomes `target_name` beside a `target_kind`, the same pair `eval_suites` uses
5
+ -- (0038). Every link that existed before reads as a workflow link through the default.
6
+ --
7
+ -- No CHECK on `target_kind`, as `eval_suites.target_kind` (0038) and `runs.entity_kind`
8
+ -- (0037): a box running an older build must be able to store a kind it cannot run
9
+ -- rather than reject the write. The register endpoint refuses what it cannot start.
10
+ --
11
+ -- `RENAME COLUMN` has no `IF EXISTS`, and a DBA who applies the platform SQL by hand
12
+ -- (`STACKBONE_SKIP_MIGRATIONS=1`) can run it twice, so the rename is guarded on the
13
+ -- old column still being there.
14
+
15
+ DO $$
16
+ BEGIN
17
+ IF EXISTS (
18
+ SELECT 1 FROM information_schema.columns
19
+ WHERE table_schema = 'stackbone_platform'
20
+ AND table_name = 'trigger_subscriptions'
21
+ AND column_name = 'workflow_name'
22
+ ) THEN
23
+ ALTER TABLE stackbone_platform.trigger_subscriptions
24
+ RENAME COLUMN workflow_name TO target_name;
25
+ END IF;
26
+ END $$;
27
+
28
+ ALTER TABLE stackbone_platform.trigger_subscriptions
29
+ ADD COLUMN IF NOT EXISTS target_kind text NOT NULL DEFAULT 'workflow';
@@ -0,0 +1,23 @@
1
+ -- 0098_model_selections_provider_id.sql — the provider an operator picked next
2
+ -- to the model, for a box that runs more than one (STA3-17, decision D10).
3
+ --
4
+ -- Two providers may serve the same model id. A pick that names only the id is
5
+ -- ambiguous there, so the pick also names the provider that serves it. The SDK
6
+ -- composes `<providerId>::<modelId>` when it reads a pick; this column is where
7
+ -- the provider half lives.
8
+ --
9
+ -- Its own column, not a `b::gpt-4o` string in `model_id`: every box reader of
10
+ -- `model_id` would then have to split it (D10).
11
+ --
12
+ -- NULLABLE, with no default and no backfill. Every pick saved before this file
13
+ -- named no provider, and "no provider" keeps today's resolution: the box sends
14
+ -- the id to the one provider that serves it, or refuses it as ambiguous when two
15
+ -- do. Free text with NO CHECK and no foreign key (providers live in system
16
+ -- secrets, not a table), for the same reason 0073 has none on `model_id`: the
17
+ -- provider may be removed while a pick still names it, and that fails at run
18
+ -- time, not at write.
19
+ --
20
+ -- The PRIMARY KEY stays (owner_kind, owner_name, key): one pick per key, whatever
21
+ -- provider serves it.
22
+ ALTER TABLE stackbone_platform.model_selections
23
+ ADD COLUMN IF NOT EXISTS provider_id text;
@@ -0,0 +1,35 @@
1
+ -- 0099_memory.sql — per-person long-term facts and call history (FDE-68)
2
+ -- Memory is platform-owned box state: it follows RAG into the
3
+ -- stackbone_platform schema so every deployed workspace receives it.
4
+
5
+ CREATE TABLE IF NOT EXISTS stackbone_platform.memory_calls (
6
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
7
+ user_id text NOT NULL,
8
+ started_at timestamptz NOT NULL,
9
+ duration_seconds integer CHECK (duration_seconds >= 0),
10
+ outcome text CHECK (outcome IS NULL OR char_length(outcome) BETWEEN 1 AND 100),
11
+ summary text NOT NULL CHECK (char_length(summary) BETWEEN 1 AND 600),
12
+ created_at timestamptz NOT NULL DEFAULT now()
13
+ );
14
+
15
+ CREATE INDEX IF NOT EXISTS memory_calls_user_started_idx
16
+ ON stackbone_platform.memory_calls (user_id, started_at DESC, created_at DESC);
17
+
18
+ CREATE TABLE IF NOT EXISTS stackbone_platform.memory_facts (
19
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
20
+ user_id text NOT NULL,
21
+ content text NOT NULL CHECK (char_length(content) BETWEEN 1 AND 500),
22
+ insertion_order bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
23
+ created_call_id uuid REFERENCES stackbone_platform.memory_calls(id) ON DELETE SET NULL,
24
+ updated_call_id uuid REFERENCES stackbone_platform.memory_calls(id) ON DELETE SET NULL,
25
+ created_at timestamptz NOT NULL DEFAULT now(),
26
+ updated_at timestamptz NOT NULL DEFAULT now()
27
+ );
28
+
29
+ CREATE INDEX IF NOT EXISTS memory_facts_user_idx
30
+ ON stackbone_platform.memory_facts (user_id);
31
+
32
+ CREATE TABLE IF NOT EXISTS stackbone_platform.memory_user_revisions (
33
+ user_id text PRIMARY KEY,
34
+ revision bigint NOT NULL DEFAULT 0 CHECK (revision >= 0)
35
+ );
@@ -0,0 +1,9 @@
1
+ -- A memory record keeps the run that owns the full transcript without copying
2
+ -- transcript content into memory itself.
3
+
4
+ ALTER TABLE stackbone_platform.memory_calls
5
+ ADD COLUMN IF NOT EXISTS run_id uuid
6
+ REFERENCES stackbone_platform.runs(id) ON DELETE SET NULL;
7
+
8
+ CREATE INDEX IF NOT EXISTS memory_calls_run_idx
9
+ ON stackbone_platform.memory_calls (run_id);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackbone/cli",
3
- "version": "0.6.2",
3
+ "version": "0.6.3",
4
4
  "license": "UNLICENSED",
5
5
  "type": "module",
6
6
  "bin": {
Binary file
Binary file