@danypops/papyrus 0.11.1 → 0.11.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danypops/papyrus",
3
- "version": "0.11.1",
3
+ "version": "0.11.2",
4
4
  "description": "Daemon-backed graph artifacts, evidence-bearing tasks, rules, skills, and native TUI workflows for Pi",
5
5
  "type": "module",
6
6
  "keywords": ["pi-package"],
package/src/constants.ts CHANGED
@@ -142,6 +142,22 @@ export const SEED_STATUSES = [
142
142
  { name: "deprecated", kind: "skill" },
143
143
  ] as const;
144
144
 
145
+ /**
146
+ * The initial status a newly created artifact of a kind gets when no caller-supplied
147
+ * status is given. This must be an explicit, named mapping — never derived from row order
148
+ * in the `statuses` table (SEED_STATUSES' listed order, or a migration's insertion order,
149
+ * is not a semantic guarantee; a migrated database can freely have a different physical
150
+ * row order for the same logical status set). Deriving "the default" from "whichever row
151
+ * happens to be first by rowid" was the root cause of a real production defect where
152
+ * migrated databases created new Tasks as done instead of todo.
153
+ */
154
+ export const DEFAULT_STATUS_BY_KIND: Readonly<Record<string, string>> = {
155
+ doc: "draft",
156
+ task: "todo",
157
+ rule: "active",
158
+ skill: "active",
159
+ };
160
+
145
161
  /**
146
162
  * Universal relation names — any kind can link to any kind.
147
163
  *
package/src/ops.ts CHANGED
@@ -6,6 +6,7 @@ import { createRequire } from "node:module";
6
6
  import { exec } from "node:child_process";
7
7
  import type { Db } from "./db.ts";
8
8
  import { inTransaction } from "./db.ts";
9
+ import { DEFAULT_STATUS_BY_KIND } from "./constants.ts";
9
10
  import type { Artifact, ArtifactQuery, CreateArtifactInput, UpdateArtifactInput } from "./domain/artifact.ts";
10
11
  import type { Gate, GateResult, GateRunOptions } from "./domain/gate.ts";
11
12
  export type { Artifact } from "./domain/artifact.ts";
@@ -102,9 +103,13 @@ function slugify(s: string): string {
102
103
  }
103
104
 
104
105
  function defaultStatusFor(db: Db, kind: string): string {
105
- // First-inserted status per kind (seed order defines the default)
106
- const row = db.prepare("SELECT name FROM statuses WHERE kind = ? ORDER BY rowid LIMIT 1").get(kind) as { name: string } | null;
107
- return row?.name ?? "draft";
106
+ // Explicit per-kind mapping, never row order -- see DEFAULT_STATUS_BY_KIND's doc comment
107
+ // for the production defect this replaced (row order is not a semantic guarantee).
108
+ const candidate = DEFAULT_STATUS_BY_KIND[kind];
109
+ if (candidate === undefined) throw new Error(`no default status is configured for kind "${kind}"`);
110
+ const exists = db.prepare("SELECT 1 FROM statuses WHERE kind = ? AND name = ?").get(kind, candidate);
111
+ if (!exists) throw new Error(`configured default status "${candidate}" for kind "${kind}" is not a registered status`);
112
+ return candidate;
108
113
  }
109
114
 
110
115
  function rowToArtifact(row: Record<string, unknown>): Artifact {